mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
845 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03cb26cc70 | ||
|
|
3e9a39ca13 | ||
|
|
c503118d16 | ||
|
|
0788600702 | ||
|
|
477bc17f18 | ||
|
|
96be2dbeff | ||
|
|
5a335fc408 | ||
|
|
a31ffef085 | ||
|
|
87990f9e67 | ||
|
|
fb185fa839 | ||
|
|
462082c52f | ||
|
|
c821e96e2a | ||
|
|
5aa9abca5e | ||
|
|
6329219749 | ||
|
|
63eeed29cc | ||
|
|
0895bcb5dd | ||
|
|
0541f5aa54 | ||
|
|
51f51ea17d | ||
|
|
9cf67d133f | ||
|
|
ad354700c9 | ||
|
|
28a5f9dc19 | ||
|
|
5c1207c198 | ||
|
|
f61f78eac6 | ||
|
|
efb2447801 | ||
|
|
da084fade7 | ||
|
|
64941bfcad | ||
|
|
9e247c4e06 | ||
|
|
a22bc3559f | ||
|
|
840a9a97ff | ||
|
|
24f0e42e6a | ||
|
|
98b956886e | ||
|
|
6e6ef8348b | ||
|
|
291f1cef29 | ||
|
|
07cc73bddc | ||
|
|
710520e9a1 | ||
|
|
90309b5106 | ||
|
|
2294c2d2c9 | ||
|
|
48c4885b47 | ||
|
|
134e64c612 | ||
|
|
6ea85ca86c | ||
|
|
4579afa091 | ||
|
|
7e599b4450 | ||
|
|
477d924528 | ||
|
|
4f2469e054 | ||
|
|
8d785bbd5f | ||
|
|
04f2ccd16d | ||
|
|
02265bb589 | ||
|
|
40b38a4222 | ||
|
|
ca3b39c0be | ||
|
|
ee1ba56cfe | ||
|
|
3957d1ee27 | ||
|
|
536323d9fb | ||
|
|
401e1d2489 | ||
|
|
e0bdfe826e | ||
|
|
df2570a472 | ||
|
|
9ab648b8f7 | ||
|
|
03398532dc | ||
|
|
abccf766ce | ||
|
|
5c3ce9dd00 | ||
|
|
9a7d9c9465 | ||
|
|
0d53ba4395 | ||
|
|
babefbd9bf | ||
|
|
63c5d09537 | ||
|
|
8cbb6d0faf | ||
|
|
efba48dddb | ||
|
|
34412c9b41 | ||
|
|
18dc916380 | ||
|
|
9023444ad3 | ||
|
|
f4a956a60a | ||
|
|
432eb5b70a | ||
|
|
f0dff8b58c | ||
|
|
34d1f435f4 | ||
|
|
b8e1cf2d4e | ||
|
|
f3dc245c4f | ||
|
|
911a2d61d7 | ||
|
|
21160f7490 | ||
|
|
bb2d04b39d | ||
|
|
bde4f735ab | ||
|
|
6ed23bb3ab | ||
|
|
5edd812792 | ||
|
|
f7277140bb | ||
|
|
b50cbe5064 | ||
|
|
df71aaee3f | ||
|
|
e514b124ed | ||
|
|
81173bf4ab | ||
|
|
6cbdb9aa47 | ||
|
|
991f3bb5da | ||
|
|
6adebbc6a0 | ||
|
|
0e21c32c0b | ||
|
|
f8e2008e0e | ||
|
|
08604ac6a8 | ||
|
|
0f99d3f7cc | ||
|
|
13a61c4784 | ||
|
|
fcd605a227 | ||
|
|
30b69549e1 | ||
|
|
16fd506bb8 | ||
|
|
ba8cd4d55a | ||
|
|
06cd31cb2e | ||
|
|
7e941c9ecd | ||
|
|
df794993f8 | ||
|
|
7d75b1f938 | ||
|
|
841bf89639 | ||
|
|
86acdb9a12 | ||
|
|
4b328631b9 | ||
|
|
e518098688 | ||
|
|
be6f91d0d1 | ||
|
|
6341736286 | ||
|
|
e0419e988b | ||
|
|
f0a3e7c4a1 | ||
|
|
6539d4a873 | ||
|
|
cfbebd8852 | ||
|
|
42148063ec | ||
|
|
2baf5ab80d | ||
|
|
97affbebfe | ||
|
|
7b1d5c1ad9 | ||
|
|
a4cca5f60a | ||
|
|
689415e5ff | ||
|
|
09829c44b2 | ||
|
|
2dbb376175 | ||
|
|
9e915a0284 | ||
|
|
49268bff3b | ||
|
|
ca426cfdf4 | ||
|
|
7773812f58 | ||
|
|
d0820cc026 | ||
|
|
948f808da6 | ||
|
|
aa638ead42 | ||
|
|
1152b1a147 | ||
|
|
d47f733e51 | ||
|
|
c1af13efeb | ||
|
|
a86f4369a7 | ||
|
|
320fea0eaf | ||
|
|
00716f90a0 | ||
|
|
5902dfa403 | ||
|
|
d9c1b4396e | ||
|
|
02adc4061a | ||
|
|
3f396d87ef | ||
|
|
d0218e94a3 | ||
|
|
0e58741a09 | ||
|
|
0f1396acef | ||
|
|
65774e8a78 | ||
|
|
2eb26d4de4 | ||
|
|
8dbc84318c | ||
|
|
c62db97a81 | ||
|
|
4dc2a39f3a | ||
|
|
c3a0685e2d | ||
|
|
5a68d5b6a7 | ||
|
|
ead86bb1e4 | ||
|
|
22dd49d0b5 | ||
|
|
642b890b0f | ||
|
|
0223cf8c2b | ||
|
|
ed396b4d58 | ||
|
|
81c2294c11 | ||
|
|
5200bf3c59 | ||
|
|
65a38e6f70 | ||
|
|
d6e6901957 | ||
|
|
8f3720089f | ||
|
|
cc2d03d29f | ||
|
|
f5b37e3444 | ||
|
|
009f4d23d0 | ||
|
|
5ade9d4610 | ||
|
|
c98949bdd3 | ||
|
|
2f58fb8307 | ||
|
|
e817aa6824 | ||
|
|
559b70cf0a | ||
|
|
dae7608b1e | ||
|
|
6466ad2112 | ||
|
|
9f1ac24bed | ||
|
|
49e70c6203 | ||
|
|
67a5882415 | ||
|
|
8142c823ed | ||
|
|
531f4ff6b0 | ||
|
|
e52129efb4 | ||
|
|
0c5c470247 | ||
|
|
5b96dd5f70 | ||
|
|
d8d790bb7b | ||
|
|
1c5562b656 | ||
|
|
eae4f1d249 | ||
|
|
ea0bc240fd | ||
|
|
42937c9754 | ||
|
|
d7b0b9842d | ||
|
|
3cb4513077 | ||
|
|
9de3ace8c7 | ||
|
|
b77fdeba2d | ||
|
|
8423c0897a | ||
|
|
6315f16ca7 | ||
|
|
4cf89d373d | ||
|
|
f6b9445be7 | ||
|
|
77ee331be0 | ||
|
|
bba5ef3a59 | ||
|
|
374ac77c63 | ||
|
|
ffd816ebfa | ||
|
|
d04e0dc9e1 | ||
|
|
7da5b531a0 | ||
|
|
109c7f731d | ||
|
|
812a6b97d3 | ||
|
|
ad307912ab | ||
|
|
94f2fc3308 | ||
|
|
f190cb05b5 | ||
|
|
299eb28453 | ||
|
|
d2f1749edb | ||
|
|
378dd81204 | ||
|
|
056f5481ac | ||
|
|
4ebf01b344 | ||
|
|
b5755e9c33 | ||
|
|
c49e31a6d9 | ||
|
|
ebc6391bea | ||
|
|
9de755334d | ||
|
|
ed1e7817cc | ||
|
|
5c06b7f862 | ||
|
|
0e4e27ea9d | ||
|
|
1ef6996207 | ||
|
|
d17fa784bc | ||
|
|
793d4e7b8d | ||
|
|
ddf6caf955 | ||
|
|
af621be509 | ||
|
|
4970e2f703 | ||
|
|
59958f0a61 | ||
|
|
3f09eff6c5 | ||
|
|
956a84f73a | ||
|
|
269d660b0a | ||
|
|
be78c6da49 | ||
|
|
f7378bc092 | ||
|
|
1f6cbad01a | ||
|
|
4d60ef59bc | ||
|
|
7fabca3548 | ||
|
|
27247164b5 | ||
|
|
08a68f2f0f | ||
|
|
f21cd31b94 | ||
|
|
a94887e051 | ||
|
|
3edc2bd6ab | ||
|
|
968c90a6ce | ||
|
|
7485a0c1f7 | ||
|
|
cbc0ac3267 | ||
|
|
7fdb5ae4c4 | ||
|
|
0d748c5c2b | ||
|
|
6692fa60e9 | ||
|
|
f03602a4c9 | ||
|
|
bc75c1b4e1 | ||
|
|
d054dddcb3 | ||
|
|
b04eb4770b | ||
|
|
503887843f | ||
|
|
7c9e8b103f | ||
|
|
545ea9ea33 | ||
|
|
ebd83699dc | ||
|
|
bdb92f9ace | ||
|
|
2061f0d8b4 | ||
|
|
e8e4f43d52 | ||
|
|
296ada9441 | ||
|
|
287897d702 | ||
|
|
16614447eb | ||
|
|
96479dabe6 | ||
|
|
88c1683029 | ||
|
|
8d3e4f989d | ||
|
|
53481aabe5 | ||
|
|
4dd16c2686 | ||
|
|
8627656f55 | ||
|
|
6e5338d28f | ||
|
|
014844d0da | ||
|
|
779cbfe8bd | ||
|
|
700df9eb16 | ||
|
|
c33c38bd96 | ||
|
|
da7f31d4bc | ||
|
|
759a0be6cd | ||
|
|
b17bafb530 | ||
|
|
7ca691fc2f | ||
|
|
e619fe6b01 | ||
|
|
613f254908 | ||
|
|
97ab767a2f | ||
|
|
7f032c8bb3 | ||
|
|
99cb8dc0a4 | ||
|
|
710394b872 | ||
|
|
4f4c3d3ebe | ||
|
|
f1e19d6c96 | ||
|
|
80c9dc2e0c | ||
|
|
c706ca1522 | ||
|
|
0a1d6e1cb3 | ||
|
|
c8fa8092c5 | ||
|
|
26d8caedb6 | ||
|
|
572aaa7231 | ||
|
|
04cb35e3f4 | ||
|
|
9bf100c91c | ||
|
|
bfbca596ea | ||
|
|
6e6eb5b55e | ||
|
|
a5b053f81d | ||
|
|
2f395e60a0 | ||
|
|
d46c61c5d4 | ||
|
|
0cd3325a06 | ||
|
|
14eddf72b4 | ||
|
|
411e2b43ba | ||
|
|
3bf46d4c80 | ||
|
|
3447e226d9 | ||
|
|
ae87455242 | ||
|
|
62ccccc303 | ||
|
|
671b49ba23 | ||
|
|
e5b7fff990 | ||
|
|
da2b3c92d3 | ||
|
|
84fc807bc6 | ||
|
|
56d7c04226 | ||
|
|
6e6b2b84fe | ||
|
|
705f27151d | ||
|
|
bc053f1b13 | ||
|
|
91739be120 | ||
|
|
ae6cf4c922 | ||
|
|
7cb61d3960 | ||
|
|
880a57c920 | ||
|
|
07bc292fa5 | ||
|
|
6411c3ae42 | ||
|
|
066fc6c0d1 | ||
|
|
77ae3de4f3 | ||
|
|
599275b610 | ||
|
|
b8c1b2e174 | ||
|
|
b5440e2d0a | ||
|
|
e90c6f3c44 | ||
|
|
dbc09ad0ef | ||
|
|
d9e1c2cd22 | ||
|
|
aaa5e2e9ea | ||
|
|
2987476e0a | ||
|
|
3dbae423bb | ||
|
|
68fa97eb0e | ||
|
|
789a39a2d5 | ||
|
|
99da1dff90 | ||
|
|
1038a07dd6 | ||
|
|
839f3a74ff | ||
|
|
652b60320d | ||
|
|
275cb5c92f | ||
|
|
8676759c38 | ||
|
|
76087f1235 | ||
|
|
db6f894e5e | ||
|
|
31c3685117 | ||
|
|
8dfb79e077 | ||
|
|
e99b64097a | ||
|
|
92b3764553 | ||
|
|
de91c30cd3 | ||
|
|
aa4efdd7ec | ||
|
|
ac01ceddbb | ||
|
|
097cff2d85 | ||
|
|
bac0d1e689 | ||
|
|
0c73ff8129 | ||
|
|
db42caef42 | ||
|
|
db9f9b7820 | ||
|
|
655d9cdb0d | ||
|
|
74ab468bb4 | ||
|
|
1651cc3953 | ||
|
|
b4d04bdff6 | ||
|
|
3c8cf0988d | ||
|
|
75e6762897 | ||
|
|
969b5ee4e8 | ||
|
|
12f4cd951a | ||
|
|
aeeae661fc | ||
|
|
362942fa6e | ||
|
|
b5992fe90b | ||
|
|
2c6f528c23 | ||
|
|
f322e924ad | ||
|
|
4888dd2e01 | ||
|
|
8f096b8be5 | ||
|
|
a831aa214c | ||
|
|
e155e3690c | ||
|
|
8e48fc445f | ||
|
|
8946a09afd | ||
|
|
9efaff73fa | ||
|
|
a33e8342ee | ||
|
|
f5b53ae4be | ||
|
|
08e47ebd4b | ||
|
|
c4b3ed7223 | ||
|
|
bce76271c5 | ||
|
|
2e2b1e4230 | ||
|
|
2cb7ec04a4 | ||
|
|
c06a827cbb | ||
|
|
f648f2856d | ||
|
|
ac8e4b9cb6 | ||
|
|
45d43f9441 | ||
|
|
91747094ae | ||
|
|
90810a9691 | ||
|
|
16af54e583 | ||
|
|
4ff972c329 | ||
|
|
3508440684 | ||
|
|
0fa1e2c241 | ||
|
|
694da82691 | ||
|
|
0ea6406d5b | ||
|
|
148a9ed5ce | ||
|
|
0a7bcc9a7d | ||
|
|
4587cc7ffa | ||
|
|
f444ef6ccf | ||
|
|
aa5ae7616e | ||
|
|
cf25bd74b0 | ||
|
|
06396593c9 | ||
|
|
f5a52eda2b | ||
|
|
a34893a402 | ||
|
|
57ed006992 | ||
|
|
6120d0fb1b | ||
|
|
6475052692 | ||
|
|
980e03fa0a | ||
|
|
5e3c62dc6a | ||
|
|
03108fb88b | ||
|
|
3202e8ac7a | ||
|
|
d5d4053795 | ||
|
|
0042f1de75 | ||
|
|
2dca959084 | ||
|
|
52800a871a | ||
|
|
29e668c302 | ||
|
|
cafab9e981 | ||
|
|
611d13ed1b | ||
|
|
8aabb9012d | ||
|
|
bdb0ad98c4 | ||
|
|
45fb9187c5 | ||
|
|
7f73eebbdb | ||
|
|
9fd187c9fe | ||
|
|
b851887738 | ||
|
|
1dd2451895 | ||
|
|
fb763c4043 | ||
|
|
dd82064aba | ||
|
|
e12179a3e6 | ||
|
|
3ede2dc53a | ||
|
|
032b8e4c1d | ||
|
|
204f05fdf1 | ||
|
|
7ee9ecaaf4 | ||
|
|
2034fc2d84 | ||
|
|
503e047c3a | ||
|
|
605fa8307b | ||
|
|
925c862f98 | ||
|
|
2bd90d9263 | ||
|
|
fb79e75afc | ||
|
|
9ac06bf613 | ||
|
|
e91638e6c5 | ||
|
|
99f39291fd | ||
|
|
a20b383be8 | ||
|
|
bb8cd9eb24 | ||
|
|
75e22ff34e | ||
|
|
8d7c4ea334 | ||
|
|
12657ef59f | ||
|
|
f70c9937fb | ||
|
|
c9ea235a0e | ||
|
|
2aabf58bf5 | ||
|
|
d34f31fe7b | ||
|
|
e73cb1ff80 | ||
|
|
9fab9bc645 | ||
|
|
9fe6ece262 | ||
|
|
c27be842e4 | ||
|
|
9731be1126 | ||
|
|
eedfb36c66 | ||
|
|
3901a1dd7d | ||
|
|
76d9bf2792 | ||
|
|
0ab22fea2c | ||
|
|
211786ab42 | ||
|
|
8049666eff | ||
|
|
50a4c16d34 | ||
|
|
4bb12a2d8d | ||
|
|
92082ad439 | ||
|
|
0f727127d5 | ||
|
|
11d4d57c06 | ||
|
|
be421dccea | ||
|
|
1f029dd2dc | ||
|
|
c396baca46 | ||
|
|
ef7d889a79 | ||
|
|
1a4b5ecc75 | ||
|
|
f3c3870159 | ||
|
|
1e5e02801f | ||
|
|
19aeac40c9 | ||
|
|
3fe362e3bd | ||
|
|
2381f32ae0 | ||
|
|
9b60fcdc39 | ||
|
|
86c760a0d9 | ||
|
|
f77d2c2d16 | ||
|
|
f5704d32b3 | ||
|
|
ed5459f268 | ||
|
|
e0a8da2f46 | ||
|
|
c7e9056c1e | ||
|
|
a14aee9213 | ||
|
|
c5a656c9dc | ||
|
|
e483cde1bb | ||
|
|
973eb16ae9 | ||
|
|
4885dc0ed4 | ||
|
|
652e7b9b8a | ||
|
|
404316eea1 | ||
|
|
4ab88a8173 | ||
|
|
cf7e0cdf95 | ||
|
|
709f7fe607 | ||
|
|
d38787c578 | ||
|
|
1ae7deb393 | ||
|
|
6ae2597d19 | ||
|
|
3485e89080 | ||
|
|
1324aa451f | ||
|
|
dcb6d02b98 | ||
|
|
cde2c4449b | ||
|
|
d8adf6a6d7 | ||
|
|
14041e97e7 | ||
|
|
32506dadfc | ||
|
|
8b500d7873 | ||
|
|
7310ab6fa7 | ||
|
|
d2d1248342 | ||
|
|
2073007214 | ||
|
|
351058ab0f | ||
|
|
28c6652e57 | ||
|
|
4e414f0d84 | ||
|
|
284d5e011a | ||
|
|
8ecf736982 | ||
|
|
1218765df2 | ||
|
|
9880a2aba9 | ||
|
|
82fce5a4e2 | ||
|
|
78dcb1862c | ||
|
|
5da7c63127 | ||
|
|
f5137e84bb | ||
|
|
f1b6657833 | ||
|
|
72b9e920a3 | ||
|
|
1af11a7c92 | ||
|
|
281b99af0a | ||
|
|
0acabcdd27 | ||
|
|
85f43c7026 | ||
|
|
5788d36583 | ||
|
|
41895d7df1 | ||
|
|
85d155543e | ||
|
|
3430e20f64 | ||
|
|
7d853241ad | ||
|
|
fc8bfe07f3 | ||
|
|
fa34fae55f | ||
|
|
f427d94422 | ||
|
|
345c922cb9 | ||
|
|
b38beb606f | ||
|
|
c2f6dfce88 | ||
|
|
9d775a140f | ||
|
|
70d80cc064 | ||
|
|
f493d434ba | ||
|
|
2500d0aaac | ||
|
|
279c4f91a3 | ||
|
|
24cf15d2b2 | ||
|
|
c2ae380503 | ||
|
|
07bad79fd7 | ||
|
|
0d2e38db25 | ||
|
|
38468d3d9b | ||
|
|
e4c3401a33 | ||
|
|
7a3e6caca4 | ||
|
|
ad223f2b50 | ||
|
|
84445aae93 | ||
|
|
143471b739 | ||
|
|
e1822cfdad | ||
|
|
ae6f27277b | ||
|
|
4dbb33a1fa | ||
|
|
c56af8daeb | ||
|
|
d756844096 | ||
|
|
9d3f656572 | ||
|
|
f59665e8aa | ||
|
|
9482ded365 | ||
|
|
e4ea60dd96 | ||
|
|
44dd056647 | ||
|
|
091c9d9f93 | ||
|
|
d8c3912a18 | ||
|
|
1a28f64d5d | ||
|
|
8f6cbf849a | ||
|
|
5a0d01ca13 | ||
|
|
38c0d328c2 | ||
|
|
134bcd84e6 | ||
|
|
b6f6772df4 | ||
|
|
cf1983c70e | ||
|
|
32a39f9b95 | ||
|
|
b2589d4f3f | ||
|
|
5c0bb38a65 | ||
|
|
b9971302be | ||
|
|
fda8a04ec2 | ||
|
|
34e35c9ae0 | ||
|
|
95352db1e5 | ||
|
|
a551398135 | ||
|
|
962dec2913 | ||
|
|
7df88793a0 | ||
|
|
61cfe3e72d | ||
|
|
260cebb8cf | ||
|
|
bcfe546ea2 | ||
|
|
f668603694 | ||
|
|
1e3fd2f6a5 | ||
|
|
d5682cd306 | ||
|
|
21b88c3ea0 | ||
|
|
e25a47b32c | ||
|
|
9fb1c46cd1 | ||
|
|
deb3cbd501 | ||
|
|
9701fde209 | ||
|
|
5c4e1726f8 | ||
|
|
cee01edb97 | ||
|
|
91549ddd71 | ||
|
|
a6878accd5 | ||
|
|
7b3fc72aea | ||
|
|
7e81b0ded7 | ||
|
|
156ea392a2 | ||
|
|
e8313e3e69 | ||
|
|
43d2e09e1f | ||
|
|
d1390906c8 | ||
|
|
93f5f6ed45 | ||
|
|
43bd0e53c4 | ||
|
|
17a167dbb6 | ||
|
|
9c3a7ddcc9 | ||
|
|
11750efb78 | ||
|
|
dea5776fb9 | ||
|
|
7909f7a8c9 | ||
|
|
23150f8647 | ||
|
|
d59fe4eee7 | ||
|
|
fa0fdc6500 | ||
|
|
cec884e863 | ||
|
|
6444e29b04 | ||
|
|
a002093a55 | ||
|
|
43f0478fa8 | ||
|
|
664431ff14 | ||
|
|
1e97d1413e | ||
|
|
e8e21aeb7e | ||
|
|
a40a70ad86 | ||
|
|
f0b7412495 | ||
|
|
37557c8b35 | ||
|
|
9756fc2dd0 | ||
|
|
35dd1523c9 | ||
|
|
a122da4bef | ||
|
|
dae0b2ef72 | ||
|
|
35d7721683 | ||
|
|
5450de0f5e | ||
|
|
0361df4ae0 | ||
|
|
36daf9c0b0 | ||
|
|
64c5e3d8cb | ||
|
|
4bfd51cc42 | ||
|
|
1c2587f8cb | ||
|
|
5e0bbf9c3b | ||
|
|
793327cff8 | ||
|
|
cc08c44cb9 | ||
|
|
5a72225856 | ||
|
|
f7c215d92d | ||
|
|
59ae01ff71 | ||
|
|
c61104df47 | ||
|
|
4c92f89f0f | ||
|
|
93bd88e681 | ||
|
|
d42ef186a1 | ||
|
|
ee528ae808 | ||
|
|
befd342da4 | ||
|
|
eff78f746a | ||
|
|
99e608c240 | ||
|
|
f994665610 | ||
|
|
ba59c62044 | ||
|
|
ce1bd9c7ff | ||
|
|
0374c3dc71 | ||
|
|
aa94e9b8f3 | ||
|
|
9b1ba14f2d | ||
|
|
60e6609011 | ||
|
|
defc5dcd9c | ||
|
|
66e00ce710 | ||
|
|
59d01712ad | ||
|
|
afea6a10a2 | ||
|
|
9b1cb9621f | ||
|
|
72fb687e3f | ||
|
|
904b1ea599 | ||
|
|
fcf92d86b5 | ||
|
|
f98b16f308 | ||
|
|
1722708612 | ||
|
|
c063bb8d30 | ||
|
|
e1af17d93a | ||
|
|
44d026bfb1 | ||
|
|
a1285ea57d | ||
|
|
23929f41a7 | ||
|
|
1dfd60b609 | ||
|
|
5208ff86c8 | ||
|
|
dcfd6ffd4b | ||
|
|
08258d5dad | ||
|
|
4115fa8a87 | ||
|
|
1379abb94b | ||
|
|
c81e808e98 | ||
|
|
17badfbff5 | ||
|
|
13503061e6 | ||
|
|
e1ff142392 | ||
|
|
e0830640b7 | ||
|
|
66a31c19e8 | ||
|
|
48bf141364 | ||
|
|
b629727901 | ||
|
|
ae9359c859 | ||
|
|
791ca3388f | ||
|
|
623755cc70 | ||
|
|
7ad68d499a | ||
|
|
86339b4cb4 | ||
|
|
e7d4eae527 | ||
|
|
0ef999978c | ||
|
|
c8b3041e62 | ||
|
|
b6801e355e | ||
|
|
66d5163ee7 | ||
|
|
ea19bb026f | ||
|
|
1bbe48b24c | ||
|
|
4bc8635c59 | ||
|
|
82bc07cce4 | ||
|
|
7543e00a07 | ||
|
|
df0907845a | ||
|
|
669d9a8ae8 | ||
|
|
d378da64ee | ||
|
|
b1cfdc761a | ||
|
|
0f21781fbe | ||
|
|
ca97385da5 | ||
|
|
f7dace151c | ||
|
|
861d902c21 | ||
|
|
f787139805 | ||
|
|
338beebc83 | ||
|
|
4de04e84b5 | ||
|
|
62fa595ac6 | ||
|
|
ab16af2941 | ||
|
|
9fd5ea4951 | ||
|
|
d6d1074ca3 | ||
|
|
4cff045cc6 | ||
|
|
0ae9610e09 | ||
|
|
0274b7923b | ||
|
|
edd59a0f97 | ||
|
|
c718225c5f | ||
|
|
360f0639f4 | ||
|
|
8fe5a43d9d | ||
|
|
f1c7f0c0b8 | ||
|
|
50b0e58c88 | ||
|
|
ba17758ac0 | ||
|
|
5c128023da | ||
|
|
e1fccc3615 | ||
|
|
5f7935f8e0 | ||
|
|
e33ccb0c95 | ||
|
|
1b76c0a183 | ||
|
|
1e65bda0a7 | ||
|
|
cd1684bd2e | ||
|
|
03361a6c29 | ||
|
|
8845326aa2 | ||
|
|
3d6b5459ef | ||
|
|
ac118c9924 | ||
|
|
bdb7bcdbe3 | ||
|
|
f979380620 | ||
|
|
0f9afc3017 | ||
|
|
e4e8062dda | ||
|
|
880f62bab8 | ||
|
|
0213177027 | ||
|
|
d642060378 | ||
|
|
1d852e9ce5 | ||
|
|
a518651402 | ||
|
|
348a1bc30e | ||
|
|
07cdb37dda | ||
|
|
5faf8b756c | ||
|
|
e51ebe045f | ||
|
|
1da0ff395d | ||
|
|
39a1b8f4db | ||
|
|
d5bb0781e4 | ||
|
|
89f0db2173 | ||
|
|
d6696e1bdd | ||
|
|
3d70ce9ad1 | ||
|
|
d06623fb97 | ||
|
|
93ee6f5e62 | ||
|
|
4b5ce23d97 | ||
|
|
50c8335d3d | ||
|
|
1ac1231292 | ||
|
|
d7dc1a9226 | ||
|
|
66fde245b3 | ||
|
|
8ec89bc884 | ||
|
|
64305dab16 | ||
|
|
b55d75ea62 | ||
|
|
f0e618431d | ||
|
|
a2ee681665 | ||
|
|
79b8f8e477 | ||
|
|
5087f633f6 | ||
|
|
ce1765c8af | ||
|
|
cfca2759fc | ||
|
|
4b469b98cf | ||
|
|
e1281b5488 | ||
|
|
fa0d573fef | ||
|
|
d9695a0fd9 | ||
|
|
4d0872df5f | ||
|
|
c204d5404b | ||
|
|
b930b3fb41 | ||
|
|
6af2f57644 | ||
|
|
2dcd36f2f0 | ||
|
|
6958ec4922 | ||
|
|
7b77189c80 | ||
|
|
3dde848c8f | ||
|
|
96aa2441ad | ||
|
|
f34ace7673 | ||
|
|
ede262684d | ||
|
|
7498647e3f | ||
|
|
fa94635282 | ||
|
|
ff36fa99e6 | ||
|
|
3c715daa9d | ||
|
|
73b4668bd9 | ||
|
|
a1a5fe0c93 | ||
|
|
0319a4a09a | ||
|
|
c3eaa41b77 | ||
|
|
ceafd4cee7 | ||
|
|
5870452d25 | ||
|
|
3a3dd0096c | ||
|
|
08bebaae4f | ||
|
|
85e936d4bd | ||
|
|
2cc723f77b | ||
|
|
d1f03bae84 | ||
|
|
ae5e8c47ae | ||
|
|
8cd45e4ade | ||
|
|
c999ea6471 | ||
|
|
64368a195a | ||
|
|
08d6386482 | ||
|
|
5cd4f5daeb | ||
|
|
b3388c343d | ||
|
|
2d25862477 | ||
|
|
ce386ba4c6 | ||
|
|
87e9ce6960 | ||
|
|
5694ac2b1e | ||
|
|
3fa5df4161 | ||
|
|
587a420429 | ||
|
|
c7cabc88b4 | ||
|
|
de21c9ba4f | ||
|
|
3be95dbf93 | ||
|
|
296f2e1182 | ||
|
|
d6c68e4738 | ||
|
|
3e819ac797 | ||
|
|
149b61b213 | ||
|
|
04a449d3f3 | ||
|
|
e93467a96c | ||
|
|
4f8054fa82 | ||
|
|
b51a9e1ef1 | ||
|
|
8746c16593 | ||
|
|
1870eb4295 | ||
|
|
733e636f02 | ||
|
|
e536f88012 | ||
|
|
c865f93c9b | ||
|
|
3e37f59933 | ||
|
|
5cc1e73e52 | ||
|
|
05eb567a7c | ||
|
|
75bf240b83 | ||
|
|
be6a1975d2 | ||
|
|
f27893f822 | ||
|
|
696f6cae47 | ||
|
|
1f7f484f61 | ||
|
|
4e0b5e8a63 | ||
|
|
c159515e91 | ||
|
|
26336363cc | ||
|
|
7c2c4fb3fc | ||
|
|
ad60fe110b | ||
|
|
025f1f9d9b | ||
|
|
555ac68ea5 | ||
|
|
dd539df633 | ||
|
|
c76fd6df21 | ||
|
|
9269e40ba5 | ||
|
|
e1e0ac2ee3 | ||
|
|
08aff4ed5a | ||
|
|
c4c3540a1d | ||
|
|
03d8cdc4f2 | ||
|
|
3cb73b3a45 | ||
|
|
bf0d0d9f9b | ||
|
|
5a7076442c | ||
|
|
a4ce596f7b | ||
|
|
1387ac5571 | ||
|
|
de9eb3e0fa | ||
|
|
dc951af167 | ||
|
|
3dff551399 | ||
|
|
4783fe7402 | ||
|
|
e10413b96c | ||
|
|
317d754946 | ||
|
|
eff1ece368 | ||
|
|
05c7130f06 |
70
.github/workflows/build.yaml
vendored
Normal file
70
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Cloud Hypervisor Actions
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
|
||||
release:
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (stable)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: true
|
||||
prerelease: true
|
||||
- name: Upload cloud-hypervisor
|
||||
id: upload-release-cloud-hypervisor
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/release/cloud-hypervisor
|
||||
asset_name: cloud-hypervisor
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload ch-remote
|
||||
id: upload-release-ch-remote
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/release/ch-remote
|
||||
asset_name: ch-remote
|
||||
asset_content_type: application/octet-stream
|
||||
1
.rustfmt.toml
Normal file
1
.rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
edition = "2018"
|
||||
30
.travis.yml
30
.travis.yml
@@ -1,30 +0,0 @@
|
||||
language: rust
|
||||
|
||||
rust:
|
||||
- stable
|
||||
|
||||
before_script:
|
||||
- rustup component add clippy
|
||||
- rustup component add rustfmt
|
||||
|
||||
script:
|
||||
- cargo rustc --bin cloud-hypervisor -- -D warnings
|
||||
- cargo rustc --bin cloud-hypervisor --no-default-features --features "pci" -- -D warnings
|
||||
- cargo rustc --bin cloud-hypervisor --no-default-features --features "pci,acpi" -- -D warnings
|
||||
- cargo rustc --bin cloud-hypervisor --no-default-features --features "mmio" -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net --no-default-features --features "pci" -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net --no-default-features --features "pci,acpi" -- -D warnings
|
||||
- cargo rustc --bin vhost_user_net --no-default-features --features "mmio" -- -D warnings
|
||||
- cargo test
|
||||
- cargo clippy --all-targets --all-features -- -D warnings
|
||||
- find . -name "*.rs" | xargs rustfmt --check
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key: $GITHUB_OAUTH_TOKEN
|
||||
file: target/release/cloud-hypervisor
|
||||
skip_cleanup: true
|
||||
draft: true
|
||||
on:
|
||||
tags: true
|
||||
@@ -51,7 +51,7 @@ Signed-off-by: Rob Bradford <robert.bradford@intel.com>
|
||||
Cloud Hypervisor uses the “fork-and-pull” development model. Follow these steps if
|
||||
you want to merge your changes to `cloud-hypervisor`:
|
||||
|
||||
1. Fork the [cloud-hypervisor](https://github.com/intel/cloud-hypervisor) project
|
||||
1. Fork the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) project
|
||||
into your github organization.
|
||||
2. 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/)
|
||||
@@ -65,7 +65,7 @@ you want to merge your changes to `cloud-hypervisor`:
|
||||
## Issue tracking
|
||||
|
||||
If you have a problem, please let us know. We recommend using
|
||||
[github issues](https://github.com/intel/cloud-hypervisor/issues/new) for formally
|
||||
[github issues](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new) for formally
|
||||
reporting and documenting them.
|
||||
|
||||
To quickly and informally bring something up to us, you can also reach out on [Slack](https://cloud-hypervisor.slack.com).
|
||||
|
||||
971
Cargo.lock
generated
971
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
45
Cargo.toml
45
Cargo.toml
@@ -1,36 +1,39 @@
|
||||
[package]
|
||||
name = "cloud-hypervisor"
|
||||
version = "0.1.0"
|
||||
version = "0.6.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
default-run = "cloud-hypervisor"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
epoll = "4.1.0"
|
||||
arc-swap = ">=0.4.4"
|
||||
clap = { version = "2.33.0", features=["wrap_help"] }
|
||||
epoll = ">=4.0.1"
|
||||
futures = { version = "0.3.4", features = ["thread-pool"] }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.65"
|
||||
libc = "0.2.68"
|
||||
log = { version = "0.4.8", features = ["std"] }
|
||||
net_gen = { path = "net_gen" }
|
||||
net_util = { path = "net_util" }
|
||||
serde_json = ">=1.0.9"
|
||||
vhost_user_backend = { path = "vhost_user_backend"}
|
||||
vhost_user_block = { path = "vhost_user_block"}
|
||||
vhost_user_fs = { path = "vhost_user_fs"}
|
||||
vhost_user_net = { path = "vhost_user_net"}
|
||||
virtio-bindings = "0.1.0"
|
||||
vmm = { path = "vmm" }
|
||||
vm-device = { path = "vm-device" }
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
vm-memory = "0.1.0"
|
||||
vmm-sys-util = "0.4.0"
|
||||
vm-virtio = { path = "vm-virtio" }
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ssh2 = "0.4.0"
|
||||
ssh2 = "0.8.0"
|
||||
dirs = "2.0.2"
|
||||
credibility = "0.1.3"
|
||||
tempdir= "0.3.7"
|
||||
lazy_static= "1.4.0"
|
||||
|
||||
[dependencies.vhost_rs]
|
||||
path = "vhost_rs"
|
||||
features = ["vhost-user-slave"]
|
||||
|
||||
[features]
|
||||
default = ["acpi", "pci", "cmos"]
|
||||
acpi = ["vmm/acpi"]
|
||||
@@ -43,5 +46,21 @@ integration_tests = []
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"arch",
|
||||
"devices",
|
||||
"qcow",
|
||||
"pci",
|
||||
"vmm",
|
||||
"vm-virtio",
|
||||
"vm-device",
|
||||
"vhost_user_block",
|
||||
"vhost_user_backend",
|
||||
"vhost_user_fs",
|
||||
"vhost_user_net",
|
||||
"vfio",
|
||||
"net_util",
|
||||
"acpi_tables",
|
||||
"arch_gen",
|
||||
"net_gen",
|
||||
"vm-allocator",
|
||||
]
|
||||
|
||||
100
Jenkinsfile
vendored
100
Jenkinsfile
vendored
@@ -1,22 +1,88 @@
|
||||
stage ("Builds") {
|
||||
node ('bionic') {
|
||||
stage ('Checkout') {
|
||||
checkout scm
|
||||
pipeline{
|
||||
agent none
|
||||
stages {
|
||||
stage ('Early checks') {
|
||||
agent { node { label 'master' } }
|
||||
stages {
|
||||
stage ('Check for RFC/WIP builds') {
|
||||
when {
|
||||
changeRequest comparator: 'REGEXP', title: '.*(rfc|RFC|wip|WIP).*'
|
||||
beforeAgent true
|
||||
}
|
||||
steps {
|
||||
error("Failing as this is marked as a WIP or RFC PR.")
|
||||
}
|
||||
}
|
||||
stage ('Cancel older builds') {
|
||||
when { not { branch 'master' } }
|
||||
steps {
|
||||
cancelPreviousBuilds()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Install system packages') {
|
||||
sh "sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential mtools libssl-dev pkg-config"
|
||||
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system libglib2.0-dev libpixman-1-dev libseccomp-dev socat"
|
||||
}
|
||||
stage ('Install Rust') {
|
||||
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y"
|
||||
}
|
||||
stage ('Run unit tests') {
|
||||
sh "scripts/run_unit_tests.sh"
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
sh "sudo mount -t tmpfs tmpfs /tmp"
|
||||
sh "scripts/run_integration_tests.sh"
|
||||
stage ('Build') {
|
||||
failFast true
|
||||
parallel {
|
||||
stage ('Master build') {
|
||||
agent { node { label 'master' } }
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Run Cargo tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --cargo"
|
||||
}
|
||||
}
|
||||
stage ('Run OpenAPI tests') {
|
||||
steps {
|
||||
sh "scripts/run_openapi_tests.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Worker build') {
|
||||
agent { node { label 'bionic' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Run unit tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --unit"
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def cancelPreviousBuilds() {
|
||||
// Check for other instances of this particular build, cancel any that are older than the current one
|
||||
def jobName = env.JOB_NAME
|
||||
def currentBuildNumber = env.BUILD_NUMBER.toInteger()
|
||||
def currentJob = Jenkins.instance.getItemByFullName(jobName)
|
||||
|
||||
// Loop through all instances of this particular job/branch
|
||||
for (def build : currentJob.builds) {
|
||||
if (build.isBuilding() && (build.number.toInteger() < currentBuildNumber)) {
|
||||
echo "Older build still queued. Sending kill signal to build number: ${build.number}"
|
||||
build.doStop()
|
||||
}
|
||||
}
|
||||
}
|
||||
91
README.md
91
README.md
@@ -1,4 +1,4 @@
|
||||
[](https://travis-ci.com/intel/cloud-hypervisor)
|
||||
[](https://travis-ci.com/cloud-hypervisor/cloud-hypervisor)
|
||||
|
||||
1. [What is Cloud Hypervisor?](#1-what-is-cloud-hypervisor)
|
||||
* [Requirements](#requirements)
|
||||
@@ -14,6 +14,7 @@
|
||||
- [Disk image](#disk-image)
|
||||
- [Booting the guest VM](#booting-the-guest-vm)
|
||||
3. [Status](#2-status)
|
||||
* [Device Model](#device-model)
|
||||
* [TODO](#todo)
|
||||
4. [rust-vmm dependency](#4-rust-vmm-dependency)
|
||||
* [Firecracker and crosvm](#firecracker-and-crosvm)
|
||||
@@ -73,7 +74,7 @@ First you need to clone and build the cloud-hypervisor repo:
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
$ git clone https://github.com/intel/cloud-hypervisor.git
|
||||
$ git clone https://github.com/cloud-hypervisor/cloud-hypervisor.git
|
||||
$ cd cloud-hypervisor
|
||||
$ cargo build --release
|
||||
|
||||
@@ -85,6 +86,34 @@ $ popd
|
||||
|
||||
This will build a `cloud-hypervisor` binary under `$CLOUDH/cloud-hypervisor/target/release/cloud-hypervisor`.
|
||||
|
||||
### Containerized builds and tests
|
||||
|
||||
If you want to build and test Cloud Hypervisor without having to install all the
|
||||
required dependencies (The rust toolchain, cargo tools, etc), you can also use
|
||||
Cloud Hypervisor's development script: `dev_cli.sh`. Please note that upon its
|
||||
first invocation, this script will pull a fairly large container image.
|
||||
|
||||
For example, to build the Cloud Hypervisor release binary:
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
$ cd cloud-hypervisor
|
||||
$ ./scripts/dev_cli.sh build --release
|
||||
```
|
||||
|
||||
With `dev_cli.sh`, one can also run the Cloud Hypervisor CI locally. This can be
|
||||
very convenient for debugging CI errors without having to fully rely on the
|
||||
Cloud Hypervisor CI infrastructure.
|
||||
|
||||
For example, to run the Cloud Hypervisor unit tests:
|
||||
|
||||
```shell
|
||||
$ ./scripts/dev_cli.sh tests --unit
|
||||
```
|
||||
|
||||
Run the `./scripts/dev_cli.sh --help` command to view all the supported
|
||||
development script commands and their related options.
|
||||
|
||||
## Run
|
||||
|
||||
You can run a guest VM by either using an existing cloud image or booting into your own kernel and disk image.
|
||||
@@ -94,7 +123,7 @@ You can run a guest VM by either using an existing cloud image or booting into y
|
||||
`cloud-hypervisor` supports booting disk images containing all needed
|
||||
components to run cloud workloads, a.k.a. cloud images. To do that we rely on
|
||||
the [Rust Hypervisor
|
||||
Firmware](https://github.com/intel/rust-hypervisor-firmware) project to provide
|
||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) project to provide
|
||||
an ELF
|
||||
formatted KVM firmware for `cloud-hypervisor` to directly boot into.
|
||||
|
||||
@@ -102,9 +131,9 @@ We need to get the latest `rust-hypervisor-firmware` release and also a working
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
$ wget https://download.clearlinux.org/releases/29160/clear/clear-29160-kvm.img.xz
|
||||
$ unxz clear-29160-kvm.img.xz
|
||||
$ wget https://github.com/intel/rust-hypervisor-firmware/releases/download/0.2.0/hypervisor-fw
|
||||
$ wget https://download.clearlinux.org/releases/31890/clear/clear-31890-kvm.img.xz
|
||||
$ unxz clear-31890-kvm.img.xz
|
||||
$ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.2.6/hypervisor-fw
|
||||
$ popd
|
||||
```
|
||||
|
||||
@@ -113,8 +142,8 @@ $ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./hypervisor-fw \
|
||||
--disk path=clear-29160-kvm.img \
|
||||
--cpus 4 \
|
||||
--disk path=clear-31890-kvm.img \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--rng
|
||||
@@ -128,18 +157,19 @@ Multiple arguments can be given to the `--disk` parameter.
|
||||
#### Building your kernel
|
||||
|
||||
`cloud-hypervisor` also supports direct kernel boot into a `vmlinux` ELF kernel
|
||||
image. You want to build such an image first:
|
||||
image. In order to support virtio-fs and virtio-iommu we have our own development branch. You are of course able to use your own kernel but these instructions will continue with the version that we develop and test against.
|
||||
|
||||
To build the kernel:
|
||||
|
||||
```shell
|
||||
|
||||
# Clone a 5.0 Linux kernel
|
||||
# Clone the Cloud Hypervisor Linux branch
|
||||
$ pushd $CLOUDH
|
||||
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux-cloud-hypervisor
|
||||
$ cd linux-cloud-hypervisor
|
||||
$ git reset --hard v5.0
|
||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b virtio-fs-virtio-iommu-virtio-mem-5.6-rc4 linux-cloud-hypervisor
|
||||
$ pushd linux-cloud-hypervisor
|
||||
|
||||
# Use the cloud-hypervisor kernel config to build your kernel
|
||||
$ cp $CLOUDH/cloud-hypervisor/resources/linux-5.0-config .config
|
||||
$ cp $CLOUDH/cloud-hypervisor/resources/linux-config .config
|
||||
$ make bzImage -j `nproc`
|
||||
$ popd
|
||||
```
|
||||
@@ -152,8 +182,8 @@ For the disk image, we will use a Clear Linux cloud image that contains a root p
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
$ wget https://download.clearlinux.org/releases/29160/clear/clear-29160-kvm.img.xz
|
||||
$ unxz clear-29160-kvm.img.xz
|
||||
$ wget https://download.clearlinux.org/releases/31890/clear/clear-31890-kvm.img.xz
|
||||
$ unxz clear-31890-kvm.img.xz
|
||||
$ popd
|
||||
```
|
||||
|
||||
@@ -167,9 +197,9 @@ $ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--disk path=clear-29160-kvm.img \
|
||||
--disk path=clear-31890-kvm.img \
|
||||
--cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--cpus 4 \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--rng
|
||||
@@ -187,27 +217,38 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--console off \
|
||||
--serial tty \
|
||||
--disk path=clear-29160-kvm.img \
|
||||
--disk path=clear-31890-kvm.img \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--cpus 4 \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--rng
|
||||
```
|
||||
|
||||
|
||||
# 3. Status
|
||||
|
||||
`cloud-hypervisor` is in a very early, pre-alpha stage. Use at your own risk!
|
||||
|
||||
As of 2019/05/12, booting cloud images has only been tested with [Clear Linux images](https://download.clearlinux.org/current/).
|
||||
Direct kernel boot to userspace should work with most rootfs and it's been tested with
|
||||
Clear Linux root partitions, and also basic initrd/initramfs images.
|
||||
As of 2019-12-12, the following cloud images are supported:
|
||||
* [Clear Linux](https://download.clearlinux.org/current/) (cloudguest and kvm)
|
||||
* [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
|
||||
* [Ubuntu Eoan](https://cloud-images.ubuntu.com/eoan/current/) (cloudimg)
|
||||
|
||||
Direct kernel boot to userspace should work with most rootfs.
|
||||
|
||||
## Hot Plug
|
||||
|
||||
This [document](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/hotplug.md) details how to add devices to
|
||||
a running VM. Currently only CPU hot plug is supported.
|
||||
|
||||
## Device Model
|
||||
|
||||
Follow this [documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/device_model.md).
|
||||
|
||||
## TODO
|
||||
|
||||
We are not tracking the `cloud-hypervisor` TODO list from a specific git tracked file but through
|
||||
[github issues](https://github.com/intel/cloud-hypervisor/issues/new) instead.
|
||||
[github issues](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/new) instead.
|
||||
|
||||
# 4. `rust-vmm` project dependency
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vm-memory = "0.1.0"
|
||||
|
||||
|
||||
1988
acpi_tables/src/aml.rs
Normal file
1988
acpi_tables/src/aml.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
pub mod aml;
|
||||
pub mod rsdp;
|
||||
pub mod sdt;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ impl RSDP {
|
||||
};
|
||||
|
||||
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
|
||||
rsdp.extended_checksum = super::generate_checksum(&rsdp.as_slice());;
|
||||
rsdp.extended_checksum = super::generate_checksum(&rsdp.as_slice());
|
||||
rsdp
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,14 @@ impl SDT {
|
||||
self.write(orig_length, value);
|
||||
}
|
||||
|
||||
pub fn append_slice(&mut self, data: &[u8]) {
|
||||
let orig_length = self.data.len();
|
||||
let new_length = orig_length + data.len();
|
||||
self.write_u32(4, new_length as u32);
|
||||
self.data.extend_from_slice(data);
|
||||
self.update_checksum();
|
||||
}
|
||||
|
||||
/// Write a value at the given offset
|
||||
pub fn write<T>(&mut self, offset: usize, value: T) {
|
||||
assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len());
|
||||
|
||||
@@ -5,24 +5,20 @@ authors = ["The Chromium OS Authors"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
acpi = ["acpi_tables"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.2"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = "0.2.60"
|
||||
byteorder = "1.3.4"
|
||||
kvm-bindings = "0.2.0"
|
||||
kvm-ioctls = "0.5.0"
|
||||
libc = "0.2.68"
|
||||
vm-memory = { version = "0.1.0", features = ["backend-mmap"] }
|
||||
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
arch_gen = { path = "../arch_gen" }
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
features = ["backend-mmap"]
|
||||
|
||||
[dependencies.linux-loader]
|
||||
git = "https://github.com/rust-vmm/linux-loader"
|
||||
features = ["elf", "bzimage"]
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7.0"
|
||||
rand = "0.7.3"
|
||||
|
||||
@@ -16,8 +16,7 @@ pub fn configure_system(
|
||||
_cmdline_addr: GuestAddress,
|
||||
_cmdline_size: usize,
|
||||
_num_cpus: u8,
|
||||
_serial_enabled: bool,
|
||||
_virt_iommu: Option<(u32, &[u32])>,
|
||||
_rsdp_addr: Option<GuestAddress>,
|
||||
) -> super::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -22,7 +24,7 @@ extern crate vm_memory;
|
||||
|
||||
use std::result;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
/// X86_64 specific error triggered during system configuration.
|
||||
@@ -30,7 +32,15 @@ pub enum Error {
|
||||
/// The zero page extends past the end of guest_mem.
|
||||
ZeroPagePastRamEnd,
|
||||
/// Error writing the zero page of guest memory.
|
||||
ZeroPageSetup,
|
||||
ZeroPageSetup(vm_memory::GuestMemoryError),
|
||||
/// The memory map table extends past the end of guest memory.
|
||||
MemmapTablePastRamEnd,
|
||||
/// Error writing memory map table to guest memory.
|
||||
MemmapTableSetup,
|
||||
/// The hvm_start_info structure extends past the end of guest memory.
|
||||
StartInfoPastRamEnd,
|
||||
/// Error writing hvm_start_info to guest memory.
|
||||
StartInfoSetup,
|
||||
}
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -67,4 +77,5 @@ pub mod x86_64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
||||
BootProtocol, EntryPoint,
|
||||
};
|
||||
|
||||
@@ -1,484 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
use acpi_tables::{
|
||||
rsdp::RSDP,
|
||||
sdt::{GenericAddress, SDT},
|
||||
};
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
use vm_memory::{Address, ByteValued, Bytes};
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use super::layout;
|
||||
|
||||
#[repr(packed)]
|
||||
struct LocalAPIC {
|
||||
pub r#type: u8,
|
||||
pub length: u8,
|
||||
pub processor_id: u8,
|
||||
pub apic_id: u8,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IOAPIC {
|
||||
pub r#type: u8,
|
||||
pub length: u8,
|
||||
pub ioapic_id: u8,
|
||||
_reserved: u8,
|
||||
pub apic_address: u32,
|
||||
pub gsi_base: u32,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct InterruptSourceOverride {
|
||||
pub r#type: u8,
|
||||
pub length: u8,
|
||||
pub bus: u8,
|
||||
pub source: u8,
|
||||
pub gsi: u32,
|
||||
pub flags: u16,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct PCIRangeEntry {
|
||||
pub base_address: u64,
|
||||
pub segment: u16,
|
||||
pub start: u8,
|
||||
pub end: u8,
|
||||
_reserved: u32,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortParavirtIommuNode {
|
||||
pub type_: u8,
|
||||
pub length: u16,
|
||||
pub revision: u8,
|
||||
_reserved1: u32,
|
||||
pub num_id_mappings: u32,
|
||||
pub ref_id_mappings: u32,
|
||||
pub device_id: u32,
|
||||
_reserved2: [u32; 3],
|
||||
pub model: u32,
|
||||
pub flags: u32,
|
||||
_reserved3: [u32; 4],
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortPciRootComplexNode {
|
||||
pub type_: u8,
|
||||
pub length: u16,
|
||||
pub revision: u8,
|
||||
_reserved1: u32,
|
||||
pub num_id_mappings: u32,
|
||||
pub ref_id_mappings: u32,
|
||||
pub mem_access_props: IortMemoryAccessProperties,
|
||||
pub ats_attr: u32,
|
||||
pub pci_seg_num: u32,
|
||||
pub mem_addr_size_limit: u8,
|
||||
_reserved2: [u8; 3],
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortMemoryAccessProperties {
|
||||
pub cca: u32,
|
||||
pub ah: u8,
|
||||
_reserved: u16,
|
||||
pub maf: u8,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
struct IortIdMapping {
|
||||
pub input_base: u32,
|
||||
pub num_of_ids: u32,
|
||||
pub ouput_base: u32,
|
||||
pub output_ref: u32,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
pub fn create_dsdt_table(
|
||||
serial_enabled: bool,
|
||||
start_of_device_area: GuestAddress,
|
||||
end_of_device_area: GuestAddress,
|
||||
) -> SDT {
|
||||
/*
|
||||
The hex tables in this file are generated from the ASL below with:
|
||||
"iasl -tc <dsdt.asl>"
|
||||
|
||||
As the output contains a table header that is not required the first 36 bytes
|
||||
should be disregarded.
|
||||
*/
|
||||
|
||||
/*
|
||||
Device (_SB.PCI0)
|
||||
{
|
||||
Name (_HID, EisaId ("PNP0A08") /* PCI Express Bus */) // _HID: Hardware ID
|
||||
Name (_CID, EisaId ("PNP0A03") /* PCI Bus */) // _CID: Compatible ID
|
||||
Name (_ADR, Zero) // _ADR: Address
|
||||
Name (_SEG, Zero) // _SEG: PCI Segment
|
||||
Name (_UID, Zero) // _UID: Unique ID
|
||||
Name (SUPP, Zero)
|
||||
}
|
||||
|
||||
Scope (_SB.PCI0)
|
||||
{
|
||||
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
|
||||
{
|
||||
WordBusNumber (ResourceProducer, MinFixed, MaxFixed, PosDecode,
|
||||
0x0000, // Granularity
|
||||
0x0000, // Range Minimum
|
||||
0x00FF, // Range Maximum
|
||||
0x0000, // Translation Offset
|
||||
0x0100, // Length
|
||||
,, )
|
||||
IO (Decode16,
|
||||
0x0CF8, // Range Minimum
|
||||
0x0CF8, // Range Maximum
|
||||
0x01, // Alignment
|
||||
0x08, // Length
|
||||
)
|
||||
WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
|
||||
0x0000, // Granularity
|
||||
0x0000, // Range Minimum
|
||||
0x0CF7, // Range Maximum
|
||||
0x0000, // Translation Offset
|
||||
0x0CF8, // Length
|
||||
,, , TypeStatic, DenseTranslation)
|
||||
WordIO (ResourceProducer, MinFixed, MaxFixed, PosDecode, EntireRange,
|
||||
0x0000, // Granularity
|
||||
0x0D00, // Range Minimum
|
||||
0xFFFF, // Range Maximum
|
||||
0x0000, // Translation Offset
|
||||
0xF300, // Length
|
||||
,, , TypeStatic, DenseTranslation)
|
||||
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
|
||||
0x00000000, // Granularity
|
||||
0x000A0000, // Range Minimum
|
||||
0x000BFFFF, // Range Maximum
|
||||
0x00000000, // Translation Offset
|
||||
0x00020000, // Length
|
||||
,, , AddressRangeMemory, TypeStatic)
|
||||
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, NonCacheable, ReadWrite,
|
||||
0x00000000, // Granularity
|
||||
0xC0000000, // Range Minimum
|
||||
0xFEBFFFFF, // Range Maximum
|
||||
0x00000000, // Translation Offset
|
||||
0x3EC00000, // Length
|
||||
,, , AddressRangeMemory, TypeStatic)
|
||||
QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
|
||||
0x0000000000000000, // Granularity
|
||||
0x0000000800000000, // Range Minimum
|
||||
0x0000000FFFFFFFFF, // Range Maximum
|
||||
0x0000000000000000, // Translation Offset
|
||||
0x0000000800000000, // Length
|
||||
,, , AddressRangeMemory, TypeStatic)
|
||||
})
|
||||
}
|
||||
*/
|
||||
let mut pci_dsdt_data = [
|
||||
0x5Bu8, 0x82, 0x36, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x48,
|
||||
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0A, 0x08, 0x08, 0x5F, 0x43, 0x49, 0x44, 0x0C, 0x41, 0xD0,
|
||||
0x0A, 0x03, 0x08, 0x5F, 0x41, 0x44, 0x52, 0x00, 0x08, 0x5F, 0x53, 0x45, 0x47, 0x00, 0x08,
|
||||
0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x53, 0x55, 0x50, 0x50, 0x00, 0x10, 0x41, 0x0B, 0x2E,
|
||||
0x5F, 0x53, 0x42, 0x5F, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11, 0x40,
|
||||
0x0A, 0x0A, 0x9C, 0x88, 0x0D, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x47, 0x01, 0xF8, 0x0C, 0xF8, 0x0C, 0x01, 0x08, 0x88, 0x0D, 0x00,
|
||||
0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0xF7, 0x0C, 0x00, 0x00, 0xF8, 0x0C, 0x88, 0x0D,
|
||||
0x00, 0x01, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF3, 0x87,
|
||||
0x17, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0xFF, 0xFF,
|
||||
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x87, 0x17, 0x00, 0x00, 0x0C,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0xBF, 0xFE, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xC0, 0x3E, 0x8A, 0x2B, 0x00, 0x00, 0x0C, 0x03, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x79, 0x00,
|
||||
];
|
||||
|
||||
// Patch Range Minimum/Range Maximum/Length for the the 64-bit device area
|
||||
pci_dsdt_data[170..174].copy_from_slice(&layout::MEM_32BIT_DEVICES_START.0.to_le_bytes()[0..4]);
|
||||
pci_dsdt_data[174..178].copy_from_slice(
|
||||
&(layout::MEM_32BIT_DEVICES_START.0 + layout::MEM_32BIT_DEVICES_SIZE - 1).to_le_bytes()
|
||||
[0..4],
|
||||
);
|
||||
pci_dsdt_data[182..186].copy_from_slice(&layout::MEM_32BIT_DEVICES_SIZE.to_le_bytes()[0..4]);
|
||||
|
||||
// Patch the Range Minimum/Range Maximum/Length for the the 64-bit device area
|
||||
pci_dsdt_data[200..208].copy_from_slice(&(start_of_device_area.0).to_le_bytes());
|
||||
pci_dsdt_data[208..216].copy_from_slice(&end_of_device_area.0.to_le_bytes());
|
||||
pci_dsdt_data[224..232].copy_from_slice(
|
||||
&(end_of_device_area.unchecked_offset_from(start_of_device_area) + 1).to_le_bytes(),
|
||||
);
|
||||
|
||||
/*
|
||||
Device (_SB.MBRD)
|
||||
{
|
||||
Name (_HID, EisaId ("PNP0C02") /* PNP Motherboard Resources */) // _HID: Hardware ID
|
||||
Name (_UID, Zero) // _UID: Unique ID
|
||||
}
|
||||
|
||||
Scope (_SB.MBRD)
|
||||
{
|
||||
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
|
||||
{
|
||||
Memory32Fixed (ReadWrite,
|
||||
0xE8000000, // Address Base
|
||||
0x10000000, // Address Length
|
||||
)
|
||||
})
|
||||
}
|
||||
*/
|
||||
let mut mbrd_dsdt_data = [
|
||||
0x5Bu8, 0x82, 0x1A, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x42, 0x52, 0x44, 0x08, 0x5F, 0x48,
|
||||
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x0C, 0x02, 0x08, 0x5F, 0x55, 0x49, 0x44, 0x00, 0x10, 0x21,
|
||||
0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x4D, 0x42, 0x52, 0x44, 0x08, 0x5F, 0x43, 0x52, 0x53, 0x11,
|
||||
0x11, 0x0A, 0x0E, 0x86, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x10,
|
||||
0x79, 0x00,
|
||||
];
|
||||
|
||||
mbrd_dsdt_data[52..56].copy_from_slice(&layout::PCI_MMCONFIG_START.0.to_le_bytes()[0..4]);
|
||||
mbrd_dsdt_data[56..60].copy_from_slice(&layout::PCI_MMCONFIG_SIZE.to_le_bytes()[0..4]);
|
||||
|
||||
/*
|
||||
Device (_SB.COM1)
|
||||
{
|
||||
Name (_HID, EisaId ("PNP0501") /* 16550A-compatible COM Serial Port */) // _HID: Hardware ID
|
||||
Name (_UID, Zero) // _UID: Unique ID
|
||||
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
|
||||
{
|
||||
Interrupt (ResourceConsumer, Edge, ActiveHigh, Exclusive, ,, )
|
||||
{
|
||||
0x00000004,
|
||||
}
|
||||
IO (Decode16,
|
||||
0x03F8, // Range Minimum
|
||||
0x03F8, // Range Maximum
|
||||
0x00, // Alignment
|
||||
0x08, // Length
|
||||
)
|
||||
})
|
||||
}
|
||||
*/
|
||||
let com1_dsdt_data = [
|
||||
0x5Bu8, 0x82, 0x36, 0x2E, 0x5F, 0x53, 0x42, 0x5F, 0x43, 0x4F, 0x4D, 0x31, 0x08, 0x5F, 0x48,
|
||||
0x49, 0x44, 0x0C, 0x41, 0xD0, 0x05, 0x01, 0x08, 0x5F, 0x55, 0x49, 0x44, 0x00, 0x08, 0x5F,
|
||||
0x43, 0x52, 0x53, 0x11, 0x16, 0x0A, 0x13, 0x89, 0x06, 0x00, 0x03, 0x01, 0x04, 0x00, 0x00,
|
||||
0x00, 0x47, 0x01, 0xF8, 0x03, 0xF8, 0x03, 0x00, 0x08, 0x79, 0x00,
|
||||
];
|
||||
|
||||
/*
|
||||
Name (\_S5, Package (0x01) // _S5_: S5 System State
|
||||
{
|
||||
0x05
|
||||
})
|
||||
*/
|
||||
let s5_sleep_data = [0x08u8, 0x5F, 0x53, 0x35, 0x5F, 0x12, 0x04, 0x01, 0x0A, 0x05];
|
||||
|
||||
// DSDT
|
||||
let mut dsdt = SDT::new(*b"DSDT", 36, 6, *b"CLOUDH", *b"CHDSDT ", 1);
|
||||
dsdt.append(pci_dsdt_data);
|
||||
dsdt.append(mbrd_dsdt_data);
|
||||
if serial_enabled {
|
||||
dsdt.append(com1_dsdt_data);
|
||||
}
|
||||
dsdt.append(s5_sleep_data);
|
||||
|
||||
dsdt
|
||||
}
|
||||
pub fn create_acpi_tables(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
num_cpus: u8,
|
||||
serial_enabled: bool,
|
||||
start_of_device_area: GuestAddress,
|
||||
end_of_device_area: GuestAddress,
|
||||
virt_iommu: Option<(u32, &[u32])>,
|
||||
) -> GuestAddress {
|
||||
// RSDP is at the EBDA
|
||||
let rsdp_offset = layout::RSDP_POINTER;
|
||||
let mut tables: Vec<u64> = Vec::new();
|
||||
|
||||
// DSDT
|
||||
let dsdt = create_dsdt_table(serial_enabled, start_of_device_area, end_of_device_area);
|
||||
let dsdt_offset = rsdp_offset.checked_add(RSDP::len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(dsdt.as_slice(), dsdt_offset)
|
||||
.expect("Error writing DSDT table");
|
||||
|
||||
// FACP aka FADT
|
||||
// Revision 6 of the ACPI FADT table is 276 bytes long
|
||||
let mut facp = SDT::new(*b"FACP", 276, 6, *b"CLOUDH", *b"CHFACP ", 1);
|
||||
|
||||
// HW_REDUCED_ACPI and RESET_REG_SUP
|
||||
let fadt_flags: u32 = 1 << 20 | 1 << 10;
|
||||
facp.write(112, fadt_flags);
|
||||
|
||||
// RESET_REG
|
||||
facp.write(116, GenericAddress::io_port_address(0x3c0));
|
||||
// RESET_VALUE
|
||||
facp.write(128, 1u8);
|
||||
|
||||
facp.write(131, 3u8); // FADT minor version
|
||||
facp.write(140, dsdt_offset.0); // X_DSDT
|
||||
|
||||
// SLEEP_CONTROL_REG
|
||||
facp.write(244, GenericAddress::io_port_address(0x3c0));
|
||||
// SLEEP_STATUS_REG
|
||||
facp.write(256, GenericAddress::io_port_address(0x3c0));
|
||||
|
||||
facp.write(268, b"CLOUDHYP"); // Hypervisor Vendor Identity
|
||||
|
||||
facp.update_checksum();
|
||||
let facp_offset = dsdt_offset.checked_add(dsdt.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(facp.as_slice(), facp_offset)
|
||||
.expect("Error writing FACP table");
|
||||
tables.push(facp_offset.0);
|
||||
|
||||
// MADT
|
||||
let mut madt = SDT::new(*b"APIC", 44, 5, *b"CLOUDH", *b"CHMADT ", 1);
|
||||
madt.write(36, layout::APIC_START);
|
||||
|
||||
for cpu in 0..num_cpus {
|
||||
let lapic = LocalAPIC {
|
||||
r#type: 0,
|
||||
length: 8,
|
||||
processor_id: cpu,
|
||||
apic_id: cpu,
|
||||
flags: 1,
|
||||
};
|
||||
madt.append(lapic);
|
||||
}
|
||||
|
||||
madt.append(IOAPIC {
|
||||
r#type: 1,
|
||||
length: 12,
|
||||
ioapic_id: 0,
|
||||
apic_address: layout::IOAPIC_START.0 as u32,
|
||||
gsi_base: 0,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
madt.append(InterruptSourceOverride {
|
||||
r#type: 2,
|
||||
length: 10,
|
||||
bus: 0,
|
||||
source: 4,
|
||||
gsi: 4,
|
||||
flags: 0,
|
||||
});
|
||||
|
||||
let madt_offset = facp_offset.checked_add(facp.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(madt.as_slice(), madt_offset)
|
||||
.expect("Error writing MADT table");
|
||||
tables.push(madt_offset.0);
|
||||
|
||||
// MCFG
|
||||
let mut mcfg = SDT::new(*b"MCFG", 36, 1, *b"CLOUDH", *b"CHMCFG ", 1);
|
||||
|
||||
// MCFG reserved 8 bytes
|
||||
mcfg.append(0u64);
|
||||
|
||||
// 32-bit PCI enhanced configuration mechanism
|
||||
mcfg.append(PCIRangeEntry {
|
||||
base_address: layout::PCI_MMCONFIG_START.0,
|
||||
segment: 0,
|
||||
start: 0,
|
||||
end: 0xff,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mcfg_offset = madt_offset.checked_add(madt.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(mcfg.as_slice(), mcfg_offset)
|
||||
.expect("Error writing MCFG table");
|
||||
tables.push(mcfg_offset.0);
|
||||
|
||||
let (prev_tbl_len, prev_tbl_off) = if let Some((iommu_id, dev_ids)) = &virt_iommu {
|
||||
// IORT
|
||||
let mut iort = SDT::new(*b"IORT", 36, 1, *b"CLOUDH", *b"CHIORT ", 1);
|
||||
// IORT number of nodes
|
||||
iort.append(2u32);
|
||||
// IORT offset to array of IORT nodes
|
||||
iort.append(48u32);
|
||||
// IORT reserved 4 bytes
|
||||
iort.append(0u32);
|
||||
// IORT paravirtualized IOMMU node
|
||||
iort.append(IortParavirtIommuNode {
|
||||
type_: 128,
|
||||
length: 56,
|
||||
revision: 0,
|
||||
num_id_mappings: 0,
|
||||
ref_id_mappings: 56,
|
||||
device_id: *iommu_id,
|
||||
model: 1,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let num_entries = dev_ids.len();
|
||||
let length: u16 = (36 + (20 * num_entries)).try_into().unwrap();
|
||||
|
||||
// IORT PCI root complex node
|
||||
iort.append(IortPciRootComplexNode {
|
||||
type_: 2,
|
||||
length,
|
||||
revision: 0,
|
||||
num_id_mappings: num_entries as u32,
|
||||
ref_id_mappings: 36,
|
||||
ats_attr: 0,
|
||||
pci_seg_num: 0,
|
||||
mem_addr_size_limit: 255,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
for dev_id in dev_ids.iter() {
|
||||
// IORT ID mapping
|
||||
iort.append(IortIdMapping {
|
||||
input_base: *dev_id,
|
||||
num_of_ids: 1,
|
||||
ouput_base: *dev_id,
|
||||
output_ref: 48,
|
||||
flags: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let iort_offset = mcfg_offset.checked_add(mcfg.len() as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(iort.as_slice(), iort_offset)
|
||||
.expect("Error writing IORT table");
|
||||
tables.push(iort_offset.0);
|
||||
|
||||
(iort.len(), iort_offset)
|
||||
} else {
|
||||
(mcfg.len(), mcfg_offset)
|
||||
};
|
||||
|
||||
// XSDT
|
||||
let mut xsdt = SDT::new(*b"XSDT", 36, 1, *b"CLOUDH", *b"CHXSDT ", 1);
|
||||
for table in tables {
|
||||
xsdt.append(table);
|
||||
}
|
||||
xsdt.update_checksum();
|
||||
|
||||
let xsdt_offset = prev_tbl_off.checked_add(prev_tbl_len as u64).unwrap();
|
||||
guest_mem
|
||||
.write_slice(xsdt.as_slice(), xsdt_offset)
|
||||
.expect("Error writing XSDT table");
|
||||
|
||||
// RSDP
|
||||
let rsdp = RSDP::new(*b"CLOUDH", xsdt_offset.0);
|
||||
guest_mem
|
||||
.write_slice(rsdp.as_slice(), rsdp_offset)
|
||||
.expect("Error writing RSDP");
|
||||
|
||||
rsdp_offset
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -11,21 +13,47 @@ use kvm_bindings::kvm_segment;
|
||||
|
||||
/// Constructor for a conventional segment GDT (or LDT) entry. Derived from the kernel's segment.h.
|
||||
pub fn gdt_entry(flags: u16, base: u32, limit: u32) -> u64 {
|
||||
((((base as u64) & 0xff000000u64) << (56 - 24))
|
||||
(((base as u64) & 0xff000000u64) << (56 - 24))
|
||||
| (((flags as u64) & 0x0000f0ffu64) << 40)
|
||||
| (((limit as u64) & 0x000f0000u64) << (48 - 16))
|
||||
| (((base as u64) & 0x00ffffffu64) << 16)
|
||||
| ((limit as u64) & 0x0000ffffu64))
|
||||
| ((limit as u64) & 0x0000ffffu64)
|
||||
}
|
||||
|
||||
fn get_base(entry: u64) -> u64 {
|
||||
((((entry) & 0xFF00000000000000) >> 32)
|
||||
(((entry) & 0xFF00000000000000) >> 32)
|
||||
| (((entry) & 0x000000FF00000000) >> 16)
|
||||
| (((entry) & 0x00000000FFFF0000) >> 16))
|
||||
| (((entry) & 0x00000000FFFF0000) >> 16)
|
||||
}
|
||||
|
||||
// Extract the segment limit from the GDT segment descriptor.
|
||||
//
|
||||
// In a segment descriptor, the limit field is 20 bits, so it can directly describe
|
||||
// a range from 0 to 0xFFFFF (1MByte). When G flag is set (4-KByte page granularity) it
|
||||
// scales the value in the limit field by a factor of 2^12 (4Kbytes), making the effective
|
||||
// limit range from 0xFFF (4 KBytes) to 0xFFFF_FFFF (4 GBytes).
|
||||
//
|
||||
// However, the limit field in the VMCS definition is a 32 bit field, and the limit value is not
|
||||
// automatically scaled using the G flag. This means that for a desired range of 4GB for a
|
||||
// given segment, its limit must be specified as 0xFFFF_FFFF. Therefore the method of obtaining
|
||||
// the limit from the GDT entry is not sufficient, since it only provides 20 bits when 32 bits
|
||||
// are necessary. Fortunately, we can check if the G flag is set when extracting the limit since
|
||||
// the full GDT entry is passed as an argument, and perform the scaling of the limit value to
|
||||
// return the full 32 bit value.
|
||||
//
|
||||
// The scaling mentioned above is required when using PVH boot, since the guest boots in protected
|
||||
// (32-bit) mode and must be able to access the entire 32-bit address space. It does not cause issues
|
||||
// for the case of direct boot to 64-bit (long) mode, since in 64-bit mode the processor does not
|
||||
// perform runtime limit checking on code or data segments.
|
||||
fn get_limit(entry: u64) -> u32 {
|
||||
((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32
|
||||
let limit: u32 =
|
||||
((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32;
|
||||
|
||||
// Perform manual limit scaling if G flag is set
|
||||
match get_g(entry) {
|
||||
0 => limit,
|
||||
_ => ((limit << 12) | 0xFFF), // G flag is either 0 or 1
|
||||
}
|
||||
}
|
||||
|
||||
fn get_g(entry: u64) -> u8 {
|
||||
@@ -109,7 +137,7 @@ mod tests {
|
||||
assert_eq!(0xB, seg.type_);
|
||||
// base and limit
|
||||
assert_eq!(0x100000, seg.base);
|
||||
assert_eq!(0xfffff, seg.limit);
|
||||
assert_eq!(0xffffffff, seg.limit);
|
||||
assert_eq!(0x0, seg.unusable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::io::{self, Cursor};
|
||||
use std::io::Cursor;
|
||||
use std::mem;
|
||||
use std::result;
|
||||
|
||||
@@ -16,8 +16,8 @@ use kvm_ioctls;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
GetLapic(io::Error),
|
||||
SetLapic(io::Error),
|
||||
GetLapic(kvm_ioctls::Error),
|
||||
SetLapic(kvm_ioctls::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -55,7 +55,7 @@ fn set_klapic_reg(klapic: &mut kvm_lapic_state, reg_offset: usize, value: u32) {
|
||||
}
|
||||
|
||||
fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
||||
(((reg) & !0x700) | ((mode) << 8))
|
||||
((reg) & !0x700) | ((mode) << 8)
|
||||
}
|
||||
|
||||
/// Configures LAPICs. LAPIC0 is set for external interrupts, LAPIC1 is set for NMI.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -26,6 +28,13 @@ pub const LOW_RAM_START: GuestAddress = GuestAddress(0x0);
|
||||
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
|
||||
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
||||
|
||||
/// Address for the hvm_start_info struct used in PVH boot
|
||||
pub const PVH_INFO_START: GuestAddress = GuestAddress(0x6000);
|
||||
|
||||
/// Address of memory map table used in PVH boot. Can overlap
|
||||
/// with the zero page address since they are mutually exclusive.
|
||||
pub const MEMMAP_START: GuestAddress = GuestAddress(0x7000);
|
||||
|
||||
/// The 'zero page', a.k.a linux kernel bootparams.
|
||||
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -5,9 +7,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
mod acpi;
|
||||
|
||||
mod gdt;
|
||||
pub mod interrupts;
|
||||
pub mod layout;
|
||||
@@ -16,14 +15,57 @@ pub mod regs;
|
||||
|
||||
use crate::RegionType;
|
||||
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||
use linux_loader::loader::start_info::{hvm_memmap_table_entry, hvm_start_info};
|
||||
use std::mem;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum BootProtocol {
|
||||
LinuxBoot,
|
||||
PvhBoot,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for BootProtocol {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
match self {
|
||||
BootProtocol::LinuxBoot => write!(f, "Linux 64-bit boot protocol"),
|
||||
BootProtocol::PvhBoot => write!(f, "PVH boot protocol"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
/// Specifies the entry point address where the guest must start
|
||||
/// executing code, as well as which of the supported boot protocols
|
||||
/// is to be used to configure the guest initial state.
|
||||
pub struct EntryPoint {
|
||||
/// Address in guest memory where the guest must start execution
|
||||
pub entry_addr: GuestAddress,
|
||||
/// Specifies which boot protocol to use
|
||||
pub protocol: BootProtocol,
|
||||
}
|
||||
|
||||
const E820_RAM: u32 = 1;
|
||||
const E820_RESERVED: u32 = 2;
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
// * all of the parameters being passed to the trait (if there are any) are also foreign
|
||||
// is prohibited.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct StartInfoWrapper(hvm_start_info);
|
||||
|
||||
// It is safe to initialize StartInfoWrapper which is a wrapper over `hvm_start_info` (a series of ints).
|
||||
unsafe impl ByteValued for StartInfoWrapper {}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MemmapTableEntryWrapper(hvm_memmap_table_entry);
|
||||
|
||||
unsafe impl ByteValued for MemmapTableEntryWrapper {}
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
@@ -35,7 +77,7 @@ struct BootParamsWrapper(boot_params);
|
||||
// It is safe to initialize BootParamsWrap which is a wrapper over `boot_params` (a series of ints).
|
||||
unsafe impl ByteValued for BootParamsWrapper {}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid e820 setup params.
|
||||
E820Configuration,
|
||||
@@ -111,36 +153,175 @@ pub fn configure_system(
|
||||
cmdline_size: usize,
|
||||
num_cpus: u8,
|
||||
setup_hdr: Option<setup_header>,
|
||||
_serial_enabled: bool,
|
||||
_end_of_range: GuestAddress,
|
||||
_virt_iommu: Option<(u32, &[u32])>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
boot_prot: BootProtocol,
|
||||
) -> super::Result<()> {
|
||||
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
||||
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?;
|
||||
|
||||
match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
configure_pvh(guest_mem, cmdline_addr, rsdp_addr)?;
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
configure_64bit_boot(guest_mem, cmdline_addr, cmdline_size, setup_hdr, rsdp_addr)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_pvh(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
) -> super::Result<()> {
|
||||
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
||||
|
||||
let mut start_info: StartInfoWrapper = StartInfoWrapper(hvm_start_info::default());
|
||||
|
||||
start_info.0.magic = XEN_HVM_START_MAGIC_VALUE;
|
||||
start_info.0.version = 1; // pvh has version 1
|
||||
start_info.0.nr_modules = 0;
|
||||
start_info.0.cmdline_paddr = cmdline_addr.raw_value() as u64;
|
||||
start_info.0.memmap_paddr = layout::MEMMAP_START.raw_value();
|
||||
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
start_info.0.rsdp_paddr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
// Vector to hold the memory maps which needs to be written to guest memory
|
||||
// at MEMMAP_START after all of the mappings are recorded.
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
|
||||
// Create the memory map entries.
|
||||
add_memmap_entry(&mut memmap, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
|
||||
let mem_end = guest_mem.last_addr();
|
||||
|
||||
if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
} else {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
|
||||
E820_RAM,
|
||||
)?;
|
||||
if mem_end > layout::RAM_64BIT_START {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::RAM_64BIT_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::PCI_MMCONFIG_START.0,
|
||||
layout::PCI_MMCONFIG_SIZE,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
|
||||
start_info.0.memmap_entries = memmap.len() as u32;
|
||||
|
||||
// Copy the vector with the memmap table to the MEMMAP_START address
|
||||
// which is already saved in the memmap_paddr field of hvm_start_info struct.
|
||||
let mut memmap_start_addr = layout::MEMMAP_START;
|
||||
|
||||
guest_mem
|
||||
.checked_offset(
|
||||
memmap_start_addr,
|
||||
mem::size_of::<hvm_memmap_table_entry>() * start_info.0.memmap_entries as usize,
|
||||
)
|
||||
.ok_or(super::Error::MemmapTablePastRamEnd)?;
|
||||
|
||||
// For every entry in the memmap vector, create a MemmapTableEntryWrapper
|
||||
// and write it to guest memory.
|
||||
for memmap_entry in memmap {
|
||||
let map_entry_wrapper: MemmapTableEntryWrapper = MemmapTableEntryWrapper(memmap_entry);
|
||||
|
||||
guest_mem
|
||||
.write_obj(map_entry_wrapper, memmap_start_addr)
|
||||
.map_err(|_| super::Error::MemmapTableSetup)?;
|
||||
memmap_start_addr =
|
||||
memmap_start_addr.unchecked_add(mem::size_of::<hvm_memmap_table_entry>() as u64);
|
||||
}
|
||||
|
||||
// The hvm_start_info struct itself must be stored at PVH_START_INFO
|
||||
// address, and %rbx will be initialized to contain PVH_INFO_START prior to
|
||||
// starting the guest, as required by the PVH ABI.
|
||||
let start_info_addr = layout::PVH_INFO_START;
|
||||
|
||||
guest_mem
|
||||
.checked_offset(start_info_addr, mem::size_of::<hvm_start_info>())
|
||||
.ok_or(super::Error::StartInfoPastRamEnd)?;
|
||||
|
||||
// Write the start_info struct to guest memory.
|
||||
guest_mem
|
||||
.write_obj(start_info, start_info_addr)
|
||||
.map_err(|_| super::Error::StartInfoSetup)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_memmap_entry(
|
||||
memmap: &mut Vec<hvm_memmap_table_entry>,
|
||||
addr: u64,
|
||||
size: u64,
|
||||
mem_type: u32,
|
||||
) -> Result<(), Error> {
|
||||
// Add the table entry to the vector
|
||||
memmap.push(hvm_memmap_table_entry {
|
||||
addr,
|
||||
size,
|
||||
type_: mem_type,
|
||||
reserved: 0,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_64bit_boot(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
setup_hdr: Option<setup_header>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
) -> super::Result<()> {
|
||||
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
|
||||
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
|
||||
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
|
||||
|
||||
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
||||
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?;
|
||||
|
||||
let mut params: BootParamsWrapper = BootParamsWrapper(boot_params::default());
|
||||
|
||||
if let Some(hdr) = setup_hdr {
|
||||
// We should use the header if the loader provides one (e.g. from a bzImage).
|
||||
params.0.hdr = hdr;
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
} else {
|
||||
params.0.hdr.type_of_loader = KERNEL_LOADER_OTHER;
|
||||
params.0.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
|
||||
params.0.hdr.header = KERNEL_HDR_MAGIC;
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
|
||||
};
|
||||
|
||||
// Common bootparams settings
|
||||
if params.0.hdr.type_of_loader == 0 {
|
||||
params.0.hdr.type_of_loader = KERNEL_LOADER_OTHER;
|
||||
}
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
|
||||
add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
|
||||
let mem_end = guest_mem.end_addr();
|
||||
let mem_end = guest_mem.last_addr();
|
||||
if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||
add_e820_entry(
|
||||
&mut params.0,
|
||||
@@ -172,21 +353,7 @@ pub fn configure_system(
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
{
|
||||
let start_of_device_area = if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||
layout::RAM_64BIT_START
|
||||
} else {
|
||||
guest_mem.end_addr().unchecked_add(1)
|
||||
};
|
||||
let rsdp_addr = acpi::create_acpi_tables(
|
||||
guest_mem,
|
||||
num_cpus,
|
||||
_serial_enabled,
|
||||
start_of_device_area,
|
||||
_end_of_range,
|
||||
_virt_iommu,
|
||||
);
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
params.0.acpi_rsdp_addr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
@@ -196,7 +363,7 @@ pub fn configure_system(
|
||||
.ok_or(super::Error::ZeroPagePastRamEnd)?;
|
||||
guest_mem
|
||||
.write_obj(params, zero_page_addr)
|
||||
.map_err(|_| super::Error::ZeroPageSetup)?;
|
||||
.map_err(super::Error::ZeroPageSetup)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -245,24 +412,17 @@ mod tests {
|
||||
#[test]
|
||||
fn test_system_configuration() {
|
||||
let no_vcpus = 4;
|
||||
let gm = GuestMemoryMmap::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let gm = GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let config_err = configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
1,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
);
|
||||
assert!(config_err.is_err());
|
||||
assert_eq!(
|
||||
config_err.unwrap_err(),
|
||||
super::super::Error::X86_64Setup(super::Error::MpTableSetup(
|
||||
mptable::Error::NotEnoughMemory
|
||||
))
|
||||
);
|
||||
|
||||
// Now assigning some memory that falls before the 32bit memory hole.
|
||||
let mem_size = 128 << 20;
|
||||
@@ -272,16 +432,26 @@ mod tests {
|
||||
.filter(|r| r.2 == RegionType::Ram)
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -293,16 +463,26 @@ mod tests {
|
||||
.filter(|r| r.2 == RegionType::Ram)
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -314,16 +494,26 @@ mod tests {
|
||||
.filter(|r| r.2 == RegionType::Ram)
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::new(&ram_regions).unwrap();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
false,
|
||||
GuestAddress((1 << 36) - 1),
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -367,4 +557,29 @@ mod tests {
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_memmap_entry() {
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
|
||||
let expected_memmap = vec![
|
||||
hvm_memmap_table_entry {
|
||||
addr: 0x0,
|
||||
size: 0x1000,
|
||||
type_: E820_RAM,
|
||||
..Default::default()
|
||||
},
|
||||
hvm_memmap_table_entry {
|
||||
addr: 0x10000,
|
||||
size: 0xa000,
|
||||
type_: E820_RESERVED,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
|
||||
add_memmap_entry(&mut memmap, 0, 0x1000, E820_RAM).unwrap();
|
||||
add_memmap_entry(&mut memmap, 0x10000, 0xa000, E820_RESERVED).unwrap();
|
||||
|
||||
assert_eq!(format!("{:?}", memmap), format!("{:?}", expected_memmap));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use libc::c_char;
|
||||
|
||||
use arch_gen::x86::mpspec;
|
||||
use layout::{APIC_START, IOAPIC_START, MPTABLE_START};
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestMemory, GuestMemoryMmap};
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `ByteValued`) where:
|
||||
@@ -45,30 +45,30 @@ unsafe impl ByteValued for MpcTableWrapper {}
|
||||
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// There was too little guest memory to store the entire MP table.
|
||||
NotEnoughMemory,
|
||||
/// The MP table has too little address space to be stored.
|
||||
AddressOverflow,
|
||||
/// Failure while zeroing out the memory for the MP table.
|
||||
Clear,
|
||||
Clear(GuestMemoryError),
|
||||
/// Number of CPUs exceeds the maximum supported CPUs
|
||||
TooManyCpus,
|
||||
/// Failure to write the MP floating pointer.
|
||||
WriteMpfIntel,
|
||||
WriteMpfIntel(GuestMemoryError),
|
||||
/// Failure to write MP CPU entry.
|
||||
WriteMpcCpu,
|
||||
WriteMpcCpu(GuestMemoryError),
|
||||
/// Failure to write MP ioapic entry.
|
||||
WriteMpcIoapic,
|
||||
WriteMpcIoapic(GuestMemoryError),
|
||||
/// Failure to write MP bus entry.
|
||||
WriteMpcBus,
|
||||
WriteMpcBus(GuestMemoryError),
|
||||
/// Failure to write MP interrupt source entry.
|
||||
WriteMpcIntsrc,
|
||||
WriteMpcIntsrc(GuestMemoryError),
|
||||
/// Failure to write MP local interrupt source entry.
|
||||
WriteMpcLintsrc,
|
||||
WriteMpcLintsrc(GuestMemoryError),
|
||||
/// Failure to write MP table header.
|
||||
WriteMpcTable,
|
||||
WriteMpcTable(GuestMemoryError),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -145,7 +145,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
}
|
||||
|
||||
mem.read_exact_from(base_mp, &mut io::repeat(0), mp_size)
|
||||
.map_err(|_| Error::Clear)?;
|
||||
.map_err(Error::Clear)?;
|
||||
|
||||
{
|
||||
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
||||
@@ -156,7 +156,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
mpf_intel.0.physptr = (base_mp.raw_value() + size) as u32;
|
||||
mpf_intel.0.checksum = mpf_intel_compute_checksum(&mpf_intel.0);
|
||||
mem.write_obj(mpf_intel, base_mp)
|
||||
.map_err(|_| Error::WriteMpfIntel)?;
|
||||
.map_err(Error::WriteMpfIntel)?;
|
||||
base_mp = base_mp.unchecked_add(size);
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
mpc_cpu.0.cpufeature = CPU_STEPPING;
|
||||
mpc_cpu.0.featureflag = CPU_FEATURE_APIC | CPU_FEATURE_FPU;
|
||||
mem.write_obj(mpc_cpu, base_mp)
|
||||
.map_err(|_| Error::WriteMpcCpu)?;
|
||||
.map_err(Error::WriteMpcCpu)?;
|
||||
base_mp = base_mp.unchecked_add(size as u64);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu.0));
|
||||
}
|
||||
@@ -193,7 +193,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
mpc_bus.0.busid = 0;
|
||||
mpc_bus.0.bustype = BUS_TYPE_ISA;
|
||||
mem.write_obj(mpc_bus, base_mp)
|
||||
.map_err(|_| Error::WriteMpcBus)?;
|
||||
.map_err(Error::WriteMpcBus)?;
|
||||
base_mp = base_mp.unchecked_add(size as u64);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_bus.0));
|
||||
}
|
||||
@@ -206,7 +206,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
mpc_ioapic.0.flags = mpspec::MPC_APIC_USABLE as u8;
|
||||
mpc_ioapic.0.apicaddr = IOAPIC_START.0 as u32;
|
||||
mem.write_obj(mpc_ioapic, base_mp)
|
||||
.map_err(|_| Error::WriteMpcIoapic)?;
|
||||
.map_err(Error::WriteMpcIoapic)?;
|
||||
base_mp = base_mp.unchecked_add(size as u64);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic.0));
|
||||
}
|
||||
@@ -222,7 +222,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
mpc_intsrc.0.dstapic = ioapicid;
|
||||
mpc_intsrc.0.dstirq = i;
|
||||
mem.write_obj(mpc_intsrc, base_mp)
|
||||
.map_err(|_| Error::WriteMpcIntsrc)?;
|
||||
.map_err(Error::WriteMpcIntsrc)?;
|
||||
base_mp = base_mp.unchecked_add(size as u64);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc.0));
|
||||
}
|
||||
@@ -237,7 +237,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
mpc_lintsrc.0.destapic = 0;
|
||||
mpc_lintsrc.0.destapiclint = 0;
|
||||
mem.write_obj(mpc_lintsrc, base_mp)
|
||||
.map_err(|_| Error::WriteMpcLintsrc)?;
|
||||
.map_err(Error::WriteMpcLintsrc)?;
|
||||
base_mp = base_mp.unchecked_add(size as u64);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
|
||||
}
|
||||
@@ -252,7 +252,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
mpc_lintsrc.0.destapic = 0xFF; /* to all local APICs */
|
||||
mpc_lintsrc.0.destapiclint = 1;
|
||||
mem.write_obj(mpc_lintsrc, base_mp)
|
||||
.map_err(|_| Error::WriteMpcLintsrc)?;
|
||||
.map_err(Error::WriteMpcLintsrc)?;
|
||||
base_mp = base_mp.unchecked_add(size as u64);
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
|
||||
}
|
||||
@@ -271,7 +271,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
||||
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
||||
mem.write_obj(mpc_table, table_base)
|
||||
.map_err(|_| Error::WriteMpcTable)?;
|
||||
.map_err(Error::WriteMpcTable)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -296,7 +296,8 @@ mod tests {
|
||||
#[test]
|
||||
fn bounds_check() {
|
||||
let num_cpus = 4;
|
||||
let mem = GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
|
||||
setup_mptable(&mem, num_cpus).unwrap();
|
||||
}
|
||||
@@ -304,7 +305,8 @@ mod tests {
|
||||
#[test]
|
||||
fn bounds_check_fails() {
|
||||
let num_cpus = 4;
|
||||
let mem = GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)]).unwrap();
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
|
||||
.unwrap();
|
||||
|
||||
assert!(setup_mptable(&mem, num_cpus).is_err());
|
||||
}
|
||||
@@ -312,7 +314,8 @@ mod tests {
|
||||
#[test]
|
||||
fn mpf_intel_checksum() {
|
||||
let num_cpus = 1;
|
||||
let mem = GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
|
||||
setup_mptable(&mem, num_cpus).unwrap();
|
||||
|
||||
@@ -327,7 +330,8 @@ mod tests {
|
||||
#[test]
|
||||
fn mpc_table_checksum() {
|
||||
let num_cpus = 4;
|
||||
let mem = GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
|
||||
setup_mptable(&mem, num_cpus).unwrap();
|
||||
|
||||
@@ -356,9 +360,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cpu_entry_count() {
|
||||
let mem =
|
||||
GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(MAX_SUPPORTED_CPUS as u8))])
|
||||
.unwrap();
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(
|
||||
MPTABLE_START,
|
||||
compute_mp_size(MAX_SUPPORTED_CPUS as u8),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
||||
setup_mptable(&mem, i).unwrap();
|
||||
@@ -391,9 +397,10 @@ mod tests {
|
||||
#[test]
|
||||
fn cpu_entry_count_max() {
|
||||
let cpus = MAX_SUPPORTED_CPUS + 1;
|
||||
let mem = GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
||||
|
||||
let result = setup_mptable(&mem, cpus as u8).unwrap_err();
|
||||
assert_eq!(result, Error::TooManyCpus);
|
||||
let result = setup_mptable(&mem, cpus as u8);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -5,14 +7,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::{io, mem, result};
|
||||
use std::{mem, result};
|
||||
|
||||
use super::gdt::{gdt_entry, kvm_segment_from_gdt};
|
||||
use super::BootProtocol;
|
||||
use arch_gen::x86::msr_index;
|
||||
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_msrs, kvm_regs, kvm_sregs};
|
||||
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_regs, kvm_sregs, Msrs};
|
||||
use kvm_ioctls::VcpuFd;
|
||||
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryMmap};
|
||||
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PVH_INFO_START};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
|
||||
// MTRR constants
|
||||
const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
|
||||
@@ -21,25 +24,27 @@ const MTRR_MEM_TYPE_WB: u64 = 0x6;
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to get SREGs for this CPU.
|
||||
GetStatusRegisters(io::Error),
|
||||
GetStatusRegisters(kvm_ioctls::Error),
|
||||
/// Failed to set base registers for this CPU.
|
||||
SetBaseRegisters(io::Error),
|
||||
SetBaseRegisters(kvm_ioctls::Error),
|
||||
/// Failed to configure the FPU.
|
||||
SetFPURegisters(io::Error),
|
||||
SetFPURegisters(kvm_ioctls::Error),
|
||||
/// Setting up MSRs failed.
|
||||
SetModelSpecificRegisters(io::Error),
|
||||
SetModelSpecificRegisters(kvm_ioctls::Error),
|
||||
/// Failed to set SREGs for this CPU.
|
||||
SetStatusRegisters(io::Error),
|
||||
SetStatusRegisters(kvm_ioctls::Error),
|
||||
/// Checking the GDT address failed.
|
||||
CheckGDTAddr,
|
||||
/// Writing the GDT to RAM failed.
|
||||
WriteGDT,
|
||||
WriteGDT(GuestMemoryError),
|
||||
/// Writing the IDT to RAM failed.
|
||||
WriteIDT,
|
||||
WriteIDT(GuestMemoryError),
|
||||
/// Writing PDPTE to RAM failed.
|
||||
WritePDPTEAddress,
|
||||
WritePDPTEAddress(GuestMemoryError),
|
||||
/// Writing PDE to RAM failed.
|
||||
WritePDEAddress,
|
||||
WritePDEAddress(GuestMemoryError),
|
||||
/// Writing PML4 to RAM failed.
|
||||
WritePML4Address,
|
||||
WritePML4Address(GuestMemoryError),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -65,26 +70,10 @@ pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
||||
let entry_vec = create_msr_entries();
|
||||
let vec_size_bytes =
|
||||
mem::size_of::<kvm_msrs>() + (entry_vec.len() * mem::size_of::<kvm_msr_entry>());
|
||||
let vec: Vec<u8> = Vec::with_capacity(vec_size_bytes);
|
||||
let msrs: &mut kvm_msrs = unsafe {
|
||||
// Converting the vector's memory to a struct is unsafe. Carefully using the read-only
|
||||
// vector to size and set the members ensures no out-of-bounds errors below.
|
||||
&mut *(vec.as_ptr() as *mut kvm_msrs)
|
||||
};
|
||||
vcpu.set_msrs(&create_msr_entries())
|
||||
.map_err(Error::SetModelSpecificRegisters)?;
|
||||
|
||||
unsafe {
|
||||
// Mapping the unsized array to a slice is unsafe because the length isn't known.
|
||||
// Providing the length used to create the struct guarantees the entire slice is valid.
|
||||
let entries: &mut [kvm_msr_entry] = msrs.entries.as_mut_slice(entry_vec.len());
|
||||
entries.copy_from_slice(&entry_vec);
|
||||
}
|
||||
msrs.nmsrs = entry_vec.len() as u32;
|
||||
|
||||
vcpu.set_msrs(msrs)
|
||||
.map_err(Error::SetModelSpecificRegisters)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Configure base registers for a given CPU.
|
||||
@@ -95,16 +84,31 @@ pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
||||
/// * `boot_ip` - Starting instruction pointer.
|
||||
/// * `boot_sp` - Starting stack pointer.
|
||||
/// * `boot_si` - Must point to zero page address per Linux ABI.
|
||||
pub fn setup_regs(vcpu: &VcpuFd, boot_ip: u64, boot_sp: u64, boot_si: u64) -> Result<()> {
|
||||
let regs: kvm_regs = kvm_regs {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rip: boot_ip,
|
||||
rsp: boot_sp,
|
||||
rbp: boot_sp,
|
||||
rsi: boot_si,
|
||||
..Default::default()
|
||||
pub fn setup_regs(
|
||||
vcpu: &VcpuFd,
|
||||
boot_ip: u64,
|
||||
boot_sp: u64,
|
||||
boot_si: u64,
|
||||
boot_prot: BootProtocol,
|
||||
) -> Result<()> {
|
||||
let regs: kvm_regs = match boot_prot {
|
||||
// Configure regs as required by PVH boot protocol.
|
||||
BootProtocol::PvhBoot => kvm_regs {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rbx: PVH_INFO_START.raw_value(),
|
||||
rip: boot_ip,
|
||||
..Default::default()
|
||||
},
|
||||
// Configure regs as required by Linux 64-bit boot protocol.
|
||||
BootProtocol::LinuxBoot => kvm_regs {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rip: boot_ip,
|
||||
rsp: boot_sp,
|
||||
rbp: boot_sp,
|
||||
rsi: boot_si,
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||
}
|
||||
|
||||
@@ -114,11 +118,14 @@ pub fn setup_regs(vcpu: &VcpuFd, boot_ip: u64, boot_sp: u64, boot_si: u64) -> Re
|
||||
///
|
||||
/// * `mem` - The memory that will be passed to the guest.
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &VcpuFd) -> Result<()> {
|
||||
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &VcpuFd, boot_prot: BootProtocol) -> Result<()> {
|
||||
let mut sregs: kvm_sregs = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||
|
||||
configure_segments_and_sregs(mem, &mut sregs)?;
|
||||
setup_page_tables(mem, &mut sregs)?; // TODO(dgreid) - Can this be done once per system instead?
|
||||
configure_segments_and_sregs(mem, &mut sregs, boot_prot)?;
|
||||
|
||||
if let BootProtocol::LinuxBoot = boot_prot {
|
||||
setup_page_tables(mem, &mut sregs)?; // TODO(dgreid) - Can this be done once per system instead?
|
||||
}
|
||||
|
||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||
}
|
||||
@@ -137,10 +144,8 @@ fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
for (index, entry) in table.iter().enumerate() {
|
||||
let addr = guest_mem
|
||||
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
|
||||
.ok_or(Error::WriteGDT)?;
|
||||
guest_mem
|
||||
.write_obj(*entry, addr)
|
||||
.map_err(|_| Error::WriteGDT)?;
|
||||
.ok_or(Error::CheckGDTAddr)?;
|
||||
guest_mem.write_obj(*entry, addr).map_err(Error::WriteGDT)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -149,16 +154,34 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let boot_idt_addr = BOOT_IDT_START;
|
||||
guest_mem
|
||||
.write_obj(val, boot_idt_addr)
|
||||
.map_err(|_| Error::WriteIDT)
|
||||
.map_err(Error::WriteIDT)
|
||||
}
|
||||
|
||||
fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> {
|
||||
let gdt_table: [u64; BOOT_GDT_MAX as usize] = [
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xa09b, 0, 0xfffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xfffff), // DATA
|
||||
gdt_entry(0x808b, 0, 0xfffff), // TSS
|
||||
];
|
||||
fn configure_segments_and_sregs(
|
||||
mem: &GuestMemoryMmap,
|
||||
sregs: &mut kvm_sregs,
|
||||
boot_prot: BootProtocol,
|
||||
) -> Result<()> {
|
||||
let gdt_table: [u64; BOOT_GDT_MAX as usize] = match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
// Configure GDT entries as specified by PVH boot protocol
|
||||
[
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xc09b, 0, 0xffffffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xffffffff), // DATA
|
||||
gdt_entry(0x008b, 0, 0x67), // TSS
|
||||
]
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
// Configure GDT entries as specified by Linux 64bit boot protocol
|
||||
[
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xa09b, 0, 0xfffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xfffff), // DATA
|
||||
gdt_entry(0x808b, 0, 0xfffff), // TSS
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let code_seg = kvm_segment_from_gdt(gdt_table[1], 1);
|
||||
let data_seg = kvm_segment_from_gdt(gdt_table[2], 2);
|
||||
@@ -181,9 +204,17 @@ fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) ->
|
||||
sregs.ss = data_seg;
|
||||
sregs.tr = tss_seg;
|
||||
|
||||
/* 64-bit protected mode */
|
||||
sregs.cr0 |= X86_CR0_PE;
|
||||
sregs.efer |= EFER_LME | EFER_LMA;
|
||||
match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
sregs.cr0 = X86_CR0_PE;
|
||||
sregs.cr4 = 0;
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
/* 64-bit protected mode */
|
||||
sregs.cr0 |= X86_CR0_PE;
|
||||
sregs.efer |= EFER_LME | EFER_LMA;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -193,16 +224,16 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
|
||||
|
||||
// Entry covering VA [0..512GB)
|
||||
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
|
||||
.map_err(|_| Error::WritePML4Address)?;
|
||||
.map_err(Error::WritePML4Address)?;
|
||||
|
||||
// Entry covering VA [0..1GB)
|
||||
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
|
||||
.map_err(|_| Error::WritePDPTEAddress)?;
|
||||
.map_err(Error::WritePDPTEAddress)?;
|
||||
// 512 2MB entries together covering VA [0..1GB). Note we are assuming
|
||||
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
|
||||
for i in 0..512 {
|
||||
mem.write_obj((i << 21) + 0x83u64, PDE_START.unchecked_add(i * 8))
|
||||
.map_err(|_| Error::WritePDEAddress)?;
|
||||
.map_err(Error::WritePDEAddress)?;
|
||||
}
|
||||
|
||||
sregs.cr3 = PML4_START.raw_value();
|
||||
@@ -211,7 +242,7 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_msr_entries() -> Vec<kvm_msr_entry> {
|
||||
fn create_msr_entries() -> Msrs {
|
||||
let mut entries = Vec::<kvm_msr_entry>::new();
|
||||
|
||||
entries.push(kvm_msr_entry {
|
||||
@@ -272,7 +303,7 @@ fn create_msr_entries() -> Vec<kvm_msr_entry> {
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
entries
|
||||
Msrs::from_entries(&entries)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -285,7 +316,7 @@ mod tests {
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
fn create_guest_mem() -> GuestMemoryMmap {
|
||||
GuestMemoryMmap::new(&vec![(GuestAddress(0), 0x10000)]).unwrap()
|
||||
GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap()
|
||||
}
|
||||
|
||||
fn read_u64(gm: &GuestMemoryMmap, offset: GuestAddress) -> u64 {
|
||||
@@ -296,7 +327,7 @@ mod tests {
|
||||
fn segments_and_sregs() {
|
||||
let mut sregs: kvm_sregs = Default::default();
|
||||
let gm = create_guest_mem();
|
||||
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
||||
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::LinuxBoot).unwrap();
|
||||
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
@@ -314,16 +345,46 @@ mod tests {
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
||||
|
||||
assert_eq!(0, sregs.cs.base);
|
||||
assert_eq!(0xfffff, sregs.ds.limit);
|
||||
assert_eq!(0xffffffff, sregs.ds.limit);
|
||||
assert_eq!(0x10, sregs.es.selector);
|
||||
assert_eq!(1, sregs.fs.present);
|
||||
assert_eq!(1, sregs.gs.g);
|
||||
assert_eq!(0, sregs.ss.avl);
|
||||
assert_eq!(0, sregs.tr.base);
|
||||
assert_eq!(0xfffff, sregs.tr.limit);
|
||||
assert_eq!(0xffffffff, sregs.tr.limit);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert_eq!(X86_CR0_PE, sregs.cr0);
|
||||
assert_eq!(EFER_LME | EFER_LMA, sregs.efer);
|
||||
|
||||
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::PvhBoot).unwrap();
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
0xcf9b000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
|
||||
);
|
||||
assert_eq!(
|
||||
0xcf93000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
|
||||
);
|
||||
assert_eq!(
|
||||
0x8b0000000067,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
|
||||
);
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
||||
|
||||
assert_eq!(0, sregs.cs.base);
|
||||
assert_eq!(0xffffffff, sregs.ds.limit);
|
||||
assert_eq!(0x10, sregs.es.selector);
|
||||
assert_eq!(1, sregs.fs.present);
|
||||
assert_eq!(1, sregs.gs.g);
|
||||
assert_eq!(0, sregs.ss.avl);
|
||||
assert_eq!(0, sregs.tr.base);
|
||||
assert_eq!(0, sregs.tr.g);
|
||||
assert_eq!(0x67, sregs.tr.limit);
|
||||
assert_eq!(0xb, sregs.tr.type_);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert_eq!(X86_CR0_PE, sregs.cr0);
|
||||
assert_eq!(0, sregs.cr4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -377,24 +438,11 @@ mod tests {
|
||||
|
||||
// This test will check against the last MSR entry configured (the tenth one).
|
||||
// See create_msr_entries for details.
|
||||
let test_kvm_msrs_entry = [kvm_msr_entry {
|
||||
let mut msrs = Msrs::from_entries(&[kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_MISC_ENABLE,
|
||||
..Default::default()
|
||||
}];
|
||||
let vec_size_bytes = mem::size_of::<kvm_msrs>() + mem::size_of::<kvm_msr_entry>();
|
||||
let vec: Vec<u8> = Vec::with_capacity(vec_size_bytes);
|
||||
let mut msrs: &mut kvm_msrs = unsafe {
|
||||
// Converting the vector's memory to a struct is unsafe. Carefully using the read-only
|
||||
// vector to size and set the members ensures no out-of-bounds errors below.
|
||||
&mut *(vec.as_ptr() as *mut kvm_msrs)
|
||||
};
|
||||
}]);
|
||||
|
||||
unsafe {
|
||||
let entries: &mut [kvm_msr_entry] = msrs.entries.as_mut_slice(1);
|
||||
entries.copy_from_slice(&test_kvm_msrs_entry);
|
||||
}
|
||||
|
||||
msrs.nmsrs = 1;
|
||||
// get_msrs returns the number of msrs that it succeed in reading. We only want to read 1
|
||||
// in this test case scenario.
|
||||
let read_msrs = vcpu.get_msrs(&mut msrs).unwrap();
|
||||
@@ -404,9 +452,7 @@ mod tests {
|
||||
// tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we
|
||||
// expect.
|
||||
let entry_vec = create_msr_entries();
|
||||
unsafe {
|
||||
assert_eq!(entry_vec[9], msrs.entries.as_slice(1)[0]);
|
||||
}
|
||||
assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -429,6 +475,7 @@ mod tests {
|
||||
expected_regs.rip,
|
||||
expected_regs.rsp,
|
||||
expected_regs.rsi,
|
||||
BootProtocol::LinuxBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -444,10 +491,10 @@ mod tests {
|
||||
|
||||
let mut expected_sregs: kvm_sregs = vcpu.get_sregs().unwrap();
|
||||
let gm = create_guest_mem();
|
||||
configure_segments_and_sregs(&gm, &mut expected_sregs).unwrap();
|
||||
configure_segments_and_sregs(&gm, &mut expected_sregs, BootProtocol::LinuxBoot).unwrap();
|
||||
setup_page_tables(&gm, &mut expected_sregs).unwrap();
|
||||
|
||||
setup_sregs(&gm, &vcpu).unwrap();
|
||||
setup_sregs(&gm, &vcpu, BootProtocol::LinuxBoot).unwrap();
|
||||
let actual_sregs: kvm_sregs = vcpu.get_sregs().unwrap();
|
||||
assert_eq!(expected_sregs, actual_sregs);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,6 @@
|
||||
clippy::should_implement_trait,
|
||||
clippy::transmute_ptr_to_ptr
|
||||
)]
|
||||
pub mod bootparam;
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||
|
||||
22
build.rs
Normal file
22
build.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let git_out = Command::new("git")
|
||||
.args(&["describe", "--dirty"])
|
||||
.output()
|
||||
.expect("Expect to get git describe output");
|
||||
|
||||
// This println!() has a special behavior, as it will set the environment
|
||||
// variable BUILT_VERSION, so that it can be reused from the binary.
|
||||
// Particularly, this is used from src/main.rs to display the exact
|
||||
// version.
|
||||
println!(
|
||||
"cargo:rustc-env=BUILT_VERSION={}",
|
||||
String::from_utf8(git_out.stdout).unwrap()
|
||||
);
|
||||
}
|
||||
@@ -4,19 +4,20 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.2"
|
||||
epoll = "4.1.0"
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = "0.2.60"
|
||||
bitflags = ">=1.2.1"
|
||||
byteorder = "1.3.4"
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.68"
|
||||
log = "0.4.8"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
vm-device = { path = "../vm-device" }
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
vm-memory = "0.1.0"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
acpi = []
|
||||
acpi = ["acpi_tables"]
|
||||
cmos = []
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use acpi_tables::{aml, aml::Aml};
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use BusDevice;
|
||||
use HotPlugNotificationFlags;
|
||||
|
||||
/// A device for handling ACPI shutdown and reboot
|
||||
pub struct AcpiShutdownDevice {
|
||||
@@ -44,9 +48,104 @@ impl BusDevice for AcpiShutdownDevice {
|
||||
const SLEEP_VALUE_BIT: u8 = 2;
|
||||
if data[0] == (S5_SLEEP_VALUE << SLEEP_VALUE_BIT) | (1 << SLEEP_STATUS_EN_BIT) {
|
||||
debug!("ACPI Shutdown signalled");
|
||||
extern crate bitflags;
|
||||
if let Err(e) = self.exit_evt.write(1) {
|
||||
error!("Error triggering ACPI shutdown event: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A device for handling ACPI GED event generation
|
||||
pub struct AcpiGEDDevice {
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
notification_type: HotPlugNotificationFlags,
|
||||
ged_irq: u32,
|
||||
}
|
||||
|
||||
impl AcpiGEDDevice {
|
||||
pub fn new(interrupt: Arc<Box<dyn InterruptSourceGroup>>, ged_irq: u32) -> AcpiGEDDevice {
|
||||
AcpiGEDDevice {
|
||||
interrupt,
|
||||
notification_type: HotPlugNotificationFlags::NO_DEVICES_CHANGED,
|
||||
ged_irq,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(
|
||||
&mut self,
|
||||
notification_type: HotPlugNotificationFlags,
|
||||
) -> Result<(), std::io::Error> {
|
||||
self.notification_type |= notification_type;
|
||||
self.interrupt.trigger(0)
|
||||
}
|
||||
|
||||
pub fn irq(&self) -> u32 {
|
||||
self.ged_irq
|
||||
}
|
||||
}
|
||||
|
||||
// I/O port reports what type of notification was made
|
||||
impl BusDevice for AcpiGEDDevice {
|
||||
// Spec has all fields as zero
|
||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||
data[0] = self.notification_type.bits();
|
||||
self.notification_type = HotPlugNotificationFlags::NO_DEVICES_CHANGED;
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
impl Aml for AcpiGEDDevice {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
aml::Device::new(
|
||||
"_SB_.GED_".into(),
|
||||
vec![
|
||||
&aml::Name::new("_HID".into(), &"ACPI0013"),
|
||||
&aml::Name::new("_UID".into(), &aml::ZERO),
|
||||
&aml::Name::new(
|
||||
"_CRS".into(),
|
||||
&aml::ResourceTemplate::new(vec![&aml::Interrupt::new(
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
self.ged_irq,
|
||||
)]),
|
||||
),
|
||||
&aml::OpRegion::new("GDST".into(), aml::OpRegionSpace::SystemIO, 0xb000, 0x1),
|
||||
&aml::Field::new(
|
||||
"GDST".into(),
|
||||
aml::FieldAccessType::Byte,
|
||||
aml::FieldUpdateRule::WriteAsZeroes,
|
||||
vec![aml::FieldEntry::Named(*b"GDAT", 8)],
|
||||
),
|
||||
&aml::Method::new(
|
||||
"_EVT".into(),
|
||||
1,
|
||||
true,
|
||||
vec![
|
||||
&aml::Store::new(&aml::Local(0), &aml::Path::new("GDAT")),
|
||||
&aml::And::new(&aml::Local(1), &aml::Local(0), &aml::ONE),
|
||||
&aml::If::new(
|
||||
&aml::Equal::new(&aml::Local(1), &aml::ONE),
|
||||
vec![&aml::MethodCall::new("\\_SB_.CPUS.CSCN".into(), vec![])],
|
||||
),
|
||||
&aml::And::new(&aml::Local(1), &aml::Local(0), &2usize),
|
||||
&aml::If::new(
|
||||
&aml::Equal::new(&aml::Local(1), &2usize),
|
||||
vec![&aml::MethodCall::new("\\_SB_.MHPC.MSCN".into(), vec![])],
|
||||
),
|
||||
&aml::And::new(&aml::Local(1), &aml::Local(0), &4usize),
|
||||
&aml::If::new(
|
||||
&aml::Equal::new(&aml::Local(1), &4usize),
|
||||
vec![&aml::MethodCall::new("\\_SB_.PCI0.PCNT".into(), vec![])],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
.to_aml_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
|
||||
use std::collections::btree_map::BTreeMap;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Mutex, RwLock, Weak};
|
||||
use std::{convert, error, fmt, io, result};
|
||||
|
||||
/// Trait for devices that respond to reads or writes in an arbitrary address space.
|
||||
///
|
||||
@@ -30,10 +30,28 @@ pub trait BusDevice: Send {
|
||||
pub enum Error {
|
||||
/// The insertion failed because the new device overlapped with an old device.
|
||||
Overlap,
|
||||
/// Failed to operate on zero sized range.
|
||||
ZeroSizedRange,
|
||||
/// Failed to find address range.
|
||||
MissingAddressRange,
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "bus_error: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
impl convert::From<Error> for io::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds a base and length representing the address space occupied by a `BusDevice`.
|
||||
///
|
||||
/// * base - The address at which the range start.
|
||||
@@ -75,29 +93,30 @@ impl PartialOrd for BusRange {
|
||||
///
|
||||
/// This doesn't have any restrictions on what kind of device or address space this applies to. The
|
||||
/// only restriction is that no two devices can overlap in this address space.
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Default)]
|
||||
pub struct Bus {
|
||||
devices: BTreeMap<BusRange, Arc<Mutex<dyn BusDevice>>>,
|
||||
devices: RwLock<BTreeMap<BusRange, Weak<Mutex<dyn BusDevice>>>>,
|
||||
}
|
||||
|
||||
impl Bus {
|
||||
/// Constructs an a bus with an empty address space.
|
||||
pub fn new() -> Bus {
|
||||
Bus {
|
||||
devices: BTreeMap::new(),
|
||||
devices: RwLock::new(BTreeMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn first_before(&self, addr: u64) -> Option<(BusRange, &Mutex<dyn BusDevice>)> {
|
||||
let (range, dev) = self
|
||||
.devices
|
||||
fn first_before(&self, addr: u64) -> Option<(BusRange, Arc<Mutex<dyn BusDevice>>)> {
|
||||
let devices = self.devices.read().unwrap();
|
||||
let (range, dev) = devices
|
||||
.range(..=BusRange { base: addr, len: 1 })
|
||||
.rev()
|
||||
.next()?;
|
||||
Some((*range, dev))
|
||||
Some((*range, dev.upgrade().unwrap().clone()))
|
||||
}
|
||||
|
||||
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, &Mutex<dyn BusDevice>)> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, Arc<Mutex<dyn BusDevice>>)> {
|
||||
if let Some((range, dev)) = self.first_before(addr) {
|
||||
let offset = addr - range.base;
|
||||
if offset < range.len {
|
||||
@@ -108,14 +127,16 @@ impl Bus {
|
||||
}
|
||||
|
||||
/// Puts the given device at the given address space.
|
||||
pub fn insert(&mut self, device: Arc<Mutex<dyn BusDevice>>, base: u64, len: u64) -> Result<()> {
|
||||
pub fn insert(&self, device: Arc<Mutex<dyn BusDevice>>, base: u64, len: u64) -> Result<()> {
|
||||
if len == 0 {
|
||||
return Err(Error::Overlap);
|
||||
return Err(Error::ZeroSizedRange);
|
||||
}
|
||||
|
||||
// Reject all cases where the new device's range overlaps with an existing device.
|
||||
if self
|
||||
.devices
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|(range, _dev)| range.overlaps(base, len))
|
||||
{
|
||||
@@ -124,7 +145,9 @@ impl Bus {
|
||||
|
||||
if self
|
||||
.devices
|
||||
.insert(BusRange { base, len }, device)
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(BusRange { base, len }, Arc::downgrade(&device))
|
||||
.is_some()
|
||||
{
|
||||
return Err(Error::Overlap);
|
||||
@@ -133,6 +156,61 @@ impl Bus {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes the device at the given address space range.
|
||||
pub fn remove(&self, base: u64, len: u64) -> Result<()> {
|
||||
if len == 0 {
|
||||
return Err(Error::ZeroSizedRange);
|
||||
}
|
||||
|
||||
let bus_range = BusRange { base, len };
|
||||
|
||||
if self.devices.write().unwrap().remove(&bus_range).is_none() {
|
||||
return Err(Error::MissingAddressRange);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes all entries referencing the given device.
|
||||
pub fn remove_by_device(&self, device: &Arc<Mutex<dyn BusDevice>>) -> Result<()> {
|
||||
let mut device_list = self.devices.write().unwrap();
|
||||
let mut remove_key_list = Vec::new();
|
||||
|
||||
for (key, value) in device_list.iter() {
|
||||
if Arc::ptr_eq(&value.upgrade().unwrap(), device) {
|
||||
remove_key_list.push(*key);
|
||||
}
|
||||
}
|
||||
|
||||
for key in remove_key_list.iter() {
|
||||
device_list.remove(key);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the address range for an existing device.
|
||||
pub fn update_range(
|
||||
&self,
|
||||
old_base: u64,
|
||||
old_len: u64,
|
||||
new_base: u64,
|
||||
new_len: u64,
|
||||
) -> Result<()> {
|
||||
// Retrieve the device corresponding to the range
|
||||
let device = if let Some((_, _, dev)) = self.resolve(old_base) {
|
||||
dev.clone()
|
||||
} else {
|
||||
return Err(Error::MissingAddressRange);
|
||||
};
|
||||
|
||||
// Remove the old address range
|
||||
self.remove(old_base, old_len)?;
|
||||
|
||||
// Insert the new address range
|
||||
self.insert(device, new_base, new_len)
|
||||
}
|
||||
|
||||
/// Reads data from the device that owns the range containing `addr` and puts it into `data`.
|
||||
///
|
||||
/// Returns true on success, otherwise `data` is untouched.
|
||||
@@ -188,7 +266,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn bus_insert() {
|
||||
let mut bus = Bus::new();
|
||||
let bus = Bus::new();
|
||||
let dummy = Arc::new(Mutex::new(DummyDevice));
|
||||
assert!(bus.insert(dummy.clone(), 0x10, 0).is_err());
|
||||
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
||||
@@ -209,7 +287,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn bus_read_write() {
|
||||
let mut bus = Bus::new();
|
||||
let bus = Bus::new();
|
||||
let dummy = Arc::new(Mutex::new(DummyDevice));
|
||||
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
||||
assert!(bus.read(0x10, &mut [0, 0, 0, 0]));
|
||||
@@ -226,7 +304,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn bus_read_write_values() {
|
||||
let mut bus = Bus::new();
|
||||
let bus = Bus::new();
|
||||
let dummy = Arc::new(Mutex::new(ConstantDevice));
|
||||
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
||||
|
||||
@@ -240,7 +318,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn busrange_cmp_and_clone() {
|
||||
fn busrange_cmp() {
|
||||
let range = BusRange { base: 0x10, len: 2 };
|
||||
assert_eq!(range, BusRange { base: 0x10, len: 3 });
|
||||
assert_eq!(range, BusRange { base: 0x10, len: 2 });
|
||||
@@ -250,17 +328,13 @@ mod tests {
|
||||
|
||||
assert_eq!(range, range.clone());
|
||||
|
||||
let mut bus = Bus::new();
|
||||
let bus = Bus::new();
|
||||
let mut data = [1, 2, 3, 4];
|
||||
assert!(bus
|
||||
.insert(Arc::new(Mutex::new(DummyDevice)), 0x10, 0x10)
|
||||
.is_ok());
|
||||
let device = Arc::new(Mutex::new(DummyDevice));
|
||||
assert!(bus.insert(device.clone(), 0x10, 0x10).is_ok());
|
||||
assert!(bus.write(0x10, &mut data));
|
||||
let bus_clone = bus.clone();
|
||||
assert!(bus.read(0x10, &mut data));
|
||||
assert_eq!(data, [1, 2, 3, 4]);
|
||||
assert!(bus_clone.read(0x10, &mut data));
|
||||
assert_eq!(data, [1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -11,22 +11,35 @@
|
||||
|
||||
use crate::BusDevice;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use kvm_bindings::kvm_msi;
|
||||
use kvm_ioctls::VmFd;
|
||||
use std::io;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use std::{io, result};
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||
};
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to send an interrupt.
|
||||
InterruptFailed(io::Error),
|
||||
/// Invalid destination mode.
|
||||
InvalidDestinationMode,
|
||||
/// Invalid trigger mode.
|
||||
InvalidTriggerMode,
|
||||
/// Invalid delivery mode.
|
||||
InvalidDeliveryMode,
|
||||
/// Failed creating the interrupt source group.
|
||||
CreateInterruptSourceGroup(io::Error),
|
||||
/// Failed triggering the interrupt.
|
||||
TriggerInterrupt(io::Error),
|
||||
/// Failed masking the interrupt.
|
||||
MaskInterrupt(io::Error),
|
||||
/// Failed unmasking the interrupt.
|
||||
UnmaskInterrupt(io::Error),
|
||||
/// Failed updating the interrupt.
|
||||
UpdateInterrupt(io::Error),
|
||||
/// Failed enabling the interrupt.
|
||||
EnableInterrupt(io::Error),
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
@@ -156,8 +169,8 @@ pub struct Ioapic {
|
||||
id: u32,
|
||||
reg_sel: u32,
|
||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||
vm_fd: Arc<VmFd>,
|
||||
apic_address: GuestAddress,
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
impl BusDevice for Ioapic {
|
||||
@@ -196,14 +209,28 @@ impl BusDevice for Ioapic {
|
||||
}
|
||||
|
||||
impl Ioapic {
|
||||
pub fn new(vm_fd: Arc<VmFd>, apic_address: GuestAddress) -> Ioapic {
|
||||
Ioapic {
|
||||
pub fn new(
|
||||
apic_address: GuestAddress,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
) -> Result<Ioapic> {
|
||||
let interrupt_source_group = interrupt_manager
|
||||
.create_group(MsiIrqGroupConfig {
|
||||
base: 0 as InterruptIndex,
|
||||
count: NUM_IOAPIC_PINS as InterruptIndex,
|
||||
})
|
||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||
|
||||
interrupt_source_group
|
||||
.enable()
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
|
||||
Ok(Ioapic {
|
||||
id: 0,
|
||||
reg_sel: 0,
|
||||
reg_entries: [0; NUM_IOAPIC_PINS],
|
||||
vm_fd,
|
||||
apic_address,
|
||||
}
|
||||
interrupt_source_group,
|
||||
})
|
||||
}
|
||||
|
||||
// The ioapic must be informed about EOIs in order to deassert interrupts
|
||||
@@ -223,16 +250,30 @@ impl Ioapic {
|
||||
pub fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||
let entry = &mut self.reg_entries[irq];
|
||||
|
||||
// Don't inject the interrupt if the IRQ is masked
|
||||
if interrupt_mask(*entry) == 1 {
|
||||
return Ok(());
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
debug!("Interrupt successfully delivered");
|
||||
|
||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||
// It will be cleared when the EOI is received.
|
||||
if trigger_mode(*entry) == 1 {
|
||||
set_remote_irr(entry, 1);
|
||||
}
|
||||
// Clear the Delivery Status bit
|
||||
set_delivery_status(entry, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_entry(&self, irq: usize) -> Result<()> {
|
||||
let entry = self.reg_entries[irq];
|
||||
|
||||
// Validate Destination Mode value, and retrieve Destination ID
|
||||
let destination_mode = destination_mode(*entry);
|
||||
let destination_mode = destination_mode(entry);
|
||||
let destination_id: u8 = match destination_mode {
|
||||
x if x == DestinationMode::Physical as u8 => destination_field_physical(*entry),
|
||||
x if x == DestinationMode::Logical as u8 => destination_field_logical(*entry),
|
||||
x if x == DestinationMode::Physical as u8 => destination_field_physical(entry),
|
||||
x if x == DestinationMode::Logical as u8 => destination_field_logical(entry),
|
||||
_ => return Err(Error::InvalidDestinationMode),
|
||||
};
|
||||
|
||||
@@ -242,20 +283,20 @@ impl Ioapic {
|
||||
let redirection_hint: u8 = 1;
|
||||
|
||||
// Generate MSI message address
|
||||
let address_lo: u32 = self.apic_address.0 as u32
|
||||
let low_addr: u32 = self.apic_address.0 as u32
|
||||
| u32::from(destination_id) << 12
|
||||
| u32::from(redirection_hint) << 3
|
||||
| u32::from(destination_mode) << 2;
|
||||
|
||||
// Validate Trigger Mode value
|
||||
let trigger_mode = trigger_mode(*entry);
|
||||
let trigger_mode = trigger_mode(entry);
|
||||
match trigger_mode {
|
||||
x if (x == TriggerMode::Edge as u8) || (x == TriggerMode::Level as u8) => {}
|
||||
_ => return Err(Error::InvalidTriggerMode),
|
||||
}
|
||||
|
||||
// Validate Delivery Mode value
|
||||
let delivery_mode = delivery_mode(*entry);
|
||||
let delivery_mode = delivery_mode(entry);
|
||||
match delivery_mode {
|
||||
x if (x == DeliveryMode::Fixed as u8)
|
||||
|| (x == DeliveryMode::Lowest as u8)
|
||||
@@ -270,37 +311,31 @@ impl Ioapic {
|
||||
|
||||
// Generate MSI message data
|
||||
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(vector(*entry));
|
||||
| u32::from(vector(entry));
|
||||
|
||||
let msi = kvm_msi {
|
||||
address_lo,
|
||||
address_hi: 0x0,
|
||||
let config = MsiIrqSourceConfig {
|
||||
high_addr: 0x0,
|
||||
low_addr,
|
||||
data,
|
||||
flags: 0u32,
|
||||
devid: 0u32,
|
||||
pad: [0u8; 12],
|
||||
};
|
||||
|
||||
match self.vm_fd.signal_msi(msi) {
|
||||
Ok(ret) => {
|
||||
if ret > 0 {
|
||||
debug!("MSI message successfully delivered");
|
||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||
// It will be cleared when the EOI is received.
|
||||
if trigger_mode == 1 {
|
||||
set_remote_irr(entry, 1);
|
||||
}
|
||||
// Clear the Delivery Status bit
|
||||
set_delivery_status(entry, 0);
|
||||
} else {
|
||||
warn!("failed to deliver MSI message, blocked by guest");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(Error::InterruptFailed(e)),
|
||||
self.interrupt_source_group
|
||||
.update(irq as InterruptIndex, InterruptSourceConfig::MsiIrq(config))
|
||||
.map_err(Error::UpdateInterrupt)?;
|
||||
|
||||
if interrupt_mask(entry) == 1 {
|
||||
self.interrupt_source_group
|
||||
.mask(irq as InterruptIndex)
|
||||
.map_err(Error::MaskInterrupt)?;
|
||||
} else {
|
||||
self.interrupt_source_group
|
||||
.unmask(irq as InterruptIndex)
|
||||
.map_err(Error::UnmaskInterrupt)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ioapic_write(&mut self, val: u32) {
|
||||
@@ -320,6 +355,11 @@ impl Ioapic {
|
||||
self.reg_entries[index] &= 0xffff_ffff_0000_5000;
|
||||
self.reg_entries[index] |= u64::from(val) & 0xffff_afff;
|
||||
}
|
||||
// The entry must be updated through the interrupt source
|
||||
// group.
|
||||
if let Err(e) = self.update_entry(index) {
|
||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
||||
}
|
||||
}
|
||||
_ => error!("IOAPIC: invalid write to register offset"),
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use crate::{BusDevice, Interrupt};
|
||||
use crate::BusDevice;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::{io, result};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vmm_sys_util::errno::Result;
|
||||
|
||||
const LOOP_SIZE: usize = 0x40;
|
||||
@@ -55,7 +57,7 @@ const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
|
||||
pub struct Serial {
|
||||
interrupt_enable: u8,
|
||||
interrupt_identification: u8,
|
||||
interrupt: Box<dyn Interrupt>,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
line_control: u8,
|
||||
line_status: u8,
|
||||
modem_control: u8,
|
||||
@@ -67,7 +69,10 @@ pub struct Serial {
|
||||
}
|
||||
|
||||
impl Serial {
|
||||
pub fn new(interrupt: Box<dyn Interrupt>, out: Option<Box<dyn io::Write + Send>>) -> Serial {
|
||||
pub fn new(
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
) -> Serial {
|
||||
Serial {
|
||||
interrupt_enable: 0,
|
||||
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
||||
@@ -84,12 +89,15 @@ impl Serial {
|
||||
}
|
||||
|
||||
/// Constructs a Serial port ready for output.
|
||||
pub fn new_out(interrupt: Box<dyn Interrupt>, out: Box<dyn io::Write + Send>) -> Serial {
|
||||
pub fn new_out(
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
out: Box<dyn io::Write + Send>,
|
||||
) -> Serial {
|
||||
Self::new(interrupt, Some(out))
|
||||
}
|
||||
|
||||
/// Constructs a Serial port with no connected output.
|
||||
pub fn new_sink(interrupt: Box<dyn Interrupt>) -> Serial {
|
||||
pub fn new_sink(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
|
||||
Self::new(interrupt, None)
|
||||
}
|
||||
|
||||
@@ -149,7 +157,7 @@ impl Serial {
|
||||
}
|
||||
|
||||
fn trigger_interrupt(&mut self) -> result::Result<(), io::Error> {
|
||||
self.interrupt.deliver()
|
||||
self.interrupt.trigger(0)
|
||||
}
|
||||
|
||||
fn iir_reset(&mut self) {
|
||||
@@ -233,16 +241,24 @@ mod tests {
|
||||
use super::*;
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
struct TestInterrupt {
|
||||
event_fd: EventFd,
|
||||
}
|
||||
|
||||
impl Interrupt for TestInterrupt {
|
||||
fn deliver(&self) -> result::Result<(), std::io::Error> {
|
||||
impl InterruptSourceGroup for TestInterrupt {
|
||||
fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> {
|
||||
self.event_fd.write(1)
|
||||
}
|
||||
fn update(
|
||||
&self,
|
||||
_index: InterruptIndex,
|
||||
_config: InterruptSourceConfig,
|
||||
) -> result::Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TestInterrupt {
|
||||
@@ -278,7 +294,7 @@ mod tests {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let serial_out = SharedBuffer::new();
|
||||
let mut serial = Serial::new_out(
|
||||
Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
Box::new(serial_out.clone()),
|
||||
);
|
||||
|
||||
@@ -297,7 +313,7 @@ mod tests {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let serial_out = SharedBuffer::new();
|
||||
let mut serial = Serial::new_out(
|
||||
Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
Box::new(serial_out.clone()),
|
||||
);
|
||||
|
||||
@@ -334,8 +350,9 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_thr() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial =
|
||||
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())));
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
|
||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
@@ -354,8 +371,9 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_dlab() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial =
|
||||
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())));
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
|
||||
serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]);
|
||||
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
|
||||
@@ -373,8 +391,9 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_modem() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial =
|
||||
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())));
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
|
||||
serial.write(0, DATA as u64, &['a' as u8]);
|
||||
@@ -397,8 +416,9 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_scratch() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial =
|
||||
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())));
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
|
||||
serial.write(0, SCR as u64, &[0x12 as u8]);
|
||||
|
||||
|
||||
@@ -6,18 +6,21 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
//! Emulates virtual and hardware devices.
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
extern crate byteorder;
|
||||
extern crate epoll;
|
||||
extern crate kvm_bindings;
|
||||
extern crate kvm_ioctls;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "acpi")]
|
||||
extern crate acpi_tables;
|
||||
extern crate vm_device;
|
||||
extern crate vm_memory;
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
use std::fs::File;
|
||||
use std::{io, result};
|
||||
use std::io;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
mod acpi;
|
||||
@@ -26,7 +29,7 @@ pub mod ioapic;
|
||||
pub mod legacy;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
pub use self::acpi::AcpiShutdownDevice;
|
||||
pub use self::acpi::{AcpiGEDDevice, AcpiShutdownDevice};
|
||||
pub use self::bus::{Bus, BusDevice, Error as BusError};
|
||||
|
||||
pub type DeviceEventT = u16;
|
||||
@@ -67,6 +70,11 @@ pub enum Error {
|
||||
IoError(io::Error),
|
||||
}
|
||||
|
||||
pub trait Interrupt: Send + Sync {
|
||||
fn deliver(&self) -> result::Result<(), std::io::Error>;
|
||||
bitflags! {
|
||||
pub struct HotPlugNotificationFlags: u8 {
|
||||
const NO_DEVICES_CHANGED = 0;
|
||||
const CPU_DEVICES_CHANGED = 0b1;
|
||||
const MEMORY_DEVICES_CHANGED = 0b10;
|
||||
const PCI_DEVICES_CHANGED = 0b100;
|
||||
}
|
||||
}
|
||||
|
||||
373
docs/api.md
Normal file
373
docs/api.md
Normal file
@@ -0,0 +1,373 @@
|
||||
- [Cloud Hypervisor API](#cloud-hypervisor-api)
|
||||
* [External API](#external-api)
|
||||
+ [REST API](#rest-api)
|
||||
- [Location and availability](#location-and-availability)
|
||||
- [Endpoints](#endpoints)
|
||||
* [Virtual Machine Manager (VMM) Actions](#virtual-machine-manager-vmm-actions)
|
||||
* [Virtual Machine (VM) Actions](#virtual-machine-vm-actions)
|
||||
- [REST API Examples](#rest-api-examples)
|
||||
* [Create a Virtual Machine](#create-a-virtual-machine)
|
||||
* [Boot a Virtual Machine](#boot-a-virtual-machine)
|
||||
* [Dump a Virtual Machine Information](#dump-a-virtual-machine-information)
|
||||
* [Reboot a Virtual Machine](#reboot-a-virtual-machine)
|
||||
* [Shut a Virtual Machine Down](#shut-a-virtual-machine-down)
|
||||
+ [Command Line Interface](#command-line-interface)
|
||||
+ [REST API and CLI Architecural Relationship](#rest-api-and-cli-architectural-relationship)
|
||||
* [Internal API](#internal-api)
|
||||
+ [Goals and Design](#goals-and-design)
|
||||
* [End to End Example](#end-to-end-example)
|
||||
|
||||
# Cloud Hypervisor API
|
||||
|
||||
The Cloud Hypervisor API is made of 2 distinct interfaces:
|
||||
|
||||
1. **The external API**. This is the user facing API. Users and operators can
|
||||
control and manage Cloud Hypervisor through either a REST API or a Command
|
||||
Line Interface (CLI).
|
||||
1. **The internal API**, based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/)
|
||||
module. This API is used internally by the Cloud Hypervisor threads to
|
||||
communicate between each others.
|
||||
|
||||
The goal of this document is to describe the Cloud Hypervisor API as a whole,
|
||||
and to outline how the internal and external APIs are architecturally related.
|
||||
|
||||
## External API
|
||||
|
||||
### REST API
|
||||
|
||||
The Cloud Hypervisor [REST](https://en.wikipedia.org/wiki/Representational_state_transfer)
|
||||
API triggers VM and VMM specific actions, and as such it is designed as a
|
||||
collection of RPC-style, static methods.
|
||||
|
||||
The API is [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md)
|
||||
compliant. Please consult the [Cloud Hypervisor API](https://raw.githubusercontent.com/cloud-hypervisor/cloud-hypervisor/master/vmm/src/api/openapi/cloud-hypervisor.yaml)
|
||||
document for more details about the API payloads and responses.
|
||||
|
||||
### Location and availability
|
||||
|
||||
The REST API is available as soon as the Cloud Hypervisor binary is started,
|
||||
through a local UNIX socket.
|
||||
By default, it is located at `/run/user/{user ID}/cloud-hypervisor.{Cloud Hypervisor PID}`.
|
||||
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
|
||||
Cloud Hypervisor Guest
|
||||
API server: /tmp/cloud-hypervisor.sock
|
||||
vCPUs: 1
|
||||
Memory: 512 MB
|
||||
Kernel: None
|
||||
Kernel cmdline:
|
||||
Disk(s): None
|
||||
```
|
||||
|
||||
### Endpoints
|
||||
|
||||
The Cloud Hypervisor API exposes the following actions through its endpoints:
|
||||
|
||||
#### Virtual Machine Manager (VMM) Actions
|
||||
|
||||
Action | Endpoint | Request Body | Response Body | Prerequisites
|
||||
------------------------------------|-----------------|--------------|----------------------------|---------------------------
|
||||
Check for the REST API availability | `/vmm.ping` | N/A | `/schemas/VmmPingResponse` | N/A
|
||||
Shut the VMM down | `/vmm.shutdown` | N/A | N/A | The VMM is running
|
||||
|
||||
#### Virtual Machine (VM) Actions
|
||||
|
||||
Action | Endpoint | Request Body | Response Body | Prerequisites
|
||||
-----------------------------------|---------------------|---------------------------|-------------------|---------------------------
|
||||
Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet
|
||||
Delete the VM | `/vm.delete` | N/A | N/A | The VM is created but not booted
|
||||
Boot the VM | `/vm.boot` | N/A | N/A | The VM is created
|
||||
Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted
|
||||
Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted
|
||||
Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted
|
||||
Resume the VM | `/vm.resume` | N/A | N/A | The VM is paused
|
||||
Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted
|
||||
Remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted
|
||||
Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created
|
||||
Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | N/A | The VM is booted
|
||||
Remove VFIO PCI device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted
|
||||
|
||||
### REST API Examples
|
||||
|
||||
For the following set of examples, we assume Cloud Hypervisor is started with
|
||||
the REST API available at `/tmp/cloud-hypervisor.sock`:
|
||||
|
||||
```
|
||||
$ ./target/debug/cloud-hypervisor --api-socket /tmp/cloud-hypervisor.sock
|
||||
Cloud Hypervisor Guest
|
||||
API server: /tmp/cloud-hypervisor.sock
|
||||
vCPUs: 1
|
||||
Memory: 512 MB
|
||||
Kernel: None
|
||||
Kernel cmdline:
|
||||
Disk(s): None
|
||||
```
|
||||
|
||||
#### Create a Virtual Machine
|
||||
|
||||
We want to create a virtual machine with the following characteristics:
|
||||
|
||||
* 4 vCPUs
|
||||
* 1 GB of RAM
|
||||
* 1 virtio based networking interface
|
||||
* Direct kernel boot from a custom 5.5.0 Linux kernel located at
|
||||
`/opt/clh/kernel/vmlinux-virtio-fs-virtio-iommu`
|
||||
* Using a Clear Linux image as its root filesystem, located at
|
||||
`/opt/clh/images/clear-30080-kvm.img`
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||
-X PUT 'http://localhost/api/v1/vm.create' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"cpus":{"boot_vcpus": 4, "max_vcpus": 4},
|
||||
"kernel":{"path":"/opt/clh/kernel/vmlinux-virtio-fs-virtio-iommu"},
|
||||
"cmdline":{"args":"console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3"},
|
||||
"disks":[{"path":"/opt/clh/images/clear-30080-kvm.img"}],
|
||||
"rng":{"src":"/dev/urandom"},
|
||||
"net":[{"ip":"192.168.10.10", "mask":"255.255.255.0", "mac":"12:34:56:78:90:01"}]
|
||||
}'
|
||||
```
|
||||
|
||||
#### Boot a Virtual Machine
|
||||
|
||||
Once the VM is created, we can boot it:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.boot'
|
||||
```
|
||||
|
||||
#### Dump a Virtual Machine Information
|
||||
|
||||
We can fetch information about any VM, as soon as it's created:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||
-X GET 'http://localhost/api/v1/vm.info' \
|
||||
-H 'Accept: application/json'
|
||||
```
|
||||
|
||||
#### Reboot a Virtual Machine
|
||||
|
||||
We can reboot a VM that's already booted:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.reboot'
|
||||
```
|
||||
|
||||
#### Shut a Virtual Machine Down
|
||||
|
||||
Once booted, we can shut a VM down from the REST API:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i -X PUT 'http://localhost/api/v1/vm.shutdown'
|
||||
```
|
||||
|
||||
### Command Line Interface
|
||||
|
||||
The Cloud Hypervisor Command Line Interface (CLI) can only be used for launching
|
||||
the Cloud Hypervisor binary, i.e. it can not be used for controlling the VMM or
|
||||
the launched VM once they're up and running.
|
||||
|
||||
If you want to inspect the VMM, or control the VM after launching Cloud
|
||||
Hypervisor from the CLI, you must use the [REST API](#rest-api).
|
||||
|
||||
From the CLI, one can either:
|
||||
|
||||
1. Create and boot a complete virtual machine by using the CLI options to build
|
||||
the VM config. Run `cloud-hypervisor --help` for a complete list of CLI
|
||||
options. As soon as the `cloud-hypervisor` binary is launched, the
|
||||
[REST API](#rest-api) is available for controlling and managing the VM.
|
||||
1. Start the [REST API](#rest-api) server only, by not passing any VM
|
||||
configuration options. The VM can then be asynchronously created and booted
|
||||
by sending HTTP commands to the [REST API](#rest-api). Check the
|
||||
[REST API examples](#rest-api-examples) section for more details.
|
||||
|
||||
### REST API and CLI Architectural Relationship
|
||||
|
||||
The REST API and the CLI both rely on a common, [internal API](#internal-api).
|
||||
|
||||
The CLI options are parsed by the
|
||||
[clap crate](https://docs.rs/clap/2.33.0/clap/) and then translated into
|
||||
[internal API](#internal-api) commands.
|
||||
|
||||
The REST API is processed by an HTTP thread using the
|
||||
[Firecracker's `micro_http`](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
|
||||
crate. As with the CLI, the HTTP requests eventually get translated into
|
||||
[internal API](#internal-api) commands.
|
||||
|
||||
As a summary, the REST API and the CLI are essentially frontends for the
|
||||
[internal API](#internal-api):
|
||||
|
||||
```
|
||||
+------------------+
|
||||
REST API | |
|
||||
+--------->+ micro_http +--------+
|
||||
| | | |
|
||||
| +------------------+ |
|
||||
| | +------------------------+
|
||||
| | | |
|
||||
+------------+ | | | |
|
||||
| | | | | +--------------+ |
|
||||
| User +---------+ +------> | Internal API | |
|
||||
| | | | | +--------------+ |
|
||||
+------------+ | | | |
|
||||
| | | |
|
||||
| | +------------------------+
|
||||
| +----------+ | VMM
|
||||
| CLI | | |
|
||||
+----------->+ clap +--------------+
|
||||
| |
|
||||
+----------+
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Internal API
|
||||
|
||||
The Cloud Hypervisor internal API, as its name suggests, is used internally
|
||||
by the different Cloud Hypervisor threads (VMM, HTTP, control loop, etc) to
|
||||
send commands and responses to each others.
|
||||
|
||||
It is based on [rust's Multi-Producer, Single-Consumer (MPSC)](https://doc.rust-lang.org/std/sync/mpsc/),
|
||||
and the single consumer (a.k.a. the API receiver) is the Cloud Hypervisor
|
||||
control loop.
|
||||
|
||||
API producers are the HTTP thread handling the [REST API](#rest-api) and the
|
||||
main thread that initially parses the [CLI](#command-line-interface).
|
||||
|
||||
### Goals and Design
|
||||
|
||||
The internal API is designed for controlling, managing and inspecting a Cloud
|
||||
Hypervisor VMM and its guest. It is a backend for handling external, user
|
||||
visible requests through either the [REST API](#rest-api) or the
|
||||
[CLI](#command-line-interface) interfaces.
|
||||
|
||||
The API follows a command-response scheme that closely maps the [REST API](#rest-api).
|
||||
Any command must be replied to with a response.
|
||||
|
||||
Commands are [MPSC](https://doc.rust-lang.org/std/sync/mpsc/) based messages and
|
||||
are received and processed by the VMM control loop.
|
||||
|
||||
In order for the VMM control loop to respond to any internal API command, it
|
||||
must be able to send a response back to the MPSC sender. For that purpose, all
|
||||
internal API command payload carry the [Sender](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html)
|
||||
end of an [MPSC](https://doc.rust-lang.org/std/sync/mpsc/) channel.
|
||||
|
||||
The sender of any internal API command is therefore responsible for:
|
||||
|
||||
1. Creating an [MPSC](https://doc.rust-lang.org/std/sync/mpsc/) response
|
||||
channel.
|
||||
1. Passing the [Sender](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html)
|
||||
end of the response channel as part of the internal API command payload.
|
||||
1. Waiting for the internal API command's response on the [Receiver](https://doc.rust-lang.org/std/sync/mpsc/struct.Receiver.html)
|
||||
end of the response channel.
|
||||
|
||||
## End to End Example
|
||||
|
||||
In order to further understand how the external and internal Cloud Hypervisor
|
||||
APIs work together, let's look at a complete VM creation flow, from the
|
||||
[REST API](#rest-api) call, to the reply the external user will receive:
|
||||
|
||||
1. A user or operator sends an HTTP request to the Cloud Hypervisor
|
||||
[REST API](#rest-api) in order to creates a virtual machine:
|
||||
```
|
||||
shell
|
||||
#!/bin/bash
|
||||
|
||||
curl --unix-socket /tmp/cloud-hypervisor.sock -i \
|
||||
-X PUT 'http://localhost/api/v1/vm.create' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"cpus":{"boot_vcpus": 4, "max_vcpus": 4},
|
||||
"kernel":{"path":"/opt/clh/kernel/vmlinux-virtio-fs-virtio-iommu"},
|
||||
"cmdline":{"args":"console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3"},
|
||||
"disks":[{"path":"/opt/clh/images/clear-30080-kvm.img"}],
|
||||
"rng":{"src":"/dev/urandom"},
|
||||
"net":[{"ip":"192.168.10.10", "mask":"255.255.255.0", "mac":"12:34:56:78:90:01"}]
|
||||
}'
|
||||
```
|
||||
1. The Cloud Hypervisor HTTP thread processes the request and de-serializes the
|
||||
HTTP request JSON body into an internal `VmConfig` structure.
|
||||
1. The Cloud Hypervisor HTTP thread creates an
|
||||
[MPSC](https://doc.rust-lang.org/std/sync/mpsc/) channel for the internal API
|
||||
server to send its response back.
|
||||
1. The Cloud Hypervisor HTTP thread prepares an internal API command for creating a
|
||||
virtual machine. The command's payload is made of the de-serialized
|
||||
`VmConfig` structure and the response channel:
|
||||
```Rust
|
||||
VmCreate(Arc<Mutex<VmConfig>>, Sender<ApiResponse>)
|
||||
```
|
||||
1. The Cloud Hypervisor HTTP thread sends the internal API command, and waits
|
||||
for the response:
|
||||
```Rust
|
||||
// Send the VM creation request.
|
||||
api_sender
|
||||
.send(ApiRequest::VmCreate(config, response_sender))
|
||||
.map_err(ApiError::RequestSend)?;
|
||||
api_evt.write(1).map_err(ApiError::EventFdWrite)?;
|
||||
|
||||
response_receiver.recv().map_err(ApiError::ResponseRecv)??;
|
||||
```
|
||||
1. The Cloud Hypervisor control loop receives the command, as it listens on the
|
||||
internal API [MPSC](https://doc.rust-lang.org/std/sync/mpsc/) channel:
|
||||
```Rust
|
||||
// Read from the API receiver channel
|
||||
let api_request = api_receiver.recv().map_err(Error::ApiRequestRecv)?;
|
||||
```
|
||||
1. The Cloud Hypervisor control loop matches the received internal API against
|
||||
the `VmCreate` payload, and extracts both the `VmConfig` structure and the
|
||||
[Sender](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html) from the
|
||||
command payload. It stores the `VmConfig` structure and replies back to the
|
||||
sender ((The HTTP thread):
|
||||
```Rust
|
||||
match api_request {
|
||||
ApiRequest::VmCreate(config, sender) => {
|
||||
// We only store the passed VM config.
|
||||
// The VM will be created when being asked to boot it.
|
||||
let response = if self.vm_config.is_none() {
|
||||
self.vm_config = Some(config);
|
||||
Ok(ApiResponsePayload::Empty)
|
||||
} else {
|
||||
Err(ApiError::VmAlreadyCreated)
|
||||
};
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
```
|
||||
1. The Cloud Hypervisor HTTP thread receives the internal API command response
|
||||
as the return value from its `VmCreate` HTTP handler. Depending on the
|
||||
control loop internal API response, it generates the appropriate HTTP
|
||||
response:
|
||||
```Rust
|
||||
// Call vm_create()
|
||||
match vm_create(api_notifier, api_sender, Arc::new(Mutex::new(vm_config)))
|
||||
.map_err(HttpError::VmCreate)
|
||||
{
|
||||
Ok(_) => Response::new(Version::Http11, StatusCode::NoContent),
|
||||
Err(e) => error_response(e, StatusCode::InternalServerError),
|
||||
}
|
||||
```
|
||||
1. The Cloud Hypervisor HTTP thread sends the formed HTTP response back to the
|
||||
user. This is abstracted by the
|
||||
[micro_http](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
|
||||
crate.
|
||||
|
||||
@@ -45,7 +45,7 @@ sudo swupd bundle-add os-installer
|
||||
wget https://download.clearlinux.org/current/config/image/cloudguest.yaml
|
||||
sed -i '/size: \"864M\"/d' cloudguest.yaml
|
||||
sed -i 's/\"800M\"/\"2G\"/g' cloudguest.yaml
|
||||
sed -i 's/bootloader,/bootloader,\n iperf,/g' cloudguest.yaml
|
||||
sed -i 's/bootloader,/bootloader,\n curl,\n iperf,/g' cloudguest.yaml
|
||||
sed -i 's/systemd-networkd-autostart/sysadmin-basic,\n systemd-networkd-autostart/g' cloudguest.yaml
|
||||
# Create the custom cloudguest image
|
||||
clr-installer -c cloudguest.yaml
|
||||
|
||||
208
docs/device_model.md
Normal file
208
docs/device_model.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# Device Model
|
||||
|
||||
This document describes the device model supported by `cloud-hypervisor`.
|
||||
|
||||
## Summary
|
||||
|
||||
| Device | Build configurable | Enabled by default | Runtime configurable |
|
||||
| :----: | :----: | :----: | :----: |
|
||||
| Serial port | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| RTC/CMOS | :heavy_check_mark: | :heavy_check_mark: | :negative_squared_cross_mark: |
|
||||
| I/O APIC | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| i8042 shutdown/reboot | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :negative_squared_cross_mark: |
|
||||
| ACPI shutdown/reboot | :negative_squared_cross_mark: | :heavy_check_mark: | :negative_squared_cross_mark: |
|
||||
| virtio-blk | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| virtio-console | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| virtio-iommu | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| virtio-net | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| virtio-pmem | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| virtio-rng | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| virtio-vsock | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| vhost-user-blk | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| vhost-user-fs | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| vhost-user-net | :negative_squared_cross_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
| VFIO | :heavy_check_mark: | :negative_squared_cross_mark: | :heavy_check_mark: |
|
||||
|
||||
## Legacy devices
|
||||
|
||||
### Serial port
|
||||
|
||||
Simple emulation of a serial port by reading and writing to specific port I/O
|
||||
addresses. Used as the default console for Linux when booting with the option
|
||||
`console=ttyS0`, the serial port can be very useful to gather early logs from
|
||||
the operating system booted inside the VM.
|
||||
|
||||
This device is always built-in, and it is disabled by default. It can be
|
||||
enabled with the `--serial` option, as long as its parameter is not `off`.
|
||||
|
||||
### RTC/CMOS
|
||||
|
||||
For environments such as Windows or EFI which cannot rely on KVM clock, the
|
||||
emulation of this legacy device makes the platform usable.
|
||||
|
||||
This device is built-in by default, but it can be compiled out with Rust
|
||||
features. When compiled in, it is always enabled, and cannot be disabled
|
||||
from the command line.
|
||||
|
||||
### I/O APIC
|
||||
|
||||
`cloud-hypervisor` supports a so-called split IRQ chip implementation by
|
||||
implementing support for the [IOAPIC](https://wiki.osdev.org/IOAPIC).
|
||||
By moving part of the IRQ chip implementation from kernel space to user space,
|
||||
the IRQ chip emulation does not always run in a fully privileged mode.
|
||||
|
||||
The device is always built-in, and it is enabled depending on the presence of
|
||||
the serial port. If the serial port is disabled, and because no other device
|
||||
would require pin based interrupts (INTx), the I/O APIC is disabled.
|
||||
|
||||
### i8042
|
||||
|
||||
Simplified PS/2 port since it supports only one key to trigger a reboot or
|
||||
shutdown, depending on the ACPI support.
|
||||
|
||||
This device is always built-in, but it is disabled by default. Because ACPI is
|
||||
enabled by default, the handling of reboot/shutdown goes through the dedicated
|
||||
ACPI device. In case ACPI is disabled, this device is enabled to bring to the
|
||||
VM some reboot/shutdown support.
|
||||
|
||||
### ACPI device
|
||||
|
||||
This is a dedicated device for handling ACPI shutdown and reboot when ACPI is
|
||||
enabled.
|
||||
|
||||
This device is always built-in, and it is enabled by default since the ACPI
|
||||
feature is enabled by default.
|
||||
|
||||
## Virtio devices
|
||||
|
||||
For all virtio devices listed below, both `virtio-mmio` and `virtio-pci`
|
||||
transport layers are supported, `virtio-pci` being the default.
|
||||
|
||||
Both `virtio-mmio` and `virtio-pci` can be compiled out. `virtio-pci` is
|
||||
built-in by default, and enabled by default. If both transport layers were
|
||||
built at the same time, `virtio-pci` would be the default transport layer.
|
||||
|
||||
### virtio-block
|
||||
|
||||
The `virtio-blk` device exposes a block device to the guest. This device is
|
||||
usually used to boot the operating system running in the VM.
|
||||
|
||||
This device is always built-in, and it is enabled based on the presence of the
|
||||
flag `--disk`.
|
||||
|
||||
### virtio-console
|
||||
|
||||
`cloud-hypervisor` exposes a `virtio-console` device to the guest. Although
|
||||
using this device as a guest console can potentially cut some early boot
|
||||
messages, it can reduce the guest boot time and provides a complete console
|
||||
implementation.
|
||||
|
||||
This device is always built-in, and it is enabled by default to provide a guest
|
||||
console. It can be disabled, switching back to the legacy serial port by
|
||||
selecting `--serial tty --console off` from the command line.
|
||||
|
||||
### virtio-iommu
|
||||
|
||||
As we want to improve our nested guests support, we added support for exposing
|
||||
a [paravirtualized IOMMU](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/iommu.md)
|
||||
device through virtio. This allows for a safer nested virtio and directly
|
||||
assigned devices support.
|
||||
|
||||
This device is always built-in, and it is enabled based on the presence of the
|
||||
parameter `iommu=on` in any of the virtio or VFIO devices. If at least one of
|
||||
these devices needs to be connected to the paravirtualized IOMMU, the
|
||||
`virtio-iommu` device will be created.
|
||||
|
||||
### virtio-net
|
||||
|
||||
The `virtio-net` device provides network connectivity for the guest, as it
|
||||
creates a network interface connected to a TAP interface automatically created
|
||||
by the `cloud-hypervisor` on the host.
|
||||
|
||||
This device is always built-in, and it is enabled based on the presence of the
|
||||
flag `--net`.
|
||||
|
||||
### virtio-pmem
|
||||
|
||||
The `virtio-pmem` implementation emulates a virtual persistent memory device
|
||||
that `cloud-hypervisor` can e.g. boot from. Booting from a `virtio-pmem` device
|
||||
allows to bypass the guest page cache and improve the guest memory footprint.
|
||||
|
||||
This device is always built-in, and it is enabled based on the presence of the
|
||||
flag `--pmem`.
|
||||
|
||||
### virtio-rng
|
||||
|
||||
A VM does not generate entropy like a real machine would, which is an issue
|
||||
when workloads running in the guest need random numbers to be generated. The
|
||||
`virtio-rng` device provides entropy to the guest by relying on the generator
|
||||
that can be found on the host. By default, the chosen source of entropy is
|
||||
`/dev/urandom`.
|
||||
|
||||
This device is always built-in, and it is always enabled. The `--rng` flag can
|
||||
be used to change the source of entropy.
|
||||
|
||||
### virtio-vsock
|
||||
|
||||
In order to more efficiently and securely communicate between host and guest,
|
||||
we added a hybrid implementation of the [VSOCK](http://man7.org/linux/man-pages/man7/vsock.7.html)
|
||||
socket address family over virtio.
|
||||
Credits go to the [Firecracker](https://github.com/firecracker-microvm/firecracker/blob/master/docs/vsock.md)
|
||||
project as our implementation is a copy of theirs.
|
||||
|
||||
This device is always built-in, and it is enabled based on the presence of the
|
||||
flag `--vsock`.
|
||||
|
||||
## Vhost-user devices
|
||||
|
||||
Vhost-user devices are virtio backends running outside of the VMM, as its own
|
||||
separate process. They are usually used to bring more flexibility and increased
|
||||
isolation.
|
||||
|
||||
### vhost-user-blk
|
||||
|
||||
As part of the general effort to offload paravirtualized I/O to external
|
||||
processes, we added support for vhost-user-blk backends. This enables
|
||||
`cloud-hypervisor` users to plug a `vhost-user` based block device (e.g. SPDK)
|
||||
into the VMM as their virtio block backend.
|
||||
|
||||
This device is always built-in, and it is enabled when `vhost_user=true` and
|
||||
`socket` are provided to the `--disk` parameter.
|
||||
|
||||
### vhost-user-fs
|
||||
|
||||
`cloud-hypervisor` supports the [virtio-fs](https://virtio-fs.gitlab.io/)
|
||||
shared file system, allowing for an efficient and reliable way of sharing
|
||||
a filesystem between the host and the cloud-hypervisor guest.
|
||||
|
||||
See our [filesystem sharing](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/fs.md)
|
||||
documentation for more details on how to use virtio-fs with cloud-hypervisor.
|
||||
|
||||
This device is always built-in, and it is enabled based on the presence of the
|
||||
flag `--fs`.
|
||||
|
||||
### vhost-user-net
|
||||
|
||||
As part of the general effort to offload paravirtualized I/O to external
|
||||
processes, we added support for [vhost-user-net](https://access.redhat.com/solutions/3394851)
|
||||
backends. This enables `cloud-hypervisor` users to plug a `vhost-user` based
|
||||
networking device (e.g. DPDK) into the VMM as their virtio network backend.
|
||||
|
||||
This device is always built-in, and it is enabled when `vhost_user=true` and
|
||||
`socket` are provided to the `--net` parameter.
|
||||
|
||||
## VFIO
|
||||
|
||||
VFIO (Virtual Function I/O) is a kernel framework that exposes direct device
|
||||
access to userspace. `cloud-hypervisor` uses VFIO to directly assign host
|
||||
physical devices into its guest.
|
||||
|
||||
See our [VFIO documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/vfio.md)
|
||||
for more details on how to directly assign host devices to `cloud-hypervisor`
|
||||
guests.
|
||||
|
||||
Because VFIO implies `vfio-pci` in the `cloud-hypervisor` context, the VFIO
|
||||
support is built-in when the `pci` feature is selected. And because the `pci`
|
||||
feature is built-in by default, VFIO support is also built-in by default.
|
||||
When VFIO support is built-in, a physical device can be passed through, using
|
||||
the flag `--device` in order to enable the VFIO code.
|
||||
44
docs/fs.md
44
docs/fs.md
@@ -10,11 +10,12 @@ __virtio-fs__, also known as __vhost-user-fs__ is a virtual device defined by th
|
||||
|
||||
This virtual device relies on the _vhost-user_ protocol, which assumes the backend (device emulation) is handled by a dedicated process running on the host. This daemon is called __virtiofsd__ and needs to be present on the host.
|
||||
|
||||
_Install virtiofsd_
|
||||
_Build virtiofsd_
|
||||
```bash
|
||||
VIRTIOFSD_URL="$(curl --silent https://api.github.com/repos/intel/nemu/releases/latest | grep "browser_download_url" | grep "virtiofsd-x86_64" | grep -o 'https://.*[^ "]')"
|
||||
wget --quiet $VIRTIOFSD_URL -O "virtiofsd"
|
||||
chmod +x "virtiofsd"
|
||||
git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $VIRTIOFSD_DIR
|
||||
cd $VIRTIOFSD_DIR
|
||||
./configure --prefix=$PWD --target-list=x86_64-softmmu
|
||||
make virtiofsd -j `nproc`
|
||||
sudo setcap cap_sys_admin+epi "virtiofsd"
|
||||
```
|
||||
_Create shared directory_
|
||||
@@ -25,17 +26,20 @@ _Run virtiofsd_
|
||||
```bash
|
||||
./virtiofsd \
|
||||
-d \
|
||||
-o vhost_user_socket=/tmp/virtiofs \
|
||||
--socket-path=/tmp/virtiofs \
|
||||
-o source=/tmp/shared_dir \
|
||||
-o cache=none
|
||||
```
|
||||
The `cache=none` option here is an important one as it tells the daemon not to try any memory mapping of the files, but instead to use the _virtqueues_ to convey the files content. The support for the memory mapping of the files will be added later.
|
||||
|
||||
The `cache=none` option should be the default when using `virtiofsd` with the __cloud-hypervisor__ VMM. This prevents from using the guest page cache, which reduces the memory footprint of the guest. When running multiple virtual machines on the same host, this will let the host deal with page cache, which will increase the density of virtual machines which can be launched.
|
||||
|
||||
The `cache=always` option will allow for the guest page cache to be used, which will increase the memory footprint of the guest. This option should be used only for specific use cases where a single VM is going to be running on a host.
|
||||
|
||||
### The kernel
|
||||
|
||||
In order to leverage __virtio-fs__ support from within the guest, and because the code has not been merged in upstream Linux kernel yet, it is required to build a custom kernel embedding the patches.
|
||||
|
||||
The following branch `virtio-pmem_and_virtio-fs` on the repository https://github.com/sboeuf/linux.git includes all the needed patches to support __virtio-fs__.
|
||||
The following branch `virtio-fs-virtio-iommu` on the repository https://github.com/cloud-hypervisor/linux.git includes all the needed patches to support __virtio-fs__.
|
||||
|
||||
Make sure to build a kernel out of this branch that can be then used to boot the VM.
|
||||
|
||||
@@ -56,15 +60,29 @@ Assuming you have `clear-kvm.img` and `custom-vmlinux.bin` on your system, here
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512
|
||||
--fs tag=myfs,sock=/tmp/virtiofs,num_queues=1,queue_size=512
|
||||
```
|
||||
|
||||
By default, DAX is enabled with a cache window of 8GiB. You can specify a custom size (let's say 4GiB for this example) for the cache by explicitly setting DAX and the cache size:
|
||||
|
||||
```bash
|
||||
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512,dax=on,cache_size=4G
|
||||
|
||||
```
|
||||
|
||||
In case you don't want to use a shared window of cache to pass the shared files content, this means you will have to explicitly disable DAX with `dax=off`. Note that in this case, the `cache_size` parameter will be ignored.
|
||||
|
||||
```bash
|
||||
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512,dax=off
|
||||
|
||||
```
|
||||
|
||||
### Mount the shared directory
|
||||
The last step is to mount the shared directory inside the guest, using the `virtio_fs` filesystem type.
|
||||
The last step is to mount the shared directory inside the guest, using the `virtiofs` filesystem type.
|
||||
```bash
|
||||
mkdir mount_dir
|
||||
mount \
|
||||
-t virtio_fs /dev/null mount_dir/ \
|
||||
-o tag=virtiofs,rootmode=040000,user_id=0,group_id=0
|
||||
mount -t virtiofs -o dax myfs mount_dir/
|
||||
```
|
||||
The `tag` needs to be consistent with what has been provided through the __cloud-hypervisor__ command line.
|
||||
The `tag` needs to be consistent with what has been provided through the __cloud-hypervisor__ command line, which happens to be `myfs` in this example.
|
||||
|
||||
The `-o dax` option must be removed in case the shared cache region is not enabled from the VMM.
|
||||
|
||||
113
docs/hotplug.md
Normal file
113
docs/hotplug.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Cloud Hypervisor Hot Plug
|
||||
|
||||
Currently Cloud Hypervisor only support hot plugging of CPU devices.
|
||||
|
||||
## Kernel support
|
||||
|
||||
For hotplug on Cloud Hypervisor ACPI GED support is needed. This can either be achieved by turning on `CONFIG_ACPI_REDUCED_HARDWARE_ONLY`
|
||||
or by using this kernel patch (available in 5.5rc1 and later): https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/patch/drivers/acpi/Makefile?id=ac36d37e943635fc072e9d4f47e40a48fbcdb3f0
|
||||
|
||||
This patch is integrated into the Clear Linux KVM and cloudguest images.
|
||||
|
||||
## CPU Hot Plug
|
||||
|
||||
Extra vCPUs can be added and removed from a running Cloud Hypervisor instance. This is controlled by two mechanisms:
|
||||
|
||||
1. Specifying a number of maximum potential vCPUs that is greater than the number of default (boot) vCPUs.
|
||||
2. Making a HTTP API request to the VMM to ask for the additional vCPUs to be added.
|
||||
|
||||
To use CPU hotplug start the VM with the number of max vCPUs greater than the number of boot vCPUs, e.g.
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./hypervisor-fw \
|
||||
--disk path=clear-31890-kvm.img \
|
||||
--cpus boot=4,max=8 \
|
||||
--memory size=1024M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--rng \
|
||||
--api-socket=/tmp/ch-socket
|
||||
$ popd
|
||||
```
|
||||
|
||||
Notice the addition of `--api-socket=/tmp/ch-socket` and a `max` parameter on `--cpus boot=4.max=8`.
|
||||
|
||||
To ask the VMM to add additional vCPUs then use the resize API:
|
||||
|
||||
```shell
|
||||
curl -H "Accept: application/json" -H "Content-Type: application/json" -i -XPUT --unix-socket /tmp/ch-socket -d "{ \"desired_vcpus\":8}" http://localhost/api/v1/vm.resize
|
||||
```
|
||||
|
||||
The extra vCPU threads will be created and advertised to the running kernel. The kernel does not bring up the CPUs immediately and instead the user must "online" them from inside the VM:
|
||||
|
||||
```shell
|
||||
root@ch-guest ~ # lscpu | grep list:
|
||||
On-line CPU(s) list: 0-3
|
||||
Off-line CPU(s) list: 4-7
|
||||
root@ch-guest ~ # echo 1 | tee /sys/devices/system/cpu/cpu[4,5,6,7]/online
|
||||
1
|
||||
root@ch-guest ~ # lscpu | grep list:
|
||||
On-line CPU(s) list: 0-7
|
||||
```
|
||||
|
||||
After a reboot the added CPUs will remain.
|
||||
|
||||
Removing CPUs works similarly by reducing the number in the "desired_vcpus" field of the reisze API. The CPUs will be automatically offlined inside the guest so there is no need to run any commands inside the guest:
|
||||
|
||||
```shell
|
||||
curl -H "Accept: application/json" -H "Content-Type: application/json" -i -XPUT --unix-socket /tmp/ch-socket -d "{ \"desired_vcpus\":2}" http://localhost/api/v1/vm.resize
|
||||
```
|
||||
|
||||
As per adding CPUs to the guest, after a reboot the VM will be running with the reduced number of vCPUs.
|
||||
|
||||
## Memory Hot Plug
|
||||
|
||||
Extra memory can be added from a runing Cloud Hypervisor instance. This is controlled by two mechanisms:
|
||||
|
||||
1. Allocating some of the guest physical address space for hotplug memory.
|
||||
2. Making a HTTP API request to the VMM to ask for a new amount of RAM to be assigned to the VM. In the case of expanding the memory for the VM the new memory will be hotplugged into the running VM, if reducing the size of the memory then change will take effect after the next reboot.
|
||||
|
||||
To use memory hotplug start the VM specifying some size RAM in the "hotplug_size" parameter to the memory configuration. Not all the memory specified in this parameter will be available to hotplug as there are spacing and alignment requirements so it is recommended to make it larger than the hotplug RAM needed.
|
||||
|
||||
```shell
|
||||
$ pushd $CLOUDH
|
||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
--kernel ./hypervisor-fw \
|
||||
--disk path=clear-31890-kvm.img \
|
||||
--cpus boot=4,max=8 \
|
||||
--memory size=1024M,hotplug_size=8192M \
|
||||
--net "tap=,mac=,ip=,mask=" \
|
||||
--rng \
|
||||
--api-socket=/tmp/ch-socket
|
||||
$ popd
|
||||
```
|
||||
|
||||
Before issuing the API request it is necessary to run the following command inside the VM to make it automatically online the added memory:
|
||||
|
||||
```shell
|
||||
root@ch-guest ~ # echo online | sudo tee /sys/devices/system/memory/auto_online_blocks
|
||||
```
|
||||
|
||||
To ask the VMM to add expand the RAM for the VM (request is in bytes):
|
||||
|
||||
```shell
|
||||
curl -H "Accept: application/json" -H "Content-Type: application/json" -i -XPUT --unix-socket /tmp/ch-socket -d "{ \"desired_vcpus\": 4, \"desired_ram\" : 3221225472}" http://localhost/api/v1/vm.resize
|
||||
```
|
||||
|
||||
The new memory is now available to use inside the VM:
|
||||
|
||||
```shell
|
||||
free -h
|
||||
total used free shared buff/cache available
|
||||
Mem: 3.0Gi 71Mi 2.8Gi 0.0Ki 47Mi 2.8Gi
|
||||
Swap: 32Mi 0B 32Mi
|
||||
```
|
||||
|
||||
Due to guest OS limitations is is necessary to ensure that amount of memory added (between currently assigned RAM and that which is desired) is a multiple of 128MiB.
|
||||
|
||||
The same API can also be used to reduce the desired RAM for a VM but the change will not be applied until the VM is rebooted.
|
||||
|
||||
Memory and CPU resizing can be combined together into the same HTTP API request.
|
||||
@@ -1,6 +1,21 @@
|
||||
# How to use networking
|
||||
|
||||
cloud-hypervisor can emulate one or more virtual network interfaces, represented at the hypervisor host by [tap devices](https://www.kernel.org/doc/Documentation/networking/tuntap.txt"). This guide briefly describes, in a manual and distribution neutral way, how to setup and use networking with cloud-hypevisor.
|
||||
cloud-hypervisor can emulate one or more virtual network interfaces, represented at the hypervisor host by [tap devices](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). This guide briefly describes, in a manual and distribution neutral way, how to setup and use networking with cloud-hypevisor.
|
||||
|
||||
## Multiple queue support for net devices ##
|
||||
|
||||
While multiple vcpus defined for guest, to gain the benefit of vcpu scalable to improve performance, it suggests to define multiple queue pairs for net devices, one Tx/Rx queue pair per one vcpu, that means the number of queue pairs at least is equal to the vcpu count. In that case, after virtnet driver set cpu affinity for virtqueues in guest kernel, vcpus could handle interrupt from different virtqueue pairs in parallel.
|
||||
|
||||
It will gain better performance for guest that has multiple queues defined for net devices while it has multiple net sessions running in userspace.
|
||||
|
||||
To enable multiple queue support in cloud-hypervisor, multiple queue pairs will be defined, while multiple tap fds will be opened for the same tap device, it will also have multiple threads started, each thread will monitor and handle the events from each virtqueue pairs and the associated tap fd.
|
||||
|
||||
Note:
|
||||
|
||||
- Currently, it does not support to use ethtool to change the combined queue numbers in guest.
|
||||
- Multiple queue is enabled for vhost-user-net backend in cloud-hypervisor, however, multiple thread is not added to handle mq, thus, the performance for vhost-user-net backend is not supposed to be improved. The multiple thread will be added for backend later.
|
||||
- Performance test for vhost-user-net will be covered once vhost-user-net backend has mulitple thread supported.
|
||||
- Performance test for virtio-net is done by comparing 2 queue pairs with 1 queue pairs, that to run 2 iperf3 sessions in the same test environments, throughput is improved about 37%.
|
||||
|
||||
## Start cloud-hypervisor with net devices
|
||||
|
||||
@@ -13,18 +28,34 @@ Use one `--net` command-line argument from cloud-hypervisor to specify the emula
|
||||
--disk path=my-root-disk.img \
|
||||
--kernel my-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0 \
|
||||
tap=ich1,mac=a4:a1:c2:00:00:02,ip=10.0.1.2,mask=255.255.255.0
|
||||
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0,num_queues=2,queue_size=256 \
|
||||
tap=ich1,mac=a4:a1:c2:00:00:02,ip=10.0.1.2,mask=255.255.255.0,num_queues=2,queue_size=256
|
||||
```
|
||||
|
||||
|
||||
The `--net` argument takes 1 or more space-separated strings of key value pairs containing the following 4 keys or fields:
|
||||
|
||||
| Name | Purpose | Optional |
|
||||
| -------- |----------------------------| ----------|
|
||||
| tap | tap device name | Yes |
|
||||
| mac | vNIC mac address | Yes |
|
||||
| ip | tap IP IP address | yes |
|
||||
| mask | tap IP netmask | Yes |
|
||||
| Name | Purpose | Optional |
|
||||
| -----------|----------------------------| ----------|
|
||||
| tap | tap device name | Yes |
|
||||
| mac | vNIC mac address | Yes |
|
||||
| ip | tap IP IP address | yes |
|
||||
| mask | tap IP netmask | Yes |
|
||||
| num_queues | the number of queues | yes |
|
||||
| queue_size | the size of each queue | Yes |
|
||||
|
||||
num_queues is the total number of tx and rx queues, the default value is 2, and it could be increased by multiples of 2. Additionally, num_queues is suggested to be as 2 times of vcpu count. The default value for queue_size is 256.
|
||||
|
||||
If the tap device is pre-created on host before guest boot up. To use multiple queue support for net device in guest, the tap device should be opened like this from host.
|
||||
|
||||
```bash
|
||||
[root@localhost ~]# ip tuntap add name ich0 mode tap multi_queue
|
||||
```
|
||||
|
||||
And the `--net` device should specify support for multiple queues. `num_queues` must be a multiple of 2 starting at least from 4 since multiple queues really means multiple queue pairs. We need at least 2 pairs for this configuration to be correct:
|
||||
|
||||
```bash
|
||||
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0,num_queues=4,queue_size=256
|
||||
```
|
||||
|
||||
## Configure the tap devices
|
||||
|
||||
|
||||
132
docs/vhost-user-net-testing.md
Normal file
132
docs/vhost-user-net-testing.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# How to test Vhost-user net with OpenVSwitch/DPDK
|
||||
|
||||
The purpose of this document is to illustrate how to test vhost-user-net in cloud-hypervisor with OVS/DPDK as the backend.
|
||||
|
||||
## Framework
|
||||
|
||||
It's a simple test to validate the communication between two virtual machine, connecting them to vhost-user ports respectively provided by `OVS/DPDK`.
|
||||
```
|
||||
+----+----------+ +-------------+-----------+-------------+ +----------+----+
|
||||
| | | | | | | | | |
|
||||
| |vhost-user|----------| vhost-user | ovs | vhost-user |----------|vhost-user| |
|
||||
| |net device| | port 1 | | port 2 | |net device| |
|
||||
| | | | | | | | | |
|
||||
| +----------+ +-------------+-----------+-------------+ +----------+ |
|
||||
| | | | | |
|
||||
|vm1 | | dpdk | | vm2 |
|
||||
| | | | | |
|
||||
+--+---------------------------------------------------------------------------------------------+--+
|
||||
| | hugepages | |
|
||||
| +---------------------------------------------------------------------------------------------+ |
|
||||
| |
|
||||
| host |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------------------------+
|
||||
```
|
||||
## Prerequisites
|
||||
|
||||
Prior to running the test, the following steps need to be performed.
|
||||
- Enable hugepages
|
||||
- Install DPDK
|
||||
- Install OVS
|
||||
|
||||
Here are some good references for detailing them.
|
||||
- Red Hat
|
||||
* https://wiki.qemu.org/Documentation/vhost-user-ovs-dpdk
|
||||
- Ubuntu server
|
||||
* https://help.ubuntu.com/lts/serverguide/DPDK.html
|
||||
* https://software.intel.com/en-us/articles/set-up-open-vswitch-with-dpdk-on-ubuntu-server
|
||||
|
||||
## Test
|
||||
The test runs with multiple queue (MQ) support enabled, using 2 pairs of TX/RX queues defined for both OVS and the virtual machine. Here are the details on how the test can be run.
|
||||
|
||||
_Setup OVS_
|
||||
|
||||
`ovs_test.sh` is created to setup and start OVS. OVS will provide the `dpdkvhostuser` backend running in server mode.
|
||||
```bash
|
||||
mkdir -p /var/run/openvswitch
|
||||
modprobe openvswitch
|
||||
killall ovsdb-server ovs-vswitchd
|
||||
rm -f /var/run/openvswitch/vhost-user*
|
||||
rm -f /etc/openvswitch/conf.db
|
||||
export DB_SOCK=/var/run/openvswitch/db.sock
|
||||
ovsdb-tool create /etc/openvswitch/conf.db /usr/share/openvswitch/vswitch.ovsschema
|
||||
ovsdb-server --remote=punix:$DB_SOCK --remote=db:Open_vSwitch,Open_vSwitch,manager_options --pidfile --detach
|
||||
ovs-vsctl --no-wait init
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-lcore-mask=0xf
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-socket-mem=1024
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:pmd-cpu-mask=0xf
|
||||
ovs-vswitchd unix:$DB_SOCK --pidfile --detach --log-file=/var/log/openvswitch/ovs-vswitchd.log
|
||||
ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev
|
||||
ovs-vsctl add-port ovsbr0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuser
|
||||
ovs-vsctl add-port ovsbr0 vhost-user2 -- set Interface vhost-user2 type=dpdkvhostuser
|
||||
ovs-vsctl set Interface vhost-user1 options:n_rxq=2
|
||||
ovs-vsctl set Interface vhost-user2 options:n_rxq=2
|
||||
```
|
||||
_Run ovs_test.sh_
|
||||
```bash
|
||||
./ovs_test.sh
|
||||
```
|
||||
|
||||
_Launch the VMs_
|
||||
|
||||
VMs run in client mode. They connect to the socket created by the `dpdkvhostuser` backend.
|
||||
```bash
|
||||
# From one terminal. We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=2 \
|
||||
--memory size=512M,file=/dev/hugepages \
|
||||
--kernel vmlinux \
|
||||
--cmdline "reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--disk path=clear-kvm.img \
|
||||
--net "mac=52:54:00:02:d9:01,vhost_user=true,socket=/var/run/openvswitch/vhost-user1,num_queues=4"
|
||||
|
||||
# From another terminal. We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=2 \
|
||||
--memory size=512M,file=/dev/hugepages \
|
||||
--kernel vmlinux \
|
||||
--cmdline "reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--disk path=clear-kvm.img \
|
||||
--net "mac=52:54:20:11:C5:02,vhost_user=true,socket=/var/run/openvswitch/vhost-user2,num_queues=4"
|
||||
```
|
||||
|
||||
_Setup VM1_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ip addr add 172.100.0.1/24 dev enp0s3
|
||||
```
|
||||
|
||||
_Setup VM2_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ip addr add 172.100.0.2/24 dev enp0s3
|
||||
```
|
||||
|
||||
_Ping VM1 from VM2_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ping 172.100.0.1
|
||||
```
|
||||
|
||||
_Ping VM2 from VM1_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ping 172.100.0.2
|
||||
```
|
||||
|
||||
__Result:__ At this point, VM1 and VM2 can ping each other successfully. We can now run `iperf3` test.
|
||||
|
||||
_Run VM1 as server_
|
||||
```bash
|
||||
# From inside the guest
|
||||
iperf3 -s -p 4444
|
||||
```
|
||||
|
||||
_Run VM2 as client_
|
||||
```bash
|
||||
# From inside the guest
|
||||
iperf3 -c 172.100.0.1 -t 30 -p 4444 &
|
||||
```
|
||||
|
||||
@@ -4,4 +4,4 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
vmm-sys-util = "0.1.1"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.60"
|
||||
rand = "0.7.0"
|
||||
serde = "1.0.98"
|
||||
vmm-sys-util = "0.1.1"
|
||||
libc = "0.2.68"
|
||||
rand = "0.7.3"
|
||||
serde = "1.0.105"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
net_gen = { path = "../net_gen" }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.3.0"
|
||||
pnet = "0.22.0"
|
||||
serde_json = "1.0.40"
|
||||
pnet = "0.25.0"
|
||||
serde_json = "1.0.48"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
use rand::Rng;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::result::Result;
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Error};
|
||||
@@ -20,23 +21,31 @@ pub struct MacAddr {
|
||||
}
|
||||
|
||||
impl MacAddr {
|
||||
// The error contains the str that failed to be parsed, for nicer error message generation.
|
||||
pub fn parse_str<S>(s: &S) -> Result<MacAddr, &str>
|
||||
pub fn parse_str<S>(s: &S) -> Result<MacAddr, io::Error>
|
||||
where
|
||||
S: AsRef<str> + ?Sized,
|
||||
{
|
||||
let v: Vec<&str> = s.as_ref().split(':').collect();
|
||||
let mut bytes = [0u8; MAC_ADDR_LEN];
|
||||
let common_err = Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("parsing of {} into a MAC address failed", s.as_ref()),
|
||||
));
|
||||
|
||||
if v.len() != MAC_ADDR_LEN {
|
||||
return Err(s.as_ref());
|
||||
return common_err;
|
||||
}
|
||||
|
||||
for i in 0..MAC_ADDR_LEN {
|
||||
if v[i].len() != 2 {
|
||||
return Err(s.as_ref());
|
||||
return common_err;
|
||||
}
|
||||
bytes[i] = u8::from_str_radix(v[i], 16).map_err(|_| s.as_ref())?;
|
||||
bytes[i] = u8::from_str_radix(v[i], 16).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("parsing of {} into a MAC address failed: {}", s.as_ref(), e),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(MacAddr { bytes })
|
||||
@@ -106,7 +115,8 @@ impl<'de> Deserialize<'de> for MacAddr {
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
MacAddr::parse_str(&s).map_err(|_| D::Error::custom("The provided MAC address is invalid."))
|
||||
MacAddr::parse_str(&s)
|
||||
.map_err(|e| D::Error::custom(format!("The provided MAC address is invalid: {}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,12 @@ use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val};
|
||||
pub enum Error {
|
||||
/// Couldn't open /dev/net/tun.
|
||||
OpenTun(IoError),
|
||||
/// Unable to create tap interface.
|
||||
CreateTap(IoError),
|
||||
/// Unable to configure tap interface.
|
||||
ConfigureTap(IoError),
|
||||
/// Unable to retrieve features.
|
||||
GetFeatures(IoError),
|
||||
/// Missing multiqueue support in the kernel.
|
||||
MultiQueueKernelSupport,
|
||||
/// ioctl failed.
|
||||
IoctlError(IoError),
|
||||
/// Failed to create a socket.
|
||||
@@ -40,7 +44,7 @@ pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
#[derive(Debug)]
|
||||
pub struct Tap {
|
||||
tap_file: File,
|
||||
if_name: [u8; 16usize],
|
||||
if_name: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PartialEq for Tap {
|
||||
@@ -53,7 +57,7 @@ impl std::clone::Clone for Tap {
|
||||
fn clone(&self) -> Self {
|
||||
Tap {
|
||||
tap_file: self.tap_file.try_clone().unwrap(),
|
||||
if_name: self.if_name,
|
||||
if_name: self.if_name.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,7 +82,7 @@ fn build_terminated_if_name(if_name: &str) -> Result<Vec<u8>> {
|
||||
}
|
||||
|
||||
impl Tap {
|
||||
pub fn open_named(if_name: &str) -> Result<Tap> {
|
||||
pub fn open_named(if_name: &str, num_queue_pairs: usize) -> Result<Tap> {
|
||||
let terminated_if_name = build_terminated_if_name(if_name)?;
|
||||
|
||||
let fd = unsafe {
|
||||
@@ -96,6 +100,20 @@ impl Tap {
|
||||
// We just checked that the fd is valid.
|
||||
let tuntap = unsafe { File::from_raw_fd(fd) };
|
||||
|
||||
// Let's validate some features before going any further.
|
||||
// ioctl is safe since we call it with a valid tap fd and check the return
|
||||
// value.
|
||||
let mut features = 0;
|
||||
let ret = unsafe { ioctl_with_mut_ref(&tuntap, net_gen::TUNGETFEATURES(), &mut features) };
|
||||
if ret < 0 {
|
||||
return Err(Error::GetFeatures(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
// Check if the user parameters match the kernel support for MQ
|
||||
if (features & net_gen::IFF_MULTI_QUEUE == 0) && num_queue_pairs > 1 {
|
||||
return Err(Error::MultiQueueKernelSupport);
|
||||
}
|
||||
|
||||
// This is pretty messy because of the unions used by ifreq. Since we
|
||||
// don't call as_mut on the same union field more than once, this block
|
||||
// is safe.
|
||||
@@ -107,26 +125,31 @@ impl Tap {
|
||||
name_slice.copy_from_slice(terminated_if_name.as_slice());
|
||||
*ifru_flags =
|
||||
(net_gen::IFF_TAP | net_gen::IFF_NO_PI | net_gen::IFF_VNET_HDR) as c_short;
|
||||
if num_queue_pairs > 1 {
|
||||
*ifru_flags |= net_gen::IFF_MULTI_QUEUE as c_short;
|
||||
}
|
||||
}
|
||||
|
||||
// ioctl is safe since we call it with a valid tap fd and check the return
|
||||
// value.
|
||||
let ret = unsafe { ioctl_with_mut_ref(&tuntap, net_gen::TUNSETIFF(), &mut ifreq) };
|
||||
|
||||
if ret < 0 {
|
||||
return Err(Error::CreateTap(IoError::last_os_error()));
|
||||
return Err(Error::ConfigureTap(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
let if_name_temp = unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() };
|
||||
let mut if_name = if_name_temp.to_vec();
|
||||
if_name.truncate(terminated_if_name.len() - 1);
|
||||
// Safe since only the name is accessed, and it's cloned out.
|
||||
Ok(Tap {
|
||||
tap_file: tuntap,
|
||||
if_name: unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() },
|
||||
if_name,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new tap interface.
|
||||
pub fn new() -> Result<Tap> {
|
||||
Self::open_named("vmtap%d")
|
||||
pub fn new(num_queue_pairs: usize) -> Result<Tap> {
|
||||
Self::open_named("vmtap%d", num_queue_pairs)
|
||||
}
|
||||
|
||||
/// Set the host-side IP address for the tap interface.
|
||||
@@ -232,11 +255,16 @@ impl Tap {
|
||||
// in a single-field union.
|
||||
unsafe {
|
||||
let ifrn_name = ifreq.ifr_ifrn.ifrn_name.as_mut();
|
||||
ifrn_name.clone_from_slice(&self.if_name);
|
||||
let name_slice = &mut ifrn_name[..self.if_name.len()];
|
||||
name_slice.copy_from_slice(&self.if_name);
|
||||
}
|
||||
|
||||
ifreq
|
||||
}
|
||||
|
||||
pub fn get_if_name(&self) -> Vec<u8> {
|
||||
self.if_name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Tap {
|
||||
@@ -423,7 +451,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tap_create() {
|
||||
let t = Tap::new().unwrap();
|
||||
let t = Tap::new(1).unwrap();
|
||||
println!("created tap: {:?}", t);
|
||||
}
|
||||
|
||||
@@ -435,7 +463,7 @@ mod tests {
|
||||
// the end of the function.
|
||||
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
||||
|
||||
let tap = Tap::new().unwrap();
|
||||
let tap = Tap::new(1).unwrap();
|
||||
let ip_addr: net::Ipv4Addr = (*tap_ip_guard).parse().unwrap();
|
||||
let netmask: net::Ipv4Addr = SUBNET_MASK.parse().unwrap();
|
||||
|
||||
@@ -448,21 +476,21 @@ mod tests {
|
||||
#[test]
|
||||
fn test_set_options() {
|
||||
// This line will fail to provide an initialized FD if the test is not run as root.
|
||||
let tap = Tap::new().unwrap();
|
||||
let tap = Tap::new(1).unwrap();
|
||||
tap.set_vnet_hdr_size(16).unwrap();
|
||||
tap.set_offload(0).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tap_enable() {
|
||||
let tap = Tap::new().unwrap();
|
||||
let tap = Tap::new(1).unwrap();
|
||||
let ret = tap.enable();
|
||||
assert!(ret.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tap_get_ifreq() {
|
||||
let tap = Tap::new().unwrap();
|
||||
let tap = Tap::new(1).unwrap();
|
||||
let ret = tap.get_ifreq();
|
||||
assert_eq!(
|
||||
"__BindgenUnionField",
|
||||
@@ -472,7 +500,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_raw_fd() {
|
||||
let tap = Tap::new().unwrap();
|
||||
let tap = Tap::new(1).unwrap();
|
||||
assert_eq!(tap.as_raw_fd(), tap.tap_file.as_raw_fd());
|
||||
}
|
||||
|
||||
@@ -480,7 +508,7 @@ mod tests {
|
||||
fn test_read() {
|
||||
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
||||
|
||||
let mut tap = Tap::new().unwrap();
|
||||
let mut tap = Tap::new(1).unwrap();
|
||||
tap.set_ip_addr((*tap_ip_guard).parse().unwrap()).unwrap();
|
||||
tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap();
|
||||
tap.enable().unwrap();
|
||||
@@ -541,7 +569,7 @@ mod tests {
|
||||
fn test_write() {
|
||||
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
||||
|
||||
let mut tap = Tap::new().unwrap();
|
||||
let mut tap = Tap::new(1).unwrap();
|
||||
tap.set_ip_addr((*tap_ip_guard).parse().unwrap()).unwrap();
|
||||
tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap();
|
||||
tap.enable().unwrap();
|
||||
|
||||
@@ -6,11 +6,9 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
byteorder = "1.3.2"
|
||||
byteorder = "1.3.4"
|
||||
devices = { path = "../devices" }
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = "0.2.60"
|
||||
libc = "0.2.68"
|
||||
log = "0.4.8"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = "0.1.0"
|
||||
|
||||
175
pci/src/bus.rs
175
pci/src/bus.rs
@@ -5,16 +5,19 @@
|
||||
use crate::configuration::{
|
||||
PciBarRegionType, PciBridgeSubclass, PciClassCode, PciConfiguration, PciHeaderType,
|
||||
};
|
||||
use crate::device::{Error as PciDeviceError, PciDevice};
|
||||
use crate::device::{DeviceRelocation, Error as PciDeviceError, PciDevice};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use devices::BusDevice;
|
||||
use std;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||
|
||||
const VENDOR_ID_INTEL: u16 = 0x8086;
|
||||
const DEVICE_ID_INTEL_VIRT_PCIE_HOST: u16 = 0x0d57;
|
||||
const NUM_DEVICE_IDS: usize = 32;
|
||||
|
||||
/// Errors for device manager.
|
||||
#[derive(Debug)]
|
||||
@@ -27,6 +30,10 @@ pub enum PciRootError {
|
||||
PioInsert(devices::BusError),
|
||||
/// Could not add a device to the mmio bus.
|
||||
MmioInsert(devices::BusError),
|
||||
/// Could not find an available device slot on the PCI bus.
|
||||
NoPciDeviceSlotAvailable,
|
||||
/// Invalid PCI device identifier provided.
|
||||
InvalidPciDeviceSlot(usize),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, PciRootError>;
|
||||
|
||||
@@ -66,31 +73,43 @@ impl PciDevice for PciRoot {
|
||||
self.config.write_config_register(reg_idx, offset, data);
|
||||
}
|
||||
|
||||
fn read_config_register(&self, reg_idx: usize) -> u32 {
|
||||
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||
self.config.read_reg(reg_idx)
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PciBus {
|
||||
/// Devices attached to this bus.
|
||||
/// Device 0 is host bridge.
|
||||
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
|
||||
devices: HashMap<u32, Arc<Mutex<dyn PciDevice>>>,
|
||||
device_reloc: Arc<dyn DeviceRelocation>,
|
||||
device_ids: Vec<bool>,
|
||||
}
|
||||
|
||||
impl PciBus {
|
||||
pub fn new(pci_root: PciRoot) -> Self {
|
||||
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
|
||||
pub fn new(pci_root: PciRoot, device_reloc: Arc<dyn DeviceRelocation>) -> Self {
|
||||
let mut devices: HashMap<u32, Arc<Mutex<dyn PciDevice>>> = HashMap::new();
|
||||
let mut device_ids: Vec<bool> = vec![false; NUM_DEVICE_IDS];
|
||||
|
||||
devices.push(Arc::new(Mutex::new(pci_root)));
|
||||
devices.insert(0, Arc::new(Mutex::new(pci_root)));
|
||||
device_ids[0] = true;
|
||||
|
||||
PciBus { devices }
|
||||
PciBus {
|
||||
devices,
|
||||
device_reloc,
|
||||
device_ids,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_mapping(
|
||||
&self,
|
||||
dev: Arc<Mutex<dyn BusDevice>>,
|
||||
io_bus: &mut devices::Bus,
|
||||
mmio_bus: &mut devices::Bus,
|
||||
io_bus: &devices::Bus,
|
||||
mmio_bus: &devices::Bus,
|
||||
bars: Vec<(GuestAddress, GuestUsize, PciBarRegionType)>,
|
||||
) -> Result<()> {
|
||||
for (address, size, type_) in bars {
|
||||
@@ -110,13 +129,38 @@ impl PciBus {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_device(&mut self, device: Arc<Mutex<dyn PciDevice>>) -> Result<()> {
|
||||
self.devices.push(device);
|
||||
pub fn add_device(
|
||||
&mut self,
|
||||
pci_device_bdf: u32,
|
||||
device: Arc<Mutex<dyn PciDevice>>,
|
||||
) -> Result<()> {
|
||||
self.devices.insert(pci_device_bdf >> 3, device);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_device_id(&self) -> u32 {
|
||||
self.devices.len() as u32
|
||||
pub fn remove_by_device(&mut self, device: &Arc<Mutex<dyn PciDevice>>) -> Result<()> {
|
||||
self.devices.retain(|_, dev| !Arc::ptr_eq(dev, device));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_device_id(&mut self) -> Result<u32> {
|
||||
for (idx, device_id) in self.device_ids.iter_mut().enumerate() {
|
||||
if !(*device_id) {
|
||||
*device_id = true;
|
||||
return Ok(idx as u32);
|
||||
}
|
||||
}
|
||||
|
||||
Err(PciRootError::NoPciDeviceSlotAvailable)
|
||||
}
|
||||
|
||||
pub fn put_device_id(&mut self, id: usize) -> Result<()> {
|
||||
if id < NUM_DEVICE_IDS {
|
||||
self.device_ids[id] = false;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PciRootError::InvalidPciDeviceSlot(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +185,7 @@ impl PciConfigIo {
|
||||
}
|
||||
|
||||
let (bus, device, function, register) =
|
||||
parse_config_address(self.config_address & !0x8000_0000);
|
||||
parse_io_config_address(self.config_address & !0x8000_0000);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
@@ -157,7 +201,7 @@ impl PciConfigIo {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.devices
|
||||
.get(device)
|
||||
.get(&(device as u32))
|
||||
.map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
@@ -174,17 +218,33 @@ impl PciConfigIo {
|
||||
}
|
||||
|
||||
let (bus, device, _function, register) =
|
||||
parse_config_address(self.config_address & !0x8000_0000);
|
||||
parse_io_config_address(self.config_address & !0x8000_0000);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) {
|
||||
d.lock()
|
||||
.unwrap()
|
||||
.write_config_register(register, offset, data);
|
||||
let pci_bus = self.pci_bus.lock().unwrap();
|
||||
if let Some(d) = pci_bus.devices.get(&(device as u32)) {
|
||||
let mut device = d.lock().unwrap();
|
||||
|
||||
// Find out if one of the device's BAR is being reprogrammed, and
|
||||
// reprogram it if needed.
|
||||
if let Some(params) = device.detect_bar_reprogramming(register, data) {
|
||||
if let Err(e) = pci_bus.device_reloc.move_bar(
|
||||
params.old_base,
|
||||
params.new_base,
|
||||
params.len,
|
||||
device.deref_mut(),
|
||||
params.region_type,
|
||||
) {
|
||||
error!("Failed moving device BAR: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the register value
|
||||
device.write_config_register(register, offset, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +312,7 @@ impl PciConfigMmio {
|
||||
}
|
||||
|
||||
fn config_space_read(&self, config_address: u32) -> u32 {
|
||||
let (bus, device, _function, register) = parse_config_address(config_address);
|
||||
let (bus, device, _function, register) = parse_mmio_config_address(config_address);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
@@ -263,7 +323,7 @@ impl PciConfigMmio {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.devices
|
||||
.get(device)
|
||||
.get(&(device as u32))
|
||||
.map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
@@ -274,17 +334,33 @@ impl PciConfigMmio {
|
||||
return;
|
||||
}
|
||||
|
||||
let (bus, device, _function, register) = parse_config_address(config_address);
|
||||
let (bus, device, _function, register) = parse_mmio_config_address(config_address);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) {
|
||||
d.lock()
|
||||
.unwrap()
|
||||
.write_config_register(register, offset, data);
|
||||
let pci_bus = self.pci_bus.lock().unwrap();
|
||||
if let Some(d) = pci_bus.devices.get(&(device as u32)) {
|
||||
let mut device = d.lock().unwrap();
|
||||
|
||||
// Find out if one of the device's BAR is being reprogrammed, and
|
||||
// reprogram it if needed.
|
||||
if let Some(params) = device.detect_bar_reprogramming(register, data) {
|
||||
if let Err(e) = pci_bus.device_reloc.move_bar(
|
||||
params.old_base,
|
||||
params.new_base,
|
||||
params.len,
|
||||
device.deref_mut(),
|
||||
params.region_type,
|
||||
) {
|
||||
error!("Failed moving device BAR: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the register value
|
||||
device.write_config_register(register, offset, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,8 +391,33 @@ impl BusDevice for PciConfigMmio {
|
||||
}
|
||||
}
|
||||
|
||||
fn shift_and_mask(value: u32, offset: usize, mask: u32) -> usize {
|
||||
((value >> offset) & mask) as usize
|
||||
}
|
||||
|
||||
// Parse the MMIO address offset to a (bus, device, function, register) tuple.
|
||||
// See section 7.2.2 PCI Express Enhanced Configuration Access Mechanism (ECAM)
|
||||
// from the Pci Express Base Specification Revision 5.0 Version 1.0.
|
||||
fn parse_mmio_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
const BUS_NUMBER_OFFSET: usize = 20;
|
||||
const BUS_NUMBER_MASK: u32 = 0x00ff;
|
||||
const DEVICE_NUMBER_OFFSET: usize = 15;
|
||||
const DEVICE_NUMBER_MASK: u32 = 0x1f;
|
||||
const FUNCTION_NUMBER_OFFSET: usize = 12;
|
||||
const FUNCTION_NUMBER_MASK: u32 = 0x07;
|
||||
const REGISTER_NUMBER_OFFSET: usize = 2;
|
||||
const REGISTER_NUMBER_MASK: u32 = 0x3ff;
|
||||
|
||||
(
|
||||
shift_and_mask(config_address, BUS_NUMBER_OFFSET, BUS_NUMBER_MASK),
|
||||
shift_and_mask(config_address, DEVICE_NUMBER_OFFSET, DEVICE_NUMBER_MASK),
|
||||
shift_and_mask(config_address, FUNCTION_NUMBER_OFFSET, FUNCTION_NUMBER_MASK),
|
||||
shift_and_mask(config_address, REGISTER_NUMBER_OFFSET, REGISTER_NUMBER_MASK),
|
||||
)
|
||||
}
|
||||
|
||||
// Parse the CONFIG_ADDRESS register to a (bus, device, function, register) tuple.
|
||||
fn parse_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
fn parse_io_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
const BUS_NUMBER_OFFSET: usize = 16;
|
||||
const BUS_NUMBER_MASK: u32 = 0x00ff;
|
||||
const DEVICE_NUMBER_OFFSET: usize = 11;
|
||||
@@ -326,12 +427,10 @@ fn parse_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
const REGISTER_NUMBER_OFFSET: usize = 2;
|
||||
const REGISTER_NUMBER_MASK: u32 = 0x3f;
|
||||
|
||||
let bus_number = ((config_address >> BUS_NUMBER_OFFSET) & BUS_NUMBER_MASK) as usize;
|
||||
let device_number = ((config_address >> DEVICE_NUMBER_OFFSET) & DEVICE_NUMBER_MASK) as usize;
|
||||
let function_number =
|
||||
((config_address >> FUNCTION_NUMBER_OFFSET) & FUNCTION_NUMBER_MASK) as usize;
|
||||
let register_number =
|
||||
((config_address >> REGISTER_NUMBER_OFFSET) & REGISTER_NUMBER_MASK) as usize;
|
||||
|
||||
(bus_number, device_number, function_number, register_number)
|
||||
(
|
||||
shift_and_mask(config_address, BUS_NUMBER_OFFSET, BUS_NUMBER_MASK),
|
||||
shift_and_mask(config_address, DEVICE_NUMBER_OFFSET, DEVICE_NUMBER_MASK),
|
||||
shift_and_mask(config_address, FUNCTION_NUMBER_OFFSET, FUNCTION_NUMBER_MASK),
|
||||
shift_and_mask(config_address, REGISTER_NUMBER_OFFSET, REGISTER_NUMBER_MASK),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::device::BarReprogrammingParams;
|
||||
use crate::{MsixConfig, PciInterruptPin};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
// The number of 32bit registers in the config space, 256 bytes.
|
||||
const NUM_CONFIGURATION_REGISTERS: usize = 64;
|
||||
// The number of 32bit registers in the config space, 4096 bytes.
|
||||
const NUM_CONFIGURATION_REGISTERS: usize = 1024;
|
||||
|
||||
const STATUS_REG: usize = 1;
|
||||
const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
|
||||
@@ -249,8 +250,11 @@ pub trait PciCapability {
|
||||
pub struct PciConfiguration {
|
||||
registers: [u32; NUM_CONFIGURATION_REGISTERS],
|
||||
writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register.
|
||||
bar_addr: [u32; NUM_BAR_REGS],
|
||||
bar_size: [u32; NUM_BAR_REGS],
|
||||
bar_used: [bool; NUM_BAR_REGS],
|
||||
bar_type: [Option<PciBarRegionType>; NUM_BAR_REGS],
|
||||
rom_bar_addr: u32,
|
||||
rom_bar_size: u32,
|
||||
rom_bar_used: bool,
|
||||
// Contains the byte offset and size of the last capability.
|
||||
@@ -260,7 +264,7 @@ pub struct PciConfiguration {
|
||||
}
|
||||
|
||||
/// See pci_regs.h in kernel
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum PciBarRegionType {
|
||||
Memory32BitRegion = 0,
|
||||
IORegion = 0x01,
|
||||
@@ -343,6 +347,7 @@ impl PciConfiguration {
|
||||
) -> Self {
|
||||
let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS];
|
||||
let mut writable_bits = [0u32; NUM_CONFIGURATION_REGISTERS];
|
||||
let bar_addr = [0u32; NUM_BAR_REGS];
|
||||
let bar_size = [0u32; NUM_BAR_REGS];
|
||||
registers[0] = u32::from(device_id) << 16 | u32::from(vendor_id);
|
||||
// TODO(dverkamp): Status should be write-1-to-clear
|
||||
@@ -372,8 +377,11 @@ impl PciConfiguration {
|
||||
PciConfiguration {
|
||||
registers,
|
||||
writable_bits,
|
||||
bar_addr,
|
||||
bar_size,
|
||||
bar_used: [false; NUM_BAR_REGS],
|
||||
bar_type: [None; NUM_BAR_REGS],
|
||||
rom_bar_addr: 0,
|
||||
rom_bar_size: 0,
|
||||
rom_bar_used: false,
|
||||
last_capability: None,
|
||||
@@ -390,15 +398,20 @@ impl PciConfiguration {
|
||||
/// Writes a 32bit register to `reg_idx` in the register map.
|
||||
pub fn write_reg(&mut self, reg_idx: usize, value: u32) {
|
||||
let mut mask = self.writable_bits[reg_idx];
|
||||
if reg_idx >= BAR0_REG
|
||||
&& reg_idx < BAR0_REG + NUM_BAR_REGS
|
||||
&& (value & BAR_MEM_ADDR_MASK) == BAR_MEM_ADDR_MASK
|
||||
{
|
||||
|
||||
if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS {
|
||||
// Handle very specific case where the BAR is being written with
|
||||
// all 1's to retrieve the BAR size on next BAR reading.
|
||||
mask = self.bar_size[reg_idx - 4];
|
||||
} else if reg_idx == ROM_BAR_REG && (value & ROM_BAR_ADDR_MASK) == ROM_BAR_ADDR_MASK {
|
||||
mask = self.rom_bar_size;
|
||||
// all 1's to retrieve the BAR size during next BAR reading.
|
||||
if value == 0xffff_ffff {
|
||||
mask = self.bar_size[reg_idx - 4];
|
||||
}
|
||||
} else if reg_idx == ROM_BAR_REG {
|
||||
// Handle very specific case where the BAR is being written with
|
||||
// all 1's on bits 31-11 to retrieve the BAR size during next BAR
|
||||
// reading.
|
||||
if value & ROM_BAR_ADDR_MASK == ROM_BAR_ADDR_MASK {
|
||||
mask = self.rom_bar_size;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(r) = self.registers.get_mut(reg_idx) {
|
||||
@@ -497,6 +510,7 @@ impl PciConfiguration {
|
||||
|
||||
self.registers[bar_idx + 1] = (config.addr >> 32) as u32;
|
||||
self.writable_bits[bar_idx + 1] = 0xffff_ffff;
|
||||
self.bar_addr[config.reg_idx + 1] = self.registers[bar_idx + 1];
|
||||
self.bar_size[config.reg_idx + 1] = (config.size >> 32) as u32;
|
||||
self.bar_used[config.reg_idx + 1] = true;
|
||||
}
|
||||
@@ -512,8 +526,10 @@ impl PciConfiguration {
|
||||
|
||||
self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
|
||||
self.writable_bits[bar_idx] = mask;
|
||||
self.bar_addr[config.reg_idx] = self.registers[bar_idx];
|
||||
self.bar_size[config.reg_idx] = config.size as u32;
|
||||
self.bar_used[config.reg_idx] = true;
|
||||
self.bar_type[config.reg_idx] = Some(config.region_type);
|
||||
Ok(config.reg_idx)
|
||||
}
|
||||
|
||||
@@ -542,24 +558,25 @@ impl PciConfiguration {
|
||||
|
||||
self.registers[config.reg_idx] = (config.addr as u32) | active;
|
||||
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
|
||||
self.rom_bar_addr = self.registers[config.reg_idx];
|
||||
self.rom_bar_size = config.size as u32;
|
||||
self.rom_bar_used = true;
|
||||
Ok(config.reg_idx)
|
||||
}
|
||||
|
||||
/// Returns the address of the given 32 bits BAR region.
|
||||
pub fn get_bar32_addr(&self, bar_num: usize) -> u32 {
|
||||
/// Returns the address of the given BAR region.
|
||||
pub fn get_bar_addr(&self, bar_num: usize) -> u64 {
|
||||
let bar_idx = BAR0_REG + bar_num;
|
||||
|
||||
self.registers[bar_idx] & BAR_MEM_ADDR_MASK
|
||||
}
|
||||
let mut addr = u64::from(self.bar_addr[bar_num] & self.writable_bits[bar_idx]);
|
||||
|
||||
/// Returns the address of the given 64 bits BAR region.
|
||||
pub fn get_bar64_addr(&self, bar_num: usize) -> u64 {
|
||||
let bar_idx = BAR0_REG + bar_num;
|
||||
if let Some(bar_type) = self.bar_type[bar_num] {
|
||||
if bar_type == PciBarRegionType::Memory64BitRegion {
|
||||
addr |= u64::from(self.bar_addr[bar_num + 1]) << 32;
|
||||
}
|
||||
}
|
||||
|
||||
u64::from(self.registers[bar_idx] & BAR_MEM_ADDR_MASK)
|
||||
| (u64::from(self.registers[bar_idx + 1]) << 32)
|
||||
addr
|
||||
}
|
||||
|
||||
/// Configures the IRQ line and pin used by this device.
|
||||
@@ -645,6 +662,119 @@ impl PciConfiguration {
|
||||
pub fn read_config_register(&self, reg_idx: usize) -> u32 {
|
||||
self.read_reg(reg_idx)
|
||||
}
|
||||
|
||||
pub fn detect_bar_reprogramming(
|
||||
&mut self,
|
||||
reg_idx: usize,
|
||||
data: &[u8],
|
||||
) -> Option<BarReprogrammingParams> {
|
||||
if data.len() != 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = LittleEndian::read_u32(data);
|
||||
|
||||
let mask = self.writable_bits[reg_idx];
|
||||
if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS {
|
||||
let bar_idx = reg_idx - 4;
|
||||
if (value & mask) != (self.bar_addr[bar_idx] & mask) {
|
||||
// Handle special case where the address being written is
|
||||
// different from the address initially provided. This is a
|
||||
// BAR reprogramming case which needs to be properly caught.
|
||||
if let Some(bar_type) = self.bar_type[bar_idx] {
|
||||
match bar_type {
|
||||
PciBarRegionType::Memory64BitRegion => {}
|
||||
_ => {
|
||||
// Ignore the case where the BAR size is being
|
||||
// asked for.
|
||||
if value == 0xffff_ffff {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug!(
|
||||
"DETECT BAR REPROG: current 0x{:x}, new 0x{:x}",
|
||||
self.registers[reg_idx], value
|
||||
);
|
||||
let old_base = u64::from(self.bar_addr[bar_idx] & mask);
|
||||
let new_base = u64::from(value & mask);
|
||||
let len = u64::from(self.bar_size[bar_idx]);
|
||||
let region_type = bar_type;
|
||||
|
||||
self.bar_addr[bar_idx] = value;
|
||||
|
||||
return Some(BarReprogrammingParams {
|
||||
old_base,
|
||||
new_base,
|
||||
len,
|
||||
region_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (reg_idx > BAR0_REG)
|
||||
&& (self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1])
|
||||
!= (self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1])
|
||||
{
|
||||
// Ignore the case where the BAR size is being asked for.
|
||||
// Because we are in the 64bits case here, we have to check
|
||||
// if the lower 32bits of the current BAR have already been
|
||||
// asked for the BAR size too.
|
||||
if value == 0xffff_ffff
|
||||
&& self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]
|
||||
== self.bar_size[bar_idx - 1] & self.writable_bits[reg_idx - 1]
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
debug!(
|
||||
"DETECT BAR REPROG: current 0x{:x}, new 0x{:x}",
|
||||
self.registers[reg_idx], value
|
||||
);
|
||||
let old_base = u64::from(self.bar_addr[bar_idx] & mask) << 32
|
||||
| u64::from(self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1]);
|
||||
let new_base = u64::from(value & mask) << 32
|
||||
| u64::from(self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]);
|
||||
let len = u64::from(self.bar_size[bar_idx]) << 32
|
||||
| u64::from(self.bar_size[bar_idx - 1]);
|
||||
let region_type = PciBarRegionType::Memory64BitRegion;
|
||||
|
||||
self.bar_addr[bar_idx] = value;
|
||||
self.bar_addr[bar_idx - 1] = self.registers[reg_idx - 1];
|
||||
|
||||
return Some(BarReprogrammingParams {
|
||||
old_base,
|
||||
new_base,
|
||||
len,
|
||||
region_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if reg_idx == ROM_BAR_REG && (value & mask) != (self.rom_bar_addr & mask) {
|
||||
// Ignore the case where the BAR size is being asked for.
|
||||
if value & ROM_BAR_ADDR_MASK == ROM_BAR_ADDR_MASK {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug!(
|
||||
"DETECT ROM BAR REPROG: current 0x{:x}, new 0x{:x}",
|
||||
self.registers[reg_idx], value
|
||||
);
|
||||
let old_base = u64::from(self.rom_bar_addr & mask);
|
||||
let new_base = u64::from(value & mask);
|
||||
let len = u64::from(self.rom_bar_size);
|
||||
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||
|
||||
self.rom_bar_addr = value;
|
||||
|
||||
return Some(BarReprogrammingParams {
|
||||
old_base,
|
||||
new_base,
|
||||
len,
|
||||
region_type,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PciBarConfiguration {
|
||||
|
||||
@@ -3,22 +3,12 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use crate::configuration::{self, PciBarRegionType};
|
||||
use crate::msix::MsixTableEntry;
|
||||
use crate::PciInterruptPin;
|
||||
use devices::BusDevice;
|
||||
use std;
|
||||
use std::any::Any;
|
||||
use std::fmt::{self, Display};
|
||||
use std::sync::Arc;
|
||||
use std::{self, io, result};
|
||||
use vm_allocator::SystemAllocator;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct InterruptParameters<'a> {
|
||||
pub msix: Option<&'a MsixTableEntry>,
|
||||
}
|
||||
|
||||
pub type InterruptDelivery =
|
||||
Box<dyn Fn(InterruptParameters) -> std::result::Result<(), std::io::Error> + Send + Sync>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -47,20 +37,15 @@ impl Display for Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct BarReprogrammingParams {
|
||||
pub old_base: u64,
|
||||
pub new_base: u64,
|
||||
pub len: u64,
|
||||
pub region_type: PciBarRegionType,
|
||||
}
|
||||
|
||||
pub trait PciDevice: BusDevice {
|
||||
/// Assign a legacy PCI IRQ to this device.
|
||||
/// The device may write to `irq_evt` to trigger an interrupt.
|
||||
fn assign_pin_irq(
|
||||
&mut self,
|
||||
_irq_cb: Arc<InterruptDelivery>,
|
||||
_irq_num: u32,
|
||||
_irq_pin: PciInterruptPin,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Assign MSI-X to this device.
|
||||
fn assign_msix(&mut self, _msi_cb: Arc<InterruptDelivery>) {}
|
||||
|
||||
/// Allocates the needed PCI BARs space using the `allocate` function which takes a size and
|
||||
/// returns an address. Returns a Vec of (GuestAddress, GuestUsize) tuples.
|
||||
fn allocate_bars(
|
||||
@@ -70,18 +55,26 @@ pub trait PciDevice: BusDevice {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Gets a list of ioeventfds that should be registered with the running VM. The list is
|
||||
/// returned as a Vec of (eventfd, addr, datamatch) tuples.
|
||||
fn ioeventfds(&self) -> Vec<(&EventFd, u64, u64)> {
|
||||
Vec::new()
|
||||
/// Frees the PCI BARs previously allocated with a call to allocate_bars().
|
||||
fn free_bars(&mut self, _allocator: &mut SystemAllocator) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets a register in the configuration space.
|
||||
/// * `reg_idx` - The index of the config register to modify.
|
||||
/// * `offset` - Offset in to the register.
|
||||
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]);
|
||||
/// Gets a register from the configuration space.
|
||||
/// * `reg_idx` - The index of the config register to read.
|
||||
fn read_config_register(&self, reg_idx: usize) -> u32;
|
||||
fn read_config_register(&mut self, reg_idx: usize) -> u32;
|
||||
/// Detects if a BAR is being reprogrammed.
|
||||
fn detect_bar_reprogramming(
|
||||
&mut self,
|
||||
_reg_idx: usize,
|
||||
_data: &[u8],
|
||||
) -> Option<BarReprogrammingParams> {
|
||||
None
|
||||
}
|
||||
/// Reads from a BAR region mapped in to the device.
|
||||
/// * `addr` - The guest address inside the BAR.
|
||||
/// * `data` - Filled with the data from `addr`.
|
||||
@@ -90,4 +83,26 @@ pub trait PciDevice: BusDevice {
|
||||
/// * `addr` - The guest address inside the BAR.
|
||||
/// * `data` - The data to write.
|
||||
fn write_bar(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||
/// Relocates the BAR to a different address in guest address space.
|
||||
fn move_bar(&mut self, _old_base: u64, _new_base: u64) -> result::Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
/// Provides a mutable reference to the Any trait. This is useful to let
|
||||
/// the caller have access to the underlying type behind the trait.
|
||||
fn as_any(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
/// This trait defines a set of functions which can be triggered whenever a
|
||||
/// PCI device is modified in any way.
|
||||
pub trait DeviceRelocation: Send + Sync {
|
||||
/// The BAR needs to be moved to a different location in the guest address
|
||||
/// space. This follows a decision from the software running in the guest.
|
||||
fn move_bar(
|
||||
&self,
|
||||
old_base: u64,
|
||||
new_base: u64,
|
||||
len: u64,
|
||||
pci_dev: &mut dyn PciDevice,
|
||||
region_type: PciBarRegionType,
|
||||
) -> result::Result<(), io::Error>;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate devices;
|
||||
extern crate kvm_ioctls;
|
||||
extern crate vm_memory;
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
mod bus;
|
||||
mod configuration;
|
||||
@@ -23,9 +21,9 @@ pub use self::configuration::{
|
||||
PciNetworkControllerSubclass, PciProgrammingInterface, PciSerialBusSubClass, PciSubclass,
|
||||
};
|
||||
pub use self::device::{
|
||||
Error as PciDeviceError, InterruptDelivery, InterruptParameters, PciDevice,
|
||||
BarReprogrammingParams, DeviceRelocation, Error as PciDeviceError, PciDevice,
|
||||
};
|
||||
pub use self::msi::MsiCap;
|
||||
pub use self::msi::{msi_num_enabled_vectors, MsiCap, MsiConfig};
|
||||
pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE};
|
||||
|
||||
/// PCI has four interrupt pins A->D.
|
||||
|
||||
106
pci/src/msi.rs
106
pci/src/msi.rs
@@ -7,6 +7,10 @@ extern crate byteorder;
|
||||
extern crate vm_memory;
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig,
|
||||
};
|
||||
|
||||
// MSI control masks
|
||||
const MSI_CTL_ENABLE: u16 = 0x1;
|
||||
@@ -21,6 +25,16 @@ const MSI_MSG_ADDR_LO_OFFSET: u64 = 0x4;
|
||||
// MSI message masks
|
||||
const MSI_MSG_ADDR_LO_MASK: u32 = 0xffff_fffc;
|
||||
|
||||
pub fn msi_num_enabled_vectors(msg_ctl: u16) -> usize {
|
||||
let field = (msg_ctl >> 4) & 0x7;
|
||||
|
||||
if field > 5 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
1 << field
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct MsiCap {
|
||||
// Message Control Register
|
||||
@@ -58,21 +72,15 @@ impl MsiCap {
|
||||
self.msg_ctl & MSI_CTL_PER_VECTOR == MSI_CTL_PER_VECTOR
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
fn enabled(&self) -> bool {
|
||||
self.msg_ctl & MSI_CTL_ENABLE == MSI_CTL_ENABLE
|
||||
}
|
||||
|
||||
pub fn num_enabled_vectors(&self) -> usize {
|
||||
let field = (self.msg_ctl >> 4) & 0x7;
|
||||
|
||||
if field > 5 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
1 << field
|
||||
fn num_enabled_vectors(&self) -> usize {
|
||||
msi_num_enabled_vectors(self.msg_ctl)
|
||||
}
|
||||
|
||||
pub fn vector_masked(&self, vector: usize) -> bool {
|
||||
fn vector_masked(&self, vector: usize) -> bool {
|
||||
if !self.per_vector_mask() {
|
||||
return false;
|
||||
}
|
||||
@@ -80,7 +88,7 @@ impl MsiCap {
|
||||
(self.mask_bits >> vector) & 0x1 == 0x1
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u64 {
|
||||
fn size(&self) -> u64 {
|
||||
let mut size: u64 = 0xa;
|
||||
|
||||
if self.addr_64_bits() {
|
||||
@@ -93,7 +101,7 @@ impl MsiCap {
|
||||
size
|
||||
}
|
||||
|
||||
pub fn update(&mut self, offset: u64, data: &[u8]) {
|
||||
fn update(&mut self, offset: u64, data: &[u8]) {
|
||||
// Calculate message data offset depending on the address being 32 or
|
||||
// 64 bits.
|
||||
// Calculate upper address offset if the address is 64 bits.
|
||||
@@ -132,7 +140,7 @@ impl MsiCap {
|
||||
4 => {
|
||||
let value = LittleEndian::read_u32(data);
|
||||
match offset {
|
||||
MSI_MSG_CTL_OFFSET => {
|
||||
0x0 => {
|
||||
self.msg_ctl = (self.msg_ctl & !(MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE))
|
||||
| ((value >> 16) as u16 & (MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE))
|
||||
}
|
||||
@@ -151,3 +159,75 @@ impl MsiCap {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MsiConfig {
|
||||
cap: MsiCap,
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
impl MsiConfig {
|
||||
pub fn new(msg_ctl: u16, interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>) -> Self {
|
||||
let cap = MsiCap {
|
||||
msg_ctl,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
MsiConfig {
|
||||
cap,
|
||||
interrupt_source_group,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.cap.enabled()
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u64 {
|
||||
self.cap.size()
|
||||
}
|
||||
|
||||
pub fn num_enabled_vectors(&self) -> usize {
|
||||
self.cap.num_enabled_vectors()
|
||||
}
|
||||
|
||||
pub fn update(&mut self, offset: u64, data: &[u8]) {
|
||||
let old_enabled = self.cap.enabled();
|
||||
|
||||
self.cap.update(offset, data);
|
||||
|
||||
if self.cap.enabled() {
|
||||
for idx in 0..self.num_enabled_vectors() {
|
||||
let config = MsiIrqSourceConfig {
|
||||
high_addr: self.cap.msg_addr_hi,
|
||||
low_addr: self.cap.msg_addr_lo,
|
||||
data: self.cap.msg_data as u32,
|
||||
};
|
||||
|
||||
if let Err(e) = self
|
||||
.interrupt_source_group
|
||||
.update(idx as InterruptIndex, InterruptSourceConfig::MsiIrq(config))
|
||||
{
|
||||
error!("Failed updating vector: {:?}", e);
|
||||
}
|
||||
|
||||
if self.cap.vector_masked(idx) {
|
||||
if let Err(e) = self.interrupt_source_group.mask(idx as InterruptIndex) {
|
||||
error!("Failed masking vector: {:?}", e);
|
||||
}
|
||||
} else if let Err(e) = self.interrupt_source_group.unmask(idx as InterruptIndex) {
|
||||
error!("Failed unmasking vector: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if !old_enabled {
|
||||
if let Err(e) = self.interrupt_source_group.enable() {
|
||||
error!("Failed enabling irq_fd: {:?}", e);
|
||||
}
|
||||
}
|
||||
} else if old_enabled {
|
||||
if let Err(e) = self.interrupt_source_group.disable() {
|
||||
error!("Failed disabling irq_fd: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ extern crate vm_memory;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::device::InterruptParameters;
|
||||
use crate::{InterruptDelivery, PciCapability, PciCapabilityID};
|
||||
use crate::{PciCapability, PciCapabilityID};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig,
|
||||
};
|
||||
use vm_memory::ByteValued;
|
||||
|
||||
const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048;
|
||||
@@ -51,13 +53,16 @@ impl Default for MsixTableEntry {
|
||||
pub struct MsixConfig {
|
||||
pub table_entries: Vec<MsixTableEntry>,
|
||||
pub pba_entries: Vec<u64>,
|
||||
interrupt_cb: Option<Arc<InterruptDelivery>>,
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
masked: bool,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl MsixConfig {
|
||||
pub fn new(msix_vectors: u16) -> Self {
|
||||
pub fn new(
|
||||
msix_vectors: u16,
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
) -> Self {
|
||||
assert!(msix_vectors <= MAX_MSIX_VECTORS_PER_DEVICE);
|
||||
|
||||
let mut table_entries: Vec<MsixTableEntry> = Vec::new();
|
||||
@@ -69,16 +74,12 @@ impl MsixConfig {
|
||||
MsixConfig {
|
||||
table_entries,
|
||||
pba_entries,
|
||||
interrupt_cb: None,
|
||||
interrupt_source_group,
|
||||
masked: false,
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_interrupt_cb(&mut self, cb: Arc<InterruptDelivery>) {
|
||||
self.interrupt_cb = Some(cb);
|
||||
}
|
||||
|
||||
pub fn masked(&self) -> bool {
|
||||
self.masked
|
||||
}
|
||||
@@ -89,10 +90,50 @@ impl MsixConfig {
|
||||
|
||||
pub fn set_msg_ctl(&mut self, reg: u16) {
|
||||
let old_masked = self.masked;
|
||||
let old_enabled = self.enabled;
|
||||
|
||||
self.masked = ((reg >> FUNCTION_MASK_BIT) & 1u16) == 1u16;
|
||||
self.enabled = ((reg >> MSIX_ENABLE_BIT) & 1u16) == 1u16;
|
||||
|
||||
// Update KVM routes
|
||||
if old_masked != self.masked || old_enabled != self.enabled {
|
||||
if self.enabled && !self.masked {
|
||||
for (idx, table_entry) in self.table_entries.iter().enumerate() {
|
||||
let config = MsiIrqSourceConfig {
|
||||
high_addr: table_entry.msg_addr_hi,
|
||||
low_addr: table_entry.msg_addr_lo,
|
||||
data: table_entry.msg_data,
|
||||
};
|
||||
|
||||
if let Err(e) = self
|
||||
.interrupt_source_group
|
||||
.update(idx as InterruptIndex, InterruptSourceConfig::MsiIrq(config))
|
||||
{
|
||||
error!("Failed updating vector: {:?}", e);
|
||||
}
|
||||
|
||||
if table_entry.masked() {
|
||||
if let Err(e) = self.interrupt_source_group.mask(idx as InterruptIndex) {
|
||||
error!("Failed masking vector: {:?}", e);
|
||||
}
|
||||
} else if let Err(e) = self.interrupt_source_group.unmask(idx as InterruptIndex)
|
||||
{
|
||||
error!("Failed unmasking vector: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if !old_enabled || old_masked {
|
||||
if let Err(e) = self.interrupt_source_group.enable() {
|
||||
error!("Failed enabling irq_fd: {:?}", e);
|
||||
}
|
||||
}
|
||||
} else if old_enabled || !old_masked {
|
||||
if let Err(e) = self.interrupt_source_group.disable() {
|
||||
error!("Failed disabling irq_fd: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the Function Mask bit was set, and has just been cleared, it's
|
||||
// important to go through the entire PBA to check if there was any
|
||||
// pending MSI-X message to inject, given that the vector is not
|
||||
@@ -198,6 +239,32 @@ impl MsixConfig {
|
||||
_ => error!("invalid data length"),
|
||||
};
|
||||
|
||||
// Update interrupt routes
|
||||
if self.enabled && !self.masked {
|
||||
let table_entry = &self.table_entries[index];
|
||||
|
||||
let config = MsiIrqSourceConfig {
|
||||
high_addr: table_entry.msg_addr_hi,
|
||||
low_addr: table_entry.msg_addr_lo,
|
||||
data: table_entry.msg_data,
|
||||
};
|
||||
|
||||
if let Err(e) = self.interrupt_source_group.update(
|
||||
index as InterruptIndex,
|
||||
InterruptSourceConfig::MsiIrq(config),
|
||||
) {
|
||||
error!("Failed updating vector: {:?}", e);
|
||||
}
|
||||
|
||||
if table_entry.masked() {
|
||||
if let Err(e) = self.interrupt_source_group.mask(index as InterruptIndex) {
|
||||
error!("Failed masking vector: {:?}", e);
|
||||
}
|
||||
} else if let Err(e) = self.interrupt_source_group.unmask(index as InterruptIndex) {
|
||||
error!("Failed unmasking vector: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// After the MSI-X table entry has been updated, it is necessary to
|
||||
// check if the vector control masking bit has changed. In case the
|
||||
// bit has been flipped from 1 to 0, we need to inject a MSI message
|
||||
@@ -285,13 +352,12 @@ impl MsixConfig {
|
||||
|
||||
fn inject_msix_and_clear_pba(&mut self, vector: usize) {
|
||||
// Inject the MSI message
|
||||
if let Some(cb) = &self.interrupt_cb {
|
||||
match (cb)(InterruptParameters {
|
||||
msix: Some(&self.table_entries[vector]),
|
||||
}) {
|
||||
Ok(_) => debug!("MSI-X injected on vector control flip"),
|
||||
Err(e) => error!("failed to inject MSI-X: {}", e),
|
||||
};
|
||||
match self
|
||||
.interrupt_source_group
|
||||
.trigger(vector as InterruptIndex)
|
||||
{
|
||||
Ok(_) => debug!("MSI-X injected on vector control flip"),
|
||||
Err(e) => error!("failed to inject MSI-X: {}", e),
|
||||
}
|
||||
|
||||
// Clear the bit from PBA
|
||||
|
||||
9
qcow/Cargo.toml
Executable file → Normal file
9
qcow/Cargo.toml
Executable file → Normal file
@@ -9,11 +9,12 @@ license = "BSD-3-Clause"
|
||||
path = "src/qcow.rs"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.2"
|
||||
libc = "0.2.60"
|
||||
byteorder = "1.3.4"
|
||||
libc = "0.2.68"
|
||||
log = "0.4.8"
|
||||
remain = "0.1.3"
|
||||
vmm-sys-util = "0.1.1"
|
||||
remain = "0.2.1"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
138
qcow/src/qcow.rs
138
qcow/src/qcow.rs
@@ -12,6 +12,7 @@ mod vec_cache;
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
||||
use remain::sorted;
|
||||
use vm_virtio::RawFile;
|
||||
use vmm_sys_util::{
|
||||
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
|
||||
write_zeroes::WriteZeroes,
|
||||
@@ -19,10 +20,8 @@ use vmm_sys_util::{
|
||||
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::size_of;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use crate::qcow_raw_file::QcowRawFile;
|
||||
use crate::refcount::RefCount;
|
||||
@@ -190,7 +189,7 @@ pub struct QcowHeader {
|
||||
|
||||
impl QcowHeader {
|
||||
/// Creates a QcowHeader from a reference to a file.
|
||||
pub fn new(f: &mut File) -> Result<QcowHeader> {
|
||||
pub fn new(f: &mut RawFile) -> Result<QcowHeader> {
|
||||
f.seek(SeekFrom::Start(0)).map_err(Error::ReadingHeader)?;
|
||||
let magic = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
|
||||
if magic != QCOW_MAGIC {
|
||||
@@ -198,12 +197,12 @@ impl QcowHeader {
|
||||
}
|
||||
|
||||
// Reads the next u32 from the file.
|
||||
fn read_u32_from_file(f: &mut File) -> Result<u32> {
|
||||
fn read_u32_from_file(f: &mut RawFile) -> Result<u32> {
|
||||
f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)
|
||||
}
|
||||
|
||||
// Reads the next u64 from the file.
|
||||
fn read_u64_from_file(f: &mut File) -> Result<u64> {
|
||||
fn read_u64_from_file(f: &mut RawFile) -> Result<u64> {
|
||||
f.read_u64::<BigEndian>().map_err(Error::ReadingHeader)
|
||||
}
|
||||
|
||||
@@ -368,9 +367,11 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u
|
||||
///
|
||||
/// ```
|
||||
/// # use std::io::{Read, Seek, SeekFrom};
|
||||
/// # use vm_virtio::RawFile;
|
||||
/// # use qcow::{self, QcowFile};
|
||||
/// # fn test(file: std::fs::File) -> std::io::Result<()> {
|
||||
/// let mut q = QcowFile::from(file).expect("Can't open qcow file");
|
||||
/// let mut raw_img = RawFile::new(file, false);
|
||||
/// let mut q = QcowFile::from(raw_img).expect("Can't open qcow file");
|
||||
/// let mut buf = [0u8; 12];
|
||||
/// q.seek(SeekFrom::Start(10 as u64))?;
|
||||
/// q.read(&mut buf[..])?;
|
||||
@@ -395,7 +396,7 @@ pub struct QcowFile {
|
||||
|
||||
impl QcowFile {
|
||||
/// Creates a QcowFile from `file`. File must be a valid qcow2 image.
|
||||
pub fn from(mut file: File) -> Result<QcowFile> {
|
||||
pub fn from(mut file: RawFile) -> Result<QcowFile> {
|
||||
let header = QcowHeader::new(&mut file)?;
|
||||
|
||||
// Only v2 and v3 files are supported.
|
||||
@@ -544,7 +545,7 @@ impl QcowFile {
|
||||
}
|
||||
|
||||
/// Creates a new QcowFile at the given path.
|
||||
pub fn new(mut file: File, version: u32, virtual_size: u64) -> Result<QcowFile> {
|
||||
pub fn new(mut file: RawFile, version: u32, virtual_size: u64) -> Result<QcowFile> {
|
||||
let header = QcowHeader::create_for_size(version, virtual_size);
|
||||
file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
|
||||
header.write_to(&mut file)?;
|
||||
@@ -1220,7 +1221,12 @@ impl QcowFile {
|
||||
let refcount = self
|
||||
.refcounts
|
||||
.get_cluster_refcount(&mut self.raw_file, cluster_addr)
|
||||
.map_err(|_| std::io::Error::from_raw_os_error(EINVAL))?;
|
||||
.map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("failed to get cluster refcount: {}", e),
|
||||
)
|
||||
})?;
|
||||
if refcount == 0 {
|
||||
return Err(std::io::Error::from_raw_os_error(EINVAL));
|
||||
}
|
||||
@@ -1394,12 +1400,6 @@ impl Drop for QcowFile {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for QcowFile {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.raw_file.file().as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for QcowFile {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let address: u64 = self.current_offset as u64;
|
||||
@@ -1634,7 +1634,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_reader<R>(reader: &mut R, dst_file: File, dst_type: ImageType) -> Result<()>
|
||||
fn convert_reader<R>(reader: &mut R, dst_file: RawFile, dst_type: ImageType) -> Result<()>
|
||||
where
|
||||
R: Read + Seek + SeekHole,
|
||||
{
|
||||
@@ -1666,8 +1666,8 @@ where
|
||||
/// Copy the contents of a disk image in `src_file` into `dst_file`.
|
||||
/// The type of `src_file` is automatically detected, and the output file type is
|
||||
/// determined by `dst_type`.
|
||||
pub fn convert(src_file: File, dst_file: File, dst_type: ImageType) -> Result<()> {
|
||||
let src_type = detect_image_type(&src_file)?;
|
||||
pub fn convert(mut src_file: RawFile, dst_file: RawFile, dst_type: ImageType) -> Result<()> {
|
||||
let src_type = detect_image_type(&mut src_file)?;
|
||||
match src_type {
|
||||
ImageType::Qcow2 => {
|
||||
let mut src_reader = QcowFile::from(src_file)?;
|
||||
@@ -1682,17 +1682,18 @@ pub fn convert(src_file: File, dst_file: File, dst_type: ImageType) -> Result<()
|
||||
}
|
||||
|
||||
/// Detect the type of an image file by checking for a valid qcow2 header.
|
||||
pub fn detect_image_type(file: &File) -> Result<ImageType> {
|
||||
let mut f = file;
|
||||
let orig_seek = f.seek(SeekFrom::Current(0)).map_err(Error::SeekingFile)?;
|
||||
f.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
|
||||
let magic = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
|
||||
pub fn detect_image_type(file: &mut RawFile) -> Result<ImageType> {
|
||||
let orig_seek = file
|
||||
.seek(SeekFrom::Current(0))
|
||||
.map_err(Error::SeekingFile)?;
|
||||
file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
|
||||
let magic = file.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
|
||||
let image_type = if magic == QCOW_MAGIC {
|
||||
ImageType::Qcow2
|
||||
} else {
|
||||
ImageType::Raw
|
||||
};
|
||||
f.seek(SeekFrom::Start(orig_seek))
|
||||
file.seek(SeekFrom::Start(orig_seek))
|
||||
.map_err(Error::SeekingFile)?;
|
||||
Ok(image_type)
|
||||
}
|
||||
@@ -1700,9 +1701,9 @@ pub fn detect_image_type(file: &File) -> Result<ImageType> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use tempfile::tempfile;
|
||||
use vm_virtio::RawFile;
|
||||
|
||||
fn valid_header_v3() -> Vec<u8> {
|
||||
vec![
|
||||
@@ -1771,9 +1772,9 @@ mod tests {
|
||||
|
||||
fn with_basic_file<F>(header: &[u8], mut testfn: F)
|
||||
where
|
||||
F: FnMut(File),
|
||||
F: FnMut(RawFile),
|
||||
{
|
||||
let mut disk_file: File = tempfile().unwrap();
|
||||
let mut disk_file: RawFile = RawFile::new(tempfile().unwrap(), false);
|
||||
disk_file.write_all(&header).unwrap();
|
||||
disk_file.set_len(0x1_0000_0000).unwrap();
|
||||
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
||||
@@ -1781,11 +1782,11 @@ mod tests {
|
||||
testfn(disk_file); // File closed when the function exits.
|
||||
}
|
||||
|
||||
fn with_default_file<F>(file_size: u64, mut testfn: F)
|
||||
fn with_default_file<F>(file_size: u64, direct: bool, mut testfn: F)
|
||||
where
|
||||
F: FnMut(QcowFile),
|
||||
{
|
||||
let tmp = tempfile().unwrap();
|
||||
let tmp: RawFile = RawFile::new(tempfile().unwrap(), direct);
|
||||
let qcow_file = QcowFile::new(tmp, 3, file_size).unwrap();
|
||||
|
||||
testfn(qcow_file); // File closed when the function exits.
|
||||
@@ -1794,7 +1795,7 @@ mod tests {
|
||||
#[test]
|
||||
fn default_header_v2() {
|
||||
let header = QcowHeader::create_for_size(2, 0x10_0000);
|
||||
let mut disk_file: File = tempfile().unwrap();
|
||||
let mut disk_file: RawFile = RawFile::new(tempfile().unwrap(), false);
|
||||
header
|
||||
.write_to(&mut disk_file)
|
||||
.expect("Failed to write header to temporary file.");
|
||||
@@ -1805,7 +1806,7 @@ mod tests {
|
||||
#[test]
|
||||
fn default_header_v3() {
|
||||
let header = QcowHeader::create_for_size(3, 0x10_0000);
|
||||
let mut disk_file: File = tempfile().unwrap();
|
||||
let mut disk_file: RawFile = RawFile::new(tempfile().unwrap(), false);
|
||||
header
|
||||
.write_to(&mut disk_file)
|
||||
.expect("Failed to write header to temporary file.");
|
||||
@@ -1815,13 +1816,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn header_read() {
|
||||
with_basic_file(&valid_header_v2(), |mut disk_file: File| {
|
||||
with_basic_file(&valid_header_v2(), |mut disk_file: RawFile| {
|
||||
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
||||
assert_eq!(header.version, 2);
|
||||
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
||||
assert_eq!(header.header_size, V2_BARE_HEADER_SIZE);
|
||||
});
|
||||
with_basic_file(&valid_header_v3(), |mut disk_file: File| {
|
||||
with_basic_file(&valid_header_v3(), |mut disk_file: RawFile| {
|
||||
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
||||
assert_eq!(header.version, 3);
|
||||
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
||||
@@ -1832,7 +1833,7 @@ mod tests {
|
||||
#[test]
|
||||
fn invalid_magic() {
|
||||
let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];
|
||||
with_basic_file(&invalid_header, |mut disk_file: File| {
|
||||
with_basic_file(&invalid_header, |mut disk_file: RawFile| {
|
||||
QcowHeader::new(&mut disk_file).expect_err("Invalid header worked.");
|
||||
});
|
||||
}
|
||||
@@ -1841,7 +1842,7 @@ mod tests {
|
||||
fn invalid_refcount_order() {
|
||||
let mut header = valid_header_v3();
|
||||
header[99] = 2;
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
QcowFile::from(disk_file).expect_err("Invalid refcount order worked.");
|
||||
});
|
||||
}
|
||||
@@ -1850,7 +1851,7 @@ mod tests {
|
||||
fn invalid_cluster_bits() {
|
||||
let mut header = valid_header_v3();
|
||||
header[23] = 3;
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
@@ -1858,7 +1859,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_header_huge_file() {
|
||||
let header = test_huge_header();
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
@@ -1867,7 +1868,7 @@ mod tests {
|
||||
fn test_header_crazy_file_size_rejected() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
@@ -1876,7 +1877,7 @@ mod tests {
|
||||
fn test_huge_l1_table() {
|
||||
let mut header = valid_header_v3();
|
||||
header[36] = 0x12;
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
@@ -1888,7 +1889,7 @@ mod tests {
|
||||
header[26] = 1;
|
||||
header[31] = 0;
|
||||
// 1 TB with the min cluster size makes the arrays too big, it should fail.
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||
});
|
||||
}
|
||||
@@ -1902,7 +1903,7 @@ mod tests {
|
||||
header[31] = 0;
|
||||
// set cluster_bits
|
||||
header[23] = 16;
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
let mut qcow = QcowFile::from(disk_file).expect("Failed to create file.");
|
||||
qcow.seek(SeekFrom::Start(0x100_0000_0000 - 8))
|
||||
.expect("Failed to seek.");
|
||||
@@ -1916,7 +1917,7 @@ mod tests {
|
||||
fn test_header_huge_num_refcounts() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount clusters");
|
||||
});
|
||||
}
|
||||
@@ -1925,14 +1926,14 @@ mod tests {
|
||||
fn test_header_huge_refcount_offset() {
|
||||
let mut header = valid_header_v3();
|
||||
&mut header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
|
||||
with_basic_file(&header, |disk_file: File| {
|
||||
with_basic_file(&header, |disk_file: RawFile| {
|
||||
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount offset");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_read_start() {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: File| {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
|
||||
let mut q = QcowFile::from(disk_file).unwrap();
|
||||
q.write(b"test first bytes")
|
||||
.expect("Failed to write test string.");
|
||||
@@ -1945,7 +1946,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn offset_write_read() {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: File| {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
|
||||
let mut q = QcowFile::from(disk_file).unwrap();
|
||||
let b = [0x55u8; 0x1000];
|
||||
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
|
||||
@@ -1959,7 +1960,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn write_zeroes_read() {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: File| {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
|
||||
let mut q = QcowFile::from(disk_file).unwrap();
|
||||
// Write some test data.
|
||||
let b = [0x55u8; 0x1000];
|
||||
@@ -1985,7 +1986,7 @@ mod tests {
|
||||
// Choose a size that is larger than a cluster.
|
||||
// valid_header uses cluster_bits = 12, which corresponds to a cluster size of 4096.
|
||||
const CHUNK_SIZE: usize = 4096 * 2 + 512;
|
||||
with_basic_file(&valid_header_v3(), |disk_file: File| {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
|
||||
let mut q = QcowFile::from(disk_file).unwrap();
|
||||
// Write some test data.
|
||||
let b = [0x55u8; CHUNK_SIZE];
|
||||
@@ -2006,11 +2007,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_header() {
|
||||
with_basic_file(&valid_header_v2(), |disk_file: File| {
|
||||
with_basic_file(&valid_header_v2(), |disk_file: RawFile| {
|
||||
let q = QcowFile::from(disk_file).unwrap();
|
||||
assert_eq!(q.virtual_size(), 0x20_0000_0000);
|
||||
});
|
||||
with_basic_file(&valid_header_v3(), |disk_file: File| {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
|
||||
let q = QcowFile::from(disk_file).unwrap();
|
||||
assert_eq!(q.virtual_size(), 0x20_0000_0000);
|
||||
});
|
||||
@@ -2018,7 +2019,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn read_small_buffer() {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: File| {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
|
||||
let mut q = QcowFile::from(disk_file).unwrap();
|
||||
let mut b = [5u8; 16];
|
||||
q.seek(SeekFrom::Start(1000)).expect("Failed to seek.");
|
||||
@@ -2030,7 +2031,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn replay_ext4() {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: File| {
|
||||
with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
|
||||
let mut q = QcowFile::from(disk_file).unwrap();
|
||||
const BUF_SIZE: usize = 0x1000;
|
||||
let mut b = [0u8; BUF_SIZE];
|
||||
@@ -2406,7 +2407,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn combo_write_read() {
|
||||
with_default_file(1024 * 1024 * 1024 * 256, |mut qcow_file| {
|
||||
combo_write_read_common(false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combo_write_read_direct() {
|
||||
combo_write_read_common(true);
|
||||
}
|
||||
|
||||
fn combo_write_read_common(direct: bool) {
|
||||
with_default_file(1024 * 1024 * 1024 * 256, direct, |mut qcow_file| {
|
||||
const NUM_BLOCKS: usize = 555;
|
||||
const BLOCK_SIZE: usize = 0x1_0000;
|
||||
const OFFSET: usize = 0x1_0000_0020;
|
||||
@@ -2459,7 +2469,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn seek_data() {
|
||||
with_default_file(0x30000, |mut file| {
|
||||
seek_data_common(false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seek_data_direct() {
|
||||
seek_data_common(true);
|
||||
}
|
||||
|
||||
fn seek_data_common(direct: bool) {
|
||||
with_default_file(0x30000, direct, |mut file| {
|
||||
// seek_data at or after the end of the file should return None
|
||||
assert_eq!(file.seek_data(0x10000).unwrap(), None);
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
@@ -2492,7 +2511,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn seek_hole() {
|
||||
with_default_file(0x30000, |mut file| {
|
||||
seek_hole_common(false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seek_hole_direct() {
|
||||
seek_hole_common(true);
|
||||
}
|
||||
|
||||
fn seek_hole_common(direct: bool) {
|
||||
with_default_file(0x30000, direct, |mut file| {
|
||||
// File consisting entirely of a hole
|
||||
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
|
||||
assert_eq!(seek_cur(&mut file), 0);
|
||||
@@ -2566,7 +2594,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn rebuild_refcounts() {
|
||||
with_basic_file(&valid_header_v3(), |mut disk_file: File| {
|
||||
with_basic_file(&valid_header_v3(), |mut disk_file: RawFile| {
|
||||
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
||||
let cluster_size = 65536;
|
||||
let mut raw_file =
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufWriter, Seek, SeekFrom};
|
||||
use std::mem::size_of;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use vm_virtio::RawFile;
|
||||
use vmm_sys_util::write_zeroes::WriteZeroes;
|
||||
|
||||
/// A qcow file. Allows reading/writing clusters and appending clusters.
|
||||
#[derive(Debug)]
|
||||
pub struct QcowRawFile {
|
||||
file: File,
|
||||
file: RawFile,
|
||||
cluster_size: u64,
|
||||
cluster_mask: u64,
|
||||
}
|
||||
@@ -20,7 +20,7 @@ pub struct QcowRawFile {
|
||||
impl QcowRawFile {
|
||||
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
|
||||
/// a power of two.
|
||||
pub fn from(file: File, cluster_size: u64) -> Option<Self> {
|
||||
pub fn from(file: RawFile, cluster_size: u64) -> Option<Self> {
|
||||
if cluster_size.count_ones() != 1 {
|
||||
return None;
|
||||
}
|
||||
@@ -67,7 +67,7 @@ impl QcowRawFile {
|
||||
non_zero_flags: u64,
|
||||
) -> io::Result<()> {
|
||||
self.file.seek(SeekFrom::Start(offset))?;
|
||||
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u64>(), &self.file);
|
||||
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u64>(), &mut self.file);
|
||||
for addr in table {
|
||||
let val = if *addr == 0 {
|
||||
0
|
||||
@@ -92,7 +92,7 @@ impl QcowRawFile {
|
||||
/// Writes a refcount block to the file.
|
||||
pub fn write_refcount_block(&mut self, offset: u64, table: &[u16]) -> io::Result<()> {
|
||||
self.file.seek(SeekFrom::Start(offset))?;
|
||||
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u16>(), &self.file);
|
||||
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u16>(), &mut self.file);
|
||||
for count in table {
|
||||
buffer.write_u16::<BigEndian>(*count)?;
|
||||
}
|
||||
@@ -115,13 +115,8 @@ impl QcowRawFile {
|
||||
Ok(Some(new_cluster_address))
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying file.
|
||||
pub fn file(&self) -> &File {
|
||||
&self.file
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying file.
|
||||
pub fn file_mut(&mut self) -> &mut File {
|
||||
pub fn file_mut(&mut self) -> &mut RawFile {
|
||||
&mut self.file
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ impl<T: Cacheable> CacheMap<T> {
|
||||
{
|
||||
if self.map.len() == self.capacity {
|
||||
// TODO(dgreid) - smarter eviction strategy.
|
||||
let to_evict = *self.map.iter().nth(0).unwrap().0;
|
||||
let to_evict = *self.map.iter().next().unwrap().0;
|
||||
if let Some(evicted) = self.map.remove(&to_evict) {
|
||||
if evicted.dirty() {
|
||||
write_callback(to_evict, evicted)?;
|
||||
|
||||
304
release-notes.md
304
release-notes.md
@@ -1,32 +1,274 @@
|
||||
- [v0.6.0](#v060)
|
||||
- [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug)
|
||||
- [Shared Filesystem Improvements](#shared-filesystem-improvements)
|
||||
- [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading)
|
||||
- [Command Line Interface](#command-line-interface)
|
||||
- [PVH Boot](#pvh-boot)
|
||||
- [Contributors](#contributors)
|
||||
- [v0.5.1](#v051)
|
||||
- [v0.5.0](#v050)
|
||||
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
|
||||
- [Multi-Queue, Multi-Threaded Paravirtualization](#multi-queue-multi-threaded-paravirtualization)
|
||||
- [New Interrupt Management Framework](#new-interrupt-management-framework)
|
||||
- [Development Tools](#development-tools)
|
||||
- [Kata Containers Integration](#kata-containers-integration)
|
||||
- [Contributors](#contributors)
|
||||
- [v0.4.0](#v040)
|
||||
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
|
||||
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
|
||||
- [Filesystem and block devices vhost-user backends](#filesystem-and-block-devices-vhost-user-backends)
|
||||
- [Guest pause and resume](#guest-pause-and-resume)
|
||||
- [Userspace IOAPIC by default](#userspace-ioapic-by-default)
|
||||
- [PCI BAR reprogramming](#pci-bar-reprogramming)
|
||||
- [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization)
|
||||
- [Contributors](#contributors-1)
|
||||
- [v0.3.0](#v030)
|
||||
+ [Block device offloading](#block-device-offloading)
|
||||
+ [Network device backend](#network-device-backend)
|
||||
+ [Virtual sockets](#virtual-sockets)
|
||||
+ [HTTP based API](#http-based-api)
|
||||
+ [Memory mapped virtio transport](#memory-mapped-virtio-transport)
|
||||
+ [Paravirtualized IOMMU](#paravirtualized-iommu)
|
||||
+ [Ubuntu 19.10](#ubuntu-1910)
|
||||
+ [Guest large memory](#guest-large-memory)
|
||||
- [Block device offloading](#block-device-offloading)
|
||||
- [Network device backend](#network-device-backend)
|
||||
- [Virtual sockets](#virtual-sockets)
|
||||
- [HTTP based API](#http-based-api)
|
||||
- [Memory mapped virtio transport](#memory-mapped-virtio-transport)
|
||||
- [Paravirtualized IOMMU](#paravirtualized-iommu)
|
||||
- [Ubuntu 19.10](#ubuntu-1910)
|
||||
- [Large memory guests](#large-memory-guests)
|
||||
- [v0.2.0](#v020)
|
||||
+ [Network device offloading](#network-device-offloading)
|
||||
+ [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi)
|
||||
+ [Debug I/O port](#debug-i-o-port)
|
||||
+ [Improved direct device assignment](#improved-direct-device-assignment)
|
||||
+ [Improved shared filesystem](#improved-shared-filesystem)
|
||||
+ [Ubuntu bionic based CI](#ubuntu-bionic-based-ci)
|
||||
- [Network device offloading](#network-device-offloading)
|
||||
- [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi)
|
||||
- [Debug I/O port](#debug-io-port)
|
||||
- [Improved direct device assignment](#improved-direct-device-assignment)
|
||||
- [Improved shared filesystem](#improved-shared-filesystem)
|
||||
- [Ubuntu bionic based CI](#ubuntu-bionic-based-ci)
|
||||
- [v0.1.0](#v010)
|
||||
+ [Shared filesystem](#shared-filesystem)
|
||||
+ [Initial direct device assignment support](#initial-direct-device-assignment-support)
|
||||
+ [Userspace IOAPIC](#userspace-ioapic)
|
||||
+ [Virtual persistent memory](#virtual-persistent-memory)
|
||||
+ [Linux kernel bzImage](#linux-kernel-bzimage)
|
||||
+ [Console over virtio](#console-over-virtio)
|
||||
+ [Unit testing](#unit-testing)
|
||||
+ [Integration tests parallelization](#integration-tests-parallelization)
|
||||
- [Shared filesystem](#shared-filesystem)
|
||||
- [Initial direct device assignment support](#initial-direct-device-assignment-support)
|
||||
- [Userspace IOAPIC](#userspace-ioapic)
|
||||
- [Virtual persistent memory](#virtual-persistent-memory)
|
||||
- [Linux kernel bzImage](#linux-kernel-bzimage)
|
||||
- [Console over virtio](#console-over-virtio)
|
||||
- [Unit testing](#unit-testing)
|
||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v0.6.0
|
||||
|
||||
This release has been tracked through the [0.6.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.6.0 include:
|
||||
|
||||
### Directly Assigned Devices Hotplug
|
||||
|
||||
We continued our efforts around supporting dynamically changing the guest
|
||||
resources. After adding support for CPU and memory hotplug, Cloud Hypervisor
|
||||
now supports hot plugging and hot unplugging directly assigned (a.k.a. `VFIO`)
|
||||
devices into an already running guest. This closes the features gap for
|
||||
providing a complete Kata Containers workloads support with Cloud Hypervisor.
|
||||
|
||||
### Shared Filesystem Improvements
|
||||
|
||||
We enhanced our shared filesystem support through many `virtio-fs` improvements.
|
||||
By adding support for DAX, parallel processing of multiple requests, `FS_IO`,
|
||||
`LSEEK` and the `MMIO` virtio transport layer to our `vhost_user_fs` daemon, we
|
||||
improved our filesystem sharing performance, but also made it more stable and
|
||||
compatible with other `virtio-fs` implementations.
|
||||
|
||||
### Block and Networking IO Self Offloading
|
||||
|
||||
When choosing to offload the paravirtualized block and networking I/O to an
|
||||
external process (through the `vhost-user` protocol), Cloud Hypervisor now
|
||||
automatically spawns its default `vhost-user-blk` and `vhost-user-net` backends
|
||||
into their own, separate processes.
|
||||
This provides a seamless parvirtualized I/O user experience for those who want
|
||||
to run their guest I/O into separate executions contexts.
|
||||
|
||||
### Command Line Interface
|
||||
|
||||
More and more Cloud Hypervisor services are exposed through the
|
||||
[Rest API](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/vmm/src/api/openapi/cloud-hypervisor.yaml)
|
||||
and thus only accessible via relatively cumbersome HTTP calls. In order
|
||||
to abstract those calls into a more user friendly tool, we created a Cloud
|
||||
Hypervisor Command Line Interface (CLI) called `ch-remote`.
|
||||
The `ch-remote` binary is created with each build and available e.g. at
|
||||
`cloud-hypervisor/target/debug/ch-remote` when doing a debug build.
|
||||
|
||||
Please check `ch-remote --help` for a complete description of all available
|
||||
commands.
|
||||
|
||||
### PVH Boot
|
||||
|
||||
In addition to the traditional Linux boot protocol, Cloud Hypervisor now
|
||||
supports direct kernel booting through the [PVH ABI](https://xenbits.xen.org/docs/unstable/misc/pvh.html).
|
||||
|
||||
### Contributors
|
||||
|
||||
With the 0.6.0 release, we are welcoming a few new contributors. Many thanks
|
||||
to them and to everyone that contributed to this release:
|
||||
|
||||
* Alejandro Jimenez <alejandro.j.jimenez@oracle.com>
|
||||
* Arron Wang <arron.wang@intel.com>
|
||||
* Bin Liu <liubin0329@gmail.com>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Cathy Zhang <cathy.zhang@intel.com>
|
||||
* Eryu Guan <eguan@linux.alibaba.com>
|
||||
* Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
|
||||
* Liu Bo <bo.liu@linux.alibaba.com>
|
||||
* Qiu Wenbo <qiuwenbo@phytium.com.cn>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Samuel Ortiz <sameo@linux.intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Sergio Lopez <slp@redhat.com>
|
||||
|
||||
# v0.5.1
|
||||
|
||||
This is a bugfix release branched off v0.5.0. It contains the following fixes:
|
||||
|
||||
* Update DiskConfig to contain missing disk control features (#790) - Samuel Ortiz and Sergio Lopez
|
||||
* Prevent memory overcommit via virtio-fs (#763) - Sebastien Boeuf
|
||||
* Fixed error reporting for resize command - Samuel Ortiz
|
||||
* Double reboot workaround (#783) - Rob Bradford
|
||||
* Various CI and development tooling fixes - Sebastien Boeuf, Samuel Ortiz, Rob Bradford
|
||||
|
||||
# v0.5.0
|
||||
|
||||
This release has been tracked through the [0.5.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/6).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.5.0 include:
|
||||
|
||||
### Virtual Machine Dynamic Resizing
|
||||
|
||||
With 0.4.0 we added support for CPU hot plug, and 0.5.0 adds CPU hot unplug and
|
||||
memory hot plug as well. This allows to dynamically resize Cloud Hypervisor
|
||||
guests which is needed for e.g. Kubernetes related use cases.
|
||||
The memory hot plug implementation is based on the same framework as the CPU hot
|
||||
plug/unplug one, i.e. hardware-reduced ACPI notifications to the guest.
|
||||
|
||||
Next on our VM resizing roadmap is the PCI devices hotplug feature.
|
||||
|
||||
### Multi-Queue, Multi-Threaded Paravirtualization
|
||||
|
||||
We enhanced our virtio networking and block support by having both devices use
|
||||
multiple I/O queues handled by multiple threads. This improves our default
|
||||
paravirtualized networking and block devices throughput.
|
||||
|
||||
### New Interrupt Management Framework
|
||||
|
||||
We improved our interrupt management implementation by introducing an Interrupt
|
||||
Manager framework, based on the currently on-going [rust-vmm vm-device](https://github.com/rust-vmm/vm-device)
|
||||
crates discussions. This move made the code significantly cleaner, and allowed
|
||||
us to remove several KVM related dependencies from crates like the PCI and
|
||||
virtio ones.
|
||||
|
||||
### Development Tools
|
||||
|
||||
In order to provide a better developer experience, we worked on improving our
|
||||
build, development and testing tools.
|
||||
Somehow similar to the excellent
|
||||
[Firecracker's devtool](https://github.com/firecracker-microvm/firecracker/blob/master/tools/devtool),
|
||||
we now provide a [dev_cli script](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/scripts/dev_cli.sh).
|
||||
|
||||
With this new tool, our users and contributors will be able to build and test
|
||||
Cloud Hypervisor through a containerized environment.
|
||||
|
||||
### Kata Containers Integration
|
||||
|
||||
We spent some significant time and efforts debugging and fixing our integration
|
||||
with the [Kata Containers](https://github.com/kata-containers) project. Cloud
|
||||
Hypervisor is now a fully supported Kata Containers hypervisor, and is
|
||||
integrated into the project's CI.
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone that contributed to the 0.5.0 release:
|
||||
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Cathy Zhang <cathy.zhang@intel.com>
|
||||
* Qiu Wenbo <qiuwenbo@phytium.com.cn>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Samuel Ortiz <sameo@linux.intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Sergio Lopez <slp@redhat.com>
|
||||
* Yang Zhong <yang.zhong@intel.com>
|
||||
|
||||
# v0.4.0
|
||||
|
||||
This release has been tracked through the [0.4.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/4).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.4.0 include:
|
||||
|
||||
### Dynamic virtual CPUs addition
|
||||
|
||||
As a way to vertically scale Cloud-Hypervisor guests, we now support dynamically
|
||||
adding virtual CPUs to the guests, a mechanism also known as CPU hot plug.
|
||||
Through hardware-reduced ACPI notifications, Cloud Hypervisor can now add CPUs
|
||||
to an already running guest and the high level operations for that process are
|
||||
documented [here](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/hotplug.md)
|
||||
|
||||
During the next release cycles we are planning to extend Cloud Hypervisor
|
||||
hot plug framework to other resources, namely PCI devices and memory.
|
||||
|
||||
### Programmatic firmware tables generation
|
||||
|
||||
As part of the CPU hot plug feature enablement, and as a requirement for hot
|
||||
plugging other resources like devices or RAM, we added support for
|
||||
programmatically generating the needed ACPI tables. Through a dedicated
|
||||
`acpi-tables` crate, we now have a flexible and clean way of generating those
|
||||
tables based on the VMM device model and topology.
|
||||
|
||||
### Filesystem and block devices vhost-user backends
|
||||
|
||||
Our objective of running all Cloud Hypervisor paravirtualized I/O to a
|
||||
vhost-user based framework is getting closer as we've added Rust based
|
||||
implementations for vhost-user-blk and virtiofs backends. Together with the
|
||||
vhost-user-net backend that came with the 0.3.0 release, this will form the
|
||||
default Cloud Hypervisor I/O architecture.
|
||||
|
||||
### Guest pause and resume
|
||||
|
||||
As an initial requiremnt for enabling live migration, we added support for
|
||||
pausing and resuming any VMM components. As an intermediate step towards live
|
||||
migration, the upcoming guest snapshotting feature will be based on the pause
|
||||
and resume capabilities.
|
||||
|
||||
### Userspace IOAPIC by default
|
||||
|
||||
As a way to simplify our device manager implementation, but also in order to
|
||||
stay away from privileged rings as often as possible, any device that relies on
|
||||
pin based interrupts will be using the userspace IOAPIC implementation by
|
||||
default.
|
||||
|
||||
### PCI BAR reprogramming
|
||||
|
||||
In order to allow for a more flexible device model, and also support guests
|
||||
that would want to move PCI devices, we added support for PCI devices BAR
|
||||
reprogramming.
|
||||
|
||||
### New `cloud-hypervisor` organization
|
||||
|
||||
As we wanted to be more flexible on how we manage the Cloud Hypervisor project,
|
||||
we decided to move it under a [dedicated GitHub organization](https://github.com/cloud-hypervisor).
|
||||
Together with the [cloud-hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor)
|
||||
project, this new organization also now hosts our [kernel](https://github.com/cloud-hypervisor/linux)
|
||||
and [firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware)
|
||||
repositories. We may also use it to host any rust-vmm that we'd need to
|
||||
temporarily fork.
|
||||
Thanks to GitHub's seamless repository redirections, the move is completely
|
||||
transparent to all Cloud Hypervisor contributors, users and followers.
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone that contributed to the 0.4.0 release:
|
||||
|
||||
* Cathy Zhang <cathy.zhang@intel.com>
|
||||
* Emin Ghuliev <drmint80@gmail.com>
|
||||
* Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
|
||||
* Qiu Wenbo <qiuwenbo@phytium.com.cn>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Samuel Ortiz <sameo@linux.intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Sergio Lopez <slp@redhat.com>
|
||||
* Wu Zongyong <wuzongyong@linux.alibaba.com>
|
||||
|
||||
# v0.3.0
|
||||
|
||||
This release has been tracked through the [0.3.0 project](https://github.com/intel/cloud-hypervisor/projects/3).
|
||||
This release has been tracked through the [0.3.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/3).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.3.0 include:
|
||||
|
||||
@@ -73,7 +315,7 @@ configurations that do not require a PCI bus emulation.
|
||||
### Paravirtualized IOMMU
|
||||
|
||||
As we want to improve our nested guests support, we added support for exposing
|
||||
a [paravirtualized IOMMU](https://github.com/intel/cloud-hypervisor/blob/master/docs/iommu.md)
|
||||
a [paravirtualized IOMMU](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/iommu.md)
|
||||
device through virtio. This allows for a safer nested virtio and directly
|
||||
assigned devices support.
|
||||
|
||||
@@ -85,7 +327,7 @@ setting.
|
||||
|
||||
### Ubuntu 19.10
|
||||
|
||||
With the latest [hypervisor firmware](https://github.com/intel/rust-hypervisor-firmware),
|
||||
With the latest [hypervisor firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware),
|
||||
we can now support the latest
|
||||
[Ubuntu 19.10 (Eoan Ermine)](http://releases.ubuntu.com/19.10/) cloud images.
|
||||
|
||||
@@ -96,7 +338,7 @@ support guests with large amount of memory (more than 64GB).
|
||||
|
||||
# v0.2.0
|
||||
|
||||
This release has been tracked through the [0.2.0 project](https://github.com/intel/cloud-hypervisor/projects/2).
|
||||
This release has been tracked through the [0.2.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/2).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.2.0 include:
|
||||
|
||||
@@ -124,7 +366,7 @@ Based on the Firecracker idea of using a dedicated I/O port to measure guest
|
||||
boot times, we added support for logging guest events through the
|
||||
[0x80](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||
PC debug port. This allows, among other things, for granular guest boot time
|
||||
measurements. See our [debug port documentation](https://github.com/intel/cloud-hypervisor/blob/master/docs/debug-port.md)
|
||||
measurements. See our [debug port documentation](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/debug-port.md)
|
||||
for more details.
|
||||
|
||||
### Improved direct device assignment
|
||||
@@ -144,13 +386,13 @@ memory footprint.
|
||||
|
||||
### Ubuntu bionic based CI
|
||||
|
||||
Thanks to our [simple KVM firmware](https://github.com/intel/rust-hypervisor-firmware)
|
||||
Thanks to our [simple KVM firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware)
|
||||
improvements, we are now able to boot Ubuntu bionic images. We added those to
|
||||
our CI pipeline.
|
||||
|
||||
# v0.1.0
|
||||
|
||||
This release has been tracked through the [0.1.0 project](https://github.com/intel/cloud-hypervisor/projects/1).
|
||||
This release has been tracked through the [0.1.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/1).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.1.0 include:
|
||||
|
||||
@@ -160,7 +402,7 @@ We added support for the [virtio-fs](https://virtio-fs.gitlab.io/) shared file
|
||||
system, allowing for an efficient and reliable way of sharing a filesystem
|
||||
between the host and the `cloud-hypervisor` guest.
|
||||
|
||||
See our [filesystem sharing](https://github.com/intel/cloud-hypervisor/blob/master/docs/fs.md)
|
||||
See our [filesystem sharing](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/fs.md)
|
||||
documentation for more details on how to use virtio-fs with `cloud-hypervisor`.
|
||||
|
||||
### Initial direct device assignment support
|
||||
@@ -169,7 +411,7 @@ VFIO (Virtual Function I/O) is a kernel framework that exposes direct device
|
||||
access to userspace. `cloud-hypervisor` uses VFIO to directly assign host
|
||||
physical devices into its guest.
|
||||
|
||||
See our [VFIO](https://github.com/intel/cloud-hypervisor/blob/master/docs/vfio.md)
|
||||
See our [VFIO](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/docs/vfio.md)
|
||||
documentation for more detail on how to directly assign host devices to
|
||||
`cloud-hypervisor` guests.
|
||||
|
||||
|
||||
53
resources/Dockerfile
Normal file
53
resources/Dockerfile
Normal file
@@ -0,0 +1,53 @@
|
||||
FROM ubuntu:18.04 as dev
|
||||
|
||||
ARG RUST_TOOLCHAIN="1.42.0"
|
||||
ARG CLH_SRC_DIR="/cloud-hypervisor"
|
||||
ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build"
|
||||
ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry"
|
||||
ARG CARGO_GIT_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_git_registry"
|
||||
|
||||
ENV CARGO_HOME=/usr/local/rust
|
||||
ENV RUSTUP_HOME=$CARGO_HOME
|
||||
ENV PATH="$PATH:$CARGO_HOME/bin"
|
||||
|
||||
# Install all CI dependencies
|
||||
RUN apt-get update
|
||||
RUN apt-get -yq upgrade
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
build-essential \
|
||||
bc \
|
||||
docker.io \
|
||||
curl \
|
||||
wget \
|
||||
sudo \
|
||||
mtools \
|
||||
libssl-dev \
|
||||
pkg-config \
|
||||
flex \
|
||||
bison \
|
||||
libelf-dev \
|
||||
qemu-utils \
|
||||
qemu-system \
|
||||
libglib2.0-dev \
|
||||
libpixman-1-dev \
|
||||
libseccomp-dev \
|
||||
libcap-ng-dev \
|
||||
socat \
|
||||
dosfstools \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install the rust toolchain
|
||||
RUN nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" \
|
||||
&& rustup component add rustfmt \
|
||||
&& rustup component add clippy \
|
||||
&& cargo install cargo-audit \
|
||||
&& rm -rf "$CARGO_HOME/registry" \
|
||||
&& ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \
|
||||
&& rm -rf "$CARGO_HOME/git" \
|
||||
&& ln -s "$CARGO_GIT_REGISTRY_DIR" "$CARGO_HOME/git"
|
||||
|
||||
# Set the rust environment
|
||||
RUN echo 'source $CARGO_HOME/env' >> $HOME/.bashrc \
|
||||
&& mkdir $HOME/.cargo \
|
||||
&& ln -s $CARGO_HOME/env $HOME/.cargo/env
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Automatically generated file; DO NOT EDIT.
|
||||
# Linux/x86 5.3.0-rc3 Kernel Configuration
|
||||
# Linux/x86 5.6.0-rc4 Kernel Configuration
|
||||
#
|
||||
|
||||
#
|
||||
@@ -11,9 +11,10 @@ CONFIG_GCC_VERSION=80300
|
||||
CONFIG_CLANG_VERSION=0
|
||||
CONFIG_CC_CAN_LINK=y
|
||||
CONFIG_CC_HAS_ASM_GOTO=y
|
||||
CONFIG_CC_HAS_ASM_INLINE=y
|
||||
CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y
|
||||
CONFIG_IRQ_WORK=y
|
||||
CONFIG_BUILDTIME_EXTABLE_SORT=y
|
||||
CONFIG_BUILDTIME_TABLE_SORT=y
|
||||
CONFIG_THREAD_INFO_IN_TASK=y
|
||||
|
||||
#
|
||||
@@ -21,7 +22,6 @@ CONFIG_THREAD_INFO_IN_TASK=y
|
||||
#
|
||||
CONFIG_INIT_ENV_ARG_LIMIT=32
|
||||
# CONFIG_COMPILE_TEST is not set
|
||||
# CONFIG_HEADER_TEST is not set
|
||||
CONFIG_LOCALVERSION=""
|
||||
# CONFIG_LOCALVERSION_AUTO is not set
|
||||
CONFIG_BUILD_SALT=""
|
||||
@@ -139,6 +139,7 @@ CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y
|
||||
|
||||
CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y
|
||||
CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y
|
||||
CONFIG_CC_HAS_INT128=y
|
||||
CONFIG_ARCH_SUPPORTS_INT128=y
|
||||
CONFIG_NUMA_BALANCING=y
|
||||
# CONFIG_NUMA_BALANCING_DEFAULT_ENABLED is not set
|
||||
@@ -168,6 +169,7 @@ CONFIG_CGROUP_BPF=y
|
||||
CONFIG_SOCK_CGROUP_DATA=y
|
||||
CONFIG_NAMESPACES=y
|
||||
CONFIG_UTS_NS=y
|
||||
# CONFIG_TIME_NS is not set
|
||||
CONFIG_IPC_NS=y
|
||||
CONFIG_USER_NS=y
|
||||
CONFIG_PID_NS=y
|
||||
@@ -177,13 +179,14 @@ CONFIG_SCHED_AUTOGROUP=y
|
||||
# CONFIG_SYSFS_DEPRECATED is not set
|
||||
CONFIG_RELAY=y
|
||||
# CONFIG_BLK_DEV_INITRD is not set
|
||||
# CONFIG_BOOT_CONFIG is not set
|
||||
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
|
||||
# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
|
||||
CONFIG_SYSCTL=y
|
||||
CONFIG_SYSCTL_EXCEPTION_TRACE=y
|
||||
CONFIG_HAVE_PCSPKR_PLATFORM=y
|
||||
CONFIG_BPF=y
|
||||
# CONFIG_EXPERT is not set
|
||||
CONFIG_EXPERT=y
|
||||
CONFIG_MULTIUSER=y
|
||||
CONFIG_SGETMASK_SYSCALL=y
|
||||
CONFIG_SYSFS_SYSCALL=y
|
||||
@@ -211,11 +214,14 @@ CONFIG_KALLSYMS=y
|
||||
CONFIG_KALLSYMS_ABSOLUTE_PERCPU=y
|
||||
CONFIG_KALLSYMS_BASE_RELATIVE=y
|
||||
CONFIG_BPF_SYSCALL=y
|
||||
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
|
||||
CONFIG_USERFAULTFD=y
|
||||
CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y
|
||||
CONFIG_RSEQ=y
|
||||
# CONFIG_DEBUG_RSEQ is not set
|
||||
# CONFIG_EMBEDDED is not set
|
||||
CONFIG_HAVE_PERF_EVENTS=y
|
||||
# CONFIG_PC104 is not set
|
||||
|
||||
#
|
||||
# Kernel Performance Events And Counters
|
||||
@@ -226,9 +232,11 @@ CONFIG_PERF_EVENTS=y
|
||||
|
||||
CONFIG_VM_EVENT_COUNTERS=y
|
||||
CONFIG_SLUB_DEBUG=y
|
||||
# CONFIG_SLUB_MEMCG_SYSFS_ON is not set
|
||||
# CONFIG_COMPAT_BRK is not set
|
||||
# CONFIG_SLAB is not set
|
||||
CONFIG_SLUB=y
|
||||
# CONFIG_SLOB is not set
|
||||
CONFIG_SLAB_MERGE_DEFAULT=y
|
||||
# CONFIG_SLAB_FREELIST_RANDOM is not set
|
||||
CONFIG_SLAB_FREELIST_HARDENED=y
|
||||
@@ -296,6 +304,7 @@ CONFIG_PARAVIRT=y
|
||||
CONFIG_PARAVIRT_SPINLOCKS=y
|
||||
# CONFIG_XEN is not set
|
||||
CONFIG_KVM_GUEST=y
|
||||
CONFIG_ARCH_CPUIDLE_HALTPOLL=y
|
||||
CONFIG_PVH=y
|
||||
CONFIG_KVM_DEBUG_FS=y
|
||||
CONFIG_PARAVIRT_TIME_ACCOUNTING=y
|
||||
@@ -314,6 +323,9 @@ CONFIG_X86_CMPXCHG64=y
|
||||
CONFIG_X86_CMOV=y
|
||||
CONFIG_X86_MINIMUM_CPU_FAMILY=64
|
||||
CONFIG_X86_DEBUGCTLMSR=y
|
||||
CONFIG_IA32_FEAT_CTL=y
|
||||
CONFIG_X86_VMX_FEATURE_NAMES=y
|
||||
# CONFIG_PROCESSOR_SELECT is not set
|
||||
CONFIG_CPU_SUP_INTEL=y
|
||||
CONFIG_CPU_SUP_AMD=y
|
||||
CONFIG_CPU_SUP_HYGON=y
|
||||
@@ -322,7 +334,6 @@ CONFIG_CPU_SUP_ZHAOXIN=y
|
||||
CONFIG_HPET_TIMER=y
|
||||
CONFIG_DMI=y
|
||||
# CONFIG_GART_IOMMU is not set
|
||||
# CONFIG_CALGARY_IOMMU is not set
|
||||
# CONFIG_MAXSMP is not set
|
||||
CONFIG_NR_CPUS_RANGE_BEGIN=2
|
||||
CONFIG_NR_CPUS_RANGE_END=512
|
||||
@@ -348,6 +359,7 @@ CONFIG_PERF_EVENTS_INTEL_CSTATE=y
|
||||
CONFIG_X86_16BIT=y
|
||||
CONFIG_X86_ESPFIX64=y
|
||||
CONFIG_X86_VSYSCALL_EMULATION=y
|
||||
# CONFIG_X86_IOPL_IOPERM is not set
|
||||
# CONFIG_I8K is not set
|
||||
# CONFIG_MICROCODE is not set
|
||||
CONFIG_X86_MSR=y
|
||||
@@ -355,7 +367,6 @@ CONFIG_X86_CPUID=y
|
||||
# CONFIG_X86_5LEVEL is not set
|
||||
CONFIG_X86_DIRECT_GBPAGES=y
|
||||
# CONFIG_X86_CPA_STATISTICS is not set
|
||||
CONFIG_ARCH_HAS_MEM_ENCRYPT=y
|
||||
CONFIG_AMD_MEM_ENCRYPT=y
|
||||
# CONFIG_AMD_MEM_ENCRYPT_ACTIVE_BY_DEFAULT is not set
|
||||
CONFIG_NUMA=y
|
||||
@@ -382,9 +393,11 @@ CONFIG_X86_PAT=y
|
||||
CONFIG_ARCH_USES_PG_UNCACHED=y
|
||||
CONFIG_ARCH_RANDOM=y
|
||||
CONFIG_X86_SMAP=y
|
||||
CONFIG_X86_INTEL_UMIP=y
|
||||
# CONFIG_X86_INTEL_MPX is not set
|
||||
# CONFIG_X86_UMIP is not set
|
||||
# CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS is not set
|
||||
CONFIG_X86_INTEL_TSX_MODE_OFF=y
|
||||
# CONFIG_X86_INTEL_TSX_MODE_ON is not set
|
||||
# CONFIG_X86_INTEL_TSX_MODE_AUTO is not set
|
||||
CONFIG_EFI=y
|
||||
CONFIG_EFI_STUB=y
|
||||
# CONFIG_EFI_MIXED is not set
|
||||
@@ -398,12 +411,16 @@ CONFIG_SCHED_HRTICK=y
|
||||
# CONFIG_KEXEC is not set
|
||||
CONFIG_KEXEC_FILE=y
|
||||
CONFIG_ARCH_HAS_KEXEC_PURGATORY=y
|
||||
CONFIG_KEXEC_VERIFY_SIG=y
|
||||
# CONFIG_KEXEC_SIG is not set
|
||||
# CONFIG_CRASH_DUMP is not set
|
||||
CONFIG_PHYSICAL_START=0x1000000
|
||||
CONFIG_RELOCATABLE=y
|
||||
# CONFIG_RANDOMIZE_BASE is not set
|
||||
CONFIG_RANDOMIZE_BASE=y
|
||||
CONFIG_X86_NEED_RELOCS=y
|
||||
CONFIG_PHYSICAL_ALIGN=0x1000000
|
||||
CONFIG_DYNAMIC_MEMORY_LAYOUT=y
|
||||
CONFIG_RANDOMIZE_MEMORY=y
|
||||
CONFIG_RANDOMIZE_MEMORY_PHYSICAL_PADDING=0xa
|
||||
CONFIG_HOTPLUG_CPU=y
|
||||
# CONFIG_BOOTPARAM_HOTPLUG_CPU0 is not set
|
||||
# CONFIG_DEBUG_HOTPLUG_CPU0 is not set
|
||||
@@ -467,18 +484,19 @@ CONFIG_ACPI_PROCESSOR=y
|
||||
CONFIG_ACPI_HOTPLUG_CPU=y
|
||||
# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set
|
||||
CONFIG_ACPI_THERMAL=y
|
||||
CONFIG_ACPI_NUMA=y
|
||||
CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
|
||||
# CONFIG_ACPI_DEBUG is not set
|
||||
# CONFIG_ACPI_PCI_SLOT is not set
|
||||
CONFIG_ACPI_CONTAINER=y
|
||||
# CONFIG_ACPI_HOTPLUG_MEMORY is not set
|
||||
CONFIG_ACPI_HOTPLUG_MEMORY=y
|
||||
CONFIG_ACPI_HOTPLUG_IOAPIC=y
|
||||
# CONFIG_ACPI_SBS is not set
|
||||
# CONFIG_ACPI_HED is not set
|
||||
# CONFIG_ACPI_CUSTOM_METHOD is not set
|
||||
# CONFIG_ACPI_BGRT is not set
|
||||
CONFIG_ACPI_REDUCED_HARDWARE_ONLY=y
|
||||
# CONFIG_ACPI_NFIT is not set
|
||||
CONFIG_ACPI_NUMA=y
|
||||
# CONFIG_ACPI_HMAT is not set
|
||||
CONFIG_HAVE_ACPI_APEI=y
|
||||
CONFIG_HAVE_ACPI_APEI_NMI=y
|
||||
@@ -486,7 +504,6 @@ CONFIG_HAVE_ACPI_APEI_NMI=y
|
||||
# CONFIG_DPTF_POWER is not set
|
||||
# CONFIG_PMIC_OPREGION is not set
|
||||
# CONFIG_ACPI_CONFIGFS is not set
|
||||
CONFIG_ACPI_IORT=y
|
||||
CONFIG_X86_PM_TIMER=y
|
||||
# CONFIG_SFI is not set
|
||||
|
||||
@@ -529,6 +546,8 @@ CONFIG_CPU_IDLE=y
|
||||
CONFIG_CPU_IDLE_GOV_LADDER=y
|
||||
CONFIG_CPU_IDLE_GOV_MENU=y
|
||||
# CONFIG_CPU_IDLE_GOV_TEO is not set
|
||||
# CONFIG_CPU_IDLE_GOV_HALTPOLL is not set
|
||||
# CONFIG_HALTPOLL_CPUIDLE is not set
|
||||
# end of CPU Idle
|
||||
|
||||
CONFIG_INTEL_IDLE=y
|
||||
@@ -540,6 +559,8 @@ CONFIG_INTEL_IDLE=y
|
||||
CONFIG_PCI_DIRECT=y
|
||||
CONFIG_PCI_MMCONFIG=y
|
||||
CONFIG_MMCONF_FAM10H=y
|
||||
# CONFIG_PCI_CNB20LE_QUIRK is not set
|
||||
# CONFIG_ISA_BUS is not set
|
||||
CONFIG_ISA_DMA_API=y
|
||||
CONFIG_AMD_NB=y
|
||||
# CONFIG_X86_SYSFB is not set
|
||||
@@ -575,6 +596,8 @@ CONFIG_EFI_RUNTIME_WRAPPERS=y
|
||||
# CONFIG_EFI_TEST is not set
|
||||
# CONFIG_APPLE_PROPERTIES is not set
|
||||
# CONFIG_RESET_ATTACK_MITIGATION is not set
|
||||
# CONFIG_EFI_RCI2_TABLE is not set
|
||||
# CONFIG_EFI_DISABLE_PCI_DMA is not set
|
||||
# end of EFI (Extensible Firmware Interface) Support
|
||||
|
||||
CONFIG_EFI_EARLYCON=y
|
||||
@@ -600,6 +623,7 @@ CONFIG_HAVE_KVM_IRQ_BYPASS=y
|
||||
CONFIG_HAVE_KVM_NO_POLL=y
|
||||
CONFIG_VIRTUALIZATION=y
|
||||
CONFIG_KVM=y
|
||||
# CONFIG_KVM_WERROR is not set
|
||||
CONFIG_KVM_INTEL=y
|
||||
# CONFIG_KVM_AMD is not set
|
||||
# CONFIG_VHOST_NET is not set
|
||||
@@ -634,6 +658,7 @@ CONFIG_ARCH_HAS_SET_MEMORY=y
|
||||
CONFIG_ARCH_HAS_SET_DIRECT_MAP=y
|
||||
CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y
|
||||
CONFIG_ARCH_WANTS_DYNAMIC_TASK_STRUCT=y
|
||||
CONFIG_HAVE_ASM_MODVERSIONS=y
|
||||
CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y
|
||||
CONFIG_HAVE_RSEQ=y
|
||||
CONFIG_HAVE_FUNCTION_ARG_ACCESS_API=y
|
||||
@@ -647,7 +672,8 @@ CONFIG_HAVE_PERF_REGS=y
|
||||
CONFIG_HAVE_PERF_USER_STACK_DUMP=y
|
||||
CONFIG_HAVE_ARCH_JUMP_LABEL=y
|
||||
CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE=y
|
||||
CONFIG_HAVE_RCU_TABLE_FREE=y
|
||||
CONFIG_MMU_GATHER_TABLE_FREE=y
|
||||
CONFIG_MMU_GATHER_RCU_TABLE_FREE=y
|
||||
CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG=y
|
||||
CONFIG_HAVE_ALIGNED_STRUCT_PAGE=y
|
||||
CONFIG_HAVE_CMPXCHG_LOCAL=y
|
||||
@@ -679,17 +705,16 @@ CONFIG_ARCH_MMAP_RND_BITS=28
|
||||
CONFIG_HAVE_COPY_THREAD_TLS=y
|
||||
CONFIG_HAVE_STACK_VALIDATION=y
|
||||
CONFIG_HAVE_RELIABLE_STACKTRACE=y
|
||||
CONFIG_64BIT_TIME=y
|
||||
# CONFIG_COMPAT_32BIT_TIME is not set
|
||||
CONFIG_HAVE_ARCH_VMAP_STACK=y
|
||||
CONFIG_VMAP_STACK=y
|
||||
CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y
|
||||
CONFIG_STRICT_KERNEL_RWX=y
|
||||
CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y
|
||||
CONFIG_ARCH_HAS_REFCOUNT=y
|
||||
# CONFIG_REFCOUNT_FULL is not set
|
||||
CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y
|
||||
CONFIG_ARCH_USE_MEMREMAP_PROT=y
|
||||
# CONFIG_LOCK_EVENT_COUNTS is not set
|
||||
CONFIG_ARCH_HAS_MEM_ENCRYPT=y
|
||||
|
||||
#
|
||||
# GCOV-based kernel profiling
|
||||
@@ -708,6 +733,7 @@ CONFIG_BASE_SMALL=0
|
||||
CONFIG_MODULES_TREE_LOOKUP=y
|
||||
CONFIG_BLOCK=y
|
||||
CONFIG_BLK_SCSI_REQUEST=y
|
||||
CONFIG_BLK_CGROUP_RWSTAT=y
|
||||
CONFIG_BLK_DEV_BSG=y
|
||||
CONFIG_BLK_DEV_BSGLIB=y
|
||||
CONFIG_BLK_DEV_INTEGRITY=y
|
||||
@@ -717,6 +743,7 @@ CONFIG_BLK_DEV_THROTTLING=y
|
||||
CONFIG_BLK_CMDLINE_PARSER=y
|
||||
CONFIG_BLK_WBT=y
|
||||
# CONFIG_BLK_CGROUP_IOLATENCY is not set
|
||||
# CONFIG_BLK_CGROUP_IOCOST is not set
|
||||
CONFIG_BLK_WBT_MQ=y
|
||||
CONFIG_BLK_DEBUG_FS=y
|
||||
# CONFIG_BLK_SED_OPAL is not set
|
||||
@@ -836,10 +863,10 @@ CONFIG_GENERIC_EARLY_IOREMAP=y
|
||||
CONFIG_ARCH_HAS_PTE_DEVMAP=y
|
||||
CONFIG_ZONE_DEVICE=y
|
||||
CONFIG_DEV_PAGEMAP_OPS=y
|
||||
# CONFIG_HMM_MIRROR is not set
|
||||
# CONFIG_DEVICE_PRIVATE is not set
|
||||
CONFIG_PERCPU_STATS=y
|
||||
# CONFIG_GUP_BENCHMARK is not set
|
||||
# CONFIG_READ_ONLY_THP_FOR_FS is not set
|
||||
CONFIG_ARCH_HAS_PTE_SPECIAL=y
|
||||
# end of Memory Management options
|
||||
|
||||
@@ -856,6 +883,7 @@ CONFIG_UNIX_SCM=y
|
||||
CONFIG_UNIX_DIAG=y
|
||||
CONFIG_TLS=y
|
||||
# CONFIG_TLS_DEVICE is not set
|
||||
# CONFIG_TLS_TOE is not set
|
||||
CONFIG_XFRM=y
|
||||
CONFIG_XFRM_ALGO=y
|
||||
CONFIG_XFRM_USER=y
|
||||
@@ -890,6 +918,7 @@ CONFIG_TCP_CONG_CUBIC=y
|
||||
CONFIG_DEFAULT_TCP_CONG="cubic"
|
||||
# CONFIG_TCP_MD5SIG is not set
|
||||
# CONFIG_IPV6 is not set
|
||||
# CONFIG_MPTCP is not set
|
||||
# CONFIG_NETWORK_SECMARK is not set
|
||||
CONFIG_NET_PTP_CLASSIFY=y
|
||||
# CONFIG_NETWORK_PHY_TIMESTAMPING is not set
|
||||
@@ -919,6 +948,7 @@ CONFIG_HAVE_NET_DSA=y
|
||||
# CONFIG_OPENVSWITCH is not set
|
||||
CONFIG_VSOCKETS=y
|
||||
CONFIG_VSOCKETS_DIAG=y
|
||||
# CONFIG_VSOCKETS_LOOPBACK is not set
|
||||
CONFIG_VIRTIO_VSOCKETS=y
|
||||
CONFIG_VIRTIO_VSOCKETS_COMMON=y
|
||||
# CONFIG_NETLINK_DIAG is not set
|
||||
@@ -964,6 +994,7 @@ CONFIG_STREAM_PARSER=y
|
||||
CONFIG_GRO_CELLS=y
|
||||
CONFIG_NET_SOCK_MSG=y
|
||||
CONFIG_FAILOVER=y
|
||||
# CONFIG_ETHTOOL_NETLINK is not set
|
||||
CONFIG_HAVE_EBPF_JIT=y
|
||||
|
||||
#
|
||||
@@ -975,11 +1006,11 @@ CONFIG_HAVE_PCI=y
|
||||
CONFIG_PCI=y
|
||||
CONFIG_PCI_DOMAINS=y
|
||||
CONFIG_PCIEPORTBUS=y
|
||||
# CONFIG_HOTPLUG_PCI_PCIE is not set
|
||||
CONFIG_PCIEAER=y
|
||||
# CONFIG_PCIEAER_INJECT is not set
|
||||
# CONFIG_PCIE_ECRC is not set
|
||||
CONFIG_PCIEASPM=y
|
||||
# CONFIG_PCIEASPM_DEBUG is not set
|
||||
CONFIG_PCIEASPM_DEFAULT=y
|
||||
# CONFIG_PCIEASPM_POWERSAVE is not set
|
||||
# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set
|
||||
@@ -999,17 +1030,15 @@ CONFIG_PCI_LOCKLESS_CONFIG=y
|
||||
# CONFIG_PCI_PASID is not set
|
||||
# CONFIG_PCI_P2PDMA is not set
|
||||
CONFIG_PCI_LABEL=y
|
||||
# CONFIG_HOTPLUG_PCI is not set
|
||||
CONFIG_HOTPLUG_PCI=y
|
||||
CONFIG_HOTPLUG_PCI_ACPI=y
|
||||
# CONFIG_HOTPLUG_PCI_ACPI_IBM is not set
|
||||
# CONFIG_HOTPLUG_PCI_CPCI is not set
|
||||
# CONFIG_HOTPLUG_PCI_SHPC is not set
|
||||
|
||||
#
|
||||
# PCI controller drivers
|
||||
#
|
||||
|
||||
#
|
||||
# Cadence PCIe controllers support
|
||||
#
|
||||
# end of Cadence PCIe controllers support
|
||||
|
||||
# CONFIG_VMD is not set
|
||||
|
||||
#
|
||||
@@ -1018,6 +1047,11 @@ CONFIG_PCI_LABEL=y
|
||||
# CONFIG_PCIE_DW_PLAT_HOST is not set
|
||||
# CONFIG_PCI_MESON is not set
|
||||
# end of DesignWare PCI Core Support
|
||||
|
||||
#
|
||||
# Cadence PCIe controllers support
|
||||
#
|
||||
# end of Cadence PCIe controllers support
|
||||
# end of PCI controller drivers
|
||||
|
||||
#
|
||||
@@ -1054,6 +1088,7 @@ CONFIG_EXTRA_FIRMWARE=""
|
||||
CONFIG_FW_LOADER_USER_HELPER=y
|
||||
# CONFIG_FW_LOADER_USER_HELPER_FALLBACK is not set
|
||||
# CONFIG_FW_LOADER_COMPRESS is not set
|
||||
# CONFIG_FW_CACHE is not set
|
||||
# end of Firmware loader
|
||||
|
||||
CONFIG_ALLOW_DEV_COREDUMP=y
|
||||
@@ -1104,7 +1139,6 @@ CONFIG_BLK_DEV_RAM_SIZE=16384
|
||||
# CONFIG_CDROM_PKTCDVD is not set
|
||||
# CONFIG_ATA_OVER_ETH is not set
|
||||
CONFIG_VIRTIO_BLK=y
|
||||
# CONFIG_VIRTIO_BLK_SCSI is not set
|
||||
# CONFIG_BLK_DEV_RBD is not set
|
||||
# CONFIG_BLK_DEV_RSXX is not set
|
||||
|
||||
@@ -1122,7 +1156,6 @@ CONFIG_VIRTIO_BLK=y
|
||||
# CONFIG_DUMMY_IRQ is not set
|
||||
# CONFIG_IBM_ASM is not set
|
||||
# CONFIG_PHANTOM is not set
|
||||
# CONFIG_SGI_IOC4 is not set
|
||||
# CONFIG_TIFM_CORE is not set
|
||||
# CONFIG_ENCLOSURE_SERVICES is not set
|
||||
# CONFIG_HP_ILO is not set
|
||||
@@ -1156,41 +1189,9 @@ CONFIG_VIRTIO_BLK=y
|
||||
#
|
||||
# Intel MIC & related support
|
||||
#
|
||||
|
||||
#
|
||||
# Intel MIC Bus Driver
|
||||
#
|
||||
# CONFIG_INTEL_MIC_BUS is not set
|
||||
|
||||
#
|
||||
# SCIF Bus Driver
|
||||
#
|
||||
# CONFIG_SCIF_BUS is not set
|
||||
|
||||
#
|
||||
# VOP Bus Driver
|
||||
#
|
||||
# CONFIG_VOP_BUS is not set
|
||||
|
||||
#
|
||||
# Intel MIC Host Driver
|
||||
#
|
||||
|
||||
#
|
||||
# Intel MIC Card Driver
|
||||
#
|
||||
|
||||
#
|
||||
# SCIF Driver
|
||||
#
|
||||
|
||||
#
|
||||
# Intel MIC Coprocessor State Management (COSM) Drivers
|
||||
#
|
||||
|
||||
#
|
||||
# VOP Driver
|
||||
#
|
||||
# end of Intel MIC & related support
|
||||
|
||||
# CONFIG_GENWQE is not set
|
||||
@@ -1228,6 +1229,7 @@ CONFIG_NETDEVICES=y
|
||||
CONFIG_NET_CORE=y
|
||||
# CONFIG_BONDING is not set
|
||||
# CONFIG_DUMMY is not set
|
||||
# CONFIG_WIREGUARD is not set
|
||||
# CONFIG_EQUALIZER is not set
|
||||
# CONFIG_NET_TEAM is not set
|
||||
# CONFIG_MACVLAN is not set
|
||||
@@ -1244,10 +1246,6 @@ CONFIG_VIRTIO_NET=y
|
||||
# CONFIG_NLMON is not set
|
||||
# CONFIG_ARCNET is not set
|
||||
|
||||
#
|
||||
# CAIF transport drivers
|
||||
#
|
||||
|
||||
#
|
||||
# Distributed Switch Architecture drivers
|
||||
#
|
||||
@@ -1362,6 +1360,7 @@ CONFIG_SERIAL_EARLYCON=y
|
||||
CONFIG_SERIAL_8250=y
|
||||
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
|
||||
CONFIG_SERIAL_8250_PNP=y
|
||||
# CONFIG_SERIAL_8250_16550A_VARIANTS is not set
|
||||
# CONFIG_SERIAL_8250_FINTEK is not set
|
||||
CONFIG_SERIAL_8250_CONSOLE=y
|
||||
CONFIG_SERIAL_8250_DMA=y
|
||||
@@ -1370,11 +1369,11 @@ CONFIG_SERIAL_8250_EXAR=y
|
||||
CONFIG_SERIAL_8250_NR_UARTS=1
|
||||
CONFIG_SERIAL_8250_RUNTIME_UARTS=1
|
||||
# CONFIG_SERIAL_8250_EXTENDED is not set
|
||||
CONFIG_SERIAL_8250_DWLIB=y
|
||||
# CONFIG_SERIAL_8250_DW is not set
|
||||
# CONFIG_SERIAL_8250_RT288X is not set
|
||||
CONFIG_SERIAL_8250_LPSS=y
|
||||
CONFIG_SERIAL_8250_MID=y
|
||||
# CONFIG_SERIAL_8250_MOXA is not set
|
||||
|
||||
#
|
||||
# Non-8250 serial port support
|
||||
@@ -1391,10 +1390,12 @@ CONFIG_SERIAL_ARC=y
|
||||
CONFIG_SERIAL_ARC_NR_PORTS=1
|
||||
# CONFIG_SERIAL_RP2 is not set
|
||||
# CONFIG_SERIAL_FSL_LPUART is not set
|
||||
# CONFIG_SERIAL_FSL_LINFLEXUART is not set
|
||||
# end of Serial drivers
|
||||
|
||||
CONFIG_SERIAL_DEV_BUS=y
|
||||
CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
|
||||
# CONFIG_TTY_PRINTK is not set
|
||||
CONFIG_HVC_DRIVER=y
|
||||
CONFIG_VIRTIO_CONSOLE=y
|
||||
# CONFIG_IPMI_HANDLER is not set
|
||||
@@ -1418,6 +1419,7 @@ CONFIG_DEVPORT=y
|
||||
# end of Character devices
|
||||
|
||||
# CONFIG_RANDOM_TRUST_CPU is not set
|
||||
# CONFIG_RANDOM_TRUST_BOOTLOADER is not set
|
||||
|
||||
#
|
||||
# I2C support
|
||||
@@ -1468,12 +1470,10 @@ CONFIG_THERMAL_WRITABLE_TRIPS=y
|
||||
CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y
|
||||
# CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set
|
||||
# CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE is not set
|
||||
# CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR is not set
|
||||
CONFIG_THERMAL_GOV_FAIR_SHARE=y
|
||||
CONFIG_THERMAL_GOV_STEP_WISE=y
|
||||
# CONFIG_THERMAL_GOV_BANG_BANG is not set
|
||||
CONFIG_THERMAL_GOV_USER_SPACE=y
|
||||
# CONFIG_THERMAL_GOV_POWER_ALLOCATOR is not set
|
||||
# CONFIG_THERMAL_EMULATION is not set
|
||||
|
||||
#
|
||||
@@ -1500,7 +1500,6 @@ CONFIG_BCMA_POSSIBLE=y
|
||||
#
|
||||
# Multifunction device drivers
|
||||
#
|
||||
# CONFIG_MFD_CROS_EC is not set
|
||||
# CONFIG_MFD_MADERA is not set
|
||||
# CONFIG_HTC_PASIC3 is not set
|
||||
# CONFIG_MFD_INTEL_QUARK_I2C_GPIO is not set
|
||||
@@ -1533,7 +1532,6 @@ CONFIG_VGA_ARB=y
|
||||
CONFIG_VGA_ARB_MAX_GPUS=16
|
||||
# CONFIG_VGA_SWITCHEROO is not set
|
||||
# CONFIG_DRM is not set
|
||||
# CONFIG_DRM_DP_CEC is not set
|
||||
|
||||
#
|
||||
# ARM devices
|
||||
@@ -1609,7 +1607,7 @@ CONFIG_LCD_CLASS_DEVICE=y
|
||||
CONFIG_BACKLIGHT_CLASS_DEVICE=y
|
||||
CONFIG_BACKLIGHT_GENERIC=y
|
||||
# CONFIG_BACKLIGHT_APPLE is not set
|
||||
# CONFIG_BACKLIGHT_PM8941_WLED is not set
|
||||
# CONFIG_BACKLIGHT_QCOM_WLED is not set
|
||||
# CONFIG_BACKLIGHT_SAHARA is not set
|
||||
# end of Backlight & LCD device support
|
||||
|
||||
@@ -1673,7 +1671,6 @@ CONFIG_HID_GENERIC=y
|
||||
# CONFIG_HID_KENSINGTON is not set
|
||||
# CONFIG_HID_LCPOWER is not set
|
||||
# CONFIG_HID_LENOVO is not set
|
||||
# CONFIG_HID_LOGITECH is not set
|
||||
# CONFIG_HID_MAGICMOUSE is not set
|
||||
# CONFIG_HID_MALTRON is not set
|
||||
# CONFIG_HID_MAYFLASH is not set
|
||||
@@ -1717,7 +1714,6 @@ CONFIG_HID_REDRAGON=y
|
||||
|
||||
CONFIG_USB_OHCI_LITTLE_ENDIAN=y
|
||||
# CONFIG_USB_SUPPORT is not set
|
||||
# CONFIG_UWB is not set
|
||||
# CONFIG_MMC is not set
|
||||
# CONFIG_MEMSTICK is not set
|
||||
# CONFIG_NEW_LEDS is not set
|
||||
@@ -1740,7 +1736,9 @@ CONFIG_DMA_VIRTUAL_CHANNELS=y
|
||||
CONFIG_DMA_ACPI=y
|
||||
# CONFIG_ALTERA_MSGDMA is not set
|
||||
# CONFIG_INTEL_IDMA64 is not set
|
||||
# CONFIG_INTEL_IDXD is not set
|
||||
# CONFIG_INTEL_IOATDMA is not set
|
||||
# CONFIG_PLX_DMA is not set
|
||||
# CONFIG_QCOM_HIDMA_MGMT is not set
|
||||
# CONFIG_QCOM_HIDMA is not set
|
||||
CONFIG_DW_DMAC_CORE=y
|
||||
@@ -1749,6 +1747,7 @@ CONFIG_DW_DMAC_CORE=y
|
||||
# CONFIG_DW_EDMA is not set
|
||||
# CONFIG_DW_EDMA_PCIE is not set
|
||||
CONFIG_HSU_DMA=y
|
||||
# CONFIG_SF_PDMA is not set
|
||||
|
||||
#
|
||||
# DMA Clients
|
||||
@@ -1760,6 +1759,7 @@ CONFIG_HSU_DMA=y
|
||||
# DMABUF options
|
||||
#
|
||||
# CONFIG_SYNC_FILE is not set
|
||||
# CONFIG_DMABUF_HEAPS is not set
|
||||
# end of DMABUF options
|
||||
|
||||
# CONFIG_AUXDISPLAY is not set
|
||||
@@ -1791,6 +1791,7 @@ CONFIG_VIRTIO_PCI=y
|
||||
CONFIG_VIRTIO_PCI_LEGACY=y
|
||||
CONFIG_VIRTIO_PMEM=y
|
||||
CONFIG_VIRTIO_BALLOON=y
|
||||
CONFIG_VIRTIO_MEM=y
|
||||
CONFIG_VIRTIO_INPUT=y
|
||||
CONFIG_VIRTIO_MMIO=y
|
||||
CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
|
||||
@@ -1801,9 +1802,11 @@ CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
|
||||
# CONFIG_HYPERV is not set
|
||||
# end of Microsoft Hyper-V guest support
|
||||
|
||||
# CONFIG_GREYBUS is not set
|
||||
# CONFIG_STAGING is not set
|
||||
# CONFIG_X86_PLATFORM_DEVICES is not set
|
||||
CONFIG_PMC_ATOM=y
|
||||
# CONFIG_MFD_CROS_EC is not set
|
||||
# CONFIG_CHROME_PLATFORMS is not set
|
||||
# CONFIG_MELLANOX_PLATFORM is not set
|
||||
CONFIG_CLKDEV_LOOKUP=y
|
||||
@@ -1844,6 +1847,7 @@ CONFIG_IOMMU_DMA=y
|
||||
# CONFIG_INTEL_IOMMU is not set
|
||||
# CONFIG_IRQ_REMAP is not set
|
||||
CONFIG_VIRTIO_IOMMU=y
|
||||
CONFIG_VIRTIO_IOMMU_TOPOLOGY=y
|
||||
|
||||
#
|
||||
# Remoteproc drivers
|
||||
@@ -1889,13 +1893,6 @@ CONFIG_VIRTIO_IOMMU=y
|
||||
#
|
||||
# end of i.MX SoC drivers
|
||||
|
||||
#
|
||||
# IXP4xx SoC drivers
|
||||
#
|
||||
# CONFIG_IXP4XX_QMGR is not set
|
||||
# CONFIG_IXP4XX_NPE is not set
|
||||
# end of IXP4xx SoC drivers
|
||||
|
||||
#
|
||||
# Qualcomm SoC drivers
|
||||
#
|
||||
@@ -1933,6 +1930,7 @@ CONFIG_VIRTIO_IOMMU=y
|
||||
# CONFIG_BCM_KONA_USB2_PHY is not set
|
||||
# CONFIG_PHY_PXA_28NM_HSIC is not set
|
||||
# CONFIG_PHY_PXA_28NM_USB2 is not set
|
||||
# CONFIG_PHY_INTEL_EMMC is not set
|
||||
# end of PHY Subsystem
|
||||
|
||||
# CONFIG_POWERCAP is not set
|
||||
@@ -1944,7 +1942,7 @@ CONFIG_VIRTIO_IOMMU=y
|
||||
# end of Performance monitor support
|
||||
|
||||
CONFIG_RAS=y
|
||||
# CONFIG_THUNDERBOLT is not set
|
||||
# CONFIG_USB4 is not set
|
||||
|
||||
#
|
||||
# Android
|
||||
@@ -1974,6 +1972,7 @@ CONFIG_DAX=y
|
||||
# end of HW tracing support
|
||||
|
||||
# CONFIG_FPGA is not set
|
||||
# CONFIG_TEE is not set
|
||||
# CONFIG_UNISYS_VISORBUS is not set
|
||||
# CONFIG_SIOX is not set
|
||||
# CONFIG_SLIMBUS is not set
|
||||
@@ -2013,6 +2012,8 @@ CONFIG_EXPORTFS=y
|
||||
CONFIG_FILE_LOCKING=y
|
||||
CONFIG_MANDATORY_FILE_LOCKING=y
|
||||
CONFIG_FS_ENCRYPTION=y
|
||||
CONFIG_FS_ENCRYPTION_ALGS=y
|
||||
# CONFIG_FS_VERITY is not set
|
||||
CONFIG_FSNOTIFY=y
|
||||
CONFIG_DNOTIFY=y
|
||||
CONFIG_INOTIFY_USER=y
|
||||
@@ -2139,6 +2140,7 @@ CONFIG_NLS_MAC_TURKISH=y
|
||||
CONFIG_NLS_UTF8=y
|
||||
# CONFIG_DLM is not set
|
||||
# CONFIG_UNICODE is not set
|
||||
CONFIG_IO_WQ=y
|
||||
# end of File systems
|
||||
|
||||
#
|
||||
@@ -2185,8 +2187,8 @@ CONFIG_CRYPTO_ALGAPI=y
|
||||
CONFIG_CRYPTO_ALGAPI2=y
|
||||
CONFIG_CRYPTO_AEAD=y
|
||||
CONFIG_CRYPTO_AEAD2=y
|
||||
CONFIG_CRYPTO_BLKCIPHER=y
|
||||
CONFIG_CRYPTO_BLKCIPHER2=y
|
||||
CONFIG_CRYPTO_SKCIPHER=y
|
||||
CONFIG_CRYPTO_SKCIPHER2=y
|
||||
CONFIG_CRYPTO_HASH=y
|
||||
CONFIG_CRYPTO_HASH2=y
|
||||
CONFIG_CRYPTO_RNG=y
|
||||
@@ -2216,6 +2218,8 @@ CONFIG_CRYPTO_GLUE_HELPER_X86=y
|
||||
# CONFIG_CRYPTO_DH is not set
|
||||
# CONFIG_CRYPTO_ECDH is not set
|
||||
# CONFIG_CRYPTO_ECRDSA is not set
|
||||
# CONFIG_CRYPTO_CURVE25519 is not set
|
||||
# CONFIG_CRYPTO_CURVE25519_X86 is not set
|
||||
|
||||
#
|
||||
# Authenticated Encryption with Associated Data
|
||||
@@ -2224,16 +2228,7 @@ CONFIG_CRYPTO_GLUE_HELPER_X86=y
|
||||
CONFIG_CRYPTO_GCM=y
|
||||
# CONFIG_CRYPTO_CHACHA20POLY1305 is not set
|
||||
# CONFIG_CRYPTO_AEGIS128 is not set
|
||||
# CONFIG_CRYPTO_AEGIS128L is not set
|
||||
# CONFIG_CRYPTO_AEGIS256 is not set
|
||||
# CONFIG_CRYPTO_AEGIS128_AESNI_SSE2 is not set
|
||||
# CONFIG_CRYPTO_AEGIS128L_AESNI_SSE2 is not set
|
||||
# CONFIG_CRYPTO_AEGIS256_AESNI_SSE2 is not set
|
||||
# CONFIG_CRYPTO_MORUS640 is not set
|
||||
# CONFIG_CRYPTO_MORUS640_SSE2 is not set
|
||||
# CONFIG_CRYPTO_MORUS1280 is not set
|
||||
# CONFIG_CRYPTO_MORUS1280_SSE2 is not set
|
||||
# CONFIG_CRYPTO_MORUS1280_AVX2 is not set
|
||||
CONFIG_CRYPTO_SEQIV=y
|
||||
CONFIG_CRYPTO_ECHAINIV=y
|
||||
|
||||
@@ -2253,6 +2248,7 @@ CONFIG_CRYPTO_XTS=y
|
||||
# CONFIG_CRYPTO_NHPOLY1305_SSE2 is not set
|
||||
# CONFIG_CRYPTO_NHPOLY1305_AVX2 is not set
|
||||
# CONFIG_CRYPTO_ADIANTUM is not set
|
||||
# CONFIG_CRYPTO_ESSIV is not set
|
||||
|
||||
#
|
||||
# Hash modes
|
||||
@@ -2270,6 +2266,9 @@ CONFIG_CRYPTO_CRC32C_INTEL=y
|
||||
# CONFIG_CRYPTO_CRC32 is not set
|
||||
# CONFIG_CRYPTO_CRC32_PCLMUL is not set
|
||||
# CONFIG_CRYPTO_XXHASH is not set
|
||||
# CONFIG_CRYPTO_BLAKE2B is not set
|
||||
# CONFIG_CRYPTO_BLAKE2S is not set
|
||||
# CONFIG_CRYPTO_BLAKE2S_X86 is not set
|
||||
CONFIG_CRYPTO_CRCT10DIF=y
|
||||
# CONFIG_CRYPTO_CRCT10DIF_PCLMUL is not set
|
||||
CONFIG_CRYPTO_GHASH=y
|
||||
@@ -2300,10 +2299,8 @@ CONFIG_CRYPTO_SHA512=y
|
||||
#
|
||||
CONFIG_CRYPTO_AES=y
|
||||
# CONFIG_CRYPTO_AES_TI is not set
|
||||
CONFIG_CRYPTO_AES_X86_64=y
|
||||
CONFIG_CRYPTO_AES_NI_INTEL=y
|
||||
# CONFIG_CRYPTO_ANUBIS is not set
|
||||
CONFIG_CRYPTO_LIB_ARC4=y
|
||||
CONFIG_CRYPTO_ARC4=y
|
||||
# CONFIG_CRYPTO_BLOWFISH is not set
|
||||
# CONFIG_CRYPTO_BLOWFISH_X86_64 is not set
|
||||
@@ -2360,6 +2357,21 @@ CONFIG_CRYPTO_USER_API=y
|
||||
# CONFIG_CRYPTO_USER_API_SKCIPHER is not set
|
||||
CONFIG_CRYPTO_USER_API_RNG=y
|
||||
# CONFIG_CRYPTO_USER_API_AEAD is not set
|
||||
|
||||
#
|
||||
# Crypto library routines
|
||||
#
|
||||
CONFIG_CRYPTO_LIB_AES=y
|
||||
CONFIG_CRYPTO_LIB_ARC4=y
|
||||
# CONFIG_CRYPTO_LIB_BLAKE2S is not set
|
||||
# CONFIG_CRYPTO_LIB_CHACHA is not set
|
||||
# CONFIG_CRYPTO_LIB_CURVE25519 is not set
|
||||
CONFIG_CRYPTO_LIB_DES=y
|
||||
CONFIG_CRYPTO_LIB_POLY1305_RSIZE=11
|
||||
CONFIG_CRYPTO_LIB_POLY1305_GENERIC=y
|
||||
# CONFIG_CRYPTO_LIB_POLY1305 is not set
|
||||
# CONFIG_CRYPTO_LIB_CHACHA20POLY1305 is not set
|
||||
CONFIG_CRYPTO_LIB_SHA256=y
|
||||
# CONFIG_CRYPTO_HW is not set
|
||||
# CONFIG_ASYMMETRIC_KEY_TYPE is not set
|
||||
|
||||
@@ -2424,15 +2436,16 @@ CONFIG_CPU_RMAP=y
|
||||
CONFIG_DQL=y
|
||||
CONFIG_NLATTR=y
|
||||
CONFIG_IRQ_POLL=y
|
||||
# CONFIG_DIMLIB is not set
|
||||
CONFIG_UCS2_STRING=y
|
||||
CONFIG_HAVE_GENERIC_VDSO=y
|
||||
CONFIG_GENERIC_GETTIMEOFDAY=y
|
||||
CONFIG_GENERIC_VDSO_TIME_NS=y
|
||||
CONFIG_FONT_SUPPORT=y
|
||||
# CONFIG_FONTS is not set
|
||||
CONFIG_FONT_8x8=y
|
||||
CONFIG_FONT_8x16=y
|
||||
CONFIG_ARCH_HAS_PMEM_API=y
|
||||
CONFIG_MEMREGION=y
|
||||
CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE=y
|
||||
CONFIG_ARCH_STACKWALK=y
|
||||
CONFIG_SBITMAP=y
|
||||
@@ -2453,6 +2466,8 @@ CONFIG_CONSOLE_LOGLEVEL_QUIET=4
|
||||
CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4
|
||||
# CONFIG_BOOT_PRINTK_DELAY is not set
|
||||
CONFIG_DYNAMIC_DEBUG=y
|
||||
CONFIG_SYMBOLIC_ERRNAME=y
|
||||
# CONFIG_DEBUG_BUGVERBOSE is not set
|
||||
# end of printk and dmesg options
|
||||
|
||||
#
|
||||
@@ -2463,8 +2478,6 @@ CONFIG_DYNAMIC_DEBUG=y
|
||||
CONFIG_FRAME_WARN=2048
|
||||
CONFIG_STRIP_ASM_SYMS=y
|
||||
# CONFIG_READABLE_ASM is not set
|
||||
CONFIG_UNUSED_SYMBOLS=y
|
||||
CONFIG_DEBUG_FS=y
|
||||
# CONFIG_HEADERS_INSTALL is not set
|
||||
CONFIG_OPTIMIZE_INLINING=y
|
||||
CONFIG_DEBUG_SECTION_MISMATCH=y
|
||||
@@ -2474,9 +2487,20 @@ CONFIG_STACK_VALIDATION=y
|
||||
# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set
|
||||
# end of Compile-time checks and compiler options
|
||||
|
||||
#
|
||||
# Generic Kernel Debugging Instruments
|
||||
#
|
||||
CONFIG_MAGIC_SYSRQ=y
|
||||
CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1
|
||||
CONFIG_MAGIC_SYSRQ_SERIAL=y
|
||||
CONFIG_DEBUG_FS=y
|
||||
CONFIG_HAVE_ARCH_KGDB=y
|
||||
# CONFIG_KGDB is not set
|
||||
CONFIG_ARCH_HAS_UBSAN_SANITIZE_ALL=y
|
||||
# CONFIG_UBSAN is not set
|
||||
CONFIG_UBSAN_ALIGNMENT=y
|
||||
# end of Generic Kernel Debugging Instruments
|
||||
|
||||
CONFIG_DEBUG_KERNEL=y
|
||||
CONFIG_DEBUG_MISC=y
|
||||
|
||||
@@ -2488,45 +2512,50 @@ CONFIG_DEBUG_MISC=y
|
||||
# CONFIG_PAGE_OWNER is not set
|
||||
# CONFIG_PAGE_POISONING is not set
|
||||
# CONFIG_DEBUG_RODATA_TEST is not set
|
||||
CONFIG_GENERIC_PTDUMP=y
|
||||
# CONFIG_PTDUMP_DEBUGFS is not set
|
||||
# CONFIG_DEBUG_OBJECTS is not set
|
||||
# CONFIG_SLUB_DEBUG_ON is not set
|
||||
# CONFIG_SLUB_STATS is not set
|
||||
CONFIG_HAVE_DEBUG_KMEMLEAK=y
|
||||
# CONFIG_DEBUG_KMEMLEAK is not set
|
||||
# CONFIG_DEBUG_STACK_USAGE is not set
|
||||
# CONFIG_SCHED_STACK_END_CHECK is not set
|
||||
# CONFIG_DEBUG_VM is not set
|
||||
CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y
|
||||
# CONFIG_DEBUG_VIRTUAL is not set
|
||||
CONFIG_DEBUG_MEMORY_INIT=y
|
||||
# CONFIG_DEBUG_PER_CPU_MAPS is not set
|
||||
CONFIG_HAVE_ARCH_KASAN=y
|
||||
CONFIG_HAVE_ARCH_KASAN_VMALLOC=y
|
||||
CONFIG_CC_HAS_KASAN_GENERIC=y
|
||||
# CONFIG_KASAN is not set
|
||||
CONFIG_KASAN_STACK=1
|
||||
# end of Memory Debugging
|
||||
|
||||
CONFIG_ARCH_HAS_KCOV=y
|
||||
CONFIG_CC_HAS_SANCOV_TRACE_PC=y
|
||||
# CONFIG_KCOV is not set
|
||||
# CONFIG_DEBUG_SHIRQ is not set
|
||||
|
||||
#
|
||||
# Debug Lockups and Hangs
|
||||
# Debug Oops, Lockups and Hangs
|
||||
#
|
||||
# CONFIG_PANIC_ON_OOPS is not set
|
||||
CONFIG_PANIC_ON_OOPS_VALUE=0
|
||||
CONFIG_PANIC_TIMEOUT=0
|
||||
# CONFIG_SOFTLOCKUP_DETECTOR is not set
|
||||
CONFIG_HARDLOCKUP_CHECK_TIMESTAMP=y
|
||||
# CONFIG_HARDLOCKUP_DETECTOR is not set
|
||||
# CONFIG_DETECT_HUNG_TASK is not set
|
||||
# CONFIG_WQ_WATCHDOG is not set
|
||||
# end of Debug Lockups and Hangs
|
||||
# end of Debug Oops, Lockups and Hangs
|
||||
|
||||
# CONFIG_PANIC_ON_OOPS is not set
|
||||
CONFIG_PANIC_ON_OOPS_VALUE=0
|
||||
CONFIG_PANIC_TIMEOUT=0
|
||||
#
|
||||
# Scheduler Debugging
|
||||
#
|
||||
# CONFIG_SCHED_DEBUG is not set
|
||||
CONFIG_SCHED_INFO=y
|
||||
# CONFIG_SCHEDSTATS is not set
|
||||
# CONFIG_SCHED_STACK_END_CHECK is not set
|
||||
# end of Scheduler Debugging
|
||||
|
||||
# CONFIG_DEBUG_TIMEKEEPING is not set
|
||||
|
||||
#
|
||||
@@ -2550,11 +2579,17 @@ CONFIG_LOCK_DEBUGGING_SUPPORT=y
|
||||
CONFIG_STACKTRACE=y
|
||||
# CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set
|
||||
# CONFIG_DEBUG_KOBJECT is not set
|
||||
CONFIG_DEBUG_BUGVERBOSE=y
|
||||
|
||||
#
|
||||
# Debug kernel data structures
|
||||
#
|
||||
CONFIG_DEBUG_LIST=y
|
||||
# CONFIG_DEBUG_PLIST is not set
|
||||
# CONFIG_DEBUG_SG is not set
|
||||
# CONFIG_DEBUG_NOTIFIERS is not set
|
||||
CONFIG_BUG_ON_DATA_CORRUPTION=y
|
||||
# end of Debug kernel data structures
|
||||
|
||||
# CONFIG_DEBUG_CREDENTIALS is not set
|
||||
|
||||
#
|
||||
@@ -2570,14 +2605,13 @@ CONFIG_RCU_CPU_STALL_TIMEOUT=59
|
||||
# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set
|
||||
# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set
|
||||
# CONFIG_CPU_HOTPLUG_STATE_CONTROL is not set
|
||||
# CONFIG_NOTIFIER_ERROR_INJECTION is not set
|
||||
# CONFIG_FAULT_INJECTION is not set
|
||||
# CONFIG_LATENCYTOP is not set
|
||||
CONFIG_USER_STACKTRACE_SUPPORT=y
|
||||
CONFIG_HAVE_FUNCTION_TRACER=y
|
||||
CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y
|
||||
CONFIG_HAVE_DYNAMIC_FTRACE=y
|
||||
CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y
|
||||
CONFIG_HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y
|
||||
CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
|
||||
CONFIG_HAVE_SYSCALL_TRACEPOINTS=y
|
||||
CONFIG_HAVE_FENTRY=y
|
||||
@@ -2585,6 +2619,49 @@ CONFIG_HAVE_C_RECORDMCOUNT=y
|
||||
CONFIG_TRACING_SUPPORT=y
|
||||
# CONFIG_FTRACE is not set
|
||||
# CONFIG_PROVIDE_OHCI1394_DMA_INIT is not set
|
||||
# CONFIG_SAMPLES is not set
|
||||
CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y
|
||||
CONFIG_STRICT_DEVMEM=y
|
||||
# CONFIG_IO_STRICT_DEVMEM is not set
|
||||
|
||||
#
|
||||
# x86 Debugging
|
||||
#
|
||||
CONFIG_TRACE_IRQFLAGS_SUPPORT=y
|
||||
CONFIG_X86_VERBOSE_BOOTUP=y
|
||||
CONFIG_EARLY_PRINTK=y
|
||||
# CONFIG_EARLY_PRINTK_DBGP is not set
|
||||
# CONFIG_EARLY_PRINTK_USB_XDBC is not set
|
||||
# CONFIG_EFI_PGT_DUMP is not set
|
||||
# CONFIG_DEBUG_WX is not set
|
||||
CONFIG_DOUBLEFAULT=y
|
||||
# CONFIG_DEBUG_TLBFLUSH is not set
|
||||
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
|
||||
# CONFIG_X86_DECODER_SELFTEST is not set
|
||||
CONFIG_IO_DELAY_0X80=y
|
||||
# CONFIG_IO_DELAY_0XED is not set
|
||||
# CONFIG_IO_DELAY_UDELAY is not set
|
||||
# CONFIG_IO_DELAY_NONE is not set
|
||||
# CONFIG_DEBUG_BOOT_PARAMS is not set
|
||||
# CONFIG_CPA_DEBUG is not set
|
||||
# CONFIG_DEBUG_ENTRY is not set
|
||||
# CONFIG_DEBUG_NMI_SELFTEST is not set
|
||||
# CONFIG_X86_DEBUG_FPU is not set
|
||||
# CONFIG_PUNIT_ATOM_DEBUG is not set
|
||||
# CONFIG_UNWINDER_ORC is not set
|
||||
CONFIG_UNWINDER_FRAME_POINTER=y
|
||||
# CONFIG_UNWINDER_GUESS is not set
|
||||
# end of x86 Debugging
|
||||
|
||||
#
|
||||
# Kernel Testing and Coverage
|
||||
#
|
||||
# CONFIG_KUNIT is not set
|
||||
# CONFIG_NOTIFIER_ERROR_INJECTION is not set
|
||||
# CONFIG_FAULT_INJECTION is not set
|
||||
CONFIG_ARCH_HAS_KCOV=y
|
||||
CONFIG_CC_HAS_SANCOV_TRACE_PC=y
|
||||
# CONFIG_KCOV is not set
|
||||
CONFIG_RUNTIME_TESTING_MENU=y
|
||||
# CONFIG_LKDTM is not set
|
||||
# CONFIG_TEST_LIST_SORT is not set
|
||||
@@ -2615,37 +2692,5 @@ CONFIG_RUNTIME_TESTING_MENU=y
|
||||
# CONFIG_TEST_STACKINIT is not set
|
||||
# CONFIG_TEST_MEMINIT is not set
|
||||
# CONFIG_MEMTEST is not set
|
||||
CONFIG_BUG_ON_DATA_CORRUPTION=y
|
||||
# CONFIG_SAMPLES is not set
|
||||
CONFIG_HAVE_ARCH_KGDB=y
|
||||
# CONFIG_KGDB is not set
|
||||
CONFIG_ARCH_HAS_UBSAN_SANITIZE_ALL=y
|
||||
# CONFIG_UBSAN is not set
|
||||
CONFIG_UBSAN_ALIGNMENT=y
|
||||
CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y
|
||||
CONFIG_STRICT_DEVMEM=y
|
||||
# CONFIG_IO_STRICT_DEVMEM is not set
|
||||
CONFIG_TRACE_IRQFLAGS_SUPPORT=y
|
||||
CONFIG_X86_VERBOSE_BOOTUP=y
|
||||
CONFIG_EARLY_PRINTK=y
|
||||
# CONFIG_EARLY_PRINTK_DBGP is not set
|
||||
# CONFIG_EARLY_PRINTK_USB_XDBC is not set
|
||||
# CONFIG_X86_PTDUMP is not set
|
||||
# CONFIG_EFI_PGT_DUMP is not set
|
||||
# CONFIG_DEBUG_WX is not set
|
||||
CONFIG_DOUBLEFAULT=y
|
||||
# CONFIG_DEBUG_TLBFLUSH is not set
|
||||
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
|
||||
CONFIG_IO_DELAY_0X80=y
|
||||
# CONFIG_IO_DELAY_0XED is not set
|
||||
# CONFIG_IO_DELAY_UDELAY is not set
|
||||
# CONFIG_IO_DELAY_NONE is not set
|
||||
# CONFIG_DEBUG_BOOT_PARAMS is not set
|
||||
# CONFIG_CPA_DEBUG is not set
|
||||
# CONFIG_DEBUG_ENTRY is not set
|
||||
# CONFIG_DEBUG_NMI_SELFTEST is not set
|
||||
# CONFIG_X86_DEBUG_FPU is not set
|
||||
# CONFIG_PUNIT_ATOM_DEBUG is not set
|
||||
# CONFIG_UNWINDER_ORC is not set
|
||||
CONFIG_UNWINDER_FRAME_POINTER=y
|
||||
# end of Kernel Testing and Coverage
|
||||
# end of Kernel hacking
|
||||
329
scripts/dev_cli.sh
Executable file
329
scripts/dev_cli.sh
Executable file
@@ -0,0 +1,329 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# Copyright © 2020 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
CLI_NAME="Cloud Hypervisor"
|
||||
|
||||
CTR_IMAGE_TAG="cloudhypervisor/dev"
|
||||
CTR_IMAGE_VERSION="v1"
|
||||
CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}"
|
||||
|
||||
DOCKER_RUNTIME="docker"
|
||||
|
||||
# Host paths
|
||||
CLH_SCRIPTS_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
CLH_ROOT_DIR=$(cd "${CLH_SCRIPTS_DIR}/.." && pwd)
|
||||
CLH_BUILD_DIR="${CLH_ROOT_DIR}/build"
|
||||
CLH_CARGO_TARGET="${CLH_BUILD_DIR}/cargo_target"
|
||||
CLH_DOCKERFILE="${CLH_SCRIPTS_DIR}/../resources/Dockerfile"
|
||||
CLH_CTR_BUILD_DIR="/tmp/cloud-hypervisor/ctr-build"
|
||||
CLH_INTEGRATION_WORKLOADS="${HOME}/workloads"
|
||||
|
||||
# Container paths
|
||||
CTR_CLH_ROOT_DIR="/cloud-hypervisor"
|
||||
CTR_CLH_CARGO_BUILT_DIR="${CTR_CLH_ROOT_DIR}/build"
|
||||
CTR_CLH_CARGO_TARGET="${CTR_CLH_CARGO_BUILT_DIR}/cargo_target"
|
||||
CTR_CLH_INTEGRATION_WORKLOADS="/root/workloads"
|
||||
|
||||
# Cargo paths
|
||||
# Full path to the cargo registry dir on the host. This appears on the host
|
||||
# because we want to persist the cargo registry across container invocations.
|
||||
# Otherwise, any rust crates from crates.io would be downloaded again each time
|
||||
# we build or test.
|
||||
CARGO_REGISTRY_DIR="${CLH_BUILD_DIR}/cargo_registry"
|
||||
|
||||
# Full path to the cargo git registry on the host. This serves the same purpose
|
||||
# as CARGO_REGISTRY_DIR, for crates downloaded from GitHub repos instead of
|
||||
# crates.io.
|
||||
CARGO_GIT_REGISTRY_DIR="${CLH_BUILD_DIR}/cargo_git_registry"
|
||||
|
||||
# Full path to the cargo target dir on the host.
|
||||
CARGO_TARGET_DIR="${CLH_BUILD_DIR}/cargo_target"
|
||||
|
||||
# Send a decorated message to stdout, followed by a new line
|
||||
#
|
||||
say() {
|
||||
[ -t 1 ] && [ -n "$TERM" ] \
|
||||
&& echo "$(tput setaf 2)[$CLI_NAME]$(tput sgr0) $*" \
|
||||
|| echo "[$CLI_NAME] $*"
|
||||
}
|
||||
|
||||
# Send a decorated message to stdout, without a trailing new line
|
||||
#
|
||||
say_noln() {
|
||||
[ -t 1 ] && [ -n "$TERM" ] \
|
||||
&& echo -n "$(tput setaf 2)[$CLI_NAME]$(tput sgr0) $*" \
|
||||
|| echo "[$CLI_NAME] $*"
|
||||
}
|
||||
|
||||
# Send a text message to stderr
|
||||
#
|
||||
say_err() {
|
||||
[ -t 2 ] && [ -n "$TERM" ] \
|
||||
&& echo "$(tput setaf 1)[$CLI_NAME] $*$(tput sgr0)" 1>&2 \
|
||||
|| echo "[$CLI_NAME] $*" 1>&2
|
||||
}
|
||||
|
||||
# Send a warning-highlighted text to stdout
|
||||
say_warn() {
|
||||
[ -t 1 ] && [ -n "$TERM" ] \
|
||||
&& echo "$(tput setaf 3)[$CLI_NAME] $*$(tput sgr0)" \
|
||||
|| echo "[$CLI_NAME] $*"
|
||||
}
|
||||
|
||||
# Exit with an error message and (optional) code
|
||||
# Usage: die [-c <error code>] <error message>
|
||||
#
|
||||
die() {
|
||||
code=1
|
||||
[[ "$1" = "-c" ]] && {
|
||||
code="$2"
|
||||
shift 2
|
||||
}
|
||||
say_err "$@"
|
||||
exit $code
|
||||
}
|
||||
|
||||
# Exit with an error message if the last exit code is not 0
|
||||
#
|
||||
ok_or_die() {
|
||||
code=$?
|
||||
[[ $code -eq 0 ]] || die -c $code "$@"
|
||||
}
|
||||
|
||||
# Make sure the build/ dirs are available. Exit if we can't create them.
|
||||
# Upon returning from this call, the caller can be certain the build/ dirs exist.
|
||||
#
|
||||
ensure_build_dir() {
|
||||
for dir in "$CLH_BUILD_DIR" \
|
||||
"$CLH_INTEGRATION_WORKLOADS" \
|
||||
"$CLH_CTR_BUILD_DIR" \
|
||||
"$CARGO_TARGET_DIR" \
|
||||
"$CARGO_REGISTRY_DIR" \
|
||||
"$CARGO_GIT_REGISTRY_DIR"; do
|
||||
mkdir -p "$dir" || die "Error: cannot create dir $dir"
|
||||
[ -x "$dir" ] && [ -w "$dir" ] || \
|
||||
{
|
||||
say "Wrong permissions for $dir. Attempting to fix them ..."
|
||||
chmod +x+w "$dir"
|
||||
} || \
|
||||
die "Error: wrong permissions for $dir. Should be +x+w"
|
||||
done
|
||||
}
|
||||
|
||||
# Fix main directory permissions after a container ran as root.
|
||||
# Since the container ran as root, any files it creates will be owned by root.
|
||||
# This fixes that by recursively changing the ownership of /cloud-hypervisor to the
|
||||
# current user.
|
||||
#
|
||||
fix_dir_perms() {
|
||||
# Yes, running Docker to get elevated privileges, just to chown some files
|
||||
# is a dirty hack.
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
"$CTR_IMAGE" \
|
||||
chown -R "$(id -u):$(id -g)" "$CTR_CLH_ROOT_DIR"
|
||||
|
||||
return $1
|
||||
}
|
||||
|
||||
cmd_help() {
|
||||
echo ""
|
||||
echo "Cloud Hypervisor $(basename $0)"
|
||||
echo "Usage: $(basename $0) <command> [<command args>]"
|
||||
echo ""
|
||||
echo "Available commands:"
|
||||
echo ""
|
||||
echo " build [--debug|--release] [-- [<cargo args>]]"
|
||||
echo " Build the Cloud Hypervisor binaries."
|
||||
echo " --debug Build the debug binaries. This is the default."
|
||||
echo " --release Build the release binaries."
|
||||
echo ""
|
||||
echo " tests [--unit|--cargo|--all] [-- [<cargo test args>]]"
|
||||
echo " Run the Cloud Hypervisor tests."
|
||||
echo " --unit Run the unit tests."
|
||||
echo " --cargo Run the cargo tests."
|
||||
echo " --integration Run the integration tests."
|
||||
echo " --all Run all tests."
|
||||
echo ""
|
||||
echo " build-container [--type]"
|
||||
echo " Build the Cloud Hypervisor container."
|
||||
echo " --dev Build dev container. This is the default."
|
||||
echo ""
|
||||
echo " clean [<cargo args>]]"
|
||||
echo " Remove the Cloud Hypervisor artifacts."
|
||||
echo ""
|
||||
echo " help"
|
||||
echo " Display this help message."
|
||||
echo ""
|
||||
}
|
||||
|
||||
cmd_build() {
|
||||
build="debug"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--debug") { build="debug"; } ;;
|
||||
"--release") { build="release"; } ;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown build argument: $1. Please use --help for help."
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
cargo_args=("$@")
|
||||
[ $build = "release" ] && cargo_args+=("--release")
|
||||
|
||||
$DOCKER_RUNTIME run \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
"$CTR_IMAGE" \
|
||||
cargo build \
|
||||
--target-dir "$CTR_CLH_CARGO_TARGET" \
|
||||
"${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$build"
|
||||
}
|
||||
|
||||
cmd_clean() {
|
||||
cargo_args=("$@")
|
||||
|
||||
$DOCKER_RUNTIME run \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
"$CTR_IMAGE" \
|
||||
cargo clean \
|
||||
--target-dir "$CTR_CLH_CARGO_TARGET" \
|
||||
"${cargo_args[@]}"
|
||||
}
|
||||
|
||||
cmd_tests() {
|
||||
unit=false
|
||||
cargo=false
|
||||
integration=false
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--unit") { unit=true; } ;;
|
||||
"--cargo") { cargo=true; } ;;
|
||||
"--integration") { integration=true; } ;;
|
||||
"--all") { cargo=true; unit=true; integration=true; } ;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown tests argument: $1. Please use --help for help."
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "$unit" = true ] ; then
|
||||
say "Running unit tests..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--device /dev/kvm \
|
||||
--device /dev/net/tun \
|
||||
--cap-add net_admin \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$cargo" = true ] ; then
|
||||
say "Running cargo tests..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_cargo_tests.sh || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$integration" = true ] ; then
|
||||
say "Running integration tests..."
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net=host \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
fix_dir_perms $?
|
||||
}
|
||||
|
||||
cmd_build-container() {
|
||||
container_type="dev"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--dev") { container_type="dev"; } ;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown build-container argument: $1. Please use --help for help."
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
BUILD_DIR=/tmp/cloud-hypervisor/container/
|
||||
|
||||
mkdir -p $BUILD_DIR
|
||||
cp $CLH_DOCKERFILE $BUILD_DIR
|
||||
|
||||
$DOCKER_RUNTIME build \
|
||||
--target $container_type \
|
||||
-t $CTR_IMAGE \
|
||||
-f $BUILD_DIR/Dockerfile \
|
||||
$BUILD_DIR
|
||||
}
|
||||
|
||||
# Parse main command line args.
|
||||
#
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help) { cmd_help; exit 1; } ;;
|
||||
-y|--unattended) { OPT_UNATTENDED=true; } ;;
|
||||
-*)
|
||||
die "Unknown arg: $1. Please use \`$0 help\` for help."
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# $1 is now a command name. Check if it is a valid command and, if so,
|
||||
# run it.
|
||||
#
|
||||
declare -f "cmd_$1" > /dev/null
|
||||
ok_or_die "Unknown command: $1. Please use \`$0 help\` for help."
|
||||
|
||||
cmd=cmd_$1
|
||||
shift
|
||||
|
||||
ensure_build_dir
|
||||
|
||||
$cmd "$@"
|
||||
28
scripts/run_cargo_tests.sh
Executable file
28
scripts/run_cargo_tests.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
set -x
|
||||
|
||||
source $HOME/.cargo/env
|
||||
|
||||
# Install cargo components
|
||||
time rustup component add clippy
|
||||
time rustup component add rustfmt
|
||||
time which cargo-audit || cargo install cargo-audit
|
||||
|
||||
# Run cargo builds and checks
|
||||
time cargo clippy --all-targets --all-features -- -D warnings
|
||||
time cargo rustc --bin cloud-hypervisor -- -D warnings
|
||||
time cargo rustc --bin vhost_user_net -- -D warnings
|
||||
time cargo test
|
||||
time cargo audit
|
||||
time cargo clippy --all-targets --no-default-features --features "pci,acpi" -- -D warnings
|
||||
time cargo rustc --bin cloud-hypervisor --no-default-features --features "pci,acpi" -- -D warnings
|
||||
time cargo rustc --bin vhost_user_net --no-default-features --features "pci,acpi" -- -D warnings
|
||||
time cargo clippy --all-targets --no-default-features --features "pci" -- -D warnings
|
||||
time cargo rustc --bin cloud-hypervisor --no-default-features --features "pci" -- -D warnings
|
||||
time cargo rustc --bin vhost_user_net --no-default-features --features "pci" -- -D warnings
|
||||
time cargo clippy --all-targets --no-default-features --features "mmio" -- -D warnings
|
||||
time cargo rustc --bin cloud-hypervisor --no-default-features --features "mmio" -- -D warnings
|
||||
time cargo rustc --bin vhost_user_net --no-default-features --features "mmio" -- -D warnings
|
||||
time cargo fmt -- --check
|
||||
time cargo build --release
|
||||
@@ -6,28 +6,30 @@ source $HOME/.cargo/env
|
||||
WORKLOADS_DIR="$HOME/workloads"
|
||||
mkdir -p "$WORKLOADS_DIR"
|
||||
|
||||
FW_URL=$(curl --silent https://api.github.com/repos/intel/rust-hypervisor-firmware/releases/latest | grep "browser_download_url" | grep -o 'https://.*[^ "]')
|
||||
cp scripts/sha1sums $WORKLOADS_DIR
|
||||
|
||||
FW_URL=$(curl --silent https://api.github.com/repos/cloud-hypervisor/rust-hypervisor-firmware/releases/latest | grep "browser_download_url" | grep -o 'https://.*[^ "]')
|
||||
FW="$WORKLOADS_DIR/hypervisor-fw"
|
||||
if [ ! -f "$FW" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $FW_URL
|
||||
time wget --quiet $FW_URL || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
CLEAR_OS_IMAGE_NAME="clear-cloudguest.img"
|
||||
CLEAR_OS_IMAGE_NAME="clear-31311-cloudguest.img"
|
||||
CLEAR_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$CLEAR_OS_IMAGE_NAME"
|
||||
CLEAR_OS_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_NAME"
|
||||
if [ ! -f "$CLEAR_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $CLEAR_OS_IMAGE_URL
|
||||
time wget --quiet $CLEAR_OS_IMAGE_URL || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
CLEAR_OS_RAW_IMAGE_NAME="clear-cloudguest-raw.img"
|
||||
CLEAR_OS_RAW_IMAGE_NAME="clear-31311-cloudguest-raw.img"
|
||||
CLEAR_OS_RAW_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$CLEAR_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
qemu-img convert -p -f qcow2 -O raw $CLEAR_OS_IMAGE_NAME $CLEAR_OS_RAW_IMAGE_NAME
|
||||
time qemu-img convert -p -f qcow2 -O raw $CLEAR_OS_IMAGE_NAME $CLEAR_OS_RAW_IMAGE_NAME || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -36,7 +38,7 @@ BIONIC_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images
|
||||
BIONIC_OS_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_IMAGE_NAME"
|
||||
if [ ! -f "$BIONIC_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $BIONIC_OS_IMAGE_URL
|
||||
time wget --quiet $BIONIC_OS_IMAGE_URL || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -44,7 +46,7 @@ BIONIC_OS_RAW_IMAGE_NAME="bionic-server-cloudimg-amd64-raw.img"
|
||||
BIONIC_OS_RAW_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
qemu-img convert -p -f qcow2 -O raw $BIONIC_OS_IMAGE_NAME $BIONIC_OS_RAW_IMAGE_NAME
|
||||
time qemu-img convert -p -f qcow2 -O raw $BIONIC_OS_IMAGE_NAME $BIONIC_OS_RAW_IMAGE_NAME || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -54,7 +56,7 @@ EOAN_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$
|
||||
EOAN_OS_IMAGE="$WORKLOADS_DIR/$EOAN_OS_IMAGE_NAME"
|
||||
if [ ! -f "$EOAN_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
wget --quiet $EOAN_OS_IMAGE_URL
|
||||
time wget --quiet $EOAN_OS_IMAGE_URL || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -62,45 +64,66 @@ EOAN_OS_RAW_IMAGE_NAME="eoan-server-cloudimg-amd64-raw.img"
|
||||
EOAN_OS_RAW_IMAGE="$WORKLOADS_DIR/$EOAN_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$EOAN_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
qemu-img convert -p -f qcow2 -O raw $EOAN_OS_IMAGE_NAME $EOAN_OS_RAW_IMAGE_NAME
|
||||
time qemu-img convert -p -f qcow2 -O raw $EOAN_OS_IMAGE_NAME $EOAN_OS_RAW_IMAGE_NAME || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
pushd $WORKLOADS_DIR
|
||||
sha1sum sha1sums --check
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "sha1sum validation of images failed, remove invalid images to fix the issue."
|
||||
exit 1
|
||||
fi
|
||||
popd
|
||||
|
||||
# Build custom kernel based on virtio-pmem and virtio-fs upstream patches
|
||||
VMLINUX_IMAGE="$WORKLOADS_DIR/vmlinux"
|
||||
VMLINUX_PVH_IMAGE="$WORKLOADS_DIR/vmlinux.pvh"
|
||||
BZIMAGE_IMAGE="$WORKLOADS_DIR/bzImage"
|
||||
|
||||
LINUX_CUSTOM_DIR="linux-custom"
|
||||
LINUX_CUSTOM_DIR="$WORKLOADS_DIR/linux-custom"
|
||||
|
||||
if [ ! -f "$VMLINUX_IMAGE" ]; then
|
||||
if [ ! -f "$VMLINUX_IMAGE" ] || [ ! -f "$VMLINUX_PVH_IMAGE" ]; then
|
||||
SRCDIR=$PWD
|
||||
pushd $WORKLOADS_DIR
|
||||
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-fs-virtio-iommu" $LINUX_CUSTOM_DIR
|
||||
pushd $LINUX_CUSTOM_DIR
|
||||
cp $SRCDIR/resources/linux-virtio-fs-virtio-iommu-config .config
|
||||
make bzImage -j `nproc`
|
||||
cp vmlinux $VMLINUX_IMAGE
|
||||
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE
|
||||
popd
|
||||
rm -rf $LINUX_CUSTOM_DIR
|
||||
time git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b "virtio-fs-virtio-iommu-virtio-mem-5.6-rc4" $LINUX_CUSTOM_DIR
|
||||
cp $SRCDIR/resources/linux-config $LINUX_CUSTOM_DIR/.config
|
||||
popd
|
||||
fi
|
||||
|
||||
if [ ! -f "$VMLINUX_IMAGE" ]; then
|
||||
pushd $LINUX_CUSTOM_DIR
|
||||
scripts/config --disable "CONFIG_PVH"
|
||||
time make bzImage -j `nproc`
|
||||
cp vmlinux $VMLINUX_IMAGE || exit 1
|
||||
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
if [ ! -f "$VMLINUX_PVH_IMAGE" ]; then
|
||||
pushd $LINUX_CUSTOM_DIR
|
||||
scripts/config --enable "CONFIG_PVH"
|
||||
time make bzImage -j `nproc`
|
||||
cp vmlinux $VMLINUX_PVH_IMAGE || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
if [ -d "$LINUX_CUSTOM_DIR" ]; then
|
||||
rm -rf $LINUX_CUSTOM_DIR
|
||||
fi
|
||||
|
||||
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
|
||||
VUBD="$WORKLOADS_DIR/vubd"
|
||||
QEMU_DIR="qemu_build"
|
||||
if [ ! -f "$VIRTIOFSD" ] || [ ! -f "$VUBD" ]; then
|
||||
if [ ! -f "$VIRTIOFSD" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $QEMU_DIR
|
||||
pushd $QEMU_DIR
|
||||
./configure --prefix=$PWD --target-list=x86_64-softmmu
|
||||
make virtiofsd vhost-user-blk -j `nproc`
|
||||
cp virtiofsd $VIRTIOFSD
|
||||
cp vhost-user-blk $VUBD
|
||||
time ./configure --prefix=$PWD --target-list=x86_64-softmmu
|
||||
time make virtiofsd -j `nproc`
|
||||
cp virtiofsd $VIRTIOFSD || exit 1
|
||||
popd
|
||||
rm -rf $QEMU_DIR
|
||||
sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd"
|
||||
sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd" || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -112,7 +135,7 @@ if [ ! -f "$BLK_IMAGE" ]; then
|
||||
mkfs.ext4 -j $BLK_IMAGE
|
||||
mkdir $MNT_DIR
|
||||
sudo mount -t ext4 $BLK_IMAGE $MNT_DIR
|
||||
sudo bash -c "echo bar > $MNT_DIR/foo"
|
||||
sudo bash -c "echo bar > $MNT_DIR/foo" || exit 1
|
||||
sudo umount $BLK_IMAGE
|
||||
rm -r $MNT_DIR
|
||||
popd
|
||||
@@ -122,16 +145,15 @@ SHARED_DIR="$WORKLOADS_DIR/shared_dir"
|
||||
if [ ! -d "$SHARED_DIR" ]; then
|
||||
mkdir -p $SHARED_DIR
|
||||
echo "foo" > "$SHARED_DIR/file1"
|
||||
echo "bar" > "$SHARED_DIR/file3"
|
||||
echo "bar" > "$SHARED_DIR/file3" || exit 1
|
||||
fi
|
||||
|
||||
VFIO_DIR="$WORKLOADS_DIR/vfio"
|
||||
if [ ! -d "$VFIO_DIR" ]; then
|
||||
mkdir -p $VFIO_DIR
|
||||
cp $CLEAR_OS_IMAGE $VFIO_DIR
|
||||
cp $FW $VFIO_DIR
|
||||
cp $VMLINUX_IMAGE $VFIO_DIR
|
||||
fi
|
||||
rm -rf $VFIO_DIR
|
||||
mkdir -p $VFIO_DIR
|
||||
cp $CLEAR_OS_IMAGE $VFIO_DIR
|
||||
cp $FW $VFIO_DIR
|
||||
cp $VMLINUX_IMAGE $VFIO_DIR || exit 1
|
||||
|
||||
# VFIO test network setup.
|
||||
# We reserve a different IP class for it: 172.17.0.0/24.
|
||||
@@ -147,28 +169,56 @@ sudo ip tuntap add vfio-tap1 mode tap
|
||||
sudo ip link set vfio-tap1 master vfio-br0
|
||||
sudo ip link set vfio-tap1 up
|
||||
|
||||
cargo build
|
||||
sudo setcap cap_net_admin+ep target/debug/cloud-hypervisor
|
||||
sudo setcap cap_net_admin+ep target/debug/vhost_user_net
|
||||
sudo ip tuntap add vfio-tap2 mode tap
|
||||
sudo ip link set vfio-tap2 master vfio-br0
|
||||
sudo ip link set vfio-tap2 up
|
||||
|
||||
sudo ip tuntap add vfio-tap3 mode tap
|
||||
sudo ip link set vfio-tap3 master vfio-br0
|
||||
sudo ip link set vfio-tap3 up
|
||||
|
||||
# Create tap interface without multipe queues support for vhost_user_net test.
|
||||
sudo ip tuntap add name vunet-tap0 mode tap
|
||||
# Create tap interface with multipe queues support for vhost_user_net test.
|
||||
sudo ip tuntap add name vunet-tap1 mode tap multi_queue
|
||||
|
||||
cargo build --release
|
||||
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor
|
||||
sudo setcap cap_net_admin+ep target/release/vhost_user_net
|
||||
|
||||
# We always copy a fresh version of our binary for our L2 guest.
|
||||
cp target/debug/cloud-hypervisor $VFIO_DIR
|
||||
cp target/release/cloud-hypervisor $VFIO_DIR
|
||||
cp target/release/ch-remote $VFIO_DIR
|
||||
|
||||
# Enable KSM with some reasonable parameters so that it won't take too long
|
||||
# for the memory to be merged between two processes.
|
||||
sudo bash -c "echo 1000000 > /sys/kernel/mm/ksm/pages_to_scan"
|
||||
sudo bash -c "echo 10 > /sys/kernel/mm/ksm/sleep_millisecs"
|
||||
sudo bash -c "echo 1 > /sys/kernel/mm/ksm/run"
|
||||
|
||||
# Ensure test binary has the same caps as the cloud-hypervisor one
|
||||
time cargo test --no-run --features "integration_tests" -- --nocapture || exit 1
|
||||
ls target/debug/deps/cloud_hypervisor-* | xargs -n 1 sudo setcap cap_net_admin+ep
|
||||
|
||||
# test_vfio relies on hugepages
|
||||
echo 4096 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
sudo chmod a+rwX /dev/hugepages
|
||||
|
||||
sudo adduser $USER kvm
|
||||
newgrp kvm << EOF
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test --features "integration_tests" -- --nocapture
|
||||
time cargo test --features "integration_tests" "$@" -- --nocapture
|
||||
EOF
|
||||
RES=$?
|
||||
|
||||
if [ $RES -eq 0 ]; then
|
||||
# virtio-mmio based testing
|
||||
cargo build --no-default-features --features "mmio"
|
||||
sudo setcap cap_net_admin+ep target/debug/cloud-hypervisor
|
||||
cargo build --release --no-default-features --features "mmio"
|
||||
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor
|
||||
|
||||
newgrp kvm << EOF
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test --features "integration_tests,mmio" -- --nocapture
|
||||
time cargo test --features "integration_tests,mmio" "$@" -- --nocapture
|
||||
EOF
|
||||
|
||||
RES=$?
|
||||
@@ -178,5 +228,11 @@ fi
|
||||
sudo ip link del vfio-br0
|
||||
sudo ip link del vfio-tap0
|
||||
sudo ip link del vfio-tap1
|
||||
sudo ip link del vfio-tap2
|
||||
sudo ip link del vfio-tap3
|
||||
|
||||
# Tear vhost_user_net test network down
|
||||
sudo ip link del vunet-tap0
|
||||
sudo ip link del vunet-tap1
|
||||
|
||||
exit $RES
|
||||
|
||||
5
scripts/run_openapi_tests.sh
Executable file
5
scripts/run_openapi_tests.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
set -x
|
||||
|
||||
sudo docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli validate -i /local/vmm/src/api/openapi/cloud-hypervisor.yaml
|
||||
@@ -2,23 +2,13 @@
|
||||
|
||||
source $HOME/.cargo/env
|
||||
|
||||
# More effective than just cargo test --all as it captures crates within crates
|
||||
for f in $(find . -name Cargo.toml -printf '%h\n' | sort -u); do
|
||||
pushd $f > /dev/null;
|
||||
cargo test --no-run || exit 1;
|
||||
popd > /dev/null;
|
||||
done
|
||||
|
||||
cargo test --workspace --no-run
|
||||
pushd target/debug
|
||||
ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep
|
||||
popd
|
||||
|
||||
sudo adduser $USER kvm
|
||||
newgrp kvm << EOF || exit 1
|
||||
for f in \$(find . -name Cargo.toml -printf '%h\n' | sort -u); do
|
||||
pushd \$f > /dev/null;
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test || exit 1;
|
||||
popd > /dev/null;
|
||||
done
|
||||
cargo test --workspace "$@" || exit 1;
|
||||
EOF
|
||||
|
||||
6
scripts/sha1sums
Normal file
6
scripts/sha1sums
Normal file
@@ -0,0 +1,6 @@
|
||||
cf7cfa783082fc4d6b4d1c0a53e4402648c14b82 clear-31311-cloudguest.img
|
||||
142a410546b592ff9536b46bb410faf8ac11edee clear-31311-cloudguest-raw.img
|
||||
27f3b17962ace69b51f0ddc2012095e3109e6ed8 bionic-server-cloudimg-amd64.img
|
||||
8db9cc58b01452ce2d06c313177e6e74d8582d93 bionic-server-cloudimg-amd64-raw.img
|
||||
4a452cdcf781f95d31a1668ecb92a937c176709a eoan-server-cloudimg-amd64.img
|
||||
91fdfb21df8920fd55915edf7669282cda2505f6 eoan-server-cloudimg-amd64-raw.img
|
||||
311
src/bin/ch-remote.rs
Normal file
311
src/bin/ch-remote.rs
Normal file
@@ -0,0 +1,311 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#[macro_use(crate_authors)]
|
||||
extern crate clap;
|
||||
extern crate serde_json;
|
||||
extern crate vmm;
|
||||
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::process;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
Socket(std::io::Error),
|
||||
StatusCodeParsing(std::num::ParseIntError),
|
||||
MissingProtocol,
|
||||
ContentLengthParsing(std::num::ParseIntError),
|
||||
ServerResponse(StatusCode),
|
||||
InvalidCPUCount(std::num::ParseIntError),
|
||||
InvalidMemorySize(std::num::ParseIntError),
|
||||
AddDeviceConfig(vmm::config::Error),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum StatusCode {
|
||||
Continue,
|
||||
OK,
|
||||
NoContent,
|
||||
BadRequest,
|
||||
NotFound,
|
||||
InternalServerError,
|
||||
NotImplemented,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl StatusCode {
|
||||
fn from_raw(code: usize) -> StatusCode {
|
||||
match code {
|
||||
100 => StatusCode::Continue,
|
||||
200 => StatusCode::OK,
|
||||
204 => StatusCode::NoContent,
|
||||
400 => StatusCode::BadRequest,
|
||||
404 => StatusCode::NotFound,
|
||||
500 => StatusCode::InternalServerError,
|
||||
501 => StatusCode::NotImplemented,
|
||||
_ => StatusCode::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(code: &str) -> Result<StatusCode, Error> {
|
||||
Ok(StatusCode::from_raw(
|
||||
code.trim().parse().map_err(Error::StatusCodeParsing)?,
|
||||
))
|
||||
}
|
||||
|
||||
fn check(self) -> Result<(), Error> {
|
||||
match self {
|
||||
StatusCode::OK | StatusCode::Continue | StatusCode::NoContent => Ok(()),
|
||||
_ => Err(Error::ServerResponse(self)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
|
||||
let header_str = format!("{}: ", header);
|
||||
if let Some(o) = res.find(&header_str) {
|
||||
Some(&res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_status_code(res: &str) -> Result<StatusCode, Error> {
|
||||
if let Some(o) = res.find("HTTP/1.1") {
|
||||
Ok(StatusCode::parse(
|
||||
&res[o + "HTTP/1.1 ".len()..res[o..].find('\r').unwrap()],
|
||||
)?)
|
||||
} else {
|
||||
Err(Error::MissingProtocol)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_http_response(socket: &mut UnixStream) -> Result<Option<String>, Error> {
|
||||
let mut res = String::new();
|
||||
let mut body_offset = None;
|
||||
let mut content_length: Option<usize> = None;
|
||||
loop {
|
||||
let mut bytes = vec![0; 256];
|
||||
let count = socket.read(&mut bytes).map_err(Error::Socket)?;
|
||||
res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
|
||||
|
||||
// End of headers
|
||||
if let Some(o) = res.find("\r\n\r\n") {
|
||||
body_offset = Some(o + "\r\n\r\n".len());
|
||||
|
||||
// With all headers available we can see if there is any body
|
||||
content_length = if let Some(length) = get_header(&res, "Content-Length") {
|
||||
Some(length.trim().parse().map_err(Error::ContentLengthParsing)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if content_length.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(body_offset) = body_offset {
|
||||
if let Some(content_length) = content_length {
|
||||
if res.len() >= content_length + body_offset {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get_status_code(&res)?.check()?;
|
||||
|
||||
Ok(content_length.and(Some(String::from(&res[body_offset.unwrap()..]))))
|
||||
}
|
||||
|
||||
fn simple_api_command(
|
||||
socket: &mut UnixStream,
|
||||
method: &str,
|
||||
c: &str,
|
||||
request_body: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
socket
|
||||
.write_all(
|
||||
format!(
|
||||
"{} /api/v1/vm.{} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n",
|
||||
method, c
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.map_err(Error::Socket)?;
|
||||
|
||||
if let Some(request_body) = request_body {
|
||||
socket
|
||||
.write_all(format!("Content-Length: {}\r\n", request_body.len()).as_bytes())
|
||||
.map_err(Error::Socket)?;
|
||||
}
|
||||
|
||||
socket.write_all(b"\r\n").map_err(Error::Socket)?;
|
||||
|
||||
if let Some(request_body) = request_body {
|
||||
socket
|
||||
.write_all(request_body.as_bytes())
|
||||
.map_err(Error::Socket)?;
|
||||
}
|
||||
|
||||
socket.flush().map_err(Error::Socket)?;
|
||||
|
||||
if let Some(body) = parse_http_response(socket)? {
|
||||
println!("{}", body);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resize_api_command(
|
||||
socket: &mut UnixStream,
|
||||
cpus: Option<&str>,
|
||||
memory: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
let desired_vcpus: Option<u8> = if let Some(cpus) = cpus {
|
||||
Some(cpus.parse().map_err(Error::InvalidCPUCount)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let desired_ram: Option<u64> = if let Some(memory) = memory {
|
||||
Some(memory.parse().map_err(Error::InvalidMemorySize)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let resize = vmm::api::VmResizeData {
|
||||
desired_vcpus,
|
||||
desired_ram,
|
||||
};
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"resize",
|
||||
Some(&serde_json::to_string(&resize).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_device_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let device_config = vmm::config::DeviceConfig::parse(config).map_err(Error::AddDeviceConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-device",
|
||||
Some(&serde_json::to_string(&device_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn remove_device_api_command(socket: &mut UnixStream, id: &str) -> Result<(), Error> {
|
||||
let remove_device_data = vmm::api::VmRemoveDeviceData { id: id.to_owned() };
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"remove-device",
|
||||
Some(&serde_json::to_string(&remove_device_data).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn do_command(matches: &ArgMatches) -> Result<(), Error> {
|
||||
let mut socket =
|
||||
UnixStream::connect(matches.value_of("api-socket").unwrap()).map_err(Error::Socket)?;
|
||||
|
||||
match matches.subcommand_name() {
|
||||
Some("info") => simple_api_command(&mut socket, "GET", "info", None),
|
||||
Some("resize") => resize_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("resize")
|
||||
.unwrap()
|
||||
.value_of("cpus"),
|
||||
matches
|
||||
.subcommand_matches("resize")
|
||||
.unwrap()
|
||||
.value_of("memory"),
|
||||
),
|
||||
Some("add-device") => add_device_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-device")
|
||||
.unwrap()
|
||||
.value_of("device_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("remove-device") => remove_device_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("remove-device")
|
||||
.unwrap()
|
||||
.value_of("id")
|
||||
.unwrap(),
|
||||
),
|
||||
Some(c) => simple_api_command(&mut socket, "PUT", c, None),
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new("ch-remote")
|
||||
.author(crate_authors!())
|
||||
.setting(AppSettings::SubcommandRequired)
|
||||
.about("Remotely control a cloud-hypervisor VMM.")
|
||||
.arg(
|
||||
Arg::with_name("api-socket")
|
||||
.long("api-socket")
|
||||
.help("HTTP API socket path (UNIX domain socket).")
|
||||
.takes_value(true)
|
||||
.min_values(1)
|
||||
.required(true),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-device")
|
||||
.about("Add VFIO device")
|
||||
.arg(
|
||||
Arg::with_name("device_config")
|
||||
.index(1)
|
||||
.help(vmm::config::DeviceConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("remove-device")
|
||||
.about("Remove VFIO device")
|
||||
.arg(Arg::with_name("id").index(1).help("<device_id>")),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("info").about("Info on the VM"))
|
||||
.subcommand(SubCommand::with_name("pause").about("Pause the VM"))
|
||||
.subcommand(SubCommand::with_name("reboot").about("Reboot the VM"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("resize")
|
||||
.about("Resize the VM")
|
||||
.arg(
|
||||
Arg::with_name("cpus")
|
||||
.long("cpus")
|
||||
.help("New vCPUs count")
|
||||
.takes_value(true)
|
||||
.number_of_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("memory")
|
||||
.long("memory")
|
||||
.help("New memory size (in MiB)")
|
||||
.takes_value(true)
|
||||
.number_of_values(1),
|
||||
),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("resume").about("Resume the VM"))
|
||||
.subcommand(SubCommand::with_name("shutdown").about("Shutdown the VM"));
|
||||
|
||||
let matches = app.get_matches();
|
||||
|
||||
if let Err(e) = do_command(&matches) {
|
||||
eprintln!("Error running command: {:?}", e);
|
||||
process::exit(1)
|
||||
};
|
||||
}
|
||||
38
src/bin/vhost_user_blk.rs
Normal file
38
src/bin/vhost_user_blk.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2019 Red Hat, Inc. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
|
||||
|
||||
#[macro_use(crate_version, crate_authors)]
|
||||
extern crate clap;
|
||||
extern crate vhost_user_block;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use vhost_user_block::start_block_backend;
|
||||
|
||||
fn main() {
|
||||
let cmd_arguments = App::new("vhost-user-blk backend")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.about("Launch a vhost-user-blk backend.")
|
||||
.arg(
|
||||
Arg::with_name("block-backend")
|
||||
.long("block-backend")
|
||||
.help(
|
||||
"vhost-user-block backend parameters \
|
||||
\"image=<image_path>,sock=<socket_path>,num_queues=<number_of_queues>,\
|
||||
readonly=true|false,direct=true|false,poll_queue=true|false\"",
|
||||
)
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let backend_command = cmd_arguments.value_of("block-backend").unwrap();
|
||||
start_block_backend(backend_command);
|
||||
}
|
||||
345
src/bin/vhost_user_fs.rs
Normal file
345
src/bin/vhost_user_fs.rs
Normal file
@@ -0,0 +1,345 @@
|
||||
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
|
||||
|
||||
#[macro_use(crate_version, crate_authors)]
|
||||
extern crate clap;
|
||||
extern crate log;
|
||||
extern crate vhost_rs;
|
||||
extern crate vhost_user_backend;
|
||||
extern crate vm_virtio;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use epoll;
|
||||
use futures::executor::{ThreadPool, ThreadPoolBuilder};
|
||||
use libc::EFD_NONBLOCK;
|
||||
use log::*;
|
||||
use std::num::Wrapping;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::{convert, error, fmt, io, process};
|
||||
|
||||
use vhost_rs::vhost_user::message::*;
|
||||
use vhost_rs::vhost_user::SlaveFsCacheReq;
|
||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
|
||||
use vhost_user_fs::descriptor_utils::Error as VufDescriptorError;
|
||||
use vhost_user_fs::descriptor_utils::{Reader, Writer};
|
||||
use vhost_user_fs::filesystem::FileSystem;
|
||||
use vhost_user_fs::passthrough::{self, PassthroughFs};
|
||||
use vhost_user_fs::server::Server;
|
||||
use vhost_user_fs::Error as VhostUserFsError;
|
||||
use virtio_bindings::bindings::virtio_net::*;
|
||||
use virtio_bindings::bindings::virtio_ring::{
|
||||
VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC,
|
||||
};
|
||||
use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_virtio::queue::DescriptorChain;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: usize = 1024;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
const THREAD_POOL_SIZE: usize = 64;
|
||||
|
||||
// The guest queued an available buffer for the high priority queue.
|
||||
const HIPRIO_QUEUE_EVENT: u16 = 0;
|
||||
// The guest queued an available buffer for the request queue.
|
||||
const REQ_QUEUE_EVENT: u16 = 1;
|
||||
// The device has been dropped.
|
||||
const KILL_EVENT: u16 = 2;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Failed to create kill eventfd.
|
||||
CreateKillEventFd(io::Error),
|
||||
/// Failed to create thread pool.
|
||||
CreateThreadPool(io::Error),
|
||||
/// Failed to handle event other than input event.
|
||||
HandleEventNotEpollIn,
|
||||
/// Failed to handle unknown event.
|
||||
HandleEventUnknownEvent,
|
||||
/// No memory configured.
|
||||
NoMemoryConfigured,
|
||||
/// Processing queue failed.
|
||||
ProcessQueue(VhostUserFsError),
|
||||
/// Creating a queue reader failed.
|
||||
QueueReader(VufDescriptorError),
|
||||
/// Creating a queue writer failed.
|
||||
QueueWriter(VufDescriptorError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "vhost_user_fs_error: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
impl convert::From<Error> for io::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
}
|
||||
|
||||
struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
|
||||
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
kill_evt: EventFd,
|
||||
server: Arc<Server<F>>,
|
||||
// handle request from slave to master
|
||||
vu_req: Option<SlaveFsCacheReq>,
|
||||
event_idx: bool,
|
||||
pool: ThreadPool,
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsBackend<F> {
|
||||
fn clone(&self) -> Self {
|
||||
VhostUserFsBackend {
|
||||
mem: self.mem.clone(),
|
||||
kill_evt: self.kill_evt.try_clone().unwrap(),
|
||||
server: self.server.clone(),
|
||||
vu_req: self.vu_req.clone(),
|
||||
event_idx: self.event_idx,
|
||||
pool: self.pool.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
|
||||
fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
|
||||
Ok(VhostUserFsBackend {
|
||||
mem: None,
|
||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
||||
server: Arc::new(Server::new(fs)),
|
||||
vu_req: None,
|
||||
event_idx: false,
|
||||
pool: ThreadPoolBuilder::new()
|
||||
.pool_size(thread_pool_size)
|
||||
.create()
|
||||
.map_err(Error::CreateThreadPool)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn process_queue(&mut self, vring_lock: Arc<RwLock<Vring>>) -> Result<bool> {
|
||||
let mut used_any = false;
|
||||
let (atomic_mem, mem) = match &self.mem {
|
||||
Some(m) => (m, m.memory()),
|
||||
None => return Err(Error::NoMemoryConfigured),
|
||||
};
|
||||
let mut vring = vring_lock.write().unwrap();
|
||||
|
||||
while let Some(avail_desc) = vring.mut_queue().iter(&mem).next() {
|
||||
used_any = true;
|
||||
|
||||
// Prepare a set of objects that can be moved to the worker thread.
|
||||
let desc_head = avail_desc.get_head();
|
||||
let atomic_mem = atomic_mem.clone();
|
||||
let server = self.server.clone();
|
||||
let mut vu_req = self.vu_req.clone();
|
||||
let event_idx = self.event_idx;
|
||||
let vring_lock = vring_lock.clone();
|
||||
|
||||
self.pool.spawn_ok(async move {
|
||||
let mem = atomic_mem.memory();
|
||||
let desc = DescriptorChain::new_from_head(&mem, desc_head).unwrap();
|
||||
let head_index = desc.index;
|
||||
|
||||
let reader = Reader::new(&mem, desc.clone())
|
||||
.map_err(Error::QueueReader)
|
||||
.unwrap();
|
||||
let writer = Writer::new(&mem, desc.clone())
|
||||
.map_err(Error::QueueWriter)
|
||||
.unwrap();
|
||||
|
||||
server
|
||||
.handle_message(reader, writer, vu_req.as_mut())
|
||||
.map_err(Error::ProcessQueue)
|
||||
.unwrap();
|
||||
|
||||
let mut vring = vring_lock.write().unwrap();
|
||||
|
||||
if event_idx {
|
||||
if let Some(used_idx) = vring.mut_queue().add_used(&mem, head_index, 0) {
|
||||
if vring.needs_notification(&mem, Wrapping(used_idx)) {
|
||||
vring.signal_used_queue().unwrap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vring.mut_queue().add_used(&mem, head_index, 0);
|
||||
vring.signal_used_queue().unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(used_any)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBackend<F> {
|
||||
fn num_queues(&self) -> usize {
|
||||
NUM_QUEUES
|
||||
}
|
||||
|
||||
fn max_queue_size(&self) -> usize {
|
||||
QUEUE_SIZE
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
1 << VIRTIO_F_VERSION_1
|
||||
| 1 << VIRTIO_RING_F_INDIRECT_DESC
|
||||
| 1 << VIRTIO_RING_F_EVENT_IDX
|
||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||
}
|
||||
|
||||
fn protocol_features(&self) -> VhostUserProtocolFeatures {
|
||||
VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::SLAVE_REQ
|
||||
}
|
||||
|
||||
fn set_event_idx(&mut self, enabled: bool) {
|
||||
self.event_idx = enabled;
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||
self.mem = Some(GuestMemoryAtomic::new(mem));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
) -> VhostUserBackendResult<bool> {
|
||||
if evset != epoll::Events::EPOLLIN {
|
||||
return Err(Error::HandleEventNotEpollIn.into());
|
||||
}
|
||||
|
||||
let mem = match &self.mem {
|
||||
Some(m) => m.memory(),
|
||||
None => return Err(Error::NoMemoryConfigured.into()),
|
||||
};
|
||||
|
||||
let vring_lock = match device_event {
|
||||
HIPRIO_QUEUE_EVENT => {
|
||||
debug!("HIPRIO_QUEUE_EVENT");
|
||||
vrings[0].clone()
|
||||
}
|
||||
REQ_QUEUE_EVENT => {
|
||||
debug!("QUEUE_EVENT");
|
||||
vrings[1].clone()
|
||||
}
|
||||
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
||||
};
|
||||
|
||||
if self.event_idx {
|
||||
// vm-virtio's Queue implementation only checks avail_index
|
||||
// once, so to properly support EVENT_IDX we need to keep
|
||||
// calling process_queue() until it stops finding new
|
||||
// requests on the queue.
|
||||
loop {
|
||||
{
|
||||
let mut vring = vring_lock.write().unwrap();
|
||||
vring.mut_queue().update_avail_event(&mem);
|
||||
}
|
||||
if !self.process_queue(vring_lock.clone())? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Without EVENT_IDX, a single call is enough.
|
||||
self.process_queue(vring_lock)?;
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
|
||||
Some((self.kill_evt.try_clone().unwrap(), Some(KILL_EVENT)))
|
||||
}
|
||||
|
||||
fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) {
|
||||
self.vu_req = Some(vu_req);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cmd_arguments = App::new("vhost-user-fs backend")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.about("Launch a vhost-user-fs backend.")
|
||||
.arg(
|
||||
Arg::with_name("shared-dir")
|
||||
.long("shared-dir")
|
||||
.help("Shared directory path")
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("sock")
|
||||
.long("sock")
|
||||
.help("vhost-user socket path")
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("thread-pool-size")
|
||||
.long("thread-pool-size")
|
||||
.help("thread pool size (default 64)")
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("disable-xattr")
|
||||
.long("disable-xattr")
|
||||
.help("Disable support for extended attributes"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// Retrieve arguments
|
||||
let shared_dir = cmd_arguments
|
||||
.value_of("shared-dir")
|
||||
.expect("Failed to retrieve shared directory path");
|
||||
let sock = cmd_arguments
|
||||
.value_of("sock")
|
||||
.expect("Failed to retrieve vhost-user socket path");
|
||||
let thread_pool_size: usize = match cmd_arguments.value_of("thread-pool-size") {
|
||||
Some(size) => size.parse().expect("Invalid argument for thread-pool-size"),
|
||||
None => THREAD_POOL_SIZE,
|
||||
};
|
||||
let xattr: bool = !cmd_arguments.is_present("disable-xattr");
|
||||
|
||||
// Convert into appropriate types
|
||||
let sock = String::from(sock);
|
||||
|
||||
let fs_cfg = passthrough::Config {
|
||||
root_dir: shared_dir.to_string(),
|
||||
xattr,
|
||||
..Default::default()
|
||||
};
|
||||
let fs = PassthroughFs::new(fs_cfg).unwrap();
|
||||
let fs_backend = Arc::new(RwLock::new(
|
||||
VhostUserFsBackend::new(fs, thread_pool_size).unwrap(),
|
||||
));
|
||||
|
||||
let mut daemon = VhostUserDaemon::new(
|
||||
String::from("vhost-user-fs-backend"),
|
||||
sock,
|
||||
fs_backend.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Err(e) = daemon.start() {
|
||||
error!("Failed to start daemon: {:?}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(e) = daemon.wait() {
|
||||
error!("Waiting for daemon failed: {:?}", e);
|
||||
}
|
||||
|
||||
let kill_evt = &fs_backend.read().unwrap().kill_evt;
|
||||
if let Err(e) = kill_evt.write(1) {
|
||||
error!("Error shutting down worker thread: {:?}", e)
|
||||
}
|
||||
}
|
||||
@@ -8,532 +8,10 @@
|
||||
|
||||
#[macro_use(crate_version, crate_authors)]
|
||||
extern crate clap;
|
||||
extern crate log;
|
||||
extern crate net_util;
|
||||
extern crate vhost_rs;
|
||||
extern crate vhost_user_backend;
|
||||
extern crate vm_virtio;
|
||||
extern crate vhost_user_net;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use epoll;
|
||||
use libc::{self, EAGAIN, EFD_NONBLOCK};
|
||||
use log::*;
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::io::Read;
|
||||
use std::io::{self, Write};
|
||||
use std::mem;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::process;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::vec::Vec;
|
||||
|
||||
use vhost_rs::vhost_user::message::*;
|
||||
use vhost_rs::vhost_user::Error as VhostUserError;
|
||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
||||
|
||||
use net_gen;
|
||||
|
||||
use net_util::{Tap, TapError};
|
||||
use virtio_bindings::bindings::virtio_net::*;
|
||||
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// The maximum buffer size when segmentation offload is enabled. This
|
||||
/// includes the 12-byte virtio net header.
|
||||
/// http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html#x1-1740003
|
||||
const MAX_BUFFER_SIZE: usize = 65562;
|
||||
const QUEUE_SIZE: usize = 1024;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
|
||||
// The guest has made a buffer available to receive a frame into.
|
||||
const RX_QUEUE_EVENT: u16 = 0;
|
||||
// The transmit queue has a frame that is ready to send from the guest.
|
||||
const TX_QUEUE_EVENT: u16 = 1;
|
||||
// A frame is available for reading from the tap device to receive in the guest.
|
||||
const RX_TAP_EVENT: u16 = 2;
|
||||
// The device has been dropped.
|
||||
const KILL_EVENT: u16 = 3;
|
||||
|
||||
pub type VhostUserResult<T> = std::result::Result<T, VhostUserError>;
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to activate device.
|
||||
BadActivate,
|
||||
/// Failed to create kill eventfd
|
||||
CreateKillEventFd,
|
||||
/// Failed to add event.
|
||||
EpollCtl(io::Error),
|
||||
/// Fail to wait event.
|
||||
EpollWait(io::Error),
|
||||
/// Failed to create EventFd.
|
||||
EpollCreateFd,
|
||||
/// Failed to read Tap.
|
||||
FailedReadTap,
|
||||
/// Failed to signal used queue.
|
||||
FailedSignalingUsedQueue,
|
||||
/// Failed to handle event other than input event.
|
||||
HandleEventNotEpollIn,
|
||||
/// Failed to handle unknown event.
|
||||
HandleEventUnknownEvent,
|
||||
/// Invalid vring address.
|
||||
InvalidVringAddr,
|
||||
/// No vring call fd to notify.
|
||||
NoVringCallFdNotify,
|
||||
/// No memory configured.
|
||||
NoMemoryConfigured,
|
||||
/// Failed to parse sock parameter.
|
||||
ParseSockParam,
|
||||
/// Failed to parse ip parameter.
|
||||
ParseIpParam,
|
||||
/// Failed to parse mask parameter.
|
||||
ParseMaskParam,
|
||||
/// Open tap device failed.
|
||||
TapOpen(TapError),
|
||||
/// Setting tap IP failed.
|
||||
TapSetIp(TapError),
|
||||
/// Setting tap netmask failed.
|
||||
TapSetNetmask(TapError),
|
||||
/// Setting tap interface offload flags failed.
|
||||
TapSetOffload(TapError),
|
||||
/// Setting vnet header size failed.
|
||||
TapSetVnetHdrSize(TapError),
|
||||
/// Enabling tap interface failed.
|
||||
TapEnable(TapError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "vhost_user_net_error: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl std::convert::From<Error> for std::io::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
std::io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TxVirtio {
|
||||
iovec: Vec<(GuestAddress, usize)>,
|
||||
frame_buf: [u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
impl TxVirtio {
|
||||
fn new() -> Self {
|
||||
TxVirtio {
|
||||
iovec: Vec::new(),
|
||||
frame_buf: [0u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RxVirtio {
|
||||
deferred_frame: bool,
|
||||
deferred_irqs: bool,
|
||||
bytes_read: usize,
|
||||
frame_buf: [u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
|
||||
impl RxVirtio {
|
||||
fn new() -> Self {
|
||||
RxVirtio {
|
||||
deferred_frame: false,
|
||||
deferred_irqs: false,
|
||||
bytes_read: 0,
|
||||
frame_buf: [0u8; MAX_BUFFER_SIZE],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn vnet_hdr_len() -> usize {
|
||||
mem::size_of::<virtio_net_hdr_v1>()
|
||||
}
|
||||
|
||||
struct VhostUserNetBackend {
|
||||
mem: Option<GuestMemoryMmap>,
|
||||
vring_worker: Option<Arc<VringWorker>>,
|
||||
kill_evt: EventFd,
|
||||
tap: Tap,
|
||||
rx: RxVirtio,
|
||||
tx: TxVirtio,
|
||||
rx_tap_listening: bool,
|
||||
}
|
||||
|
||||
impl std::clone::Clone for VhostUserNetBackend {
|
||||
fn clone(&self) -> Self {
|
||||
VhostUserNetBackend {
|
||||
mem: self.mem.clone(),
|
||||
vring_worker: self.vring_worker.clone(),
|
||||
kill_evt: self.kill_evt.try_clone().unwrap(),
|
||||
tap: self.tap.clone(),
|
||||
rx: self.rx.clone(),
|
||||
tx: self.tx.clone(),
|
||||
rx_tap_listening: self.rx_tap_listening,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserNetBackend {
|
||||
/// Create a new virtio network device with the given TAP interface.
|
||||
pub fn new_with_tap(tap: Tap) -> Result<Self> {
|
||||
// Set offload flags to match the virtio features below.
|
||||
tap.set_offload(
|
||||
net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6,
|
||||
)
|
||||
.map_err(Error::TapSetOffload)?;
|
||||
|
||||
let vnet_hdr_size = vnet_hdr_len() as i32;
|
||||
tap.set_vnet_hdr_size(vnet_hdr_size)
|
||||
.map_err(Error::TapSetVnetHdrSize)?;
|
||||
|
||||
let rx = RxVirtio::new();
|
||||
let tx = TxVirtio::new();
|
||||
|
||||
Ok(VhostUserNetBackend {
|
||||
mem: None,
|
||||
vring_worker: None,
|
||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(|_| Error::CreateKillEventFd)?,
|
||||
tap,
|
||||
rx,
|
||||
tx,
|
||||
rx_tap_listening: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new virtio network device with the given IP address and
|
||||
/// netmask.
|
||||
pub fn new(ip_addr: Ipv4Addr, netmask: Ipv4Addr) -> Result<Self> {
|
||||
let tap = Tap::new().map_err(Error::TapOpen)?;
|
||||
tap.set_ip_addr(ip_addr).map_err(Error::TapSetIp)?;
|
||||
tap.set_netmask(netmask).map_err(Error::TapSetNetmask)?;
|
||||
tap.enable().map_err(Error::TapEnable)?;
|
||||
|
||||
Self::new_with_tap(tap)
|
||||
}
|
||||
|
||||
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
|
||||
// if a buffer was used, and false if the frame must be deferred until a buffer
|
||||
// is made available by the driver.
|
||||
fn rx_single_frame(&mut self, vring: &mut Vring) -> Result<bool> {
|
||||
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
|
||||
|
||||
let mut next_desc = vring.mut_queue().iter(&mem).next();
|
||||
|
||||
if next_desc.is_none() {
|
||||
// Queue has no available descriptors
|
||||
if self.rx_tap_listening {
|
||||
self.vring_worker
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.unregister_listener(
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(RX_TAP_EVENT),
|
||||
)
|
||||
.unwrap();
|
||||
self.rx_tap_listening = false;
|
||||
}
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// We just checked that the head descriptor exists.
|
||||
let head_index = next_desc.as_ref().unwrap().index;
|
||||
let mut write_count = 0;
|
||||
|
||||
// Copy from frame into buffer, which may span multiple descriptors.
|
||||
loop {
|
||||
match next_desc {
|
||||
Some(desc) => {
|
||||
if !desc.is_write_only() {
|
||||
break;
|
||||
}
|
||||
let limit = cmp::min(write_count + desc.len as usize, self.rx.bytes_read);
|
||||
let source_slice = &self.rx.frame_buf[write_count..limit];
|
||||
let write_result = mem.write_slice(source_slice, desc.addr);
|
||||
|
||||
match write_result {
|
||||
Ok(_) => {
|
||||
write_count = limit;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to write slice: {:?}", e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if write_count >= self.rx.bytes_read {
|
||||
break;
|
||||
}
|
||||
next_desc = desc.next_descriptor();
|
||||
}
|
||||
None => {
|
||||
warn!("Receiving buffer is too small to hold frame of current size");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vring
|
||||
.mut_queue()
|
||||
.add_used(&mem, head_index, write_count as u32);
|
||||
|
||||
// Mark that we have at least one pending packet and we need to interrupt the guest.
|
||||
self.rx.deferred_irqs = true;
|
||||
|
||||
Ok(write_count >= self.rx.bytes_read)
|
||||
}
|
||||
|
||||
fn process_rx(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
// Read as many frames as possible.
|
||||
loop {
|
||||
match self.read_tap() {
|
||||
Ok(count) => {
|
||||
self.rx.bytes_read = count;
|
||||
if !self.rx_single_frame(vring)? {
|
||||
self.rx.deferred_frame = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// The tap device is non-blocking, so any error aside from EAGAIN is
|
||||
// unexpected.
|
||||
match e.raw_os_error() {
|
||||
Some(err) if err == EAGAIN => (),
|
||||
_ => {
|
||||
error!("Failed to read tap: {:?}", e);
|
||||
return Err(Error::FailedReadTap);
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue().unwrap();
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn resume_rx(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
if self.rx.deferred_frame {
|
||||
if self.rx_single_frame(vring)? {
|
||||
self.rx.deferred_frame = false;
|
||||
// process_rx() was interrupted possibly before consuming all
|
||||
// packets in the tap; try continuing now.
|
||||
self.process_rx(vring)
|
||||
} else if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue().unwrap();
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn process_tx(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
|
||||
|
||||
let mut used_desc_heads = [(0, 0); QUEUE_SIZE];
|
||||
let mut used_count = 0;
|
||||
while let Some(avail_desc) = vring.mut_queue().iter(&mem).next() {
|
||||
let head_index = avail_desc.index;
|
||||
let mut read_count = 0;
|
||||
let mut next_desc = Some(avail_desc);
|
||||
|
||||
self.tx.iovec.clear();
|
||||
while let Some(desc) = next_desc {
|
||||
if desc.is_write_only() {
|
||||
break;
|
||||
}
|
||||
self.tx.iovec.push((desc.addr, desc.len as usize));
|
||||
read_count += desc.len as usize;
|
||||
next_desc = desc.next_descriptor();
|
||||
}
|
||||
used_desc_heads[used_count] = (head_index, read_count);
|
||||
used_count += 1;
|
||||
read_count = 0;
|
||||
// Copy buffer from across multiple descriptors.
|
||||
// TODO(performance - Issue #420): change this to use `writev()` instead of `write()`
|
||||
// and get rid of the intermediate buffer.
|
||||
for (desc_addr, desc_len) in self.tx.iovec.drain(..) {
|
||||
let limit = cmp::min((read_count + desc_len) as usize, self.tx.frame_buf.len());
|
||||
|
||||
let read_result = mem.read_slice(
|
||||
&mut self.tx.frame_buf[read_count..limit as usize],
|
||||
desc_addr,
|
||||
);
|
||||
match read_result {
|
||||
Ok(_) => {
|
||||
// Increment by number of bytes actually read
|
||||
read_count += limit - read_count;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to read slice: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let write_result = self.tap.write(&self.tx.frame_buf[..read_count as usize]);
|
||||
match write_result {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("net: tx: error failed to write to tap: {}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if used_count > 0 {
|
||||
for &(desc_index, _) in &used_desc_heads[..used_count] {
|
||||
vring.mut_queue().add_used(&mem, desc_index, 0);
|
||||
}
|
||||
vring.signal_used_queue().unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_tap(&mut self) -> io::Result<usize> {
|
||||
self.tap.read(&mut self.rx.frame_buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserBackend for VhostUserNetBackend {
|
||||
fn num_queues(&self) -> usize {
|
||||
NUM_QUEUES
|
||||
}
|
||||
|
||||
fn max_queue_size(&self) -> usize {
|
||||
QUEUE_SIZE
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
1 << VIRTIO_NET_F_GUEST_CSUM
|
||||
| 1 << VIRTIO_NET_F_CSUM
|
||||
| 1 << VIRTIO_NET_F_GUEST_TSO4
|
||||
| 1 << VIRTIO_NET_F_GUEST_UFO
|
||||
| 1 << VIRTIO_NET_F_HOST_TSO4
|
||||
| 1 << VIRTIO_NET_F_HOST_UFO
|
||||
| 1 << VIRTIO_F_VERSION_1
|
||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||
self.mem = Some(mem);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
) -> VhostUserBackendResult<bool> {
|
||||
if evset != epoll::Events::EPOLLIN {
|
||||
return Err(Error::HandleEventNotEpollIn.into());
|
||||
}
|
||||
|
||||
match device_event {
|
||||
RX_QUEUE_EVENT => {
|
||||
let mut vring = vrings[0].write().unwrap();
|
||||
self.resume_rx(&mut vring)?;
|
||||
|
||||
if !self.rx_tap_listening {
|
||||
self.vring_worker.as_ref().unwrap().register_listener(
|
||||
self.tap.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(RX_TAP_EVENT),
|
||||
)?;
|
||||
self.rx_tap_listening = true;
|
||||
}
|
||||
}
|
||||
TX_QUEUE_EVENT => {
|
||||
let mut vring = vrings[1].write().unwrap();
|
||||
self.process_tx(&mut vring)?;
|
||||
}
|
||||
RX_TAP_EVENT => {
|
||||
let mut vring = vrings[0].write().unwrap();
|
||||
if self.rx.deferred_frame
|
||||
// Process a deferred frame first if available. Don't read from tap again
|
||||
// until we manage to receive this deferred frame.
|
||||
{
|
||||
if self.rx_single_frame(&mut vring)? {
|
||||
self.rx.deferred_frame = false;
|
||||
self.process_rx(&mut vring)?;
|
||||
} else if self.rx.deferred_irqs {
|
||||
self.rx.deferred_irqs = false;
|
||||
vring.signal_used_queue()?;
|
||||
}
|
||||
} else {
|
||||
self.process_rx(&mut vring)?;
|
||||
}
|
||||
}
|
||||
KILL_EVENT => {
|
||||
self.kill_evt.read().unwrap();
|
||||
return Ok(true);
|
||||
}
|
||||
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserNetBackendConfig<'a> {
|
||||
pub ip: Ipv4Addr,
|
||||
pub mask: Ipv4Addr,
|
||||
pub sock: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> VhostUserNetBackendConfig<'a> {
|
||||
pub fn parse(backend: &'a str) -> Result<Self> {
|
||||
let params_list: Vec<&str> = backend.split(',').collect();
|
||||
|
||||
let mut ip_str: &str = "";
|
||||
let mut mask_str: &str = "";
|
||||
let mut sock: &str = "";
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("ip=") {
|
||||
ip_str = ¶m[3..];
|
||||
} else if param.starts_with("mask=") {
|
||||
mask_str = ¶m[5..];
|
||||
} else if param.starts_with("sock=") {
|
||||
sock = ¶m[5..];
|
||||
}
|
||||
}
|
||||
|
||||
let mut ip: Ipv4Addr = Ipv4Addr::new(192, 168, 100, 1);
|
||||
let mut mask: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);
|
||||
|
||||
if sock.is_empty() {
|
||||
return Err(Error::ParseSockParam);
|
||||
}
|
||||
if !ip_str.is_empty() {
|
||||
ip = ip_str.parse().map_err(|_| Error::ParseIpParam)?;
|
||||
}
|
||||
if !mask_str.is_empty() {
|
||||
mask = mask_str.parse().map_err(|_| Error::ParseMaskParam)?;
|
||||
}
|
||||
|
||||
Ok(VhostUserNetBackendConfig { ip, mask, sock })
|
||||
}
|
||||
}
|
||||
use vhost_user_net::start_net_backend;
|
||||
|
||||
fn main() {
|
||||
let cmd_arguments = App::new("vhost-user-net backend")
|
||||
@@ -541,57 +19,18 @@ fn main() {
|
||||
.author(crate_authors!())
|
||||
.about("Launch a vhost-user-net backend.")
|
||||
.arg(
|
||||
Arg::with_name("backend")
|
||||
.long("backend")
|
||||
Arg::with_name("net-backend")
|
||||
.long("net-backend")
|
||||
.help(
|
||||
"Backend parameters \"ip=<ip_addr>,\
|
||||
mask=<net_mask>,sock=<socket_path>\"",
|
||||
"vhost-user-net backend parameters \
|
||||
\"ip=<ip_addr>,mask=<net_mask>,sock=<socket_path>,\
|
||||
num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,tap=<if_name>\"",
|
||||
)
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let vhost_user_net_backend = cmd_arguments.value_of("backend").unwrap();
|
||||
|
||||
let backend_config = match VhostUserNetBackendConfig::parse(vhost_user_net_backend) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
println!("Failed parsing parameters {:?}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let net_backend = Arc::new(RwLock::new(
|
||||
VhostUserNetBackend::new(backend_config.ip, backend_config.mask).unwrap(),
|
||||
));
|
||||
let name = "vhost-user-net-backend";
|
||||
let mut net_daemon = VhostUserDaemon::new(
|
||||
name.to_string(),
|
||||
backend_config.sock.to_string(),
|
||||
net_backend.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let vring_worker = net_daemon.get_vring_worker();
|
||||
|
||||
if let Err(e) = vring_worker.register_listener(
|
||||
net_backend.read().unwrap().kill_evt.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(KILL_EVENT),
|
||||
) {
|
||||
println!("failed to register listener for kill event: {:?}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
net_backend.write().unwrap().vring_worker = Some(vring_worker);
|
||||
|
||||
if let Err(e) = net_daemon.start() {
|
||||
println!(
|
||||
"failed to start daemon for vhost-user-net with error: {:?}",
|
||||
e
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
net_daemon.wait().unwrap();
|
||||
let backend_command = cmd_arguments.value_of("net-backend").unwrap();
|
||||
start_net_backend(backend_command);
|
||||
}
|
||||
|
||||
3695
src/main.rs
3695
src/main.rs
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ write_files:
|
||||
Gateway=192.168.2.1
|
||||
|
||||
-
|
||||
path: /etc/systemd/network/00-static-l2.network
|
||||
path: /etc/systemd/network/00-static-l2-1.network
|
||||
permissions: 0644
|
||||
content: |
|
||||
[Match]
|
||||
@@ -27,6 +27,28 @@ write_files:
|
||||
Address=192.168.2.3/24
|
||||
Gateway=192.168.2.1
|
||||
|
||||
-
|
||||
path: /etc/systemd/network/00-static-l2-2.network
|
||||
permissions: 0644
|
||||
content: |
|
||||
[Match]
|
||||
MACAddress=de:ad:be:ef:34:56
|
||||
|
||||
[Network]
|
||||
Address=192.168.2.4/24
|
||||
Gateway=192.168.2.1
|
||||
|
||||
-
|
||||
path: /etc/systemd/network/00-static-l2-3.network
|
||||
permissions: 0644
|
||||
content: |
|
||||
[Match]
|
||||
MACAddress=de:ad:be:ef:56:78
|
||||
|
||||
[Network]
|
||||
Address=192.168.2.5/24
|
||||
Gateway=192.168.2.1
|
||||
|
||||
-
|
||||
path: /etc/systemd/system/vfio.service
|
||||
permissions: 0644
|
||||
@@ -47,8 +69,12 @@ write_files:
|
||||
content: |
|
||||
#!/bin/bash
|
||||
|
||||
mount -t virtio_fs virtiofs /mnt -o rootmode=040000,user_id=0,group_id=0,dax
|
||||
mount -t virtiofs -o dax myfs /mnt
|
||||
bash -c "echo 0000:00:05.0 > /sys/bus/pci/devices/0000\:00\:05.0/driver/unbind"
|
||||
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
|
||||
|
||||
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda2 VFIOTAG" --disk path=/mnt/clear-cloudguest.img path=/mnt/cloudinit.img --cpus 1 --memory size=512M --rng --device path=/sys/bus/pci/devices/0000:00:05.0/
|
||||
bash -c "echo 0000:00:06.0 > /sys/bus/pci/devices/0000\:00\:06.0/driver/unbind"
|
||||
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
|
||||
# 512M ram requires 256 pages
|
||||
echo 256 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
sudo chmod a+rwX /dev/hugepages
|
||||
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=PARTUUID=6fb4d1a8-6c8c-4dd7-9f7c-1fe0b9f2574c VFIOTAG" --disk path=/mnt/clear-31311-cloudguest.img path=/mnt/cloudinit.img --cpus boot=1 --memory size=512M,file=/dev/hugepages --device path=/sys/bus/pci/devices/0000:00:05.0/ path=/sys/bus/pci/devices/0000:00:06.0/ --api-socket /tmp/ch_api.sock
|
||||
|
||||
3450
tests/integration.rs
Normal file
3450
tests/integration.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,18 +4,19 @@ version = "0.0.1"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.2"
|
||||
arc-swap = ">=0.4.4"
|
||||
byteorder = "1.3.4"
|
||||
devices = { path = "../devices" }
|
||||
kvm-bindings = "0.1.1"
|
||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
||||
libc = "0.2.60"
|
||||
kvm-bindings = "0.2.0"
|
||||
kvm-ioctls = "0.5.0"
|
||||
libc = "0.2.68"
|
||||
log = "0.4.8"
|
||||
pci = { path = "../pci" }
|
||||
vfio-bindings = "0.1.0"
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
vm-memory = { version = "0.1.0", features = ["backend-mmap"] }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
features = ["backend-mmap"]
|
||||
[dependencies.vfio-bindings]
|
||||
git = "https://github.com/rust-vmm/vfio-bindings"
|
||||
features = ["fam-wrappers"]
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
//#![deny(missing_docs)]
|
||||
//! Virtual Function I/O (VFIO) API
|
||||
extern crate arc_swap;
|
||||
extern crate byteorder;
|
||||
extern crate devices;
|
||||
extern crate kvm_bindings;
|
||||
|
||||
@@ -6,22 +6,29 @@ use crate::vec_with_array_field;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use kvm_ioctls::*;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CString;
|
||||
use std::fmt;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::num;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::unix::prelude::FileExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::result;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::Arc;
|
||||
use std::u32;
|
||||
use vfio_bindings::bindings::vfio::*;
|
||||
use vfio_bindings::bindings::IrqSet;
|
||||
use vfio_ioctls::*;
|
||||
use vm_device::ExternalDmaMapping;
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
|
||||
use vm_device::{get_host_address_range, ExternalDmaMapping};
|
||||
use vm_memory::{
|
||||
Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap,
|
||||
GuestMemoryRegion,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::fam::FamStruct;
|
||||
use vmm_sys_util::ioctl::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -38,7 +45,7 @@ pub enum VfioError {
|
||||
UnsetContainer,
|
||||
ContainerSetIOMMU,
|
||||
GroupGetDeviceFD,
|
||||
KvmSetDeviceAttr(io::Error),
|
||||
KvmSetDeviceAttr(kvm_ioctls::Error),
|
||||
VfioDeviceGetInfo,
|
||||
VfioDeviceGetRegionInfo,
|
||||
InvalidPath,
|
||||
@@ -46,6 +53,9 @@ pub enum VfioError {
|
||||
IommuDmaUnmap,
|
||||
VfioDeviceGetIrqInfo,
|
||||
VfioDeviceSetIrq,
|
||||
VfioNoIommuGroup(String),
|
||||
ReadLink(io::Error),
|
||||
ParseInt(num::ParseIntError),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, VfioError>;
|
||||
|
||||
@@ -94,6 +104,9 @@ impl fmt::Display for VfioError {
|
||||
}
|
||||
VfioError::VfioDeviceGetIrqInfo => write!(f, "failed to get vfio device irq info"),
|
||||
VfioError::VfioDeviceSetIrq => write!(f, "failed to set vfio deviece irq"),
|
||||
VfioError::ReadLink(e) => write!(f, "failed to read link from path: {}", e),
|
||||
VfioError::VfioNoIommuGroup(e) => write!(f, "Failed to find iommu_group for: {}", e),
|
||||
VfioError::ParseInt(e) => write!(f, "failed to parse integer: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,7 +281,7 @@ impl VfioGroup {
|
||||
.map_err(VfioError::KvmSetDeviceAttr)
|
||||
}
|
||||
|
||||
fn kvm_device_del_group(&self) -> std::result::Result<(), io::Error> {
|
||||
fn kvm_device_del_group(&self) -> std::result::Result<(), kvm_ioctls::Error> {
|
||||
let group_fd = self.as_raw_fd();
|
||||
let group_fd_ptr = &group_fd as *const i32;
|
||||
let dev_attr = kvm_bindings::kvm_device_attr {
|
||||
@@ -435,7 +448,7 @@ impl VfioDeviceInfo {
|
||||
ioctl_with_mut_ref(&self.device, VFIO_DEVICE_GET_REGION_INFO(), &mut reg_info)
|
||||
};
|
||||
if ret < 0 {
|
||||
error!("Could not get region #{} info", i);
|
||||
warn!("Could not get region #{} info", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -461,7 +474,7 @@ impl VfioDeviceInfo {
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
error!("Could not get region #{} info", i);
|
||||
warn!("Could not get region #{} info", i);
|
||||
continue;
|
||||
}
|
||||
// region_with_cap[0].cap_info may contain vfio_region_info_cap_sparse_mmap
|
||||
@@ -513,23 +526,22 @@ impl VfioDeviceInfo {
|
||||
/// associated with a specific VFIO container.
|
||||
pub struct VfioDmaMapping {
|
||||
container: Arc<VfioContainer>,
|
||||
memory: Arc<RwLock<GuestMemoryMmap>>,
|
||||
memory: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
}
|
||||
|
||||
impl VfioDmaMapping {
|
||||
pub fn new(container: Arc<VfioContainer>, memory: Arc<RwLock<GuestMemoryMmap>>) -> Self {
|
||||
pub fn new(container: Arc<VfioContainer>, memory: GuestMemoryAtomic<GuestMemoryMmap>) -> Self {
|
||||
VfioDmaMapping { container, memory }
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternalDmaMapping for VfioDmaMapping {
|
||||
fn map(&self, iova: u64, gpa: u64, size: u64) -> result::Result<(), io::Error> {
|
||||
let user_addr = if let Some(addr) = self
|
||||
.memory
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_host_address(GuestAddress(gpa))
|
||||
{
|
||||
let user_addr = if let Some(addr) = get_host_address_range(
|
||||
&self.memory.memory(),
|
||||
GuestAddress(gpa),
|
||||
size.try_into().unwrap(),
|
||||
) {
|
||||
addr as u64
|
||||
} else {
|
||||
return Err(io::Error::new(
|
||||
@@ -577,7 +589,7 @@ pub struct VfioDevice {
|
||||
group: VfioGroup,
|
||||
regions: Vec<VfioRegion>,
|
||||
irqs: HashMap<u32, VfioIrq>,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
iommu_attached: bool,
|
||||
}
|
||||
|
||||
@@ -588,16 +600,19 @@ impl VfioDevice {
|
||||
pub fn new(
|
||||
sysfspath: &Path,
|
||||
device_fd: Arc<DeviceFd>,
|
||||
mem: Arc<RwLock<GuestMemoryMmap>>,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
iommu_attached: bool,
|
||||
) -> Result<Self> {
|
||||
let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect();
|
||||
let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?;
|
||||
if !uuid_path.exists() {
|
||||
return Err(VfioError::VfioNoIommuGroup(
|
||||
uuid_path.to_str().unwrap().to_string(),
|
||||
));
|
||||
}
|
||||
let group_path = uuid_path.read_link().map_err(VfioError::ReadLink)?;
|
||||
let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
|
||||
let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?;
|
||||
let group_id = group_str
|
||||
.parse::<u32>()
|
||||
.map_err(|_| VfioError::InvalidPath)?;
|
||||
let group_id = group_str.parse::<u32>().map_err(VfioError::ParseInt)?;
|
||||
|
||||
let group = VfioGroup::new(group_id, device_fd)?;
|
||||
let device_info = group.get_device(sysfspath)?;
|
||||
@@ -641,34 +656,25 @@ impl VfioDevice {
|
||||
return Err(VfioError::VfioDeviceSetIrq);
|
||||
}
|
||||
|
||||
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(event_fds.len());
|
||||
irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32
|
||||
+ (event_fds.len() * mem::size_of::<u32>()) as u32;
|
||||
irq_set[0].flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
|
||||
irq_set[0].index = irq_index;
|
||||
irq_set[0].start = 0;
|
||||
irq_set[0].count = irq.count;
|
||||
let mut irq_set_wrapper = IrqSet::new(event_fds.len() * mem::size_of::<u32>());
|
||||
let mut irq_set = irq_set_wrapper.as_mut_fam_struct();
|
||||
let fds = irq_set.as_mut_slice();
|
||||
|
||||
{
|
||||
// irq_set.data could be none, bool or fd according to flags, so irq_set.data
|
||||
// is u8 default, here irq_set.data is a vector of fds as u32, so 4 default u8
|
||||
// are combined together as u32 for each fd.
|
||||
// It is safe as enough space is reserved through
|
||||
// vec_with_array_field(u32)<event_fds.len()>.
|
||||
let fds = unsafe {
|
||||
irq_set[0]
|
||||
.data
|
||||
.as_mut_slice(event_fds.len() * mem::size_of::<u32>())
|
||||
};
|
||||
for (index, event_fd) in event_fds.iter().enumerate() {
|
||||
let fds_offset = index * mem::size_of::<u32>();
|
||||
let fd = &mut fds[fds_offset..fds_offset + mem::size_of::<u32>()];
|
||||
LittleEndian::write_u32(fd, event_fd.as_raw_fd() as u32);
|
||||
}
|
||||
for (index, event_fd) in event_fds.iter().enumerate() {
|
||||
let fds_offset = index * mem::size_of::<u32>();
|
||||
let fd = &mut fds[fds_offset..fds_offset + mem::size_of::<u32>()];
|
||||
LittleEndian::write_u32(fd, event_fd.as_raw_fd() as u32);
|
||||
}
|
||||
|
||||
irq_set.argsz = mem::size_of::<vfio_irq_set>() as u32
|
||||
+ (event_fds.len() * mem::size_of::<u32>()) as u32;
|
||||
irq_set.flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
|
||||
irq_set.index = irq_index;
|
||||
irq_set.start = 0;
|
||||
irq_set.count = irq.count;
|
||||
|
||||
// Safe as we are the owner of self and irq_set which are valid value
|
||||
let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), &irq_set[0]) };
|
||||
let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), irq_set) };
|
||||
if ret < 0 {
|
||||
return Err(VfioError::VfioDeviceSetIrq);
|
||||
}
|
||||
@@ -692,10 +698,10 @@ impl VfioDevice {
|
||||
|
||||
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0);
|
||||
irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32;
|
||||
irq_set[0].flags = VFIO_IRQ_SET_ACTION_MASK;
|
||||
irq_set[0].flags = VFIO_IRQ_SET_ACTION_TRIGGER | VFIO_IRQ_SET_DATA_NONE;
|
||||
irq_set[0].index = irq_index;
|
||||
irq_set[0].start = 0;
|
||||
irq_set[0].count = irq.count;
|
||||
irq_set[0].count = 0;
|
||||
|
||||
// Safe as we are the owner of self and irq_set which are valid value
|
||||
let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), &irq_set[0]) };
|
||||
@@ -845,7 +851,7 @@ impl VfioDevice {
|
||||
/// then vfio kernel driver could access guest memory from gfn
|
||||
pub fn setup_dma_map(&self) -> Result<()> {
|
||||
if !self.iommu_attached {
|
||||
self.mem.read().unwrap().with_regions(|_index, region| {
|
||||
self.mem.memory().with_regions(|_index, region| {
|
||||
self.vfio_dma_map(
|
||||
region.start_addr().raw_value(),
|
||||
region.len() as u64,
|
||||
@@ -860,7 +866,7 @@ impl VfioDevice {
|
||||
/// then vfio kernel driver couldn't access this guest memory
|
||||
pub fn unset_dma_map(&self) -> Result<()> {
|
||||
if !self.iommu_attached {
|
||||
self.mem.read().unwrap().with_regions(|_index, region| {
|
||||
self.mem.memory().with_regions(|_index, region| {
|
||||
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
|
||||
})?;
|
||||
}
|
||||
|
||||
@@ -7,25 +7,26 @@ extern crate devices;
|
||||
extern crate pci;
|
||||
extern crate vm_allocator;
|
||||
|
||||
use crate::vec_with_array_field;
|
||||
use crate::vfio_device::VfioDevice;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use devices::BusDevice;
|
||||
use kvm_bindings::{
|
||||
kvm_irq_routing, kvm_irq_routing_entry, kvm_userspace_memory_region, KVM_IRQ_ROUTING_MSI,
|
||||
};
|
||||
use kvm_bindings::kvm_userspace_memory_region;
|
||||
use kvm_ioctls::*;
|
||||
use pci::{
|
||||
MsiCap, MsixCap, MsixConfig, PciBarConfiguration, PciBarRegionType, PciCapabilityID,
|
||||
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
||||
MSIX_TABLE_ENTRY_SIZE,
|
||||
msi_num_enabled_vectors, BarReprogrammingParams, MsiConfig, MsixCap, MsixConfig,
|
||||
PciBarConfiguration, PciBarRegionType, PciCapabilityID, PciClassCode, PciConfiguration,
|
||||
PciDevice, PciDeviceError, PciHeaderType, PciSubclass, MSIX_TABLE_ENTRY_SIZE,
|
||||
};
|
||||
use std::any::Any;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::ptr::null_mut;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, io};
|
||||
use std::{fmt, io, result};
|
||||
use vfio_bindings::bindings::vfio::*;
|
||||
use vm_allocator::SystemAllocator;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
|
||||
};
|
||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
@@ -33,10 +34,15 @@ use vmm_sys_util::eventfd::EventFd;
|
||||
pub enum VfioPciError {
|
||||
AllocateGsi,
|
||||
EventFd(io::Error),
|
||||
IrqFd(io::Error),
|
||||
InterruptSourceGroupCreate(io::Error),
|
||||
IrqFd(kvm_ioctls::Error),
|
||||
NewVfioPciDevice,
|
||||
MapRegionGuest(io::Error),
|
||||
SetGsiRouting(io::Error),
|
||||
MapRegionGuest(kvm_ioctls::Error),
|
||||
SetGsiRouting(kvm_ioctls::Error),
|
||||
MsiNotConfigured,
|
||||
MsixNotConfigured,
|
||||
UpdateMsiEventFd,
|
||||
UpdateMsixEventFd,
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, VfioPciError>;
|
||||
|
||||
@@ -45,12 +51,19 @@ impl fmt::Display for VfioPciError {
|
||||
match self {
|
||||
VfioPciError::AllocateGsi => write!(f, "failed to allocate GSI"),
|
||||
VfioPciError::EventFd(e) => write!(f, "failed to create eventfd: {}", e),
|
||||
VfioPciError::InterruptSourceGroupCreate(e) => {
|
||||
write!(f, "failed to create interrupt source group: {}", e)
|
||||
}
|
||||
VfioPciError::IrqFd(e) => write!(f, "failed to register irqfd: {}", e),
|
||||
VfioPciError::NewVfioPciDevice => write!(f, "failed to create VFIO PCI device"),
|
||||
VfioPciError::MapRegionGuest(e) => {
|
||||
write!(f, "failed to map VFIO PCI region into guest: {}", e)
|
||||
}
|
||||
VfioPciError::SetGsiRouting(e) => write!(f, "failed to set GSI routes for KVM: {}", e),
|
||||
VfioPciError::MsiNotConfigured => write!(f, "MSI interrupt not yet configured"),
|
||||
VfioPciError::MsixNotConfigured => write!(f, "MSI-X interrupt not yet configured"),
|
||||
VfioPciError::UpdateMsiEventFd => write!(f, "failed to update MSI eventfd"),
|
||||
VfioPciError::UpdateMsixEventFd => write!(f, "failed to update MSI-X eventfd"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,19 +86,19 @@ enum InterruptUpdateAction {
|
||||
DisableMsix,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct VfioMsi {
|
||||
cap: MsiCap,
|
||||
cfg: MsiConfig,
|
||||
cap_offset: u32,
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
impl VfioMsi {
|
||||
fn update(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> {
|
||||
let old_enabled = self.cap.enabled();
|
||||
let old_enabled = self.cfg.enabled();
|
||||
|
||||
self.cap.update(offset, data);
|
||||
self.cfg.update(offset, data);
|
||||
|
||||
let new_enabled = self.cap.enabled();
|
||||
let new_enabled = self.cfg.enabled();
|
||||
|
||||
if !old_enabled && new_enabled {
|
||||
return Some(InterruptUpdateAction::EnableMsi);
|
||||
@@ -103,18 +116,19 @@ struct VfioMsix {
|
||||
bar: MsixConfig,
|
||||
cap: MsixCap,
|
||||
cap_offset: u32,
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
impl VfioMsix {
|
||||
fn update(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> {
|
||||
let old_enabled = self.cap.enabled();
|
||||
let old_enabled = self.bar.enabled();
|
||||
|
||||
// Update "Message Control" word
|
||||
if offset == 2 && data.len() == 2 {
|
||||
self.cap.set_msg_ctl(LittleEndian::read_u16(data));
|
||||
self.bar.set_msg_ctl(LittleEndian::read_u16(data));
|
||||
}
|
||||
|
||||
let new_enabled = self.cap.enabled();
|
||||
let new_enabled = self.bar.enabled();
|
||||
|
||||
if !old_enabled && new_enabled {
|
||||
return Some(InterruptUpdateAction::EnableMsix);
|
||||
@@ -163,7 +177,7 @@ impl Interrupt {
|
||||
fn accessed(&self, offset: u64) -> Option<(PciCapabilityID, u64)> {
|
||||
if let Some(msi) = &self.msi {
|
||||
if offset >= u64::from(msi.cap_offset)
|
||||
&& offset < u64::from(msi.cap_offset) + msi.cap.size()
|
||||
&& offset < u64::from(msi.cap_offset) + msi.cfg.size()
|
||||
{
|
||||
return Some((
|
||||
PciCapabilityID::MessageSignalledInterrupts,
|
||||
@@ -181,22 +195,6 @@ impl Interrupt {
|
||||
None
|
||||
}
|
||||
|
||||
fn msix_enabled(&self) -> bool {
|
||||
if let Some(msix) = &self.msix {
|
||||
return msix.cap.enabled();
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn msix_function_masked(&self) -> bool {
|
||||
if let Some(msix) = &self.msix {
|
||||
return msix.cap.masked();
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn msix_table_accessed(&self, bar_index: u32, offset: u64) -> bool {
|
||||
if let Some(msix) = &self.msix {
|
||||
return msix.table_accessed(bar_index, offset);
|
||||
@@ -220,41 +218,15 @@ impl Interrupt {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MsiVector {
|
||||
msg_addr_lo: u32,
|
||||
msg_addr_hi: u32,
|
||||
msg_data: u32,
|
||||
masked: bool,
|
||||
}
|
||||
|
||||
struct InterruptRoute {
|
||||
gsi: u32,
|
||||
irq_fd: EventFd,
|
||||
msi_vector: MsiVector,
|
||||
}
|
||||
|
||||
impl InterruptRoute {
|
||||
fn new(vm: &Arc<VmFd>, allocator: &mut SystemAllocator, msi_vector: MsiVector) -> Result<Self> {
|
||||
let irq_fd = EventFd::new(libc::EFD_NONBLOCK).map_err(VfioPciError::EventFd)?;
|
||||
let gsi = allocator.allocate_gsi().ok_or(VfioPciError::AllocateGsi)?;
|
||||
|
||||
vm.register_irqfd(irq_fd.as_raw_fd(), gsi)
|
||||
.map_err(VfioPciError::IrqFd)?;
|
||||
|
||||
Ok(InterruptRoute {
|
||||
gsi,
|
||||
irq_fd,
|
||||
msi_vector,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct MmioRegion {
|
||||
start: GuestAddress,
|
||||
length: GuestUsize,
|
||||
type_: PciBarRegionType,
|
||||
index: u32,
|
||||
mem_slot: Option<u32>,
|
||||
host_addr: Option<u64>,
|
||||
mmap_size: Option<usize>,
|
||||
}
|
||||
|
||||
struct VfioPciConfig {
|
||||
@@ -310,15 +282,14 @@ pub struct VfioPciDevice {
|
||||
configuration: PciConfiguration,
|
||||
mmio_regions: Vec<MmioRegion>,
|
||||
interrupt: Interrupt,
|
||||
interrupt_routes: Vec<InterruptRoute>,
|
||||
}
|
||||
|
||||
impl VfioPciDevice {
|
||||
/// Constructs a new Vfio Pci device for the given Vfio device
|
||||
pub fn new(
|
||||
vm_fd: &Arc<VmFd>,
|
||||
allocator: &mut SystemAllocator,
|
||||
device: VfioDevice,
|
||||
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
) -> Result<Self> {
|
||||
let device = Arc::new(device);
|
||||
device.reset();
|
||||
@@ -347,71 +318,18 @@ impl VfioPciDevice {
|
||||
msi: None,
|
||||
msix: None,
|
||||
},
|
||||
interrupt_routes: Vec::new(),
|
||||
};
|
||||
|
||||
vfio_pci_device.parse_capabilities();
|
||||
|
||||
// Allocate temporary interrupt routes for now.
|
||||
// The MSI vectors will be filled when the guest driver programs the device.
|
||||
let max_interrupts = vfio_pci_device.device.max_interrupts();
|
||||
for _ in 0..max_interrupts {
|
||||
let msi_vector: MsiVector = Default::default();
|
||||
let route = InterruptRoute::new(vm_fd, allocator, msi_vector)?;
|
||||
vfio_pci_device.interrupt_routes.push(route);
|
||||
}
|
||||
vfio_pci_device.parse_capabilities(interrupt_manager);
|
||||
|
||||
Ok(vfio_pci_device)
|
||||
}
|
||||
|
||||
fn irq_fds(&self) -> Result<Vec<&EventFd>> {
|
||||
let mut irq_fds: Vec<&EventFd> = Vec::new();
|
||||
|
||||
for r in &self.interrupt_routes {
|
||||
irq_fds.push(&r.irq_fd);
|
||||
}
|
||||
|
||||
Ok(irq_fds)
|
||||
}
|
||||
|
||||
fn set_kvm_routes(&self) -> Result<()> {
|
||||
let mut entry_vec: Vec<kvm_irq_routing_entry> = Vec::new();
|
||||
for route in self.interrupt_routes.iter() {
|
||||
// Do not add masked vectors to the GSI mapping
|
||||
if route.msi_vector.masked {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut entry = kvm_irq_routing_entry {
|
||||
gsi: route.gsi,
|
||||
type_: KVM_IRQ_ROUTING_MSI,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
entry.u.msi.address_lo = route.msi_vector.msg_addr_lo;
|
||||
entry.u.msi.address_hi = route.msi_vector.msg_addr_hi;
|
||||
entry.u.msi.data = route.msi_vector.msg_data;
|
||||
|
||||
entry_vec.push(entry);
|
||||
}
|
||||
|
||||
let mut irq_routing =
|
||||
vec_with_array_field::<kvm_irq_routing, kvm_irq_routing_entry>(entry_vec.len());
|
||||
irq_routing[0].nr = entry_vec.len() as u32;
|
||||
irq_routing[0].flags = 0;
|
||||
|
||||
unsafe {
|
||||
let entries: &mut [kvm_irq_routing_entry] =
|
||||
irq_routing[0].entries.as_mut_slice(entry_vec.len());
|
||||
entries.copy_from_slice(&entry_vec);
|
||||
}
|
||||
|
||||
self.vm_fd
|
||||
.set_gsi_routing(&irq_routing[0])
|
||||
.map_err(VfioPciError::SetGsiRouting)
|
||||
}
|
||||
|
||||
fn parse_msix_capabilities(&mut self, cap: u8) {
|
||||
fn parse_msix_capabilities(
|
||||
&mut self,
|
||||
cap: u8,
|
||||
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
) {
|
||||
let msg_ctl = self
|
||||
.vfio_pci_configuration
|
||||
.read_config_word((cap + 2).into());
|
||||
@@ -429,30 +347,53 @@ impl VfioPciDevice {
|
||||
table,
|
||||
pba,
|
||||
};
|
||||
let msix_config = MsixConfig::new(msix_cap.table_size());
|
||||
|
||||
let interrupt_source_group = interrupt_manager
|
||||
.create_group(MsiIrqGroupConfig {
|
||||
base: 0,
|
||||
count: msix_cap.table_size() as InterruptIndex,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let msix_config = MsixConfig::new(msix_cap.table_size(), interrupt_source_group.clone());
|
||||
|
||||
self.interrupt.msix = Some(VfioMsix {
|
||||
bar: msix_config,
|
||||
cap: msix_cap,
|
||||
cap_offset: cap.into(),
|
||||
interrupt_source_group,
|
||||
});
|
||||
}
|
||||
|
||||
fn parse_msi_capabilities(&mut self, cap: u8) {
|
||||
fn parse_msi_capabilities(
|
||||
&mut self,
|
||||
cap: u8,
|
||||
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
) {
|
||||
let msg_ctl = self
|
||||
.vfio_pci_configuration
|
||||
.read_config_word((cap + 2).into());
|
||||
|
||||
let interrupt_source_group = interrupt_manager
|
||||
.create_group(MsiIrqGroupConfig {
|
||||
base: 0,
|
||||
count: msi_num_enabled_vectors(msg_ctl) as InterruptIndex,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let msi_config = MsiConfig::new(msg_ctl, interrupt_source_group.clone());
|
||||
|
||||
self.interrupt.msi = Some(VfioMsi {
|
||||
cap: MsiCap {
|
||||
msg_ctl,
|
||||
..Default::default()
|
||||
},
|
||||
cfg: msi_config,
|
||||
cap_offset: cap.into(),
|
||||
interrupt_source_group,
|
||||
});
|
||||
}
|
||||
|
||||
fn parse_capabilities(&mut self) {
|
||||
fn parse_capabilities(
|
||||
&mut self,
|
||||
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
) {
|
||||
let mut cap_next = self
|
||||
.vfio_pci_configuration
|
||||
.read_config_byte(PCI_CONFIG_CAPABILITY_OFFSET);
|
||||
@@ -464,10 +405,10 @@ impl VfioPciDevice {
|
||||
|
||||
match PciCapabilityID::from(cap_id) {
|
||||
PciCapabilityID::MessageSignalledInterrupts => {
|
||||
self.parse_msi_capabilities(cap_next);
|
||||
self.parse_msi_capabilities(cap_next, interrupt_manager);
|
||||
}
|
||||
PciCapabilityID::MSIX => {
|
||||
self.parse_msix_capabilities(cap_next);
|
||||
self.parse_msix_capabilities(cap_next, interrupt_manager);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
@@ -478,67 +419,26 @@ impl VfioPciDevice {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_msi_interrupt_routes(&mut self, msi: &VfioMsi) -> Result<()> {
|
||||
let num_vectors = msi.cap.num_enabled_vectors();
|
||||
for (idx, route) in self.interrupt_routes.iter_mut().enumerate() {
|
||||
// Mask the MSI vector if the amount of vectors supported by the
|
||||
// guest OS does not match the expected amount. This is related
|
||||
// to "Multiple Message Capable" and "Multiple Message Enable"
|
||||
// fields from the "Message Control" register.
|
||||
if idx >= num_vectors {
|
||||
route.msi_vector.masked = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
route.msi_vector.msg_addr_lo = msi.cap.msg_addr_lo;
|
||||
route.msi_vector.msg_addr_hi = msi.cap.msg_addr_hi;
|
||||
route.msi_vector.msg_data = u32::from(msi.cap.msg_data) | (idx as u32);
|
||||
route.msi_vector.masked = msi.cap.vector_masked(idx);
|
||||
}
|
||||
|
||||
// Check if we need to update KVM GSI mapping, based on the status of
|
||||
// the "MSI Enable" bit.
|
||||
if msi.cap.enabled() {
|
||||
return self.set_kvm_routes();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_msix_table(&mut self, offset: u64, data: &mut [u8]) {
|
||||
self.interrupt.msix_read_table(offset, data);
|
||||
}
|
||||
|
||||
fn update_msix_table(&mut self, offset: u64, data: &[u8]) -> Result<()> {
|
||||
self.interrupt.msix_write_table(offset, data);
|
||||
|
||||
if self.interrupt.msix_enabled() && !self.interrupt.msix_function_masked() {
|
||||
// Fill tables
|
||||
if let Some(msix) = &self.interrupt.msix {
|
||||
for (idx, entry) in msix.bar.table_entries.iter().enumerate() {
|
||||
self.interrupt_routes[idx].msi_vector.msg_addr_lo = entry.msg_addr_lo;
|
||||
self.interrupt_routes[idx].msi_vector.msg_addr_hi = entry.msg_addr_hi;
|
||||
self.interrupt_routes[idx].msi_vector.msg_data = entry.msg_data;
|
||||
self.interrupt_routes[idx].msi_vector.masked = entry.masked();
|
||||
}
|
||||
}
|
||||
|
||||
return self.set_kvm_routes();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_msi_capabilities(&mut self, offset: u64, data: &[u8]) -> Result<()> {
|
||||
match self.interrupt.update_msi(offset, data) {
|
||||
Some(InterruptUpdateAction::EnableMsi) => match self.irq_fds() {
|
||||
Ok(fds) => {
|
||||
if let Err(e) = self.device.enable_msi(fds) {
|
||||
Some(InterruptUpdateAction::EnableMsi) => {
|
||||
if let Some(msi) = &self.interrupt.msi {
|
||||
let mut irq_fds: Vec<&EventFd> = Vec::new();
|
||||
for i in 0..msi.cfg.num_enabled_vectors() {
|
||||
if let Some(eventfd) =
|
||||
msi.interrupt_source_group.notifier(i as InterruptIndex)
|
||||
{
|
||||
irq_fds.push(eventfd);
|
||||
} else {
|
||||
return Err(VfioPciError::UpdateMsiEventFd);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = self.device.enable_msi(irq_fds) {
|
||||
warn!("Could not enable MSI: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => warn!("Could not get IRQ fds: {}", e),
|
||||
},
|
||||
}
|
||||
Some(InterruptUpdateAction::DisableMsi) => {
|
||||
if let Err(e) = self.device.disable_msi() {
|
||||
warn!("Could not disable MSI: {}", e);
|
||||
@@ -547,34 +447,38 @@ impl VfioPciDevice {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Update the interrupt_routes table now that the MSI cache has been
|
||||
// updated. The point is to always update the table based on latest
|
||||
// changes to the cache, and based on the state of masking flags, the
|
||||
// KVM GSI routes should be configured.
|
||||
if let Some(msi) = self.interrupt.msi {
|
||||
return self.update_msi_interrupt_routes(&msi);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_msix_capabilities(&mut self, offset: u64, data: &[u8]) {
|
||||
fn update_msix_capabilities(&mut self, offset: u64, data: &[u8]) -> Result<()> {
|
||||
match self.interrupt.update_msix(offset, data) {
|
||||
Some(InterruptUpdateAction::EnableMsix) => match self.irq_fds() {
|
||||
Ok(fds) => {
|
||||
if let Err(e) = self.device.enable_msix(fds) {
|
||||
Some(InterruptUpdateAction::EnableMsix) => {
|
||||
if let Some(msix) = &self.interrupt.msix {
|
||||
let mut irq_fds: Vec<&EventFd> = Vec::new();
|
||||
for i in 0..msix.bar.table_entries.len() {
|
||||
if let Some(eventfd) =
|
||||
msix.interrupt_source_group.notifier(i as InterruptIndex)
|
||||
{
|
||||
irq_fds.push(eventfd);
|
||||
} else {
|
||||
return Err(VfioPciError::UpdateMsiEventFd);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = self.device.enable_msix(irq_fds) {
|
||||
warn!("Could not enable MSI-X: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => warn!("Could not get IRQ fds: {}", e),
|
||||
},
|
||||
}
|
||||
Some(InterruptUpdateAction::DisableMsix) => {
|
||||
if let Err(e) = self.device.disable_msix() {
|
||||
warn!("Could not disable MSI: {}", e);
|
||||
warn!("Could not disable MSI-X: {}", e);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_region(&self, addr: u64) -> Option<MmioRegion> {
|
||||
@@ -599,11 +503,13 @@ impl VfioPciDevice {
|
||||
/// # Return value
|
||||
///
|
||||
/// This function returns the updated KVM memory slot id.
|
||||
pub fn map_mmio_regions(&mut self, vm: &Arc<VmFd>, mem_slot: u32) -> Result<u32> {
|
||||
pub fn map_mmio_regions<F>(&mut self, vm: &Arc<VmFd>, mem_slot: F) -> Result<()>
|
||||
where
|
||||
F: Fn() -> u32,
|
||||
{
|
||||
let fd = self.device.as_raw_fd();
|
||||
let mut new_mem_slot = mem_slot;
|
||||
|
||||
for region in self.mmio_regions.iter() {
|
||||
for region in self.mmio_regions.iter_mut() {
|
||||
// We want to skip the mapping of the BAR containing the MSI-X
|
||||
// table even if it is mappable. The reason is we need to trap
|
||||
// any access to the MSI-X table and update the GSI routing
|
||||
@@ -645,8 +551,9 @@ impl VfioPciDevice {
|
||||
continue;
|
||||
}
|
||||
|
||||
let slot = mem_slot();
|
||||
let mem_region = kvm_userspace_memory_region {
|
||||
slot: new_mem_slot as u32,
|
||||
slot,
|
||||
guest_phys_addr: region.start.raw_value() + mmap_offset,
|
||||
memory_size: mmap_size as u64,
|
||||
userspace_addr: host_addr as u64,
|
||||
@@ -658,22 +565,67 @@ impl VfioPciDevice {
|
||||
vm.set_user_memory_region(mem_region)
|
||||
.map_err(VfioPciError::MapRegionGuest)?;
|
||||
}
|
||||
new_mem_slot += 1;
|
||||
|
||||
// Update the region with memory mapped info.
|
||||
region.mem_slot = Some(slot);
|
||||
region.host_addr = Some(host_addr as u64);
|
||||
region.mmap_size = Some(mmap_size as usize);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(new_mem_slot)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unmap_mmio_regions(&mut self) {
|
||||
for region in self.mmio_regions.iter() {
|
||||
if let (Some(host_addr), Some(mmap_size), Some(mem_slot)) =
|
||||
(region.host_addr, region.mmap_size, region.mem_slot)
|
||||
{
|
||||
let (mmap_offset, _) = self.device.get_region_mmap(region.index);
|
||||
|
||||
// Remove region from KVM
|
||||
let kvm_region = kvm_userspace_memory_region {
|
||||
slot: mem_slot,
|
||||
guest_phys_addr: region.start.raw_value() + mmap_offset,
|
||||
memory_size: 0,
|
||||
userspace_addr: host_addr,
|
||||
flags: 0,
|
||||
};
|
||||
// Safe because the guest regions are guaranteed not to overlap.
|
||||
if let Err(e) = unsafe { self.vm_fd.set_user_memory_region(kvm_region) } {
|
||||
error!(
|
||||
"Could not remove the userspace memory region from KVM: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
let ret = unsafe { libc::munmap(host_addr as *mut libc::c_void, mmap_size) };
|
||||
if ret != 0 {
|
||||
error!(
|
||||
"Could not unmap region {}, error:{}",
|
||||
region.index,
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VfioPciDevice {
|
||||
fn drop(&mut self) {
|
||||
if self.interrupt.msi.is_some() && self.device.disable_msi().is_err() {
|
||||
error!("Could not disable MSI");
|
||||
self.unmap_mmio_regions();
|
||||
|
||||
if let Some(msix) = &self.interrupt.msix {
|
||||
if msix.bar.enabled() && self.device.disable_msix().is_err() {
|
||||
error!("Could not disable MSI-X");
|
||||
}
|
||||
}
|
||||
|
||||
if self.interrupt.msix.is_some() && self.device.disable_msix().is_err() {
|
||||
error!("Could not disable MSI-X");
|
||||
if let Some(msi) = &self.interrupt.msi {
|
||||
if msi.cfg.enabled() && self.device.disable_msi().is_err() {
|
||||
error!("Could not disable MSI");
|
||||
}
|
||||
}
|
||||
|
||||
if self.device.unset_dma_map().is_err() {
|
||||
@@ -800,7 +752,7 @@ impl PciDevice for VfioPciDevice {
|
||||
let msb_bar_offset: u32 = PCI_CONFIG_BAR_OFFSET + (bar_id + 1) * 4;
|
||||
|
||||
self.vfio_pci_configuration
|
||||
.write_config_dword(msb_bar_offset, msb_size);
|
||||
.write_config_dword(msb_size, msb_bar_offset);
|
||||
|
||||
msb_size = self
|
||||
.vfio_pci_configuration
|
||||
@@ -869,7 +821,11 @@ impl PciDevice for VfioPciDevice {
|
||||
self.mmio_regions.push(MmioRegion {
|
||||
start: bar_addr,
|
||||
length: region_size,
|
||||
type_: region_type,
|
||||
index: bar_id as u32,
|
||||
mem_slot: None,
|
||||
host_addr: None,
|
||||
mmap_size: None,
|
||||
});
|
||||
|
||||
bar_id += 1;
|
||||
@@ -885,6 +841,26 @@ impl PciDevice for VfioPciDevice {
|
||||
Ok(ranges)
|
||||
}
|
||||
|
||||
fn free_bars(
|
||||
&mut self,
|
||||
allocator: &mut SystemAllocator,
|
||||
) -> std::result::Result<(), PciDeviceError> {
|
||||
for region in self.mmio_regions.iter() {
|
||||
match region.type_ {
|
||||
PciBarRegionType::IORegion => {
|
||||
allocator.free_io_addresses(region.start, region.length);
|
||||
}
|
||||
PciBarRegionType::Memory32BitRegion => {
|
||||
allocator.free_mmio_hole_addresses(region.start, region.length);
|
||||
}
|
||||
PciBarRegionType::Memory64BitRegion => {
|
||||
allocator.free_mmio_addresses(region.start, region.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
|
||||
// When the guest wants to write to a BAR, we trap it into
|
||||
// our local configuration space. We're not reprogramming
|
||||
@@ -901,8 +877,6 @@ impl PciDevice for VfioPciDevice {
|
||||
}
|
||||
|
||||
let reg = (reg_idx * PCI_CONFIG_REGISTER_SIZE) as u64;
|
||||
self.device
|
||||
.region_write(VFIO_PCI_CONFIG_REGION_INDEX, data, reg + offset);
|
||||
|
||||
// If the MSI or MSI-X capabilities are accessed, we need to
|
||||
// update our local cache accordingly.
|
||||
@@ -917,14 +891,27 @@ impl PciDevice for VfioPciDevice {
|
||||
}
|
||||
}
|
||||
PciCapabilityID::MSIX => {
|
||||
self.update_msix_capabilities(cap_offset, data);
|
||||
if let Err(e) = self.update_msix_capabilities(cap_offset, data) {
|
||||
error!("Could not update MSI-X capabilities: {}", e);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to write to the device's PCI config space after MSI/MSI-X
|
||||
// interrupts have been enabled/disabled. In case of MSI, when the
|
||||
// interrupts are enabled through VFIO (using VFIO_DEVICE_SET_IRQS),
|
||||
// the MSI Enable bit in the MSI capability structure found in the PCI
|
||||
// config space is disabled by default. That's why when the guest is
|
||||
// enabling this bit, we first need to enable the MSI interrupts with
|
||||
// VFIO through VFIO_DEVICE_SET_IRQS ioctl, and only after we can write
|
||||
// to the device region to update the MSI Enable bit.
|
||||
self.device
|
||||
.region_write(VFIO_PCI_CONFIG_REGION_INDEX, data, reg + offset);
|
||||
}
|
||||
|
||||
fn read_config_register(&self, reg_idx: usize) -> u32 {
|
||||
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||
// When reading the BARs, we trap it and return what comes
|
||||
// from our local configuration space. We want the guest to
|
||||
// use that and not the VFIO device BARs as it does not map
|
||||
@@ -958,13 +945,21 @@ impl PciDevice for VfioPciDevice {
|
||||
& mask
|
||||
}
|
||||
|
||||
fn detect_bar_reprogramming(
|
||||
&mut self,
|
||||
reg_idx: usize,
|
||||
data: &[u8],
|
||||
) -> Option<BarReprogrammingParams> {
|
||||
self.configuration.detect_bar_reprogramming(reg_idx, data)
|
||||
}
|
||||
|
||||
fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
||||
let addr = base + offset;
|
||||
if let Some(region) = self.find_region(addr) {
|
||||
let offset = addr - region.start.raw_value();
|
||||
|
||||
if self.interrupt.msix_table_accessed(region.index, offset) {
|
||||
self.read_msix_table(offset, data);
|
||||
self.interrupt.msix_read_table(offset, data);
|
||||
} else {
|
||||
self.device.region_read(region.index, data, offset);
|
||||
}
|
||||
@@ -978,12 +973,60 @@ impl PciDevice for VfioPciDevice {
|
||||
|
||||
// If the MSI-X table is written to, we need to update our cache.
|
||||
if self.interrupt.msix_table_accessed(region.index, offset) {
|
||||
if let Err(e) = self.update_msix_table(offset, data) {
|
||||
error!("Could not update MSI-X table: {}", e);
|
||||
}
|
||||
self.interrupt.msix_write_table(offset, data);
|
||||
} else {
|
||||
self.device.region_write(region.index, data, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_bar(&mut self, old_base: u64, new_base: u64) -> result::Result<(), io::Error> {
|
||||
for region in self.mmio_regions.iter_mut() {
|
||||
if region.start.raw_value() == old_base {
|
||||
region.start = GuestAddress(new_base);
|
||||
|
||||
if let Some(mem_slot) = region.mem_slot {
|
||||
if let Some(host_addr) = region.host_addr {
|
||||
let (mmap_offset, mmap_size) = self.device.get_region_mmap(region.index);
|
||||
|
||||
// Remove old region from KVM
|
||||
let old_mem_region = kvm_userspace_memory_region {
|
||||
slot: mem_slot,
|
||||
guest_phys_addr: old_base + mmap_offset,
|
||||
memory_size: 0,
|
||||
userspace_addr: host_addr,
|
||||
flags: 0,
|
||||
};
|
||||
// Safe because the guest regions are guaranteed not to overlap.
|
||||
unsafe {
|
||||
self.vm_fd
|
||||
.set_user_memory_region(old_mem_region)
|
||||
.map_err(|e| io::Error::from_raw_os_error(e.errno()))?;
|
||||
}
|
||||
|
||||
// Insert new region to KVM
|
||||
let new_mem_region = kvm_userspace_memory_region {
|
||||
slot: mem_slot,
|
||||
guest_phys_addr: new_base + mmap_offset,
|
||||
memory_size: mmap_size as u64,
|
||||
userspace_addr: host_addr,
|
||||
flags: 0,
|
||||
};
|
||||
// Safe because the guest regions are guaranteed not to overlap.
|
||||
unsafe {
|
||||
self.vm_fd
|
||||
.set_user_memory_region(new_mem_region)
|
||||
.map_err(|e| io::Error::from_raw_os_error(e.errno()))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
[package]
|
||||
name = "vhost_rs"
|
||||
version = "0.1.0"
|
||||
authors = ["Liu Jiang <gerry@linux.alibaba.com>"]
|
||||
repository = "https://github.com/rust-vmm/vhost"
|
||||
license = "Apache-2.0 or BSD-3-Clause"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
vhost-vsock = []
|
||||
vhost-kern = ["vm-memory"]
|
||||
vhost-user-master = []
|
||||
vhost-user-slave = []
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.1.0"
|
||||
libc = "0.2.60"
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
202
vhost_rs/LICENSE
202
vhost_rs/LICENSE
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,24 +0,0 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# vHost
|
||||
A crate to support vhost backend drivers for virtio devices.
|
||||
|
||||
## Kernel-based vHost Backend Drivers
|
||||
The vhost drivers in Linux provide in-kernel virtio device emulation. Normally the hypervisor userspace process emulates I/O accesses from the guest. Vhost puts virtio emulation code into the kernel, taking hypervisor userspace out of the picture. This allows device emulation code to directly call into kernel subsystems instead of performing system calls from userspace. The hypervisor relies on ioctl based interfaces to control those in-kernel vhost drivers, such as vhost-net, vhost-scsi and vhost-vsock etc.
|
||||
|
||||
## vHost-user Backend Drivers
|
||||
The vhost-user protocol is aiming to implement vhost backend drivers in userspace, which complements the ioctl interface used to control the vhost implementation in the Linux kernel. It implements the control plane needed to establish virtqueue sharing with a user space process on the same host. It uses communication over a Unix domain socket to share file descriptors in the ancillary data of the message.
|
||||
|
||||
The protocol defines two sides of the communication, master and slave. Master is the application that shares its virtqueues, slave is the consumer of the virtqueues. Master and slave can be either a client (i.e. connecting) or server (listening) in the socket communication.
|
||||
@@ -1,130 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD file.
|
||||
|
||||
//! Common traits and structs for vhost-kern and vhost-user backend drivers.
|
||||
|
||||
use super::Result;
|
||||
use std::os::unix::io::RawFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// Maximum number of memory regions supported.
|
||||
pub const VHOST_MAX_MEMORY_REGIONS: usize = 255;
|
||||
|
||||
/// Vring/virtque configuration data.
|
||||
pub struct VringConfigData {
|
||||
/// Maximum queue size supported by the driver.
|
||||
pub queue_max_size: u16,
|
||||
/// Actual queue size negotiated by the driver.
|
||||
pub queue_size: u16,
|
||||
/// Bitmask of vring flags.
|
||||
pub flags: u32,
|
||||
/// Descriptor table address.
|
||||
pub desc_table_addr: u64,
|
||||
/// Used ring buffer address.
|
||||
pub used_ring_addr: u64,
|
||||
/// Available ring buffer address.
|
||||
pub avail_ring_addr: u64,
|
||||
/// Optional address for logging.
|
||||
pub log_addr: Option<u64>,
|
||||
}
|
||||
|
||||
/// Memory region configuration data.
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct VhostUserMemoryRegionInfo {
|
||||
/// Guest physical address of the memory region.
|
||||
pub guest_phys_addr: u64,
|
||||
/// Size of the memory region.
|
||||
pub memory_size: u64,
|
||||
/// Virtual address in the current process.
|
||||
pub userspace_addr: u64,
|
||||
/// Optional offset where region starts in the mapped memory.
|
||||
pub mmap_offset: u64,
|
||||
/// Optional file diescriptor for mmap
|
||||
pub mmap_handle: RawFd,
|
||||
}
|
||||
|
||||
/// An interface for setting up vhost-based backend drivers.
|
||||
///
|
||||
/// Vhost-based virtio devices are different from regular virtio devices because the the vhost
|
||||
/// backend takes care of handling all the data transfer. The device itself only needs to deal with
|
||||
/// setting up the the backend driver and managing the control channel.
|
||||
pub trait VhostBackend: std::marker::Sized {
|
||||
/// Get a bitmask of supported virtio/vhost features.
|
||||
fn get_features(&mut self) -> Result<u64>;
|
||||
|
||||
/// Inform the vhost subsystem which features to enable.
|
||||
/// This should be a subset of supported features from get_features().
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `features` - Bitmask of features to set.
|
||||
fn set_features(&mut self, features: u64) -> Result<()>;
|
||||
|
||||
/// Set the current process as the owner of the vhost backend.
|
||||
/// This must be run before any other vhost commands.
|
||||
fn set_owner(&mut self) -> Result<()>;
|
||||
|
||||
/// Used to be sent to request disabling all rings
|
||||
/// This is no longer used.
|
||||
fn reset_owner(&mut self) -> Result<()>;
|
||||
|
||||
/// Set the guest memory mappings for vhost to use.
|
||||
fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>;
|
||||
|
||||
/// Set base address for page modification logging.
|
||||
fn set_log_base(&mut self, base: u64, fd: Option<RawFd>) -> Result<()>;
|
||||
|
||||
/// Specify an eventfd file descriptor to signal on log write.
|
||||
fn set_log_fd(&mut self, fd: RawFd) -> Result<()>;
|
||||
|
||||
/// Set the number of descriptors in the vring.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to set descriptor count for.
|
||||
/// * `num` - Number of descriptors in the queue.
|
||||
fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()>;
|
||||
|
||||
/// Set the addresses for a given vring.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to set addresses for.
|
||||
/// * `config_data` - Configuration data for a vring.
|
||||
fn set_vring_addr(&mut self, queue_index: usize, config_data: &VringConfigData) -> Result<()>;
|
||||
|
||||
/// Set the first index to look for available descriptors.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `num` - Index where available descriptors start.
|
||||
fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()>;
|
||||
|
||||
/// Get the available vring base offset.
|
||||
fn get_vring_base(&mut self, queue_index: usize) -> Result<u32>;
|
||||
|
||||
/// Set the eventfd to trigger when buffers have been used by the host.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd to trigger.
|
||||
fn set_vring_call(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;
|
||||
|
||||
/// Set the eventfd that will be signaled by the guest when buffers are
|
||||
/// available for the host to process.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd that will be signaled from guest.
|
||||
fn set_vring_kick(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;
|
||||
|
||||
/// Set the eventfd that will be signaled by the guest when error happens.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd that will be signaled from guest.
|
||||
fn set_vring_err(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD file.
|
||||
|
||||
//! Virtio Vhost Backend Drivers
|
||||
//!
|
||||
//! Virtio devices use virtqueues to transport data efficiently. Virtqueue is a set of three
|
||||
//! different single-producer, single-consumer ring structures designed to store generic
|
||||
//! scatter-gather I/O.
|
||||
//!
|
||||
//! Vhost is a mechanism to improve performance of Virtio devices by delegate data plane operations
|
||||
//! to dedicated IO service processes. Only the configuration, I/O submission notification, and I/O
|
||||
//! completion interruption are piped through the hypervisor.
|
||||
//! It uses the same virtqueue layout as Virtio to allow Vhost devices to be mapped directly to
|
||||
//! Virtio devices. This allows a Vhost device to be accessed directly by a guest OS inside a
|
||||
//! hypervisor process with an existing Virtio (PCI) driver.
|
||||
//!
|
||||
//! The initial vhost implementation is a part of the Linux kernel and uses ioctl interface to
|
||||
//! communicate with userspace applications. Dedicated kernel worker threads are created to handle
|
||||
//! IO requests from the guest.
|
||||
//!
|
||||
//! Later Vhost-user protocol is introduced to complement the ioctl interface used to control the
|
||||
//! vhost implementation in the Linux kernel. It implements the control plane needed to establish
|
||||
//! virtqueues sharing with a user space process on the same host. It uses communication over a
|
||||
//! Unix domain socket to share file descriptors in the ancillary data of the message.
|
||||
//! The protocol defines 2 sides of the communication, master and slave. Master is the application
|
||||
//! that shares its virtqueues. Slave is the consumer of the virtqueues. Master and slave can be
|
||||
//! either a client (i.e. connecting) or server (listening) in the socket communication.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[cfg_attr(
|
||||
any(feature = "vhost-user-master", feature = "vhost-user-slave"),
|
||||
macro_use
|
||||
)]
|
||||
extern crate bitflags;
|
||||
extern crate libc;
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
extern crate vm_memory;
|
||||
#[cfg_attr(feature = "vhost-kern", macro_use)]
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
mod backend;
|
||||
pub use backend::*;
|
||||
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
pub mod vhost_kern;
|
||||
#[cfg(any(feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
pub mod vhost_user;
|
||||
#[cfg(feature = "vhost-vsock")]
|
||||
pub mod vsock;
|
||||
|
||||
/// Error codes for vhost operations
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid operations.
|
||||
InvalidOperation,
|
||||
/// Invalid guest memory.
|
||||
InvalidGuestMemory,
|
||||
/// Invalid guest memory region.
|
||||
InvalidGuestMemoryRegion,
|
||||
/// Invalid queue.
|
||||
InvalidQueue,
|
||||
/// Invalid descriptor table address.
|
||||
DescriptorTableAddress,
|
||||
/// Invalid used address.
|
||||
UsedAddress,
|
||||
/// Invalid available address.
|
||||
AvailAddress,
|
||||
/// Invalid log address.
|
||||
LogAddress,
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
/// Error opening the vhost backend driver.
|
||||
VhostOpen(std::io::Error),
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
/// Error while running ioctl.
|
||||
IoctlError(std::io::Error),
|
||||
/// Error from IO subsystem.
|
||||
IOError(std::io::Error),
|
||||
#[cfg(any(feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
/// Error from the vhost-user subsystem.
|
||||
VhostUserProtocol(vhost_user::Error),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::InvalidOperation => write!(f, "invalid vhost operations"),
|
||||
Error::InvalidGuestMemory => write!(f, "invalid guest memory object"),
|
||||
Error::InvalidGuestMemoryRegion => write!(f, "invalid guest memory region"),
|
||||
Error::InvalidQueue => write!(f, "invalid virtque"),
|
||||
Error::DescriptorTableAddress => write!(f, "invalid virtque descriptor talbe address"),
|
||||
Error::UsedAddress => write!(f, "invalid virtque used talbe address"),
|
||||
Error::AvailAddress => write!(f, "invalid virtque available talbe address"),
|
||||
Error::LogAddress => write!(f, "invalid virtque log address"),
|
||||
Error::IOError(e) => write!(f, "IO error: {}", e),
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
Error::VhostOpen(e) => write!(f, "failure in opening vhost file: {}", e),
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
Error::IoctlError(e) => write!(f, "failure in vhost ioctl: {}", e),
|
||||
#[cfg(any(feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
Error::VhostUserProtocol(e) => write!(f, "vhost-user: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
impl std::convert::From<vhost_user::Error> for Error {
|
||||
fn from(err: vhost_user::Error) -> Self {
|
||||
Error::VhostUserProtocol(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of vhost operations
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -1,320 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD file.
|
||||
|
||||
//! Traits and structs to control Linux in-kernel vhost drivers.
|
||||
//!
|
||||
//! The initial vhost implementation is a part of the Linux kernel and uses ioctl interface to
|
||||
//! communicate with userspace applications. This sub module provides ioctl based interfaces to
|
||||
//! control the in-kernel net, scsi, vsock vhost drivers.
|
||||
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::ptr::null;
|
||||
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestUsize};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref};
|
||||
|
||||
use super::{
|
||||
Error, Result, VhostBackend, VhostUserMemoryRegionInfo, VringConfigData,
|
||||
VHOST_MAX_MEMORY_REGIONS,
|
||||
};
|
||||
|
||||
pub mod vhost_binding;
|
||||
use self::vhost_binding::*;
|
||||
|
||||
#[cfg(feature = "vhost-vsock")]
|
||||
pub mod vsock;
|
||||
|
||||
#[inline]
|
||||
fn ioctl_result<T>(rc: i32, res: T) -> Result<T> {
|
||||
if rc < 0 {
|
||||
Err(Error::IoctlError(std::io::Error::last_os_error()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
fn guest_addr(addr: u64) -> GuestAddress {
|
||||
GuestAddress::new(addr)
|
||||
}
|
||||
|
||||
/// Represent an in-kernel vhost device backend.
|
||||
pub trait VhostKernBackend<'a>: AsRawFd {
|
||||
/// Associated type to access the guest's memory.
|
||||
type M: GuestMemory<'a>;
|
||||
|
||||
/// Get the object to access the guest's memory.
|
||||
fn mem(&self) -> &Self::M;
|
||||
|
||||
/// Check whether the ring configuration is valid.
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
#[allow(clippy::needless_bool)]
|
||||
fn is_valid(
|
||||
&self,
|
||||
queue_max_size: u16,
|
||||
queue_size: u16,
|
||||
desc_addr: GuestAddress,
|
||||
avail_addr: GuestAddress,
|
||||
used_addr: GuestAddress,
|
||||
) -> bool {
|
||||
let desc_table_size = 16 * u64::from(queue_size) as GuestUsize;
|
||||
let avail_ring_size = 6 + 2 * u64::from(queue_size) as GuestUsize;
|
||||
let used_ring_size = 6 + 8 * u64::from(queue_size) as GuestUsize;
|
||||
if queue_size > queue_max_size || queue_size == 0 || (queue_size & (queue_size - 1)) != 0 {
|
||||
false
|
||||
} else if desc_addr
|
||||
.checked_add(desc_table_size)
|
||||
.map_or(true, |v| !self.mem().address_in_range(v))
|
||||
{
|
||||
false
|
||||
} else if avail_addr
|
||||
.checked_add(avail_ring_size)
|
||||
.map_or(true, |v| !self.mem().address_in_range(v))
|
||||
{
|
||||
false
|
||||
} else if used_addr
|
||||
.checked_add(used_ring_size)
|
||||
.map_or(true, |v| !self.mem().address_in_range(v))
|
||||
{
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: VhostKernBackend<'a>> VhostBackend for T {
|
||||
/// Set the current process as the owner of this file descriptor.
|
||||
/// This must be run before any other vhost ioctls.
|
||||
fn set_owner(&mut self) -> Result<()> {
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl(self, VHOST_SET_OWNER()) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
fn reset_owner(&mut self) -> Result<()> {
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl(self, VHOST_RESET_OWNER()) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Get a bitmask of supported virtio/vhost features.
|
||||
fn get_features(&mut self) -> Result<u64> {
|
||||
let mut avail_features: u64 = 0;
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_mut_ref(self, VHOST_GET_FEATURES(), &mut avail_features) };
|
||||
ioctl_result(ret, avail_features)
|
||||
}
|
||||
|
||||
/// Inform the vhost subsystem which features to enable. This should be a subset of
|
||||
/// supported features from VHOST_GET_FEATURES.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `features` - Bitmask of features to set.
|
||||
fn set_features(&mut self, features: u64) -> Result<()> {
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_FEATURES(), &features) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the guest memory mappings for vhost to use.
|
||||
fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
|
||||
if regions.is_empty() || regions.len() > VHOST_MAX_MEMORY_REGIONS {
|
||||
return Err(Error::InvalidGuestMemory);
|
||||
}
|
||||
|
||||
let mut vhost_memory = VhostMemory::new(regions.len() as u16);
|
||||
for (index, region) in regions.iter().enumerate() {
|
||||
vhost_memory.set_region(
|
||||
index as u32,
|
||||
&vhost_memory_region {
|
||||
guest_phys_addr: region.guest_phys_addr,
|
||||
memory_size: region.memory_size,
|
||||
userspace_addr: region.userspace_addr,
|
||||
flags_padding: 0u64,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
// This ioctl is called with a pointer that is valid for the lifetime
|
||||
// of this function. The kernel will make its own copy of the memory
|
||||
// tables. As always, check the return value.
|
||||
let ret = unsafe { ioctl_with_ptr(self, VHOST_SET_MEM_TABLE(), vhost_memory.as_ptr()) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set base address for page modification logging.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base` - Base address for page modification logging.
|
||||
fn set_log_base(&mut self, base: u64, fd: Option<RawFd>) -> Result<()> {
|
||||
if fd.is_some() {
|
||||
return Err(Error::LogAddress);
|
||||
}
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_LOG_BASE(), &base) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Specify an eventfd file descriptor to signal on log write.
|
||||
fn set_log_fd(&mut self, fd: RawFd) -> Result<()> {
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let val: i32 = fd;
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_LOG_FD(), &val) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the number of descriptors in the vring.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to set descriptor count for.
|
||||
/// * `num` - Number of descriptors in the queue.
|
||||
fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()> {
|
||||
let vring_state = vhost_vring_state {
|
||||
index: queue_index as u32,
|
||||
num: u32::from(num),
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_NUM(), &vring_state) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the addresses for a given vring.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_max_size` - Maximum queue size supported by the device.
|
||||
/// * `queue_size` - Actual queue size negotiated by the driver.
|
||||
/// * `queue_index` - Index of the queue to set addresses for.
|
||||
/// * `flags` - Bitmask of vring flags.
|
||||
/// * `desc_table_addr` - Descriptor table address.
|
||||
/// * `used_ring_addr` - Used ring buffer address.
|
||||
/// * `avail_ring_addr` - Available ring buffer address.
|
||||
/// * `log_addr` - Optional address for logging.
|
||||
fn set_vring_addr(&mut self, queue_index: usize, config_data: &VringConfigData) -> Result<()> {
|
||||
if !self.is_valid(
|
||||
config_data.queue_max_size,
|
||||
config_data.queue_size,
|
||||
guest_addr(config_data.desc_table_addr),
|
||||
guest_addr(config_data.used_ring_addr),
|
||||
guest_addr(config_data.avail_ring_addr),
|
||||
) {
|
||||
return Err(Error::InvalidQueue);
|
||||
}
|
||||
|
||||
let desc_addr = self
|
||||
.mem()
|
||||
.get_host_address(guest_addr(config_data.desc_table_addr))
|
||||
.ok_or(Error::DescriptorTableAddress)?;
|
||||
let used_addr = self
|
||||
.mem()
|
||||
.get_host_address(guest_addr(config_data.used_ring_addr))
|
||||
.ok_or(Error::UsedAddress)?;
|
||||
let avail_addr = self
|
||||
.mem()
|
||||
.get_host_address(guest_addr(config_data.avail_ring_addr))
|
||||
.ok_or(Error::AvailAddress)?;
|
||||
let log_addr = match config_data.log_addr {
|
||||
None => null(),
|
||||
Some(a) => self
|
||||
.mem()
|
||||
.get_host_address(guest_addr(a))
|
||||
.ok_or(Error::LogAddress)?,
|
||||
};
|
||||
|
||||
let vring_addr = vhost_vring_addr {
|
||||
index: queue_index as u32,
|
||||
flags: config_data.flags,
|
||||
desc_user_addr: desc_addr as u64,
|
||||
used_user_addr: used_addr as u64,
|
||||
avail_user_addr: avail_addr as u64,
|
||||
log_guest_addr: log_addr as u64,
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its
|
||||
// return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_ADDR(), &vring_addr) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the first index to look for available descriptors.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `num` - Index where available descriptors start.
|
||||
fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()> {
|
||||
let vring_state = vhost_vring_state {
|
||||
index: queue_index as u32,
|
||||
num: u32::from(base),
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_BASE(), &vring_state) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Get a bitmask of supported virtio/vhost features.
|
||||
fn get_vring_base(&mut self, queue_index: usize) -> Result<u32> {
|
||||
let vring_state = vhost_vring_state {
|
||||
index: queue_index as u32,
|
||||
num: 0,
|
||||
};
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_GET_VRING_BASE(), &vring_state) };
|
||||
ioctl_result(ret, vring_state.num)
|
||||
}
|
||||
|
||||
/// Set the eventfd to trigger when buffers have been used by the host.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd to trigger.
|
||||
fn set_vring_call(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let vring_file = vhost_vring_file {
|
||||
index: queue_index as u32,
|
||||
fd: fd.as_raw_fd(),
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_CALL(), &vring_file) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the eventfd that will be signaled by the guest when buffers are
|
||||
/// available for the host to process.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd that will be signaled from guest.
|
||||
fn set_vring_kick(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let vring_file = vhost_vring_file {
|
||||
index: queue_index as u32,
|
||||
fd: fd.as_raw_fd(),
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_KICK(), &vring_file) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the eventfd to signal an error from the vhost backend.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd that will be signaled from the backend.
|
||||
fn set_vring_err(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let vring_file = vhost_vring_file {
|
||||
index: queue_index as u32,
|
||||
fd: fd.as_raw_fd(),
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_ERR(), &vring_file) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD file.
|
||||
|
||||
/* Auto-generated by bindgen then manually edited for simplicity */
|
||||
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::os::raw;
|
||||
use {Error, Result};
|
||||
|
||||
pub const VHOST: raw::c_uint = 0xaf;
|
||||
pub const VHOST_VRING_F_LOG: raw::c_uint = 0;
|
||||
pub const VHOST_ACCESS_RO: raw::c_uint = 1;
|
||||
pub const VHOST_ACCESS_WO: raw::c_uint = 2;
|
||||
pub const VHOST_ACCESS_RW: raw::c_uint = 3;
|
||||
pub const VHOST_IOTLB_MISS: raw::c_uint = 1;
|
||||
pub const VHOST_IOTLB_UPDATE: raw::c_uint = 2;
|
||||
pub const VHOST_IOTLB_INVALIDATE: raw::c_uint = 3;
|
||||
pub const VHOST_IOTLB_ACCESS_FAIL: raw::c_uint = 4;
|
||||
pub const VHOST_IOTLB_MSG: raw::c_uint = 1;
|
||||
pub const VHOST_PAGE_SIZE: raw::c_uint = 4096;
|
||||
pub const VHOST_VIRTIO: raw::c_uint = 175;
|
||||
pub const VHOST_VRING_LITTLE_ENDIAN: raw::c_uint = 0;
|
||||
pub const VHOST_VRING_BIG_ENDIAN: raw::c_uint = 1;
|
||||
pub const VHOST_F_LOG_ALL: raw::c_uint = 26;
|
||||
pub const VHOST_NET_F_VIRTIO_NET_HDR: raw::c_uint = 27;
|
||||
pub const VHOST_SCSI_ABI_VERSION: raw::c_uint = 1;
|
||||
|
||||
ioctl_ior_nr!(VHOST_GET_FEATURES, VHOST, 0x00, raw::c_ulonglong);
|
||||
ioctl_iow_nr!(VHOST_SET_FEATURES, VHOST, 0x00, raw::c_ulonglong);
|
||||
ioctl_io_nr!(VHOST_SET_OWNER, VHOST, 0x01);
|
||||
ioctl_io_nr!(VHOST_RESET_OWNER, VHOST, 0x02);
|
||||
ioctl_iow_nr!(VHOST_SET_MEM_TABLE, VHOST, 0x03, vhost_memory);
|
||||
ioctl_iow_nr!(VHOST_SET_LOG_BASE, VHOST, 0x04, raw::c_ulonglong);
|
||||
ioctl_iow_nr!(VHOST_SET_LOG_FD, VHOST, 0x07, raw::c_int);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_NUM, VHOST, 0x10, vhost_vring_state);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_ADDR, VHOST, 0x11, vhost_vring_addr);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_BASE, VHOST, 0x12, vhost_vring_state);
|
||||
ioctl_iowr_nr!(VHOST_GET_VRING_BASE, VHOST, 0x12, vhost_vring_state);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_KICK, VHOST, 0x20, vhost_vring_file);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_CALL, VHOST, 0x21, vhost_vring_file);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_ERR, VHOST, 0x22, vhost_vring_file);
|
||||
ioctl_iow_nr!(vhost_SET_BACKEND, VHOST, 0x30, vhost_vring_file);
|
||||
ioctl_iow_nr!(VHOST_SCSI_SET_ENDPOINT, VHOST, 0x40, vhost_scsi_target);
|
||||
ioctl_iow_nr!(VHOST_SCSI_CLEAR_ENDPOINT, VHOST, 0x41, vhost_scsi_target);
|
||||
ioctl_iow_nr!(VHOST_SCSI_GET_ABI_VERSION, VHOST, 0x42, raw::c_int);
|
||||
ioctl_iow_nr!(VHOST_SCSI_SET_EVENTS_MISSED, VHOST, 0x43, raw::c_uint);
|
||||
ioctl_iow_nr!(VHOST_SCSI_GET_EVENTS_MISSED, VHOST, 0x44, raw::c_uint);
|
||||
ioctl_iow_nr!(VHOST_VSOCK_SET_GUEST_CID, VHOST, 0x60, raw::c_ulonglong);
|
||||
ioctl_iow_nr!(VHOST_VSOCK_SET_RUNNING, VHOST, 0x61, raw::c_int);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);
|
||||
|
||||
impl<T> __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
__IncompleteArrayField(::std::marker::PhantomData)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[allow(clippy::useless_transmute)]
|
||||
pub unsafe fn as_ptr(&self) -> *const T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::useless_transmute)]
|
||||
pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
|
||||
::std::slice::from_raw_parts(self.as_ptr(), len)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
|
||||
::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
|
||||
fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
fmt.write_str("__IncompleteArrayField")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct vhost_vring_state {
|
||||
pub index: raw::c_uint,
|
||||
pub num: raw::c_uint,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct vhost_vring_file {
|
||||
pub index: raw::c_uint,
|
||||
pub fd: raw::c_int,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct vhost_vring_addr {
|
||||
pub index: raw::c_uint,
|
||||
pub flags: raw::c_uint,
|
||||
pub desc_user_addr: raw::c_ulonglong,
|
||||
pub used_user_addr: raw::c_ulonglong,
|
||||
pub avail_user_addr: raw::c_ulonglong,
|
||||
pub log_guest_addr: raw::c_ulonglong,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct vhost_iotlb_msg {
|
||||
pub iova: raw::c_ulonglong,
|
||||
pub size: raw::c_ulonglong,
|
||||
pub uaddr: raw::c_ulonglong,
|
||||
pub perm: raw::c_uchar,
|
||||
pub type_: raw::c_uchar,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct vhost_msg {
|
||||
pub type_: raw::c_int,
|
||||
pub __bindgen_anon_1: vhost_msg__bindgen_ty_1,
|
||||
}
|
||||
|
||||
impl Default for vhost_msg {
|
||||
fn default() -> Self {
|
||||
unsafe { ::std::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union vhost_msg__bindgen_ty_1 {
|
||||
pub iotlb: vhost_iotlb_msg,
|
||||
pub padding: [raw::c_uchar; 64usize],
|
||||
_bindgen_union_align: [u64; 8usize],
|
||||
}
|
||||
|
||||
impl Default for vhost_msg__bindgen_ty_1 {
|
||||
fn default() -> Self {
|
||||
unsafe { ::std::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct vhost_memory_region {
|
||||
pub guest_phys_addr: raw::c_ulonglong,
|
||||
pub memory_size: raw::c_ulonglong,
|
||||
pub userspace_addr: raw::c_ulonglong,
|
||||
pub flags_padding: raw::c_ulonglong,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct vhost_memory {
|
||||
pub nregions: raw::c_uint,
|
||||
pub padding: raw::c_uint,
|
||||
pub regions: __IncompleteArrayField<vhost_memory_region>,
|
||||
__force_alignment: [u64; 0],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct vhost_scsi_target {
|
||||
pub abi_version: raw::c_int,
|
||||
pub vhost_wwpn: [raw::c_char; 224usize],
|
||||
pub vhost_tpgt: raw::c_ushort,
|
||||
pub reserved: raw::c_ushort,
|
||||
}
|
||||
|
||||
impl Default for vhost_scsi_target {
|
||||
fn default() -> Self {
|
||||
unsafe { ::std::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to support vhost::set_mem_table()
|
||||
pub struct VhostMemory {
|
||||
buf: Vec<vhost_memory>,
|
||||
}
|
||||
|
||||
impl VhostMemory {
|
||||
// Limit number of regions to u16 to simplify error handling
|
||||
pub fn new(entries: u16) -> Self {
|
||||
let size = std::mem::size_of::<vhost_memory_region>() * entries as usize;
|
||||
let count = (size + 2 * std::mem::size_of::<vhost_memory>() - 1)
|
||||
/ std::mem::size_of::<vhost_memory>();
|
||||
let mut buf: Vec<vhost_memory> = vec![Default::default(); count];
|
||||
buf[0].nregions = u32::from(entries);
|
||||
VhostMemory { buf }
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *const char {
|
||||
&self.buf[0] as *const vhost_memory as *const char
|
||||
}
|
||||
|
||||
pub fn get_header(&self) -> &vhost_memory {
|
||||
&self.buf[0]
|
||||
}
|
||||
|
||||
pub fn get_region(&self, index: u32) -> Option<&vhost_memory_region> {
|
||||
if index >= self.buf[0].nregions {
|
||||
return None;
|
||||
}
|
||||
// Safe because we have allocated enough space nregions
|
||||
let regions = unsafe { self.buf[0].regions.as_slice(self.buf[0].nregions as usize) };
|
||||
Some(®ions[index as usize])
|
||||
}
|
||||
|
||||
pub fn set_region(&mut self, index: u32, region: &vhost_memory_region) -> Result<()> {
|
||||
if index >= self.buf[0].nregions {
|
||||
return Err(Error::InvalidGuestMemory);
|
||||
}
|
||||
// Safe because we have allocated enough space nregions and checked the index.
|
||||
let regions = unsafe { self.buf[0].regions.as_mut_slice(index as usize + 1) };
|
||||
regions[index as usize] = *region;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_vring_state() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_vring_state>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(vhost_vring_state))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_vring_state>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(vhost_vring_state))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_vring_file() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_vring_file>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(vhost_vring_file))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_vring_file>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(vhost_vring_file))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_vring_addr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_vring_addr>(),
|
||||
40usize,
|
||||
concat!("Size of: ", stringify!(vhost_vring_addr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_vring_addr>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_vring_addr))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_msg__bindgen_ty_1() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_msg__bindgen_ty_1>(),
|
||||
64usize,
|
||||
concat!("Size of: ", stringify!(vhost_msg__bindgen_ty_1))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_msg__bindgen_ty_1>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_msg__bindgen_ty_1))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_msg() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_msg>(),
|
||||
72usize,
|
||||
concat!("Size of: ", stringify!(vhost_msg))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_msg>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_msg))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_memory_region() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_memory_region>(),
|
||||
32usize,
|
||||
concat!("Size of: ", stringify!(vhost_memory_region))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_memory_region>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_memory_region))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_memory() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_memory>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(vhost_memory))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_memory>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_memory))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_iotlb_msg() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_iotlb_msg>(),
|
||||
32usize,
|
||||
concat!("Size of: ", stringify!(vhost_iotlb_msg))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_iotlb_msg>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_iotlb_msg))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_scsi_target() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_scsi_target>(),
|
||||
232usize,
|
||||
concat!("Size of: ", stringify!(vhost_scsi_target))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_scsi_target>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(vhost_scsi_target))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vhostmemory() {
|
||||
let mut obj = VhostMemory::new(2);
|
||||
let region = vhost_memory_region {
|
||||
guest_phys_addr: 0x1000u64,
|
||||
memory_size: 0x2000u64,
|
||||
userspace_addr: 0x300000u64,
|
||||
flags_padding: 0u64,
|
||||
};
|
||||
assert!(obj.get_region(2).is_none());
|
||||
|
||||
{
|
||||
let header = obj.get_header();
|
||||
assert_eq!(header.nregions, 2u32);
|
||||
}
|
||||
{
|
||||
assert!(obj.set_region(0, ®ion).is_ok());
|
||||
assert!(obj.set_region(1, ®ion).is_ok());
|
||||
assert!(obj.set_region(2, ®ion).is_err());
|
||||
}
|
||||
|
||||
let region1 = obj.get_region(1).unwrap();
|
||||
assert_eq!(region1.guest_phys_addr, 0x1000u64);
|
||||
assert_eq!(region1.memory_size, 0x2000u64);
|
||||
assert_eq!(region1.userspace_addr, 0x300000u64);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
//
|
||||
// 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.
|
||||
|
||||
//! Kernel-based vsock vhost backend.
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::marker::PhantomData;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use super::vhost_binding::{VHOST_VSOCK_SET_GUEST_CID, VHOST_VSOCK_SET_RUNNING};
|
||||
use super::{ioctl_result, Error, Result, VhostKernBackend};
|
||||
use libc;
|
||||
use vm_memory::GuestMemory;
|
||||
use vmm_sys_util::ioctl::ioctl_with_ref;
|
||||
|
||||
const VHOST_PATH: &str = "/dev/vhost-vsock";
|
||||
|
||||
/// Handle for running VHOST_VSOCK ioctls.
|
||||
pub struct Vsock<'a, M: GuestMemory<'a>> {
|
||||
fd: File,
|
||||
mem: M,
|
||||
_phatomdata: PhantomData<&'a M>, // Get rid of unused type parameter `a
|
||||
}
|
||||
|
||||
impl<'a, M: GuestMemory<'a>> Vsock<'a, M> {
|
||||
/// Open a handle to a new VHOST-VSOCK instance.
|
||||
pub fn new(mem: &M) -> Result<Self> {
|
||||
Ok(Vsock {
|
||||
fd: OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
|
||||
.open(VHOST_PATH)
|
||||
.map_err(Error::VhostOpen)?,
|
||||
mem: mem.clone(),
|
||||
_phatomdata: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the CID for the guest. This number is used for routing all data destined for
|
||||
/// running in the guest. Each guest on a hypervisor must have an unique CID
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `cid` - CID to assign to the guest
|
||||
pub fn set_guest_cid(&self, cid: u64) -> Result<()> {
|
||||
let ret = unsafe { ioctl_with_ref(&self.fd, VHOST_VSOCK_SET_GUEST_CID(), &cid) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Tell the VHOST driver to start performing data transfer.
|
||||
pub fn start(&self) -> Result<()> {
|
||||
self.set_running(true)
|
||||
}
|
||||
|
||||
/// Tell the VHOST driver to stop performing data transfer.
|
||||
pub fn stop(&self) -> Result<()> {
|
||||
self.set_running(false)
|
||||
}
|
||||
|
||||
fn set_running(&self, running: bool) -> Result<()> {
|
||||
let on: ::std::os::raw::c_int = if running { 1 } else { 0 };
|
||||
let ret = unsafe { ioctl_with_ref(&self.fd, VHOST_VSOCK_SET_RUNNING(), &on) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M: GuestMemory<'a>> VhostKernBackend<'a> for Vsock<'a, M> {
|
||||
type M = M;
|
||||
|
||||
fn mem(&self) -> &Self::M {
|
||||
&self.mem
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M: GuestMemory<'a>> AsRawFd for Vsock<'a, M> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
@@ -1,737 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Structs for Unix Domain Socket listener and endpoint.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use libc::{c_void, iovec};
|
||||
use std::io::ErrorKind;
|
||||
use std::marker::PhantomData;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::{mem, slice};
|
||||
|
||||
use super::message::*;
|
||||
use super::sock_ctrl_msg::ScmSocket;
|
||||
use super::{Error, Result};
|
||||
|
||||
/// Unix domain socket listener for accepting incoming connections.
|
||||
pub struct Listener {
|
||||
fd: UnixListener,
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
/// Create a unix domain socket listener.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - the new Listener object on success.
|
||||
/// * - SocketError: failed to create listener socket.
|
||||
pub fn new(path: &str, unlink: bool) -> Result<Self> {
|
||||
if unlink {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
let fd = UnixListener::bind(path).map_err(Error::SocketError)?;
|
||||
Ok(Listener {
|
||||
fd,
|
||||
path: path.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Accept an incoming connection.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - Some(UnixStream): new UnixStream object if new incoming connection is available.
|
||||
/// * - None: no incoming connection available.
|
||||
/// * - SocketError: errors from accept().
|
||||
pub fn accept(&self) -> Result<Option<UnixStream>> {
|
||||
loop {
|
||||
match self.fd.accept() {
|
||||
Ok((socket, _addr)) => return Ok(Some(socket)),
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
// No incoming connection available.
|
||||
ErrorKind::WouldBlock => return Ok(None),
|
||||
// New connection closed by peer.
|
||||
ErrorKind::ConnectionAborted => return Ok(None),
|
||||
// Interrupted by signals, retry
|
||||
ErrorKind::Interrupted => continue,
|
||||
_ => return Err(Error::SocketError(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Change blocking status on the listener.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - () on success.
|
||||
/// * - SocketError: failure from set_nonblocking().
|
||||
pub fn set_nonblocking(&self, block: bool) -> Result<()> {
|
||||
self.fd.set_nonblocking(block).map_err(Error::SocketError)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Listener {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Listener {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_file(self.path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Unix domain socket endpoint for vhost-user connection.
|
||||
pub(super) struct Endpoint<R: Req> {
|
||||
sock: UnixStream,
|
||||
_r: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<R: Req> Endpoint<R> {
|
||||
/// Create a new stream by connecting to server at `str`.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - the new Endpoint object on success.
|
||||
/// * - SocketConnect: failed to connect to peer.
|
||||
pub fn connect(path: &str) -> Result<Self> {
|
||||
let sock = UnixStream::connect(path).map_err(Error::SocketConnect)?;
|
||||
Ok(Self::from_stream(sock))
|
||||
}
|
||||
|
||||
/// Create an endpoint from a stream object.
|
||||
pub fn from_stream(sock: UnixStream) -> Self {
|
||||
Endpoint {
|
||||
sock,
|
||||
_r: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends bytes from scatter-gather vectors over the socket with optional attached file
|
||||
/// descriptors.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
pub fn send_iovec(&mut self, iovs: &[&[u8]], fds: Option<&[RawFd]>) -> Result<usize> {
|
||||
let rfds = match fds {
|
||||
Some(rfds) => rfds,
|
||||
_ => &[],
|
||||
};
|
||||
self.sock.send_with_fds(iovs, rfds).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Sends bytes from a slice over the socket with optional attached file descriptors.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
pub fn send_slice(&mut self, data: &[u8], fds: Option<&[RawFd]>) -> Result<usize> {
|
||||
self.send_iovec(&[data], fds)
|
||||
}
|
||||
|
||||
/// Sends a header-only message with optional attached file descriptors.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
pub fn send_header(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<R>,
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> Result<()> {
|
||||
// Safe because there can't be other mutable referance to hdr.
|
||||
let iovs = unsafe {
|
||||
[slice::from_raw_parts(
|
||||
hdr as *const VhostUserMsgHeader<R> as *const u8,
|
||||
mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
)]
|
||||
};
|
||||
let bytes = self.send_iovec(&iovs[..], fds)?;
|
||||
if bytes != mem::size_of::<VhostUserMsgHeader<R>>() {
|
||||
return Err(Error::PartialMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a message with header and body. Optional file descriptors may be attached to
|
||||
/// the message.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
pub fn send_message<T: Sized>(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<R>,
|
||||
body: &T,
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> Result<()> {
|
||||
// Safe because there can't be other mutable referance to hdr and body.
|
||||
let iovs = unsafe {
|
||||
[
|
||||
slice::from_raw_parts(
|
||||
hdr as *const VhostUserMsgHeader<R> as *const u8,
|
||||
mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
),
|
||||
slice::from_raw_parts(body as *const T as *const u8, mem::size_of::<T>()),
|
||||
]
|
||||
};
|
||||
let bytes = self.send_iovec(&iovs[..], fds)?;
|
||||
if bytes != mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>() {
|
||||
return Err(Error::PartialMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a message with header, body and payload. Optional file descriptors
|
||||
/// may also be attached to the message.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - OversizedMsg: message size is too big.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
/// * - IncorrectFds: wrong number of attached fds.
|
||||
pub fn send_message_with_payload<T: Sized, P: Sized>(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<R>,
|
||||
body: &T,
|
||||
payload: &[P],
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> Result<()> {
|
||||
let len = payload.len() * mem::size_of::<P>();
|
||||
if len > MAX_MSG_SIZE - mem::size_of::<T>() {
|
||||
return Err(Error::OversizedMsg);
|
||||
}
|
||||
if let Some(fd_arr) = fds {
|
||||
if fd_arr.len() > MAX_ATTACHED_FD_ENTRIES {
|
||||
return Err(Error::IncorrectFds);
|
||||
}
|
||||
}
|
||||
|
||||
// Safe because there can't be other mutable reference to hdr, body and payload.
|
||||
let iovs = unsafe {
|
||||
[
|
||||
slice::from_raw_parts(
|
||||
hdr as *const VhostUserMsgHeader<R> as *const u8,
|
||||
mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
),
|
||||
slice::from_raw_parts(body as *const T as *const u8, mem::size_of::<T>()),
|
||||
slice::from_raw_parts(payload.as_ptr() as *const u8, len),
|
||||
]
|
||||
};
|
||||
let total = mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>() + len;
|
||||
let len = self.send_iovec(&iovs, fds)?;
|
||||
if len != total {
|
||||
return Err(Error::PartialMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads bytes from the socket into the given scatter/gather vectors.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (number of bytes received, buf) on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
pub fn recv_data(&mut self, len: usize) -> Result<(usize, Vec<u8>)> {
|
||||
let mut rbuf = vec![0u8; len];
|
||||
let mut iovs = [iovec {
|
||||
iov_base: rbuf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: len,
|
||||
}];
|
||||
let (bytes, _) = self.sock.recv_with_fds(&mut iovs, &mut [])?;
|
||||
Ok((bytes, rbuf))
|
||||
}
|
||||
|
||||
/// Reads bytes from the socket into the given scatter/gather vectors with optional attached
|
||||
/// file descriptors.
|
||||
///
|
||||
/// The underlying communication channel is a Unix domain socket in STREAM mode. It's a little
|
||||
/// tricky to pass file descriptors through such a communication channel. Let's assume that a
|
||||
/// sender sending a message with some file descriptors attached. To successfully receive those
|
||||
/// attached file descriptors, the receiver must obey following rules:
|
||||
/// 1) file descriptors are attached to a message.
|
||||
/// 2) message(packet) boundaries must be respected on the receive side.
|
||||
/// In other words, recvmsg() operations must not cross the packet boundary, otherwise the
|
||||
/// attached file descriptors will get lost.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (number of bytes received, [received fds]) on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
pub fn recv_into_iovec(&mut self, iovs: &mut [iovec]) -> Result<(usize, Option<Vec<RawFd>>)> {
|
||||
let mut fd_array = vec![0; MAX_ATTACHED_FD_ENTRIES];
|
||||
let (bytes, fds) = self.sock.recv_with_fds(iovs, &mut fd_array)?;
|
||||
let rfds = match fds {
|
||||
0 => None,
|
||||
n => {
|
||||
let mut fds = Vec::with_capacity(n);
|
||||
fds.extend_from_slice(&fd_array[0..n]);
|
||||
Some(fds)
|
||||
}
|
||||
};
|
||||
|
||||
Ok((bytes, rfds))
|
||||
}
|
||||
|
||||
/// Reads bytes from the socket into a new buffer with optional attached
|
||||
/// file descriptors. Received file descriptors are set close-on-exec.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (number of bytes received, buf, [received fds]) on success.
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
pub fn recv_into_buf(
|
||||
&mut self,
|
||||
buf_size: usize,
|
||||
) -> Result<(usize, Vec<u8>, Option<Vec<RawFd>>)> {
|
||||
let mut buf = vec![0u8; buf_size];
|
||||
let (bytes, rfds) = {
|
||||
let mut iovs = [iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf_size,
|
||||
}];
|
||||
self.recv_into_iovec(&mut iovs)?
|
||||
};
|
||||
Ok((bytes, buf, rfds))
|
||||
}
|
||||
|
||||
/// Receive a header-only message with optional attached file descriptors.
|
||||
/// Note, only the first MAX_ATTACHED_FD_ENTRIES file descriptors will be
|
||||
/// accepted and all other file descriptor will be discard silently.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (message header, [received fds]) on success.
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
/// * - InvalidMessage: received a invalid message.
|
||||
pub fn recv_header(&mut self) -> Result<(VhostUserMsgHeader<R>, Option<Vec<RawFd>>)> {
|
||||
let mut hdr = VhostUserMsgHeader::default();
|
||||
let mut iovs = [iovec {
|
||||
iov_base: (&mut hdr as *mut VhostUserMsgHeader<R>) as *mut c_void,
|
||||
iov_len: mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
}];
|
||||
let (bytes, rfds) = self.recv_into_iovec(&mut iovs[..])?;
|
||||
|
||||
if bytes != mem::size_of::<VhostUserMsgHeader<R>>() {
|
||||
return Err(Error::PartialMessage);
|
||||
} else if !hdr.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
Ok((hdr, rfds))
|
||||
}
|
||||
|
||||
/// Receive a message with optional attached file descriptors.
|
||||
/// Note, only the first MAX_ATTACHED_FD_ENTRIES file descriptors will be
|
||||
/// accepted and all other file descriptor will be discard silently.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (message header, message body, [received fds]) on success.
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
/// * - InvalidMessage: received a invalid message.
|
||||
pub fn recv_body<T: Sized + Default + VhostUserMsgValidator>(
|
||||
&mut self,
|
||||
) -> Result<(VhostUserMsgHeader<R>, T, Option<Vec<RawFd>>)> {
|
||||
let mut hdr = VhostUserMsgHeader::default();
|
||||
let mut body: T = Default::default();
|
||||
let mut iovs = [
|
||||
iovec {
|
||||
iov_base: (&mut hdr as *mut VhostUserMsgHeader<R>) as *mut c_void,
|
||||
iov_len: mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
},
|
||||
iovec {
|
||||
iov_base: (&mut body as *mut T) as *mut c_void,
|
||||
iov_len: mem::size_of::<T>(),
|
||||
},
|
||||
];
|
||||
let (bytes, rfds) = self.recv_into_iovec(&mut iovs[..])?;
|
||||
|
||||
let total = mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>();
|
||||
if bytes != total {
|
||||
return Err(Error::PartialMessage);
|
||||
} else if !hdr.is_valid() || !body.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
Ok((hdr, body, rfds))
|
||||
}
|
||||
|
||||
/// Receive a message with header and optional content. Callers need to
|
||||
/// pre-allocate a big enough buffer to receive the message body and
|
||||
/// optional payload. If there are attached file descriptor associated
|
||||
/// with the message, the first MAX_ATTACHED_FD_ENTRIES file descriptors
|
||||
/// will be accepted and all other file descriptor will be discard
|
||||
/// silently.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (message header, message size, [received fds]) on success.
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
/// * - InvalidMessage: received a invalid message.
|
||||
pub fn recv_body_into_buf(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
) -> Result<(VhostUserMsgHeader<R>, usize, Option<Vec<RawFd>>)> {
|
||||
let mut hdr = VhostUserMsgHeader::default();
|
||||
let mut iovs = [
|
||||
iovec {
|
||||
iov_base: (&mut hdr as *mut VhostUserMsgHeader<R>) as *mut c_void,
|
||||
iov_len: mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
},
|
||||
iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf.len(),
|
||||
},
|
||||
];
|
||||
let (bytes, rfds) = self.recv_into_iovec(&mut iovs[..])?;
|
||||
|
||||
if bytes < mem::size_of::<VhostUserMsgHeader<R>>() {
|
||||
return Err(Error::PartialMessage);
|
||||
} else if !hdr.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
Ok((hdr, bytes - mem::size_of::<VhostUserMsgHeader<R>>(), rfds))
|
||||
}
|
||||
|
||||
/// Receive a message with optional payload and attached file descriptors.
|
||||
/// Note, only the first MAX_ATTACHED_FD_ENTRIES file descriptors will be
|
||||
/// accepted and all other file descriptor will be discard silently.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (message header, message body, size of payload, [received fds]) on success.
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
/// * - InvalidMessage: received a invalid message.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
|
||||
pub fn recv_payload_into_buf<T: Sized + Default + VhostUserMsgValidator>(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
) -> Result<(VhostUserMsgHeader<R>, T, usize, Option<Vec<RawFd>>)> {
|
||||
let mut hdr = VhostUserMsgHeader::default();
|
||||
let mut body: T = Default::default();
|
||||
let mut iovs = [
|
||||
iovec {
|
||||
iov_base: (&mut hdr as *mut VhostUserMsgHeader<R>) as *mut c_void,
|
||||
iov_len: mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
},
|
||||
iovec {
|
||||
iov_base: (&mut body as *mut T) as *mut c_void,
|
||||
iov_len: mem::size_of::<T>(),
|
||||
},
|
||||
iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf.len(),
|
||||
},
|
||||
];
|
||||
let (bytes, rfds) = self.recv_into_iovec(&mut iovs[..])?;
|
||||
|
||||
let total = mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>();
|
||||
if bytes < total {
|
||||
return Err(Error::PartialMessage);
|
||||
} else if !hdr.is_valid() || !body.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
Ok((hdr, body, bytes - total, rfds))
|
||||
}
|
||||
|
||||
/// Close all raw file descriptors.
|
||||
pub fn close_rfds(rfds: Option<Vec<RawFd>>) {
|
||||
if let Some(fds) = rfds {
|
||||
for fd in fds {
|
||||
// safe because the rawfds are valid and we don't care about the result.
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Req> AsRawFd for Endpoint<T> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.sock.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate tempfile;
|
||||
|
||||
use self::tempfile::tempfile;
|
||||
use super::*;
|
||||
use libc;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
const UNIX_SOCKET_LISTENER: &'static str = "/tmp/vhost_user_test_rust_listener";
|
||||
const UNIX_SOCKET_CONNECTION: &'static str = "/tmp/vhost_user_test_rust_connection";
|
||||
const UNIX_SOCKET_DATA: &'static str = "/tmp/vhost_user_test_rust_data";
|
||||
const UNIX_SOCKET_FD: &'static str = "/tmp/vhost_user_test_rust_fd";
|
||||
const UNIX_SOCKET_SEND: &'static str = "/tmp/vhost_user_test_rust_send";
|
||||
|
||||
#[test]
|
||||
fn create_listener() {
|
||||
let _ = Listener::new(UNIX_SOCKET_LISTENER, true).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accept_connection() {
|
||||
let listener = Listener::new(UNIX_SOCKET_CONNECTION, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
|
||||
// accept on a fd without incoming connection
|
||||
let conn = listener.accept().unwrap();
|
||||
assert!(conn.is_none());
|
||||
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
|
||||
// accept on a closed fd
|
||||
unsafe {
|
||||
libc::close(listener.as_raw_fd());
|
||||
}
|
||||
let conn2 = listener.accept();
|
||||
assert!(conn2.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_data() {
|
||||
let listener = Listener::new(UNIX_SOCKET_DATA, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
let mut master = Endpoint::<MasterReq>::connect(UNIX_SOCKET_DATA).unwrap();
|
||||
let sock = listener.accept().unwrap().unwrap();
|
||||
let mut slave = Endpoint::<MasterReq>::from_stream(sock);
|
||||
|
||||
let buf1 = vec![0x1, 0x2, 0x3, 0x4];
|
||||
let mut len = master.send_slice(&buf1[..], None).unwrap();
|
||||
assert_eq!(len, 4);
|
||||
let (bytes, buf2, _) = slave.recv_into_buf(0x1000).unwrap();
|
||||
assert_eq!(bytes, 4);
|
||||
assert_eq!(&buf1[..], &buf2[..bytes]);
|
||||
|
||||
len = master.send_slice(&buf1[..], None).unwrap();
|
||||
assert_eq!(len, 4);
|
||||
let (bytes, buf2, _) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[..2], &buf2[..]);
|
||||
let (bytes, buf2, _) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[2..], &buf2[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_fd() {
|
||||
let listener = Listener::new(UNIX_SOCKET_FD, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
let mut master = Endpoint::<MasterReq>::connect(UNIX_SOCKET_FD).unwrap();
|
||||
let sock = listener.accept().unwrap().unwrap();
|
||||
let mut slave = Endpoint::<MasterReq>::from_stream(sock);
|
||||
|
||||
let mut fd = tempfile().unwrap();
|
||||
write!(fd, "test").unwrap();
|
||||
|
||||
// Normal case for sending/receiving file descriptors
|
||||
let buf1 = vec![0x1, 0x2, 0x3, 0x4];
|
||||
let len = master
|
||||
.send_slice(&buf1[..], Some(&[fd.as_raw_fd()]))
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(4).unwrap();
|
||||
assert_eq!(bytes, 4);
|
||||
assert_eq!(&buf1[..], &buf2[..]);
|
||||
assert!(rfds.is_some());
|
||||
let fds = rfds.unwrap();
|
||||
{
|
||||
assert_eq!(fds.len(), 1);
|
||||
let mut file = unsafe { File::from_raw_fd(fds[0]) };
|
||||
let mut content = String::new();
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
file.read_to_string(&mut content).unwrap();
|
||||
assert_eq!(content, "test");
|
||||
}
|
||||
|
||||
// Following communication pattern should work:
|
||||
// Sending side: data(header, body) with fds
|
||||
// Receiving side: data(header) with fds, data(body)
|
||||
let len = master
|
||||
.send_slice(
|
||||
&buf1[..],
|
||||
Some(&[fd.as_raw_fd(), fd.as_raw_fd(), fd.as_raw_fd()]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[..2], &buf2[..]);
|
||||
assert!(rfds.is_some());
|
||||
let fds = rfds.unwrap();
|
||||
{
|
||||
assert_eq!(fds.len(), 3);
|
||||
let mut file = unsafe { File::from_raw_fd(fds[1]) };
|
||||
let mut content = String::new();
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
file.read_to_string(&mut content).unwrap();
|
||||
assert_eq!(content, "test");
|
||||
}
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[2..], &buf2[..]);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
// Following communication pattern should not work:
|
||||
// Sending side: data(header, body) with fds
|
||||
// Receiving side: data(header), data(body) with fds
|
||||
let len = master
|
||||
.send_slice(
|
||||
&buf1[..],
|
||||
Some(&[fd.as_raw_fd(), fd.as_raw_fd(), fd.as_raw_fd()]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, buf4) = slave.recv_data(2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[..2], &buf4[..]);
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[2..], &buf2[..]);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
// Following communication pattern should work:
|
||||
// Sending side: data, data with fds
|
||||
// Receiving side: data, data with fds
|
||||
let len = master.send_slice(&buf1[..], None).unwrap();
|
||||
assert_eq!(len, 4);
|
||||
let len = master
|
||||
.send_slice(
|
||||
&buf1[..],
|
||||
Some(&[fd.as_raw_fd(), fd.as_raw_fd(), fd.as_raw_fd()]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x4).unwrap();
|
||||
assert_eq!(bytes, 4);
|
||||
assert_eq!(&buf1[..], &buf2[..]);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[..2], &buf2[..]);
|
||||
assert!(rfds.is_some());
|
||||
let fds = rfds.unwrap();
|
||||
{
|
||||
assert_eq!(fds.len(), 3);
|
||||
let mut file = unsafe { File::from_raw_fd(fds[1]) };
|
||||
let mut content = String::new();
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
file.read_to_string(&mut content).unwrap();
|
||||
assert_eq!(content, "test");
|
||||
}
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[2..], &buf2[..]);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
// Following communication pattern should not work:
|
||||
// Sending side: data1, data2 with fds
|
||||
// Receiving side: data + partial of data2, left of data2 with fds
|
||||
let len = master.send_slice(&buf1[..], None).unwrap();
|
||||
assert_eq!(len, 4);
|
||||
let len = master
|
||||
.send_slice(
|
||||
&buf1[..],
|
||||
Some(&[fd.as_raw_fd(), fd.as_raw_fd(), fd.as_raw_fd()]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, _) = slave.recv_data(5).unwrap();
|
||||
assert_eq!(bytes, 5);
|
||||
|
||||
let (bytes, _, rfds) = slave.recv_into_buf(0x4).unwrap();
|
||||
assert_eq!(bytes, 3);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
// If the target fd array is too small, extra file descriptors will get lost.
|
||||
let len = master
|
||||
.send_slice(
|
||||
&buf1[..],
|
||||
Some(&[fd.as_raw_fd(), fd.as_raw_fd(), fd.as_raw_fd()]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, _, rfds) = slave.recv_into_buf(0x4).unwrap();
|
||||
assert_eq!(bytes, 4);
|
||||
assert!(rfds.is_some());
|
||||
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
Endpoint::<MasterReq>::close_rfds(None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_recv() {
|
||||
let listener = Listener::new(UNIX_SOCKET_SEND, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
let mut master = Endpoint::<MasterReq>::connect(UNIX_SOCKET_SEND).unwrap();
|
||||
let sock = listener.accept().unwrap().unwrap();
|
||||
let mut slave = Endpoint::<MasterReq>::from_stream(sock);
|
||||
|
||||
let mut hdr1 =
|
||||
VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0, mem::size_of::<u64>() as u32);
|
||||
hdr1.set_need_reply(true);
|
||||
let features1 = 0x1u64;
|
||||
master.send_message(&hdr1, &features1, None).unwrap();
|
||||
|
||||
let mut features2 = 0u64;
|
||||
let slice = unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
(&mut features2 as *mut u64) as *mut u8,
|
||||
mem::size_of::<u64>(),
|
||||
)
|
||||
};
|
||||
let (hdr2, bytes, rfds) = slave.recv_body_into_buf(slice).unwrap();
|
||||
assert_eq!(hdr1, hdr2);
|
||||
assert_eq!(bytes, 8);
|
||||
assert_eq!(features1, features2);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
master.send_header(&hdr1, None).unwrap();
|
||||
let (hdr2, rfds) = slave.recv_header().unwrap();
|
||||
assert_eq!(hdr1, hdr2);
|
||||
assert!(rfds.is_none());
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::message::*;
|
||||
use super::*;
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
pub const MAX_QUEUE_NUM: usize = 2;
|
||||
pub const MAX_VRING_NUM: usize = 256;
|
||||
pub const VIRTIO_FEATURES: u64 = 0x40000003;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DummySlaveReqHandler {
|
||||
pub owned: bool,
|
||||
pub features_acked: bool,
|
||||
pub acked_features: u64,
|
||||
pub acked_protocol_features: u64,
|
||||
pub queue_num: usize,
|
||||
pub vring_num: [u32; MAX_QUEUE_NUM],
|
||||
pub vring_base: [u32; MAX_QUEUE_NUM],
|
||||
pub call_fd: [Option<RawFd>; MAX_QUEUE_NUM],
|
||||
pub kick_fd: [Option<RawFd>; MAX_QUEUE_NUM],
|
||||
pub err_fd: [Option<RawFd>; MAX_QUEUE_NUM],
|
||||
pub vring_started: [bool; MAX_QUEUE_NUM],
|
||||
pub vring_enabled: [bool; MAX_QUEUE_NUM],
|
||||
}
|
||||
|
||||
impl DummySlaveReqHandler {
|
||||
pub fn new() -> Self {
|
||||
DummySlaveReqHandler {
|
||||
queue_num: MAX_QUEUE_NUM,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserSlaveReqHandler for DummySlaveReqHandler {
|
||||
fn set_owner(&mut self) -> Result<()> {
|
||||
if self.owned {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
self.owned = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_owner(&mut self) -> Result<()> {
|
||||
self.owned = false;
|
||||
self.features_acked = false;
|
||||
self.acked_features = 0;
|
||||
self.acked_protocol_features = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_features(&mut self) -> Result<u64> {
|
||||
Ok(VIRTIO_FEATURES)
|
||||
}
|
||||
|
||||
fn set_features(&mut self, features: u64) -> Result<()> {
|
||||
if !self.owned {
|
||||
return Err(Error::InvalidOperation);
|
||||
} else if self.features_acked {
|
||||
return Err(Error::InvalidOperation);
|
||||
} else if (features & !VIRTIO_FEATURES) != 0 {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
|
||||
self.acked_features = features;
|
||||
self.features_acked = true;
|
||||
|
||||
// If VHOST_USER_F_PROTOCOL_FEATURES has not been negotiated,
|
||||
// the ring is initialized in an enabled state.
|
||||
// If VHOST_USER_F_PROTOCOL_FEATURES has been negotiated,
|
||||
// the ring is initialized in a disabled state. Client must not
|
||||
// pass data to/from the backend until ring is enabled by
|
||||
// VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has
|
||||
// been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0.
|
||||
let vring_enabled =
|
||||
self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0;
|
||||
for enabled in &mut self.vring_enabled {
|
||||
*enabled = vring_enabled;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures> {
|
||||
Ok(VhostUserProtocolFeatures::all())
|
||||
}
|
||||
|
||||
fn set_protocol_features(&mut self, features: u64) -> Result<()> {
|
||||
// Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must
|
||||
// support this message even before VHOST_USER_SET_FEATURES was
|
||||
// called.
|
||||
// What happens if the master calls set_features() with
|
||||
// VHOST_USER_F_PROTOCOL_FEATURES cleared after calling this
|
||||
// interface?
|
||||
self.acked_protocol_features = features;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_mem_table(&mut self, _ctx: &[VhostUserMemoryRegion], _fds: &[RawFd]) -> Result<()> {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_queue_num(&mut self) -> Result<u64> {
|
||||
Ok(MAX_QUEUE_NUM as u64)
|
||||
}
|
||||
|
||||
fn set_vring_num(&mut self, index: u32, num: u32) -> Result<()> {
|
||||
if index as usize >= self.queue_num || num == 0 || num as usize > MAX_VRING_NUM {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.vring_num[index as usize] = num;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_addr(
|
||||
&mut self,
|
||||
index: u32,
|
||||
_flags: VhostUserVringAddrFlags,
|
||||
_descriptor: u64,
|
||||
_used: u64,
|
||||
_available: u64,
|
||||
_log: u64,
|
||||
) -> Result<()> {
|
||||
if index as usize >= self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_base(&mut self, index: u32, base: u32) -> Result<()> {
|
||||
if index as usize >= self.queue_num || base as usize >= MAX_VRING_NUM {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.vring_base[index as usize] = base;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_vring_base(&mut self, index: u32) -> Result<VhostUserVringState> {
|
||||
if index as usize >= self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
// Quotation from vhost-user spec:
|
||||
// Client must start ring upon receiving a kick (that is, detecting
|
||||
// that file descriptor is readable) on the descriptor specified by
|
||||
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
self.vring_started[index as usize] = false;
|
||||
Ok(VhostUserVringState::new(
|
||||
index,
|
||||
self.vring_base[index as usize],
|
||||
))
|
||||
}
|
||||
|
||||
fn set_vring_kick(&mut self, index: u8, fd: Option<RawFd>) -> Result<()> {
|
||||
if index as usize >= self.queue_num || index as usize > self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
if self.kick_fd[index as usize].is_some() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(self.kick_fd[index as usize].unwrap()) };
|
||||
}
|
||||
self.kick_fd[index as usize] = fd;
|
||||
|
||||
// Quotation from vhost-user spec:
|
||||
// Client must start ring upon receiving a kick (that is, detecting
|
||||
// that file descriptor is readable) on the descriptor specified by
|
||||
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
//
|
||||
// So we should add fd to event monitor(select, poll, epoll) here.
|
||||
self.vring_started[index as usize] = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_call(&mut self, index: u8, fd: Option<RawFd>) -> Result<()> {
|
||||
if index as usize >= self.queue_num || index as usize > self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
if self.call_fd[index as usize].is_some() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(self.call_fd[index as usize].unwrap()) };
|
||||
}
|
||||
self.call_fd[index as usize] = fd;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_err(&mut self, index: u8, fd: Option<RawFd>) -> Result<()> {
|
||||
if index as usize >= self.queue_num || index as usize > self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
if self.err_fd[index as usize].is_some() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(self.err_fd[index as usize].unwrap()) };
|
||||
}
|
||||
self.err_fd[index as usize] = fd;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_enable(&mut self, index: u32, enable: bool) -> Result<()> {
|
||||
// This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES
|
||||
// has been negotiated.
|
||||
if self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
} else if index as usize >= self.queue_num || index as usize > self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
|
||||
// Slave must not pass data to/from the backend until ring is
|
||||
// enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1,
|
||||
// or after it has been disabled by VHOST_USER_SET_VRING_ENABLE
|
||||
// with parameter 0.
|
||||
self.vring_enabled[index as usize] = enable;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
_flags: VhostUserConfigFlags,
|
||||
) -> Result<Vec<u8>> {
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
Ok(vec![0xa5; size as usize])
|
||||
}
|
||||
|
||||
fn set_config(&mut self, offset: u32, buf: &[u8], _flags: VhostUserConfigFlags) -> Result<()> {
|
||||
let size = buf.len() as u32;
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,762 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Traits and Struct for vhost-user master.
|
||||
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::connection::Endpoint;
|
||||
use super::message::*;
|
||||
use super::{Error as VhostUserError, Result as VhostUserResult};
|
||||
use crate::backend::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData};
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// Trait for vhost-user master to provide extra methods not covered by the VhostBackend yet.
|
||||
pub trait VhostUserMaster: VhostBackend {
|
||||
/// Get the protocol feature bitmask from the underlying vhost implementation.
|
||||
fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures>;
|
||||
|
||||
/// Enable protocol features in the underlying vhost implementation.
|
||||
fn set_protocol_features(&mut self, features: VhostUserProtocolFeatures) -> Result<()>;
|
||||
|
||||
/// Query how many queues the backend supports.
|
||||
fn get_queue_num(&mut self) -> Result<u64>;
|
||||
|
||||
/// Signal slave to enable or disable corresponding vring.
|
||||
///
|
||||
/// Slave must not pass data to/from the backend until ring is enabled by
|
||||
/// VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has been
|
||||
/// disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0.
|
||||
fn set_vring_enable(&mut self, queue_index: usize, enable: bool) -> Result<()>;
|
||||
|
||||
/// Fetch the contents of the virtio device configuration space.
|
||||
fn get_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
flags: VhostUserConfigFlags,
|
||||
buf: &[u8],
|
||||
) -> Result<(VhostUserConfig, VhostUserConfigPayload)>;
|
||||
|
||||
/// Change the virtio device configuration space. It also can be used for live migration on the
|
||||
/// destination host to set readonly configuration space fields.
|
||||
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()>;
|
||||
|
||||
/// Setup slave communication channel.
|
||||
fn set_slave_request_fd(&mut self, fd: RawFd) -> Result<()>;
|
||||
}
|
||||
|
||||
fn error_code<T>(err: VhostUserError) -> Result<T> {
|
||||
Err(Error::VhostUserProtocol(err))
|
||||
}
|
||||
|
||||
/// Struct for the vhost-user master endpoint.
|
||||
#[derive(Clone)]
|
||||
pub struct Master {
|
||||
node: Arc<Mutex<MasterInternal>>,
|
||||
}
|
||||
|
||||
impl Master {
|
||||
/// Create a new instance.
|
||||
fn new(ep: Endpoint<MasterReq>, max_queue_num: u64) -> Self {
|
||||
Master {
|
||||
node: Arc::new(Mutex::new(MasterInternal {
|
||||
main_sock: ep,
|
||||
virtio_features: 0,
|
||||
acked_virtio_features: 0,
|
||||
protocol_features: 0,
|
||||
acked_protocol_features: 0,
|
||||
protocol_features_ready: false,
|
||||
max_queue_num,
|
||||
error: None,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new instance from a Unix stream socket.
|
||||
pub fn from_stream(sock: UnixStream, max_queue_num: u64) -> Self {
|
||||
Self::new(Endpoint::<MasterReq>::from_stream(sock), max_queue_num)
|
||||
}
|
||||
|
||||
/// Create a new vhost-user master endpoint.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `path` - path of Unix domain socket listener to connect to
|
||||
pub fn connect(path: &str, max_queue_num: u64) -> Result<Self> {
|
||||
Ok(Self::new(
|
||||
Endpoint::<MasterReq>::connect(path)?,
|
||||
max_queue_num,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostBackend for Master {
|
||||
/// Get from the underlying vhost implementation the feature bitmask.
|
||||
fn get_features(&mut self) -> Result<u64> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let hdr = node.send_request_header(MasterReq::GET_FEATURES, None)?;
|
||||
let val = node.recv_reply::<VhostUserU64>(&hdr)?;
|
||||
node.virtio_features = val.value;
|
||||
Ok(node.virtio_features)
|
||||
}
|
||||
|
||||
/// Enable features in the underlying vhost implementation using a bitmask.
|
||||
fn set_features(&mut self, features: u64) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let val = VhostUserU64::new(features);
|
||||
let _ = node.send_request_with_body(MasterReq::SET_FEATURES, &val, None)?;
|
||||
// Don't wait for ACK here because the protocol feature negotiation process hasn't been
|
||||
// completed yet.
|
||||
node.acked_virtio_features = features & node.virtio_features;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the current Master as an owner of the session.
|
||||
fn set_owner(&mut self) -> Result<()> {
|
||||
// We unwrap() the return value to assert that we are not expecting threads to ever fail
|
||||
// while holding the lock.
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let _ = node.send_request_header(MasterReq::SET_OWNER, None)?;
|
||||
// Don't wait for ACK here because the protocol feature negotiation process hasn't been
|
||||
// completed yet.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_owner(&mut self) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let _ = node.send_request_header(MasterReq::RESET_OWNER, None)?;
|
||||
// Don't wait for ACK here because the protocol feature negotiation process hasn't been
|
||||
// completed yet.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the memory map regions on the slave so it can translate the vring
|
||||
/// addresses. In the ancillary data there is an array of file descriptors
|
||||
fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
|
||||
if regions.is_empty() || regions.len() > MAX_ATTACHED_FD_ENTRIES {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let mut ctx = VhostUserMemoryContext::new();
|
||||
for region in regions.iter() {
|
||||
if region.memory_size == 0 || region.mmap_handle < 0 {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
let reg = VhostUserMemoryRegion {
|
||||
guest_phys_addr: region.guest_phys_addr,
|
||||
memory_size: region.memory_size,
|
||||
user_addr: region.userspace_addr,
|
||||
mmap_offset: region.mmap_offset,
|
||||
};
|
||||
ctx.append(®, region.mmap_handle);
|
||||
}
|
||||
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let body = VhostUserMemory::new(ctx.regions.len() as u32);
|
||||
let hdr = node.send_request_with_payload(
|
||||
MasterReq::SET_MEM_TABLE,
|
||||
&body,
|
||||
ctx.regions.as_slice(),
|
||||
Some(ctx.fds.as_slice()),
|
||||
)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
// Clippy doesn't seem to know that if let with && is still experimental
|
||||
#[allow(clippy::unnecessary_unwrap)]
|
||||
fn set_log_base(&mut self, base: u64, fd: Option<RawFd>) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let val = VhostUserU64::new(base);
|
||||
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0
|
||||
&& fd.is_some()
|
||||
{
|
||||
let fds = [fd.unwrap()];
|
||||
let _ = node.send_request_with_body(MasterReq::SET_LOG_BASE, &val, Some(&fds))?;
|
||||
} else {
|
||||
let _ = node.send_request_with_body(MasterReq::SET_LOG_BASE, &val, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_log_fd(&mut self, fd: RawFd) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let fds = [fd];
|
||||
node.send_request_header(MasterReq::SET_LOG_FD, Some(&fds))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the size of the queue.
|
||||
fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let val = VhostUserVringState::new(queue_index as u32, num.into());
|
||||
let hdr = node.send_request_with_body(MasterReq::SET_VRING_NUM, &val, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Sets the addresses of the different aspects of the vring.
|
||||
fn set_vring_addr(&mut self, queue_index: usize, config_data: &VringConfigData) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num
|
||||
|| config_data.flags & !(VhostUserVringAddrFlags::all().bits()) != 0
|
||||
{
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let val = VhostUserVringAddr::from_config_data(queue_index as u32, config_data);
|
||||
let hdr = node.send_request_with_body(MasterReq::SET_VRING_ADDR, &val, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Sets the base offset in the available vring.
|
||||
fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let val = VhostUserVringState::new(queue_index as u32, base.into());
|
||||
let hdr = node.send_request_with_body(MasterReq::SET_VRING_BASE, &val, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_vring_base(&mut self, queue_index: usize) -> Result<u32> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let req = VhostUserVringState::new(queue_index as u32, 0);
|
||||
let hdr = node.send_request_with_body(MasterReq::GET_VRING_BASE, &req, None)?;
|
||||
let reply = node.recv_reply::<VhostUserVringState>(&hdr)?;
|
||||
Ok(reply.num)
|
||||
}
|
||||
|
||||
/// Set the event file descriptor to signal when buffers are used.
|
||||
/// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag
|
||||
/// is set when there is no file descriptor in the ancillary data. This signals that polling
|
||||
/// will be used instead of waiting for the call.
|
||||
fn set_vring_call(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
node.send_fd_for_vring(MasterReq::SET_VRING_CALL, queue_index, fd.as_raw_fd())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the event file descriptor for adding buffers to the vring.
|
||||
/// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag
|
||||
/// is set when there is no file descriptor in the ancillary data. This signals that polling
|
||||
/// should be used instead of waiting for a kick.
|
||||
fn set_vring_kick(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
node.send_fd_for_vring(MasterReq::SET_VRING_KICK, queue_index, fd.as_raw_fd())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the event file descriptor to signal when error occurs.
|
||||
/// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag
|
||||
/// is set when there is no file descriptor in the ancillary data.
|
||||
fn set_vring_err(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
node.send_fd_for_vring(MasterReq::SET_VRING_ERR, queue_index, fd.as_raw_fd())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMaster for Master {
|
||||
fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let flag = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
if node.virtio_features & flag == 0 || node.acked_virtio_features & flag == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
let hdr = node.send_request_header(MasterReq::GET_PROTOCOL_FEATURES, None)?;
|
||||
let val = node.recv_reply::<VhostUserU64>(&hdr)?;
|
||||
node.protocol_features = val.value;
|
||||
// Should we support forward compatibility?
|
||||
// If so just mask out unrecognized flags instead of return errors.
|
||||
match VhostUserProtocolFeatures::from_bits(node.protocol_features) {
|
||||
Some(val) => Ok(val),
|
||||
None => error_code(VhostUserError::InvalidMessage),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_protocol_features(&mut self, features: VhostUserProtocolFeatures) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let flag = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
if node.virtio_features & flag == 0 || node.acked_virtio_features & flag == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
let val = VhostUserU64::new(features.bits());
|
||||
let _ = node.send_request_with_body(MasterReq::SET_PROTOCOL_FEATURES, &val, None)?;
|
||||
// Don't wait for ACK here because the protocol feature negotiation process hasn't been
|
||||
// completed yet.
|
||||
node.acked_protocol_features = features.bits();
|
||||
node.protocol_features_ready = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_queue_num(&mut self) -> Result<u64> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if !node.is_feature_mq_available() {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
let hdr = node.send_request_header(MasterReq::GET_QUEUE_NUM, None)?;
|
||||
let val = node.recv_reply::<VhostUserU64>(&hdr)?;
|
||||
if val.value > VHOST_USER_MAX_VRINGS {
|
||||
return error_code(VhostUserError::InvalidMessage);
|
||||
}
|
||||
node.max_queue_num = val.value;
|
||||
Ok(node.max_queue_num)
|
||||
}
|
||||
|
||||
fn set_vring_enable(&mut self, queue_index: usize, enable: bool) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
// set_vring_enable() is supported only when PROTOCOL_FEATURES has been enabled.
|
||||
if node.acked_virtio_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
} else if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let flag = if enable { 1 } else { 0 };
|
||||
let val = VhostUserVringState::new(queue_index as u32, flag);
|
||||
let hdr = node.send_request_with_body(MasterReq::SET_VRING_ENABLE, &val, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
flags: VhostUserConfigFlags,
|
||||
buf: &[u8],
|
||||
) -> Result<(VhostUserConfig, VhostUserConfigPayload)> {
|
||||
let body = VhostUserConfig::new(offset, size, flags);
|
||||
if !body.is_valid() {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let mut node = self.node.lock().unwrap();
|
||||
// depends on VhostUserProtocolFeatures::CONFIG
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
// vhost-user spec states that:
|
||||
// "Master payload: virtio device config space"
|
||||
// "Slave payload: virtio device config space"
|
||||
let hdr = node.send_request_with_payload(MasterReq::GET_CONFIG, &body, buf, None)?;
|
||||
let (body_reply, buf_reply, rfds) =
|
||||
node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
|
||||
if rfds.is_some() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return error_code(VhostUserError::InvalidMessage);
|
||||
} else if body_reply.size == 0 {
|
||||
return error_code(VhostUserError::SlaveInternalError);
|
||||
} else if body_reply.size != body.size || body_reply.size as usize != buf.len() {
|
||||
return error_code(VhostUserError::InvalidMessage);
|
||||
}
|
||||
Ok((body_reply, buf_reply))
|
||||
}
|
||||
|
||||
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()> {
|
||||
if buf.len() > MAX_MSG_SIZE {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
let body = VhostUserConfig::new(offset, buf.len() as u32, flags);
|
||||
if !body.is_valid() {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let mut node = self.node.lock().unwrap();
|
||||
// depends on VhostUserProtocolFeatures::CONFIG
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
let hdr = node.send_request_with_payload(MasterReq::SET_CONFIG, &body, buf, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn set_slave_request_fd(&mut self, fd: RawFd) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::SLAVE_REQ.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
let fds = [fd];
|
||||
node.send_request_header(MasterReq::SET_SLAVE_REQ_FD, Some(&fds))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Master {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
let node = self.node.lock().unwrap();
|
||||
node.main_sock.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
/// Context object to pass guest memory configuration to VhostUserMaster::set_mem_table().
|
||||
struct VhostUserMemoryContext {
|
||||
regions: VhostUserMemoryPayload,
|
||||
fds: Vec<RawFd>,
|
||||
}
|
||||
|
||||
impl VhostUserMemoryContext {
|
||||
/// Create a context object.
|
||||
pub fn new() -> Self {
|
||||
VhostUserMemoryContext {
|
||||
regions: VhostUserMemoryPayload::new(),
|
||||
fds: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a user memory region and corresponding RawFd into the context object.
|
||||
pub fn append(&mut self, region: &VhostUserMemoryRegion, fd: RawFd) {
|
||||
self.regions.push(*region);
|
||||
self.fds.push(fd);
|
||||
}
|
||||
}
|
||||
|
||||
struct MasterInternal {
|
||||
// Used to send requests to the slave.
|
||||
main_sock: Endpoint<MasterReq>,
|
||||
// Cached virtio features from the slave.
|
||||
virtio_features: u64,
|
||||
// Cached acked virtio features from the driver.
|
||||
acked_virtio_features: u64,
|
||||
// Cached vhost-user protocol features from the slave.
|
||||
protocol_features: u64,
|
||||
// Cached vhost-user protocol features.
|
||||
acked_protocol_features: u64,
|
||||
// Cached vhost-user protocol features are ready to use.
|
||||
protocol_features_ready: bool,
|
||||
// Cached maxinum number of queues supported from the slave.
|
||||
max_queue_num: u64,
|
||||
// Internal flag to mark failure state.
|
||||
error: Option<i32>,
|
||||
}
|
||||
|
||||
impl MasterInternal {
|
||||
fn send_request_header(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
|
||||
self.check_state()?;
|
||||
let hdr = Self::new_request_header(code, 0);
|
||||
self.main_sock.send_header(&hdr, fds)?;
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn send_request_with_body<T: Sized>(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
msg: &T,
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let hdr = Self::new_request_header(code, mem::size_of::<T>() as u32);
|
||||
self.main_sock.send_message(&hdr, msg, fds)?;
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn send_request_with_payload<T: Sized, P: Sized>(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
msg: &T,
|
||||
payload: &[P],
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
|
||||
let len = mem::size_of::<T>() + payload.len() * mem::size_of::<P>();
|
||||
if len > MAX_MSG_SIZE {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
if let Some(ref fd_arr) = fds {
|
||||
if fd_arr.len() > MAX_ATTACHED_FD_ENTRIES {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let hdr = Self::new_request_header(code, len as u32);
|
||||
self.main_sock
|
||||
.send_message_with_payload(&hdr, msg, payload, fds)?;
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn send_fd_for_vring(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
queue_index: usize,
|
||||
fd: RawFd,
|
||||
) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
|
||||
if queue_index as u64 >= self.max_queue_num {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag.
|
||||
// This flag is set when there is no file descriptor in the ancillary data. This signals
|
||||
// that polling will be used instead of waiting for the call.
|
||||
let msg = VhostUserU64::new(queue_index as u64);
|
||||
let hdr = Self::new_request_header(code, mem::size_of::<VhostUserU64>() as u32);
|
||||
self.main_sock.send_message(&hdr, &msg, Some(&[fd]))?;
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn recv_reply<T: Sized + Default + VhostUserMsgValidator>(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
) -> VhostUserResult<T> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE || hdr.is_reply() {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let (reply, body, rfds) = self.main_sock.recv_body::<T>()?;
|
||||
if !reply.is_reply_for(&hdr) || rfds.is_some() || !body.is_valid() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(VhostUserError::InvalidMessage);
|
||||
}
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
fn recv_reply_with_payload<T: Sized + Default + VhostUserMsgValidator>(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
) -> VhostUserResult<(T, Vec<u8>, Option<Vec<RawFd>>)> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE || hdr.is_reply() {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let mut buf: Vec<u8> = vec![0; hdr.get_size() as usize - mem::size_of::<T>()];
|
||||
let (reply, body, bytes, rfds) = self.main_sock.recv_payload_into_buf::<T>(&mut buf)?;
|
||||
if !reply.is_reply_for(hdr)
|
||||
|| reply.get_size() as usize != mem::size_of::<T>() + bytes
|
||||
|| rfds.is_some()
|
||||
|| !body.is_valid()
|
||||
{
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(VhostUserError::InvalidMessage);
|
||||
} else if bytes > MAX_MSG_SIZE - mem::size_of::<T>() {
|
||||
return Err(VhostUserError::InvalidMessage);
|
||||
} else if bytes < buf.len() {
|
||||
// It's safe because we have checked the buffer size
|
||||
unsafe { buf.set_len(bytes) };
|
||||
}
|
||||
Ok((body, buf, rfds))
|
||||
}
|
||||
|
||||
fn wait_for_ack(&mut self, hdr: &VhostUserMsgHeader<MasterReq>) -> VhostUserResult<()> {
|
||||
if self.acked_protocol_features & VhostUserProtocolFeatures::REPLY_ACK.bits() == 0
|
||||
|| !hdr.is_need_reply()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let (reply, body, rfds) = self.main_sock.recv_body::<VhostUserU64>()?;
|
||||
if !reply.is_reply_for(&hdr) || rfds.is_some() || !body.is_valid() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(VhostUserError::InvalidMessage);
|
||||
}
|
||||
if body.value != 0 {
|
||||
return Err(VhostUserError::SlaveInternalError);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_feature_mq_available(&self) -> bool {
|
||||
self.acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() != 0
|
||||
}
|
||||
|
||||
fn check_state(&self) -> VhostUserResult<()> {
|
||||
match self.error {
|
||||
Some(e) => Err(VhostUserError::SocketBroken(
|
||||
std::io::Error::from_raw_os_error(e),
|
||||
)),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_request_header(request: MasterReq, size: u32) -> VhostUserMsgHeader<MasterReq> {
|
||||
// TODO: handle NEED_REPLY flag
|
||||
VhostUserMsgHeader::new(request, 0x1, size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::connection::Listener;
|
||||
use super::*;
|
||||
|
||||
const UNIX_SOCKET_MASTER: &'static str = "/tmp/vhost_user_test_rust_master";
|
||||
const UNIX_SOCKET_MASTER2: &'static str = "/tmp/vhost_user_test_rust_master2";
|
||||
const UNIX_SOCKET_MASTER3: &'static str = "/tmp/vhost_user_test_rust_master3";
|
||||
const UNIX_SOCKET_MASTER4: &'static str = "/tmp/vhost_user_test_rust_master4";
|
||||
|
||||
fn create_pair(path: &str) -> (Master, Endpoint<MasterReq>) {
|
||||
let listener = Listener::new(path, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
let master = Master::connect(path, 2).unwrap();
|
||||
let slave = listener.accept().unwrap().unwrap();
|
||||
(master, Endpoint::from_stream(slave))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_master() {
|
||||
let listener = Listener::new(UNIX_SOCKET_MASTER, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
|
||||
let mut master = Master::connect(UNIX_SOCKET_MASTER, 2).unwrap();
|
||||
let mut slave = Endpoint::<MasterReq>::from_stream(listener.accept().unwrap().unwrap());
|
||||
|
||||
// Send two messages continuously
|
||||
master.set_owner().unwrap();
|
||||
master.reset_owner().unwrap();
|
||||
|
||||
let (hdr, rfds) = slave.recv_header().unwrap();
|
||||
assert_eq!(hdr.get_code(), MasterReq::SET_OWNER);
|
||||
assert_eq!(hdr.get_size(), 0);
|
||||
assert_eq!(hdr.get_version(), 0x1);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
let (hdr, rfds) = slave.recv_header().unwrap();
|
||||
assert_eq!(hdr.get_code(), MasterReq::RESET_OWNER);
|
||||
assert_eq!(hdr.get_size(), 0);
|
||||
assert_eq!(hdr.get_version(), 0x1);
|
||||
assert!(rfds.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_failure() {
|
||||
let _ = Listener::new(UNIX_SOCKET_MASTER2, true).unwrap();
|
||||
let _ = Listener::new(UNIX_SOCKET_MASTER2, false).is_err();
|
||||
assert!(Master::connect(UNIX_SOCKET_MASTER2, 2).is_err());
|
||||
|
||||
let listener = Listener::new(UNIX_SOCKET_MASTER2, true).unwrap();
|
||||
assert!(Listener::new(UNIX_SOCKET_MASTER2, false).is_err());
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
|
||||
let _master = Master::connect(UNIX_SOCKET_MASTER2, 2).unwrap();
|
||||
let _slave = listener.accept().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_features() {
|
||||
let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER3);
|
||||
|
||||
master.set_owner().unwrap();
|
||||
let (hdr, rfds) = peer.recv_header().unwrap();
|
||||
assert_eq!(hdr.get_code(), MasterReq::SET_OWNER);
|
||||
assert_eq!(hdr.get_size(), 0);
|
||||
assert_eq!(hdr.get_version(), 0x1);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
let hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0x4, 8);
|
||||
let msg = VhostUserU64::new(0x15);
|
||||
peer.send_message(&hdr, &msg, None).unwrap();
|
||||
let features = master.get_features().unwrap();
|
||||
assert_eq!(features, 0x15u64);
|
||||
let (_hdr, rfds) = peer.recv_header().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
|
||||
master.set_features(0x15).unwrap();
|
||||
let (_hdr, msg, rfds) = peer.recv_body::<VhostUserU64>().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
let val = msg.value;
|
||||
assert_eq!(val, 0x15);
|
||||
|
||||
let hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0x4, 8);
|
||||
let msg = 0x15u32;
|
||||
peer.send_message(&hdr, &msg, None).unwrap();
|
||||
assert!(master.get_features().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_protocol_features() {
|
||||
let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER4);
|
||||
|
||||
master.set_owner().unwrap();
|
||||
let (hdr, rfds) = peer.recv_header().unwrap();
|
||||
assert_eq!(hdr.get_code(), MasterReq::SET_OWNER);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
assert!(master.get_protocol_features().is_err());
|
||||
assert!(master
|
||||
.set_protocol_features(VhostUserProtocolFeatures::all())
|
||||
.is_err());
|
||||
|
||||
let vfeatures = 0x15 | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
let hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0x4, 8);
|
||||
let msg = VhostUserU64::new(vfeatures);
|
||||
peer.send_message(&hdr, &msg, None).unwrap();
|
||||
let features = master.get_features().unwrap();
|
||||
assert_eq!(features, vfeatures);
|
||||
let (_hdr, rfds) = peer.recv_header().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
|
||||
master.set_features(vfeatures).unwrap();
|
||||
let (_hdr, msg, rfds) = peer.recv_body::<VhostUserU64>().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
let val = msg.value;
|
||||
assert_eq!(val, vfeatures);
|
||||
|
||||
let pfeatures = VhostUserProtocolFeatures::all();
|
||||
let hdr = VhostUserMsgHeader::new(MasterReq::GET_PROTOCOL_FEATURES, 0x4, 8);
|
||||
let msg = VhostUserU64::new(pfeatures.bits());
|
||||
peer.send_message(&hdr, &msg, None).unwrap();
|
||||
let features = master.get_protocol_features().unwrap();
|
||||
assert_eq!(features, pfeatures);
|
||||
let (_hdr, rfds) = peer.recv_header().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
|
||||
master.set_protocol_features(pfeatures).unwrap();
|
||||
let (_hdr, msg, rfds) = peer.recv_body::<VhostUserU64>().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
let val = msg.value;
|
||||
assert_eq!(val, pfeatures.bits());
|
||||
|
||||
let hdr = VhostUserMsgHeader::new(MasterReq::SET_PROTOCOL_FEATURES, 0x4, 8);
|
||||
let msg = VhostUserU64::new(pfeatures.bits());
|
||||
peer.send_message(&hdr, &msg, None).unwrap();
|
||||
assert!(master.get_protocol_features().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_mem_table() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_ring_num() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Traits and Structs to handle vhost-user requests from the slave to the master.
|
||||
|
||||
use libc;
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::connection::Endpoint;
|
||||
use super::message::*;
|
||||
use super::{Error, HandlerResult, Result};
|
||||
|
||||
/// Trait to handle vhost-user requests from the slave to the master.
|
||||
pub trait VhostUserMasterReqHandler {
|
||||
// fn handle_iotlb_msg(&mut self, iotlb: VhostUserIotlb);
|
||||
// fn handle_vring_host_notifier(&mut self, area: VhostUserVringArea, fd: RawFd);
|
||||
|
||||
/// Handle device configuration change notifications from the slave.
|
||||
fn handle_config_change(&mut self) -> HandlerResult<()> {
|
||||
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
}
|
||||
|
||||
/// Handle virtio-fs map file requests from the slave.
|
||||
fn fs_slave_map(&mut self, _fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<()> {
|
||||
// Safe because we have just received the rawfd from kernel.
|
||||
unsafe { libc::close(fd) };
|
||||
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
}
|
||||
|
||||
/// Handle virtio-fs unmap file requests from the slave.
|
||||
fn fs_slave_unmap(&mut self, _fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
|
||||
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
}
|
||||
|
||||
/// Handle virtio-fs sync file requests from the slave.
|
||||
fn fs_slave_sync(&mut self, _fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
|
||||
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
}
|
||||
}
|
||||
|
||||
/// A vhost-user master request endpoint which relays all received requests from the slave to the
|
||||
/// provided request handler.
|
||||
pub struct MasterReqHandler<S: VhostUserMasterReqHandler> {
|
||||
// underlying Unix domain socket for communication
|
||||
sub_sock: Endpoint<SlaveReq>,
|
||||
tx_sock: UnixStream,
|
||||
// the VirtIO backend device object
|
||||
backend: Arc<Mutex<S>>,
|
||||
// whether the endpoint has encountered any failure
|
||||
error: Option<i32>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
/// Create a vhost-user slave request handler.
|
||||
/// This opens a pair of connected anonymous sockets.
|
||||
/// Returns Self and the socket that must be sent to the slave via SET_SLAVE_REQ_FD.
|
||||
pub fn new(backend: Arc<Mutex<S>>) -> Result<Self> {
|
||||
let (tx, rx) = UnixStream::pair().map_err(Error::SocketError)?;
|
||||
|
||||
Ok(MasterReqHandler {
|
||||
sub_sock: Endpoint::<SlaveReq>::from_stream(rx),
|
||||
tx_sock: tx,
|
||||
backend,
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the raw fd to send to the slave as slave communication channel.
|
||||
pub fn get_tx_raw_fd(&self) -> RawFd {
|
||||
self.tx_sock.as_raw_fd()
|
||||
}
|
||||
|
||||
/// Mark endpoint as failed or normal state.
|
||||
pub fn set_failed(&mut self, error: i32) {
|
||||
self.error = Some(error);
|
||||
}
|
||||
|
||||
/// Receive and handle one incoming request message from the slave.
|
||||
/// The caller needs to:
|
||||
/// . serialize calls to this function
|
||||
/// . decide what to do when errer happens
|
||||
/// . optional recover from failure
|
||||
pub fn handle_request(&mut self) -> Result<()> {
|
||||
// Return error if the endpoint is already in failed state.
|
||||
self.check_state()?;
|
||||
|
||||
// The underlying communication channel is a Unix domain socket in
|
||||
// stream mode, and recvmsg() is a little tricky here. To successfully
|
||||
// receive attached file descriptors, we need to receive messages and
|
||||
// corresponding attached file descriptors in this way:
|
||||
// . recv messsage header and optional attached file
|
||||
// . validate message header
|
||||
// . recv optional message body and payload according size field in
|
||||
// message header
|
||||
// . validate message body and optional payload
|
||||
let (hdr, rfds) = self.sub_sock.recv_header()?;
|
||||
let rfds = self.check_attached_rfds(&hdr, rfds)?;
|
||||
let (size, buf) = match hdr.get_size() {
|
||||
0 => (0, vec![0u8; 0]),
|
||||
len => {
|
||||
let (size2, rbuf) = self.sub_sock.recv_data(len as usize)?;
|
||||
if size2 != len as usize {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
(size2, rbuf)
|
||||
}
|
||||
};
|
||||
|
||||
let res = match hdr.get_code() {
|
||||
SlaveReq::CONFIG_CHANGE_MSG => {
|
||||
self.check_msg_size(&hdr, size, 0)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.handle_config_change()
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_MAP => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_map(msg, rfds.unwrap()[0])
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_UNMAP => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_unmap(msg)
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_SYNC => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_sync(msg)
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
_ => Err(Error::InvalidMessage),
|
||||
};
|
||||
|
||||
self.send_ack_message(&hdr, &res)?;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn check_state(&self) -> Result<()> {
|
||||
match self.error {
|
||||
Some(e) => Err(Error::SocketBroken(std::io::Error::from_raw_os_error(e))),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_msg_size(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<SlaveReq>,
|
||||
size: usize,
|
||||
expected: usize,
|
||||
) -> Result<()> {
|
||||
if hdr.get_size() as usize != expected
|
||||
|| hdr.is_reply()
|
||||
|| hdr.get_version() != 0x1
|
||||
|| size != expected
|
||||
{
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_attached_rfds(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<SlaveReq>,
|
||||
rfds: Option<Vec<RawFd>>,
|
||||
) -> Result<Option<Vec<RawFd>>> {
|
||||
match hdr.get_code() {
|
||||
SlaveReq::FS_MAP => {
|
||||
// Expect an fd set with a single fd.
|
||||
match rfds {
|
||||
None => Err(Error::InvalidMessage),
|
||||
Some(fds) => {
|
||||
if fds.len() != 1 {
|
||||
Endpoint::<SlaveReq>::close_rfds(Some(fds));
|
||||
Err(Error::InvalidMessage)
|
||||
} else {
|
||||
Ok(Some(fds))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if rfds.is_some() {
|
||||
Endpoint::<SlaveReq>::close_rfds(rfds);
|
||||
Err(Error::InvalidMessage)
|
||||
} else {
|
||||
Ok(rfds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_msg_body<'a, T: Sized + VhostUserMsgValidator>(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<SlaveReq>,
|
||||
size: usize,
|
||||
buf: &'a [u8],
|
||||
) -> Result<&'a T> {
|
||||
self.check_msg_size(hdr, size, mem::size_of::<T>())?;
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const T) };
|
||||
if !msg.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
fn new_reply_header<T: Sized>(
|
||||
&self,
|
||||
req: &VhostUserMsgHeader<SlaveReq>,
|
||||
) -> Result<VhostUserMsgHeader<SlaveReq>> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
Ok(VhostUserMsgHeader::new(
|
||||
req.get_code(),
|
||||
VhostUserHeaderFlag::REPLY.bits(),
|
||||
mem::size_of::<T>() as u32,
|
||||
))
|
||||
}
|
||||
|
||||
fn send_ack_message(
|
||||
&mut self,
|
||||
req: &VhostUserMsgHeader<SlaveReq>,
|
||||
res: &Result<()>,
|
||||
) -> Result<()> {
|
||||
if req.is_need_reply() {
|
||||
let hdr = self.new_reply_header::<VhostUserU64>(req)?;
|
||||
let val = match res {
|
||||
Ok(_) => 0,
|
||||
Err(_) => 1,
|
||||
};
|
||||
let msg = VhostUserU64::new(val);
|
||||
self.sub_sock.send_message(&hdr, &msg, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserMasterReqHandler> AsRawFd for MasterReqHandler<S> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.sub_sock.as_raw_fd()
|
||||
}
|
||||
}
|
||||
@@ -1,811 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Define communication messages for the vhost-user protocol.
|
||||
//!
|
||||
//! For message definition, please refer to the [vhost-user spec](https://github.com/qemu/qemu/blob/f7526eece29cd2e36a63b6703508b24453095eb8/docs/interop/vhost-user.txt).
|
||||
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use VringConfigData;
|
||||
|
||||
/// The vhost-user specification uses a field of u32 to store message length.
|
||||
/// On the other hand, preallocated buffers are needed to receive messages from the Unix domain
|
||||
/// socket. To preallocating a 4GB buffer for each vhost-user message is really just an overhead.
|
||||
/// Among all defined vhost-user messages, only the VhostUserConfig and VhostUserMemory has variable
|
||||
/// message size. For the VhostUserConfig, a maximum size of 4K is enough because the user
|
||||
/// configuration space for virtio devices is (4K - 0x100) bytes at most. For the VhostUserMemory,
|
||||
/// 4K should be enough too because it can support 255 memory regions at most.
|
||||
pub const MAX_MSG_SIZE: usize = 0x1000;
|
||||
|
||||
/// The VhostUserMemory message has variable message size and variable number of attached file
|
||||
/// descriptors. Each user memory region entry in the message payload occupies 32 bytes,
|
||||
/// so setting maximum number of attached file descriptors based on the maximum message size.
|
||||
/// But rust only implements Default and AsMut traits for arrays with 0 - 32 entries, so further
|
||||
/// reduce the maximum number...
|
||||
// pub const MAX_ATTACHED_FD_ENTRIES: usize = (MAX_MSG_SIZE - 8) / 32;
|
||||
pub const MAX_ATTACHED_FD_ENTRIES: usize = 32;
|
||||
|
||||
/// Starting position (inclusion) of the device configuration space in virtio devices.
|
||||
pub const VHOST_USER_CONFIG_OFFSET: u32 = 0x100;
|
||||
|
||||
/// Ending position (exclusion) of the device configuration space in virtio devices.
|
||||
pub const VHOST_USER_CONFIG_SIZE: u32 = 0x1000;
|
||||
|
||||
/// Maximum number of vrings supported.
|
||||
pub const VHOST_USER_MAX_VRINGS: u64 = 0xFFu64;
|
||||
|
||||
pub(super) trait Req:
|
||||
Clone + Copy + Debug + PartialEq + Eq + PartialOrd + Ord + Into<u32>
|
||||
{
|
||||
fn is_valid(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Type of requests sending from masters to slaves.
|
||||
#[repr(u32)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum MasterReq {
|
||||
/// Null operation.
|
||||
NOOP = 0,
|
||||
/// Get from the underlying vhost implementation the features bit mask.
|
||||
GET_FEATURES = 1,
|
||||
/// Enable features in the underlying vhost implementation using a bit mask.
|
||||
SET_FEATURES = 2,
|
||||
/// Set the current Master as an owner of the session.
|
||||
SET_OWNER = 3,
|
||||
/// No longer used.
|
||||
RESET_OWNER = 4,
|
||||
/// Set the memory map regions on the slave so it can translate the vring addresses.
|
||||
SET_MEM_TABLE = 5,
|
||||
/// Set logging shared memory space.
|
||||
SET_LOG_BASE = 6,
|
||||
/// Set the logging file descriptor, which is passed as ancillary data.
|
||||
SET_LOG_FD = 7,
|
||||
/// Set the size of the queue.
|
||||
SET_VRING_NUM = 8,
|
||||
/// Set the addresses of the different aspects of the vring.
|
||||
SET_VRING_ADDR = 9,
|
||||
/// Set the base offset in the available vring.
|
||||
SET_VRING_BASE = 10,
|
||||
/// Get the available vring base offset.
|
||||
GET_VRING_BASE = 11,
|
||||
/// Set the event file descriptor for adding buffers to the vring.
|
||||
SET_VRING_KICK = 12,
|
||||
/// Set the event file descriptor to signal when buffers are used.
|
||||
SET_VRING_CALL = 13,
|
||||
/// Set the event file descriptor to signal when error occurs.
|
||||
SET_VRING_ERR = 14,
|
||||
/// Get the protocol feature bit mask from the underlying vhost implementation.
|
||||
GET_PROTOCOL_FEATURES = 15,
|
||||
/// Enable protocol features in the underlying vhost implementation.
|
||||
SET_PROTOCOL_FEATURES = 16,
|
||||
/// Query how many queues the backend supports.
|
||||
GET_QUEUE_NUM = 17,
|
||||
/// Signal slave to enable or disable corresponding vring.
|
||||
SET_VRING_ENABLE = 18,
|
||||
/// Ask vhost user backend to broadcast a fake RARP to notify the migration is terminated
|
||||
/// for guest that does not support GUEST_ANNOUNCE.
|
||||
SEND_RARP = 19,
|
||||
/// Set host MTU value exposed to the guest.
|
||||
NET_SET_MTU = 20,
|
||||
/// Set the socket file descriptor for slave initiated requests.
|
||||
SET_SLAVE_REQ_FD = 21,
|
||||
/// Send IOTLB messages with struct vhost_iotlb_msg as payload.
|
||||
IOTLB_MSG = 22,
|
||||
/// Set the endianness of a VQ for legacy devices.
|
||||
SET_VRING_ENDIAN = 23,
|
||||
/// Fetch the contents of the virtio device configuration space.
|
||||
GET_CONFIG = 24,
|
||||
/// Change the contents of the virtio device configuration space.
|
||||
SET_CONFIG = 25,
|
||||
/// Create a session for crypto operation.
|
||||
CREATE_CRYPTO_SESSION = 26,
|
||||
/// Close a session for crypto operation.
|
||||
CLOSE_CRYPTO_SESSION = 27,
|
||||
/// Advise slave that a migration with postcopy enabled is underway.
|
||||
POSTCOPY_ADVISE = 28,
|
||||
/// Advise slave that a transition to postcopy mode has happened.
|
||||
POSTCOPY_LISTEN = 29,
|
||||
/// Advise that postcopy migration has now completed.
|
||||
POSTCOPY_END = 30,
|
||||
/// Get a shared buffer from slave.
|
||||
GET_INFLIGHT_FD = 31,
|
||||
/// Send the shared inflight buffer back to slave
|
||||
SET_INFLIGHT_FD = 32,
|
||||
/// Upper bound of valid commands.
|
||||
MAX_CMD = 33,
|
||||
}
|
||||
|
||||
impl Into<u32> for MasterReq {
|
||||
fn into(self) -> u32 {
|
||||
self as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl Req for MasterReq {
|
||||
fn is_valid(&self) -> bool {
|
||||
(*self > MasterReq::NOOP) && (*self < MasterReq::MAX_CMD)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of requests sending from slaves to masters.
|
||||
#[repr(u32)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SlaveReq {
|
||||
/// Null operation.
|
||||
NOOP = 0,
|
||||
/// Send IOTLB messages with struct vhost_iotlb_msg as payload.
|
||||
IOTLB_MSG = 1,
|
||||
/// Notify that the virtio device's configuration space has changed.
|
||||
CONFIG_CHANGE_MSG = 2,
|
||||
/// Set host notifier for a specified queue.
|
||||
VRING_HOST_NOTIFIER_MSG = 3,
|
||||
/// Virtio-fs draft: map file content into the window.
|
||||
FS_MAP = 4,
|
||||
/// Virtio-fs draft: unmap file content from the window.
|
||||
FS_UNMAP = 5,
|
||||
/// Virtio-fs draft: sync file content.
|
||||
FS_SYNC = 6,
|
||||
/// Upper bound of valid commands.
|
||||
MAX_CMD = 7,
|
||||
}
|
||||
|
||||
impl Into<u32> for SlaveReq {
|
||||
fn into(self) -> u32 {
|
||||
self as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl Req for SlaveReq {
|
||||
fn is_valid(&self) -> bool {
|
||||
(*self > SlaveReq::NOOP) && (*self < SlaveReq::MAX_CMD)
|
||||
}
|
||||
}
|
||||
|
||||
/// Vhost message Validator.
|
||||
pub trait VhostUserMsgValidator {
|
||||
/// Validate message syntax only.
|
||||
/// It doesn't validate message semantics such as protocol version number and dependency
|
||||
/// on feature flags etc.
|
||||
fn is_valid(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Common message flags for vhost-user requests and replies.
|
||||
pub struct VhostUserHeaderFlag: u32 {
|
||||
/// Bits[0..2] is message version number.
|
||||
const VERSION = 0x3;
|
||||
/// Mark message as reply.
|
||||
const REPLY = 0x4;
|
||||
/// Sender anticipates a reply message from the peer.
|
||||
const NEED_REPLY = 0x8;
|
||||
/// All valid bits.
|
||||
const ALL_FLAGS = 0xc;
|
||||
/// All reserved bits.
|
||||
const RESERVED_BITS = !0xf;
|
||||
}
|
||||
}
|
||||
|
||||
/// Common message header for vhost-user requests and replies.
|
||||
/// A vhost-user message consists of 3 header fields and an optional payload. All numbers are in the
|
||||
/// machine native byte order.
|
||||
#[allow(safe_packed_borrows)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(super) struct VhostUserMsgHeader<R: Req> {
|
||||
request: u32,
|
||||
flags: u32,
|
||||
size: u32,
|
||||
_r: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<R: Req> VhostUserMsgHeader<R> {
|
||||
/// Create a new instance of `VhostUserMsgHeader`.
|
||||
pub fn new(request: R, flags: u32, size: u32) -> Self {
|
||||
// Default to protocol version 1
|
||||
let fl = (flags & VhostUserHeaderFlag::ALL_FLAGS.bits()) | 0x1;
|
||||
VhostUserMsgHeader {
|
||||
request: request.into(),
|
||||
flags: fl,
|
||||
size,
|
||||
_r: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get message type.
|
||||
pub fn get_code(&self) -> R {
|
||||
// It's safe because R is marked as repr(u32).
|
||||
unsafe { std::mem::transmute_copy::<u32, R>(&self.request) }
|
||||
}
|
||||
|
||||
/// Set message type.
|
||||
pub fn set_code(&mut self, request: R) {
|
||||
self.request = request.into();
|
||||
}
|
||||
|
||||
/// Get message version number.
|
||||
pub fn get_version(&self) -> u32 {
|
||||
self.flags & 0x3
|
||||
}
|
||||
|
||||
/// Set message version number.
|
||||
pub fn set_version(&mut self, ver: u32) {
|
||||
self.flags &= !0x3;
|
||||
self.flags |= ver & 0x3;
|
||||
}
|
||||
|
||||
/// Check whether it's a reply message.
|
||||
pub fn is_reply(&self) -> bool {
|
||||
(self.flags & VhostUserHeaderFlag::REPLY.bits()) != 0
|
||||
}
|
||||
|
||||
/// Mark message as reply.
|
||||
pub fn set_reply(&mut self, is_reply: bool) {
|
||||
if is_reply {
|
||||
self.flags |= VhostUserHeaderFlag::REPLY.bits();
|
||||
} else {
|
||||
self.flags &= !VhostUserHeaderFlag::REPLY.bits();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether reply for this message is requested.
|
||||
pub fn is_need_reply(&self) -> bool {
|
||||
(self.flags & VhostUserHeaderFlag::NEED_REPLY.bits()) != 0
|
||||
}
|
||||
|
||||
/// Mark that reply for this message is needed.
|
||||
pub fn set_need_reply(&mut self, need_reply: bool) {
|
||||
if need_reply {
|
||||
self.flags |= VhostUserHeaderFlag::NEED_REPLY.bits();
|
||||
} else {
|
||||
self.flags &= !VhostUserHeaderFlag::NEED_REPLY.bits();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether it's the reply message for the request `req`.
|
||||
pub fn is_reply_for(&self, req: &VhostUserMsgHeader<R>) -> bool {
|
||||
self.is_reply() && !req.is_reply() && self.get_code() == req.get_code()
|
||||
}
|
||||
|
||||
/// Get message size.
|
||||
pub fn get_size(&self) -> u32 {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Set message size.
|
||||
pub fn set_size(&mut self, size: u32) {
|
||||
self.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Req> Default for VhostUserMsgHeader<R> {
|
||||
fn default() -> Self {
|
||||
VhostUserMsgHeader {
|
||||
request: 0,
|
||||
flags: 0x1,
|
||||
size: 0,
|
||||
_r: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Req> VhostUserMsgValidator for VhostUserMsgHeader<T> {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn is_valid(&self) -> bool {
|
||||
if !self.get_code().is_valid() {
|
||||
return false;
|
||||
} else if self.size as usize > MAX_MSG_SIZE {
|
||||
return false;
|
||||
} else if self.get_version() != 0x1 {
|
||||
return false;
|
||||
} else if (self.flags & VhostUserHeaderFlag::RESERVED_BITS.bits()) != 0 {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Transport specific flags in VirtIO feature set defined by vhost-user.
|
||||
pub struct VhostUserVirtioFeatures: u64 {
|
||||
/// Feature flag for the protocol feature.
|
||||
const PROTOCOL_FEATURES = 0x4000_0000;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Vhost-user protocol feature flags.
|
||||
pub struct VhostUserProtocolFeatures: u64 {
|
||||
/// Support multiple queues.
|
||||
const MQ = 0x0000_0001;
|
||||
/// Support logging through shared memory fd.
|
||||
const LOG_SHMFD = 0x0000_0002;
|
||||
/// Support broadcasting fake RARP packet.
|
||||
const RARP = 0x0000_0004;
|
||||
/// Support sending reply messages for requests with NEED_REPLY flag set.
|
||||
const REPLY_ACK = 0x0000_0008;
|
||||
/// Support setting MTU for virtio-net devices.
|
||||
const MTU = 0x0000_0010;
|
||||
/// Allow the slave to send requests to the master by an optional communication channel.
|
||||
const SLAVE_REQ = 0x0000_0020;
|
||||
/// Support setting slave endian by SET_VRING_ENDIAN.
|
||||
const CROSS_ENDIAN = 0x0000_0040;
|
||||
/// Support crypto operations.
|
||||
const CRYPTO_SESSION = 0x0000_0080;
|
||||
/// Support sending userfault_fd from slaves to masters.
|
||||
const PAGEFAULT = 0x0000_0100;
|
||||
/// Support Virtio device configuration.
|
||||
const CONFIG = 0x0000_0200;
|
||||
/// Allow the slave to send fds (at most 8 descriptors in each message) to the master.
|
||||
const SLAVE_SEND_FD = 0x0000_0400;
|
||||
/// Allow the slave to register a host notifier.
|
||||
const HOST_NOTIFIER = 0x0000_0800;
|
||||
/// Support inflight shmfd.
|
||||
const INFLIGHT_SHMFD = 0x0000_1000;
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic message to encapsulate a 64-bit value.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserU64 {
|
||||
/// The encapsulated 64-bit common value.
|
||||
pub value: u64,
|
||||
}
|
||||
|
||||
impl VhostUserU64 {
|
||||
/// Create a new instance.
|
||||
pub fn new(value: u64) -> Self {
|
||||
VhostUserU64 { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserU64 {}
|
||||
|
||||
/// Memory region descriptor for the SET_MEM_TABLE request.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserMemory {
|
||||
/// Number of memory regions in the payload.
|
||||
pub num_regions: u32,
|
||||
/// Padding for alignment.
|
||||
pub padding1: u32,
|
||||
}
|
||||
|
||||
impl VhostUserMemory {
|
||||
/// Create a new instance.
|
||||
pub fn new(cnt: u32) -> Self {
|
||||
VhostUserMemory {
|
||||
num_regions: cnt,
|
||||
padding1: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserMemory {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn is_valid(&self) -> bool {
|
||||
if self.padding1 != 0 {
|
||||
return false;
|
||||
} else if self.num_regions == 0 || self.num_regions > MAX_ATTACHED_FD_ENTRIES as u32 {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory region descriptors as payload for the SET_MEM_TABLE request.
|
||||
#[repr(packed)]
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct VhostUserMemoryRegion {
|
||||
/// Guest physical address of the memory region.
|
||||
pub guest_phys_addr: u64,
|
||||
/// Size of the memory region.
|
||||
pub memory_size: u64,
|
||||
/// Virtual address in the current process.
|
||||
pub user_addr: u64,
|
||||
/// Offset where region starts in the mapped memory.
|
||||
pub mmap_offset: u64,
|
||||
}
|
||||
|
||||
impl VhostUserMemoryRegion {
|
||||
/// Create a new instance.
|
||||
pub fn new(guest_phys_addr: u64, memory_size: u64, user_addr: u64, mmap_offset: u64) -> Self {
|
||||
VhostUserMemoryRegion {
|
||||
guest_phys_addr,
|
||||
memory_size,
|
||||
user_addr,
|
||||
mmap_offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserMemoryRegion {
|
||||
fn is_valid(&self) -> bool {
|
||||
if self.memory_size == 0
|
||||
|| self.guest_phys_addr.checked_add(self.memory_size).is_none()
|
||||
|| self.user_addr.checked_add(self.memory_size).is_none()
|
||||
|| self.mmap_offset.checked_add(self.memory_size).is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload of the VhostUserMemory message.
|
||||
pub type VhostUserMemoryPayload = Vec<VhostUserMemoryRegion>;
|
||||
|
||||
/// Vring state descriptor.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserVringState {
|
||||
/// Vring index.
|
||||
pub index: u32,
|
||||
/// A common 32bit value to encapsulate vring state etc.
|
||||
pub num: u32,
|
||||
}
|
||||
|
||||
impl VhostUserVringState {
|
||||
/// Create a new instance.
|
||||
pub fn new(index: u32, num: u32) -> Self {
|
||||
VhostUserVringState { index, num }
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserVringState {}
|
||||
|
||||
bitflags! {
|
||||
/// Flags for vring address.
|
||||
pub struct VhostUserVringAddrFlags: u32 {
|
||||
/// Support log of vring operations.
|
||||
/// Modifications to "used" vring should be logged.
|
||||
const VHOST_VRING_F_LOG = 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Vring address descriptor.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserVringAddr {
|
||||
/// Vring index.
|
||||
pub index: u32,
|
||||
/// Vring flags defined by VhostUserVringAddrFlags.
|
||||
pub flags: u32,
|
||||
/// Ring address of the vring descriptor table.
|
||||
pub descriptor: u64,
|
||||
/// Ring address of the vring used ring.
|
||||
pub used: u64,
|
||||
/// Ring address of the vring available ring.
|
||||
pub available: u64,
|
||||
/// Guest address for logging.
|
||||
pub log: u64,
|
||||
}
|
||||
|
||||
impl VhostUserVringAddr {
|
||||
/// Create a new instance.
|
||||
pub fn new(
|
||||
index: u32,
|
||||
flags: VhostUserVringAddrFlags,
|
||||
descriptor: u64,
|
||||
used: u64,
|
||||
available: u64,
|
||||
log: u64,
|
||||
) -> Self {
|
||||
VhostUserVringAddr {
|
||||
index,
|
||||
flags: flags.bits(),
|
||||
descriptor,
|
||||
used,
|
||||
available,
|
||||
log,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new instance from `VringConfigData`.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::identity_conversion))]
|
||||
pub fn from_config_data(index: u32, config_data: &VringConfigData) -> Self {
|
||||
let log_addr = config_data.log_addr.unwrap_or(0);
|
||||
VhostUserVringAddr {
|
||||
index,
|
||||
flags: config_data.flags,
|
||||
descriptor: config_data.desc_table_addr,
|
||||
used: config_data.used_ring_addr,
|
||||
available: config_data.avail_ring_addr,
|
||||
log: log_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserVringAddr {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn is_valid(&self) -> bool {
|
||||
if (self.flags & !VhostUserVringAddrFlags::all().bits()) != 0 {
|
||||
return false;
|
||||
} else if self.descriptor & 0xf != 0 {
|
||||
return false;
|
||||
} else if self.available & 0x1 != 0 {
|
||||
return false;
|
||||
} else if self.used & 0x3 != 0 {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Flags for the device configuration message.
|
||||
pub struct VhostUserConfigFlags: u32 {
|
||||
/// Vhost master messages used for writeable fields.
|
||||
const WRITABLE = 0x0;
|
||||
/// Vhost master messages used for live migration.
|
||||
const LIVE_MIGRATION = 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Message to read/write device configuration space.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserConfig {
|
||||
/// Offset of virtio device's configuration space.
|
||||
pub offset: u32,
|
||||
/// Configuration space access size in bytes.
|
||||
pub size: u32,
|
||||
/// Flags for the device configuration operation.
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
impl VhostUserConfig {
|
||||
/// Create a new instance.
|
||||
pub fn new(offset: u32, size: u32, flags: VhostUserConfigFlags) -> Self {
|
||||
VhostUserConfig {
|
||||
offset,
|
||||
size,
|
||||
flags: flags.bits(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserConfig {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn is_valid(&self) -> bool {
|
||||
if (self.flags & !VhostUserConfigFlags::all().bits()) != 0 {
|
||||
return false;
|
||||
} else if self.offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| self.size == 0
|
||||
|| self.size > VHOST_USER_CONFIG_SIZE
|
||||
|| self.size + self.offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload for the VhostUserConfig message.
|
||||
pub type VhostUserConfigPayload = Vec<u8>;
|
||||
|
||||
/*
|
||||
* TODO: support dirty log, live migration and IOTLB operations.
|
||||
#[repr(packed)]
|
||||
pub struct VhostUserVringArea {
|
||||
pub index: u32,
|
||||
pub flags: u32,
|
||||
pub size: u64,
|
||||
pub offset: u64,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct VhostUserLog {
|
||||
pub size: u64,
|
||||
pub offset: u64,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct VhostUserIotlb {
|
||||
pub iova: u64,
|
||||
pub size: u64,
|
||||
pub user_addr: u64,
|
||||
pub permission: u8,
|
||||
pub optype: u8,
|
||||
}
|
||||
*/
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
/// Flags for virtio-fs slave messages.
|
||||
pub struct VhostUserFSSlaveMsgFlags: u64 {
|
||||
/// Empty permission.
|
||||
const EMPTY = 0x0;
|
||||
/// Read permission.
|
||||
const MAP_R = 0x1;
|
||||
/// Write permission.
|
||||
const MAP_W = 0x2;
|
||||
}
|
||||
}
|
||||
|
||||
/// Max entries in one virtio-fs slave request.
|
||||
const VHOST_USER_FS_SLAVE_ENTRIES: usize = 8;
|
||||
|
||||
/// Slave request message to update the MMIO window.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserFSSlaveMsg {
|
||||
/// TODO:
|
||||
pub fd_offset: [u64; VHOST_USER_FS_SLAVE_ENTRIES],
|
||||
/// TODO:
|
||||
pub cache_offset: [u64; VHOST_USER_FS_SLAVE_ENTRIES],
|
||||
/// Size of region to map.
|
||||
pub len: [u64; VHOST_USER_FS_SLAVE_ENTRIES],
|
||||
/// Flags for the mmap operation
|
||||
pub flags: [VhostUserFSSlaveMsgFlags; VHOST_USER_FS_SLAVE_ENTRIES],
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserFSSlaveMsg {
|
||||
fn is_valid(&self) -> bool {
|
||||
for i in 0..VHOST_USER_FS_SLAVE_ENTRIES {
|
||||
if ({ self.flags[i] }.bits() & !VhostUserFSSlaveMsgFlags::all().bits()) != 0
|
||||
|| self.fd_offset[i].checked_add(self.len[i]).is_none()
|
||||
|| self.cache_offset[i].checked_add(self.len[i]).is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::mem;
|
||||
|
||||
#[test]
|
||||
fn check_request_code() {
|
||||
let code = MasterReq::NOOP;
|
||||
assert!(!code.is_valid());
|
||||
let code = MasterReq::MAX_CMD;
|
||||
assert!(!code.is_valid());
|
||||
let code = MasterReq::GET_FEATURES;
|
||||
assert!(code.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msg_header_ops() {
|
||||
let mut hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0, 0x100);
|
||||
assert_eq!(hdr.get_code(), MasterReq::GET_FEATURES);
|
||||
hdr.set_code(MasterReq::SET_FEATURES);
|
||||
assert_eq!(hdr.get_code(), MasterReq::SET_FEATURES);
|
||||
|
||||
assert_eq!(hdr.get_version(), 0x1);
|
||||
|
||||
assert_eq!(hdr.is_reply(), false);
|
||||
hdr.set_reply(true);
|
||||
assert_eq!(hdr.is_reply(), true);
|
||||
hdr.set_reply(false);
|
||||
|
||||
assert_eq!(hdr.is_need_reply(), false);
|
||||
hdr.set_need_reply(true);
|
||||
assert_eq!(hdr.is_need_reply(), true);
|
||||
hdr.set_need_reply(false);
|
||||
|
||||
assert_eq!(hdr.get_size(), 0x100);
|
||||
hdr.set_size(0x200);
|
||||
assert_eq!(hdr.get_size(), 0x200);
|
||||
|
||||
assert_eq!(hdr.is_need_reply(), false);
|
||||
assert_eq!(hdr.is_reply(), false);
|
||||
assert_eq!(hdr.get_version(), 0x1);
|
||||
|
||||
// Check message length
|
||||
assert!(hdr.is_valid());
|
||||
hdr.set_size(0x2000);
|
||||
assert!(!hdr.is_valid());
|
||||
hdr.set_size(0x100);
|
||||
assert_eq!(hdr.get_size(), 0x100);
|
||||
assert!(hdr.is_valid());
|
||||
hdr.set_size((MAX_MSG_SIZE - mem::size_of::<VhostUserMsgHeader<MasterReq>>()) as u32);
|
||||
assert!(hdr.is_valid());
|
||||
hdr.set_size(0x0);
|
||||
assert!(hdr.is_valid());
|
||||
|
||||
// Check version
|
||||
hdr.set_version(0x0);
|
||||
assert!(!hdr.is_valid());
|
||||
hdr.set_version(0x2);
|
||||
assert!(!hdr.is_valid());
|
||||
hdr.set_version(0x1);
|
||||
assert!(hdr.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_user_memory() {
|
||||
let mut msg = VhostUserMemory::new(1);
|
||||
assert!(msg.is_valid());
|
||||
msg.num_regions = MAX_ATTACHED_FD_ENTRIES as u32;
|
||||
assert!(msg.is_valid());
|
||||
|
||||
msg.num_regions += 1;
|
||||
assert!(!msg.is_valid());
|
||||
msg.num_regions = 0xFFFFFFFF;
|
||||
assert!(!msg.is_valid());
|
||||
msg.num_regions = MAX_ATTACHED_FD_ENTRIES as u32;
|
||||
msg.padding1 = 1;
|
||||
assert!(!msg.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_user_memory_region() {
|
||||
let mut msg = VhostUserMemoryRegion {
|
||||
guest_phys_addr: 0,
|
||||
memory_size: 0x1000,
|
||||
user_addr: 0,
|
||||
mmap_offset: 0,
|
||||
};
|
||||
assert!(msg.is_valid());
|
||||
msg.guest_phys_addr = 0xFFFFFFFFFFFFEFFF;
|
||||
assert!(msg.is_valid());
|
||||
msg.guest_phys_addr = 0xFFFFFFFFFFFFF000;
|
||||
assert!(!msg.is_valid());
|
||||
msg.guest_phys_addr = 0xFFFFFFFFFFFF0000;
|
||||
msg.memory_size = 0;
|
||||
assert!(!msg.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_user_vring_addr() {
|
||||
let mut msg =
|
||||
VhostUserVringAddr::new(0, VhostUserVringAddrFlags::all(), 0x0, 0x0, 0x0, 0x0);
|
||||
assert!(msg.is_valid());
|
||||
|
||||
msg.descriptor = 1;
|
||||
assert!(!msg.is_valid());
|
||||
msg.descriptor = 0;
|
||||
|
||||
msg.available = 1;
|
||||
assert!(!msg.is_valid());
|
||||
msg.available = 0;
|
||||
|
||||
msg.used = 1;
|
||||
assert!(!msg.is_valid());
|
||||
msg.used = 0;
|
||||
|
||||
msg.flags |= 0x80000000;
|
||||
assert!(!msg.is_valid());
|
||||
msg.flags &= !0x80000000;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_user_config_msg() {
|
||||
let mut msg = VhostUserConfig::new(
|
||||
VHOST_USER_CONFIG_OFFSET,
|
||||
VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET,
|
||||
VhostUserConfigFlags::WRITABLE,
|
||||
);
|
||||
|
||||
assert!(msg.is_valid());
|
||||
msg.size = 0;
|
||||
assert!(!msg.is_valid());
|
||||
msg.size = 1;
|
||||
assert!(msg.is_valid());
|
||||
msg.offset = 0;
|
||||
assert!(!msg.is_valid());
|
||||
msg.offset = VHOST_USER_CONFIG_SIZE;
|
||||
assert!(!msg.is_valid());
|
||||
msg.offset = VHOST_USER_CONFIG_SIZE - 1;
|
||||
assert!(msg.is_valid());
|
||||
msg.size = 2;
|
||||
assert!(!msg.is_valid());
|
||||
msg.size = 1;
|
||||
msg.flags |= VhostUserConfigFlags::LIVE_MIGRATION.bits();
|
||||
assert!(msg.is_valid());
|
||||
msg.flags |= 0x4;
|
||||
assert!(!msg.is_valid());
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! The protocol for vhost-user is based on the existing implementation of vhost for the Linux
|
||||
//! Kernel. The protocol defines two sides of the communication, master and slave. Master is
|
||||
//! the application that shares its virtqueues. Slave is the consumer of the virtqueues.
|
||||
//!
|
||||
//! The communication channel between the master and the slave includes two sub channels. One is
|
||||
//! used to send requests from the master to the slave and optional replies from the slave to the
|
||||
//! master. This sub channel is created on master startup by connecting to the slave service
|
||||
//! endpoint. The other is used to send requests from the slave to the master and optional replies
|
||||
//! from the master to the slave. This sub channel is created by the master issuing a
|
||||
//! VHOST_USER_SET_SLAVE_REQ_FD request to the slave with an auxiliary file descriptor.
|
||||
//!
|
||||
//! Unix domain socket is used as the underlying communication channel because the master needs to
|
||||
//! send file descriptors to the slave.
|
||||
//!
|
||||
//! Most messages that can be sent via the Unix domain socket implementing vhost-user have an
|
||||
//! equivalent ioctl to the kernel implementation.
|
||||
|
||||
use libc;
|
||||
use std::io::Error as IOError;
|
||||
|
||||
mod connection;
|
||||
pub mod message;
|
||||
pub use self::connection::Listener;
|
||||
#[cfg(feature = "vhost-user-master")]
|
||||
mod master;
|
||||
#[cfg(feature = "vhost-user-master")]
|
||||
pub use self::master::{Master, VhostUserMaster};
|
||||
#[cfg(feature = "vhost-user-master")]
|
||||
mod master_req_handler;
|
||||
#[cfg(feature = "vhost-user-master")]
|
||||
pub use self::master_req_handler::{MasterReqHandler, VhostUserMasterReqHandler};
|
||||
|
||||
#[cfg(feature = "vhost-user-slave")]
|
||||
mod slave;
|
||||
#[cfg(feature = "vhost-user-slave")]
|
||||
pub use self::slave::SlaveListener;
|
||||
#[cfg(feature = "vhost-user-slave")]
|
||||
mod slave_req_handler;
|
||||
#[cfg(feature = "vhost-user-slave")]
|
||||
pub use self::slave_req_handler::{SlaveReqHandler, VhostUserSlaveReqHandler};
|
||||
|
||||
pub mod sock_ctrl_msg;
|
||||
|
||||
/// Errors for vhost-user operations
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid parameters.
|
||||
InvalidParam,
|
||||
/// Unsupported operations due to that the protocol feature hasn't been negotiated.
|
||||
InvalidOperation,
|
||||
/// Invalid message format, flag or content.
|
||||
InvalidMessage,
|
||||
/// Only part of a message have been sent or received successfully
|
||||
PartialMessage,
|
||||
/// Message is too large
|
||||
OversizedMsg,
|
||||
/// Fd array in question is too big or too small
|
||||
IncorrectFds,
|
||||
/// Can't connect to peer.
|
||||
SocketConnect(std::io::Error),
|
||||
/// Generic socket errors.
|
||||
SocketError(std::io::Error),
|
||||
/// The socket is broken or has been closed.
|
||||
SocketBroken(std::io::Error),
|
||||
/// Should retry the socket operation again.
|
||||
SocketRetry(std::io::Error),
|
||||
/// Failure from the slave side.
|
||||
SlaveInternalError,
|
||||
/// Virtio/protocol features mismatch.
|
||||
FeatureMismatch,
|
||||
/// Error from request handler
|
||||
ReqHandlerError(IOError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::InvalidParam => write!(f, "invalid parameters"),
|
||||
Error::InvalidOperation => write!(f, "invalid operation"),
|
||||
Error::InvalidMessage => write!(f, "invalid message"),
|
||||
Error::PartialMessage => write!(f, "partial message"),
|
||||
Error::OversizedMsg => write!(f, "oversized message"),
|
||||
Error::IncorrectFds => write!(f, "wrong number of attached fds"),
|
||||
Error::SocketError(e) => write!(f, "socket error: {}", e),
|
||||
Error::SocketConnect(e) => write!(f, "can't connect to peer: {}", e),
|
||||
Error::SocketBroken(e) => write!(f, "socket is broken: {}", e),
|
||||
Error::SocketRetry(e) => write!(f, "temporary socket error: {}", e),
|
||||
Error::SlaveInternalError => write!(f, "slave internal error"),
|
||||
Error::FeatureMismatch => write!(f, "virtio/protocol features mismatch"),
|
||||
Error::ReqHandlerError(e) => write!(f, "handler failed to handle request: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Determine whether to rebuild the underline communication channel.
|
||||
pub fn should_reconnect(&self) -> bool {
|
||||
match *self {
|
||||
// Should reconnect because it may be caused by temporary network errors.
|
||||
Error::PartialMessage => true,
|
||||
// Should reconnect because the underline socket is broken.
|
||||
Error::SocketBroken(_) => true,
|
||||
// Slave internal error, hope it recovers on reconnect.
|
||||
Error::SlaveInternalError => true,
|
||||
// Should just retry the IO operation instead of rebuilding the underline connection.
|
||||
Error::SocketRetry(_) => false,
|
||||
Error::InvalidParam | Error::InvalidOperation => false,
|
||||
Error::InvalidMessage | Error::IncorrectFds | Error::OversizedMsg => false,
|
||||
Error::SocketError(_) | Error::SocketConnect(_) => false,
|
||||
Error::FeatureMismatch => false,
|
||||
Error::ReqHandlerError(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<vmm_sys_util::errno::Error> for Error {
|
||||
/// Convert raw socket errors into meaningful vhost-user errors.
|
||||
///
|
||||
/// The vmm_sys_util::errno::Error is a simple wrapper over the raw errno, which doesn't means much
|
||||
/// to the vhost-user connection manager. So convert it into meaningful errors to simplify
|
||||
/// the connection manager logic.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - Error::SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - Error::SocketBroken: the underline socket is broken.
|
||||
/// * - Error::SocketError: other socket related errors.
|
||||
#[allow(unreachable_patterns)] // EWOULDBLOCK equals to EGAIN on linux
|
||||
fn from(err: vmm_sys_util::errno::Error) -> Self {
|
||||
match err.errno() {
|
||||
// The socket is marked nonblocking and the requested operation would block.
|
||||
libc::EAGAIN => Error::SocketRetry(IOError::from_raw_os_error(libc::EAGAIN)),
|
||||
// The socket is marked nonblocking and the requested operation would block.
|
||||
libc::EWOULDBLOCK => Error::SocketRetry(IOError::from_raw_os_error(libc::EWOULDBLOCK)),
|
||||
// A signal occurred before any data was transmitted
|
||||
libc::EINTR => Error::SocketRetry(IOError::from_raw_os_error(libc::EINTR)),
|
||||
// The output queue for a network interface was full. This generally indicates
|
||||
// that the interface has stopped sending, but may be caused by transient congestion.
|
||||
libc::ENOBUFS => Error::SocketRetry(IOError::from_raw_os_error(libc::ENOBUFS)),
|
||||
// No memory available.
|
||||
libc::ENOMEM => Error::SocketRetry(IOError::from_raw_os_error(libc::ENOMEM)),
|
||||
// Connection reset by peer.
|
||||
libc::ECONNRESET => Error::SocketBroken(IOError::from_raw_os_error(libc::ECONNRESET)),
|
||||
// The local end has been shut down on a connection oriented socket. In this case the
|
||||
// process will also receive a SIGPIPE unless MSG_NOSIGNAL is set.
|
||||
libc::EPIPE => Error::SocketBroken(IOError::from_raw_os_error(libc::EPIPE)),
|
||||
// Write permission is denied on the destination socket file, or search permission is
|
||||
// denied for one of the directories the path prefix.
|
||||
libc::EACCES => Error::SocketConnect(IOError::from_raw_os_error(libc::EACCES)),
|
||||
// Catch all other errors
|
||||
e => Error::SocketError(IOError::from_raw_os_error(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of vhost-user operations
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Result of request handler.
|
||||
pub type HandlerResult<T> = std::result::Result<T, IOError>;
|
||||
|
||||
#[cfg(all(test, feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
mod dummy_slave;
|
||||
|
||||
#[cfg(all(test, feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
mod tests {
|
||||
use super::dummy_slave::{DummySlaveReqHandler, VIRTIO_FEATURES};
|
||||
use super::message::*;
|
||||
use super::*;
|
||||
use crate::backend::VhostBackend;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use std::thread;
|
||||
|
||||
fn create_slave<S: VhostUserSlaveReqHandler>(
|
||||
path: &str,
|
||||
backend: Arc<Mutex<S>>,
|
||||
) -> (Master, SlaveReqHandler<S>) {
|
||||
let mut slave_listener = SlaveListener::new(path, true, backend).unwrap();
|
||||
let master = Master::connect(path).unwrap();
|
||||
(master, slave_listener.accept().unwrap().unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_dummy_slave() {
|
||||
let mut slave = DummySlaveReqHandler::new();
|
||||
|
||||
slave.set_owner().unwrap();
|
||||
assert!(slave.set_owner().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_owner() {
|
||||
let slave_be = Arc::new(Mutex::new(DummySlaveReqHandler::new()));
|
||||
let (mut master, mut slave) =
|
||||
create_slave("/tmp/vhost_user_lib_unit_test_owner", slave_be.clone());
|
||||
|
||||
assert_eq!(slave_be.lock().unwrap().owned, false);
|
||||
master.set_owner().unwrap();
|
||||
slave.handle_request().unwrap();
|
||||
assert_eq!(slave_be.lock().unwrap().owned, true);
|
||||
master.set_owner().unwrap();
|
||||
assert!(slave.handle_request().is_err());
|
||||
assert_eq!(slave_be.lock().unwrap().owned, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_features() {
|
||||
let mbar = Arc::new(Barrier::new(2));
|
||||
let sbar = mbar.clone();
|
||||
let slave_be = Arc::new(Mutex::new(DummySlaveReqHandler::new()));
|
||||
let (mut master, mut slave) =
|
||||
create_slave("/tmp/vhost_user_lib_unit_test_feature", slave_be.clone());
|
||||
|
||||
thread::spawn(move || {
|
||||
slave.handle_request().unwrap();
|
||||
assert_eq!(slave_be.lock().unwrap().owned, true);
|
||||
|
||||
slave.handle_request().unwrap();
|
||||
slave.handle_request().unwrap();
|
||||
assert_eq!(
|
||||
slave_be.lock().unwrap().acked_features,
|
||||
VIRTIO_FEATURES & !0x1
|
||||
);
|
||||
|
||||
slave.handle_request().unwrap();
|
||||
slave.handle_request().unwrap();
|
||||
assert_eq!(
|
||||
slave_be.lock().unwrap().acked_protocol_features,
|
||||
VhostUserProtocolFeatures::all().bits()
|
||||
);
|
||||
|
||||
sbar.wait();
|
||||
});
|
||||
|
||||
master.set_owner().unwrap();
|
||||
|
||||
// set virtio features
|
||||
let features = master.get_features().unwrap();
|
||||
assert_eq!(features, VIRTIO_FEATURES);
|
||||
master.set_features(VIRTIO_FEATURES & !0x1).unwrap();
|
||||
|
||||
// set vhost protocol features
|
||||
let features = master.get_protocol_features().unwrap();
|
||||
assert_eq!(features.bits(), VhostUserProtocolFeatures::all().bits());
|
||||
master.set_protocol_features(features).unwrap();
|
||||
|
||||
mbar.wait();
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Traits and Structs for vhost-user slave.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::connection::{Endpoint, Listener};
|
||||
use super::message::*;
|
||||
use super::{Result, SlaveReqHandler, VhostUserSlaveReqHandler};
|
||||
|
||||
/// Vhost-user slave side connection listener.
|
||||
pub struct SlaveListener<S: VhostUserSlaveReqHandler> {
|
||||
listener: Listener,
|
||||
backend: Option<Arc<Mutex<S>>>,
|
||||
}
|
||||
|
||||
/// Sets up a listener for incoming master connections, and handles construction
|
||||
/// of a Slave on success.
|
||||
impl<S: VhostUserSlaveReqHandler> SlaveListener<S> {
|
||||
/// Create a unix domain socket for incoming master connections.
|
||||
///
|
||||
/// Be careful, the file at `path` will be unlinked if unlink is true
|
||||
pub fn new(path: &str, unlink: bool, backend: Arc<Mutex<S>>) -> Result<Self> {
|
||||
Ok(SlaveListener {
|
||||
listener: Listener::new(path, unlink)?,
|
||||
backend: Some(backend),
|
||||
})
|
||||
}
|
||||
|
||||
/// Accept an incoming connection from the master, returning Some(Slave) on
|
||||
/// success, or None if the socket is nonblocking and no incoming connection
|
||||
/// was detected
|
||||
pub fn accept(&mut self) -> Result<Option<SlaveReqHandler<S>>> {
|
||||
if let Some(fd) = self.listener.accept()? {
|
||||
return Ok(Some(SlaveReqHandler::new(
|
||||
Endpoint::<MasterReq>::from_stream(fd),
|
||||
self.backend.take().unwrap(),
|
||||
)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Change blocking status on the listener.
|
||||
pub fn set_nonblocking(&self, block: bool) -> Result<()> {
|
||||
self.listener.set_nonblocking(block)
|
||||
}
|
||||
}
|
||||
@@ -1,582 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Traits and Structs to handle vhost-user requests from the master to the slave.
|
||||
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::slice;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::connection::Endpoint;
|
||||
use super::message::*;
|
||||
use super::{Error, Result};
|
||||
|
||||
/// Trait to handle vhost-user requests from the master to the slave.
|
||||
#[allow(missing_docs)]
|
||||
pub trait VhostUserSlaveReqHandler {
|
||||
fn set_owner(&mut self) -> Result<()>;
|
||||
fn reset_owner(&mut self) -> Result<()>;
|
||||
fn get_features(&mut self) -> Result<u64>;
|
||||
fn set_features(&mut self, features: u64) -> Result<()>;
|
||||
fn set_mem_table(&mut self, ctx: &[VhostUserMemoryRegion], fds: &[RawFd]) -> Result<()>;
|
||||
fn set_vring_num(&mut self, index: u32, num: u32) -> Result<()>;
|
||||
fn set_vring_addr(
|
||||
&mut self,
|
||||
index: u32,
|
||||
flags: VhostUserVringAddrFlags,
|
||||
descriptor: u64,
|
||||
used: u64,
|
||||
available: u64,
|
||||
log: u64,
|
||||
) -> Result<()>;
|
||||
fn set_vring_base(&mut self, index: u32, base: u32) -> Result<()>;
|
||||
fn get_vring_base(&mut self, index: u32) -> Result<VhostUserVringState>;
|
||||
fn set_vring_kick(&mut self, index: u8, fd: Option<RawFd>) -> Result<()>;
|
||||
fn set_vring_call(&mut self, index: u8, fd: Option<RawFd>) -> Result<()>;
|
||||
fn set_vring_err(&mut self, index: u8, fd: Option<RawFd>) -> Result<()>;
|
||||
|
||||
fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures>;
|
||||
fn set_protocol_features(&mut self, features: u64) -> Result<()>;
|
||||
fn get_queue_num(&mut self) -> Result<u64>;
|
||||
fn set_vring_enable(&mut self, index: u32, enable: bool) -> Result<()>;
|
||||
fn get_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
flags: VhostUserConfigFlags,
|
||||
) -> Result<Vec<u8>>;
|
||||
fn set_config(&mut self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()>;
|
||||
}
|
||||
|
||||
/// A vhost-user slave endpoint which relays all received requests from the
|
||||
/// master to the virtio backend device object.
|
||||
///
|
||||
/// The lifetime of the SlaveReqHandler object should be the same as the underline Unix Domain
|
||||
/// Socket, so it gets simpler to recover from disconnect.
|
||||
pub struct SlaveReqHandler<S: VhostUserSlaveReqHandler> {
|
||||
// underlying Unix domain socket for communication
|
||||
main_sock: Endpoint<MasterReq>,
|
||||
// the vhost-user backend device object
|
||||
backend: Arc<Mutex<S>>,
|
||||
|
||||
virtio_features: u64,
|
||||
acked_virtio_features: u64,
|
||||
protocol_features: VhostUserProtocolFeatures,
|
||||
acked_protocol_features: u64,
|
||||
|
||||
// sending ack for messages without payload
|
||||
reply_ack_enabled: bool,
|
||||
// whether the endpoint has encountered any failure
|
||||
error: Option<i32>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
|
||||
/// Create a vhost-user slave endpoint.
|
||||
pub(super) fn new(main_sock: Endpoint<MasterReq>, backend: Arc<Mutex<S>>) -> Self {
|
||||
SlaveReqHandler {
|
||||
main_sock,
|
||||
backend,
|
||||
virtio_features: 0,
|
||||
acked_virtio_features: 0,
|
||||
protocol_features: VhostUserProtocolFeatures::empty(),
|
||||
acked_protocol_features: 0,
|
||||
reply_ack_enabled: false,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new vhost-user slave endpoint.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * - `path` - path of Unix domain socket listener to connect to
|
||||
/// * - `backend` - handler for requests from the master to the slave
|
||||
pub fn connect(path: &str, backend: Arc<Mutex<S>>) -> Result<Self> {
|
||||
Ok(Self::new(Endpoint::<MasterReq>::connect(path)?, backend))
|
||||
}
|
||||
|
||||
/// Mark endpoint as failed with specified error code.
|
||||
pub fn set_failed(&mut self, error: i32) {
|
||||
self.error = Some(error);
|
||||
}
|
||||
|
||||
/// Receive and handle one incoming request message from the master.
|
||||
/// The caller needs to:
|
||||
/// . serialize calls to this function
|
||||
/// . decide what to do when error happens
|
||||
/// . optional recover from failure
|
||||
pub fn handle_request(&mut self) -> Result<()> {
|
||||
// Return error if the endpoint is already in failed state.
|
||||
self.check_state()?;
|
||||
|
||||
// The underlying communication channel is a Unix domain socket in
|
||||
// stream mode, and recvmsg() is a little tricky here. To successfully
|
||||
// receive attached file descriptors, we need to receive messages and
|
||||
// corresponding attached file descriptors in this way:
|
||||
// . recv messsage header and optional attached file
|
||||
// . validate message header
|
||||
// . recv optional message body and payload according size field in
|
||||
// message header
|
||||
// . validate message body and optional payload
|
||||
let (hdr, rfds) = self.main_sock.recv_header()?;
|
||||
let rfds = self.check_attached_rfds(&hdr, rfds)?;
|
||||
let (size, buf) = match hdr.get_size() {
|
||||
0 => (0, vec![0u8; 0]),
|
||||
len => {
|
||||
let (size2, rbuf) = self.main_sock.recv_data(len as usize)?;
|
||||
if size2 != len as usize {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
(size2, rbuf)
|
||||
}
|
||||
};
|
||||
|
||||
match hdr.get_code() {
|
||||
MasterReq::SET_OWNER => {
|
||||
self.check_request_size(&hdr, size, 0)?;
|
||||
self.backend.lock().unwrap().set_owner()?;
|
||||
}
|
||||
MasterReq::RESET_OWNER => {
|
||||
self.check_request_size(&hdr, size, 0)?;
|
||||
self.backend.lock().unwrap().reset_owner()?;
|
||||
}
|
||||
MasterReq::GET_FEATURES => {
|
||||
self.check_request_size(&hdr, size, 0)?;
|
||||
let features = self.backend.lock().unwrap().get_features()?;
|
||||
let msg = VhostUserU64::new(features);
|
||||
self.send_reply_message(&hdr, &msg)?;
|
||||
self.virtio_features = features;
|
||||
self.update_reply_ack_flag();
|
||||
}
|
||||
MasterReq::SET_FEATURES => {
|
||||
let msg = self.extract_request_body::<VhostUserU64>(&hdr, size, &buf)?;
|
||||
self.backend.lock().unwrap().set_features(msg.value)?;
|
||||
self.acked_virtio_features = msg.value;
|
||||
self.update_reply_ack_flag();
|
||||
}
|
||||
MasterReq::SET_MEM_TABLE => {
|
||||
let res = self.set_mem_table(&hdr, size, &buf, rfds);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::SET_VRING_NUM => {
|
||||
let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
|
||||
let res = self
|
||||
.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_vring_num(msg.index, msg.num);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::SET_VRING_ADDR => {
|
||||
let msg = self.extract_request_body::<VhostUserVringAddr>(&hdr, size, &buf)?;
|
||||
let flags = match VhostUserVringAddrFlags::from_bits(msg.flags) {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::InvalidMessage),
|
||||
};
|
||||
let res = self.backend.lock().unwrap().set_vring_addr(
|
||||
msg.index,
|
||||
flags,
|
||||
msg.descriptor,
|
||||
msg.used,
|
||||
msg.available,
|
||||
msg.log,
|
||||
);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::SET_VRING_BASE => {
|
||||
let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
|
||||
let res = self
|
||||
.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_vring_base(msg.index, msg.num);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::GET_VRING_BASE => {
|
||||
let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
|
||||
let reply = self.backend.lock().unwrap().get_vring_base(msg.index)?;
|
||||
self.send_reply_message(&hdr, &reply)?;
|
||||
}
|
||||
MasterReq::SET_VRING_CALL => {
|
||||
self.check_request_size(&hdr, size, mem::size_of::<VhostUserU64>())?;
|
||||
let (index, rfds) = self.handle_vring_fd_request(&buf, rfds)?;
|
||||
let res = self.backend.lock().unwrap().set_vring_call(index, rfds);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::SET_VRING_KICK => {
|
||||
self.check_request_size(&hdr, size, mem::size_of::<VhostUserU64>())?;
|
||||
let (index, rfds) = self.handle_vring_fd_request(&buf, rfds)?;
|
||||
let res = self.backend.lock().unwrap().set_vring_kick(index, rfds);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::SET_VRING_ERR => {
|
||||
self.check_request_size(&hdr, size, mem::size_of::<VhostUserU64>())?;
|
||||
let (index, rfds) = self.handle_vring_fd_request(&buf, rfds)?;
|
||||
let res = self.backend.lock().unwrap().set_vring_err(index, rfds);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::GET_PROTOCOL_FEATURES => {
|
||||
self.check_request_size(&hdr, size, 0)?;
|
||||
let features = self.backend.lock().unwrap().get_protocol_features()?;
|
||||
let msg = VhostUserU64::new(features.bits());
|
||||
self.send_reply_message(&hdr, &msg)?;
|
||||
self.protocol_features = features;
|
||||
self.update_reply_ack_flag();
|
||||
}
|
||||
MasterReq::SET_PROTOCOL_FEATURES => {
|
||||
let msg = self.extract_request_body::<VhostUserU64>(&hdr, size, &buf)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_protocol_features(msg.value)?;
|
||||
self.acked_protocol_features = msg.value;
|
||||
self.update_reply_ack_flag();
|
||||
}
|
||||
MasterReq::GET_QUEUE_NUM => {
|
||||
if self.acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
self.check_request_size(&hdr, size, 0)?;
|
||||
let num = self.backend.lock().unwrap().get_queue_num()?;
|
||||
let msg = VhostUserU64::new(num);
|
||||
self.send_reply_message(&hdr, &msg)?;
|
||||
}
|
||||
MasterReq::SET_VRING_ENABLE => {
|
||||
let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
|
||||
if self.acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() == 0
|
||||
&& msg.index > 0
|
||||
{
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
let enable = match msg.num {
|
||||
1 => true,
|
||||
0 => false,
|
||||
_ => return Err(Error::InvalidParam),
|
||||
};
|
||||
|
||||
let res = self
|
||||
.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_vring_enable(msg.index, enable);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::GET_CONFIG => {
|
||||
if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
self.check_request_size(&hdr, size, mem::size_of::<VhostUserConfig>())?;
|
||||
self.get_config(&hdr, &buf)?;
|
||||
}
|
||||
MasterReq::SET_CONFIG => {
|
||||
if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
self.check_request_size(&hdr, size, hdr.get_size() as usize)?;
|
||||
self.set_config(&hdr, size, &buf)?;
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_mem_table(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
size: usize,
|
||||
buf: &[u8],
|
||||
rfds: Option<Vec<RawFd>>,
|
||||
) -> Result<()> {
|
||||
self.check_request_size(&hdr, size, hdr.get_size() as usize)?;
|
||||
|
||||
// check message size is consistent
|
||||
let hdrsize = mem::size_of::<VhostUserMemory>();
|
||||
if size < hdrsize {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const VhostUserMemory) };
|
||||
if !msg.is_valid() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
if size != hdrsize + msg.num_regions as usize * mem::size_of::<VhostUserMemoryRegion>() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
// validate number of fds matching number of memory regions
|
||||
let fds = match rfds {
|
||||
None => return Err(Error::InvalidMessage),
|
||||
Some(fds) => {
|
||||
if fds.len() != msg.num_regions as usize {
|
||||
Endpoint::<MasterReq>::close_rfds(Some(fds));
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
fds
|
||||
}
|
||||
};
|
||||
|
||||
// Validate memory regions
|
||||
let regions = unsafe {
|
||||
slice::from_raw_parts(
|
||||
buf.as_ptr().add(hdrsize) as *const VhostUserMemoryRegion,
|
||||
msg.num_regions as usize,
|
||||
)
|
||||
};
|
||||
for region in regions.iter() {
|
||||
if !region.is_valid() {
|
||||
Endpoint::<MasterReq>::close_rfds(Some(fds));
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
}
|
||||
|
||||
self.backend.lock().unwrap().set_mem_table(®ions, &fds)
|
||||
}
|
||||
|
||||
fn get_config(&mut self, hdr: &VhostUserMsgHeader<MasterReq>, buf: &[u8]) -> Result<()> {
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const VhostUserConfig) };
|
||||
if !msg.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
let flags = match VhostUserConfigFlags::from_bits(msg.flags) {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::InvalidMessage),
|
||||
};
|
||||
let res = self
|
||||
.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_config(msg.offset, msg.size, flags);
|
||||
|
||||
// vhost-user slave's payload size MUST match master's request
|
||||
// on success, uses zero length of payload to indicate an error
|
||||
// to vhost-user master.
|
||||
match res {
|
||||
Ok(ref buf) if buf.len() == msg.size as usize => {
|
||||
let reply = VhostUserConfig::new(msg.offset, buf.len() as u32, flags);
|
||||
self.send_reply_with_payload(&hdr, &reply, buf.as_slice())?;
|
||||
}
|
||||
Ok(_) => {
|
||||
let reply = VhostUserConfig::new(msg.offset, 0, flags);
|
||||
self.send_reply_message(&hdr, &reply)?;
|
||||
}
|
||||
Err(_) => {
|
||||
let reply = VhostUserConfig::new(msg.offset, 0, flags);
|
||||
self.send_reply_message(&hdr, &reply)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_config(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
size: usize,
|
||||
buf: &[u8],
|
||||
) -> Result<()> {
|
||||
if size < mem::size_of::<VhostUserConfig>() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const VhostUserConfig) };
|
||||
if !msg.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
if size - mem::size_of::<VhostUserConfig>() != msg.size as usize {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
let flags: VhostUserConfigFlags;
|
||||
match VhostUserConfigFlags::from_bits(msg.flags) {
|
||||
Some(val) => flags = val,
|
||||
None => return Err(Error::InvalidMessage),
|
||||
}
|
||||
|
||||
let res = self
|
||||
.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_config(msg.offset, buf, flags);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_vring_fd_request(
|
||||
&mut self,
|
||||
buf: &[u8],
|
||||
rfds: Option<Vec<RawFd>>,
|
||||
) -> Result<(u8, Option<RawFd>)> {
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const VhostUserU64) };
|
||||
if !msg.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
// Bits (0-7) of the payload contain the vring index. Bit 8 is the
|
||||
// invalid FD flag. This flag is set when there is no file descriptor
|
||||
// in the ancillary data. This signals that polling will be used
|
||||
// instead of waiting for the call.
|
||||
let nofd = match msg.value & 0x100u64 {
|
||||
0x100u64 => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let mut rfd = None;
|
||||
match rfds {
|
||||
Some(fds) => {
|
||||
if !nofd && fds.len() == 1 {
|
||||
rfd = Some(fds[0]);
|
||||
} else if (nofd && !fds.is_empty()) || (!nofd && fds.len() != 1) {
|
||||
Endpoint::<MasterReq>::close_rfds(Some(fds));
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if !nofd {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((msg.value as u8, rfd))
|
||||
}
|
||||
|
||||
fn check_state(&self) -> Result<()> {
|
||||
match self.error {
|
||||
Some(e) => Err(Error::SocketBroken(std::io::Error::from_raw_os_error(e))),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_request_size(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
size: usize,
|
||||
expected: usize,
|
||||
) -> Result<()> {
|
||||
if hdr.get_size() as usize != expected
|
||||
|| hdr.is_reply()
|
||||
|| hdr.get_version() != 0x1
|
||||
|| size != expected
|
||||
{
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_attached_rfds(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
rfds: Option<Vec<RawFd>>,
|
||||
) -> Result<Option<Vec<RawFd>>> {
|
||||
match hdr.get_code() {
|
||||
MasterReq::SET_MEM_TABLE => Ok(rfds),
|
||||
MasterReq::SET_VRING_CALL => Ok(rfds),
|
||||
MasterReq::SET_VRING_KICK => Ok(rfds),
|
||||
MasterReq::SET_VRING_ERR => Ok(rfds),
|
||||
MasterReq::SET_LOG_BASE => Ok(rfds),
|
||||
MasterReq::SET_LOG_FD => Ok(rfds),
|
||||
MasterReq::SET_SLAVE_REQ_FD => Ok(rfds),
|
||||
MasterReq::SET_INFLIGHT_FD => Ok(rfds),
|
||||
_ => {
|
||||
if rfds.is_some() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
Err(Error::InvalidMessage)
|
||||
} else {
|
||||
Ok(rfds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_request_body<'a, T: Sized + VhostUserMsgValidator>(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
size: usize,
|
||||
buf: &'a [u8],
|
||||
) -> Result<&'a T> {
|
||||
self.check_request_size(hdr, size, mem::size_of::<T>())?;
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const T) };
|
||||
if !msg.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
fn update_reply_ack_flag(&mut self) {
|
||||
let vflag = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
let pflag = VhostUserProtocolFeatures::REPLY_ACK;
|
||||
if (self.virtio_features & vflag) != 0
|
||||
&& (self.acked_virtio_features & vflag) != 0
|
||||
&& self.protocol_features.contains(pflag)
|
||||
&& (self.acked_protocol_features & pflag.bits()) != 0
|
||||
{
|
||||
self.reply_ack_enabled = true;
|
||||
} else {
|
||||
self.reply_ack_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn new_reply_header<T: Sized>(
|
||||
&self,
|
||||
req: &VhostUserMsgHeader<MasterReq>,
|
||||
) -> Result<VhostUserMsgHeader<MasterReq>> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
Ok(VhostUserMsgHeader::new(
|
||||
req.get_code(),
|
||||
VhostUserHeaderFlag::REPLY.bits(),
|
||||
mem::size_of::<T>() as u32,
|
||||
))
|
||||
}
|
||||
|
||||
fn send_ack_message(
|
||||
&mut self,
|
||||
req: &VhostUserMsgHeader<MasterReq>,
|
||||
res: Result<()>,
|
||||
) -> Result<()> {
|
||||
if self.reply_ack_enabled {
|
||||
let hdr = self.new_reply_header::<VhostUserU64>(req)?;
|
||||
let val = match res {
|
||||
Ok(_) => 0,
|
||||
Err(_) => 1,
|
||||
};
|
||||
let msg = VhostUserU64::new(val);
|
||||
self.main_sock.send_message(&hdr, &msg, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_reply_message<T>(
|
||||
&mut self,
|
||||
req: &VhostUserMsgHeader<MasterReq>,
|
||||
msg: &T,
|
||||
) -> Result<()> {
|
||||
let hdr = self.new_reply_header::<T>(req)?;
|
||||
self.main_sock.send_message(&hdr, msg, None)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_reply_with_payload<T, P>(
|
||||
&mut self,
|
||||
req: &VhostUserMsgHeader<MasterReq>,
|
||||
msg: &T,
|
||||
payload: &[P],
|
||||
) -> Result<()>
|
||||
where
|
||||
T: Sized,
|
||||
P: Sized,
|
||||
{
|
||||
let hdr = self.new_reply_header::<T>(req)?;
|
||||
self.main_sock
|
||||
.send_message_with_payload(&hdr, msg, payload, None)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserSlaveReqHandler> AsRawFd for SlaveReqHandler<S> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.main_sock.as_raw_fd()
|
||||
}
|
||||
}
|
||||
@@ -1,464 +0,0 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Used to send and receive messages with file descriptors on sockets that accept control messages
|
||||
//! (e.g. Unix domain sockets).
|
||||
|
||||
// TODO: move this file into the vmm-sys-util crate
|
||||
|
||||
use std::fs::File;
|
||||
use std::mem::size_of;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::unix::net::{UnixDatagram, UnixStream};
|
||||
use std::ptr::{copy_nonoverlapping, null_mut, write_unaligned};
|
||||
|
||||
use libc::{
|
||||
c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET,
|
||||
};
|
||||
use vmm_sys_util::errno::{Error, Result};
|
||||
|
||||
// Each of the following macros performs the same function as their C counterparts. They are each
|
||||
// macros because they are used to size statically allocated arrays.
|
||||
|
||||
macro_rules! CMSG_ALIGN {
|
||||
($len:expr) => {
|
||||
(($len) + size_of::<c_long>() - 1) & !(size_of::<c_long>() - 1)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! CMSG_SPACE {
|
||||
($len:expr) => {
|
||||
size_of::<cmsghdr>() + CMSG_ALIGN!($len)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! CMSG_LEN {
|
||||
($len:expr) => {
|
||||
size_of::<cmsghdr>() + ($len)
|
||||
};
|
||||
}
|
||||
|
||||
// This function (macro in the C version) is not used in any compile time constant slots, so is just
|
||||
// an ordinary function. The returned pointer is hard coded to be RawFd because that's all that this
|
||||
// module supports.
|
||||
#[allow(non_snake_case)]
|
||||
#[inline(always)]
|
||||
fn CMSG_DATA(cmsg_buffer: *mut cmsghdr) -> *mut RawFd {
|
||||
// Essentially returns a pointer to just past the header.
|
||||
cmsg_buffer.wrapping_offset(1) as *mut RawFd
|
||||
}
|
||||
|
||||
// This function is like CMSG_NEXT, but safer because it reads only from references, although it
|
||||
// does some pointer arithmetic on cmsg_ptr.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
|
||||
fn get_next_cmsg(msghdr: &msghdr, cmsg: &cmsghdr, cmsg_ptr: *mut cmsghdr) -> *mut cmsghdr {
|
||||
let next_cmsg = (cmsg_ptr as *mut u8).wrapping_add(CMSG_ALIGN!(cmsg.cmsg_len)) as *mut cmsghdr;
|
||||
if next_cmsg
|
||||
.wrapping_offset(1)
|
||||
.wrapping_sub(msghdr.msg_control as usize) as usize
|
||||
> msghdr.msg_controllen
|
||||
{
|
||||
null_mut()
|
||||
} else {
|
||||
next_cmsg
|
||||
}
|
||||
}
|
||||
|
||||
const CMSG_BUFFER_INLINE_CAPACITY: usize = CMSG_SPACE!(size_of::<RawFd>() * 32);
|
||||
|
||||
enum CmsgBuffer {
|
||||
Inline([u64; (CMSG_BUFFER_INLINE_CAPACITY + 7) / 8]),
|
||||
Heap(Box<[cmsghdr]>),
|
||||
}
|
||||
|
||||
impl CmsgBuffer {
|
||||
fn with_capacity(capacity: usize) -> CmsgBuffer {
|
||||
let cap_in_cmsghdr_units =
|
||||
(capacity.checked_add(size_of::<cmsghdr>()).unwrap() - 1) / size_of::<cmsghdr>();
|
||||
if capacity <= CMSG_BUFFER_INLINE_CAPACITY {
|
||||
CmsgBuffer::Inline([0u64; (CMSG_BUFFER_INLINE_CAPACITY + 7) / 8])
|
||||
} else {
|
||||
CmsgBuffer::Heap(
|
||||
vec![
|
||||
cmsghdr {
|
||||
cmsg_len: 0,
|
||||
cmsg_level: 0,
|
||||
cmsg_type: 0,
|
||||
};
|
||||
cap_in_cmsghdr_units
|
||||
]
|
||||
.into_boxed_slice(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_mut_ptr(&mut self) -> *mut cmsghdr {
|
||||
match self {
|
||||
CmsgBuffer::Inline(a) => a.as_mut_ptr() as *mut cmsghdr,
|
||||
CmsgBuffer::Heap(a) => a.as_mut_ptr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_sendmsg<D: IntoIovec>(fd: RawFd, out_data: &[D], out_fds: &[RawFd]) -> Result<usize> {
|
||||
let cmsg_capacity = CMSG_SPACE!(size_of::<RawFd>() * out_fds.len());
|
||||
let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity);
|
||||
|
||||
let mut iovecs = Vec::with_capacity(out_data.len());
|
||||
for data in out_data {
|
||||
iovecs.push(iovec {
|
||||
iov_base: data.as_ptr() as *mut c_void,
|
||||
iov_len: data.size(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut msg = msghdr {
|
||||
msg_name: null_mut(),
|
||||
msg_namelen: 0,
|
||||
msg_iov: iovecs.as_mut_ptr(),
|
||||
msg_iovlen: iovecs.len(),
|
||||
msg_control: null_mut(),
|
||||
msg_controllen: 0,
|
||||
msg_flags: 0,
|
||||
};
|
||||
|
||||
if !out_fds.is_empty() {
|
||||
let cmsg = cmsghdr {
|
||||
cmsg_len: CMSG_LEN!(size_of::<RawFd>() * out_fds.len()),
|
||||
cmsg_level: SOL_SOCKET,
|
||||
cmsg_type: SCM_RIGHTS,
|
||||
};
|
||||
unsafe {
|
||||
// Safe because cmsg_buffer was allocated to be large enough to contain cmsghdr.
|
||||
write_unaligned(cmsg_buffer.as_mut_ptr() as *mut cmsghdr, cmsg);
|
||||
// Safe because the cmsg_buffer was allocated to be large enough to hold out_fds.len()
|
||||
// file descriptors.
|
||||
copy_nonoverlapping(
|
||||
out_fds.as_ptr(),
|
||||
CMSG_DATA(cmsg_buffer.as_mut_ptr()),
|
||||
out_fds.len(),
|
||||
);
|
||||
}
|
||||
|
||||
msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
|
||||
msg.msg_controllen = cmsg_capacity;
|
||||
}
|
||||
|
||||
// Safe because the msghdr was properly constructed from valid (or null) pointers of the
|
||||
// indicated length and we check the return value.
|
||||
let write_count = unsafe { sendmsg(fd, &msg, MSG_NOSIGNAL) };
|
||||
|
||||
if write_count == -1 {
|
||||
Err(Error::last())
|
||||
} else {
|
||||
Ok(write_count as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_recvmsg(fd: RawFd, iovecs: &mut [iovec], in_fds: &mut [RawFd]) -> Result<(usize, usize)> {
|
||||
let cmsg_capacity = CMSG_SPACE!(size_of::<RawFd>() * in_fds.len());
|
||||
let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity);
|
||||
let mut msg = msghdr {
|
||||
msg_name: null_mut(),
|
||||
msg_namelen: 0,
|
||||
msg_iov: iovecs.as_mut_ptr(),
|
||||
msg_iovlen: iovecs.len(),
|
||||
msg_control: null_mut(),
|
||||
msg_controllen: 0,
|
||||
msg_flags: 0,
|
||||
};
|
||||
|
||||
if !in_fds.is_empty() {
|
||||
msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
|
||||
msg.msg_controllen = cmsg_capacity;
|
||||
}
|
||||
|
||||
// Safe because the msghdr was properly constructed from valid (or null) pointers of the
|
||||
// indicated length and we check the return value.
|
||||
let total_read = unsafe { recvmsg(fd, &mut msg, libc::MSG_WAITALL) };
|
||||
|
||||
if total_read == -1 {
|
||||
return Err(Error::last());
|
||||
}
|
||||
|
||||
if total_read == 0 && msg.msg_controllen < size_of::<cmsghdr>() {
|
||||
return Ok((0, 0));
|
||||
}
|
||||
|
||||
let mut cmsg_ptr = msg.msg_control as *mut cmsghdr;
|
||||
let mut in_fds_count = 0;
|
||||
while !cmsg_ptr.is_null() {
|
||||
// Safe because we checked that cmsg_ptr was non-null, and the loop is constructed such that
|
||||
// that only happens when there is at least sizeof(cmsghdr) space after the pointer to read.
|
||||
let cmsg = unsafe { (cmsg_ptr as *mut cmsghdr).read_unaligned() };
|
||||
|
||||
if cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS {
|
||||
let fd_count = (cmsg.cmsg_len - CMSG_LEN!(0)) / size_of::<RawFd>();
|
||||
unsafe {
|
||||
copy_nonoverlapping(
|
||||
CMSG_DATA(cmsg_ptr),
|
||||
in_fds[in_fds_count..(in_fds_count + fd_count)].as_mut_ptr(),
|
||||
fd_count,
|
||||
);
|
||||
}
|
||||
in_fds_count += fd_count;
|
||||
}
|
||||
|
||||
cmsg_ptr = get_next_cmsg(&msg, &cmsg, cmsg_ptr);
|
||||
}
|
||||
|
||||
Ok((total_read as usize, in_fds_count))
|
||||
}
|
||||
|
||||
/// Trait for file descriptors can send and receive socket control messages via `sendmsg` and
|
||||
/// `recvmsg`.
|
||||
pub trait ScmSocket {
|
||||
/// Gets the file descriptor of this socket.
|
||||
fn socket_fd(&self) -> RawFd;
|
||||
|
||||
/// Sends the given data and file descriptor over the socket.
|
||||
///
|
||||
/// On success, returns the number of bytes sent.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `buf` - A buffer of data to send on the `socket`.
|
||||
/// * `fd` - A file descriptors to be sent.
|
||||
fn send_with_fd<D: IntoIovec>(&self, buf: D, fd: RawFd) -> Result<usize> {
|
||||
self.send_with_fds(&[buf], &[fd])
|
||||
}
|
||||
|
||||
/// Sends the given data and file descriptors over the socket.
|
||||
///
|
||||
/// On success, returns the number of bytes sent.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `bufs` - A list of data buffer to send on the `socket`.
|
||||
/// * `fds` - A list of file descriptors to be sent.
|
||||
fn send_with_fds<D: IntoIovec>(&self, bufs: &[D], fds: &[RawFd]) -> Result<usize> {
|
||||
raw_sendmsg(self.socket_fd(), bufs, fds)
|
||||
}
|
||||
|
||||
/// Receives data and potentially a file descriptor from the socket.
|
||||
///
|
||||
/// On success, returns the number of bytes and an optional file descriptor.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `buf` - A buffer to receive data from the socket.
|
||||
fn recv_with_fd(&self, buf: &mut [u8]) -> Result<(usize, Option<File>)> {
|
||||
let mut fd = [0];
|
||||
let mut iovecs = [iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf.len(),
|
||||
}];
|
||||
|
||||
let (read_count, fd_count) = self.recv_with_fds(&mut iovecs[..], &mut fd)?;
|
||||
let file = if fd_count == 0 {
|
||||
None
|
||||
} else {
|
||||
// Safe because the first fd from recv_with_fds is owned by us and valid because this
|
||||
// branch was taken.
|
||||
Some(unsafe { File::from_raw_fd(fd[0]) })
|
||||
};
|
||||
Ok((read_count, file))
|
||||
}
|
||||
|
||||
/// Receives data and file descriptors from the socket.
|
||||
///
|
||||
/// On success, returns the number of bytes and file descriptors received as a tuple
|
||||
/// `(bytes count, files count)`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `iovecs` - A list of iovec to receive data from the socket.
|
||||
/// * `fds` - A slice of `RawFd`s to put the received file descriptors into. On success, the
|
||||
/// number of valid file descriptors is indicated by the second element of the
|
||||
/// returned tuple. The caller owns these file descriptors, but they will not be
|
||||
/// closed on drop like a `File`-like type would be. It is recommended that each valid
|
||||
/// file descriptor gets wrapped in a drop type that closes it after this returns.
|
||||
fn recv_with_fds(&self, iovecs: &mut [iovec], fds: &mut [RawFd]) -> Result<(usize, usize)> {
|
||||
raw_recvmsg(self.socket_fd(), iovecs, fds)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScmSocket for UnixDatagram {
|
||||
fn socket_fd(&self) -> RawFd {
|
||||
self.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl ScmSocket for UnixStream {
|
||||
fn socket_fd(&self) -> RawFd {
|
||||
self.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that can be converted into an `iovec` that can be referenced by a syscall for
|
||||
/// the lifetime of this object.
|
||||
///
|
||||
/// This trait is unsafe because interfaces that use this trait depend on the base pointer and size
|
||||
/// being accurate.
|
||||
pub unsafe trait IntoIovec {
|
||||
/// Gets the base pointer of this `iovec`.
|
||||
fn as_ptr(&self) -> *const c_void;
|
||||
|
||||
/// Gets the size in bytes of this `iovec`.
|
||||
fn size(&self) -> usize;
|
||||
}
|
||||
|
||||
// Safe because this slice can not have another mutable reference and it's pointer and size are
|
||||
// guaranteed to be valid.
|
||||
unsafe impl<'a> IntoIovec for &'a [u8] {
|
||||
// Clippy false positive: https://github.com/rust-lang/rust-clippy/issues/3480
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::useless_asref))]
|
||||
fn as_ptr(&self) -> *const c_void {
|
||||
self.as_ref().as_ptr() as *const c_void
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::io::Write;
|
||||
use std::mem::size_of;
|
||||
use std::os::raw::c_long;
|
||||
use std::os::unix::net::UnixDatagram;
|
||||
use std::slice::from_raw_parts;
|
||||
|
||||
use libc::cmsghdr;
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[test]
|
||||
fn buffer_len() {
|
||||
assert_eq!(CMSG_SPACE!(0 * size_of::<RawFd>()), size_of::<cmsghdr>());
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(1 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>()
|
||||
);
|
||||
if size_of::<RawFd>() == 4 {
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(2 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>()
|
||||
);
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(3 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>() * 2
|
||||
);
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(4 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>() * 2
|
||||
);
|
||||
} else if size_of::<RawFd>() == 8 {
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(2 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>() * 2
|
||||
);
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(3 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>() * 3
|
||||
);
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(4 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>() * 4
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_recv_no_fd() {
|
||||
let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
|
||||
|
||||
let write_count = s1
|
||||
.send_with_fds(&[[1u8, 1, 2].as_ref(), [21u8, 34, 55].as_ref()], &[])
|
||||
.expect("failed to send data");
|
||||
|
||||
assert_eq!(write_count, 6);
|
||||
|
||||
let mut buf = [0u8; 6];
|
||||
let mut files = [0; 1];
|
||||
let mut iovecs = [iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf.len(),
|
||||
}];
|
||||
let (read_count, file_count) = s2
|
||||
.recv_with_fds(&mut iovecs[..], &mut files)
|
||||
.expect("failed to recv data");
|
||||
|
||||
assert_eq!(read_count, 6);
|
||||
assert_eq!(file_count, 0);
|
||||
assert_eq!(buf, [1, 1, 2, 21, 34, 55]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_recv_only_fd() {
|
||||
let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
|
||||
|
||||
let evt = EventFd::new(0).expect("failed to create eventfd");
|
||||
let write_count = s1
|
||||
.send_with_fd([].as_ref(), evt.as_raw_fd())
|
||||
.expect("failed to send fd");
|
||||
|
||||
assert_eq!(write_count, 0);
|
||||
|
||||
let (read_count, file_opt) = s2.recv_with_fd(&mut []).expect("failed to recv fd");
|
||||
|
||||
let mut file = file_opt.unwrap();
|
||||
|
||||
assert_eq!(read_count, 0);
|
||||
assert!(file.as_raw_fd() >= 0);
|
||||
assert_ne!(file.as_raw_fd(), s1.as_raw_fd());
|
||||
assert_ne!(file.as_raw_fd(), s2.as_raw_fd());
|
||||
assert_ne!(file.as_raw_fd(), evt.as_raw_fd());
|
||||
|
||||
file.write(unsafe { from_raw_parts(&1203u64 as *const u64 as *const u8, 8) })
|
||||
.expect("failed to write to sent fd");
|
||||
|
||||
assert_eq!(evt.read().expect("failed to read from eventfd"), 1203);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_recv_with_fd() {
|
||||
let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
|
||||
|
||||
let evt = EventFd::new(0).expect("failed to create eventfd");
|
||||
let write_count = s1
|
||||
.send_with_fds(&[[237].as_ref()], &[evt.as_raw_fd()])
|
||||
.expect("failed to send fd");
|
||||
|
||||
assert_eq!(write_count, 1);
|
||||
|
||||
let mut files = [0; 2];
|
||||
let mut buf = [0u8];
|
||||
let mut iovecs = [iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf.len(),
|
||||
}];
|
||||
let (read_count, file_count) = s2
|
||||
.recv_with_fds(&mut iovecs[..], &mut files)
|
||||
.expect("failed to recv fd");
|
||||
|
||||
assert_eq!(read_count, 1);
|
||||
assert_eq!(buf[0], 237);
|
||||
assert_eq!(file_count, 1);
|
||||
assert!(files[0] >= 0);
|
||||
assert_ne!(files[0], s1.as_raw_fd());
|
||||
assert_ne!(files[0], s2.as_raw_fd());
|
||||
assert_ne!(files[0], evt.as_raw_fd());
|
||||
|
||||
let mut file = unsafe { File::from_raw_fd(files[0]) };
|
||||
|
||||
file.write(unsafe { from_raw_parts(&1203u64 as *const u64 as *const u8, 8) })
|
||||
.expect("failed to write to sent fd");
|
||||
|
||||
assert_eq!(evt.read().expect("failed to read from eventfd"), 1203);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD file.
|
||||
|
||||
//! Trait to control vhost-vsock backend drivers.
|
||||
|
||||
use crate::backend::VhostBackend;
|
||||
use crate::Result;
|
||||
|
||||
/// Trait to control vhost-vsock backend drivers.
|
||||
pub trait VhostVsock: VhostBackend {
|
||||
/// Set the CID for the guest.
|
||||
/// This number is used for routing all data destined for running in the guest.
|
||||
/// Each guest on a hypervisor must have an unique CID.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `cid` - CID to assign to the guest
|
||||
fn set_guest_cid(&mut self, cid: u64) -> Result<()>;
|
||||
|
||||
/// Tell the VHOST driver to start performing data transfer.
|
||||
fn start(&mut self) -> Result<()>;
|
||||
|
||||
/// Tell the VHOST driver to stop performing data transfer.
|
||||
fn stop(&mut self) -> Result<()>;
|
||||
}
|
||||
@@ -10,13 +10,11 @@ pci_support = ["vm-virtio/pci_support"]
|
||||
mmio_support = ["vm-virtio/mmio_support"]
|
||||
|
||||
[dependencies]
|
||||
epoll = "4.1.0"
|
||||
libc = "0.2.65"
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.68"
|
||||
log = "0.4.8"
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = "0.1.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = "0.1.1"
|
||||
|
||||
[dependencies.vhost_rs]
|
||||
path = "../vhost_rs"
|
||||
features = ["vhost-user-slave"]
|
||||
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
// Copyright 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::error;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
@@ -15,11 +18,12 @@ use std::thread;
|
||||
use vhost_rs::vhost_user::message::{
|
||||
VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures,
|
||||
VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState,
|
||||
VHOST_USER_CONFIG_OFFSET, VHOST_USER_CONFIG_SIZE,
|
||||
};
|
||||
use vhost_rs::vhost_user::{
|
||||
Error as VhostUserError, Result as VhostUserResult, SlaveListener, VhostUserSlaveReqHandler,
|
||||
Error as VhostUserError, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener,
|
||||
VhostUserSlaveReqHandler,
|
||||
};
|
||||
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||
use vm_memory::guest_memory::FileOffset;
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
use vm_virtio::Queue;
|
||||
@@ -63,6 +67,12 @@ pub trait VhostUserBackend: Send + Sync + 'static {
|
||||
/// Virtio features.
|
||||
fn features(&self) -> u64;
|
||||
|
||||
/// Virtio protocol features.
|
||||
fn protocol_features(&self) -> VhostUserProtocolFeatures;
|
||||
|
||||
/// Tell the backend if EVENT_IDX has been negotiated.
|
||||
fn set_event_idx(&mut self, enabled: bool);
|
||||
|
||||
/// Update guest memory regions.
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> result::Result<(), io::Error>;
|
||||
|
||||
@@ -90,6 +100,19 @@ pub trait VhostUserBackend: Send + Sync + 'static {
|
||||
fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Provide an exit EventFd
|
||||
/// When this EventFd is written to the worker thread will exit. An optional id may
|
||||
/// also be provided, if it not provided then the exit event will be first event id
|
||||
/// after the last queue
|
||||
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Set slave fd.
|
||||
/// A default implementation is provided as we cannot expect all backends
|
||||
/// to implement this function.
|
||||
fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {}
|
||||
}
|
||||
|
||||
/// This structure is the public API the backend is allowed to interact with
|
||||
@@ -151,9 +174,14 @@ impl<S: VhostUserBackend> VhostUserDaemon<S> {
|
||||
/// terminate.
|
||||
pub fn wait(&mut self) -> Result<()> {
|
||||
if let Some(handle) = self.main_thread.take() {
|
||||
let _ = handle.join().map_err(Error::WaitDaemon)?;
|
||||
match handle.join().map_err(Error::WaitDaemon)? {
|
||||
Ok(()) => Ok(()),
|
||||
Err(Error::HandleRequest(VhostUserError::SocketBroken(_))) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve the vring worker. This is necessary to perform further
|
||||
@@ -167,7 +195,7 @@ impl<S: VhostUserBackend> VhostUserDaemon<S> {
|
||||
struct AddrMapping {
|
||||
vmm_addr: u64,
|
||||
size: u64,
|
||||
offset: u64,
|
||||
gpa_base: u64,
|
||||
}
|
||||
|
||||
struct Memory {
|
||||
@@ -180,6 +208,8 @@ pub struct Vring {
|
||||
call: Option<EventFd>,
|
||||
err: Option<EventFd>,
|
||||
enabled: bool,
|
||||
event_idx: bool,
|
||||
signalled_used: Option<Wrapping<u16>>,
|
||||
}
|
||||
|
||||
impl Vring {
|
||||
@@ -190,6 +220,8 @@ impl Vring {
|
||||
call: None,
|
||||
err: None,
|
||||
enabled: false,
|
||||
event_idx: false,
|
||||
signalled_used: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,12 +229,37 @@ impl Vring {
|
||||
&mut self.queue
|
||||
}
|
||||
|
||||
pub fn signal_used_queue(&self) -> result::Result<(), io::Error> {
|
||||
if let Some(call) = self.call.as_ref() {
|
||||
return call.write(1);
|
||||
pub fn set_event_idx(&mut self, enabled: bool) {
|
||||
/* Also reset the last signalled event */
|
||||
self.signalled_used = None;
|
||||
self.event_idx = enabled;
|
||||
}
|
||||
|
||||
pub fn needs_notification(&mut self, mem: &GuestMemoryMmap, used_idx: Wrapping<u16>) -> bool {
|
||||
if !self.event_idx {
|
||||
return true;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
let mut notify = true;
|
||||
|
||||
if let Some(old_idx) = self.signalled_used {
|
||||
if let Some(used_event) = self.mut_queue().get_used_event(&mem) {
|
||||
if (used_idx - used_event - Wrapping(1u16)) >= (used_idx - old_idx) {
|
||||
notify = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.signalled_used = Some(used_idx);
|
||||
notify
|
||||
}
|
||||
|
||||
pub fn signal_used_queue(&mut self) -> result::Result<(), io::Error> {
|
||||
if let Some(call) = self.call.as_ref() {
|
||||
call.write(1)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +282,7 @@ type VringEpollHandlerResult<T> = std::result::Result<T, VringEpollHandlerError>
|
||||
struct VringEpollHandler<S: VhostUserBackend> {
|
||||
backend: Arc<RwLock<S>>,
|
||||
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||
exit_event_id: Option<u16>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VringEpollHandler<S> {
|
||||
@@ -233,6 +291,10 @@ impl<S: VhostUserBackend> VringEpollHandler<S> {
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
) -> VringEpollHandlerResult<bool> {
|
||||
if self.exit_event_id == Some(device_event) {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let num_queues = self.vrings.len();
|
||||
if (device_event as usize) < num_queues {
|
||||
if let Some(kick) = &self.vrings[device_event as usize].read().unwrap().kick {
|
||||
@@ -260,6 +322,8 @@ impl<S: VhostUserBackend> VringEpollHandler<S> {
|
||||
enum VringWorkerError {
|
||||
/// Failed while waiting for events.
|
||||
EpollWait(io::Error),
|
||||
/// Failed to handle the event.
|
||||
HandleEvent(VringEpollHandlerError),
|
||||
}
|
||||
|
||||
/// Result of vring worker operations.
|
||||
@@ -304,11 +368,10 @@ impl VringWorker {
|
||||
|
||||
let ev_type = event.data as u16;
|
||||
|
||||
if let Err(e) = handler.handle_event(ev_type, evset) {
|
||||
println!(
|
||||
"vring handler handle event {} with error {:?}\n",
|
||||
ev_type, e
|
||||
);
|
||||
if handler
|
||||
.handle_event(ev_type, evset)
|
||||
.map_err(VringWorkerError::HandleEvent)?
|
||||
{
|
||||
break 'epoll;
|
||||
}
|
||||
}
|
||||
@@ -364,6 +427,8 @@ pub enum VhostUserHandlerError {
|
||||
SpawnVringWorker(io::Error),
|
||||
/// Could not find the mapping from memory regions.
|
||||
MissingMemoryMapping,
|
||||
/// Could not register exit event
|
||||
RegisterExitEvent(io::Error),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VhostUserHandlerError {
|
||||
@@ -374,6 +439,9 @@ impl std::fmt::Display for VhostUserHandlerError {
|
||||
write!(f, "failed spawning the vring worker: {}", e)
|
||||
}
|
||||
VhostUserHandlerError::MissingMemoryMapping => write!(f, "Missing memory mapping"),
|
||||
VhostUserHandlerError::RegisterExitEvent(e) => {
|
||||
write!(f, "Failed to register exit event: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,6 +462,7 @@ struct VhostUserHandler<S: VhostUserBackend> {
|
||||
max_queue_size: usize,
|
||||
memory: Option<Memory>,
|
||||
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||
worker_thread: Option<thread::JoinHandle<VringWorkerResult<()>>>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||
@@ -410,17 +479,36 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||
// Create the epoll file descriptor
|
||||
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?;
|
||||
|
||||
let vring_handler = VringEpollHandler {
|
||||
backend: backend.clone(),
|
||||
vrings: vrings.clone(),
|
||||
};
|
||||
let vring_worker = Arc::new(VringWorker { epoll_fd });
|
||||
let worker = vring_worker.clone();
|
||||
|
||||
thread::Builder::new()
|
||||
.name("vring_worker".to_string())
|
||||
.spawn(move || vring_worker.run(vring_handler))
|
||||
.map_err(VhostUserHandlerError::SpawnVringWorker)?;
|
||||
let exit_event_id =
|
||||
if let Some((exit_event_fd, exit_event_id)) = backend.read().unwrap().exit_event() {
|
||||
let exit_event_id = exit_event_id.unwrap_or(num_queues as u16);
|
||||
worker
|
||||
.register_listener(
|
||||
exit_event_fd.as_raw_fd(),
|
||||
epoll::Events::EPOLLIN,
|
||||
u64::from(exit_event_id),
|
||||
)
|
||||
.map_err(VhostUserHandlerError::RegisterExitEvent)?;
|
||||
Some(exit_event_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let vring_handler = VringEpollHandler {
|
||||
backend: backend.clone(),
|
||||
vrings: vrings.clone(),
|
||||
exit_event_id,
|
||||
};
|
||||
|
||||
let worker_thread = Some(
|
||||
thread::Builder::new()
|
||||
.name("vring_worker".to_string())
|
||||
.spawn(move || vring_worker.run(vring_handler))
|
||||
.map_err(VhostUserHandlerError::SpawnVringWorker)?,
|
||||
);
|
||||
|
||||
Ok(VhostUserHandler {
|
||||
backend,
|
||||
@@ -433,6 +521,7 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||
max_queue_size,
|
||||
memory: None,
|
||||
vrings,
|
||||
worker_thread,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -444,7 +533,7 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||
if let Some(memory) = &self.memory {
|
||||
for mapping in memory.mappings.iter() {
|
||||
if vmm_va >= mapping.vmm_addr && vmm_va < mapping.vmm_addr + mapping.size {
|
||||
return Ok(vmm_va - mapping.vmm_addr + mapping.offset);
|
||||
return Ok(vmm_va - mapping.vmm_addr + mapping.gpa_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,9 +564,7 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
}
|
||||
|
||||
fn set_features(&mut self, features: u64) -> VhostUserResult<()> {
|
||||
if !self.owned {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if (features & !self.backend.read().unwrap().features()) != 0 {
|
||||
if (features & !self.backend.read().unwrap().features()) != 0 {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
@@ -501,7 +588,7 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
}
|
||||
|
||||
fn get_protocol_features(&mut self) -> VhostUserResult<VhostUserProtocolFeatures> {
|
||||
Ok(VhostUserProtocolFeatures::all())
|
||||
Ok(self.backend.read().unwrap().protocol_features())
|
||||
}
|
||||
|
||||
fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> {
|
||||
@@ -524,19 +611,19 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
|
||||
for (idx, region) in ctx.iter().enumerate() {
|
||||
let g_addr = GuestAddress(region.guest_phys_addr);
|
||||
let len = (region.memory_size + region.mmap_offset) as usize;
|
||||
let len = region.memory_size as usize;
|
||||
let file = unsafe { File::from_raw_fd(fds[idx]) };
|
||||
let f_off = FileOffset::new(file, 0);
|
||||
let f_off = FileOffset::new(file, region.mmap_offset);
|
||||
|
||||
regions.push((g_addr, len, Some(f_off)));
|
||||
mappings.push(AddrMapping {
|
||||
vmm_addr: region.user_addr,
|
||||
size: region.memory_size,
|
||||
offset: region.mmap_offset,
|
||||
gpa_base: region.guest_phys_addr,
|
||||
});
|
||||
}
|
||||
|
||||
let mem = GuestMemoryMmap::with_files(regions).map_err(|e| {
|
||||
let mem = GuestMemoryMmap::from_ranges_with_files(regions).map_err(|e| {
|
||||
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||
})?;
|
||||
self.backend
|
||||
@@ -610,6 +697,13 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
.queue
|
||||
.next_avail = Wrapping(base as u16);
|
||||
self.vrings[index as usize].write().unwrap().queue.next_used = Wrapping(base as u16);
|
||||
|
||||
let event_idx: bool = (self.acked_features & (1 << VIRTIO_RING_F_EVENT_IDX)) != 0;
|
||||
self.vrings[index as usize]
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_event_idx(event_idx);
|
||||
self.backend.write().unwrap().set_event_idx(event_idx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -720,16 +814,6 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
size: u32,
|
||||
_flags: VhostUserConfigFlags,
|
||||
) -> VhostUserResult<Vec<u8>> {
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
Ok(self.backend.read().unwrap().get_config(offset, size))
|
||||
}
|
||||
|
||||
@@ -739,21 +823,24 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||
buf: &[u8],
|
||||
_flags: VhostUserConfigFlags,
|
||||
) -> VhostUserResult<()> {
|
||||
let size = buf.len() as u32;
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(VhostUserError::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
self.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_config(offset, buf)
|
||||
.map_err(VhostUserError::ReqHandlerError)
|
||||
}
|
||||
|
||||
fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) {
|
||||
self.backend.write().unwrap().set_slave_req_fd(vu_req);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserBackend> Drop for VhostUserHandler<S> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(thread) = self.worker_thread.take() {
|
||||
if let Err(e) = thread.join() {
|
||||
error!("Error in vring worker: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
vhost_user_block/Cargo.toml
Normal file
18
vhost_user_block/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "vhost_user_block"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.1.0"
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.68"
|
||||
log = "0.4.8"
|
||||
qcow = { path = "../qcow" }
|
||||
vhost_user_backend = { path = "../vhost_user_backend" }
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = "0.1.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
450
vhost_user_block/src/lib.rs
Normal file
450
vhost_user_block/src/lib.rs
Normal file
@@ -0,0 +1,450 @@
|
||||
// Copyright 2019 Red Hat, Inc. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
|
||||
|
||||
extern crate log;
|
||||
extern crate vhost_rs;
|
||||
extern crate vhost_user_backend;
|
||||
extern crate vm_virtio;
|
||||
|
||||
use epoll;
|
||||
use libc::EFD_NONBLOCK;
|
||||
use log::*;
|
||||
use qcow::{self, ImageType, QcowFile};
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Read;
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
use std::mem;
|
||||
use std::num::Wrapping;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::slice;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Instant;
|
||||
use std::vec::Vec;
|
||||
use std::{convert, error, fmt, io};
|
||||
use vhost_rs::vhost_user::message::*;
|
||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
||||
use virtio_bindings::bindings::virtio_blk::*;
|
||||
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||
use vm_memory::{Bytes, GuestMemoryError, GuestMemoryMmap};
|
||||
use vm_virtio::block::{build_disk_image_id, Request};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: usize = 1024;
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT;
|
||||
const BLK_SIZE: u32 = 512;
|
||||
// Current (2020) enterprise SSDs have a latency lower than 30us.
|
||||
// Polling for 50us should be enough to cover for the device latency
|
||||
// and the overhead of the emulation layer.
|
||||
const POLL_QUEUE_US: u128 = 50;
|
||||
|
||||
trait DiskFile: Read + Seek + Write + Send + Sync {}
|
||||
impl<D: Read + Seek + Write + Send + Sync> DiskFile for D {}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to detect image type.
|
||||
DetectImageType,
|
||||
/// Bad memory address.
|
||||
GuestMemory(GuestMemoryError),
|
||||
/// Can't open image file.
|
||||
OpenImage,
|
||||
/// Failed to parse direct parameter.
|
||||
ParseDirectParam,
|
||||
/// Failed to parse image parameter.
|
||||
ParseImageParam,
|
||||
/// Failed to parse sock parameter.
|
||||
ParseSockParam,
|
||||
/// Failed to parse readonly parameter.
|
||||
ParseReadOnlyParam,
|
||||
/// Failed parsing fs number of queues parameter.
|
||||
ParseBlkNumQueuesParam(std::num::ParseIntError),
|
||||
/// Failed to parse the poll_queue parameter.
|
||||
ParsePollQueueParam,
|
||||
/// Failed to handle event other than input event.
|
||||
HandleEventNotEpollIn,
|
||||
/// Failed to create kill eventfd
|
||||
CreateKillEventFd(io::Error),
|
||||
/// Failed to handle unknown event.
|
||||
HandleEventUnknownEvent,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "vhost_user_block_error: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
impl convert::From<Error> for io::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserBlkBackend {
|
||||
mem: Option<GuestMemoryMmap>,
|
||||
vring_worker: Option<Arc<VringWorker>>,
|
||||
disk_image: Box<dyn DiskFile>,
|
||||
disk_image_id: Vec<u8>,
|
||||
disk_nsectors: u64,
|
||||
config: virtio_blk_config,
|
||||
rdonly: bool,
|
||||
poll_queue: bool,
|
||||
event_idx: bool,
|
||||
kill_evt: EventFd,
|
||||
}
|
||||
|
||||
impl VhostUserBlkBackend {
|
||||
pub fn new(
|
||||
image_path: String,
|
||||
num_queues: usize,
|
||||
rdonly: bool,
|
||||
direct: bool,
|
||||
poll_queue: bool,
|
||||
) -> Result<Self> {
|
||||
let mut options = OpenOptions::new();
|
||||
options.read(true);
|
||||
options.write(!rdonly);
|
||||
if direct {
|
||||
options.custom_flags(libc::O_DIRECT);
|
||||
}
|
||||
let image: File = options.open(&image_path).unwrap();
|
||||
let mut raw_img: vm_virtio::RawFile = vm_virtio::RawFile::new(image, direct);
|
||||
|
||||
let image_id = build_disk_image_id(&PathBuf::from(&image_path));
|
||||
let image_type = qcow::detect_image_type(&mut raw_img).unwrap();
|
||||
let mut image = match image_type {
|
||||
ImageType::Raw => Box::new(raw_img) as Box<dyn DiskFile>,
|
||||
ImageType::Qcow2 => Box::new(QcowFile::from(raw_img).unwrap()) as Box<dyn DiskFile>,
|
||||
};
|
||||
|
||||
let nsectors = (image.seek(SeekFrom::End(0)).unwrap() as u64) / SECTOR_SIZE;
|
||||
let mut config = virtio_blk_config::default();
|
||||
|
||||
config.capacity = nsectors;
|
||||
config.blk_size = BLK_SIZE;
|
||||
config.size_max = 65535;
|
||||
config.seg_max = 128 - 2;
|
||||
config.min_io_size = 1;
|
||||
config.opt_io_size = 1;
|
||||
config.num_queues = num_queues as u16;
|
||||
config.wce = 1;
|
||||
|
||||
Ok(VhostUserBlkBackend {
|
||||
mem: None,
|
||||
vring_worker: None,
|
||||
disk_image: image,
|
||||
disk_image_id: image_id,
|
||||
disk_nsectors: nsectors,
|
||||
config,
|
||||
rdonly,
|
||||
poll_queue,
|
||||
event_idx: false,
|
||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_queue(&mut self, vring: &mut Vring) -> bool {
|
||||
let mut used_any = false;
|
||||
let mem = match self.mem.as_ref() {
|
||||
Some(m) => m,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
while let Some(head) = vring.mut_queue().iter(mem).next() {
|
||||
debug!("got an element in the queue");
|
||||
let len;
|
||||
match Request::parse(&head, mem) {
|
||||
Ok(request) => {
|
||||
debug!("element is a valid request");
|
||||
let status = match request.execute(
|
||||
&mut self.disk_image,
|
||||
self.disk_nsectors,
|
||||
mem,
|
||||
&self.disk_image_id,
|
||||
) {
|
||||
Ok(l) => {
|
||||
len = l;
|
||||
VIRTIO_BLK_S_OK
|
||||
}
|
||||
Err(e) => {
|
||||
len = 1;
|
||||
e.status()
|
||||
}
|
||||
};
|
||||
mem.write_obj(status, request.status_addr).unwrap();
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to parse available descriptor chain: {:?}", err);
|
||||
len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if self.event_idx {
|
||||
if let Some(used_idx) = vring.mut_queue().add_used(mem, head.index, len) {
|
||||
if vring.needs_notification(&mem, Wrapping(used_idx)) {
|
||||
debug!("signalling queue");
|
||||
vring.signal_used_queue().unwrap();
|
||||
} else {
|
||||
debug!("omitting signal (event_idx)");
|
||||
}
|
||||
used_any = true;
|
||||
}
|
||||
} else {
|
||||
debug!("signalling queue");
|
||||
vring.mut_queue().add_used(mem, head.index, len);
|
||||
vring.signal_used_queue().unwrap();
|
||||
used_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
used_any
|
||||
}
|
||||
|
||||
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
|
||||
self.vring_worker = vring_worker;
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserBackend for VhostUserBlkBackend {
|
||||
fn num_queues(&self) -> usize {
|
||||
self.config.num_queues as usize
|
||||
}
|
||||
|
||||
fn max_queue_size(&self) -> usize {
|
||||
QUEUE_SIZE
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
let mut avail_features = 1 << VIRTIO_BLK_F_MQ
|
||||
| 1 << VIRTIO_BLK_F_CONFIG_WCE
|
||||
| 1 << VIRTIO_RING_F_EVENT_IDX
|
||||
| 1 << VIRTIO_F_VERSION_1
|
||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
|
||||
if self.rdonly {
|
||||
avail_features |= 1 << VIRTIO_BLK_F_RO;
|
||||
}
|
||||
avail_features
|
||||
}
|
||||
|
||||
fn protocol_features(&self) -> VhostUserProtocolFeatures {
|
||||
VhostUserProtocolFeatures::CONFIG
|
||||
}
|
||||
|
||||
fn set_event_idx(&mut self, enabled: bool) {
|
||||
self.event_idx = enabled;
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||
self.mem = Some(mem);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
) -> VhostUserBackendResult<bool> {
|
||||
if evset != epoll::Events::EPOLLIN {
|
||||
return Err(Error::HandleEventNotEpollIn.into());
|
||||
}
|
||||
|
||||
debug!("event received: {:?}", device_event);
|
||||
|
||||
match device_event {
|
||||
q if device_event < self.config.num_queues => {
|
||||
let mut vring = vrings[q as usize].write().unwrap();
|
||||
|
||||
if self.poll_queue {
|
||||
// Actively poll the queue until POLL_QUEUE_US has passed
|
||||
// without seeing a new request.
|
||||
let mut now = Instant::now();
|
||||
loop {
|
||||
if self.process_queue(&mut vring) {
|
||||
now = Instant::now();
|
||||
} else if now.elapsed().as_micros() > POLL_QUEUE_US {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.event_idx {
|
||||
// vm-virtio's Queue implementation only checks avail_index
|
||||
// once, so to properly support EVENT_IDX we need to keep
|
||||
// calling process_queue() until it stops finding new
|
||||
// requests on the queue.
|
||||
loop {
|
||||
vring
|
||||
.mut_queue()
|
||||
.update_avail_event(self.mem.as_ref().unwrap());
|
||||
if !self.process_queue(&mut vring) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Without EVENT_IDX, a single call is enough.
|
||||
self.process_queue(&mut vring);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
_ => Err(Error::HandleEventUnknownEvent.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
|
||||
// self.config is a statically allocated virtio_blk_config
|
||||
let buf = unsafe {
|
||||
slice::from_raw_parts(
|
||||
&self.config as *const virtio_blk_config as *const _,
|
||||
mem::size_of::<virtio_blk_config>(),
|
||||
)
|
||||
};
|
||||
|
||||
buf.to_vec()
|
||||
}
|
||||
|
||||
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
|
||||
Some((self.kill_evt.try_clone().unwrap(), None))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VhostUserBlkBackendConfig<'a> {
|
||||
pub image: &'a str,
|
||||
pub sock: &'a str,
|
||||
pub num_queues: usize,
|
||||
pub readonly: bool,
|
||||
pub direct: bool,
|
||||
pub poll_queue: bool,
|
||||
}
|
||||
|
||||
impl<'a> VhostUserBlkBackendConfig<'a> {
|
||||
pub fn parse(backend: &'a str) -> Result<Self> {
|
||||
let params_list: Vec<&str> = backend.split(',').collect();
|
||||
|
||||
let mut image: &str = "";
|
||||
let mut sock: &str = "";
|
||||
let mut num_queues_str: &str = "";
|
||||
let mut readonly: bool = false;
|
||||
let mut direct: bool = false;
|
||||
let mut poll_queue: bool = true;
|
||||
|
||||
for param in params_list.iter() {
|
||||
if param.starts_with("image=") {
|
||||
image = ¶m[6..];
|
||||
} else if param.starts_with("sock=") {
|
||||
sock = ¶m[5..];
|
||||
} else if param.starts_with("num_queues=") {
|
||||
num_queues_str = ¶m[11..];
|
||||
} else if param.starts_with("readonly=") {
|
||||
readonly = match param[9..].parse::<bool>() {
|
||||
Ok(b) => b,
|
||||
Err(_) => return Err(Error::ParseReadOnlyParam),
|
||||
}
|
||||
} else if param.starts_with("direct=") {
|
||||
direct = match param[7..].parse::<bool>() {
|
||||
Ok(b) => b,
|
||||
Err(_) => return Err(Error::ParseDirectParam),
|
||||
}
|
||||
} else if param.starts_with("poll_queue=") {
|
||||
poll_queue = match param[11..].parse::<bool>() {
|
||||
Ok(b) => b,
|
||||
Err(_) => return Err(Error::ParsePollQueueParam),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut num_queues: usize = 1;
|
||||
if image.is_empty() {
|
||||
return Err(Error::ParseImageParam);
|
||||
}
|
||||
if sock.is_empty() {
|
||||
return Err(Error::ParseSockParam);
|
||||
}
|
||||
if !num_queues_str.is_empty() {
|
||||
num_queues = num_queues_str
|
||||
.parse()
|
||||
.map_err(Error::ParseBlkNumQueuesParam)?;
|
||||
}
|
||||
Ok(VhostUserBlkBackendConfig {
|
||||
image,
|
||||
sock,
|
||||
num_queues,
|
||||
readonly,
|
||||
direct,
|
||||
poll_queue,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_block_backend(backend_command: &str) {
|
||||
let backend_config = match VhostUserBlkBackendConfig::parse(backend_command) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
println!("Failed parsing parameters {:?}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let blk_backend = Arc::new(RwLock::new(
|
||||
VhostUserBlkBackend::new(
|
||||
backend_config.image.to_string(),
|
||||
backend_config.num_queues,
|
||||
backend_config.readonly,
|
||||
backend_config.direct,
|
||||
backend_config.poll_queue,
|
||||
)
|
||||
.unwrap(),
|
||||
));
|
||||
|
||||
debug!("blk_backend is created!\n");
|
||||
|
||||
let name = "vhost-user-blk-backend";
|
||||
let mut blk_daemon = VhostUserDaemon::new(
|
||||
name.to_string(),
|
||||
backend_config.sock.to_string(),
|
||||
blk_backend.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
debug!("blk_daemon is created!\n");
|
||||
|
||||
let vring_worker = blk_daemon.get_vring_worker();
|
||||
blk_backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_vring_worker(Some(vring_worker));
|
||||
|
||||
if let Err(e) = blk_daemon.start() {
|
||||
error!(
|
||||
"Failed to start daemon for vhost-user-block with error: {:?}\n",
|
||||
e
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(e) = blk_daemon.wait() {
|
||||
error!("Error from the main thread: {:?}", e);
|
||||
}
|
||||
|
||||
let kill_evt = &blk_backend.write().unwrap().kill_evt;
|
||||
if let Err(e) = kill_evt.write(1) {
|
||||
error!("Error shutting down worker thread: {:?}", e)
|
||||
}
|
||||
}
|
||||
13
vhost_user_fs/Cargo.toml
Normal file
13
vhost_user_fs/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "vhost_user_fs"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.1.0"
|
||||
libc = "0.2.68"
|
||||
log = "0.4.8"
|
||||
vm-memory = "0.1.0"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user