mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
994 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
226d226fef | ||
|
|
293b8fad41 | ||
|
|
54ade15f9e | ||
|
|
03f64d12c3 | ||
|
|
9c3166237d | ||
|
|
3534540e6b | ||
|
|
f444451a04 | ||
|
|
89c25ea00c | ||
|
|
d89ab632e9 | ||
|
|
e32be99d6c | ||
|
|
4a62821e07 | ||
|
|
548ef43ca2 | ||
|
|
27c1b40c83 | ||
|
|
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 | ||
|
|
8e8a7b6d07 | ||
|
|
2d7bfdd920 | ||
|
|
5822969afa | ||
|
|
defc33927f | ||
|
|
efbafdf9ed | ||
|
|
2b60452195 | ||
|
|
e8a25c22f6 | ||
|
|
b8be1dc24a | ||
|
|
83631599a3 | ||
|
|
78e2f7a99a | ||
|
|
205b8c1cd5 | ||
|
|
3acf9dfcf3 | ||
|
|
63c30a6e79 | ||
|
|
c65ead5de8 | ||
|
|
837bcbc6ba | ||
|
|
3598e603d5 | ||
|
|
34bb31791b | ||
|
|
9085a39c7d | ||
|
|
5fc3f37c9b | ||
|
|
3bb51d4d5e | ||
|
|
cc72ed1cc9 | ||
|
|
8663b429b3 | ||
|
|
df336ade57 | ||
|
|
9e78c2e686 | ||
|
|
786e33931f | ||
|
|
90d1083bda | ||
|
|
5ca068a068 | ||
|
|
2a0ba7aef8 | ||
|
|
097b30669f | ||
|
|
ef090cf37d | ||
|
|
db3ece8ef2 | ||
|
|
af41d6fc88 | ||
|
|
d2d3abb13c | ||
|
|
dbbd04a4cf | ||
|
|
4ac0cb9cff | ||
|
|
80c3fd922a | ||
|
|
37a7000fdd | ||
|
|
6e9e24ec0f | ||
|
|
1fc8ee945a | ||
|
|
cb59f826ad | ||
|
|
688ec0eb2d | ||
|
|
8e018d6feb | ||
|
|
c446b9d510 | ||
|
|
6df7cd0e4b | ||
|
|
3a04db5936 | ||
|
|
ad7d02cd74 | ||
|
|
1298b508bf | ||
|
|
a95fa1c4e8 | ||
|
|
228adebc32 | ||
|
|
14eb071b29 | ||
|
|
5652cc7a05 | ||
|
|
2c50c963f5 | ||
|
|
46848fdc43 | ||
|
|
b918220b49 | ||
|
|
278ab05cbc | ||
|
|
32d07e40cc | ||
|
|
63869bde75 | ||
|
|
fb4769388b | ||
|
|
20c4ed829a | ||
|
|
4b8d7e718d | ||
|
|
6e0aa56f06 | ||
|
|
03352f45f9 | ||
|
|
f40adff2a1 | ||
|
|
0acb1e329d | ||
|
|
6566c739e1 | ||
|
|
9ab00dcb75 | ||
|
|
ee1899c6f6 | ||
|
|
392f1ec155 | ||
|
|
9fad680db1 | ||
|
|
9ebb1a55bc | ||
|
|
85e1865cb5 | ||
|
|
4b1328a29c | ||
|
|
8225d4cd6e | ||
|
|
dac7737919 | ||
|
|
3e750de43f | ||
|
|
eb91bc812b | ||
|
|
59b4aaba87 | ||
|
|
8288cb2ac8 | ||
|
|
2a466132a0 | ||
|
|
fc5c210498 | ||
|
|
8dbb16df4d | ||
|
|
7abbad0a62 | ||
|
|
7328ecdb3b | ||
|
|
f9daf2e247 | ||
|
|
43b3642955 | ||
|
|
42758244a0 | ||
|
|
27af983ec9 | ||
|
|
b70344158b | ||
|
|
7e0cb078ed | ||
|
|
9a93f4f0a6 | ||
|
|
c505cfae2b | ||
|
|
8a5e47f989 | ||
|
|
46cde1a38e | ||
|
|
ce0b475ef7 | ||
|
|
f674019ea1 | ||
|
|
f2de4d0315 | ||
|
|
6a722e5c0b | ||
|
|
aa31748781 | ||
|
|
b14fd37db9 | ||
|
|
f27aa21e3f | ||
|
|
fe5561df50 | ||
|
|
2371325f9c | ||
|
|
e50f4418a2 | ||
|
|
8916dad2da | ||
|
|
1b66a2fa60 | ||
|
|
8ea4145f98 | ||
|
|
47367eb61b | ||
|
|
df3e5c874f | ||
|
|
8ec6cda0c5 | ||
|
|
8c33eb3069 | ||
|
|
f6d1a9d9b8 | ||
|
|
d724511a91 | ||
|
|
9ff42060e0 | ||
|
|
db151819f1 | ||
|
|
9356af80c6 | ||
|
|
6a4a931b9e | ||
|
|
038f198129 | ||
|
|
833a3d456c | ||
|
|
c0ca3b6b8e | ||
|
|
a9eb352aea | ||
|
|
09392f0530 | ||
|
|
527dd68ce1 | ||
|
|
4a1af7f63c | ||
|
|
c4309515c9 | ||
|
|
347611b0c7 | ||
|
|
f14ab872ec | ||
|
|
cfc8c39446 | ||
|
|
1aab372a06 | ||
|
|
c1b26b1fab | ||
|
|
d80ac43ef1 | ||
|
|
5f076923d3 | ||
|
|
36de390caf | ||
|
|
d4f7f73bc8 | ||
|
|
4ed81894aa | ||
|
|
2e2cad91ae | ||
|
|
b5ee9212c1 | ||
|
|
162791b571 | ||
|
|
8207b2e97d | ||
|
|
a0455167d0 | ||
|
|
1bc47507b7 | ||
|
|
5ba61f6d5e | ||
|
|
6d6e290000 | ||
|
|
0e7a1fc923 | ||
|
|
f63cb85f93 | ||
|
|
ff1cb11946 | ||
|
|
df2516f229 | ||
|
|
2ae3919181 | ||
|
|
8188074300 | ||
|
|
e235c6de4f | ||
|
|
151f96e454 | ||
|
|
2f1ff23066 | ||
|
|
4671a5831f | ||
|
|
03ab6839c1 | ||
|
|
6710a39b5a | ||
|
|
feb1c33084 | ||
|
|
47167a658e | ||
|
|
ea7abc6c80 | ||
|
|
e6ef9ece2c | ||
|
|
2e9d815701 | ||
|
|
2e0f1c2afe | ||
|
|
e869283b4e | ||
|
|
bdfd1a3f38 | ||
|
|
9c5135da7a | ||
|
|
b79c1f7722 | ||
|
|
acc60b0ad5 | ||
|
|
3dc7aff00e | ||
|
|
5f8a62f3d0 | ||
|
|
36137232f0 | ||
|
|
79a02f9171 | ||
|
|
00674cd850 | ||
|
|
5323da031c | ||
|
|
0688bec298 | ||
|
|
675e46355c | ||
|
|
036890e5be | ||
|
|
9c5bfb8e13 | ||
|
|
dbff0e94b8 | ||
|
|
0c8f9d2768 | ||
|
|
f06b2aaaa7 | ||
|
|
a2f3da3488 | ||
|
|
2cd406ba50 | ||
|
|
29b3848ffb | ||
|
|
d723b7dae8 | ||
|
|
0a229ef4f5 | ||
|
|
6b06cec611 | ||
|
|
360980d93c | ||
|
|
39083d705b | ||
|
|
a949ab21f7 | ||
|
|
4164853ec6 | ||
|
|
c7559bb7a4 | ||
|
|
397d388710 | ||
|
|
b232de9963 | ||
|
|
927148dd3c | ||
|
|
c347f84d74 | ||
|
|
0a0c7358a2 | ||
|
|
5f0337c21d | ||
|
|
b488d4859b | ||
|
|
5b3ca78dac | ||
|
|
180e6d1e78 | ||
|
|
f0360c92d9 | ||
|
|
f9b0875a60 | ||
|
|
bf4f3db6c4 | ||
|
|
eb60106159 | ||
|
|
67ef4f0d74 | ||
|
|
f622a76597 | ||
|
|
3bc11a4a2e | ||
|
|
4df5ebea12 | ||
|
|
0739c2c7fd | ||
|
|
7358144f09 | ||
|
|
3567206059 | ||
|
|
ea7f4d7f0b | ||
|
|
c0fede0774 | ||
|
|
1097afbaff | ||
|
|
ccb83282e9 | ||
|
|
3ad4b8486f | ||
|
|
1099f0726b | ||
|
|
26974c7625 | ||
|
|
c042483953 | ||
|
|
6d27ac9dfc | ||
|
|
3df1680888 | ||
|
|
f5a44ea1ad | ||
|
|
e950aa6b9a | ||
|
|
05b5115e67 | ||
|
|
7edc46f492 | ||
|
|
7c6ef7fd63 | ||
|
|
bf37b960ec | ||
|
|
4d86359a09 | ||
|
|
5e5c2f2c48 | ||
|
|
a9b2207bcf | ||
|
|
40fc6c3f0f | ||
|
|
389f9e3779 | ||
|
|
8f37dec498 | ||
|
|
c00b58de9b | ||
|
|
5f752dade7 | ||
|
|
8e7d67fcfe | ||
|
|
b515d48eca | ||
|
|
eb46aa2b22 | ||
|
|
7975394901 | ||
|
|
5a3472847d | ||
|
|
475e487ac3 | ||
|
|
434a5d0edf | ||
|
|
df61a8fea2 | ||
|
|
22f91ab3a2 | ||
|
|
4ccc81fdf9 | ||
|
|
11e7ece9f5 | ||
|
|
c48ca61417 | ||
|
|
69e27288a2 | ||
|
|
d2db34edf2 | ||
|
|
d089ee4e25 | ||
|
|
2f4de81175 | ||
|
|
9ac967e3d8 | ||
|
|
f740a3568d | ||
|
|
e21bc972b0 | ||
|
|
d446a8217a | ||
|
|
2432ad07ea | ||
|
|
9661e8da5d | ||
|
|
7688e6e231 | ||
|
|
d784ac2982 | ||
|
|
8c2a9a75ec | ||
|
|
b8622b5c69 | ||
|
|
fe9398fe87 | ||
|
|
1f06c5907f | ||
|
|
5dd675710b | ||
|
|
037807f949 | ||
|
|
47ca277690 | ||
|
|
f59cad15a3 | ||
|
|
7ce0db90f0 | ||
|
|
9e764fc091 | ||
|
|
bb2e7bb942 | ||
|
|
40f9da524f | ||
|
|
8308e1bf25 | ||
|
|
ad128bf72d | ||
|
|
7205700c5f | ||
|
|
3af5619256 | ||
|
|
614eb68f16 | ||
|
|
5a187ee2c2 | ||
|
|
ae66a44d26 | ||
|
|
ebe8edd423 | ||
|
|
011496bda0 | ||
|
|
2610f4353d | ||
|
|
15387cd96a | ||
|
|
638bf0378c | ||
|
|
451502b50b | ||
|
|
98f81c36ec | ||
|
|
ee83c2d44e | ||
|
|
eea6f1dc9e | ||
|
|
3e99098bf3 | ||
|
|
e05de4514d | ||
|
|
6fb7c3bbc2 | ||
|
|
a44a903587 | ||
|
|
b4187a1b9d | ||
|
|
8718043dfc | ||
|
|
add0471120 | ||
|
|
772191b409 | ||
|
|
97699a521f | ||
|
|
a4ebcf486d | ||
|
|
cdfe576eb1 | ||
|
|
bc42420583 | ||
|
|
b7d3ad9063 | ||
|
|
56cad00f2e | ||
|
|
cc7a96e9d3 | ||
|
|
f21d54f6b0 | ||
|
|
584a2cccee | ||
|
|
633f51af9c | ||
|
|
51306555e7 | ||
|
|
ce60ff16c4 | ||
|
|
3dd329052c | ||
|
|
b2f85cbdc4 | ||
|
|
18a8bb0072 | ||
|
|
151637b647 | ||
|
|
c316c161a6 | ||
|
|
808fcaa43b | ||
|
|
bc87c9f19b | ||
|
|
66a7a94a12 | ||
|
|
dfb18ef14a | ||
|
|
0b8856d148 | ||
|
|
ec0b5567c8 | ||
|
|
f4d41d600b | ||
|
|
582fc7f989 | ||
|
|
44d8ab06ac | ||
|
|
5f52dd2d1e | ||
|
|
aface5bca2 | ||
|
|
dc31db478a | ||
|
|
b528e99b18 | ||
|
|
e84179994e | ||
|
|
1d9ad9e7b0 | ||
|
|
26a210a83a | ||
|
|
55f01b2180 | ||
|
|
49a129f3d3 | ||
|
|
1c23a09cc6 | ||
|
|
f0082fecb9 | ||
|
|
8b78e1221e | ||
|
|
f5a6e3c1ca | ||
|
|
d6e3b703ab | ||
|
|
71154d8362 | ||
|
|
7245cf7c5e | ||
|
|
7adb9d55ec | ||
|
|
760791abbe | ||
|
|
7b718f3029 | ||
|
|
08ed88c8d1 | ||
|
|
ab6a8f19f0 | ||
|
|
567eda45ec | ||
|
|
0affdd0df7 | ||
|
|
658c076eb2 | ||
|
|
c8364172a3 | ||
|
|
c52e276a5c | ||
|
|
48a9300667 | ||
|
|
10abfd4448 | ||
|
|
6678cbfb79 | ||
|
|
513d2fdcf6 | ||
|
|
76e3a30c31 | ||
|
|
0d53a5890d | ||
|
|
b3c809a78c | ||
|
|
af9a72eab6 | ||
|
|
2e0508cdc6 | ||
|
|
3c29c47783 | ||
|
|
74225ab5b3 | ||
|
|
f30ba069b7 | ||
|
|
e0fda0611c | ||
|
|
e2b38cc050 | ||
|
|
d97079d793 | ||
|
|
d180deb679 | ||
|
|
c6feb03dc0 | ||
|
|
ef2e8b6bc2 | ||
|
|
3645bf8d0f | ||
|
|
021e8d9e13 | ||
|
|
4b3fd906f7 | ||
|
|
bc5b72ff73 | ||
|
|
6c06420a11 | ||
|
|
b608671031 | ||
|
|
97c964891c | ||
|
|
df5058ec0a | ||
|
|
d9a355f85a | ||
|
|
f910476dd7 | ||
|
|
aa44726658 | ||
|
|
c0e2bbb23f | ||
|
|
4c9759e4fc | ||
|
|
b7ed71b012 | ||
|
|
8fcaf91d3b | ||
|
|
87195c9ccc | ||
|
|
fca911e5f3 | ||
|
|
91ce39e2a6 | ||
|
|
6cc3e88742 | ||
|
|
d1cd3c89c6 | ||
|
|
91c7f271b3 | ||
|
|
87f148cbff | ||
|
|
778c60f21d | ||
|
|
846505d360 | ||
|
|
9caad7394d | ||
|
|
ac950d9a97 | ||
|
|
a1f408a247 | ||
|
|
dfd44a6080 | ||
|
|
7d6bf75138 | ||
|
|
20f8d8d700 | ||
|
|
4ba1d2274e | ||
|
|
bd612b6e53 | ||
|
|
b713737f81 | ||
|
|
35a3b47554 | ||
|
|
f927d1a2d7 | ||
|
|
0c9547618a | ||
|
|
6abd50f4b1 | ||
|
|
c7f8498571 | ||
|
|
8a7cfe8ec4 | ||
|
|
49ef201cd1 | ||
|
|
a548a01423 | ||
|
|
baec27698e | ||
|
|
1a484a82f9 | ||
|
|
532f6a96f3 | ||
|
|
c0756c429d | ||
|
|
8c4c162109 | ||
|
|
d52684450f | ||
|
|
facc3b303a | ||
|
|
09aced9ed1 | ||
|
|
56c4b7000a | ||
|
|
d18c8d4c8c | ||
|
|
d217089b54 | ||
|
|
347f8a036b | ||
|
|
b6ae2ccda4 | ||
|
|
f86b9dd95e | ||
|
|
be199e5560 | ||
|
|
98d7955e34 |
@@ -3,15 +3,8 @@ language: rust
|
|||||||
rust:
|
rust:
|
||||||
- stable
|
- stable
|
||||||
|
|
||||||
before_script:
|
|
||||||
- rustup component add clippy
|
|
||||||
- rustup component add rustfmt
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- cargo build --release
|
- cargo build --release
|
||||||
- cargo test
|
|
||||||
- cargo clippy --all-targets --all-features -- -D warnings
|
|
||||||
- find . -name "*.rs" | grep -v "vendor/" | xargs rustfmt --check
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
provider: releases
|
provider: releases
|
||||||
|
|||||||
@@ -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
|
Cloud Hypervisor uses the “fork-and-pull” development model. Follow these steps if
|
||||||
you want to merge your changes to `cloud-hypervisor`:
|
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.
|
into your github organization.
|
||||||
2. Within your fork, create a branch for your contribution.
|
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/)
|
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
|
## Issue tracking
|
||||||
|
|
||||||
If you have a problem, please let us know. We recommend using
|
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.
|
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).
|
To quickly and informally bring something up to us, you can also reach out on [Slack](https://cloud-hypervisor.slack.com).
|
||||||
|
|||||||
1084
Cargo.lock
generated
1084
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
58
Cargo.toml
58
Cargo.toml
@@ -1,24 +1,68 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cloud-hypervisor"
|
name = "cloud-hypervisor"
|
||||||
version = "0.1.0"
|
version = "0.5.1"
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
default-run = "cloud-hypervisor"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "=2.27.1"
|
arc-swap = "0.4.4"
|
||||||
|
clap = "2.33.0"
|
||||||
|
epoll = "4.1.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
libc = "0.2.66"
|
||||||
|
log = { version = "0.4.8", features = ["std"] }
|
||||||
|
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" }
|
vmm = { path = "vmm" }
|
||||||
|
vm-device = { path = "vm-device" }
|
||||||
|
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||||
|
vmm-sys-util = "0.4.0"
|
||||||
|
vm-virtio = { path = "vm-virtio" }
|
||||||
|
|
||||||
|
[dependencies.vhost_rs]
|
||||||
|
path = "vhost_rs"
|
||||||
|
features = ["vhost-user-slave"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ssh2 = "=0.3.3"
|
ssh2 = "0.7.1"
|
||||||
dirs = "2.0.0"
|
dirs = "2.0.2"
|
||||||
credibility = "0.1.3"
|
credibility = "0.1.3"
|
||||||
tempdir="0.3.7"
|
tempdir= "0.3.7"
|
||||||
lazy_static=">=1.1.0"
|
lazy_static= "1.4.0"
|
||||||
|
serde_json = "1.0.48"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["acpi", "pci", "cmos"]
|
||||||
|
acpi = ["vmm/acpi"]
|
||||||
|
pci = ["vmm/pci_support"]
|
||||||
|
mmio = ["vmm/mmio_support"]
|
||||||
|
cmos = ["vmm/cmos"]
|
||||||
|
|
||||||
# Integration tests require a special environment to run in
|
# Integration tests require a special environment to run in
|
||||||
integration_tests = []
|
integration_tests = []
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"arch",
|
||||||
|
"devices",
|
||||||
|
"vhost_rs",
|
||||||
|
"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",
|
||||||
|
]
|
||||||
|
|||||||
98
Jenkinsfile
vendored
98
Jenkinsfile
vendored
@@ -1,23 +1,85 @@
|
|||||||
stage ("Builds") {
|
pipeline{
|
||||||
node ('bionic') {
|
agent none
|
||||||
stage ('Checkout') {
|
stages {
|
||||||
checkout scm
|
stage ('Master build') {
|
||||||
|
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') {
|
||||||
|
steps {
|
||||||
|
cancelPreviousBuilds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
stage ('Install system packages') {
|
stage ('Worker build') {
|
||||||
sh "sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq build-essential mtools libssl-dev pkg-config"
|
agent { node { label 'bionic' } }
|
||||||
sh "sudo apt-get install -yq flex bison libelf-dev qemu-utils qemu-system"
|
options {
|
||||||
}
|
timeout(time: 1, unit: 'HOURS')
|
||||||
stage ('Install Rust') {
|
}
|
||||||
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y"
|
stages {
|
||||||
}
|
stage ('Checkout') {
|
||||||
stage ('Run unit tests') {
|
steps {
|
||||||
sh "sudo chmod a+rw /dev/kvm"
|
checkout scm
|
||||||
sh "scripts/run_unit_tests.sh"
|
}
|
||||||
}
|
}
|
||||||
stage ('Run integration tests') {
|
stage ('Install system packages') {
|
||||||
sh "sudo mount -t tmpfs tmpfs /tmp"
|
steps {
|
||||||
sh "scripts/run_integration_tests.sh"
|
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 libcap-ng-dev socat"
|
||||||
|
sh "sudo snap install docker"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Install Rust') {
|
||||||
|
steps {
|
||||||
|
sh "nohup curl https://sh.rustup.rs -sSf | sh -s -- -y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run Cargo tests') {
|
||||||
|
steps {
|
||||||
|
sh "scripts/run_cargo_tests.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run OpenAPI tests') {
|
||||||
|
steps {
|
||||||
|
sh "scripts/run_openapi_tests.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run unit tests') {
|
||||||
|
steps {
|
||||||
|
sh "scripts/run_unit_tests.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage ('Run integration tests') {
|
||||||
|
steps {
|
||||||
|
sh "sudo mount -t tmpfs tmpfs /tmp"
|
||||||
|
sh "scripts/run_integration_tests.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
95
README.md
95
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)
|
1. [What is Cloud Hypervisor?](#1-what-is-cloud-hypervisor)
|
||||||
* [Requirements](#requirements)
|
* [Requirements](#requirements)
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
- [Disk image](#disk-image)
|
- [Disk image](#disk-image)
|
||||||
- [Booting the guest VM](#booting-the-guest-vm)
|
- [Booting the guest VM](#booting-the-guest-vm)
|
||||||
3. [Status](#2-status)
|
3. [Status](#2-status)
|
||||||
|
* [Device Model](#device-model)
|
||||||
* [TODO](#todo)
|
* [TODO](#todo)
|
||||||
4. [rust-vmm dependency](#4-rust-vmm-dependency)
|
4. [rust-vmm dependency](#4-rust-vmm-dependency)
|
||||||
* [Firecracker and crosvm](#firecracker-and-crosvm)
|
* [Firecracker and crosvm](#firecracker-and-crosvm)
|
||||||
@@ -73,7 +74,7 @@ First you need to clone and build the cloud-hypervisor repo:
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ pushd $CLOUDH
|
$ pushd $CLOUDH
|
||||||
$ git clone https://github.com/intel/cloud-hypervisor.git
|
$ git clone https://github.com/cloud-hypervisor/cloud-hypervisor.git
|
||||||
$ cd cloud-hypervisor
|
$ cd cloud-hypervisor
|
||||||
$ cargo build --release
|
$ cargo build --release
|
||||||
|
|
||||||
@@ -85,6 +86,34 @@ $ popd
|
|||||||
|
|
||||||
This will build a `cloud-hypervisor` binary under `$CLOUDH/cloud-hypervisor/target/release/cloud-hypervisor`.
|
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
|
## Run
|
||||||
|
|
||||||
You can run a guest VM by either using an existing cloud image or booting into your own kernel and disk image.
|
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
|
`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
|
components to run cloud workloads, a.k.a. cloud images. To do that we rely on
|
||||||
the [Rust Hypervisor
|
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
|
an ELF
|
||||||
formatted KVM firmware for `cloud-hypervisor` to directly boot into.
|
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
|
```shell
|
||||||
$ pushd $CLOUDH
|
$ pushd $CLOUDH
|
||||||
$ wget https://download.clearlinux.org/releases/29160/clear/clear-29160-kvm.img.xz
|
$ wget https://download.clearlinux.org/releases/31890/clear/clear-31890-kvm.img.xz
|
||||||
$ unxz clear-29160-kvm.img.xz
|
$ unxz clear-31890-kvm.img.xz
|
||||||
$ wget https://github.com/intel/rust-hypervisor-firmware/releases/download/0.1.0/hypervisor-fw
|
$ wget https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.2.6/hypervisor-fw
|
||||||
$ popd
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -113,33 +142,34 @@ $ pushd $CLOUDH
|
|||||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||||
--kernel ./hypervisor-fw \
|
--kernel ./hypervisor-fw \
|
||||||
--disk ./clear-29160-kvm.img \
|
--disk path=clear-31890-kvm.img \
|
||||||
--cpus 4 \
|
--cpus boot=4 \
|
||||||
--memory size=1024M \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
--rng
|
--rng
|
||||||
$ popd
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
Multiple arguments can be given to the `--disk` parameter, currently the firmware requires that the bootable image is on the first disk.
|
Multiple arguments can be given to the `--disk` parameter.
|
||||||
|
|
||||||
### Custom kernel and disk image
|
### Custom kernel and disk image
|
||||||
|
|
||||||
#### Building your kernel
|
#### Building your kernel
|
||||||
|
|
||||||
`cloud-hypervisor` also supports direct kernel boot into a `vmlinux` ELF 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
|
```shell
|
||||||
|
|
||||||
# Clone a 5.0 Linux kernel
|
# Clone the Cloud Hypervisor Linux branch
|
||||||
$ pushd $CLOUDH
|
$ pushd $CLOUDH
|
||||||
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux-cloud-hypervisor
|
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b virtio-fs-virtio-iommu-5.5-rc1 linux-cloud-hypervisor
|
||||||
$ cd linux-cloud-hypervisor
|
$ pushd linux-cloud-hypervisor
|
||||||
$ git reset --hard v5.0
|
|
||||||
|
|
||||||
# Use the cloud-hypervisor kernel config to build your kernel
|
# 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-virtio-fs-virtio-iommu-config .config
|
||||||
$ make bzImage -j `nproc`
|
$ make bzImage -j `nproc`
|
||||||
$ popd
|
$ popd
|
||||||
```
|
```
|
||||||
@@ -152,8 +182,8 @@ For the disk image, we will use a Clear Linux cloud image that contains a root p
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ pushd $CLOUDH
|
$ pushd $CLOUDH
|
||||||
$ wget https://download.clearlinux.org/releases/29160/clear/clear-29160-kvm.img.xz
|
$ wget https://download.clearlinux.org/releases/31890/clear/clear-31890-kvm.img.xz
|
||||||
$ unxz clear-29160-kvm.img.xz
|
$ unxz clear-31890-kvm.img.xz
|
||||||
$ popd
|
$ popd
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -167,9 +197,9 @@ $ pushd $CLOUDH
|
|||||||
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
$ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor
|
||||||
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
$ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--disk ./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" \
|
--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 \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
--rng
|
--rng
|
||||||
@@ -187,27 +217,38 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
|||||||
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
--kernel ./linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \
|
||||||
--console off \
|
--console off \
|
||||||
--serial tty \
|
--serial tty \
|
||||||
--disk ./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" \
|
--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 \
|
--memory size=1024M \
|
||||||
--net "tap=,mac=,ip=,mask=" \
|
--net "tap=,mac=,ip=,mask=" \
|
||||||
--rng
|
--rng
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
# 3. Status
|
# 3. Status
|
||||||
|
|
||||||
`cloud-hypervisor` is in a very early, pre-alpha stage. Use at your own risk!
|
`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/).
|
As of 2019-12-12, the following cloud images are supported:
|
||||||
Direct kernel boot to userspace should work with most rootfs and it's been tested with
|
* [Clear Linux](https://download.clearlinux.org/current/) (cloudguest and kvm)
|
||||||
Clear Linux root partitions, and also basic initrd/initramfs images.
|
* [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
|
## TODO
|
||||||
|
|
||||||
We are not tracking the `cloud-hypervisor` TODO list from a specific git tracked file but through
|
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
|
# 4. `rust-vmm` project dependency
|
||||||
|
|
||||||
@@ -255,7 +296,7 @@ etc, are all equal and welcome means of contribution. See the [CONTRIBUTING](CON
|
|||||||
|
|
||||||
## Join us
|
## Join us
|
||||||
|
|
||||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LTc0YzlmYzQxZDkxNDVhYzZjZjA5MTkxMGY3NTI3YzMzYTFkM2IyY2E0YTIxMzkyYTEwYzdlMzBhMWYxYzVmNDI)
|
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI)
|
||||||
and [join us on Slack](https://cloud-hypervisor.slack.com/).
|
and [join us on Slack](https://cloud-hypervisor.slack.com/).
|
||||||
|
|
||||||
# 6. Security
|
# 6. Security
|
||||||
|
|||||||
9
acpi_tables/Cargo.toml
Normal file
9
acpi_tables/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "acpi_tables"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||||
|
|
||||||
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
12
acpi_tables/src/lib.rs
Normal file
12
acpi_tables/src/lib.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright © 2019 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
pub mod aml;
|
||||||
|
pub mod rsdp;
|
||||||
|
pub mod sdt;
|
||||||
|
|
||||||
|
fn generate_checksum(data: &[u8]) -> u8 {
|
||||||
|
(255 - data.iter().fold(0u8, |acc, x| acc.wrapping_add(*x))).wrapping_add(1)
|
||||||
|
}
|
||||||
67
acpi_tables/src/rsdp.rs
Normal file
67
acpi_tables/src/rsdp.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright © 2019 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use vm_memory::ByteValued;
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct RSDP {
|
||||||
|
pub signature: [u8; 8],
|
||||||
|
pub checksum: u8,
|
||||||
|
pub oem_id: [u8; 6],
|
||||||
|
pub revision: u8,
|
||||||
|
_rsdt_addr: u32,
|
||||||
|
pub length: u32,
|
||||||
|
pub xsdt_addr: u64,
|
||||||
|
pub extended_checksum: u8,
|
||||||
|
_reserved: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl ByteValued for RSDP {}
|
||||||
|
|
||||||
|
impl RSDP {
|
||||||
|
pub fn new(oem_id: [u8; 6], xsdt_addr: u64) -> Self {
|
||||||
|
let mut rsdp = RSDP {
|
||||||
|
signature: *b"RSD PTR ",
|
||||||
|
checksum: 0,
|
||||||
|
oem_id,
|
||||||
|
revision: 2,
|
||||||
|
_rsdt_addr: 0,
|
||||||
|
length: std::mem::size_of::<RSDP>() as u32,
|
||||||
|
xsdt_addr,
|
||||||
|
extended_checksum: 0,
|
||||||
|
_reserved: [0; 3],
|
||||||
|
};
|
||||||
|
|
||||||
|
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
|
||||||
|
rsdp.extended_checksum = super::generate_checksum(&rsdp.as_slice());
|
||||||
|
rsdp
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len() -> usize {
|
||||||
|
std::mem::size_of::<RSDP>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::RSDP;
|
||||||
|
use vm_memory::bytes::ByteValued;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rsdp() {
|
||||||
|
let rsdp = RSDP::new(*b"CHYPER", 0xdead_beef);
|
||||||
|
let sum = rsdp
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
let sum: u8 = rsdp
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
137
acpi_tables/src/sdt.rs
Normal file
137
acpi_tables/src/sdt.rs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// Copyright © 2019 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
pub struct GenericAddress {
|
||||||
|
pub address_space_id: u8,
|
||||||
|
pub register_bit_width: u8,
|
||||||
|
pub register_bit_offset: u8,
|
||||||
|
pub access_size: u8,
|
||||||
|
pub address: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenericAddress {
|
||||||
|
pub fn io_port_address(address: u16) -> Self {
|
||||||
|
GenericAddress {
|
||||||
|
address_space_id: 1,
|
||||||
|
register_bit_width: 8,
|
||||||
|
register_bit_offset: 0,
|
||||||
|
access_size: 1,
|
||||||
|
address: u64::from(address),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SDT {
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::len_without_is_empty)]
|
||||||
|
impl SDT {
|
||||||
|
pub fn new(
|
||||||
|
signature: [u8; 4],
|
||||||
|
length: u32,
|
||||||
|
revision: u8,
|
||||||
|
oem_id: [u8; 6],
|
||||||
|
oem_table: [u8; 8],
|
||||||
|
oem_revision: u32,
|
||||||
|
) -> Self {
|
||||||
|
assert!(length >= 36);
|
||||||
|
|
||||||
|
let mut data = Vec::with_capacity(length as usize);
|
||||||
|
data.extend_from_slice(&signature);
|
||||||
|
data.extend_from_slice(&length.to_le_bytes());
|
||||||
|
data.push(revision);
|
||||||
|
data.push(0); // checksum
|
||||||
|
data.extend_from_slice(&oem_id);
|
||||||
|
data.extend_from_slice(&oem_table);
|
||||||
|
data.extend_from_slice(&oem_revision.to_le_bytes());
|
||||||
|
data.extend_from_slice(b"CLDH");
|
||||||
|
data.extend_from_slice(&0u32.to_le_bytes());
|
||||||
|
assert_eq!(data.len(), 36);
|
||||||
|
|
||||||
|
data.resize(length as usize, 0);
|
||||||
|
let mut sdt = SDT { data };
|
||||||
|
|
||||||
|
sdt.update_checksum();
|
||||||
|
sdt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_checksum(&mut self) {
|
||||||
|
self.data[9] = 0;
|
||||||
|
let checksum = super::generate_checksum(self.data.as_slice());
|
||||||
|
self.data[9] = checksum
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
&self.data.as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append<T>(&mut self, value: T) {
|
||||||
|
let orig_length = self.data.len();
|
||||||
|
let new_length = orig_length + std::mem::size_of::<T>();
|
||||||
|
self.data.resize(new_length, 0);
|
||||||
|
self.write_u32(4, new_length as u32);
|
||||||
|
self.write(orig_length, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_slice(&mut self, data: &[u8]) {
|
||||||
|
let orig_length = self.data.len();
|
||||||
|
let new_length = orig_length + data.len();
|
||||||
|
self.write_u32(4, new_length as u32);
|
||||||
|
self.data.extend_from_slice(data);
|
||||||
|
self.update_checksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a value at the given offset
|
||||||
|
pub fn write<T>(&mut self, offset: usize, value: T) {
|
||||||
|
assert!((offset + (std::mem::size_of::<T>() - 1)) < self.data.len());
|
||||||
|
unsafe {
|
||||||
|
*(((self.data.as_mut_ptr() as usize) + offset) as *mut T) = value;
|
||||||
|
}
|
||||||
|
self.update_checksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u8(&mut self, offset: usize, val: u8) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u16(&mut self, offset: usize, val: u16) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u32(&mut self, offset: usize, val: u32) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_u64(&mut self, offset: usize, val: u64) {
|
||||||
|
self.write(offset, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.data.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::SDT;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sdt() {
|
||||||
|
let mut sdt = SDT::new(*b"TEST", 40, 1, *b"CLOUDH", *b"TESTTEST", 1);
|
||||||
|
let sum: u8 = sdt
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
sdt.write_u32(36, 0x12345678);
|
||||||
|
let sum: u8 = sdt
|
||||||
|
.as_slice()
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |acc, x| acc.wrapping_add(*x));
|
||||||
|
assert_eq!(sum, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,16 @@ name = "arch"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
|
|
||||||
[dependencies]
|
[features]
|
||||||
byteorder = "=1.2.1"
|
default = []
|
||||||
kvm-bindings = "0.1"
|
|
||||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
|
||||||
libc = ">=0.2.39"
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
byteorder = "1.3.4"
|
||||||
|
kvm-bindings = "0.2.0"
|
||||||
|
kvm-ioctls = "0.5.0"
|
||||||
|
libc = "0.2.66"
|
||||||
|
|
||||||
|
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||||
arch_gen = { path = "../arch_gen" }
|
arch_gen = { path = "../arch_gen" }
|
||||||
|
|
||||||
[dependencies.vm-memory]
|
[dependencies.vm-memory]
|
||||||
@@ -20,4 +24,4 @@ git = "https://github.com/rust-vmm/linux-loader"
|
|||||||
features = ["elf", "bzimage"]
|
features = ["elf", "bzimage"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = ">=0.5.5"
|
rand = "0.7.3"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub fn configure_system(
|
|||||||
_cmdline_addr: GuestAddress,
|
_cmdline_addr: GuestAddress,
|
||||||
_cmdline_size: usize,
|
_cmdline_size: usize,
|
||||||
_num_cpus: u8,
|
_num_cpus: u8,
|
||||||
|
_rsdp_addr: Option<GuestAddress>,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#![allow(
|
#![allow(
|
||||||
clippy::unreadable_literal,
|
clippy::unreadable_literal,
|
||||||
clippy::const_static_lifetime,
|
clippy::redundant_static_lifetimes,
|
||||||
clippy::cast_lossless,
|
clippy::cast_lossless,
|
||||||
clippy::transmute_ptr_to_ptr,
|
clippy::transmute_ptr_to_ptr,
|
||||||
clippy::cast_ptr_alignment
|
clippy::cast_ptr_alignment
|
||||||
@@ -13,15 +13,16 @@ extern crate byteorder;
|
|||||||
extern crate kvm_bindings;
|
extern crate kvm_bindings;
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
|
||||||
|
#[cfg(feature = "acpi")]
|
||||||
|
extern crate acpi_tables;
|
||||||
extern crate arch_gen;
|
extern crate arch_gen;
|
||||||
extern crate kvm_ioctls;
|
extern crate kvm_ioctls;
|
||||||
extern crate linux_loader;
|
extern crate linux_loader;
|
||||||
extern crate vm_memory;
|
extern crate vm_memory;
|
||||||
|
|
||||||
use std::result;
|
use std::result;
|
||||||
use vm_memory::GuestAddress;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
/// X86_64 specific error triggered during system configuration.
|
/// X86_64 specific error triggered during system configuration.
|
||||||
@@ -29,7 +30,7 @@ pub enum Error {
|
|||||||
/// The zero page extends past the end of guest_mem.
|
/// The zero page extends past the end of guest_mem.
|
||||||
ZeroPagePastRamEnd,
|
ZeroPagePastRamEnd,
|
||||||
/// Error writing the zero page of guest memory.
|
/// Error writing the zero page of guest memory.
|
||||||
ZeroPageSetup,
|
ZeroPageSetup(vm_memory::GuestMemoryError),
|
||||||
}
|
}
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
@@ -51,9 +52,6 @@ pub enum RegionType {
|
|||||||
Reserved,
|
Reserved,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1MB. We don't put anything above here except the kernel itself.
|
|
||||||
pub const HIMEM_START: GuestAddress = GuestAddress(0x100000);
|
|
||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub mod aarch64;
|
pub mod aarch64;
|
||||||
|
|
||||||
@@ -68,6 +66,5 @@ pub mod x86_64;
|
|||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub use x86_64::{
|
pub use x86_64::{
|
||||||
arch_memory_regions, configure_system, get_32bit_gap_start as get_reserved_mem_addr,
|
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
||||||
layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use std::io::{self, Cursor};
|
use std::io::Cursor;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::result;
|
use std::result;
|
||||||
|
|
||||||
@@ -16,8 +16,8 @@ use kvm_ioctls;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
GetLapic(io::Error),
|
GetLapic(kvm_ioctls::Error),
|
||||||
SetLapic(io::Error),
|
SetLapic(kvm_ioctls::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|||||||
@@ -5,21 +5,90 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::{GuestAddress, GuestUsize};
|
||||||
|
|
||||||
/// Magic addresses externally used to lay out x86_64 VMs.
|
/*
|
||||||
|
|
||||||
|
Memory layout documentation and constants
|
||||||
|
~~~~~~ ~~~~~~ ~~~~~~~~~~~~~ ~~~ ~~~~~~~~~
|
||||||
|
|
||||||
|
Constants are in order and grouped by range. Take care to update all references
|
||||||
|
when making changes and keep them in order.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ** Low RAM (start: 0, length: 640KiB) **
|
||||||
|
pub const LOW_RAM_START: GuestAddress = GuestAddress(0x0);
|
||||||
|
|
||||||
|
// == Fixed addresses within the "Low RAM" range: ==
|
||||||
|
|
||||||
|
// Initial GDT/IDT needed to boot kernel
|
||||||
|
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
|
||||||
|
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
||||||
|
|
||||||
|
/// The 'zero page', a.k.a linux kernel bootparams.
|
||||||
|
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
|
||||||
|
|
||||||
/// Initial stack for the boot CPU.
|
/// Initial stack for the boot CPU.
|
||||||
pub const BOOT_STACK_START: GuestAddress = GuestAddress(0x8000);
|
pub const BOOT_STACK_START: GuestAddress = GuestAddress(0x8000);
|
||||||
pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0);
|
pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0);
|
||||||
|
|
||||||
|
// Initial pagetables.
|
||||||
|
pub const PML4_START: GuestAddress = GuestAddress(0x9000);
|
||||||
|
pub const PDPTE_START: GuestAddress = GuestAddress(0xa000);
|
||||||
|
pub const PDE_START: GuestAddress = GuestAddress(0xb000);
|
||||||
|
|
||||||
/// Kernel command line start address.
|
/// Kernel command line start address.
|
||||||
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
||||||
/// Kernel command line start address maximum size.
|
/// Kernel command line start address maximum size.
|
||||||
pub const CMDLINE_MAX_SIZE: usize = 0x10000;
|
pub const CMDLINE_MAX_SIZE: usize = 0x10000;
|
||||||
|
|
||||||
/// Address for the TSS setup.
|
// MPTABLE, describing VCPUS.
|
||||||
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffbd000);
|
pub const MPTABLE_START: GuestAddress = GuestAddress(0x9fc00);
|
||||||
|
|
||||||
/// The 'zero page', a.k.a linux kernel bootparams.
|
// == End of "Low RAM" range. ==
|
||||||
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
|
|
||||||
|
// ** EBDA reserved area (start: 640KiB, length: 384KiB) **
|
||||||
|
pub const EBDA_START: GuestAddress = GuestAddress(0xa0000);
|
||||||
|
|
||||||
|
// == Fixed constants within the "EBDA" range ==
|
||||||
|
|
||||||
|
// ACPI RSDP table
|
||||||
|
pub const RSDP_POINTER: GuestAddress = EBDA_START;
|
||||||
|
|
||||||
|
// == End of "EBDA" range ==
|
||||||
|
|
||||||
|
// ** High RAM (start: 1MiB, length: 3071MiB) **
|
||||||
|
pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
|
||||||
|
|
||||||
|
// == No fixed addresses in the "High RAM" range ==
|
||||||
|
|
||||||
|
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
|
||||||
|
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
|
||||||
|
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = (1024 << 20);
|
||||||
|
|
||||||
|
// == Fixed constants within the "32-bit reserved" range ==
|
||||||
|
|
||||||
|
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
|
||||||
|
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
|
||||||
|
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = (640 << 20);
|
||||||
|
|
||||||
|
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
|
||||||
|
pub const PCI_MMCONFIG_START: GuestAddress =
|
||||||
|
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
|
||||||
|
pub const PCI_MMCONFIG_SIZE: GuestUsize = (256 << 20);
|
||||||
|
|
||||||
|
// IOAPIC
|
||||||
|
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
||||||
|
pub const IOAPIC_SIZE: GuestUsize = 0x20;
|
||||||
|
|
||||||
|
// APIC
|
||||||
|
pub const APIC_START: GuestAddress = GuestAddress(0xfee0_0000);
|
||||||
|
|
||||||
|
/// Address for the TSS setup.
|
||||||
|
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
|
||||||
|
|
||||||
|
// == End of "32-bit reserved" range. ==
|
||||||
|
|
||||||
|
// ** 64-bit RAM start (start: 4GiB, length: varies) **
|
||||||
|
pub const RAM_64BIT_START: GuestAddress = GuestAddress(0x1_0000_0000);
|
||||||
|
|||||||
@@ -12,12 +12,15 @@ mod mptable;
|
|||||||
pub mod regs;
|
pub mod regs;
|
||||||
|
|
||||||
use crate::RegionType;
|
use crate::RegionType;
|
||||||
use linux_loader::loader::bootparam::{boot_params, setup_header, E820_RAM};
|
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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
|
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||||
// trait (in this case `DataInit`) where:
|
// trait (in this case `DataInit`) where:
|
||||||
// * the type that is implementing the trait is foreign or
|
// * the type that is implementing the trait is foreign or
|
||||||
@@ -29,7 +32,7 @@ struct BootParamsWrapper(boot_params);
|
|||||||
// It is safe to initialize BootParamsWrap which is a wrapper over `boot_params` (a series of ints).
|
// It is safe to initialize BootParamsWrap which is a wrapper over `boot_params` (a series of ints).
|
||||||
unsafe impl ByteValued for BootParamsWrapper {}
|
unsafe impl ByteValued for BootParamsWrapper {}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Invalid e820 setup params.
|
/// Invalid e820 setup params.
|
||||||
E820Configuration,
|
E820Configuration,
|
||||||
@@ -43,73 +46,53 @@ impl From<Error> for super::Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Where BIOS/VGA magic would live on a real PC.
|
|
||||||
const EBDA_START: GuestAddress = GuestAddress(0x9fc00);
|
|
||||||
const FIRST_ADDR_PAST_32BITS: GuestAddress = GuestAddress(1 << 32);
|
|
||||||
|
|
||||||
// Our 32-bit memory gap starts at 3G.
|
|
||||||
const MEM_32BIT_GAP_START: GuestAddress = GuestAddress(0xc000_0000);
|
|
||||||
|
|
||||||
// Our 32-bit memory gap size is 1GB.
|
|
||||||
const MEM_32BIT_GAP_SIZE: GuestUsize = (1024 << 20);
|
|
||||||
|
|
||||||
// We reserve 768MB in our memory gap for 32-bit devices (e.g. 32-bit PCI BARs).
|
|
||||||
const MEM_32BIT_DEVICES_GAP_SIZE: GuestUsize = (768 << 20);
|
|
||||||
|
|
||||||
/// Returns a Vec of the valid memory addresses.
|
/// Returns a Vec of the valid memory addresses.
|
||||||
/// These should be used to configure the GuestMemory structure for the platform.
|
/// These should be used to configure the GuestMemory structure for the platform.
|
||||||
/// For x86_64 all addresses are valid from the start of the kernel except a
|
/// For x86_64 all addresses are valid from the start of the kernel except a
|
||||||
/// carve out at the end of 32bit address space.
|
/// carve out at the end of 32bit address space.
|
||||||
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||||
let reserved_memory_gap_start = MEM_32BIT_GAP_START
|
let reserved_memory_gap_start = layout::MEM_32BIT_RESERVED_START
|
||||||
.checked_add(MEM_32BIT_DEVICES_GAP_SIZE)
|
.checked_add(layout::MEM_32BIT_DEVICES_SIZE)
|
||||||
.expect("32-bit reserved region is too large");
|
.expect("32-bit reserved region is too large");
|
||||||
|
|
||||||
let requested_memory_size = GuestAddress(size as u64);
|
let requested_memory_size = GuestAddress(size as u64);
|
||||||
let mut regions = Vec::new();
|
let mut regions = Vec::new();
|
||||||
|
|
||||||
// case1: guest memory fits before the gap
|
// case1: guest memory fits before the gap
|
||||||
if size as u64 <= MEM_32BIT_GAP_START.raw_value() {
|
if size as u64 <= layout::MEM_32BIT_RESERVED_START.raw_value() {
|
||||||
regions.push((GuestAddress(0), size as usize, RegionType::Ram));
|
regions.push((GuestAddress(0), size as usize, RegionType::Ram));
|
||||||
// case2: guest memory extends beyond the gap
|
// case2: guest memory extends beyond the gap
|
||||||
} else {
|
} else {
|
||||||
// push memory before the gap
|
// push memory before the gap
|
||||||
regions.push((
|
regions.push((
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
MEM_32BIT_GAP_START.raw_value() as usize,
|
layout::MEM_32BIT_RESERVED_START.raw_value() as usize,
|
||||||
RegionType::Ram,
|
RegionType::Ram,
|
||||||
));
|
));
|
||||||
regions.push((
|
regions.push((
|
||||||
FIRST_ADDR_PAST_32BITS,
|
layout::RAM_64BIT_START,
|
||||||
requested_memory_size.unchecked_offset_from(MEM_32BIT_GAP_START) as usize,
|
requested_memory_size.unchecked_offset_from(layout::MEM_32BIT_RESERVED_START) as usize,
|
||||||
RegionType::Ram,
|
RegionType::Ram,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the 32-bit device memory hole as a sub region.
|
// Add the 32-bit device memory hole as a sub region.
|
||||||
regions.push((
|
regions.push((
|
||||||
MEM_32BIT_GAP_START,
|
layout::MEM_32BIT_RESERVED_START,
|
||||||
MEM_32BIT_DEVICES_GAP_SIZE as usize,
|
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
||||||
RegionType::SubRegion,
|
RegionType::SubRegion,
|
||||||
));
|
));
|
||||||
|
|
||||||
// Add the 32-bit reserved memory hole as a sub region.
|
// Add the 32-bit reserved memory hole as a sub region.
|
||||||
regions.push((
|
regions.push((
|
||||||
reserved_memory_gap_start,
|
reserved_memory_gap_start,
|
||||||
(MEM_32BIT_GAP_SIZE - MEM_32BIT_DEVICES_GAP_SIZE) as usize,
|
(layout::MEM_32BIT_RESERVED_SIZE - layout::MEM_32BIT_DEVICES_SIZE) as usize,
|
||||||
RegionType::Reserved,
|
RegionType::Reserved,
|
||||||
));
|
));
|
||||||
|
|
||||||
regions
|
regions
|
||||||
}
|
}
|
||||||
|
|
||||||
/// X86 specific memory hole/memory mapped devices/reserved area.
|
|
||||||
pub fn get_32bit_gap_start() -> GuestAddress {
|
|
||||||
FIRST_ADDR_PAST_32BITS
|
|
||||||
.checked_sub(MEM_32BIT_GAP_SIZE)
|
|
||||||
.expect("32-bit hole is too large")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -118,29 +101,27 @@ pub fn get_32bit_gap_start() -> GuestAddress {
|
|||||||
/// * `cmdline_addr` - Address in `guest_mem` where the kernel command line was loaded.
|
/// * `cmdline_addr` - Address in `guest_mem` where the kernel command line was loaded.
|
||||||
/// * `cmdline_size` - Size of the kernel command line in bytes including the null terminator.
|
/// * `cmdline_size` - Size of the kernel command line in bytes including the null terminator.
|
||||||
/// * `num_cpus` - Number of virtual CPUs the guest will have.
|
/// * `num_cpus` - Number of virtual CPUs the guest will have.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn configure_system(
|
pub fn configure_system(
|
||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline_addr: GuestAddress,
|
cmdline_addr: GuestAddress,
|
||||||
cmdline_size: usize,
|
cmdline_size: usize,
|
||||||
num_cpus: u8,
|
num_cpus: u8,
|
||||||
setup_hdr: Option<setup_header>,
|
setup_hdr: Option<setup_header>,
|
||||||
|
rsdp_addr: Option<GuestAddress>,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
|
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
|
||||||
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
|
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
|
||||||
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||||
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
|
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
|
||||||
let first_addr_past_32bits = FIRST_ADDR_PAST_32BITS;
|
|
||||||
let end_32bit_gap_start = get_32bit_gap_start();
|
|
||||||
|
|
||||||
let himem_start = super::HIMEM_START;
|
|
||||||
|
|
||||||
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
// 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)?;
|
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?;
|
||||||
|
|
||||||
let mut params: BootParamsWrapper = BootParamsWrapper(boot_params::default());
|
let mut params: BootParamsWrapper = BootParamsWrapper(boot_params::default());
|
||||||
|
|
||||||
if setup_hdr.is_some() {
|
if let Some(hdr) = setup_hdr {
|
||||||
params.0.hdr = setup_hdr.unwrap();
|
params.0.hdr = hdr;
|
||||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||||
} else {
|
} else {
|
||||||
@@ -152,40 +133,51 @@ pub fn configure_system(
|
|||||||
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
|
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
|
||||||
};
|
};
|
||||||
|
|
||||||
add_e820_entry(&mut params.0, 0, EBDA_START.raw_value(), E820_RAM)?;
|
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 < end_32bit_gap_start {
|
if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||||
add_e820_entry(
|
add_e820_entry(
|
||||||
&mut params.0,
|
&mut params.0,
|
||||||
himem_start.raw_value(),
|
layout::HIGH_RAM_START.raw_value(),
|
||||||
mem_end.unchecked_offset_from(himem_start),
|
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
||||||
E820_RAM,
|
E820_RAM,
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
add_e820_entry(
|
add_e820_entry(
|
||||||
&mut params.0,
|
&mut params.0,
|
||||||
himem_start.raw_value(),
|
layout::HIGH_RAM_START.raw_value(),
|
||||||
end_32bit_gap_start.unchecked_offset_from(himem_start),
|
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
|
||||||
E820_RAM,
|
E820_RAM,
|
||||||
)?;
|
)?;
|
||||||
if mem_end > first_addr_past_32bits {
|
if mem_end > layout::RAM_64BIT_START {
|
||||||
add_e820_entry(
|
add_e820_entry(
|
||||||
&mut params.0,
|
&mut params.0,
|
||||||
first_addr_past_32bits.raw_value(),
|
layout::RAM_64BIT_START.raw_value(),
|
||||||
mem_end.unchecked_offset_from(first_addr_past_32bits),
|
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
|
||||||
E820_RAM,
|
E820_RAM,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_e820_entry(
|
||||||
|
&mut params.0,
|
||||||
|
layout::PCI_MMCONFIG_START.0,
|
||||||
|
layout::PCI_MMCONFIG_SIZE,
|
||||||
|
E820_RESERVED,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(rsdp_addr) = rsdp_addr {
|
||||||
|
params.0.acpi_rsdp_addr = rsdp_addr.0;
|
||||||
|
}
|
||||||
|
|
||||||
let zero_page_addr = layout::ZERO_PAGE_START;
|
let zero_page_addr = layout::ZERO_PAGE_START;
|
||||||
guest_mem
|
guest_mem
|
||||||
.checked_offset(zero_page_addr, mem::size_of::<boot_params>())
|
.checked_offset(zero_page_addr, mem::size_of::<boot_params>())
|
||||||
.ok_or(super::Error::ZeroPagePastRamEnd)?;
|
.ok_or(super::Error::ZeroPagePastRamEnd)?;
|
||||||
guest_mem
|
guest_mem
|
||||||
.write_obj(params, zero_page_addr)
|
.write_obj(params, zero_page_addr)
|
||||||
.map_err(|_| super::Error::ZeroPageSetup)?;
|
.map_err(super::Error::ZeroPageSetup)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -198,13 +190,13 @@ fn add_e820_entry(
|
|||||||
size: u64,
|
size: u64,
|
||||||
mem_type: u32,
|
mem_type: u32,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if params.e820_entries >= params.e820_map.len() as u8 {
|
if params.e820_entries >= params.e820_table.len() as u8 {
|
||||||
return Err(Error::E820Configuration);
|
return Err(Error::E820Configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
params.e820_map[params.e820_entries as usize].addr = addr;
|
params.e820_table[params.e820_entries as usize].addr = addr;
|
||||||
params.e820_map[params.e820_entries as usize].size = size;
|
params.e820_table[params.e820_entries as usize].size = size;
|
||||||
params.e820_map[params.e820_entries as usize].type_ = mem_type;
|
params.e820_table[params.e820_entries as usize].type_ = mem_type;
|
||||||
params.e820_entries += 1;
|
params.e820_entries += 1;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -213,7 +205,7 @@ fn add_e820_entry(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use linux_loader::loader::bootparam::e820entry;
|
use linux_loader::loader::bootparam::boot_e820_entry;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn regions_lt_4gb() {
|
fn regions_lt_4gb() {
|
||||||
@@ -231,28 +223,12 @@ mod tests {
|
|||||||
assert_eq!(GuestAddress(1 << 32), regions[1].0);
|
assert_eq!(GuestAddress(1 << 32), regions[1].0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_32bit_gap() {
|
|
||||||
assert_eq!(
|
|
||||||
get_32bit_gap_start(),
|
|
||||||
FIRST_ADDR_PAST_32BITS
|
|
||||||
.checked_sub(MEM_32BIT_GAP_SIZE as u64)
|
|
||||||
.expect("32-bit hole is too large")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_system_configuration() {
|
fn test_system_configuration() {
|
||||||
let no_vcpus = 4;
|
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);
|
let config_err = configure_system(&gm, GuestAddress(0), 0, 1, None, None);
|
||||||
assert!(config_err.is_err());
|
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.
|
// Now assigning some memory that falls before the 32bit memory hole.
|
||||||
let mem_size = 128 << 20;
|
let mem_size = 128 << 20;
|
||||||
@@ -262,8 +238,8 @@ mod tests {
|
|||||||
.filter(|r| r.2 == RegionType::Ram)
|
.filter(|r| r.2 == RegionType::Ram)
|
||||||
.map(|r| (r.0, r.1))
|
.map(|r| (r.0, r.1))
|
||||||
.collect();
|
.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).unwrap();
|
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
|
||||||
|
|
||||||
// Now assigning some memory that is equal to the start of the 32bit memory hole.
|
// Now assigning some memory that is equal to the start of the 32bit memory hole.
|
||||||
let mem_size = 3328 << 20;
|
let mem_size = 3328 << 20;
|
||||||
@@ -273,8 +249,8 @@ mod tests {
|
|||||||
.filter(|r| r.2 == RegionType::Ram)
|
.filter(|r| r.2 == RegionType::Ram)
|
||||||
.map(|r| (r.0, r.1))
|
.map(|r| (r.0, r.1))
|
||||||
.collect();
|
.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).unwrap();
|
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
|
||||||
|
|
||||||
// Now assigning some memory that falls after the 32bit memory hole.
|
// Now assigning some memory that falls after the 32bit memory hole.
|
||||||
let mem_size = 3330 << 20;
|
let mem_size = 3330 << 20;
|
||||||
@@ -284,20 +260,20 @@ mod tests {
|
|||||||
.filter(|r| r.2 == RegionType::Ram)
|
.filter(|r| r.2 == RegionType::Ram)
|
||||||
.map(|r| (r.0, r.1))
|
.map(|r| (r.0, r.1))
|
||||||
.collect();
|
.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).unwrap();
|
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_e820_entry() {
|
fn test_add_e820_entry() {
|
||||||
let e820_map = [(e820entry {
|
let e820_table = [(boot_e820_entry {
|
||||||
addr: 0x1,
|
addr: 0x1,
|
||||||
size: 4,
|
size: 4,
|
||||||
type_: 1,
|
type_: 1,
|
||||||
}); 128];
|
}); 128];
|
||||||
|
|
||||||
let expected_params = boot_params {
|
let expected_params = boot_params {
|
||||||
e820_map,
|
e820_table,
|
||||||
e820_entries: 1,
|
e820_entries: 1,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
@@ -305,25 +281,25 @@ mod tests {
|
|||||||
let mut params: boot_params = Default::default();
|
let mut params: boot_params = Default::default();
|
||||||
add_e820_entry(
|
add_e820_entry(
|
||||||
&mut params,
|
&mut params,
|
||||||
e820_map[0].addr,
|
e820_table[0].addr,
|
||||||
e820_map[0].size,
|
e820_table[0].size,
|
||||||
e820_map[0].type_,
|
e820_table[0].type_,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{:?}", params.e820_map[0]),
|
format!("{:?}", params.e820_table[0]),
|
||||||
format!("{:?}", expected_params.e820_map[0])
|
format!("{:?}", expected_params.e820_table[0])
|
||||||
);
|
);
|
||||||
assert_eq!(params.e820_entries, expected_params.e820_entries);
|
assert_eq!(params.e820_entries, expected_params.e820_entries);
|
||||||
|
|
||||||
// Exercise the scenario where the field storing the length of the e820 entry table is
|
// Exercise the scenario where the field storing the length of the e820 entry table is
|
||||||
// is bigger than the allocated memory.
|
// is bigger than the allocated memory.
|
||||||
params.e820_entries = params.e820_map.len() as u8 + 1;
|
params.e820_entries = params.e820_table.len() as u8 + 1;
|
||||||
assert!(add_e820_entry(
|
assert!(add_e820_entry(
|
||||||
&mut params,
|
&mut params,
|
||||||
e820_map[0].addr,
|
e820_table[0].addr,
|
||||||
e820_map[0].size,
|
e820_table[0].size,
|
||||||
e820_map[0].type_
|
e820_table[0].type_
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ use std::slice;
|
|||||||
use libc::c_char;
|
use libc::c_char;
|
||||||
|
|
||||||
use arch_gen::x86::mpspec;
|
use arch_gen::x86::mpspec;
|
||||||
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap};
|
use layout::{APIC_START, IOAPIC_START, MPTABLE_START};
|
||||||
|
use vm_memory::{Address, ByteValued, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||||
|
|
||||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||||
// trait (in this case `ByteValued`) where:
|
// trait (in this case `ByteValued`) where:
|
||||||
@@ -44,33 +45,30 @@ unsafe impl ByteValued for MpcTableWrapper {}
|
|||||||
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
unsafe impl ByteValued for MpcLintsrcWrapper {}
|
||||||
unsafe impl ByteValued for MpfIntelWrapper {}
|
unsafe impl ByteValued for MpfIntelWrapper {}
|
||||||
|
|
||||||
// MPTABLE, describing VCPUS.
|
#[derive(Debug)]
|
||||||
const MPTABLE_START: GuestAddress = GuestAddress(0x9fc00);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// There was too little guest memory to store the entire MP table.
|
/// There was too little guest memory to store the entire MP table.
|
||||||
NotEnoughMemory,
|
NotEnoughMemory,
|
||||||
/// The MP table has too little address space to be stored.
|
/// The MP table has too little address space to be stored.
|
||||||
AddressOverflow,
|
AddressOverflow,
|
||||||
/// Failure while zeroing out the memory for the MP table.
|
/// Failure while zeroing out the memory for the MP table.
|
||||||
Clear,
|
Clear(GuestMemoryError),
|
||||||
/// Number of CPUs exceeds the maximum supported CPUs
|
/// Number of CPUs exceeds the maximum supported CPUs
|
||||||
TooManyCpus,
|
TooManyCpus,
|
||||||
/// Failure to write the MP floating pointer.
|
/// Failure to write the MP floating pointer.
|
||||||
WriteMpfIntel,
|
WriteMpfIntel(GuestMemoryError),
|
||||||
/// Failure to write MP CPU entry.
|
/// Failure to write MP CPU entry.
|
||||||
WriteMpcCpu,
|
WriteMpcCpu(GuestMemoryError),
|
||||||
/// Failure to write MP ioapic entry.
|
/// Failure to write MP ioapic entry.
|
||||||
WriteMpcIoapic,
|
WriteMpcIoapic(GuestMemoryError),
|
||||||
/// Failure to write MP bus entry.
|
/// Failure to write MP bus entry.
|
||||||
WriteMpcBus,
|
WriteMpcBus(GuestMemoryError),
|
||||||
/// Failure to write MP interrupt source entry.
|
/// Failure to write MP interrupt source entry.
|
||||||
WriteMpcIntsrc,
|
WriteMpcIntsrc(GuestMemoryError),
|
||||||
/// Failure to write MP local interrupt source entry.
|
/// Failure to write MP local interrupt source entry.
|
||||||
WriteMpcLintsrc,
|
WriteMpcLintsrc(GuestMemoryError),
|
||||||
/// Failure to write MP table header.
|
/// Failure to write MP table header.
|
||||||
WriteMpcTable,
|
WriteMpcTable(GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
@@ -92,8 +90,6 @@ const MPC_SPEC: i8 = 4;
|
|||||||
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
|
const MPC_OEM: [c_char; 8] = char_array!(c_char; 'F', 'C', ' ', ' ', ' ', ' ', ' ', ' ');
|
||||||
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
|
const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12];
|
||||||
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' ');
|
||||||
const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec00000; // source: linux/arch/x86/include/asm/apicdef.h
|
|
||||||
const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee00000; // source: linux/arch/x86/include/asm/apicdef.h
|
|
||||||
const APIC_VERSION: u8 = 0x14;
|
const APIC_VERSION: u8 = 0x14;
|
||||||
const CPU_STEPPING: u32 = 0x600;
|
const CPU_STEPPING: u32 = 0x600;
|
||||||
const CPU_FEATURE_APIC: u32 = 0x200;
|
const CPU_FEATURE_APIC: u32 = 0x200;
|
||||||
@@ -149,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)
|
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());
|
let mut mpf_intel = MpfIntelWrapper(mpspec::mpf_intel::default());
|
||||||
@@ -160,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.physptr = (base_mp.raw_value() + size) as u32;
|
||||||
mpf_intel.0.checksum = mpf_intel_compute_checksum(&mpf_intel.0);
|
mpf_intel.0.checksum = mpf_intel_compute_checksum(&mpf_intel.0);
|
||||||
mem.write_obj(mpf_intel, base_mp)
|
mem.write_obj(mpf_intel, base_mp)
|
||||||
.map_err(|_| Error::WriteMpfIntel)?;
|
.map_err(Error::WriteMpfIntel)?;
|
||||||
base_mp = base_mp.unchecked_add(size);
|
base_mp = base_mp.unchecked_add(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +181,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
|||||||
mpc_cpu.0.cpufeature = CPU_STEPPING;
|
mpc_cpu.0.cpufeature = CPU_STEPPING;
|
||||||
mpc_cpu.0.featureflag = CPU_FEATURE_APIC | CPU_FEATURE_FPU;
|
mpc_cpu.0.featureflag = CPU_FEATURE_APIC | CPU_FEATURE_FPU;
|
||||||
mem.write_obj(mpc_cpu, base_mp)
|
mem.write_obj(mpc_cpu, base_mp)
|
||||||
.map_err(|_| Error::WriteMpcCpu)?;
|
.map_err(Error::WriteMpcCpu)?;
|
||||||
base_mp = base_mp.unchecked_add(size as u64);
|
base_mp = base_mp.unchecked_add(size as u64);
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_cpu.0));
|
||||||
}
|
}
|
||||||
@@ -197,7 +193,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
|||||||
mpc_bus.0.busid = 0;
|
mpc_bus.0.busid = 0;
|
||||||
mpc_bus.0.bustype = BUS_TYPE_ISA;
|
mpc_bus.0.bustype = BUS_TYPE_ISA;
|
||||||
mem.write_obj(mpc_bus, base_mp)
|
mem.write_obj(mpc_bus, base_mp)
|
||||||
.map_err(|_| Error::WriteMpcBus)?;
|
.map_err(Error::WriteMpcBus)?;
|
||||||
base_mp = base_mp.unchecked_add(size as u64);
|
base_mp = base_mp.unchecked_add(size as u64);
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_bus.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_bus.0));
|
||||||
}
|
}
|
||||||
@@ -208,9 +204,9 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
|||||||
mpc_ioapic.0.apicid = ioapicid;
|
mpc_ioapic.0.apicid = ioapicid;
|
||||||
mpc_ioapic.0.apicver = APIC_VERSION;
|
mpc_ioapic.0.apicver = APIC_VERSION;
|
||||||
mpc_ioapic.0.flags = mpspec::MPC_APIC_USABLE as u8;
|
mpc_ioapic.0.flags = mpspec::MPC_APIC_USABLE as u8;
|
||||||
mpc_ioapic.0.apicaddr = IO_APIC_DEFAULT_PHYS_BASE;
|
mpc_ioapic.0.apicaddr = IOAPIC_START.0 as u32;
|
||||||
mem.write_obj(mpc_ioapic, base_mp)
|
mem.write_obj(mpc_ioapic, base_mp)
|
||||||
.map_err(|_| Error::WriteMpcIoapic)?;
|
.map_err(Error::WriteMpcIoapic)?;
|
||||||
base_mp = base_mp.unchecked_add(size as u64);
|
base_mp = base_mp.unchecked_add(size as u64);
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_ioapic.0));
|
||||||
}
|
}
|
||||||
@@ -226,7 +222,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
|||||||
mpc_intsrc.0.dstapic = ioapicid;
|
mpc_intsrc.0.dstapic = ioapicid;
|
||||||
mpc_intsrc.0.dstirq = i;
|
mpc_intsrc.0.dstirq = i;
|
||||||
mem.write_obj(mpc_intsrc, base_mp)
|
mem.write_obj(mpc_intsrc, base_mp)
|
||||||
.map_err(|_| Error::WriteMpcIntsrc)?;
|
.map_err(Error::WriteMpcIntsrc)?;
|
||||||
base_mp = base_mp.unchecked_add(size as u64);
|
base_mp = base_mp.unchecked_add(size as u64);
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_intsrc.0));
|
||||||
}
|
}
|
||||||
@@ -241,7 +237,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
|||||||
mpc_lintsrc.0.destapic = 0;
|
mpc_lintsrc.0.destapic = 0;
|
||||||
mpc_lintsrc.0.destapiclint = 0;
|
mpc_lintsrc.0.destapiclint = 0;
|
||||||
mem.write_obj(mpc_lintsrc, base_mp)
|
mem.write_obj(mpc_lintsrc, base_mp)
|
||||||
.map_err(|_| Error::WriteMpcLintsrc)?;
|
.map_err(Error::WriteMpcLintsrc)?;
|
||||||
base_mp = base_mp.unchecked_add(size as u64);
|
base_mp = base_mp.unchecked_add(size as u64);
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
|
||||||
}
|
}
|
||||||
@@ -256,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.destapic = 0xFF; /* to all local APICs */
|
||||||
mpc_lintsrc.0.destapiclint = 1;
|
mpc_lintsrc.0.destapiclint = 1;
|
||||||
mem.write_obj(mpc_lintsrc, base_mp)
|
mem.write_obj(mpc_lintsrc, base_mp)
|
||||||
.map_err(|_| Error::WriteMpcLintsrc)?;
|
.map_err(Error::WriteMpcLintsrc)?;
|
||||||
base_mp = base_mp.unchecked_add(size as u64);
|
base_mp = base_mp.unchecked_add(size as u64);
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_lintsrc.0));
|
||||||
}
|
}
|
||||||
@@ -271,11 +267,11 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
|||||||
mpc_table.0.spec = MPC_SPEC;
|
mpc_table.0.spec = MPC_SPEC;
|
||||||
mpc_table.0.oem = MPC_OEM;
|
mpc_table.0.oem = MPC_OEM;
|
||||||
mpc_table.0.productid = MPC_PRODUCT_ID;
|
mpc_table.0.productid = MPC_PRODUCT_ID;
|
||||||
mpc_table.0.lapic = APIC_DEFAULT_PHYS_BASE;
|
mpc_table.0.lapic = APIC_START.0 as u32;
|
||||||
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
checksum = checksum.wrapping_add(compute_checksum(&mpc_table.0));
|
||||||
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
mpc_table.0.checksum = (!checksum).wrapping_add(1) as i8;
|
||||||
mem.write_obj(mpc_table, table_base)
|
mem.write_obj(mpc_table, table_base)
|
||||||
.map_err(|_| Error::WriteMpcTable)?;
|
.map_err(Error::WriteMpcTable)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -284,7 +280,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use vm_memory::GuestUsize;
|
use vm_memory::{GuestAddress, GuestUsize};
|
||||||
|
|
||||||
fn table_entry_size(type_: u8) -> usize {
|
fn table_entry_size(type_: u8) -> usize {
|
||||||
match type_ as u32 {
|
match type_ as u32 {
|
||||||
@@ -300,7 +296,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn bounds_check() {
|
fn bounds_check() {
|
||||||
let num_cpus = 4;
|
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();
|
setup_mptable(&mem, num_cpus).unwrap();
|
||||||
}
|
}
|
||||||
@@ -308,7 +305,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn bounds_check_fails() {
|
fn bounds_check_fails() {
|
||||||
let num_cpus = 4;
|
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());
|
assert!(setup_mptable(&mem, num_cpus).is_err());
|
||||||
}
|
}
|
||||||
@@ -316,7 +314,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn mpf_intel_checksum() {
|
fn mpf_intel_checksum() {
|
||||||
let num_cpus = 1;
|
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();
|
setup_mptable(&mem, num_cpus).unwrap();
|
||||||
|
|
||||||
@@ -331,7 +330,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn mpc_table_checksum() {
|
fn mpc_table_checksum() {
|
||||||
let num_cpus = 4;
|
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();
|
setup_mptable(&mem, num_cpus).unwrap();
|
||||||
|
|
||||||
@@ -360,9 +360,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cpu_entry_count() {
|
fn cpu_entry_count() {
|
||||||
let mem =
|
let mem = GuestMemoryMmap::from_ranges(&[(
|
||||||
GuestMemoryMmap::new(&[(MPTABLE_START, compute_mp_size(MAX_SUPPORTED_CPUS as u8))])
|
MPTABLE_START,
|
||||||
.unwrap();
|
compute_mp_size(MAX_SUPPORTED_CPUS as u8),
|
||||||
|
)])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
||||||
setup_mptable(&mem, i).unwrap();
|
setup_mptable(&mem, i).unwrap();
|
||||||
@@ -395,9 +397,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn cpu_entry_count_max() {
|
fn cpu_entry_count_max() {
|
||||||
let cpus = MAX_SUPPORTED_CPUS + 1;
|
let cpus = MAX_SUPPORTED_CPUS + 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();
|
let result = setup_mptable(&mem, cpus as u8);
|
||||||
assert_eq!(result, Error::TooManyCpus);
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,41 +5,43 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use std::{io, mem, result};
|
use std::{mem, result};
|
||||||
|
|
||||||
use super::gdt::{gdt_entry, kvm_segment_from_gdt};
|
use super::gdt::{gdt_entry, kvm_segment_from_gdt};
|
||||||
use arch_gen::x86::msr_index;
|
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 kvm_ioctls::VcpuFd;
|
||||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap};
|
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START};
|
||||||
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||||
|
|
||||||
// Initial pagetables.
|
// MTRR constants
|
||||||
const PML4_START: GuestAddress = GuestAddress(0x9000);
|
const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
|
||||||
const PDPTE_START: GuestAddress = GuestAddress(0xa000);
|
const MTRR_MEM_TYPE_WB: u64 = 0x6;
|
||||||
const PDE_START: GuestAddress = GuestAddress(0xb000);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to get SREGs for this CPU.
|
/// Failed to get SREGs for this CPU.
|
||||||
GetStatusRegisters(io::Error),
|
GetStatusRegisters(kvm_ioctls::Error),
|
||||||
/// Failed to set base registers for this CPU.
|
/// Failed to set base registers for this CPU.
|
||||||
SetBaseRegisters(io::Error),
|
SetBaseRegisters(kvm_ioctls::Error),
|
||||||
/// Failed to configure the FPU.
|
/// Failed to configure the FPU.
|
||||||
SetFPURegisters(io::Error),
|
SetFPURegisters(kvm_ioctls::Error),
|
||||||
/// Setting up MSRs failed.
|
/// Setting up MSRs failed.
|
||||||
SetModelSpecificRegisters(io::Error),
|
SetModelSpecificRegisters(kvm_ioctls::Error),
|
||||||
/// Failed to set SREGs for this CPU.
|
/// 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.
|
/// Writing the GDT to RAM failed.
|
||||||
WriteGDT,
|
WriteGDT(GuestMemoryError),
|
||||||
/// Writing the IDT to RAM failed.
|
/// Writing the IDT to RAM failed.
|
||||||
WriteIDT,
|
WriteIDT(GuestMemoryError),
|
||||||
/// Writing PDPTE to RAM failed.
|
/// Writing PDPTE to RAM failed.
|
||||||
WritePDPTEAddress,
|
WritePDPTEAddress(GuestMemoryError),
|
||||||
/// Writing PDE to RAM failed.
|
/// Writing PDE to RAM failed.
|
||||||
WritePDEAddress,
|
WritePDEAddress(GuestMemoryError),
|
||||||
/// Writing PML4 to RAM failed.
|
/// Writing PML4 to RAM failed.
|
||||||
WritePML4Address,
|
WritePML4Address(GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
@@ -65,26 +67,10 @@ pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
|
|||||||
///
|
///
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
||||||
let entry_vec = create_msr_entries();
|
vcpu.set_msrs(&create_msr_entries())
|
||||||
let vec_size_bytes =
|
.map_err(Error::SetModelSpecificRegisters)?;
|
||||||
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)
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
Ok(())
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure base registers for a given CPU.
|
/// Configure base registers for a given CPU.
|
||||||
@@ -123,9 +109,6 @@ pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &VcpuFd) -> Result<()> {
|
|||||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||||
}
|
}
|
||||||
|
|
||||||
const BOOT_GDT_OFFSET: GuestAddress = GuestAddress(0x500);
|
|
||||||
const BOOT_IDT_OFFSET: GuestAddress = GuestAddress(0x520);
|
|
||||||
|
|
||||||
const BOOT_GDT_MAX: usize = 4;
|
const BOOT_GDT_MAX: usize = 4;
|
||||||
|
|
||||||
const EFER_LMA: u64 = 0x400;
|
const EFER_LMA: u64 = 0x400;
|
||||||
@@ -136,23 +119,21 @@ const X86_CR0_PG: u64 = 0x80000000;
|
|||||||
const X86_CR4_PAE: u64 = 0x20;
|
const X86_CR4_PAE: u64 = 0x20;
|
||||||
|
|
||||||
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||||
let boot_gdt_addr = BOOT_GDT_OFFSET;
|
let boot_gdt_addr = BOOT_GDT_START;
|
||||||
for (index, entry) in table.iter().enumerate() {
|
for (index, entry) in table.iter().enumerate() {
|
||||||
let addr = guest_mem
|
let addr = guest_mem
|
||||||
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
|
.checked_offset(boot_gdt_addr, index * mem::size_of::<u64>())
|
||||||
.ok_or(Error::WriteGDT)?;
|
.ok_or(Error::CheckGDTAddr)?;
|
||||||
guest_mem
|
guest_mem.write_obj(*entry, addr).map_err(Error::WriteGDT)?;
|
||||||
.write_obj(*entry, addr)
|
|
||||||
.map_err(|_| Error::WriteGDT)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||||
let boot_idt_addr = BOOT_IDT_OFFSET;
|
let boot_idt_addr = BOOT_IDT_START;
|
||||||
guest_mem
|
guest_mem
|
||||||
.write_obj(val, boot_idt_addr)
|
.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<()> {
|
fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> {
|
||||||
@@ -169,11 +150,11 @@ fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) ->
|
|||||||
|
|
||||||
// Write segments
|
// Write segments
|
||||||
write_gdt_table(&gdt_table[..], mem)?;
|
write_gdt_table(&gdt_table[..], mem)?;
|
||||||
sregs.gdt.base = BOOT_GDT_OFFSET.raw_value();
|
sregs.gdt.base = BOOT_GDT_START.raw_value();
|
||||||
sregs.gdt.limit = mem::size_of_val(&gdt_table) as u16 - 1;
|
sregs.gdt.limit = mem::size_of_val(&gdt_table) as u16 - 1;
|
||||||
|
|
||||||
write_idt_value(0, mem)?;
|
write_idt_value(0, mem)?;
|
||||||
sregs.idt.base = BOOT_IDT_OFFSET.raw_value();
|
sregs.idt.base = BOOT_IDT_START.raw_value();
|
||||||
sregs.idt.limit = mem::size_of::<u64>() as u16 - 1;
|
sregs.idt.limit = mem::size_of::<u64>() as u16 - 1;
|
||||||
|
|
||||||
sregs.cs = code_seg;
|
sregs.cs = code_seg;
|
||||||
@@ -196,16 +177,16 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
|
|||||||
|
|
||||||
// Entry covering VA [0..512GB)
|
// Entry covering VA [0..512GB)
|
||||||
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
|
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
|
||||||
.map_err(|_| Error::WritePML4Address)?;
|
.map_err(Error::WritePML4Address)?;
|
||||||
|
|
||||||
// Entry covering VA [0..1GB)
|
// Entry covering VA [0..1GB)
|
||||||
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
|
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
|
// 512 2MB entries together covering VA [0..1GB). Note we are assuming
|
||||||
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
|
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
|
||||||
for i in 0..512 {
|
for i in 0..512 {
|
||||||
mem.write_obj((i << 21) + 0x83u64, PDE_START.unchecked_add(i * 8))
|
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();
|
sregs.cr3 = PML4_START.raw_value();
|
||||||
@@ -214,7 +195,7 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_msr_entries() -> Vec<kvm_msr_entry> {
|
fn create_msr_entries() -> Msrs {
|
||||||
let mut entries = Vec::<kvm_msr_entry>::new();
|
let mut entries = Vec::<kvm_msr_entry>::new();
|
||||||
|
|
||||||
entries.push(kvm_msr_entry {
|
entries.push(kvm_msr_entry {
|
||||||
@@ -269,8 +250,13 @@ fn create_msr_entries() -> Vec<kvm_msr_entry> {
|
|||||||
data: msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64,
|
data: msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
entries.push(kvm_msr_entry {
|
||||||
|
index: msr_index::MSR_MTRRdefType,
|
||||||
|
data: MTRR_ENABLE | MTRR_MEM_TYPE_WB,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
entries
|
Msrs::from_entries(&entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -283,7 +269,7 @@ mod tests {
|
|||||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||||
|
|
||||||
fn create_guest_mem() -> 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 {
|
fn read_u64(gm: &GuestMemoryMmap, offset: GuestAddress) -> u64 {
|
||||||
@@ -296,20 +282,20 @@ mod tests {
|
|||||||
let gm = create_guest_mem();
|
let gm = create_guest_mem();
|
||||||
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
||||||
|
|
||||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_OFFSET));
|
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
0xaf9b000000ffff,
|
0xaf9b000000ffff,
|
||||||
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(8))
|
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
0xcf93000000ffff,
|
0xcf93000000ffff,
|
||||||
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(16))
|
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
0x8f8b000000ffff,
|
0x8f8b000000ffff,
|
||||||
read_u64(&gm, BOOT_GDT_OFFSET.unchecked_add(24))
|
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
|
||||||
);
|
);
|
||||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_OFFSET));
|
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
||||||
|
|
||||||
assert_eq!(0, sregs.cs.base);
|
assert_eq!(0, sregs.cs.base);
|
||||||
assert_eq!(0xfffff, sregs.ds.limit);
|
assert_eq!(0xfffff, sregs.ds.limit);
|
||||||
@@ -375,24 +361,11 @@ mod tests {
|
|||||||
|
|
||||||
// This test will check against the last MSR entry configured (the tenth one).
|
// This test will check against the last MSR entry configured (the tenth one).
|
||||||
// See create_msr_entries for details.
|
// 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,
|
index: msr_index::MSR_IA32_MISC_ENABLE,
|
||||||
..Default::default()
|
..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
|
// get_msrs returns the number of msrs that it succeed in reading. We only want to read 1
|
||||||
// in this test case scenario.
|
// in this test case scenario.
|
||||||
let read_msrs = vcpu.get_msrs(&mut msrs).unwrap();
|
let read_msrs = vcpu.get_msrs(&mut msrs).unwrap();
|
||||||
@@ -402,9 +375,7 @@ mod tests {
|
|||||||
// tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we
|
// tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we
|
||||||
// expect.
|
// expect.
|
||||||
let entry_vec = create_msr_entries();
|
let entry_vec = create_msr_entries();
|
||||||
unsafe {
|
assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]);
|
||||||
assert_eq!(entry_vec[9], msrs.entries.as_slice(1)[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -10,17 +10,16 @@
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[allow(
|
#[allow(
|
||||||
clippy::unreadable_literal,
|
clippy::unreadable_literal,
|
||||||
clippy::const_static_lifetime,
|
clippy::redundant_static_lifetimes,
|
||||||
clippy::trivially_copy_pass_by_ref,
|
clippy::trivially_copy_pass_by_ref,
|
||||||
clippy::useless_transmute,
|
clippy::useless_transmute,
|
||||||
clippy::should_implement_trait,
|
clippy::should_implement_trait,
|
||||||
clippy::transmute_ptr_to_ptr
|
clippy::transmute_ptr_to_ptr
|
||||||
)]
|
)]
|
||||||
pub mod bootparam;
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
#[allow(clippy::unreadable_literal, clippy::const_static_lifetime)]
|
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||||
pub mod mpspec;
|
pub mod mpspec;
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
#[allow(clippy::unreadable_literal, clippy::const_static_lifetime)]
|
#[allow(clippy::unreadable_literal, clippy::redundant_static_lifetimes)]
|
||||||
pub mod msr_index;
|
pub mod msr_index;
|
||||||
|
|||||||
@@ -4,14 +4,19 @@ version = "0.1.0"
|
|||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = ">=1.2.1"
|
bitflags = "1.2.1"
|
||||||
epoll = "=4.0.1"
|
byteorder = "1.3.4"
|
||||||
kvm-bindings = "0.1"
|
epoll = "4.1.0"
|
||||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
libc = "0.2.66"
|
||||||
libc = ">=0.2.39"
|
log = "0.4.8"
|
||||||
log = "*"
|
vm-device = { path = "../vm-device" }
|
||||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
vmm-sys-util = "0.4.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = ">=3.0.2"
|
tempfile = "3.1.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
acpi = []
|
||||||
|
cmos = []
|
||||||
|
|||||||
96
devices/src/acpi.rs
Normal file
96
devices/src/acpi.rs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// Copyright © 2019 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
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 {
|
||||||
|
exit_evt: EventFd,
|
||||||
|
reset_evt: EventFd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AcpiShutdownDevice {
|
||||||
|
/// Constructs a device that will signal the given event when the guest requests it.
|
||||||
|
pub fn new(exit_evt: EventFd, reset_evt: EventFd) -> AcpiShutdownDevice {
|
||||||
|
AcpiShutdownDevice {
|
||||||
|
exit_evt,
|
||||||
|
reset_evt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same I/O port used for shutdown and reboot
|
||||||
|
impl BusDevice for AcpiShutdownDevice {
|
||||||
|
// Spec has all fields as zero
|
||||||
|
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||||
|
for i in data.iter_mut() {
|
||||||
|
*i = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
|
||||||
|
if data[0] == 1 {
|
||||||
|
debug!("ACPI Reboot signalled");
|
||||||
|
if let Err(e) = self.reset_evt.write(1) {
|
||||||
|
error!("Error triggering ACPI reset event: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The ACPI DSDT table specifies the S5 sleep state (shutdown) as value 5
|
||||||
|
const S5_SLEEP_VALUE: u8 = 5;
|
||||||
|
const SLEEP_STATUS_EN_BIT: u8 = 5;
|
||||||
|
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]) {}
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
|
|
||||||
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
|
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
|
||||||
use std::collections::btree_map::BTreeMap;
|
use std::collections::btree_map::BTreeMap;
|
||||||
use std::result;
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::{convert, error, fmt, io, result};
|
||||||
|
|
||||||
/// Trait for devices that respond to reads or writes in an arbitrary address space.
|
/// 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 {
|
pub enum Error {
|
||||||
/// The insertion failed because the new device overlapped with an old device.
|
/// The insertion failed because the new device overlapped with an old device.
|
||||||
Overlap,
|
Overlap,
|
||||||
|
/// Failed to operate on zero sized range.
|
||||||
|
ZeroSizedRange,
|
||||||
|
/// Failed to find address range.
|
||||||
|
MissingAddressRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
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`.
|
/// Holds a base and length representing the address space occupied by a `BusDevice`.
|
||||||
///
|
///
|
||||||
/// * base - The address at which the range start.
|
/// * 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
|
/// 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.
|
/// only restriction is that no two devices can overlap in this address space.
|
||||||
#[derive(Clone, Default)]
|
#[derive(Default)]
|
||||||
pub struct Bus {
|
pub struct Bus {
|
||||||
devices: BTreeMap<BusRange, Arc<Mutex<BusDevice>>>,
|
devices: RwLock<BTreeMap<BusRange, Arc<Mutex<dyn BusDevice>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bus {
|
impl Bus {
|
||||||
/// Constructs an a bus with an empty address space.
|
/// Constructs an a bus with an empty address space.
|
||||||
pub fn new() -> Bus {
|
pub fn new() -> Bus {
|
||||||
Bus {
|
Bus {
|
||||||
devices: BTreeMap::new(),
|
devices: RwLock::new(BTreeMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn first_before(&self, addr: u64) -> Option<(BusRange, &Mutex<BusDevice>)> {
|
fn first_before(&self, addr: u64) -> Option<(BusRange, Arc<Mutex<dyn BusDevice>>)> {
|
||||||
let (range, dev) = self
|
let devices = self.devices.read().unwrap();
|
||||||
.devices
|
let (range, dev) = devices
|
||||||
.range(..=BusRange { base: addr, len: 1 })
|
.range(..=BusRange { base: addr, len: 1 })
|
||||||
.rev()
|
.rev()
|
||||||
.next()?;
|
.next()?;
|
||||||
Some((*range, dev))
|
Some((*range, dev.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve(&self, addr: u64) -> Option<(u64, u64, &Mutex<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) {
|
if let Some((range, dev)) = self.first_before(addr) {
|
||||||
let offset = addr - range.base;
|
let offset = addr - range.base;
|
||||||
if offset < range.len {
|
if offset < range.len {
|
||||||
@@ -108,14 +127,16 @@ impl Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Puts the given device at the given address space.
|
/// Puts the given device at the given address space.
|
||||||
pub fn insert(&mut self, device: Arc<Mutex<BusDevice>>, base: u64, len: u64) -> Result<()> {
|
pub fn insert(&self, device: Arc<Mutex<dyn BusDevice>>, base: u64, len: u64) -> Result<()> {
|
||||||
if len == 0 {
|
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.
|
// Reject all cases where the new device's range overlaps with an existing device.
|
||||||
if self
|
if self
|
||||||
.devices
|
.devices
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(range, _dev)| range.overlaps(base, len))
|
.any(|(range, _dev)| range.overlaps(base, len))
|
||||||
{
|
{
|
||||||
@@ -124,6 +145,8 @@ impl Bus {
|
|||||||
|
|
||||||
if self
|
if self
|
||||||
.devices
|
.devices
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
.insert(BusRange { base, len }, device)
|
.insert(BusRange { base, len }, device)
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
@@ -133,6 +156,43 @@ impl Bus {
|
|||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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`.
|
/// Reads data from the device that owns the range containing `addr` and puts it into `data`.
|
||||||
///
|
///
|
||||||
/// Returns true on success, otherwise `data` is untouched.
|
/// Returns true on success, otherwise `data` is untouched.
|
||||||
@@ -188,7 +248,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bus_insert() {
|
fn bus_insert() {
|
||||||
let mut bus = Bus::new();
|
let bus = Bus::new();
|
||||||
let dummy = Arc::new(Mutex::new(DummyDevice));
|
let dummy = Arc::new(Mutex::new(DummyDevice));
|
||||||
assert!(bus.insert(dummy.clone(), 0x10, 0).is_err());
|
assert!(bus.insert(dummy.clone(), 0x10, 0).is_err());
|
||||||
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
||||||
@@ -209,7 +269,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bus_read_write() {
|
fn bus_read_write() {
|
||||||
let mut bus = Bus::new();
|
let bus = Bus::new();
|
||||||
let dummy = Arc::new(Mutex::new(DummyDevice));
|
let dummy = Arc::new(Mutex::new(DummyDevice));
|
||||||
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
||||||
assert!(bus.read(0x10, &mut [0, 0, 0, 0]));
|
assert!(bus.read(0x10, &mut [0, 0, 0, 0]));
|
||||||
@@ -226,7 +286,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bus_read_write_values() {
|
fn bus_read_write_values() {
|
||||||
let mut bus = Bus::new();
|
let bus = Bus::new();
|
||||||
let dummy = Arc::new(Mutex::new(ConstantDevice));
|
let dummy = Arc::new(Mutex::new(ConstantDevice));
|
||||||
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
|
||||||
|
|
||||||
@@ -240,7 +300,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn busrange_cmp_and_clone() {
|
fn busrange_cmp() {
|
||||||
let range = BusRange { base: 0x10, len: 2 };
|
let range = BusRange { base: 0x10, len: 2 };
|
||||||
assert_eq!(range, BusRange { base: 0x10, len: 3 });
|
assert_eq!(range, BusRange { base: 0x10, len: 3 });
|
||||||
assert_eq!(range, BusRange { base: 0x10, len: 2 });
|
assert_eq!(range, BusRange { base: 0x10, len: 2 });
|
||||||
@@ -250,17 +310,14 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(range, range.clone());
|
assert_eq!(range, range.clone());
|
||||||
|
|
||||||
let mut bus = Bus::new();
|
let bus = Bus::new();
|
||||||
let mut data = [1, 2, 3, 4];
|
let mut data = [1, 2, 3, 4];
|
||||||
assert!(bus
|
assert!(bus
|
||||||
.insert(Arc::new(Mutex::new(DummyDevice)), 0x10, 0x10)
|
.insert(Arc::new(Mutex::new(DummyDevice)), 0x10, 0x10)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(bus.write(0x10, &mut data));
|
assert!(bus.write(0x10, &mut data));
|
||||||
let bus_clone = bus.clone();
|
|
||||||
assert!(bus.read(0x10, &mut data));
|
assert!(bus.read(0x10, &mut data));
|
||||||
assert_eq!(data, [1, 2, 3, 4]);
|
assert_eq!(data, [1, 2, 3, 4]);
|
||||||
assert!(bus_clone.read(0x10, &mut data));
|
|
||||||
assert_eq!(data, [1, 2, 3, 4]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -11,21 +11,35 @@
|
|||||||
|
|
||||||
use crate::BusDevice;
|
use crate::BusDevice;
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use kvm_bindings::kvm_msi;
|
use std::io;
|
||||||
use kvm_ioctls::VmFd;
|
use std::result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{io, result};
|
use vm_device::interrupt::{
|
||||||
|
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||||
|
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||||
|
};
|
||||||
|
use vm_memory::GuestAddress;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Failed to send an interrupt.
|
|
||||||
InterruptFailed(io::Error),
|
|
||||||
/// Invalid destination mode.
|
/// Invalid destination mode.
|
||||||
InvalidDestinationMode,
|
InvalidDestinationMode,
|
||||||
/// Invalid trigger mode.
|
/// Invalid trigger mode.
|
||||||
InvalidTriggerMode,
|
InvalidTriggerMode,
|
||||||
/// Invalid delivery mode.
|
/// Invalid delivery mode.
|
||||||
InvalidDeliveryMode,
|
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>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
@@ -155,7 +169,8 @@ pub struct Ioapic {
|
|||||||
id: u32,
|
id: u32,
|
||||||
reg_sel: u32,
|
reg_sel: u32,
|
||||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||||
vm_fd: Arc<VmFd>,
|
apic_address: GuestAddress,
|
||||||
|
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BusDevice for Ioapic {
|
impl BusDevice for Ioapic {
|
||||||
@@ -188,20 +203,34 @@ impl BusDevice for Ioapic {
|
|||||||
IOWIN_OFF => self.ioapic_write(value),
|
IOWIN_OFF => self.ioapic_write(value),
|
||||||
_ => {
|
_ => {
|
||||||
error!("IOAPIC: failed writing at offset {}", offset);
|
error!("IOAPIC: failed writing at offset {}", offset);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ioapic {
|
impl Ioapic {
|
||||||
pub fn new(vm_fd: Arc<VmFd>) -> Ioapic {
|
pub fn new(
|
||||||
Ioapic {
|
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,
|
id: 0,
|
||||||
reg_sel: 0,
|
reg_sel: 0,
|
||||||
reg_entries: [0; NUM_IOAPIC_PINS],
|
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
|
// The ioapic must be informed about EOIs in order to deassert interrupts
|
||||||
@@ -221,16 +250,30 @@ impl Ioapic {
|
|||||||
pub fn service_irq(&mut self, irq: usize) -> Result<()> {
|
pub fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||||
let entry = &mut self.reg_entries[irq];
|
let entry = &mut self.reg_entries[irq];
|
||||||
|
|
||||||
// Don't inject the interrupt if the IRQ is masked
|
self.interrupt_source_group
|
||||||
if interrupt_mask(*entry) == 1 {
|
.trigger(irq as InterruptIndex)
|
||||||
return Ok(());
|
.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
|
// 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 {
|
let destination_id: u8 = match destination_mode {
|
||||||
x if x == DestinationMode::Physical as u8 => destination_field_physical(*entry),
|
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::Logical as u8 => destination_field_logical(entry),
|
||||||
_ => return Err(Error::InvalidDestinationMode),
|
_ => return Err(Error::InvalidDestinationMode),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -240,20 +283,20 @@ impl Ioapic {
|
|||||||
let redirection_hint: u8 = 1;
|
let redirection_hint: u8 = 1;
|
||||||
|
|
||||||
// Generate MSI message address
|
// Generate MSI message address
|
||||||
let address_lo: u32 = 0xfee0_0000
|
let low_addr: u32 = self.apic_address.0 as u32
|
||||||
| u32::from(destination_id) << 12
|
| u32::from(destination_id) << 12
|
||||||
| u32::from(redirection_hint) << 3
|
| u32::from(redirection_hint) << 3
|
||||||
| u32::from(destination_mode) << 2;
|
| u32::from(destination_mode) << 2;
|
||||||
|
|
||||||
// Validate Trigger Mode value
|
// Validate Trigger Mode value
|
||||||
let trigger_mode = trigger_mode(*entry);
|
let trigger_mode = trigger_mode(entry);
|
||||||
match trigger_mode {
|
match trigger_mode {
|
||||||
x if (x == TriggerMode::Edge as u8) || (x == TriggerMode::Level as u8) => {}
|
x if (x == TriggerMode::Edge as u8) || (x == TriggerMode::Level as u8) => {}
|
||||||
_ => return Err(Error::InvalidTriggerMode),
|
_ => return Err(Error::InvalidTriggerMode),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate Delivery Mode value
|
// Validate Delivery Mode value
|
||||||
let delivery_mode = delivery_mode(*entry);
|
let delivery_mode = delivery_mode(entry);
|
||||||
match delivery_mode {
|
match delivery_mode {
|
||||||
x if (x == DeliveryMode::Fixed as u8)
|
x if (x == DeliveryMode::Fixed as u8)
|
||||||
|| (x == DeliveryMode::Lowest as u8)
|
|| (x == DeliveryMode::Lowest as u8)
|
||||||
@@ -268,37 +311,31 @@ impl Ioapic {
|
|||||||
|
|
||||||
// Generate MSI message data
|
// Generate MSI message data
|
||||||
let data: u32 = u32::from(trigger_mode) << 15
|
let data: u32 = u32::from(trigger_mode) << 15
|
||||||
| u32::from(remote_irr(*entry)) << 14
|
| u32::from(remote_irr(entry)) << 14
|
||||||
| u32::from(delivery_mode) << 8
|
| u32::from(delivery_mode) << 8
|
||||||
| u32::from(vector(*entry));
|
| u32::from(vector(entry));
|
||||||
|
|
||||||
let msi = kvm_msi {
|
let config = MsiIrqSourceConfig {
|
||||||
address_lo,
|
high_addr: 0x0,
|
||||||
address_hi: 0x0,
|
low_addr,
|
||||||
data,
|
data,
|
||||||
flags: 0u32,
|
|
||||||
devid: 0u32,
|
|
||||||
pad: [0u8; 12],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.vm_fd.signal_msi(msi) {
|
self.interrupt_source_group
|
||||||
Ok(ret) => {
|
.update(irq as InterruptIndex, InterruptSourceConfig::MsiIrq(config))
|
||||||
if ret > 0 {
|
.map_err(Error::UpdateInterrupt)?;
|
||||||
debug!("MSI message successfully delivered");
|
|
||||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
if interrupt_mask(entry) == 1 {
|
||||||
// It will be cleared when the EOI is received.
|
self.interrupt_source_group
|
||||||
if trigger_mode == 1 {
|
.mask(irq as InterruptIndex)
|
||||||
set_remote_irr(entry, 1);
|
.map_err(Error::MaskInterrupt)?;
|
||||||
}
|
} else {
|
||||||
// Clear the Delivery Status bit
|
self.interrupt_source_group
|
||||||
set_delivery_status(entry, 0);
|
.unmask(irq as InterruptIndex)
|
||||||
} else {
|
.map_err(Error::UnmaskInterrupt)?;
|
||||||
warn!("failed to deliver MSI message, blocked by guest");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(e) => Err(Error::InterruptFailed(e)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ioapic_write(&mut self, val: u32) {
|
fn ioapic_write(&mut self, val: u32) {
|
||||||
@@ -318,6 +355,11 @@ impl Ioapic {
|
|||||||
self.reg_entries[index] &= 0xffff_ffff_0000_5000;
|
self.reg_entries[index] &= 0xffff_ffff_0000_5000;
|
||||||
self.reg_entries[index] |= u64::from(val) & 0xffff_afff;
|
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"),
|
_ => error!("IOAPIC: invalid write to register offset"),
|
||||||
}
|
}
|
||||||
|
|||||||
115
devices/src/legacy/cmos.rs
Normal file
115
devices/src/legacy/cmos.rs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
use libc::{gmtime_r, time, time_t, tm};
|
||||||
|
use std::cmp::min;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use crate::BusDevice;
|
||||||
|
|
||||||
|
const INDEX_MASK: u8 = 0x7f;
|
||||||
|
const INDEX_OFFSET: u64 = 0x0;
|
||||||
|
const DATA_OFFSET: u64 = 0x1;
|
||||||
|
const DATA_LEN: usize = 128;
|
||||||
|
|
||||||
|
/// A CMOS/RTC device commonly seen on x86 I/O port 0x70/0x71.
|
||||||
|
pub struct Cmos {
|
||||||
|
index: u8,
|
||||||
|
data: [u8; DATA_LEN],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cmos {
|
||||||
|
/// Constructs a CMOS/RTC device with initial data.
|
||||||
|
/// `mem_below_4g` is the size of memory in bytes below the 32-bit gap.
|
||||||
|
/// `mem_above_4g` is the size of memory in bytes above the 32-bit gap.
|
||||||
|
pub fn new(mem_below_4g: u64, mem_above_4g: u64) -> Cmos {
|
||||||
|
let mut data = [0u8; DATA_LEN];
|
||||||
|
|
||||||
|
// Extended memory from 16 MB to 4 GB in units of 64 KB
|
||||||
|
let ext_mem = min(
|
||||||
|
0xFFFF,
|
||||||
|
mem_below_4g.saturating_sub(16 * 1024 * 1024) / (64 * 1024),
|
||||||
|
);
|
||||||
|
data[0x34] = ext_mem as u8;
|
||||||
|
data[0x35] = (ext_mem >> 8) as u8;
|
||||||
|
|
||||||
|
// High memory (> 4GB) in units of 64 KB
|
||||||
|
let high_mem = min(0x00FF_FFFF, mem_above_4g / (64 * 1024));
|
||||||
|
data[0x5b] = high_mem as u8;
|
||||||
|
data[0x5c] = (high_mem >> 8) as u8;
|
||||||
|
data[0x5d] = (high_mem >> 16) as u8;
|
||||||
|
|
||||||
|
Cmos { index: 0, data }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BusDevice for Cmos {
|
||||||
|
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||||
|
if data.len() != 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match offset {
|
||||||
|
INDEX_OFFSET => self.index = data[0] & INDEX_MASK,
|
||||||
|
DATA_OFFSET => self.data[self.index as usize] = data[0],
|
||||||
|
o => panic!("bad write offset on CMOS device: {}", o),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
|
fn to_bcd(v: u8) -> u8 {
|
||||||
|
assert!(v < 100);
|
||||||
|
((v / 10) << 4) | (v % 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.len() != 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data[0] = match offset {
|
||||||
|
INDEX_OFFSET => self.index,
|
||||||
|
DATA_OFFSET => {
|
||||||
|
let seconds;
|
||||||
|
let minutes;
|
||||||
|
let hours;
|
||||||
|
let week_day;
|
||||||
|
let day;
|
||||||
|
let month;
|
||||||
|
let year;
|
||||||
|
// The time and gmtime_r calls are safe as long as the structs they are given are
|
||||||
|
// large enough, and neither of them fail. It is safe to zero initialize the tm
|
||||||
|
// struct because it contains only plain data.
|
||||||
|
unsafe {
|
||||||
|
let mut tm: tm = mem::zeroed();
|
||||||
|
let mut now: time_t = 0;
|
||||||
|
time(&mut now as *mut _);
|
||||||
|
gmtime_r(&now, &mut tm as *mut _);
|
||||||
|
// The following lines of code are safe but depend on tm being in scope.
|
||||||
|
seconds = tm.tm_sec;
|
||||||
|
minutes = tm.tm_min;
|
||||||
|
hours = tm.tm_hour;
|
||||||
|
week_day = tm.tm_wday + 1;
|
||||||
|
day = tm.tm_mday;
|
||||||
|
month = tm.tm_mon + 1;
|
||||||
|
year = tm.tm_year;
|
||||||
|
};
|
||||||
|
match self.index {
|
||||||
|
0x00 => to_bcd(seconds as u8),
|
||||||
|
0x02 => to_bcd(minutes as u8),
|
||||||
|
0x04 => to_bcd(hours as u8),
|
||||||
|
0x06 => to_bcd(week_day as u8),
|
||||||
|
0x07 => to_bcd(day as u8),
|
||||||
|
0x08 => to_bcd(month as u8),
|
||||||
|
0x09 => to_bcd((year % 100) as u8),
|
||||||
|
0x32 => to_bcd(((year + 1900) / 100) as u8),
|
||||||
|
_ => {
|
||||||
|
// self.index is always guaranteed to be in range via INDEX_MASK.
|
||||||
|
self.data[(self.index & INDEX_MASK) as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o => panic!("bad read offset on CMOS device: {}", o),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use vmm_sys_util::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use BusDevice;
|
use BusDevice;
|
||||||
|
|
||||||
@@ -34,8 +34,9 @@ impl BusDevice for I8042Device {
|
|||||||
|
|
||||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||||
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
|
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
|
||||||
|
debug!("i8042 reset signalled");
|
||||||
if let Err(e) = self.reset_evt.write(1) {
|
if let Err(e) = self.reset_evt.write(1) {
|
||||||
println!("Error triggering i8042 reset event: {}", e);
|
error!("Error triggering i8042 reset event: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
|
#[cfg(feature = "cmos")]
|
||||||
|
mod cmos;
|
||||||
mod i8042;
|
mod i8042;
|
||||||
mod serial;
|
mod serial;
|
||||||
|
|
||||||
|
#[cfg(feature = "cmos")]
|
||||||
|
pub use self::cmos::Cmos;
|
||||||
pub use self::i8042::I8042Device;
|
pub use self::i8042::I8042Device;
|
||||||
pub use self::serial::Serial;
|
pub use self::serial::Serial;
|
||||||
|
|||||||
@@ -5,10 +5,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use crate::{BusDevice, Interrupt};
|
use crate::BusDevice;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
use vmm_sys_util::Result;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
|
use vmm_sys_util::errno::Result;
|
||||||
|
|
||||||
const LOOP_SIZE: usize = 0x40;
|
const LOOP_SIZE: usize = 0x40;
|
||||||
|
|
||||||
@@ -55,7 +57,7 @@ const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
|
|||||||
pub struct Serial {
|
pub struct Serial {
|
||||||
interrupt_enable: u8,
|
interrupt_enable: u8,
|
||||||
interrupt_identification: u8,
|
interrupt_identification: u8,
|
||||||
interrupt: Box<Interrupt>,
|
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||||
line_control: u8,
|
line_control: u8,
|
||||||
line_status: u8,
|
line_status: u8,
|
||||||
modem_control: u8,
|
modem_control: u8,
|
||||||
@@ -63,11 +65,14 @@ pub struct Serial {
|
|||||||
scratch: u8,
|
scratch: u8,
|
||||||
baud_divisor: u16,
|
baud_divisor: u16,
|
||||||
in_buffer: VecDeque<u8>,
|
in_buffer: VecDeque<u8>,
|
||||||
out: Option<Box<io::Write + Send>>,
|
out: Option<Box<dyn io::Write + Send>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serial {
|
impl Serial {
|
||||||
fn new(interrupt: Box<Interrupt>, out: Option<Box<io::Write + Send>>) -> Serial {
|
pub fn new(
|
||||||
|
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||||
|
out: Option<Box<dyn io::Write + Send>>,
|
||||||
|
) -> Serial {
|
||||||
Serial {
|
Serial {
|
||||||
interrupt_enable: 0,
|
interrupt_enable: 0,
|
||||||
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
||||||
@@ -84,12 +89,15 @@ impl Serial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a Serial port ready for output.
|
/// Constructs a Serial port ready for output.
|
||||||
pub fn new_out(interrupt: Box<Interrupt>, out: Box<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))
|
Self::new(interrupt, Some(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a Serial port with no connected output.
|
/// Constructs a Serial port with no connected output.
|
||||||
pub fn new_sink(interrupt: Box<Interrupt>) -> Serial {
|
pub fn new_sink(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
|
||||||
Self::new(interrupt, None)
|
Self::new(interrupt, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +157,7 @@ impl Serial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_interrupt(&mut self) -> result::Result<(), io::Error> {
|
fn trigger_interrupt(&mut self) -> result::Result<(), io::Error> {
|
||||||
self.interrupt.deliver()
|
self.interrupt.trigger(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iir_reset(&mut self) {
|
fn iir_reset(&mut self) {
|
||||||
@@ -233,16 +241,24 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use vmm_sys_util::EventFd;
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
struct TestInterrupt {
|
struct TestInterrupt {
|
||||||
event_fd: EventFd,
|
event_fd: EventFd,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interrupt for TestInterrupt {
|
impl InterruptSourceGroup for TestInterrupt {
|
||||||
fn deliver(&self) -> result::Result<(), std::io::Error> {
|
fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> {
|
||||||
self.event_fd.write(1)
|
self.event_fd.write(1)
|
||||||
}
|
}
|
||||||
|
fn update(
|
||||||
|
&self,
|
||||||
|
_index: InterruptIndex,
|
||||||
|
_config: InterruptSourceConfig,
|
||||||
|
) -> result::Result<(), std::io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestInterrupt {
|
impl TestInterrupt {
|
||||||
@@ -278,7 +294,7 @@ mod tests {
|
|||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let serial_out = SharedBuffer::new();
|
let serial_out = SharedBuffer::new();
|
||||||
let mut serial = Serial::new_out(
|
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()),
|
Box::new(serial_out.clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -297,7 +313,7 @@ mod tests {
|
|||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let serial_out = SharedBuffer::new();
|
let serial_out = SharedBuffer::new();
|
||||||
let mut serial = Serial::new_out(
|
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()),
|
Box::new(serial_out.clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -334,8 +350,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn serial_thr() {
|
fn serial_thr() {
|
||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let mut serial =
|
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||||
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())));
|
intr_evt.try_clone().unwrap(),
|
||||||
|
))));
|
||||||
|
|
||||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||||
// counter doesn't change (for 0 it blocks)
|
// counter doesn't change (for 0 it blocks)
|
||||||
@@ -354,8 +371,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn serial_dlab() {
|
fn serial_dlab() {
|
||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let mut serial =
|
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||||
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())));
|
intr_evt.try_clone().unwrap(),
|
||||||
|
))));
|
||||||
|
|
||||||
serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]);
|
serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]);
|
||||||
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
|
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
|
||||||
@@ -373,8 +391,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn serial_modem() {
|
fn serial_modem() {
|
||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let mut serial =
|
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||||
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())));
|
intr_evt.try_clone().unwrap(),
|
||||||
|
))));
|
||||||
|
|
||||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
|
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
|
||||||
serial.write(0, DATA as u64, &['a' as u8]);
|
serial.write(0, DATA as u64, &['a' as u8]);
|
||||||
@@ -397,8 +416,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn serial_scratch() {
|
fn serial_scratch() {
|
||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let mut serial =
|
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||||
Serial::new_sink(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap())));
|
intr_evt.try_clone().unwrap(),
|
||||||
|
))));
|
||||||
|
|
||||||
serial.write(0, SCR as u64, &[0x12 as u8]);
|
serial.write(0, SCR as u64, &[0x12 as u8]);
|
||||||
|
|
||||||
|
|||||||
@@ -6,23 +6,28 @@
|
|||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
//! Emulates virtual and hardware devices.
|
//! Emulates virtual and hardware devices.
|
||||||
|
#[macro_use]
|
||||||
|
extern crate bitflags;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate epoll;
|
extern crate epoll;
|
||||||
extern crate kvm_bindings;
|
|
||||||
extern crate kvm_ioctls;
|
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
extern crate vm_device;
|
||||||
extern crate vm_memory;
|
extern crate vm_memory;
|
||||||
extern crate vmm_sys_util;
|
extern crate vmm_sys_util;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::{io, result};
|
use std::io;
|
||||||
|
|
||||||
|
#[cfg(feature = "acpi")]
|
||||||
|
mod acpi;
|
||||||
mod bus;
|
mod bus;
|
||||||
pub mod ioapic;
|
pub mod ioapic;
|
||||||
pub mod legacy;
|
pub mod legacy;
|
||||||
|
|
||||||
|
#[cfg(feature = "acpi")]
|
||||||
|
pub use self::acpi::{AcpiGEDDevice, AcpiShutdownDevice};
|
||||||
pub use self::bus::{Bus, BusDevice, Error as BusError};
|
pub use self::bus::{Bus, BusDevice, Error as BusError};
|
||||||
|
|
||||||
pub type DeviceEventT = u16;
|
pub type DeviceEventT = u16;
|
||||||
@@ -63,6 +68,10 @@ pub enum Error {
|
|||||||
IoError(io::Error),
|
IoError(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Interrupt: Send {
|
bitflags! {
|
||||||
fn deliver(&self) -> result::Result<(), std::io::Error>;
|
pub struct HotPlugNotificationFlags: u8 {
|
||||||
|
const NO_DEVICES_CHANGED = 0;
|
||||||
|
const CPU_DEVICES_CHANGED = 0b1;
|
||||||
|
const MEMORY_DEVICES_CHANGED = 0b10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
371
docs/api.md
Normal file
371
docs/api.md
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
- [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
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
96
docs/custom-image.md
Normal file
96
docs/custom-image.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# How to create a custom Clear Linux image
|
||||||
|
|
||||||
|
In the context of adding more utility to the cloudguest image being used
|
||||||
|
for integration testing, this is a quick guide on how to achieve the creation
|
||||||
|
of your own Clear Linux image using the official Clear Linux tooling.
|
||||||
|
|
||||||
|
## Prepare the environment
|
||||||
|
|
||||||
|
From the host, the goal is run a Clear Linux VM that will allow us to build
|
||||||
|
the custom image we want.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get latest CL version:
|
||||||
|
IMG_VERSION=$(curl https://download.clearlinux.org/latest)
|
||||||
|
# Get latest clear-kvm image:
|
||||||
|
wget -P $HOME/workloads/ https://download.clearlinux.org/current/clear-${IMG_VERSION}-kvm.img.xz
|
||||||
|
# Extract the image
|
||||||
|
unxz $HOME/workloads/clear-${IMG_VERSION}-kvm.img.xz
|
||||||
|
# Make sure cloud-hypervisor binary has CAP_NET_ADMIN capability set
|
||||||
|
sudo setcap cap_net_admin+ep cloud-hypervisor
|
||||||
|
# Boot cloud-hypervisor VM with the downloaded image
|
||||||
|
./cloud-hypervisor -v --kernel $HOME/workloads/vmlinux --disk path=clear-${IMG_VERSION}-kvm.img --cmdline "console=ttyS0 console=hvc0 reboot=k panic=1 nomodules root=/dev/vda3 rw" --cpus 1 --memory size=4G --net tap=,mac=
|
||||||
|
# Setup connectivity
|
||||||
|
# First make sure to enable IP forwarding (disabled on Linux by default)
|
||||||
|
sudo bash -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
|
||||||
|
# Retrieve the interface name and the gateway IP
|
||||||
|
IFACE=$(ip route | grep default | awk -F 'dev' '{print $2}' | awk -F ' ' '{print $1}')
|
||||||
|
GW=$(ip route | grep vmtap0 | awk -F ' ' '{print $1}')
|
||||||
|
# Create a new masquerade rule to tag the packets going out
|
||||||
|
sudo iptables -t nat -A POSTROUTING -s ${GW} -o ${IFACE} -j MASQUERADE
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create the image
|
||||||
|
|
||||||
|
From the guest, we can now create the image.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Setup connectivity
|
||||||
|
sudo ip addr add 192.168.249.2/24 dev enp0s3
|
||||||
|
sudo ip route add default via 192.168.249.1
|
||||||
|
# Install necessary bundles
|
||||||
|
sudo swupd bundle-add clr-installer
|
||||||
|
sudo swupd bundle-add os-installer
|
||||||
|
# Download and update cloudguest image configuration
|
||||||
|
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/systemd-networkd-autostart/sysadmin-basic,\n systemd-networkd-autostart/g' cloudguest.yaml
|
||||||
|
# Create the custom cloudguest image
|
||||||
|
clr-installer -c cloudguest.yaml
|
||||||
|
# Make the guest accessible through ssh
|
||||||
|
sudo mkdir -p /etc/ssh
|
||||||
|
sudo bash -c "echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Retrieve the image
|
||||||
|
|
||||||
|
Once the new image has been created and the guest is accessible through
|
||||||
|
`ssh`, it is time to retrieve the image from the host.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Retrieve new image (this is a raw image)
|
||||||
|
scp root@192.168.249.2:cloudguest.img .
|
||||||
|
mv cloudguest.img clear-cloudguest-raw.img
|
||||||
|
# Create the QCOW image from the RAW image
|
||||||
|
qemu-img convert -p -f raw -O qcow2 clear-cloudguest-raw.img clear-cloudguest.img
|
||||||
|
# Compress the QCOW image
|
||||||
|
xz -k -T $(nproc) clear-cloudguest.img
|
||||||
|
```
|
||||||
|
|
||||||
|
## Switch CI to use the new image
|
||||||
|
|
||||||
|
### Upload to Azure storage
|
||||||
|
|
||||||
|
The next step is to update the image stored as part of the Azure storage
|
||||||
|
account, replacing it with the newly created image. This will make this
|
||||||
|
new image available from the integration tests.
|
||||||
|
This is usually achieved through the web interface.
|
||||||
|
|
||||||
|
### Update integration tests
|
||||||
|
|
||||||
|
Last step is about updating the integration tests to work with this new image.
|
||||||
|
The key point is to identify the UUID of this new image so that it can be used
|
||||||
|
directly from the tests.
|
||||||
|
|
||||||
|
Proceed as follow to determine this UUID:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mount the image
|
||||||
|
sudo mount -o loop,offset=$((2048 * 512)) clear-cloudguest-raw.img /mnt/
|
||||||
|
# Identify UUID
|
||||||
|
sudo cat /mnt/loader/entries/Clear-linux-kvm-*.conf | grep "root=PARTUUID="
|
||||||
|
# Unmount the image
|
||||||
|
sudo umount /mnt
|
||||||
|
```
|
||||||
61
docs/debug-port.md
Normal file
61
docs/debug-port.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# `cloud-hypervisor` debug IO port
|
||||||
|
|
||||||
|
`cloud-hypervisor` uses the [`0x80`](https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html)
|
||||||
|
I/O port to trace user defined guest events.
|
||||||
|
|
||||||
|
Whenever the guest write one byte between `0x0` and `0xF` on this particular
|
||||||
|
I/O port, `cloud-hypervisor` will log and timestamp that event at the `debug`
|
||||||
|
log level.
|
||||||
|
|
||||||
|
It is up to the guest stack to decide when and what to write to the 0x80 port
|
||||||
|
in order to signal the host about specific events and have `cloud-hypervisor`
|
||||||
|
log it.
|
||||||
|
|
||||||
|
`cloud-hypervisor` defines several debug port code ranges that should be used
|
||||||
|
for debugging specific components of the guest software stack. When logging a
|
||||||
|
write of one of those codes to the debug port, `cloud-hypervisor` adds a
|
||||||
|
pre-defined string to the logs.
|
||||||
|
|
||||||
|
| Code Range | Component | Log string |
|
||||||
|
| ---------------- | ----------- | ------------ |
|
||||||
|
| `0x00` to `0x1f` | Firmware | `Firmware` |
|
||||||
|
| `0x20` to `0x3f` | Bootloader | `Bootloader` |
|
||||||
|
| `0x40` to `0x5f` | Kernel | `Kernel` |
|
||||||
|
| `0x60` to `0x7f` | Userspace | `Userspace` |
|
||||||
|
| `0x80` to `0xff` | Custom | `Custom` |
|
||||||
|
|
||||||
|
One typical use case is guest boot time measurement and tracing. By writing
|
||||||
|
different values to the debug I/O port at different boot process steps, the
|
||||||
|
guest will have `cloud-hypervisor` generate timestamped logs of all those steps.
|
||||||
|
That provides a basic but convenient way of measuring not only the overall guest
|
||||||
|
boot time but all intermediate steps as well.
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
Assuming parts of the guest software stack have been instrumented to use the
|
||||||
|
`cloud-hypervisor` debug I/O port, we may want to gather the related logs.
|
||||||
|
|
||||||
|
To do so we need to start `cloud-hypervisor` with the right debug level
|
||||||
|
(`-vvv`). It is also recommended to have it log into a dedicated file in order
|
||||||
|
to easily grep for the tracing logs (e.g.
|
||||||
|
`--log-file /tmp/cloud-hypervisor.log`):
|
||||||
|
|
||||||
|
```
|
||||||
|
./target/debug/cloud-hypervisor \
|
||||||
|
--kernel ~/rust-hypervisor-firmware/target/target/release/hypervisor-fw \
|
||||||
|
--disk path=~/hypervisor/images/clear-30080-kvm.img \
|
||||||
|
--cpus 4 \
|
||||||
|
--memory size=1024M \
|
||||||
|
--rng \
|
||||||
|
--log-file /tmp/ch-fw.log \
|
||||||
|
-vvv
|
||||||
|
```
|
||||||
|
|
||||||
|
After booting the guest, we then have to grep for the debug I/O port traces in
|
||||||
|
the log file:
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
$ grep "Debug I/O port" /tmp/ch-fw.log
|
||||||
|
cloud-hypervisor: 19.762449ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x0] 0.019004 seconds
|
||||||
|
cloud-hypervisor: 403.499628ms: DEBUG:vmm/src/vm.rs:510 -- [Debug I/O port: Firmware code 0x1] 0.402744 seconds
|
||||||
|
```
|
||||||
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.
|
||||||
46
docs/fs.md
46
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.
|
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
|
```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://.*[^ "]')"
|
git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $VIRTIOFSD_DIR
|
||||||
wget --quiet $VIRTIOFSD_URL -O "virtiofsd"
|
cd $VIRTIOFSD_DIR
|
||||||
chmod +x "virtiofsd"
|
./configure --prefix=$PWD --target-list=x86_64-softmmu
|
||||||
|
make virtiofsd -j `nproc`
|
||||||
sudo setcap cap_sys_admin+epi "virtiofsd"
|
sudo setcap cap_sys_admin+epi "virtiofsd"
|
||||||
```
|
```
|
||||||
_Create shared directory_
|
_Create shared directory_
|
||||||
@@ -25,17 +26,20 @@ _Run virtiofsd_
|
|||||||
```bash
|
```bash
|
||||||
./virtiofsd \
|
./virtiofsd \
|
||||||
-d \
|
-d \
|
||||||
-o vhost_user_socket=/tmp/virtiofs \
|
--socket-path=/tmp/virtiofs \
|
||||||
-o source=/tmp/shared_dir \
|
-o source=/tmp/shared_dir \
|
||||||
-o cache=none
|
-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
|
### 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.
|
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.
|
Make sure to build a kernel out of this branch that can be then used to boot the VM.
|
||||||
|
|
||||||
@@ -53,18 +57,32 @@ Assuming you have `clear-kvm.img` and `custom-vmlinux.bin` on your system, here
|
|||||||
./cloud-hypervisor \
|
./cloud-hypervisor \
|
||||||
--cpus 4 \
|
--cpus 4 \
|
||||||
--memory "size=512,file=/dev/shm" \
|
--memory "size=512,file=/dev/shm" \
|
||||||
--disk clear-kvm.img \
|
--disk path=clear-kvm.img \
|
||||||
--kernel custom-vmlinux.bin \
|
--kernel custom-vmlinux.bin \
|
||||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
--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
|
### 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
|
```bash
|
||||||
mkdir mount_dir
|
mkdir mount_dir
|
||||||
mount \
|
mount -t virtiofs -o dax myfs mount_dir/
|
||||||
-t virtio_fs /dev/null mount_dir/ \
|
|
||||||
-o tag=virtiofs,rootmode=040000,user_id=0,group_id=0
|
|
||||||
```
|
```
|
||||||
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:
|
||||||
|
|
||||||
|
```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.
|
||||||
209
docs/iommu.md
Normal file
209
docs/iommu.md
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
# Virtual IOMMU
|
||||||
|
|
||||||
|
## Rationales
|
||||||
|
|
||||||
|
Having the possibility to expose a virtual IOMMU to the guest can be
|
||||||
|
interesting to support specific use cases. That being said, it is always
|
||||||
|
important to keep in mind a virtual IOMMU can impact the performance of the
|
||||||
|
attached devices, which is the reason why one should be careful when enabling
|
||||||
|
this feature.
|
||||||
|
|
||||||
|
### Protect nested virtual machines
|
||||||
|
|
||||||
|
The first reason why one might want to expose a virtual IOMMU to the guest is
|
||||||
|
to increase the security regarding the memory accesses performed by the virtual
|
||||||
|
devices (VIRTIO devices), on behalf of the guest drivers.
|
||||||
|
|
||||||
|
With a virtual IOMMU, the VMM stands between the guest driver and its device
|
||||||
|
counterpart, validating and translating every address before to try accessing
|
||||||
|
the guest memory. This is standard interposition that is performed here by the
|
||||||
|
VMM.
|
||||||
|
|
||||||
|
The increased security does not apply for a simple case where we have one VM
|
||||||
|
per VMM. Because the guest cannot be trusted, as we always consider it could
|
||||||
|
be malicious and gain unauthorized privileges inside the VM, preventing some
|
||||||
|
devices from accessing the entire guest memory is pointless.
|
||||||
|
|
||||||
|
But let's take the interesting case of nested virtualization, and let's assume
|
||||||
|
we have a VMM running a first layer VM. This L1 guest is fully trusted as the
|
||||||
|
user intends to run multiple VMs from this L1. We can end up with multiple L2
|
||||||
|
VMs running on a single L1 VM. In this particular case, and without exposing a
|
||||||
|
virtual IOMMU to the L1 guest, it would be possible for any L2 guest to use the
|
||||||
|
device implementation from the host VMM to access the entire guest L1 memory.
|
||||||
|
The virtual IOMMU prevents from this kind of trouble as it will validate the
|
||||||
|
addresses the device is authorized to access.
|
||||||
|
|
||||||
|
### Achieve VFIO nested
|
||||||
|
|
||||||
|
Another reason for having a virtual IOMMU is to allow passing physical devices
|
||||||
|
from the host through multiple layers of virtualization. Let's take as example
|
||||||
|
a system with a physical IOMMU running a VM with a virtual IOMMU. The
|
||||||
|
implementation of the virtual IOMMU is responsible for updating the physical
|
||||||
|
DMA Remapping table (DMAR) everytime the DMA mapping changes. This must happen
|
||||||
|
through the VFIO framework on the host as this is the only userspace interface
|
||||||
|
to interact with a physical IOMMU.
|
||||||
|
|
||||||
|
Relying on this update mechanism, it is possible to attach physical devices to
|
||||||
|
the virtual IOMMU, which allows these devices to be passed from L1 to another
|
||||||
|
layer of virtualization.
|
||||||
|
|
||||||
|
## Why virtio-iommu?
|
||||||
|
|
||||||
|
The Cloud Hypervisor project decided to implement the brand new virtio-iommu
|
||||||
|
device in order to provide a virtual IOMMU to its users. The reason being the
|
||||||
|
simplicity brought by the paravirtualization solution. By having one side
|
||||||
|
handled from the guest itself, it removes the complexity of trapping memory
|
||||||
|
page accesses and shadowing them. This is why the project will not try to
|
||||||
|
implement a full emulation of a physical IOMMU.
|
||||||
|
|
||||||
|
## Pre-requisites
|
||||||
|
|
||||||
|
### Kernel
|
||||||
|
|
||||||
|
Since virtio-iommu has landed partially into the version 5.3 of the Linux
|
||||||
|
kernel, a special branch is needed to get things working with Cloud Hypervisor.
|
||||||
|
By partially, we are talking about x86 specifically, as it is already fully
|
||||||
|
functional for ARM architectures.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
In order to expose a virtual IOMMU to the guest, it is required to create a
|
||||||
|
virtio-iommu device and expose it through the ACPI IORT table. This can be
|
||||||
|
simply achieved by attaching at least one device to the virtual IOMMU.
|
||||||
|
|
||||||
|
The way to expose to the guest a specific device as sitting behind this IOMMU
|
||||||
|
is to explicitly tag it from the command line with the option `iommu=on`.
|
||||||
|
|
||||||
|
Not all devices support this extra option, and the default value will always
|
||||||
|
be `off` since we want to avoid the performance impact for most users who don't
|
||||||
|
need this.
|
||||||
|
|
||||||
|
Refer to the command line `--help` to find out which device support to be
|
||||||
|
attached to the virtual IOMMU.
|
||||||
|
|
||||||
|
Below is a simple example exposing the `virtio-blk` device as attached to the
|
||||||
|
virtual IOMMU:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cloud-hypervisor \
|
||||||
|
--cpus 1 \
|
||||||
|
--memory size=512M \
|
||||||
|
--disk path=clear-kvm.img,iommu=on \
|
||||||
|
--kernel custom-bzImage \
|
||||||
|
--cmdline "console=ttyS0 root=/dev/vda3" \
|
||||||
|
```
|
||||||
|
|
||||||
|
From a guest perspective, it is easy to verify if the device is protected by
|
||||||
|
the virtual IOMMU. Check the directories listed under
|
||||||
|
`/sys/kernel/iommu_groups`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls /sys/kernel/iommu_groups
|
||||||
|
0
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, only one IOMMU group should be created. Under this group, it is
|
||||||
|
possible to find out the b/d/f of the device(s) part of this group.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls /sys/kernel/iommu_groups/0/devices/
|
||||||
|
0000:00:03.0
|
||||||
|
```
|
||||||
|
|
||||||
|
And you can validate the device is the one we expect running `lspci`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lspci
|
||||||
|
00:00.0 Host bridge: Intel Corporation Device 0d57
|
||||||
|
00:01.0 Unassigned class [ffff]: Red Hat, Inc. Device 1057
|
||||||
|
00:02.0 Unassigned class [ffff]: Red Hat, Inc. Virtio console
|
||||||
|
00:03.0 Mass storage controller: Red Hat, Inc. Virtio block device
|
||||||
|
00:04.0 Unassigned class [ffff]: Red Hat, Inc. Virtio RNG
|
||||||
|
```
|
||||||
|
|
||||||
|
## Faster mappings
|
||||||
|
|
||||||
|
By default, the guest memory is mapped with 4k pages and no huge pages, which
|
||||||
|
causes the virtual IOMMU device to be asked for 4k mappings only. This
|
||||||
|
configuration slows down the setup of the physical IOMMU as an important number
|
||||||
|
of requests need to be issued in order to create large mappings.
|
||||||
|
|
||||||
|
One use case is even more impacted by the slowdown, the nested VFIO case. When
|
||||||
|
passing a device through a L2 guest, the VFIO driver running in L1 will update
|
||||||
|
the DMAR entries for the specific device. Because VFIO pins the entire guest
|
||||||
|
memory, this means the entire mapping of the L2 guest need to be stored into
|
||||||
|
multiple 4k mappings. Obviously, the bigger the L2 guest RAM is, the longer the
|
||||||
|
update of the mappings will last. There is an additional problem happening in
|
||||||
|
this case, if the L2 guest RAM is quite large, it will require a large number
|
||||||
|
of mappings, which might exceed the VFIO limit set on the host. The default
|
||||||
|
value is 65536, which can simply be reached with a 256MiB sized RAM.
|
||||||
|
|
||||||
|
The way to solve both problems, the slowdown and the limit being exceeded, is
|
||||||
|
to reduce the amount of requests to describe those same large mappings. This
|
||||||
|
can be achieved by using 2MiB pages, known as huge pages. By seeing the guest
|
||||||
|
RAM as larger pages, and because the virtual IOMMU device supports it, the
|
||||||
|
guest will require less mappings, which will prevent the limit from being
|
||||||
|
exceeded, but also will take less time to process them on the host. That's
|
||||||
|
how using huge pages as much as possible can speed up VM boot time.
|
||||||
|
|
||||||
|
### Basic usage
|
||||||
|
|
||||||
|
Let's look at an example of how to run a guest with huge pages.
|
||||||
|
|
||||||
|
First, make sure your system has enough pages to cover the entire guest RAM:
|
||||||
|
```bash
|
||||||
|
# This example creates 4096 hugepages
|
||||||
|
echo 4096 > /proc/sys/vm/nr_hugepages
|
||||||
|
```
|
||||||
|
|
||||||
|
Next step is simply to create the VM. Two things are important, first we want
|
||||||
|
the VM RAM to be mapped on huge pages by backing it with `/dev/hugepages`. And
|
||||||
|
second thing, we need to create some huge pages in the guest itself so they can
|
||||||
|
be consumed.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cloud-hypervisor \
|
||||||
|
--cpus 1 \
|
||||||
|
--memory size=8G,file=/dev/hugepages \
|
||||||
|
--disk path=clear-kvm.img \
|
||||||
|
--kernel custom-bzImage \
|
||||||
|
--cmdline "console=ttyS0 root=/dev/vda3 hugepagesz=2M hugepages=2048" \
|
||||||
|
--net tap=,mac=,iommu=on
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nested usage
|
||||||
|
|
||||||
|
Let's now look at the specific example of nested virtualization. In order to
|
||||||
|
reach optimized performances, the L2 guest also need to be mapped based on
|
||||||
|
huge pages. Here is how to achieve this, assuming the physical device you are
|
||||||
|
passing through is `0000:00:01.0`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cloud-hypervisor \
|
||||||
|
--cpus 1 \
|
||||||
|
--memory size=8G,file=/dev/hugepages \
|
||||||
|
--disk path=clear-kvm.img \
|
||||||
|
--kernel custom-bzImage \
|
||||||
|
--cmdline "console=ttyS0 root=/dev/vda3 kvm-intel.nested=1 vfio_iommu_type1.allow_unsafe_interrupts rw hugepagesz=2M hugepages=2048" \
|
||||||
|
--device path=/sys/bus/pci/devices/0000:00:01.0,iommu=on
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the L1 VM is running, unbind the device from the default driver in the
|
||||||
|
guest, and bind it to VFIO (it should appear as `0000:00:04.0`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo 0000:00:04.0 > /sys/bus/pci/devices/0000\:00\:04.0/driver/unbind
|
||||||
|
echo 8086 1502 > /sys/bus/pci/drivers/vfio-pci/new_id
|
||||||
|
```
|
||||||
|
|
||||||
|
Last thing is to start the L2 guest with the huge pages memory backend.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cloud-hypervisor \
|
||||||
|
--cpus 1 \
|
||||||
|
--memory size=4G,file=/dev/hugepages \
|
||||||
|
--disk path=clear-kvm.img \
|
||||||
|
--kernel custom-bzImage \
|
||||||
|
--cmdline "console=ttyS0 root=/dev/vda3" \
|
||||||
|
--device path=/sys/bus/pci/devices/0000:00:04.0
|
||||||
|
```
|
||||||
201
docs/networking.md
Normal file
201
docs/networking.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
Use one `--net` command-line argument from cloud-hypervisor to specify the emulation of one or more virtual NIC's. The example below instructs cloud-hypervisor to emulate for instance 2 virtual NIC's:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cloud-hypervisor \
|
||||||
|
--cpus 4 \
|
||||||
|
--memory "size=512M" \
|
||||||
|
--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,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 |
|
||||||
|
| 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
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Configure the tap devices
|
||||||
|
|
||||||
|
After starting cloud-hypervisor as shown above, 2 tap devices with state down will become available at the host:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@host:~# ip link show ich0
|
||||||
|
78: ich0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||||
|
link/ether 72:54:12:ff:ce:6f brd ff:ff:ff:ff:ff:ff
|
||||||
|
root@host:~# ip link show ich1
|
||||||
|
79: ich1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||||
|
link/ether 06:7a:fc:1b:9a:67 brd ff:ff:ff:ff:ff:ff
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the tap devices to up state:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@host:~# ip link set up ich0
|
||||||
|
root@host:~# ip link set up ich1
|
||||||
|
|
||||||
|
root@host:~# ip link show ich0
|
||||||
|
78: ich0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
|
||||||
|
link/ether 72:54:12:ff:ce:6f brd ff:ff:ff:ff:ff:ff
|
||||||
|
root@host:~# ip link show ich1
|
||||||
|
79: ich1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
|
||||||
|
link/ether 06:7a:fc:1b:9a:67 brd ff:ff:ff:ff:ff:ff
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connect tap devices
|
||||||
|
|
||||||
|
Different networking models can be used to provide external connectivity. In this example we will
|
||||||
|
use 2 linux bridges emulating 2 different networks. The integration bridge (ich-int) in this example will also be used
|
||||||
|
for external connectivity.
|
||||||
|
|
||||||
|
Create the bridges and connect the cloud-hypervisor tap devices to the bridges:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@host:~# brctl addbr ich-int
|
||||||
|
root@host:~# brctl addbr ich-dpl
|
||||||
|
root@host:~# ip link set up ich-int
|
||||||
|
root@host:~# ip link set up ich-dpl
|
||||||
|
root@host:~# brctl addif ich-int ich0
|
||||||
|
root@host:~# brctl addif ich-dpl ich1
|
||||||
|
root@host:~# brctl show
|
||||||
|
bridge name bridge id STP enabled interfaces
|
||||||
|
ich-dpl 8000.067afc1b9a67 no ich1
|
||||||
|
ich-int 8000.725412ffce6f no ich0
|
||||||
|
```
|
||||||
|
This completes the layer 2 wiring: The cloud-hypervisor is now connected to the hypervisor host via the 2 linux bridges.
|
||||||
|
|
||||||
|
## IP (Layer 3) provisioning
|
||||||
|
|
||||||
|
### Hypervisor host
|
||||||
|
|
||||||
|
On the hypervisor host add the network gateway IP address of each network to the 2 linux bridges:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@host:~# ip addr add 192.168.4.1/24 dev ich-int
|
||||||
|
root@host:~# ip addr add 10.0.1.1/24 dev ich-dpl
|
||||||
|
```
|
||||||
|
The routing table of the hypervisor host should now also have corresponding routing entries:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@host:~# route -n
|
||||||
|
Kernel IP routing table
|
||||||
|
Destination Gateway Genmask Flags Metric Ref Use Iface
|
||||||
|
0.0.0.0 192.168.178.1 0.0.0.0 UG 600 0 0 wlan1
|
||||||
|
10.0.1.0 0.0.0.0 255.255.255.0 U 0 0 0 ich-dpl
|
||||||
|
192.168.4.0 0.0.0.0 255.255.255.0 U 0 0 0 ich-int
|
||||||
|
192.168.178.0 0.0.0.0 255.255.255.0 U 600 0 0 wlan1
|
||||||
|
```
|
||||||
|
### Virtual Machine
|
||||||
|
|
||||||
|
Within the virtual machine set the vNIC's to up state and provision the corresponding IP addresses on the 2 vNIC's. The steps outlined below use the ip command as an example. Alternative distribution specific procedures can also apply.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@guest:~# ip link set up enp0s2
|
||||||
|
root@guest:~# ip link set up enp0s3
|
||||||
|
root@guest:~# ip addr add 192.168.4.2/24 dev enp0s2
|
||||||
|
root@guest:~# ip addr add 10.0.1.2/24 dev enp0s3
|
||||||
|
```
|
||||||
|
|
||||||
|
IP connectivity between the virtual machine and the hypervisor-host can be verified by sending
|
||||||
|
ICMP requests to the hypervisor-host for the gateway IP address from within the virtual machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@guest:~# ping 192.168.4.1
|
||||||
|
PING 192.168.4.1 (192.168.4.1) 56(84) bytes of data.
|
||||||
|
64 bytes from 192.168.4.1: icmp_seq=1 ttl=64 time=0.456 ms
|
||||||
|
64 bytes from 192.168.4.1: icmp_seq=2 ttl=64 time=0.226 ms
|
||||||
|
root@guest:~# ping 10.0.1.1
|
||||||
|
PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data.
|
||||||
|
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.449 ms
|
||||||
|
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=0.393 ms
|
||||||
|
```
|
||||||
|
|
||||||
|
The connection can now be used for instance to log into the virtual machine with
|
||||||
|
ssh under the precondition that the machine has an ssh daemon provisioned:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@host:~# ssh root@192.168.4.2
|
||||||
|
The authenticity of host '192.168.4.2 (192.168.4.2)' can't be established.
|
||||||
|
ECDSA key fingerprint is SHA256:qNAUmTtDMW9pNuZARkpLQhfw+Yc1tqUDBrQp7aZGSjw.
|
||||||
|
Are you sure you want to continue connecting (yes/no)? yes
|
||||||
|
Warning: Permanently added '192.168.4.2' (ECDSA) to the list of known hosts.
|
||||||
|
root@192.168.4.2's password:
|
||||||
|
Linux cloud-hypervisor 5.2.0 #2 SMP Thu Jul 11 08:08:16 CEST 2019 x86_64
|
||||||
|
|
||||||
|
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
|
||||||
|
permitted by applicable law.
|
||||||
|
|
||||||
|
|
||||||
|
Last login: Fri Jul 12 13:27:56 2019 from 192.168.4.1
|
||||||
|
root@guest:~#
|
||||||
|
```
|
||||||
|
|
||||||
|
## Internet connectivity
|
||||||
|
|
||||||
|
To enable internet connectivity a default gw and a nameserver has to be set within
|
||||||
|
the virtual machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@guest:~# ip route add default via 192.168.4.1
|
||||||
|
root@guest:~# cat /etc/resolv.conf
|
||||||
|
options timeout:2
|
||||||
|
domain vallis.nl
|
||||||
|
search vallis.nl
|
||||||
|
nameserver 192.168.178.1
|
||||||
|
```
|
||||||
|
|
||||||
|
make sure that the default gateway of the hypervisor host (in this example host 192.168.178.1 which is an adsl router) has an entry in the routing table for the 192.168.4.0/24 network otherwise IP connectivity will not work.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
root@guest:~# nslookup ftp.nl.debian.org
|
||||||
|
Server: 192.168.178.1
|
||||||
|
Address: 192.168.178.1#53
|
||||||
|
|
||||||
|
Non-authoritative answer:
|
||||||
|
cdn-fastly.deb.debian.org canonical name = prod.debian.map.fastly.net.
|
||||||
|
Name: prod.debian.map.fastly.net
|
||||||
|
Address: 151.101.36.204
|
||||||
|
|
||||||
|
root@guest:~# apt-get update
|
||||||
|
Ign:1 http://cdn-fastly.deb.debian.org/debian stretch InRelease
|
||||||
|
Get:2 http://cdn-fastly.deb.debian.org/debian stretch Release [118 kB]
|
||||||
|
Get:3 http://cdn-fastly.deb.debian.org/debian stretch Release.gpg [2434 B]
|
||||||
|
Fetched 120 kB in 1s (110 kB/s)
|
||||||
|
```
|
||||||
@@ -66,13 +66,13 @@ takes the device's sysfs path as an argument. In our example it is
|
|||||||
```
|
```
|
||||||
./target/debug/cloud-hypervisor \
|
./target/debug/cloud-hypervisor \
|
||||||
--kernel ~/vmlinux \
|
--kernel ~/vmlinux \
|
||||||
--disk ~/clear-29160-kvm.img \
|
--disk path=~/clear-29160-kvm.img \
|
||||||
--console off \
|
--console off \
|
||||||
--serial tty \
|
--serial tty \
|
||||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||||
--cpus 4 \
|
--cpus 4 \
|
||||||
--memory size=512M \
|
--memory size=512M \
|
||||||
--device /sys/bus/pci/devices/0000:01:00.0/
|
--device path=/sys/bus/pci/devices/0000:01:00.0/
|
||||||
```
|
```
|
||||||
|
|
||||||
The guest kernel will then detect the card reader on its PCI bus and provided
|
The guest kernel will then detect the card reader on its PCI bus and provided
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ version = "0.1.0"
|
|||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
vmm-sys-util = "0.4.0"
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
|||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = ">=0.2.39"
|
libc = "0.2.66"
|
||||||
rand = ">=0.6.5"
|
rand = "0.7.3"
|
||||||
serde = ">=1.0.27"
|
serde = "1.0.104"
|
||||||
|
vmm-sys-util = "0.4.0"
|
||||||
|
|
||||||
net_gen = { path = "../net_gen" }
|
net_gen = { path = "../net_gen" }
|
||||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lazy_static = ">=1.1.0"
|
lazy_static = "1.4.0"
|
||||||
pnet = "=0.22.0"
|
pnet = "0.25.0"
|
||||||
serde_json = ">=1.0.9"
|
serde_json = "1.0.48"
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
// found in the THIRD-PARTY file.
|
// found in the THIRD-PARTY file.
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
|
|
||||||
use serde::de::{Deserialize, Deserializer, Error};
|
use serde::de::{Deserialize, Deserializer, Error};
|
||||||
@@ -19,23 +21,31 @@ pub struct MacAddr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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, io::Error>
|
||||||
pub fn parse_str<S>(s: &S) -> Result<MacAddr, &str>
|
|
||||||
where
|
where
|
||||||
S: AsRef<str> + ?Sized,
|
S: AsRef<str> + ?Sized,
|
||||||
{
|
{
|
||||||
let v: Vec<&str> = s.as_ref().split(':').collect();
|
let v: Vec<&str> = s.as_ref().split(':').collect();
|
||||||
let mut bytes = [0u8; MAC_ADDR_LEN];
|
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 {
|
if v.len() != MAC_ADDR_LEN {
|
||||||
return Err(s.as_ref());
|
return common_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..MAC_ADDR_LEN {
|
for i in 0..MAC_ADDR_LEN {
|
||||||
if v[i].len() != 2 {
|
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 })
|
Ok(MacAddr { bytes })
|
||||||
@@ -66,14 +76,6 @@ impl MacAddr {
|
|||||||
&self.bytes
|
&self.bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_string(self) -> String {
|
|
||||||
let b = &self.bytes;
|
|
||||||
format!(
|
|
||||||
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
|
|
||||||
b[0], b[1], b[2], b[3], b[4], b[5]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn local_random() -> MacAddr {
|
pub fn local_random() -> MacAddr {
|
||||||
// Generate a fully random MAC
|
// Generate a fully random MAC
|
||||||
let mut random_bytes = rand::thread_rng().gen::<[u8; MAC_ADDR_LEN]>();
|
let mut random_bytes = rand::thread_rng().gen::<[u8; MAC_ADDR_LEN]>();
|
||||||
@@ -87,6 +89,17 @@ impl MacAddr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MacAddr {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let b = &self.bytes;
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
|
||||||
|
b[0], b[1], b[2], b[3], b[4], b[5]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Serialize for MacAddr {
|
impl Serialize for MacAddr {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
@@ -102,7 +115,8 @@ impl<'de> Deserialize<'de> for MacAddr {
|
|||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let s = String::deserialize(deserializer)?;
|
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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ pub type Result<T> = ::std::result::Result<T, Error>;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Tap {
|
pub struct Tap {
|
||||||
tap_file: File,
|
tap_file: File,
|
||||||
if_name: [u8; 16usize],
|
if_name: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Tap {
|
impl PartialEq for Tap {
|
||||||
@@ -49,6 +49,15 @@ impl PartialEq for Tap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::clone::Clone for Tap {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Tap {
|
||||||
|
tap_file: self.tap_file.try_clone().unwrap(),
|
||||||
|
if_name: self.if_name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns a byte vector representing the contents of a null terminated C string which
|
// Returns a byte vector representing the contents of a null terminated C string which
|
||||||
// contains if_name.
|
// contains if_name.
|
||||||
fn build_terminated_if_name(if_name: &str) -> Result<Vec<u8>> {
|
fn build_terminated_if_name(if_name: &str) -> Result<Vec<u8>> {
|
||||||
@@ -69,7 +78,7 @@ fn build_terminated_if_name(if_name: &str) -> Result<Vec<u8>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Tap {
|
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 terminated_if_name = build_terminated_if_name(if_name)?;
|
||||||
|
|
||||||
let fd = unsafe {
|
let fd = unsafe {
|
||||||
@@ -98,6 +107,9 @@ impl Tap {
|
|||||||
name_slice.copy_from_slice(terminated_if_name.as_slice());
|
name_slice.copy_from_slice(terminated_if_name.as_slice());
|
||||||
*ifru_flags =
|
*ifru_flags =
|
||||||
(net_gen::IFF_TAP | net_gen::IFF_NO_PI | net_gen::IFF_VNET_HDR) as c_short;
|
(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
|
// ioctl is safe since we call it with a valid tap fd and check the return
|
||||||
@@ -108,16 +120,19 @@ impl Tap {
|
|||||||
return Err(Error::CreateTap(IoError::last_os_error()));
|
return Err(Error::CreateTap(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.
|
// Safe since only the name is accessed, and it's cloned out.
|
||||||
Ok(Tap {
|
Ok(Tap {
|
||||||
tap_file: tuntap,
|
tap_file: tuntap,
|
||||||
if_name: unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() },
|
if_name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new tap interface.
|
/// Create a new tap interface.
|
||||||
pub fn new() -> Result<Tap> {
|
pub fn new(num_queue_pairs: usize) -> Result<Tap> {
|
||||||
Self::open_named("vmtap%d")
|
Self::open_named("vmtap%d", num_queue_pairs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the host-side IP address for the tap interface.
|
/// Set the host-side IP address for the tap interface.
|
||||||
@@ -223,11 +238,16 @@ impl Tap {
|
|||||||
// in a single-field union.
|
// in a single-field union.
|
||||||
unsafe {
|
unsafe {
|
||||||
let ifrn_name = ifreq.ifr_ifrn.ifrn_name.as_mut();
|
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
|
ifreq
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_if_name(&self) -> Vec<u8> {
|
||||||
|
self.if_name.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for Tap {
|
impl Read for Tap {
|
||||||
@@ -396,7 +416,9 @@ mod tests {
|
|||||||
// For a given interface name, this returns a tuple that contains the MAC address of the
|
// For a given interface name, this returns a tuple that contains the MAC address of the
|
||||||
// interface, an object that can be used to send Ethernet frames, and a receiver of
|
// interface, an object that can be used to send Ethernet frames, and a receiver of
|
||||||
// Ethernet frames arriving at the specified interface.
|
// Ethernet frames arriving at the specified interface.
|
||||||
fn pnet_get_mac_tx_rx(ifname: String) -> (MacAddr, Box<DataLinkSender>, Box<DataLinkReceiver>) {
|
fn pnet_get_mac_tx_rx(
|
||||||
|
ifname: String,
|
||||||
|
) -> (MacAddr, Box<dyn DataLinkSender>, Box<dyn DataLinkReceiver>) {
|
||||||
let interface_name_matches = |iface: &NetworkInterface| iface.name == ifname;
|
let interface_name_matches = |iface: &NetworkInterface| iface.name == ifname;
|
||||||
|
|
||||||
// Find the network interface with the provided name.
|
// Find the network interface with the provided name.
|
||||||
@@ -412,7 +434,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tap_create() {
|
fn test_tap_create() {
|
||||||
let t = Tap::new().unwrap();
|
let t = Tap::new(1).unwrap();
|
||||||
println!("created tap: {:?}", t);
|
println!("created tap: {:?}", t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,7 +446,7 @@ mod tests {
|
|||||||
// the end of the function.
|
// the end of the function.
|
||||||
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
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 ip_addr: net::Ipv4Addr = (*tap_ip_guard).parse().unwrap();
|
||||||
let netmask: net::Ipv4Addr = SUBNET_MASK.parse().unwrap();
|
let netmask: net::Ipv4Addr = SUBNET_MASK.parse().unwrap();
|
||||||
|
|
||||||
@@ -437,21 +459,21 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_set_options() {
|
fn test_set_options() {
|
||||||
// This line will fail to provide an initialized FD if the test is not run as root.
|
// 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_vnet_hdr_size(16).unwrap();
|
||||||
tap.set_offload(0).unwrap();
|
tap.set_offload(0).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tap_enable() {
|
fn test_tap_enable() {
|
||||||
let tap = Tap::new().unwrap();
|
let tap = Tap::new(1).unwrap();
|
||||||
let ret = tap.enable();
|
let ret = tap.enable();
|
||||||
assert!(ret.is_ok());
|
assert!(ret.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tap_get_ifreq() {
|
fn test_tap_get_ifreq() {
|
||||||
let tap = Tap::new().unwrap();
|
let tap = Tap::new(1).unwrap();
|
||||||
let ret = tap.get_ifreq();
|
let ret = tap.get_ifreq();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"__BindgenUnionField",
|
"__BindgenUnionField",
|
||||||
@@ -461,7 +483,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_raw_fd() {
|
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());
|
assert_eq!(tap.as_raw_fd(), tap.tap_file.as_raw_fd());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,7 +491,7 @@ mod tests {
|
|||||||
fn test_read() {
|
fn test_read() {
|
||||||
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
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_ip_addr((*tap_ip_guard).parse().unwrap()).unwrap();
|
||||||
tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap();
|
tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap();
|
||||||
tap.enable().unwrap();
|
tap.enable().unwrap();
|
||||||
@@ -530,7 +552,7 @@ mod tests {
|
|||||||
fn test_write() {
|
fn test_write() {
|
||||||
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
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_ip_addr((*tap_ip_guard).parse().unwrap()).unwrap();
|
||||||
tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap();
|
tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap();
|
||||||
tap.enable().unwrap();
|
tap.enable().unwrap();
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vm-allocator = { path = "../vm-allocator" }
|
vm-allocator = { path = "../vm-allocator" }
|
||||||
byteorder = "*"
|
byteorder = "1.3.4"
|
||||||
devices = { path = "../devices" }
|
devices = { path = "../devices" }
|
||||||
kvm-bindings = "0.1"
|
libc = "0.2.66"
|
||||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
log = "0.4.8"
|
||||||
libc = ">=0.2.39"
|
vm-device = { path = "../vm-device" }
|
||||||
log = "*"
|
|
||||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
|
||||||
|
|||||||
146
pci/src/bus.rs
146
pci/src/bus.rs
@@ -5,12 +5,13 @@
|
|||||||
use crate::configuration::{
|
use crate::configuration::{
|
||||||
PciBarRegionType, PciBridgeSubclass, PciClassCode, PciConfiguration, PciHeaderType,
|
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 byteorder::{ByteOrder, LittleEndian};
|
||||||
use devices::BusDevice;
|
use devices::BusDevice;
|
||||||
use std;
|
use std;
|
||||||
use std::sync::Arc;
|
use std::any::Any;
|
||||||
use std::sync::Mutex;
|
use std::ops::DerefMut;
|
||||||
|
use std::sync::{Arc, Mutex, Weak};
|
||||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||||
|
|
||||||
const VENDOR_ID_INTEL: u16 = 0x8086;
|
const VENDOR_ID_INTEL: u16 = 0x8086;
|
||||||
@@ -66,35 +67,39 @@ impl PciDevice for PciRoot {
|
|||||||
self.config.write_config_register(reg_idx, offset, data);
|
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)
|
self.config.read_reg(reg_idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_any(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PciConfigIo {
|
pub struct PciBus {
|
||||||
/// Devices attached to this bus.
|
/// Devices attached to this bus.
|
||||||
/// Device 0 is host bridge.
|
/// Device 0 is host bridge.
|
||||||
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
|
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
|
||||||
/// Config space register.
|
device_reloc: Weak<dyn DeviceRelocation>,
|
||||||
config_address: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PciConfigIo {
|
impl PciBus {
|
||||||
pub fn new(pci_root: PciRoot) -> Self {
|
pub fn new(pci_root: PciRoot, device_reloc: Weak<dyn DeviceRelocation>) -> Self {
|
||||||
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
|
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
|
||||||
|
|
||||||
devices.push(Arc::new(Mutex::new(pci_root)));
|
devices.push(Arc::new(Mutex::new(pci_root)));
|
||||||
|
|
||||||
PciConfigIo {
|
PciBus {
|
||||||
devices,
|
devices,
|
||||||
config_address: 0,
|
device_reloc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_mapping(
|
pub fn register_mapping(
|
||||||
&self,
|
&self,
|
||||||
dev: Arc<Mutex<dyn BusDevice>>,
|
dev: Arc<Mutex<dyn BusDevice>>,
|
||||||
io_bus: &mut devices::Bus,
|
io_bus: &devices::Bus,
|
||||||
mmio_bus: &mut devices::Bus,
|
mmio_bus: &devices::Bus,
|
||||||
bars: Vec<(GuestAddress, GuestUsize, PciBarRegionType)>,
|
bars: Vec<(GuestAddress, GuestUsize, PciBarRegionType)>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (address, size, type_) in bars {
|
for (address, size, type_) in bars {
|
||||||
@@ -119,13 +124,32 @@ impl PciConfigIo {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn next_device_id(&self) -> u32 {
|
||||||
|
self.devices.len() as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PciConfigIo {
|
||||||
|
/// Config space register.
|
||||||
|
config_address: u32,
|
||||||
|
pci_bus: Arc<Mutex<PciBus>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PciConfigIo {
|
||||||
|
pub fn new(pci_bus: Arc<Mutex<PciBus>>) -> Self {
|
||||||
|
PciConfigIo {
|
||||||
|
pci_bus,
|
||||||
|
config_address: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn config_space_read(&self) -> u32 {
|
pub fn config_space_read(&self) -> u32 {
|
||||||
let enabled = (self.config_address & 0x8000_0000) != 0;
|
let enabled = (self.config_address & 0x8000_0000) != 0;
|
||||||
if !enabled {
|
if !enabled {
|
||||||
return 0xffff_ffff;
|
return 0xffff_ffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (bus, device, _function, register) =
|
let (bus, device, function, register) =
|
||||||
parse_config_address(self.config_address & !0x8000_0000);
|
parse_config_address(self.config_address & !0x8000_0000);
|
||||||
|
|
||||||
// Only support one bus.
|
// Only support one bus.
|
||||||
@@ -133,9 +157,19 @@ impl PciConfigIo {
|
|||||||
return 0xffff_ffff;
|
return 0xffff_ffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.devices.get(device).map_or(0xffff_ffff, |d| {
|
// Don't support multi-function devices.
|
||||||
d.lock().unwrap().read_config_register(register)
|
if function > 0 {
|
||||||
})
|
return 0xffff_ffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pci_bus
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.devices
|
||||||
|
.get(device)
|
||||||
|
.map_or(0xffff_ffff, |d| {
|
||||||
|
d.lock().unwrap().read_config_register(register)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config_space_write(&mut self, offset: u64, data: &[u8]) {
|
pub fn config_space_write(&mut self, offset: u64, data: &[u8]) {
|
||||||
@@ -156,10 +190,26 @@ impl PciConfigIo {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(d) = self.devices.get(device) {
|
let pci_bus = self.pci_bus.lock().unwrap();
|
||||||
d.lock()
|
if let Some(d) = pci_bus.devices.get(device) {
|
||||||
.unwrap()
|
let mut device = d.lock().unwrap();
|
||||||
.write_config_register(register, offset, data);
|
|
||||||
|
// 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.upgrade().unwrap().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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +237,8 @@ impl BusDevice for PciConfigIo {
|
|||||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
// `offset` is relative to 0xcf8
|
// `offset` is relative to 0xcf8
|
||||||
let value = match offset {
|
let value = match offset {
|
||||||
0...3 => self.config_address,
|
0..=3 => self.config_address,
|
||||||
4...7 => self.config_space_read(),
|
4..=7 => self.config_space_read(),
|
||||||
_ => 0xffff_ffff,
|
_ => 0xffff_ffff,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -209,8 +259,8 @@ impl BusDevice for PciConfigIo {
|
|||||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||||
// `offset` is relative to 0xcf8
|
// `offset` is relative to 0xcf8
|
||||||
match offset {
|
match offset {
|
||||||
o @ 0...3 => self.set_config_address(o, data),
|
o @ 0..=3 => self.set_config_address(o, data),
|
||||||
o @ 4...7 => self.config_space_write(o - 4, data),
|
o @ 4..=7 => self.config_space_write(o - 4, data),
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -218,17 +268,12 @@ impl BusDevice for PciConfigIo {
|
|||||||
|
|
||||||
/// Emulates PCI memory-mapped configuration access mechanism.
|
/// Emulates PCI memory-mapped configuration access mechanism.
|
||||||
pub struct PciConfigMmio {
|
pub struct PciConfigMmio {
|
||||||
/// Devices attached to this bus.
|
pci_bus: Arc<Mutex<PciBus>>,
|
||||||
/// Device 0 is host bridge.
|
|
||||||
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PciConfigMmio {
|
impl PciConfigMmio {
|
||||||
pub fn new(pci_root: PciRoot) -> Self {
|
pub fn new(pci_bus: Arc<Mutex<PciBus>>) -> Self {
|
||||||
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
|
PciConfigMmio { pci_bus }
|
||||||
|
|
||||||
devices.push(Arc::new(Mutex::new(pci_root)));
|
|
||||||
PciConfigMmio { devices }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_space_read(&self, config_address: u32) -> u32 {
|
fn config_space_read(&self, config_address: u32) -> u32 {
|
||||||
@@ -239,9 +284,14 @@ impl PciConfigMmio {
|
|||||||
return 0xffff_ffff;
|
return 0xffff_ffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.devices.get(device).map_or(0xffff_ffff, |d| {
|
self.pci_bus
|
||||||
d.lock().unwrap().read_config_register(register)
|
.lock()
|
||||||
})
|
.unwrap()
|
||||||
|
.devices
|
||||||
|
.get(device)
|
||||||
|
.map_or(0xffff_ffff, |d| {
|
||||||
|
d.lock().unwrap().read_config_register(register)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_space_write(&mut self, config_address: u32, offset: u64, data: &[u8]) {
|
fn config_space_write(&mut self, config_address: u32, offset: u64, data: &[u8]) {
|
||||||
@@ -256,10 +306,26 @@ impl PciConfigMmio {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(d) = self.devices.get(device) {
|
let pci_bus = self.pci_bus.lock().unwrap();
|
||||||
d.lock()
|
if let Some(d) = pci_bus.devices.get(device) {
|
||||||
.unwrap()
|
let mut device = d.lock().unwrap();
|
||||||
.write_config_register(register, offset, data);
|
|
||||||
|
// 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.upgrade().unwrap().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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::device::BarReprogrammingParams;
|
||||||
use crate::{MsixConfig, PciInterruptPin};
|
use crate::{MsixConfig, PciInterruptPin};
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
@@ -14,8 +15,10 @@ const NUM_CONFIGURATION_REGISTERS: usize = 64;
|
|||||||
const STATUS_REG: usize = 1;
|
const STATUS_REG: usize = 1;
|
||||||
const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
|
const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
|
||||||
const BAR0_REG: usize = 4;
|
const BAR0_REG: usize = 4;
|
||||||
|
const ROM_BAR_REG: usize = 12;
|
||||||
const BAR_IO_ADDR_MASK: u32 = 0xffff_fffc;
|
const BAR_IO_ADDR_MASK: u32 = 0xffff_fffc;
|
||||||
const BAR_MEM_ADDR_MASK: u32 = 0xffff_fff0;
|
const BAR_MEM_ADDR_MASK: u32 = 0xffff_fff0;
|
||||||
|
const ROM_BAR_ADDR_MASK: u32 = 0xffff_f800;
|
||||||
const NUM_BAR_REGS: usize = 6;
|
const NUM_BAR_REGS: usize = 6;
|
||||||
const CAPABILITY_LIST_HEAD_OFFSET: usize = 0x34;
|
const CAPABILITY_LIST_HEAD_OFFSET: usize = 0x34;
|
||||||
const FIRST_CAPABILITY_OFFSET: usize = 0x40;
|
const FIRST_CAPABILITY_OFFSET: usize = 0x40;
|
||||||
@@ -247,8 +250,13 @@ pub trait PciCapability {
|
|||||||
pub struct PciConfiguration {
|
pub struct PciConfiguration {
|
||||||
registers: [u32; NUM_CONFIGURATION_REGISTERS],
|
registers: [u32; NUM_CONFIGURATION_REGISTERS],
|
||||||
writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register.
|
writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register.
|
||||||
|
bar_addr: [u32; NUM_BAR_REGS],
|
||||||
bar_size: [u32; NUM_BAR_REGS],
|
bar_size: [u32; NUM_BAR_REGS],
|
||||||
bar_used: [bool; 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.
|
// Contains the byte offset and size of the last capability.
|
||||||
last_capability: Option<(usize, usize)>,
|
last_capability: Option<(usize, usize)>,
|
||||||
msix_cap_reg_idx: Option<usize>,
|
msix_cap_reg_idx: Option<usize>,
|
||||||
@@ -256,7 +264,7 @@ pub struct PciConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// See pci_regs.h in kernel
|
/// See pci_regs.h in kernel
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub enum PciBarRegionType {
|
pub enum PciBarRegionType {
|
||||||
Memory32BitRegion = 0,
|
Memory32BitRegion = 0,
|
||||||
IORegion = 0x01,
|
IORegion = 0x01,
|
||||||
@@ -289,6 +297,10 @@ pub enum Error {
|
|||||||
CapabilityEmpty,
|
CapabilityEmpty,
|
||||||
CapabilityLengthInvalid(usize),
|
CapabilityLengthInvalid(usize),
|
||||||
CapabilitySpaceFull(usize),
|
CapabilitySpaceFull(usize),
|
||||||
|
RomBarAddressInvalid(u64, u64),
|
||||||
|
RomBarInUse(usize),
|
||||||
|
RomBarInvalid(usize),
|
||||||
|
RomBarSizeInvalid(u64),
|
||||||
}
|
}
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
@@ -312,6 +324,10 @@ impl Display for Error {
|
|||||||
CapabilityEmpty => write!(f, "empty capabilities are invalid"),
|
CapabilityEmpty => write!(f, "empty capabilities are invalid"),
|
||||||
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
|
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
|
||||||
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
|
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
|
||||||
|
RomBarAddressInvalid(a, s) => write!(f, "address {} size {} too big", a, s),
|
||||||
|
RomBarInUse(b) => write!(f, "rom bar {} already used", b),
|
||||||
|
RomBarInvalid(b) => write!(f, "rom bar {} invalid, max {}", b, NUM_BAR_REGS - 1),
|
||||||
|
RomBarSizeInvalid(s) => write!(f, "rom bar address {} not a power of two", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,6 +347,7 @@ impl PciConfiguration {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS];
|
let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS];
|
||||||
let mut writable_bits = [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];
|
let bar_size = [0u32; NUM_BAR_REGS];
|
||||||
registers[0] = u32::from(device_id) << 16 | u32::from(vendor_id);
|
registers[0] = u32::from(device_id) << 16 | u32::from(vendor_id);
|
||||||
// TODO(dverkamp): Status should be write-1-to-clear
|
// TODO(dverkamp): Status should be write-1-to-clear
|
||||||
@@ -360,8 +377,13 @@ impl PciConfiguration {
|
|||||||
PciConfiguration {
|
PciConfiguration {
|
||||||
registers,
|
registers,
|
||||||
writable_bits,
|
writable_bits,
|
||||||
|
bar_addr,
|
||||||
bar_size,
|
bar_size,
|
||||||
bar_used: [false; NUM_BAR_REGS],
|
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,
|
last_capability: None,
|
||||||
msix_cap_reg_idx: None,
|
msix_cap_reg_idx: None,
|
||||||
msix_config,
|
msix_config,
|
||||||
@@ -376,12 +398,20 @@ impl PciConfiguration {
|
|||||||
/// Writes a 32bit register to `reg_idx` in the register map.
|
/// Writes a 32bit register to `reg_idx` in the register map.
|
||||||
pub fn write_reg(&mut self, reg_idx: usize, value: u32) {
|
pub fn write_reg(&mut self, reg_idx: usize, value: u32) {
|
||||||
let mut mask = self.writable_bits[reg_idx];
|
let mut mask = self.writable_bits[reg_idx];
|
||||||
|
|
||||||
if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS {
|
if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS {
|
||||||
// Handle very specific case where the BAR is being written with
|
// Handle very specific case where the BAR is being written with
|
||||||
// all 1's to retrieve the BAR size on next BAR reading.
|
// all 1's to retrieve the BAR size during next BAR reading.
|
||||||
if value == 0xffff_ffff {
|
if value == 0xffff_ffff {
|
||||||
mask = self.bar_size[reg_idx - 4];
|
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) {
|
if let Some(r) = self.registers.get_mut(reg_idx) {
|
||||||
@@ -480,6 +510,7 @@ impl PciConfiguration {
|
|||||||
|
|
||||||
self.registers[bar_idx + 1] = (config.addr >> 32) as u32;
|
self.registers[bar_idx + 1] = (config.addr >> 32) as u32;
|
||||||
self.writable_bits[bar_idx + 1] = 0xffff_ffff;
|
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_size[config.reg_idx + 1] = (config.size >> 32) as u32;
|
||||||
self.bar_used[config.reg_idx + 1] = true;
|
self.bar_used[config.reg_idx + 1] = true;
|
||||||
}
|
}
|
||||||
@@ -495,24 +526,57 @@ impl PciConfiguration {
|
|||||||
|
|
||||||
self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
|
self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
|
||||||
self.writable_bits[bar_idx] = mask;
|
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_size[config.reg_idx] = config.size as u32;
|
||||||
self.bar_used[config.reg_idx] = true;
|
self.bar_used[config.reg_idx] = true;
|
||||||
|
self.bar_type[config.reg_idx] = Some(config.region_type);
|
||||||
Ok(config.reg_idx)
|
Ok(config.reg_idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the address of the given 32 bits BAR region.
|
/// Adds rom expansion BAR.
|
||||||
pub fn get_bar32_addr(&self, bar_num: usize) -> u32 {
|
pub fn add_pci_rom_bar(&mut self, config: &PciBarConfiguration, active: u32) -> Result<usize> {
|
||||||
let bar_idx = BAR0_REG + bar_num;
|
if self.rom_bar_used {
|
||||||
|
return Err(Error::RomBarInUse(config.reg_idx));
|
||||||
|
}
|
||||||
|
|
||||||
self.registers[bar_idx] & BAR_MEM_ADDR_MASK
|
if config.size.count_ones() != 1 {
|
||||||
|
return Err(Error::RomBarSizeInvalid(config.size));
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.reg_idx != ROM_BAR_REG {
|
||||||
|
return Err(Error::RomBarInvalid(config.reg_idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
let end_addr = config
|
||||||
|
.addr
|
||||||
|
.checked_add(config.size - 1)
|
||||||
|
.ok_or_else(|| Error::RomBarAddressInvalid(config.addr, config.size))?;
|
||||||
|
|
||||||
|
if end_addr > u64::from(u32::max_value()) {
|
||||||
|
return Err(Error::RomBarAddressInvalid(config.addr, config.size));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 64 bits BAR region.
|
/// Returns the address of the given BAR region.
|
||||||
pub fn get_bar64_addr(&self, bar_num: usize) -> u64 {
|
pub fn get_bar_addr(&self, bar_num: usize) -> u64 {
|
||||||
let bar_idx = BAR0_REG + bar_num;
|
let bar_idx = BAR0_REG + bar_num;
|
||||||
|
|
||||||
u64::from(self.registers[bar_idx] & BAR_MEM_ADDR_MASK)
|
let mut addr = u64::from(self.bar_addr[bar_num] & self.writable_bits[bar_idx]);
|
||||||
| (u64::from(self.registers[bar_idx + 1]) << 32)
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures the IRQ line and pin used by this device.
|
/// Configures the IRQ line and pin used by this device.
|
||||||
@@ -598,6 +662,119 @@ impl PciConfiguration {
|
|||||||
pub fn read_config_register(&self, reg_idx: usize) -> u32 {
|
pub fn read_config_register(&self, reg_idx: usize) -> u32 {
|
||||||
self.read_reg(reg_idx)
|
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 {
|
impl Default for PciBarConfiguration {
|
||||||
@@ -663,8 +840,6 @@ mod tests {
|
|||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Copy, Default)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct TestCap {
|
struct TestCap {
|
||||||
_vndr: u8,
|
|
||||||
_next: u8,
|
|
||||||
len: u8,
|
len: u8,
|
||||||
foo: u8,
|
foo: u8,
|
||||||
}
|
}
|
||||||
@@ -683,7 +858,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn add_capability() {
|
fn add_capability() {
|
||||||
let mut cfg = PciConfiguration::new(
|
let mut cfg = PciConfiguration::new(
|
||||||
0x1234,
|
0x1234,
|
||||||
@@ -698,18 +872,11 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Add two capabilities with different contents.
|
// Add two capabilities with different contents.
|
||||||
let cap1 = TestCap {
|
let cap1 = TestCap { len: 4, foo: 0xAA };
|
||||||
_vndr: 0,
|
|
||||||
_next: 0,
|
|
||||||
len: 4,
|
|
||||||
foo: 0xAA,
|
|
||||||
};
|
|
||||||
let cap1_offset = cfg.add_capability(&cap1).unwrap();
|
let cap1_offset = cfg.add_capability(&cap1).unwrap();
|
||||||
assert_eq!(cap1_offset % 4, 0);
|
assert_eq!(cap1_offset % 4, 0);
|
||||||
|
|
||||||
let cap2 = TestCap {
|
let cap2 = TestCap {
|
||||||
_vndr: 0,
|
|
||||||
_next: 0,
|
|
||||||
len: 0x04,
|
len: 0x04,
|
||||||
foo: 0x55,
|
foo: 0x55,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,22 +3,12 @@
|
|||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use crate::configuration::{self, PciBarRegionType};
|
use crate::configuration::{self, PciBarRegionType};
|
||||||
use crate::msix::MsixTableEntry;
|
|
||||||
use crate::PciInterruptPin;
|
|
||||||
use devices::BusDevice;
|
use devices::BusDevice;
|
||||||
use std;
|
use std::any::Any;
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::sync::Arc;
|
use std::{self, io, result};
|
||||||
use vm_allocator::SystemAllocator;
|
use vm_allocator::SystemAllocator;
|
||||||
use vm_memory::{GuestAddress, GuestUsize};
|
use vm_memory::{GuestAddress, GuestUsize};
|
||||||
use vmm_sys_util::EventFd;
|
|
||||||
|
|
||||||
pub struct InterruptParameters<'a> {
|
|
||||||
pub msix: Option<&'a MsixTableEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type InterruptDelivery =
|
|
||||||
Box<Fn(InterruptParameters) -> std::result::Result<(), std::io::Error> + Send + Sync>;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
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 {
|
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
|
/// 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.
|
/// returns an address. Returns a Vec of (GuestAddress, GuestUsize) tuples.
|
||||||
fn allocate_bars(
|
fn allocate_bars(
|
||||||
@@ -70,23 +55,21 @@ pub trait PciDevice: BusDevice {
|
|||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register any capabilties specified by the device.
|
|
||||||
fn register_device_capabilities(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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()
|
|
||||||
}
|
|
||||||
/// Sets a register in the configuration space.
|
/// Sets a register in the configuration space.
|
||||||
/// * `reg_idx` - The index of the config register to modify.
|
/// * `reg_idx` - The index of the config register to modify.
|
||||||
/// * `offset` - Offset in to the register.
|
/// * `offset` - Offset in to the register.
|
||||||
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]);
|
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]);
|
||||||
/// Gets a register from the configuration space.
|
/// Gets a register from the configuration space.
|
||||||
/// * `reg_idx` - The index of the config register to read.
|
/// * `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.
|
/// Reads from a BAR region mapped in to the device.
|
||||||
/// * `addr` - The guest address inside the BAR.
|
/// * `addr` - The guest address inside the BAR.
|
||||||
/// * `data` - Filled with the data from `addr`.
|
/// * `data` - Filled with the data from `addr`.
|
||||||
@@ -95,6 +78,26 @@ pub trait PciDevice: BusDevice {
|
|||||||
/// * `addr` - The guest address inside the BAR.
|
/// * `addr` - The guest address inside the BAR.
|
||||||
/// * `data` - The data to write.
|
/// * `data` - The data to write.
|
||||||
fn write_bar(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
fn write_bar(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||||
/// Invoked when the device is sandboxed.
|
/// Relocates the BAR to a different address in guest address space.
|
||||||
fn on_device_sandboxed(&mut self) {}
|
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]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate devices;
|
extern crate devices;
|
||||||
extern crate kvm_ioctls;
|
|
||||||
extern crate vm_memory;
|
extern crate vm_memory;
|
||||||
extern crate vmm_sys_util;
|
|
||||||
|
|
||||||
mod bus;
|
mod bus;
|
||||||
mod configuration;
|
mod configuration;
|
||||||
@@ -16,16 +14,16 @@ mod device;
|
|||||||
mod msi;
|
mod msi;
|
||||||
mod msix;
|
mod msix;
|
||||||
|
|
||||||
pub use self::bus::{PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
|
pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
|
||||||
pub use self::configuration::{
|
pub use self::configuration::{
|
||||||
PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityID,
|
PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityID,
|
||||||
PciClassCode, PciConfiguration, PciHeaderType, PciMassStorageSubclass,
|
PciClassCode, PciConfiguration, PciHeaderType, PciMassStorageSubclass,
|
||||||
PciNetworkControllerSubclass, PciProgrammingInterface, PciSerialBusSubClass, PciSubclass,
|
PciNetworkControllerSubclass, PciProgrammingInterface, PciSerialBusSubClass, PciSubclass,
|
||||||
};
|
};
|
||||||
pub use self::device::{
|
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};
|
pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE};
|
||||||
|
|
||||||
/// PCI has four interrupt pins A->D.
|
/// PCI has four interrupt pins A->D.
|
||||||
|
|||||||
104
pci/src/msi.rs
104
pci/src/msi.rs
@@ -7,6 +7,10 @@ extern crate byteorder;
|
|||||||
extern crate vm_memory;
|
extern crate vm_memory;
|
||||||
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use vm_device::interrupt::{
|
||||||
|
InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig,
|
||||||
|
};
|
||||||
|
|
||||||
// MSI control masks
|
// MSI control masks
|
||||||
const MSI_CTL_ENABLE: u16 = 0x1;
|
const MSI_CTL_ENABLE: u16 = 0x1;
|
||||||
@@ -21,6 +25,16 @@ const MSI_MSG_ADDR_LO_OFFSET: u64 = 0x4;
|
|||||||
// MSI message masks
|
// MSI message masks
|
||||||
const MSI_MSG_ADDR_LO_MASK: u32 = 0xffff_fffc;
|
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)]
|
#[derive(Clone, Copy, Default)]
|
||||||
pub struct MsiCap {
|
pub struct MsiCap {
|
||||||
// Message Control Register
|
// Message Control Register
|
||||||
@@ -58,21 +72,15 @@ impl MsiCap {
|
|||||||
self.msg_ctl & MSI_CTL_PER_VECTOR == MSI_CTL_PER_VECTOR
|
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
|
self.msg_ctl & MSI_CTL_ENABLE == MSI_CTL_ENABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn num_enabled_vectors(&self) -> usize {
|
fn num_enabled_vectors(&self) -> usize {
|
||||||
let field = (self.msg_ctl >> 4) & 0x7;
|
msi_num_enabled_vectors(self.msg_ctl)
|
||||||
|
|
||||||
if field > 5 {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
1 << field
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vector_masked(&self, vector: usize) -> bool {
|
fn vector_masked(&self, vector: usize) -> bool {
|
||||||
if !self.per_vector_mask() {
|
if !self.per_vector_mask() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -80,7 +88,7 @@ impl MsiCap {
|
|||||||
(self.mask_bits >> vector) & 0x1 == 0x1
|
(self.mask_bits >> vector) & 0x1 == 0x1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> u64 {
|
fn size(&self) -> u64 {
|
||||||
let mut size: u64 = 0xa;
|
let mut size: u64 = 0xa;
|
||||||
|
|
||||||
if self.addr_64_bits() {
|
if self.addr_64_bits() {
|
||||||
@@ -93,7 +101,7 @@ impl MsiCap {
|
|||||||
size
|
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
|
// Calculate message data offset depending on the address being 32 or
|
||||||
// 64 bits.
|
// 64 bits.
|
||||||
// Calculate upper address offset if the address is 64 bits.
|
// Calculate upper address offset if the address is 64 bits.
|
||||||
@@ -132,7 +140,7 @@ impl MsiCap {
|
|||||||
4 => {
|
4 => {
|
||||||
let value = LittleEndian::read_u32(data);
|
let value = LittleEndian::read_u32(data);
|
||||||
match offset {
|
match offset {
|
||||||
MSI_MSG_CTL_OFFSET => {
|
0x0 => {
|
||||||
self.msg_ctl = (self.msg_ctl & !(MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE))
|
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))
|
| ((value >> 16) as u16 & (MSI_CTL_ENABLE | MSI_CTL_MULTI_MSG_ENABLE))
|
||||||
}
|
}
|
||||||
@@ -151,3 +159,73 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
110
pci/src/msix.rs
110
pci/src/msix.rs
@@ -8,9 +8,11 @@ extern crate vm_memory;
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::device::InterruptParameters;
|
use crate::{PciCapability, PciCapabilityID};
|
||||||
use crate::{InterruptDelivery, PciCapability, PciCapabilityID};
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
use vm_device::interrupt::{
|
||||||
|
InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig,
|
||||||
|
};
|
||||||
use vm_memory::ByteValued;
|
use vm_memory::ByteValued;
|
||||||
|
|
||||||
const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048;
|
const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048;
|
||||||
@@ -51,12 +53,16 @@ impl Default for MsixTableEntry {
|
|||||||
pub struct MsixConfig {
|
pub struct MsixConfig {
|
||||||
pub table_entries: Vec<MsixTableEntry>,
|
pub table_entries: Vec<MsixTableEntry>,
|
||||||
pub pba_entries: Vec<u64>,
|
pub pba_entries: Vec<u64>,
|
||||||
interrupt_cb: Option<Arc<InterruptDelivery>>,
|
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||||
masked: bool,
|
masked: bool,
|
||||||
|
enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MsixConfig {
|
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);
|
assert!(msix_vectors <= MAX_MSIX_VECTORS_PER_DEVICE);
|
||||||
|
|
||||||
let mut table_entries: Vec<MsixTableEntry> = Vec::new();
|
let mut table_entries: Vec<MsixTableEntry> = Vec::new();
|
||||||
@@ -68,23 +74,62 @@ impl MsixConfig {
|
|||||||
MsixConfig {
|
MsixConfig {
|
||||||
table_entries,
|
table_entries,
|
||||||
pba_entries,
|
pba_entries,
|
||||||
interrupt_cb: None,
|
interrupt_source_group,
|
||||||
masked: false,
|
masked: false,
|
||||||
|
enabled: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_interrupt_cb(&mut self, cb: Arc<InterruptDelivery>) {
|
|
||||||
self.interrupt_cb = Some(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn masked(&self) -> bool {
|
pub fn masked(&self) -> bool {
|
||||||
self.masked
|
self.masked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn enabled(&self) -> bool {
|
||||||
|
self.enabled
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_msg_ctl(&mut self, reg: u16) {
|
pub fn set_msg_ctl(&mut self, reg: u16) {
|
||||||
let old_masked = self.masked;
|
let old_masked = self.masked;
|
||||||
|
let old_enabled = self.enabled;
|
||||||
|
|
||||||
self.masked = ((reg >> FUNCTION_MASK_BIT) & 1u16) == 1u16;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// 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
|
// important to go through the entire PBA to check if there was any
|
||||||
@@ -111,7 +156,7 @@ impl MsixConfig {
|
|||||||
0x0 => self.table_entries[index].msg_addr_lo,
|
0x0 => self.table_entries[index].msg_addr_lo,
|
||||||
0x4 => self.table_entries[index].msg_addr_hi,
|
0x4 => self.table_entries[index].msg_addr_hi,
|
||||||
0x8 => self.table_entries[index].msg_data,
|
0x8 => self.table_entries[index].msg_data,
|
||||||
0x10 => self.table_entries[index].vector_ctl,
|
0xc => self.table_entries[index].vector_ctl,
|
||||||
_ => {
|
_ => {
|
||||||
error!("invalid offset");
|
error!("invalid offset");
|
||||||
0
|
0
|
||||||
@@ -162,7 +207,7 @@ impl MsixConfig {
|
|||||||
0x0 => self.table_entries[index].msg_addr_lo = value,
|
0x0 => self.table_entries[index].msg_addr_lo = value,
|
||||||
0x4 => self.table_entries[index].msg_addr_hi = value,
|
0x4 => self.table_entries[index].msg_addr_hi = value,
|
||||||
0x8 => self.table_entries[index].msg_data = value,
|
0x8 => self.table_entries[index].msg_data = value,
|
||||||
0x10 => {
|
0xc => {
|
||||||
old_entry = Some(self.table_entries[index].clone());
|
old_entry = Some(self.table_entries[index].clone());
|
||||||
self.table_entries[index].vector_ctl = value;
|
self.table_entries[index].vector_ctl = value;
|
||||||
}
|
}
|
||||||
@@ -191,6 +236,32 @@ impl MsixConfig {
|
|||||||
_ => error!("invalid data length"),
|
_ => 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
|
// 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
|
// 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
|
// bit has been flipped from 1 to 0, we need to inject a MSI message
|
||||||
@@ -278,13 +349,12 @@ impl MsixConfig {
|
|||||||
|
|
||||||
fn inject_msix_and_clear_pba(&mut self, vector: usize) {
|
fn inject_msix_and_clear_pba(&mut self, vector: usize) {
|
||||||
// Inject the MSI message
|
// Inject the MSI message
|
||||||
if let Some(cb) = &self.interrupt_cb {
|
match self
|
||||||
match (cb)(InterruptParameters {
|
.interrupt_source_group
|
||||||
msix: Some(&self.table_entries[vector]),
|
.trigger(vector as InterruptIndex)
|
||||||
}) {
|
{
|
||||||
Ok(_) => debug!("MSI-X injected on vector control flip"),
|
Ok(_) => debug!("MSI-X injected on vector control flip"),
|
||||||
Err(e) => error!("failed to inject MSI-X: {}", e),
|
Err(e) => error!("failed to inject MSI-X: {}", e),
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the bit from PBA
|
// Clear the bit from PBA
|
||||||
@@ -359,11 +429,11 @@ impl MsixCap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn table_offset(&self) -> u32 {
|
pub fn table_offset(&self) -> u32 {
|
||||||
self.table >> 3
|
self.table & 0xffff_fff8
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pba_offset(&self) -> u32 {
|
pub fn pba_offset(&self) -> u32 {
|
||||||
self.pba >> 3
|
self.pba & 0xffff_fff8
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn table_bir(&self) -> u32 {
|
pub fn table_bir(&self) -> u32 {
|
||||||
|
|||||||
13
qcow/Cargo.toml
Executable file → Normal file
13
qcow/Cargo.toml
Executable file → Normal file
@@ -9,11 +9,12 @@ license = "BSD-3-Clause"
|
|||||||
path = "src/qcow.rs"
|
path = "src/qcow.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "*"
|
byteorder = "1.3.4"
|
||||||
libc = "*"
|
libc = "0.2.66"
|
||||||
log = "*"
|
log = "0.4.8"
|
||||||
remain = "*"
|
remain = "0.2.1"
|
||||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
vmm-sys-util = "0.4.0"
|
||||||
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "*"
|
tempfile = "3.1.0"
|
||||||
|
|||||||
342
qcow/src/qcow.rs
342
qcow/src/qcow.rs
@@ -12,14 +12,16 @@ mod vec_cache;
|
|||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
use libc::{EINVAL, ENOSPC, ENOTSUP};
|
||||||
use remain::sorted;
|
use remain::sorted;
|
||||||
use vmm_sys_util::{FileSetLen, FileSync, PunchHole, SeekHole, WriteZeroes};
|
use vm_virtio::RawFile;
|
||||||
|
use vmm_sys_util::{
|
||||||
|
file_traits::FileSetLen, file_traits::FileSync, seek_hole::SeekHole, write_zeroes::PunchHole,
|
||||||
|
write_zeroes::WriteZeroes,
|
||||||
|
};
|
||||||
|
|
||||||
use std::cmp::min;
|
use std::cmp::{max, min};
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
|
||||||
|
|
||||||
use crate::qcow_raw_file::QcowRawFile;
|
use crate::qcow_raw_file::QcowRawFile;
|
||||||
use crate::refcount::RefCount;
|
use crate::refcount::RefCount;
|
||||||
@@ -31,18 +33,21 @@ pub enum Error {
|
|||||||
BackingFilesNotSupported,
|
BackingFilesNotSupported,
|
||||||
CompressedBlocksNotSupported,
|
CompressedBlocksNotSupported,
|
||||||
EvictingCache(io::Error),
|
EvictingCache(io::Error),
|
||||||
|
FileTooBig(u64),
|
||||||
GettingFileSize(io::Error),
|
GettingFileSize(io::Error),
|
||||||
GettingRefcount(refcount::Error),
|
GettingRefcount(refcount::Error),
|
||||||
InvalidClusterIndex,
|
InvalidClusterIndex,
|
||||||
InvalidClusterSize,
|
InvalidClusterSize,
|
||||||
InvalidIndex,
|
InvalidIndex,
|
||||||
InvalidL1TableOffset,
|
InvalidL1TableOffset,
|
||||||
|
InvalidL1TableSize(u32),
|
||||||
InvalidMagic,
|
InvalidMagic,
|
||||||
InvalidOffset(u64),
|
InvalidOffset(u64),
|
||||||
InvalidRefcountTableOffset,
|
InvalidRefcountTableOffset,
|
||||||
InvalidRefcountTableSize,
|
InvalidRefcountTableSize(u64),
|
||||||
NoFreeClusters,
|
NoFreeClusters,
|
||||||
NoRefcountClusters,
|
NoRefcountClusters,
|
||||||
|
NotEnoughSpaceForRefcounts,
|
||||||
OpeningFile(io::Error),
|
OpeningFile(io::Error),
|
||||||
ReadingData(io::Error),
|
ReadingData(io::Error),
|
||||||
ReadingHeader(io::Error),
|
ReadingHeader(io::Error),
|
||||||
@@ -50,10 +55,14 @@ pub enum Error {
|
|||||||
ReadingRefCountBlock(refcount::Error),
|
ReadingRefCountBlock(refcount::Error),
|
||||||
ReadingRefCounts(io::Error),
|
ReadingRefCounts(io::Error),
|
||||||
RebuildingRefCounts(io::Error),
|
RebuildingRefCounts(io::Error),
|
||||||
|
RefcountTableOffEnd,
|
||||||
|
RefcountTableTooLarge,
|
||||||
SeekingFile(io::Error),
|
SeekingFile(io::Error),
|
||||||
SettingFileSize(io::Error),
|
SettingFileSize(io::Error),
|
||||||
SettingRefcountRefcount(io::Error),
|
SettingRefcountRefcount(io::Error),
|
||||||
SizeTooSmallForNumberOfClusters,
|
SizeTooSmallForNumberOfClusters,
|
||||||
|
TooManyL1Entries(u64),
|
||||||
|
TooManyRefcounts(u64),
|
||||||
UnsupportedRefcountOrder,
|
UnsupportedRefcountOrder,
|
||||||
UnsupportedVersion(u32),
|
UnsupportedVersion(u32),
|
||||||
WritingData(io::Error),
|
WritingData(io::Error),
|
||||||
@@ -72,18 +81,25 @@ impl Display for Error {
|
|||||||
BackingFilesNotSupported => write!(f, "backing files not supported"),
|
BackingFilesNotSupported => write!(f, "backing files not supported"),
|
||||||
CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
|
CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
|
||||||
EvictingCache(e) => write!(f, "failed to evict cache: {}", e),
|
EvictingCache(e) => write!(f, "failed to evict cache: {}", e),
|
||||||
|
FileTooBig(size) => write!(
|
||||||
|
f,
|
||||||
|
"file larger than max of {}: {}",
|
||||||
|
MAX_QCOW_FILE_SIZE, size
|
||||||
|
),
|
||||||
GettingFileSize(e) => write!(f, "failed to get file size: {}", e),
|
GettingFileSize(e) => write!(f, "failed to get file size: {}", e),
|
||||||
GettingRefcount(e) => write!(f, "failed to get refcount: {}", e),
|
GettingRefcount(e) => write!(f, "failed to get refcount: {}", e),
|
||||||
InvalidClusterIndex => write!(f, "invalid cluster index"),
|
InvalidClusterIndex => write!(f, "invalid cluster index"),
|
||||||
InvalidClusterSize => write!(f, "invalid cluster size"),
|
InvalidClusterSize => write!(f, "invalid cluster size"),
|
||||||
InvalidIndex => write!(f, "invalid index"),
|
InvalidIndex => write!(f, "invalid index"),
|
||||||
InvalidL1TableOffset => write!(f, "invalid L1 table offset"),
|
InvalidL1TableOffset => write!(f, "invalid L1 table offset"),
|
||||||
|
InvalidL1TableSize(size) => write!(f, "invalid L1 table size {}", size),
|
||||||
InvalidMagic => write!(f, "invalid magic"),
|
InvalidMagic => write!(f, "invalid magic"),
|
||||||
InvalidOffset(_) => write!(f, "invalid offset"),
|
InvalidOffset(_) => write!(f, "invalid offset"),
|
||||||
InvalidRefcountTableOffset => write!(f, "invalid refcount table offset"),
|
InvalidRefcountTableOffset => write!(f, "invalid refcount table offset"),
|
||||||
InvalidRefcountTableSize => write!(f, "invalid refcount table size"),
|
InvalidRefcountTableSize(size) => write!(f, "invalid refcount table size: {}", size),
|
||||||
NoFreeClusters => write!(f, "no free clusters"),
|
NoFreeClusters => write!(f, "no free clusters"),
|
||||||
NoRefcountClusters => write!(f, "no refcount clusters"),
|
NoRefcountClusters => write!(f, "no refcount clusters"),
|
||||||
|
NotEnoughSpaceForRefcounts => write!(f, "not enough space for refcounts"),
|
||||||
OpeningFile(e) => write!(f, "failed to open file: {}", e),
|
OpeningFile(e) => write!(f, "failed to open file: {}", e),
|
||||||
ReadingData(e) => write!(f, "failed to read data: {}", e),
|
ReadingData(e) => write!(f, "failed to read data: {}", e),
|
||||||
ReadingHeader(e) => write!(f, "failed to read header: {}", e),
|
ReadingHeader(e) => write!(f, "failed to read header: {}", e),
|
||||||
@@ -91,10 +107,14 @@ impl Display for Error {
|
|||||||
ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {}", e),
|
ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {}", e),
|
||||||
ReadingRefCounts(e) => write!(f, "failed to read ref counts: {}", e),
|
ReadingRefCounts(e) => write!(f, "failed to read ref counts: {}", e),
|
||||||
RebuildingRefCounts(e) => write!(f, "failed to rebuild ref counts: {}", e),
|
RebuildingRefCounts(e) => write!(f, "failed to rebuild ref counts: {}", e),
|
||||||
|
RefcountTableOffEnd => write!(f, "refcount table offset past file end"),
|
||||||
|
RefcountTableTooLarge => write!(f, "too many clusters specified for refcount table"),
|
||||||
SeekingFile(e) => write!(f, "failed to seek file: {}", e),
|
SeekingFile(e) => write!(f, "failed to seek file: {}", e),
|
||||||
SettingFileSize(e) => write!(f, "failed to set file size: {}", e),
|
SettingFileSize(e) => write!(f, "failed to set file size: {}", e),
|
||||||
SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {}", e),
|
SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {}", e),
|
||||||
SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
|
SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
|
||||||
|
TooManyL1Entries(count) => write!(f, "l1 entry table too large: {}", count),
|
||||||
|
TooManyRefcounts(count) => write!(f, "ref count table too large: {}", count),
|
||||||
UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
|
UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
|
||||||
UnsupportedVersion(v) => write!(f, "unsupported version: {}", v),
|
UnsupportedVersion(v) => write!(f, "unsupported version: {}", v),
|
||||||
WritingData(e) => write!(f, "failed to write data: {}", e),
|
WritingData(e) => write!(f, "failed to write data: {}", e),
|
||||||
@@ -108,11 +128,21 @@ pub enum ImageType {
|
|||||||
Qcow2,
|
Qcow2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maximum data size supported.
|
||||||
|
const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB.
|
||||||
|
|
||||||
// QCOW magic constant that starts the header.
|
// QCOW magic constant that starts the header.
|
||||||
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
||||||
// Default to a cluster size of 2^DEFAULT_CLUSTER_BITS
|
// Default to a cluster size of 2^DEFAULT_CLUSTER_BITS
|
||||||
const DEFAULT_CLUSTER_BITS: u32 = 16;
|
const DEFAULT_CLUSTER_BITS: u32 = 16;
|
||||||
const MAX_CLUSTER_BITS: u32 = 30;
|
// Limit clusters to reasonable sizes. Choose the same limits as qemu. Making the clusters smaller
|
||||||
|
// increases the amount of overhead for book keeping.
|
||||||
|
const MIN_CLUSTER_BITS: u32 = 9;
|
||||||
|
const MAX_CLUSTER_BITS: u32 = 21;
|
||||||
|
// The L1 and RefCount table are kept in RAM, only handle files that require less than 35M entries.
|
||||||
|
// This easily covers 1 TB files. When support for bigger files is needed the assumptions made to
|
||||||
|
// keep these tables in RAM needs to be thrown out.
|
||||||
|
const MAX_RAM_POINTER_TABLE_SIZE: u64 = 35_000_000;
|
||||||
// Only support 2 byte refcounts, 2^refcount_order bits.
|
// Only support 2 byte refcounts, 2^refcount_order bits.
|
||||||
const DEFAULT_REFCOUNT_ORDER: u32 = 4;
|
const DEFAULT_REFCOUNT_ORDER: u32 = 4;
|
||||||
|
|
||||||
@@ -159,7 +189,7 @@ pub struct QcowHeader {
|
|||||||
|
|
||||||
impl QcowHeader {
|
impl QcowHeader {
|
||||||
/// Creates a QcowHeader from a reference to a file.
|
/// Creates a QcowHeader from a reference to a file.
|
||||||
pub fn new(f: &mut File) -> Result<QcowHeader> {
|
pub fn new(f: &mut RawFile) -> Result<QcowHeader> {
|
||||||
f.seek(SeekFrom::Start(0)).map_err(Error::ReadingHeader)?;
|
f.seek(SeekFrom::Start(0)).map_err(Error::ReadingHeader)?;
|
||||||
let magic = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
|
let magic = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
|
||||||
if magic != QCOW_MAGIC {
|
if magic != QCOW_MAGIC {
|
||||||
@@ -167,12 +197,12 @@ impl QcowHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reads the next u32 from the file.
|
// 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)
|
f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads the next u64 from the file.
|
// 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)
|
f.read_u64::<BigEndian>().map_err(Error::ReadingHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,11 +349,15 @@ impl QcowHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> usize {
|
fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u32) -> u64 {
|
||||||
let refcount_bytes = (0x01u32 << refcount_order) / 8;
|
// Use u64 as the product of the u32 inputs can overflow.
|
||||||
let for_data = div_round_up_u32(num_clusters * refcount_bytes, cluster_size);
|
let refcount_bytes = (0x01 << u64::from(refcount_order)) / 8;
|
||||||
let for_refcounts = div_round_up_u32(for_data * refcount_bytes, cluster_size);
|
let for_data = div_round_up_u64(
|
||||||
for_data as usize + for_refcounts as usize
|
u64::from(num_clusters) * refcount_bytes,
|
||||||
|
u64::from(cluster_size),
|
||||||
|
);
|
||||||
|
let for_refcounts = div_round_up_u64(for_data * refcount_bytes, u64::from(cluster_size));
|
||||||
|
for_data + for_refcounts
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a qcow2 file. This is a sparse file format maintained by the qemu project.
|
/// Represents a qcow2 file. This is a sparse file format maintained by the qemu project.
|
||||||
@@ -333,9 +367,11 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use std::io::{Read, Seek, SeekFrom};
|
/// # use std::io::{Read, Seek, SeekFrom};
|
||||||
|
/// # use vm_virtio::RawFile;
|
||||||
/// # use qcow::{self, QcowFile};
|
/// # use qcow::{self, QcowFile};
|
||||||
/// # fn test(file: std::fs::File) -> std::io::Result<()> {
|
/// # 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];
|
/// let mut buf = [0u8; 12];
|
||||||
/// q.seek(SeekFrom::Start(10 as u64))?;
|
/// q.seek(SeekFrom::Start(10 as u64))?;
|
||||||
/// q.read(&mut buf[..])?;
|
/// q.read(&mut buf[..])?;
|
||||||
@@ -360,7 +396,7 @@ pub struct QcowFile {
|
|||||||
|
|
||||||
impl QcowFile {
|
impl QcowFile {
|
||||||
/// Creates a QcowFile from `file`. File must be a valid qcow2 image.
|
/// Creates a QcowFile from `file`. File must be a valid qcow2 image.
|
||||||
pub fn from(mut file: File) -> Result<QcowFile> {
|
pub fn from(mut file: RawFile) -> Result<QcowFile> {
|
||||||
let header = QcowHeader::new(&mut file)?;
|
let header = QcowHeader::new(&mut file)?;
|
||||||
|
|
||||||
// Only v2 and v3 files are supported.
|
// Only v2 and v3 files are supported.
|
||||||
@@ -368,14 +404,20 @@ impl QcowFile {
|
|||||||
return Err(Error::UnsupportedVersion(header.version));
|
return Err(Error::UnsupportedVersion(header.version));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure that the L1 table fits in RAM.
|
||||||
|
if u64::from(header.l1_size) > MAX_RAM_POINTER_TABLE_SIZE {
|
||||||
|
return Err(Error::InvalidL1TableSize(header.l1_size));
|
||||||
|
}
|
||||||
|
|
||||||
let cluster_bits: u32 = header.cluster_bits;
|
let cluster_bits: u32 = header.cluster_bits;
|
||||||
if cluster_bits > MAX_CLUSTER_BITS {
|
if cluster_bits < MIN_CLUSTER_BITS || cluster_bits > MAX_CLUSTER_BITS {
|
||||||
return Err(Error::InvalidClusterSize);
|
return Err(Error::InvalidClusterSize);
|
||||||
}
|
}
|
||||||
let cluster_size = 0x01u64 << cluster_bits;
|
let cluster_size = 0x01u64 << cluster_bits;
|
||||||
if cluster_size < size_of::<u64>() as u64 {
|
|
||||||
// Can't fit an offset in a cluster, nothing is going to work.
|
// Limit the total size of the disk.
|
||||||
return Err(Error::InvalidClusterSize);
|
if header.size > MAX_QCOW_FILE_SIZE {
|
||||||
|
return Err(Error::FileTooBig(header.size));
|
||||||
}
|
}
|
||||||
|
|
||||||
// No current support for backing files.
|
// No current support for backing files.
|
||||||
@@ -398,8 +440,13 @@ impl QcowFile {
|
|||||||
}
|
}
|
||||||
offset_is_cluster_boundary(header.backing_file_offset, header.cluster_bits)?;
|
offset_is_cluster_boundary(header.backing_file_offset, header.cluster_bits)?;
|
||||||
offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?;
|
offset_is_cluster_boundary(header.l1_table_offset, header.cluster_bits)?;
|
||||||
offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?;
|
|
||||||
offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?;
|
offset_is_cluster_boundary(header.snapshots_offset, header.cluster_bits)?;
|
||||||
|
// refcount table must be a cluster boundary, and within the file's virtual or actual size.
|
||||||
|
offset_is_cluster_boundary(header.refcount_table_offset, header.cluster_bits)?;
|
||||||
|
let file_size = file.metadata().map_err(Error::GettingFileSize)?.len();
|
||||||
|
if header.refcount_table_offset > max(file_size, header.size) {
|
||||||
|
return Err(Error::RefcountTableOffEnd);
|
||||||
|
}
|
||||||
|
|
||||||
// The first cluster should always have a non-zero refcount, so if it is 0,
|
// The first cluster should always have a non-zero refcount, so if it is 0,
|
||||||
// this is an old file with broken refcounts, which requires a rebuild.
|
// this is an old file with broken refcounts, which requires a rebuild.
|
||||||
@@ -432,6 +479,9 @@ impl QcowFile {
|
|||||||
let num_l2_clusters = div_round_up_u64(num_clusters, l2_size);
|
let num_l2_clusters = div_round_up_u64(num_clusters, l2_size);
|
||||||
let l1_clusters = div_round_up_u64(num_l2_clusters, cluster_size);
|
let l1_clusters = div_round_up_u64(num_l2_clusters, cluster_size);
|
||||||
let header_clusters = div_round_up_u64(size_of::<QcowHeader>() as u64, cluster_size);
|
let header_clusters = div_round_up_u64(size_of::<QcowHeader>() as u64, cluster_size);
|
||||||
|
if num_l2_clusters > MAX_RAM_POINTER_TABLE_SIZE {
|
||||||
|
return Err(Error::TooManyL1Entries(num_l2_clusters));
|
||||||
|
}
|
||||||
let l1_table = VecCache::from_vec(
|
let l1_table = VecCache::from_vec(
|
||||||
raw_file
|
raw_file
|
||||||
.read_pointer_table(
|
.read_pointer_table(
|
||||||
@@ -447,7 +497,14 @@ impl QcowFile {
|
|||||||
header.refcount_order,
|
header.refcount_order,
|
||||||
cluster_size as u32,
|
cluster_size as u32,
|
||||||
(num_clusters + l1_clusters + num_l2_clusters + header_clusters) as u32,
|
(num_clusters + l1_clusters + num_l2_clusters + header_clusters) as u32,
|
||||||
) as u64;
|
);
|
||||||
|
// Check that the given header doesn't have a suspiciously sized refcount table.
|
||||||
|
if u64::from(header.refcount_table_clusters) > 2 * refcount_clusters {
|
||||||
|
return Err(Error::RefcountTableTooLarge);
|
||||||
|
}
|
||||||
|
if l1_clusters + refcount_clusters > MAX_RAM_POINTER_TABLE_SIZE {
|
||||||
|
return Err(Error::TooManyRefcounts(refcount_clusters));
|
||||||
|
}
|
||||||
let refcount_block_entries = cluster_size / refcount_bytes;
|
let refcount_block_entries = cluster_size / refcount_bytes;
|
||||||
let refcounts = RefCount::new(
|
let refcounts = RefCount::new(
|
||||||
&mut raw_file,
|
&mut raw_file,
|
||||||
@@ -488,7 +545,7 @@ impl QcowFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new QcowFile at the given path.
|
/// 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);
|
let header = QcowHeader::create_for_size(version, virtual_size);
|
||||||
file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
|
file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
|
||||||
header.write_to(&mut file)?;
|
header.write_to(&mut file)?;
|
||||||
@@ -716,7 +773,7 @@ impl QcowFile {
|
|||||||
while refcounts[first_free_cluster as usize] != 0 {
|
while refcounts[first_free_cluster as usize] != 0 {
|
||||||
first_free_cluster += 1;
|
first_free_cluster += 1;
|
||||||
if first_free_cluster >= refcounts.len() as u64 {
|
if first_free_cluster >= refcounts.len() as u64 {
|
||||||
return Err(Error::InvalidRefcountTableSize);
|
return Err(Error::NotEnoughSpaceForRefcounts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -816,13 +873,13 @@ impl QcowFile {
|
|||||||
max_valid_cluster_index += refblock_clusters + reftable_clusters;
|
max_valid_cluster_index += refblock_clusters + reftable_clusters;
|
||||||
max_valid_cluster_index += refblocks_for_refs + reftable_clusters_for_refs;
|
max_valid_cluster_index += refblocks_for_refs + reftable_clusters_for_refs;
|
||||||
|
|
||||||
if max_valid_cluster_index > usize::max_value() as u64 {
|
if max_valid_cluster_index > MAX_RAM_POINTER_TABLE_SIZE {
|
||||||
return Err(Error::InvalidRefcountTableSize);
|
return Err(Error::InvalidRefcountTableSize(max_valid_cluster_index));
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
|
let max_valid_cluster_offset = max_valid_cluster_index * cluster_size;
|
||||||
if max_valid_cluster_offset < file_size - cluster_size {
|
if max_valid_cluster_offset < file_size - cluster_size {
|
||||||
return Err(Error::InvalidRefcountTableSize);
|
return Err(Error::InvalidRefcountTableSize(max_valid_cluster_offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut refcounts = vec![0; max_valid_cluster_index as usize];
|
let mut refcounts = vec![0; max_valid_cluster_index as usize];
|
||||||
@@ -1025,20 +1082,16 @@ impl QcowFile {
|
|||||||
fn get_new_cluster(&mut self) -> std::io::Result<u64> {
|
fn get_new_cluster(&mut self) -> std::io::Result<u64> {
|
||||||
// First use a pre allocated cluster if one is available.
|
// First use a pre allocated cluster if one is available.
|
||||||
if let Some(free_cluster) = self.avail_clusters.pop() {
|
if let Some(free_cluster) = self.avail_clusters.pop() {
|
||||||
let cluster_size = self.raw_file.cluster_size() as usize;
|
self.raw_file.zero_cluster(free_cluster)?;
|
||||||
self.raw_file
|
|
||||||
.file_mut()
|
|
||||||
.seek(SeekFrom::Start(free_cluster))?;
|
|
||||||
self.raw_file.file_mut().write_zeroes(cluster_size)?;
|
|
||||||
return Ok(free_cluster);
|
return Ok(free_cluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset();
|
let max_valid_cluster_offset = self.refcounts.max_valid_cluster_offset();
|
||||||
if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? {
|
if let Some(new_cluster) = self.raw_file.add_cluster_end(max_valid_cluster_offset)? {
|
||||||
return Ok(new_cluster);
|
Ok(new_cluster)
|
||||||
} else {
|
} else {
|
||||||
error!("No free clusters in get_new_cluster()");
|
error!("No free clusters in get_new_cluster()");
|
||||||
return Err(std::io::Error::from_raw_os_error(ENOSPC));
|
Err(std::io::Error::from_raw_os_error(ENOSPC))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1168,7 +1221,12 @@ impl QcowFile {
|
|||||||
let refcount = self
|
let refcount = self
|
||||||
.refcounts
|
.refcounts
|
||||||
.get_cluster_refcount(&mut self.raw_file, cluster_addr)
|
.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 {
|
if refcount == 0 {
|
||||||
return Err(std::io::Error::from_raw_os_error(EINVAL));
|
return Err(std::io::Error::from_raw_os_error(EINVAL));
|
||||||
}
|
}
|
||||||
@@ -1342,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 {
|
impl Read for QcowFile {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
let address: u64 = self.current_offset as u64;
|
let address: u64 = self.current_offset as u64;
|
||||||
@@ -1514,12 +1566,12 @@ fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
|
|||||||
|
|
||||||
// Ceiling of the division of `dividend`/`divisor`.
|
// Ceiling of the division of `dividend`/`divisor`.
|
||||||
fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 {
|
fn div_round_up_u64(dividend: u64, divisor: u64) -> u64 {
|
||||||
(dividend + divisor - 1) / divisor
|
dividend / divisor + if dividend % divisor != 0 { 1 } else { 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ceiling of the division of `dividend`/`divisor`.
|
// Ceiling of the division of `dividend`/`divisor`.
|
||||||
fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
|
fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
|
||||||
(dividend + divisor - 1) / divisor
|
dividend / divisor + if dividend % divisor != 0 { 1 } else { 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
|
fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
|
||||||
@@ -1582,7 +1634,7 @@ where
|
|||||||
Ok(())
|
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
|
where
|
||||||
R: Read + Seek + SeekHole,
|
R: Read + Seek + SeekHole,
|
||||||
{
|
{
|
||||||
@@ -1614,8 +1666,8 @@ where
|
|||||||
/// Copy the contents of a disk image in `src_file` into `dst_file`.
|
/// Copy the contents of a disk image in `src_file` into `dst_file`.
|
||||||
/// The type of `src_file` is automatically detected, and the output file type is
|
/// The type of `src_file` is automatically detected, and the output file type is
|
||||||
/// determined by `dst_type`.
|
/// determined by `dst_type`.
|
||||||
pub fn convert(src_file: File, dst_file: File, dst_type: ImageType) -> Result<()> {
|
pub fn convert(mut src_file: RawFile, dst_file: RawFile, dst_type: ImageType) -> Result<()> {
|
||||||
let src_type = detect_image_type(&src_file)?;
|
let src_type = detect_image_type(&mut src_file)?;
|
||||||
match src_type {
|
match src_type {
|
||||||
ImageType::Qcow2 => {
|
ImageType::Qcow2 => {
|
||||||
let mut src_reader = QcowFile::from(src_file)?;
|
let mut src_reader = QcowFile::from(src_file)?;
|
||||||
@@ -1630,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.
|
/// Detect the type of an image file by checking for a valid qcow2 header.
|
||||||
pub fn detect_image_type(file: &File) -> Result<ImageType> {
|
pub fn detect_image_type(file: &mut RawFile) -> Result<ImageType> {
|
||||||
let mut f = file;
|
let orig_seek = file
|
||||||
let orig_seek = f.seek(SeekFrom::Current(0)).map_err(Error::SeekingFile)?;
|
.seek(SeekFrom::Current(0))
|
||||||
f.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
|
.map_err(Error::SeekingFile)?;
|
||||||
let magic = f.read_u32::<BigEndian>().map_err(Error::ReadingHeader)?;
|
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 {
|
let image_type = if magic == QCOW_MAGIC {
|
||||||
ImageType::Qcow2
|
ImageType::Qcow2
|
||||||
} else {
|
} else {
|
||||||
ImageType::Raw
|
ImageType::Raw
|
||||||
};
|
};
|
||||||
f.seek(SeekFrom::Start(orig_seek))
|
file.seek(SeekFrom::Start(orig_seek))
|
||||||
.map_err(Error::SeekingFile)?;
|
.map_err(Error::SeekingFile)?;
|
||||||
Ok(image_type)
|
Ok(image_type)
|
||||||
}
|
}
|
||||||
@@ -1648,9 +1701,9 @@ pub fn detect_image_type(file: &File) -> Result<ImageType> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
use tempfile::tempfile;
|
use tempfile::tempfile;
|
||||||
|
use vm_virtio::RawFile;
|
||||||
|
|
||||||
fn valid_header_v3() -> Vec<u8> {
|
fn valid_header_v3() -> Vec<u8> {
|
||||||
vec![
|
vec![
|
||||||
@@ -1693,23 +1746,47 @@ mod tests {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test case found by clusterfuzz to allocate excessive memory.
|
||||||
|
fn test_huge_header() -> Vec<u8> {
|
||||||
|
vec![
|
||||||
|
0x51, 0x46, 0x49, 0xfb, // magic
|
||||||
|
0x00, 0x00, 0x00, 0x03, // version
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // backing file offset
|
||||||
|
0x00, 0x00, 0x00, 0x00, // backing file size
|
||||||
|
0x00, 0x00, 0x00, 0x09, // cluster_bits
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, // size
|
||||||
|
0x00, 0x00, 0x00, 0x00, // crypt method
|
||||||
|
0x00, 0x00, 0x01, 0x00, // L1 size
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // L1 table offset
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // refcount table offset
|
||||||
|
0x00, 0x00, 0x00, 0x03, // refcount table clusters
|
||||||
|
0x00, 0x00, 0x00, 0x00, // nb snapshots
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // snapshots offset
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // incompatible_features
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compatible_features
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // autoclear_features
|
||||||
|
0x00, 0x00, 0x00, 0x04, // refcount_order
|
||||||
|
0x00, 0x00, 0x00, 0x68, // header_length
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
fn with_basic_file<F>(header: &[u8], mut testfn: F)
|
fn with_basic_file<F>(header: &[u8], mut testfn: F)
|
||||||
where
|
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.write_all(&header).unwrap();
|
||||||
disk_file.set_len(0x5_0000).unwrap();
|
disk_file.set_len(0x1_0000_0000).unwrap();
|
||||||
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
disk_file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
|
||||||
testfn(disk_file); // File closed when the function exits.
|
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
|
where
|
||||||
F: FnMut(QcowFile),
|
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();
|
let qcow_file = QcowFile::new(tmp, 3, file_size).unwrap();
|
||||||
|
|
||||||
testfn(qcow_file); // File closed when the function exits.
|
testfn(qcow_file); // File closed when the function exits.
|
||||||
@@ -1718,7 +1795,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn default_header_v2() {
|
fn default_header_v2() {
|
||||||
let header = QcowHeader::create_for_size(2, 0x10_0000);
|
let header = QcowHeader::create_for_size(2, 0x10_0000);
|
||||||
let mut disk_file: File = tempfile().unwrap();
|
let mut disk_file: RawFile = RawFile::new(tempfile().unwrap(), false);
|
||||||
header
|
header
|
||||||
.write_to(&mut disk_file)
|
.write_to(&mut disk_file)
|
||||||
.expect("Failed to write header to temporary file.");
|
.expect("Failed to write header to temporary file.");
|
||||||
@@ -1729,7 +1806,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn default_header_v3() {
|
fn default_header_v3() {
|
||||||
let header = QcowHeader::create_for_size(3, 0x10_0000);
|
let header = QcowHeader::create_for_size(3, 0x10_0000);
|
||||||
let mut disk_file: File = tempfile().unwrap();
|
let mut disk_file: RawFile = RawFile::new(tempfile().unwrap(), false);
|
||||||
header
|
header
|
||||||
.write_to(&mut disk_file)
|
.write_to(&mut disk_file)
|
||||||
.expect("Failed to write header to temporary file.");
|
.expect("Failed to write header to temporary file.");
|
||||||
@@ -1739,13 +1816,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn header_read() {
|
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.");
|
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
||||||
assert_eq!(header.version, 2);
|
assert_eq!(header.version, 2);
|
||||||
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
||||||
assert_eq!(header.header_size, V2_BARE_HEADER_SIZE);
|
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.");
|
let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
||||||
assert_eq!(header.version, 3);
|
assert_eq!(header.version, 3);
|
||||||
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
assert_eq!(header.refcount_order, DEFAULT_REFCOUNT_ORDER);
|
||||||
@@ -1756,7 +1833,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn invalid_magic() {
|
fn invalid_magic() {
|
||||||
let invalid_header = vec![0x51u8, 0x46, 0x4a, 0xfb];
|
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.");
|
QcowHeader::new(&mut disk_file).expect_err("Invalid header worked.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1765,14 +1842,98 @@ mod tests {
|
|||||||
fn invalid_refcount_order() {
|
fn invalid_refcount_order() {
|
||||||
let mut header = valid_header_v3();
|
let mut header = valid_header_v3();
|
||||||
header[99] = 2;
|
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.");
|
QcowFile::from(disk_file).expect_err("Invalid refcount order worked.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_cluster_bits() {
|
||||||
|
let mut header = valid_header_v3();
|
||||||
|
header[23] = 3;
|
||||||
|
with_basic_file(&header, |disk_file: RawFile| {
|
||||||
|
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_header_huge_file() {
|
||||||
|
let header = test_huge_header();
|
||||||
|
with_basic_file(&header, |disk_file: RawFile| {
|
||||||
|
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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: RawFile| {
|
||||||
|
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_huge_l1_table() {
|
||||||
|
let mut header = valid_header_v3();
|
||||||
|
header[36] = 0x12;
|
||||||
|
with_basic_file(&header, |disk_file: RawFile| {
|
||||||
|
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_header_1_tb_file_min_cluster() {
|
||||||
|
let mut header = test_huge_header();
|
||||||
|
header[24] = 0;
|
||||||
|
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: RawFile| {
|
||||||
|
QcowFile::from(disk_file).expect_err("Failed to create file.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_header_1_tb_file() {
|
||||||
|
let mut header = test_huge_header();
|
||||||
|
// reset to 1 TB size.
|
||||||
|
header[24] = 0;
|
||||||
|
header[26] = 1;
|
||||||
|
header[31] = 0;
|
||||||
|
// set cluster_bits
|
||||||
|
header[23] = 16;
|
||||||
|
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.");
|
||||||
|
let value = 0x0000_0040_3f00_ffffu64;
|
||||||
|
qcow.write_all(&value.to_le_bytes())
|
||||||
|
.expect("failed to write data");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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: RawFile| {
|
||||||
|
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount clusters");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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: RawFile| {
|
||||||
|
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount offset");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_read_start() {
|
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();
|
let mut q = QcowFile::from(disk_file).unwrap();
|
||||||
q.write(b"test first bytes")
|
q.write(b"test first bytes")
|
||||||
.expect("Failed to write test string.");
|
.expect("Failed to write test string.");
|
||||||
@@ -1785,7 +1946,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn offset_write_read() {
|
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 mut q = QcowFile::from(disk_file).unwrap();
|
||||||
let b = [0x55u8; 0x1000];
|
let b = [0x55u8; 0x1000];
|
||||||
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
|
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
|
||||||
@@ -1799,7 +1960,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_zeroes_read() {
|
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();
|
let mut q = QcowFile::from(disk_file).unwrap();
|
||||||
// Write some test data.
|
// Write some test data.
|
||||||
let b = [0x55u8; 0x1000];
|
let b = [0x55u8; 0x1000];
|
||||||
@@ -1825,7 +1986,7 @@ mod tests {
|
|||||||
// Choose a size that is larger than a cluster.
|
// Choose a size that is larger than a cluster.
|
||||||
// valid_header uses cluster_bits = 12, which corresponds to a cluster size of 4096.
|
// valid_header uses cluster_bits = 12, which corresponds to a cluster size of 4096.
|
||||||
const CHUNK_SIZE: usize = 4096 * 2 + 512;
|
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();
|
let mut q = QcowFile::from(disk_file).unwrap();
|
||||||
// Write some test data.
|
// Write some test data.
|
||||||
let b = [0x55u8; CHUNK_SIZE];
|
let b = [0x55u8; CHUNK_SIZE];
|
||||||
@@ -1846,11 +2007,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_header() {
|
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();
|
let q = QcowFile::from(disk_file).unwrap();
|
||||||
assert_eq!(q.virtual_size(), 0x20_0000_0000);
|
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();
|
let q = QcowFile::from(disk_file).unwrap();
|
||||||
assert_eq!(q.virtual_size(), 0x20_0000_0000);
|
assert_eq!(q.virtual_size(), 0x20_0000_0000);
|
||||||
});
|
});
|
||||||
@@ -1858,7 +2019,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_small_buffer() {
|
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 q = QcowFile::from(disk_file).unwrap();
|
||||||
let mut b = [5u8; 16];
|
let mut b = [5u8; 16];
|
||||||
q.seek(SeekFrom::Start(1000)).expect("Failed to seek.");
|
q.seek(SeekFrom::Start(1000)).expect("Failed to seek.");
|
||||||
@@ -1870,7 +2031,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replay_ext4() {
|
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();
|
let mut q = QcowFile::from(disk_file).unwrap();
|
||||||
const BUF_SIZE: usize = 0x1000;
|
const BUF_SIZE: usize = 0x1000;
|
||||||
let mut b = [0u8; BUF_SIZE];
|
let mut b = [0u8; BUF_SIZE];
|
||||||
@@ -2246,7 +2407,16 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn combo_write_read() {
|
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 NUM_BLOCKS: usize = 555;
|
||||||
const BLOCK_SIZE: usize = 0x1_0000;
|
const BLOCK_SIZE: usize = 0x1_0000;
|
||||||
const OFFSET: usize = 0x1_0000_0020;
|
const OFFSET: usize = 0x1_0000_0020;
|
||||||
@@ -2299,7 +2469,16 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn seek_data() {
|
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
|
// seek_data at or after the end of the file should return None
|
||||||
assert_eq!(file.seek_data(0x10000).unwrap(), None);
|
assert_eq!(file.seek_data(0x10000).unwrap(), None);
|
||||||
assert_eq!(seek_cur(&mut file), 0);
|
assert_eq!(seek_cur(&mut file), 0);
|
||||||
@@ -2332,7 +2511,16 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn seek_hole() {
|
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
|
// File consisting entirely of a hole
|
||||||
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
|
assert_eq!(file.seek_hole(0).unwrap(), Some(0));
|
||||||
assert_eq!(seek_cur(&mut file), 0);
|
assert_eq!(seek_cur(&mut file), 0);
|
||||||
@@ -2406,7 +2594,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rebuild_refcounts() {
|
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 header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
|
||||||
let cluster_size = 65536;
|
let cluster_size = 65536;
|
||||||
let mut raw_file =
|
let mut raw_file =
|
||||||
|
|||||||
@@ -2,16 +2,17 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, BufWriter, Seek, SeekFrom};
|
use std::io::{self, BufWriter, Seek, SeekFrom};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
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.
|
/// A qcow file. Allows reading/writing clusters and appending clusters.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct QcowRawFile {
|
pub struct QcowRawFile {
|
||||||
file: File,
|
file: RawFile,
|
||||||
cluster_size: u64,
|
cluster_size: u64,
|
||||||
cluster_mask: u64,
|
cluster_mask: u64,
|
||||||
}
|
}
|
||||||
@@ -19,7 +20,7 @@ pub struct QcowRawFile {
|
|||||||
impl QcowRawFile {
|
impl QcowRawFile {
|
||||||
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
|
/// Creates a `QcowRawFile` from the given `File`, `None` is returned if `cluster_size` is not
|
||||||
/// a power of two.
|
/// 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 {
|
if cluster_size.count_ones() != 1 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -66,7 +67,7 @@ impl QcowRawFile {
|
|||||||
non_zero_flags: u64,
|
non_zero_flags: u64,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
self.file.seek(SeekFrom::Start(offset))?;
|
self.file.seek(SeekFrom::Start(offset))?;
|
||||||
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u64>(), &self.file);
|
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u64>(), &mut self.file);
|
||||||
for addr in table {
|
for addr in table {
|
||||||
let val = if *addr == 0 {
|
let val = if *addr == 0 {
|
||||||
0
|
0
|
||||||
@@ -91,7 +92,7 @@ impl QcowRawFile {
|
|||||||
/// Writes a refcount block to the file.
|
/// Writes a refcount block to the file.
|
||||||
pub fn write_refcount_block(&mut self, offset: u64, table: &[u16]) -> io::Result<()> {
|
pub fn write_refcount_block(&mut self, offset: u64, table: &[u16]) -> io::Result<()> {
|
||||||
self.file.seek(SeekFrom::Start(offset))?;
|
self.file.seek(SeekFrom::Start(offset))?;
|
||||||
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u16>(), &self.file);
|
let mut buffer = BufWriter::with_capacity(table.len() * size_of::<u16>(), &mut self.file);
|
||||||
for count in table {
|
for count in table {
|
||||||
buffer.write_u16::<BigEndian>(*count)?;
|
buffer.write_u16::<BigEndian>(*count)?;
|
||||||
}
|
}
|
||||||
@@ -114,13 +115,8 @@ impl QcowRawFile {
|
|||||||
Ok(Some(new_cluster_address))
|
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.
|
/// 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
|
&mut self.file
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +129,14 @@ impl QcowRawFile {
|
|||||||
pub fn cluster_offset(&self, address: u64) -> u64 {
|
pub fn cluster_offset(&self, address: u64) -> u64 {
|
||||||
address & self.cluster_mask
|
address & self.cluster_mask
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Zeros out a cluster in the file.
|
||||||
|
pub fn zero_cluster(&mut self, address: u64) -> io::Result<()> {
|
||||||
|
let cluster_size = self.cluster_size as usize;
|
||||||
|
self.file.seek(SeekFrom::Start(address))?;
|
||||||
|
self.file.write_zeroes(cluster_size)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for QcowRawFile {
|
impl Clone for QcowRawFile {
|
||||||
|
|||||||
323
release-notes.md
323
release-notes.md
@@ -1,6 +1,323 @@
|
|||||||
|
- [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)
|
||||||
|
- [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-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)
|
||||||
|
|
||||||
|
# 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/cloud-hypervisor/cloud-hypervisor/projects/3).
|
||||||
|
|
||||||
|
Highlights for `cloud-hypervisor` version 0.3.0 include:
|
||||||
|
|
||||||
|
### Block device offloading
|
||||||
|
|
||||||
|
We continue to work on offloading paravirtualized I/O to external processes,
|
||||||
|
and we added support for
|
||||||
|
[vhost-user-blk](https://access.redhat.com/solutions/3394851) backends.
|
||||||
|
This enables `cloud-hypervisor` users to plug a `vhost-user` based block device
|
||||||
|
like [SPDK](https://spdk.io)) into the VMM as their paravirtualized storage
|
||||||
|
backend.
|
||||||
|
|
||||||
|
### Network device backend
|
||||||
|
|
||||||
|
The previous release provided support for
|
||||||
|
[vhost-user-net](https://access.redhat.com/solutions/3394851) backends. Now we
|
||||||
|
also provide a TAP based vhost-user-net backend, implemented in Rust. Together
|
||||||
|
with the vhost-user-net device implementation, this will eventually become the
|
||||||
|
Cloud Hypervisor default paravirtualized networking architecture.
|
||||||
|
|
||||||
|
### Virtual sockets
|
||||||
|
|
||||||
|
In order to more efficiently and securely communicate between host and guest,
|
||||||
|
we added an 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.
|
||||||
|
|
||||||
|
### HTTP based API
|
||||||
|
|
||||||
|
In anticipation of the need to support asynchronous operations to Cloud
|
||||||
|
Hypervisor guests (e.g. resources hotplug and guest migration), we added a HTTP
|
||||||
|
based API to the VMM. The API will be more extensively documented during the
|
||||||
|
next release cycle.
|
||||||
|
|
||||||
|
### Memory mapped virtio transport
|
||||||
|
|
||||||
|
In order to support potential PCI-free use cases, we added support for the
|
||||||
|
[virtio MMIO](https://docs.oasis-open.org/virtio/virtio/v1.1/cs01/virtio-v1.1-cs01.html#x1-1440002)
|
||||||
|
transport layer. This will allow us to support simple, minimal guest
|
||||||
|
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/cloud-hypervisor/cloud-hypervisor/blob/master/docs/iommu.md)
|
||||||
|
device through virtio. This allows for a safer nested virtio and directly
|
||||||
|
assigned devices support.
|
||||||
|
|
||||||
|
To add the IOMMU support, we had to make some CLI changes for Cloud Hypervisor
|
||||||
|
users to be able to specify if devices had to be handled through this virtual
|
||||||
|
IOMMU or not. In particular, the `--disk` option now expects disk paths to be
|
||||||
|
prefixed with a `path=` string, and supports an optional `iommu=[on|off]`
|
||||||
|
setting.
|
||||||
|
|
||||||
|
### Ubuntu 19.10
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Large memory guests
|
||||||
|
|
||||||
|
After simplifying and changing our guest address space handling, we can now
|
||||||
|
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/cloud-hypervisor/cloud-hypervisor/projects/2).
|
||||||
|
|
||||||
|
Highlights for `cloud-hypervisor` version 0.2.0 include:
|
||||||
|
|
||||||
|
### Network device offloading
|
||||||
|
|
||||||
|
As part of our 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](https://dpdk.org)) into the VMM as their virtio network backend.
|
||||||
|
|
||||||
|
### Minimal hardware-reduced ACPI
|
||||||
|
|
||||||
|
In order to properly implement and guest reset and shutdown, we implemented
|
||||||
|
a minimal version of the hardware-reduced ACPI specification. Together with
|
||||||
|
a tiny I/O port based ACPI device, this allows `cloud-hypervisor` guests to
|
||||||
|
cleanly reboot and shutdown.
|
||||||
|
|
||||||
|
The ACPI implementation is a `cloud-hypervisor` build time option that is
|
||||||
|
enabled by default.
|
||||||
|
|
||||||
|
### Debug I/O port
|
||||||
|
|
||||||
|
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/cloud-hypervisor/cloud-hypervisor/blob/master/docs/debug-port.md)
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
### Improved direct device assignment
|
||||||
|
|
||||||
|
We fixed a major performance issue with our initial VFIO implementation: When
|
||||||
|
enabling VT-d through the KVM and VFIO APIs, our guest memory writes and reads
|
||||||
|
were (in many cases) not cached. After correctly tagging the guest memory from
|
||||||
|
`cloud-hypervisor` we're now able to reach the expected performance from
|
||||||
|
directly assigned devices.
|
||||||
|
|
||||||
|
### Improved shared filesystem
|
||||||
|
|
||||||
|
We added shared memory region with [DAX](https://www.kernel.org/doc/Documentation/filesystems/dax.txt)
|
||||||
|
support to our [virtio-fs](https://virtio-fs.gitlab.io/) shared file system.
|
||||||
|
This provides better shared filesystem IO performance with a smaller guest
|
||||||
|
memory footprint.
|
||||||
|
|
||||||
|
### Ubuntu bionic based CI
|
||||||
|
|
||||||
|
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
|
# 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:
|
Highlights for `cloud-hypervisor` version 0.1.0 include:
|
||||||
|
|
||||||
@@ -10,7 +327,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
|
system, allowing for an efficient and reliable way of sharing a filesystem
|
||||||
between the host and the `cloud-hypervisor` guest.
|
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`.
|
documentation for more details on how to use virtio-fs with `cloud-hypervisor`.
|
||||||
|
|
||||||
### Initial direct device assignment support
|
### Initial direct device assignment support
|
||||||
@@ -19,7 +336,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
|
access to userspace. `cloud-hypervisor` uses VFIO to directly assign host
|
||||||
physical devices into its guest.
|
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
|
documentation for more detail on how to directly assign host devices to
|
||||||
`cloud-hypervisor` guests.
|
`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.40.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,15 +1,17 @@
|
|||||||
#
|
#
|
||||||
# Automatically generated file; DO NOT EDIT.
|
# Automatically generated file; DO NOT EDIT.
|
||||||
# Linux/x86 5.2.0-rc5 Kernel Configuration
|
# Linux/x86 5.5.0-rc1 Kernel Configuration
|
||||||
#
|
#
|
||||||
|
|
||||||
#
|
#
|
||||||
# Compiler: gcc (Ubuntu 7.4.0-1ubuntu1~18.04) 7.4.0
|
# Compiler: gcc (Ubuntu 8.3.0-6ubuntu1) 8.3.0
|
||||||
#
|
#
|
||||||
CONFIG_CC_IS_GCC=y
|
CONFIG_CC_IS_GCC=y
|
||||||
CONFIG_GCC_VERSION=70400
|
CONFIG_GCC_VERSION=80300
|
||||||
CONFIG_CLANG_VERSION=0
|
CONFIG_CLANG_VERSION=0
|
||||||
|
CONFIG_CC_CAN_LINK=y
|
||||||
CONFIG_CC_HAS_ASM_GOTO=y
|
CONFIG_CC_HAS_ASM_GOTO=y
|
||||||
|
CONFIG_CC_HAS_ASM_INLINE=y
|
||||||
CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y
|
CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y
|
||||||
CONFIG_IRQ_WORK=y
|
CONFIG_IRQ_WORK=y
|
||||||
CONFIG_BUILDTIME_EXTABLE_SORT=y
|
CONFIG_BUILDTIME_EXTABLE_SORT=y
|
||||||
@@ -59,6 +61,7 @@ CONFIG_IRQ_DOMAIN=y
|
|||||||
CONFIG_IRQ_DOMAIN_HIERARCHY=y
|
CONFIG_IRQ_DOMAIN_HIERARCHY=y
|
||||||
CONFIG_GENERIC_MSI_IRQ=y
|
CONFIG_GENERIC_MSI_IRQ=y
|
||||||
CONFIG_GENERIC_MSI_IRQ_DOMAIN=y
|
CONFIG_GENERIC_MSI_IRQ_DOMAIN=y
|
||||||
|
CONFIG_IRQ_MSI_IOMMU=y
|
||||||
CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR=y
|
CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR=y
|
||||||
CONFIG_GENERIC_IRQ_RESERVATION_MODE=y
|
CONFIG_GENERIC_IRQ_RESERVATION_MODE=y
|
||||||
CONFIG_IRQ_FORCED_THREADING=y
|
CONFIG_IRQ_FORCED_THREADING=y
|
||||||
@@ -128,8 +131,15 @@ CONFIG_LOG_BUF_SHIFT=21
|
|||||||
CONFIG_LOG_CPU_MAX_BUF_SHIFT=12
|
CONFIG_LOG_CPU_MAX_BUF_SHIFT=12
|
||||||
CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=13
|
CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=13
|
||||||
CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y
|
CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y
|
||||||
|
|
||||||
|
#
|
||||||
|
# Scheduler features
|
||||||
|
#
|
||||||
|
# end of Scheduler features
|
||||||
|
|
||||||
CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y
|
CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y
|
||||||
CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y
|
CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y
|
||||||
|
CONFIG_CC_HAS_INT128=y
|
||||||
CONFIG_ARCH_SUPPORTS_INT128=y
|
CONFIG_ARCH_SUPPORTS_INT128=y
|
||||||
CONFIG_NUMA_BALANCING=y
|
CONFIG_NUMA_BALANCING=y
|
||||||
# CONFIG_NUMA_BALANCING_DEFAULT_ENABLED is not set
|
# CONFIG_NUMA_BALANCING_DEFAULT_ENABLED is not set
|
||||||
@@ -140,7 +150,6 @@ CONFIG_MEMCG_SWAP=y
|
|||||||
CONFIG_MEMCG_SWAP_ENABLED=y
|
CONFIG_MEMCG_SWAP_ENABLED=y
|
||||||
CONFIG_MEMCG_KMEM=y
|
CONFIG_MEMCG_KMEM=y
|
||||||
CONFIG_BLK_CGROUP=y
|
CONFIG_BLK_CGROUP=y
|
||||||
# CONFIG_DEBUG_BLK_CGROUP is not set
|
|
||||||
CONFIG_CGROUP_WRITEBACK=y
|
CONFIG_CGROUP_WRITEBACK=y
|
||||||
CONFIG_CGROUP_SCHED=y
|
CONFIG_CGROUP_SCHED=y
|
||||||
CONFIG_FAIR_GROUP_SCHED=y
|
CONFIG_FAIR_GROUP_SCHED=y
|
||||||
@@ -175,7 +184,7 @@ CONFIG_SYSCTL=y
|
|||||||
CONFIG_SYSCTL_EXCEPTION_TRACE=y
|
CONFIG_SYSCTL_EXCEPTION_TRACE=y
|
||||||
CONFIG_HAVE_PCSPKR_PLATFORM=y
|
CONFIG_HAVE_PCSPKR_PLATFORM=y
|
||||||
CONFIG_BPF=y
|
CONFIG_BPF=y
|
||||||
# CONFIG_EXPERT is not set
|
CONFIG_EXPERT=y
|
||||||
CONFIG_MULTIUSER=y
|
CONFIG_MULTIUSER=y
|
||||||
CONFIG_SGETMASK_SYSCALL=y
|
CONFIG_SGETMASK_SYSCALL=y
|
||||||
CONFIG_SYSFS_SYSCALL=y
|
CONFIG_SYSFS_SYSCALL=y
|
||||||
@@ -206,8 +215,10 @@ CONFIG_BPF_SYSCALL=y
|
|||||||
CONFIG_USERFAULTFD=y
|
CONFIG_USERFAULTFD=y
|
||||||
CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y
|
CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y
|
||||||
CONFIG_RSEQ=y
|
CONFIG_RSEQ=y
|
||||||
|
# CONFIG_DEBUG_RSEQ is not set
|
||||||
# CONFIG_EMBEDDED is not set
|
# CONFIG_EMBEDDED is not set
|
||||||
CONFIG_HAVE_PERF_EVENTS=y
|
CONFIG_HAVE_PERF_EVENTS=y
|
||||||
|
# CONFIG_PC104 is not set
|
||||||
|
|
||||||
#
|
#
|
||||||
# Kernel Performance Events And Counters
|
# Kernel Performance Events And Counters
|
||||||
@@ -218,9 +229,11 @@ CONFIG_PERF_EVENTS=y
|
|||||||
|
|
||||||
CONFIG_VM_EVENT_COUNTERS=y
|
CONFIG_VM_EVENT_COUNTERS=y
|
||||||
CONFIG_SLUB_DEBUG=y
|
CONFIG_SLUB_DEBUG=y
|
||||||
|
# CONFIG_SLUB_MEMCG_SYSFS_ON is not set
|
||||||
# CONFIG_COMPAT_BRK is not set
|
# CONFIG_COMPAT_BRK is not set
|
||||||
# CONFIG_SLAB is not set
|
# CONFIG_SLAB is not set
|
||||||
CONFIG_SLUB=y
|
CONFIG_SLUB=y
|
||||||
|
# CONFIG_SLOB is not set
|
||||||
CONFIG_SLAB_MERGE_DEFAULT=y
|
CONFIG_SLAB_MERGE_DEFAULT=y
|
||||||
# CONFIG_SLAB_FREELIST_RANDOM is not set
|
# CONFIG_SLAB_FREELIST_RANDOM is not set
|
||||||
CONFIG_SLAB_FREELIST_HARDENED=y
|
CONFIG_SLAB_FREELIST_HARDENED=y
|
||||||
@@ -255,7 +268,6 @@ CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y
|
|||||||
CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y
|
CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y
|
||||||
CONFIG_ARCH_HIBERNATION_POSSIBLE=y
|
CONFIG_ARCH_HIBERNATION_POSSIBLE=y
|
||||||
CONFIG_ARCH_SUSPEND_POSSIBLE=y
|
CONFIG_ARCH_SUSPEND_POSSIBLE=y
|
||||||
CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
|
|
||||||
CONFIG_ARCH_WANT_GENERAL_HUGETLB=y
|
CONFIG_ARCH_WANT_GENERAL_HUGETLB=y
|
||||||
CONFIG_ZONE_DMA32=y
|
CONFIG_ZONE_DMA32=y
|
||||||
CONFIG_AUDIT_ARCH=y
|
CONFIG_AUDIT_ARCH=y
|
||||||
@@ -289,11 +301,13 @@ CONFIG_PARAVIRT=y
|
|||||||
CONFIG_PARAVIRT_SPINLOCKS=y
|
CONFIG_PARAVIRT_SPINLOCKS=y
|
||||||
# CONFIG_XEN is not set
|
# CONFIG_XEN is not set
|
||||||
CONFIG_KVM_GUEST=y
|
CONFIG_KVM_GUEST=y
|
||||||
|
CONFIG_ARCH_CPUIDLE_HALTPOLL=y
|
||||||
CONFIG_PVH=y
|
CONFIG_PVH=y
|
||||||
CONFIG_KVM_DEBUG_FS=y
|
CONFIG_KVM_DEBUG_FS=y
|
||||||
CONFIG_PARAVIRT_TIME_ACCOUNTING=y
|
CONFIG_PARAVIRT_TIME_ACCOUNTING=y
|
||||||
CONFIG_PARAVIRT_CLOCK=y
|
CONFIG_PARAVIRT_CLOCK=y
|
||||||
# CONFIG_JAILHOUSE_GUEST is not set
|
# CONFIG_JAILHOUSE_GUEST is not set
|
||||||
|
# CONFIG_ACRN_GUEST is not set
|
||||||
# CONFIG_MK8 is not set
|
# CONFIG_MK8 is not set
|
||||||
# CONFIG_MPSC is not set
|
# CONFIG_MPSC is not set
|
||||||
# CONFIG_MCORE2 is not set
|
# CONFIG_MCORE2 is not set
|
||||||
@@ -306,14 +320,15 @@ CONFIG_X86_CMPXCHG64=y
|
|||||||
CONFIG_X86_CMOV=y
|
CONFIG_X86_CMOV=y
|
||||||
CONFIG_X86_MINIMUM_CPU_FAMILY=64
|
CONFIG_X86_MINIMUM_CPU_FAMILY=64
|
||||||
CONFIG_X86_DEBUGCTLMSR=y
|
CONFIG_X86_DEBUGCTLMSR=y
|
||||||
|
# CONFIG_PROCESSOR_SELECT is not set
|
||||||
CONFIG_CPU_SUP_INTEL=y
|
CONFIG_CPU_SUP_INTEL=y
|
||||||
CONFIG_CPU_SUP_AMD=y
|
CONFIG_CPU_SUP_AMD=y
|
||||||
CONFIG_CPU_SUP_HYGON=y
|
CONFIG_CPU_SUP_HYGON=y
|
||||||
CONFIG_CPU_SUP_CENTAUR=y
|
CONFIG_CPU_SUP_CENTAUR=y
|
||||||
|
CONFIG_CPU_SUP_ZHAOXIN=y
|
||||||
CONFIG_HPET_TIMER=y
|
CONFIG_HPET_TIMER=y
|
||||||
CONFIG_DMI=y
|
CONFIG_DMI=y
|
||||||
# CONFIG_GART_IOMMU is not set
|
# CONFIG_GART_IOMMU is not set
|
||||||
# CONFIG_CALGARY_IOMMU is not set
|
|
||||||
# CONFIG_MAXSMP is not set
|
# CONFIG_MAXSMP is not set
|
||||||
CONFIG_NR_CPUS_RANGE_BEGIN=2
|
CONFIG_NR_CPUS_RANGE_BEGIN=2
|
||||||
CONFIG_NR_CPUS_RANGE_END=512
|
CONFIG_NR_CPUS_RANGE_END=512
|
||||||
@@ -339,6 +354,7 @@ CONFIG_PERF_EVENTS_INTEL_CSTATE=y
|
|||||||
CONFIG_X86_16BIT=y
|
CONFIG_X86_16BIT=y
|
||||||
CONFIG_X86_ESPFIX64=y
|
CONFIG_X86_ESPFIX64=y
|
||||||
CONFIG_X86_VSYSCALL_EMULATION=y
|
CONFIG_X86_VSYSCALL_EMULATION=y
|
||||||
|
# CONFIG_X86_IOPL_IOPERM is not set
|
||||||
# CONFIG_I8K is not set
|
# CONFIG_I8K is not set
|
||||||
# CONFIG_MICROCODE is not set
|
# CONFIG_MICROCODE is not set
|
||||||
CONFIG_X86_MSR=y
|
CONFIG_X86_MSR=y
|
||||||
@@ -346,7 +362,6 @@ CONFIG_X86_CPUID=y
|
|||||||
# CONFIG_X86_5LEVEL is not set
|
# CONFIG_X86_5LEVEL is not set
|
||||||
CONFIG_X86_DIRECT_GBPAGES=y
|
CONFIG_X86_DIRECT_GBPAGES=y
|
||||||
# CONFIG_X86_CPA_STATISTICS is not set
|
# CONFIG_X86_CPA_STATISTICS is not set
|
||||||
CONFIG_ARCH_HAS_MEM_ENCRYPT=y
|
|
||||||
CONFIG_AMD_MEM_ENCRYPT=y
|
CONFIG_AMD_MEM_ENCRYPT=y
|
||||||
# CONFIG_AMD_MEM_ENCRYPT_ACTIVE_BY_DEFAULT is not set
|
# CONFIG_AMD_MEM_ENCRYPT_ACTIVE_BY_DEFAULT is not set
|
||||||
CONFIG_NUMA=y
|
CONFIG_NUMA=y
|
||||||
@@ -373,9 +388,12 @@ CONFIG_X86_PAT=y
|
|||||||
CONFIG_ARCH_USES_PG_UNCACHED=y
|
CONFIG_ARCH_USES_PG_UNCACHED=y
|
||||||
CONFIG_ARCH_RANDOM=y
|
CONFIG_ARCH_RANDOM=y
|
||||||
CONFIG_X86_SMAP=y
|
CONFIG_X86_SMAP=y
|
||||||
CONFIG_X86_INTEL_UMIP=y
|
# CONFIG_X86_UMIP is not set
|
||||||
# CONFIG_X86_INTEL_MPX is not set
|
# CONFIG_X86_INTEL_MPX is not set
|
||||||
# CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS 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=y
|
||||||
CONFIG_EFI_STUB=y
|
CONFIG_EFI_STUB=y
|
||||||
# CONFIG_EFI_MIXED is not set
|
# CONFIG_EFI_MIXED is not set
|
||||||
@@ -389,7 +407,7 @@ CONFIG_SCHED_HRTICK=y
|
|||||||
# CONFIG_KEXEC is not set
|
# CONFIG_KEXEC is not set
|
||||||
CONFIG_KEXEC_FILE=y
|
CONFIG_KEXEC_FILE=y
|
||||||
CONFIG_ARCH_HAS_KEXEC_PURGATORY=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_CRASH_DUMP is not set
|
||||||
CONFIG_PHYSICAL_START=0x1000000
|
CONFIG_PHYSICAL_START=0x1000000
|
||||||
CONFIG_RELOCATABLE=y
|
CONFIG_RELOCATABLE=y
|
||||||
@@ -399,6 +417,7 @@ CONFIG_HOTPLUG_CPU=y
|
|||||||
# CONFIG_BOOTPARAM_HOTPLUG_CPU0 is not set
|
# CONFIG_BOOTPARAM_HOTPLUG_CPU0 is not set
|
||||||
# CONFIG_DEBUG_HOTPLUG_CPU0 is not set
|
# CONFIG_DEBUG_HOTPLUG_CPU0 is not set
|
||||||
CONFIG_LEGACY_VSYSCALL_EMULATE=y
|
CONFIG_LEGACY_VSYSCALL_EMULATE=y
|
||||||
|
# CONFIG_LEGACY_VSYSCALL_XONLY is not set
|
||||||
# CONFIG_LEGACY_VSYSCALL_NONE is not set
|
# CONFIG_LEGACY_VSYSCALL_NONE is not set
|
||||||
# CONFIG_CMDLINE_BOOL is not set
|
# CONFIG_CMDLINE_BOOL is not set
|
||||||
CONFIG_MODIFY_LDT_SYSCALL=y
|
CONFIG_MODIFY_LDT_SYSCALL=y
|
||||||
@@ -457,18 +476,19 @@ CONFIG_ACPI_PROCESSOR=y
|
|||||||
CONFIG_ACPI_HOTPLUG_CPU=y
|
CONFIG_ACPI_HOTPLUG_CPU=y
|
||||||
# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set
|
# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set
|
||||||
CONFIG_ACPI_THERMAL=y
|
CONFIG_ACPI_THERMAL=y
|
||||||
CONFIG_ACPI_NUMA=y
|
|
||||||
CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
|
CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
|
||||||
# CONFIG_ACPI_DEBUG is not set
|
# CONFIG_ACPI_DEBUG is not set
|
||||||
# CONFIG_ACPI_PCI_SLOT is not set
|
# CONFIG_ACPI_PCI_SLOT is not set
|
||||||
CONFIG_ACPI_CONTAINER=y
|
CONFIG_ACPI_CONTAINER=y
|
||||||
# CONFIG_ACPI_HOTPLUG_MEMORY is not set
|
CONFIG_ACPI_HOTPLUG_MEMORY=y
|
||||||
CONFIG_ACPI_HOTPLUG_IOAPIC=y
|
CONFIG_ACPI_HOTPLUG_IOAPIC=y
|
||||||
# CONFIG_ACPI_SBS is not set
|
# CONFIG_ACPI_SBS is not set
|
||||||
# CONFIG_ACPI_HED is not set
|
# CONFIG_ACPI_HED is not set
|
||||||
# CONFIG_ACPI_CUSTOM_METHOD is not set
|
# CONFIG_ACPI_CUSTOM_METHOD is not set
|
||||||
# CONFIG_ACPI_BGRT is not set
|
# CONFIG_ACPI_BGRT is not set
|
||||||
|
CONFIG_ACPI_REDUCED_HARDWARE_ONLY=y
|
||||||
# CONFIG_ACPI_NFIT is not set
|
# CONFIG_ACPI_NFIT is not set
|
||||||
|
CONFIG_ACPI_NUMA=y
|
||||||
# CONFIG_ACPI_HMAT is not set
|
# CONFIG_ACPI_HMAT is not set
|
||||||
CONFIG_HAVE_ACPI_APEI=y
|
CONFIG_HAVE_ACPI_APEI=y
|
||||||
CONFIG_HAVE_ACPI_APEI_NMI=y
|
CONFIG_HAVE_ACPI_APEI_NMI=y
|
||||||
@@ -518,6 +538,8 @@ CONFIG_CPU_IDLE=y
|
|||||||
CONFIG_CPU_IDLE_GOV_LADDER=y
|
CONFIG_CPU_IDLE_GOV_LADDER=y
|
||||||
CONFIG_CPU_IDLE_GOV_MENU=y
|
CONFIG_CPU_IDLE_GOV_MENU=y
|
||||||
# CONFIG_CPU_IDLE_GOV_TEO is not set
|
# 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
|
# end of CPU Idle
|
||||||
|
|
||||||
CONFIG_INTEL_IDLE=y
|
CONFIG_INTEL_IDLE=y
|
||||||
@@ -529,6 +551,8 @@ CONFIG_INTEL_IDLE=y
|
|||||||
CONFIG_PCI_DIRECT=y
|
CONFIG_PCI_DIRECT=y
|
||||||
CONFIG_PCI_MMCONFIG=y
|
CONFIG_PCI_MMCONFIG=y
|
||||||
CONFIG_MMCONF_FAM10H=y
|
CONFIG_MMCONF_FAM10H=y
|
||||||
|
# CONFIG_PCI_CNB20LE_QUIRK is not set
|
||||||
|
# CONFIG_ISA_BUS is not set
|
||||||
CONFIG_ISA_DMA_API=y
|
CONFIG_ISA_DMA_API=y
|
||||||
CONFIG_AMD_NB=y
|
CONFIG_AMD_NB=y
|
||||||
# CONFIG_X86_SYSFB is not set
|
# CONFIG_X86_SYSFB is not set
|
||||||
@@ -541,8 +565,6 @@ CONFIG_AMD_NB=y
|
|||||||
# CONFIG_X86_X32 is not set
|
# CONFIG_X86_X32 is not set
|
||||||
# end of Binary Emulations
|
# end of Binary Emulations
|
||||||
|
|
||||||
CONFIG_HAVE_GENERIC_GUP=y
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Firmware Drivers
|
# Firmware Drivers
|
||||||
#
|
#
|
||||||
@@ -551,7 +573,6 @@ CONFIG_FIRMWARE_MEMMAP=y
|
|||||||
CONFIG_DMIID=y
|
CONFIG_DMIID=y
|
||||||
CONFIG_DMI_SYSFS=y
|
CONFIG_DMI_SYSFS=y
|
||||||
CONFIG_DMI_SCAN_MACHINE_NON_EFI_FALLBACK=y
|
CONFIG_DMI_SCAN_MACHINE_NON_EFI_FALLBACK=y
|
||||||
# CONFIG_ISCSI_IBFT_FIND is not set
|
|
||||||
# CONFIG_FW_CFG_SYSFS is not set
|
# CONFIG_FW_CFG_SYSFS is not set
|
||||||
# CONFIG_GOOGLE_FIRMWARE is not set
|
# CONFIG_GOOGLE_FIRMWARE is not set
|
||||||
|
|
||||||
@@ -567,6 +588,7 @@ CONFIG_EFI_RUNTIME_WRAPPERS=y
|
|||||||
# CONFIG_EFI_TEST is not set
|
# CONFIG_EFI_TEST is not set
|
||||||
# CONFIG_APPLE_PROPERTIES is not set
|
# CONFIG_APPLE_PROPERTIES is not set
|
||||||
# CONFIG_RESET_ATTACK_MITIGATION is not set
|
# CONFIG_RESET_ATTACK_MITIGATION is not set
|
||||||
|
# CONFIG_EFI_RCI2_TABLE is not set
|
||||||
# end of EFI (Extensible Firmware Interface) Support
|
# end of EFI (Extensible Firmware Interface) Support
|
||||||
|
|
||||||
CONFIG_EFI_EARLYCON=y
|
CONFIG_EFI_EARLYCON=y
|
||||||
@@ -578,7 +600,25 @@ CONFIG_EFI_EARLYCON=y
|
|||||||
# end of Firmware Drivers
|
# end of Firmware Drivers
|
||||||
|
|
||||||
CONFIG_HAVE_KVM=y
|
CONFIG_HAVE_KVM=y
|
||||||
# CONFIG_VIRTUALIZATION is not set
|
CONFIG_HAVE_KVM_IRQCHIP=y
|
||||||
|
CONFIG_HAVE_KVM_IRQFD=y
|
||||||
|
CONFIG_HAVE_KVM_IRQ_ROUTING=y
|
||||||
|
CONFIG_HAVE_KVM_EVENTFD=y
|
||||||
|
CONFIG_KVM_MMIO=y
|
||||||
|
CONFIG_KVM_ASYNC_PF=y
|
||||||
|
CONFIG_HAVE_KVM_MSI=y
|
||||||
|
CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT=y
|
||||||
|
CONFIG_KVM_VFIO=y
|
||||||
|
CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT=y
|
||||||
|
CONFIG_HAVE_KVM_IRQ_BYPASS=y
|
||||||
|
CONFIG_HAVE_KVM_NO_POLL=y
|
||||||
|
CONFIG_VIRTUALIZATION=y
|
||||||
|
CONFIG_KVM=y
|
||||||
|
CONFIG_KVM_INTEL=y
|
||||||
|
# CONFIG_KVM_AMD is not set
|
||||||
|
# CONFIG_VHOST_NET is not set
|
||||||
|
# CONFIG_VHOST_VSOCK is not set
|
||||||
|
# CONFIG_VHOST_CROSS_ENDIAN_LEGACY is not set
|
||||||
|
|
||||||
#
|
#
|
||||||
# General architecture-dependent options
|
# General architecture-dependent options
|
||||||
@@ -592,6 +632,7 @@ CONFIG_JUMP_LABEL=y
|
|||||||
# CONFIG_STATIC_KEYS_SELFTEST is not set
|
# CONFIG_STATIC_KEYS_SELFTEST is not set
|
||||||
CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
|
CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
|
||||||
CONFIG_ARCH_USE_BUILTIN_BSWAP=y
|
CONFIG_ARCH_USE_BUILTIN_BSWAP=y
|
||||||
|
CONFIG_USER_RETURN_NOTIFIER=y
|
||||||
CONFIG_HAVE_IOREMAP_PROT=y
|
CONFIG_HAVE_IOREMAP_PROT=y
|
||||||
CONFIG_HAVE_KPROBES=y
|
CONFIG_HAVE_KPROBES=y
|
||||||
CONFIG_HAVE_KRETPROBES=y
|
CONFIG_HAVE_KRETPROBES=y
|
||||||
@@ -607,6 +648,7 @@ CONFIG_ARCH_HAS_SET_MEMORY=y
|
|||||||
CONFIG_ARCH_HAS_SET_DIRECT_MAP=y
|
CONFIG_ARCH_HAS_SET_DIRECT_MAP=y
|
||||||
CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y
|
CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y
|
||||||
CONFIG_ARCH_WANTS_DYNAMIC_TASK_STRUCT=y
|
CONFIG_ARCH_WANTS_DYNAMIC_TASK_STRUCT=y
|
||||||
|
CONFIG_HAVE_ASM_MODVERSIONS=y
|
||||||
CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y
|
CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y
|
||||||
CONFIG_HAVE_RSEQ=y
|
CONFIG_HAVE_RSEQ=y
|
||||||
CONFIG_HAVE_FUNCTION_ARG_ACCESS_API=y
|
CONFIG_HAVE_FUNCTION_ARG_ACCESS_API=y
|
||||||
@@ -640,6 +682,7 @@ CONFIG_HAVE_MOVE_PMD=y
|
|||||||
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y
|
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y
|
||||||
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD=y
|
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD=y
|
||||||
CONFIG_HAVE_ARCH_HUGE_VMAP=y
|
CONFIG_HAVE_ARCH_HUGE_VMAP=y
|
||||||
|
CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
|
||||||
CONFIG_HAVE_ARCH_SOFT_DIRTY=y
|
CONFIG_HAVE_ARCH_SOFT_DIRTY=y
|
||||||
CONFIG_HAVE_MOD_ARCH_SPECIFIC=y
|
CONFIG_HAVE_MOD_ARCH_SPECIFIC=y
|
||||||
CONFIG_MODULES_USE_ELF_RELA=y
|
CONFIG_MODULES_USE_ELF_RELA=y
|
||||||
@@ -651,17 +694,16 @@ CONFIG_ARCH_MMAP_RND_BITS=28
|
|||||||
CONFIG_HAVE_COPY_THREAD_TLS=y
|
CONFIG_HAVE_COPY_THREAD_TLS=y
|
||||||
CONFIG_HAVE_STACK_VALIDATION=y
|
CONFIG_HAVE_STACK_VALIDATION=y
|
||||||
CONFIG_HAVE_RELIABLE_STACKTRACE=y
|
CONFIG_HAVE_RELIABLE_STACKTRACE=y
|
||||||
CONFIG_64BIT_TIME=y
|
# CONFIG_COMPAT_32BIT_TIME is not set
|
||||||
CONFIG_HAVE_ARCH_VMAP_STACK=y
|
CONFIG_HAVE_ARCH_VMAP_STACK=y
|
||||||
CONFIG_VMAP_STACK=y
|
CONFIG_VMAP_STACK=y
|
||||||
CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y
|
CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y
|
||||||
CONFIG_STRICT_KERNEL_RWX=y
|
CONFIG_STRICT_KERNEL_RWX=y
|
||||||
CONFIG_ARCH_HAS_STRICT_MODULE_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_HAVE_ARCH_PREL32_RELOCATIONS=y
|
||||||
CONFIG_ARCH_USE_MEMREMAP_PROT=y
|
CONFIG_ARCH_USE_MEMREMAP_PROT=y
|
||||||
# CONFIG_LOCK_EVENT_COUNTS is not set
|
# CONFIG_LOCK_EVENT_COUNTS is not set
|
||||||
|
CONFIG_ARCH_HAS_MEM_ENCRYPT=y
|
||||||
|
|
||||||
#
|
#
|
||||||
# GCOV-based kernel profiling
|
# GCOV-based kernel profiling
|
||||||
@@ -680,6 +722,7 @@ CONFIG_BASE_SMALL=0
|
|||||||
CONFIG_MODULES_TREE_LOOKUP=y
|
CONFIG_MODULES_TREE_LOOKUP=y
|
||||||
CONFIG_BLOCK=y
|
CONFIG_BLOCK=y
|
||||||
CONFIG_BLK_SCSI_REQUEST=y
|
CONFIG_BLK_SCSI_REQUEST=y
|
||||||
|
CONFIG_BLK_CGROUP_RWSTAT=y
|
||||||
CONFIG_BLK_DEV_BSG=y
|
CONFIG_BLK_DEV_BSG=y
|
||||||
CONFIG_BLK_DEV_BSGLIB=y
|
CONFIG_BLK_DEV_BSGLIB=y
|
||||||
CONFIG_BLK_DEV_INTEGRITY=y
|
CONFIG_BLK_DEV_INTEGRITY=y
|
||||||
@@ -689,6 +732,7 @@ CONFIG_BLK_DEV_THROTTLING=y
|
|||||||
CONFIG_BLK_CMDLINE_PARSER=y
|
CONFIG_BLK_CMDLINE_PARSER=y
|
||||||
CONFIG_BLK_WBT=y
|
CONFIG_BLK_WBT=y
|
||||||
# CONFIG_BLK_CGROUP_IOLATENCY is not set
|
# CONFIG_BLK_CGROUP_IOLATENCY is not set
|
||||||
|
# CONFIG_BLK_CGROUP_IOCOST is not set
|
||||||
CONFIG_BLK_WBT_MQ=y
|
CONFIG_BLK_WBT_MQ=y
|
||||||
CONFIG_BLK_DEBUG_FS=y
|
CONFIG_BLK_DEBUG_FS=y
|
||||||
# CONFIG_BLK_SED_OPAL is not set
|
# CONFIG_BLK_SED_OPAL is not set
|
||||||
@@ -726,6 +770,7 @@ CONFIG_BLK_PM=y
|
|||||||
# CONFIG_IOSCHED_BFQ is not set
|
# CONFIG_IOSCHED_BFQ is not set
|
||||||
# end of IO Schedulers
|
# end of IO Schedulers
|
||||||
|
|
||||||
|
CONFIG_PREEMPT_NOTIFIERS=y
|
||||||
CONFIG_INLINE_SPIN_UNLOCK_IRQ=y
|
CONFIG_INLINE_SPIN_UNLOCK_IRQ=y
|
||||||
CONFIG_INLINE_READ_UNLOCK=y
|
CONFIG_INLINE_READ_UNLOCK=y
|
||||||
CONFIG_INLINE_READ_UNLOCK_IRQ=y
|
CONFIG_INLINE_READ_UNLOCK_IRQ=y
|
||||||
@@ -766,6 +811,7 @@ CONFIG_SPARSEMEM_EXTREME=y
|
|||||||
CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
|
CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
|
||||||
CONFIG_SPARSEMEM_VMEMMAP=y
|
CONFIG_SPARSEMEM_VMEMMAP=y
|
||||||
CONFIG_HAVE_MEMBLOCK_NODE_MAP=y
|
CONFIG_HAVE_MEMBLOCK_NODE_MAP=y
|
||||||
|
CONFIG_HAVE_FAST_GUP=y
|
||||||
CONFIG_MEMORY_ISOLATION=y
|
CONFIG_MEMORY_ISOLATION=y
|
||||||
CONFIG_HAVE_BOOTMEM_INFO_NODE=y
|
CONFIG_HAVE_BOOTMEM_INFO_NODE=y
|
||||||
CONFIG_MEMORY_HOTPLUG=y
|
CONFIG_MEMORY_HOTPLUG=y
|
||||||
@@ -781,6 +827,7 @@ CONFIG_CONTIG_ALLOC=y
|
|||||||
CONFIG_PHYS_ADDR_T_64BIT=y
|
CONFIG_PHYS_ADDR_T_64BIT=y
|
||||||
CONFIG_BOUNCE=y
|
CONFIG_BOUNCE=y
|
||||||
CONFIG_VIRT_TO_BUS=y
|
CONFIG_VIRT_TO_BUS=y
|
||||||
|
CONFIG_MMU_NOTIFIER=y
|
||||||
CONFIG_KSM=y
|
CONFIG_KSM=y
|
||||||
CONFIG_DEFAULT_MMAP_MIN_ADDR=4096
|
CONFIG_DEFAULT_MMAP_MIN_ADDR=4096
|
||||||
CONFIG_TRANSPARENT_HUGEPAGE=y
|
CONFIG_TRANSPARENT_HUGEPAGE=y
|
||||||
@@ -802,12 +849,13 @@ CONFIG_ZSMALLOC_STAT=y
|
|||||||
CONFIG_GENERIC_EARLY_IOREMAP=y
|
CONFIG_GENERIC_EARLY_IOREMAP=y
|
||||||
# CONFIG_DEFERRED_STRUCT_PAGE_INIT is not set
|
# CONFIG_DEFERRED_STRUCT_PAGE_INIT is not set
|
||||||
# CONFIG_IDLE_PAGE_TRACKING is not set
|
# CONFIG_IDLE_PAGE_TRACKING is not set
|
||||||
CONFIG_ARCH_HAS_ZONE_DEVICE=y
|
CONFIG_ARCH_HAS_PTE_DEVMAP=y
|
||||||
# CONFIG_ZONE_DEVICE is not set
|
CONFIG_ZONE_DEVICE=y
|
||||||
CONFIG_ARCH_HAS_HMM_MIRROR=y
|
CONFIG_DEV_PAGEMAP_OPS=y
|
||||||
CONFIG_ARCH_HAS_HMM_DEVICE=y
|
# CONFIG_DEVICE_PRIVATE is not set
|
||||||
CONFIG_PERCPU_STATS=y
|
CONFIG_PERCPU_STATS=y
|
||||||
# CONFIG_GUP_BENCHMARK is not set
|
# CONFIG_GUP_BENCHMARK is not set
|
||||||
|
# CONFIG_READ_ONLY_THP_FOR_FS is not set
|
||||||
CONFIG_ARCH_HAS_PTE_SPECIAL=y
|
CONFIG_ARCH_HAS_PTE_SPECIAL=y
|
||||||
# end of Memory Management options
|
# end of Memory Management options
|
||||||
|
|
||||||
@@ -824,6 +872,7 @@ CONFIG_UNIX_SCM=y
|
|||||||
CONFIG_UNIX_DIAG=y
|
CONFIG_UNIX_DIAG=y
|
||||||
CONFIG_TLS=y
|
CONFIG_TLS=y
|
||||||
# CONFIG_TLS_DEVICE is not set
|
# CONFIG_TLS_DEVICE is not set
|
||||||
|
# CONFIG_TLS_TOE is not set
|
||||||
CONFIG_XFRM=y
|
CONFIG_XFRM=y
|
||||||
CONFIG_XFRM_ALGO=y
|
CONFIG_XFRM_ALGO=y
|
||||||
CONFIG_XFRM_USER=y
|
CONFIG_XFRM_USER=y
|
||||||
@@ -947,7 +996,6 @@ CONFIG_PCIEAER=y
|
|||||||
# CONFIG_PCIEAER_INJECT is not set
|
# CONFIG_PCIEAER_INJECT is not set
|
||||||
# CONFIG_PCIE_ECRC is not set
|
# CONFIG_PCIE_ECRC is not set
|
||||||
CONFIG_PCIEASPM=y
|
CONFIG_PCIEASPM=y
|
||||||
# CONFIG_PCIEASPM_DEBUG is not set
|
|
||||||
CONFIG_PCIEASPM_DEFAULT=y
|
CONFIG_PCIEASPM_DEFAULT=y
|
||||||
# CONFIG_PCIEASPM_POWERSAVE is not set
|
# CONFIG_PCIEASPM_POWERSAVE is not set
|
||||||
# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set
|
# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set
|
||||||
@@ -965,18 +1013,13 @@ CONFIG_PCI_LOCKLESS_CONFIG=y
|
|||||||
# CONFIG_PCI_IOV is not set
|
# CONFIG_PCI_IOV is not set
|
||||||
# CONFIG_PCI_PRI is not set
|
# CONFIG_PCI_PRI is not set
|
||||||
# CONFIG_PCI_PASID is not set
|
# CONFIG_PCI_PASID is not set
|
||||||
|
# CONFIG_PCI_P2PDMA is not set
|
||||||
CONFIG_PCI_LABEL=y
|
CONFIG_PCI_LABEL=y
|
||||||
# CONFIG_HOTPLUG_PCI is not set
|
# CONFIG_HOTPLUG_PCI is not set
|
||||||
|
|
||||||
#
|
#
|
||||||
# PCI controller drivers
|
# PCI controller drivers
|
||||||
#
|
#
|
||||||
|
|
||||||
#
|
|
||||||
# Cadence PCIe controllers support
|
|
||||||
#
|
|
||||||
# end of Cadence PCIe controllers support
|
|
||||||
|
|
||||||
# CONFIG_VMD is not set
|
# CONFIG_VMD is not set
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -985,6 +1028,11 @@ CONFIG_PCI_LABEL=y
|
|||||||
# CONFIG_PCIE_DW_PLAT_HOST is not set
|
# CONFIG_PCIE_DW_PLAT_HOST is not set
|
||||||
# CONFIG_PCI_MESON is not set
|
# CONFIG_PCI_MESON is not set
|
||||||
# end of DesignWare PCI Core Support
|
# end of DesignWare PCI Core Support
|
||||||
|
|
||||||
|
#
|
||||||
|
# Cadence PCIe controllers support
|
||||||
|
#
|
||||||
|
# end of Cadence PCIe controllers support
|
||||||
# end of PCI controller drivers
|
# end of PCI controller drivers
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -1016,9 +1064,12 @@ CONFIG_PREVENT_FIRMWARE_BUILD=y
|
|||||||
# Firmware loader
|
# Firmware loader
|
||||||
#
|
#
|
||||||
CONFIG_FW_LOADER=y
|
CONFIG_FW_LOADER=y
|
||||||
|
CONFIG_FW_LOADER_PAGED_BUF=y
|
||||||
CONFIG_EXTRA_FIRMWARE=""
|
CONFIG_EXTRA_FIRMWARE=""
|
||||||
CONFIG_FW_LOADER_USER_HELPER=y
|
CONFIG_FW_LOADER_USER_HELPER=y
|
||||||
# CONFIG_FW_LOADER_USER_HELPER_FALLBACK is not set
|
# 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
|
# end of Firmware loader
|
||||||
|
|
||||||
CONFIG_ALLOW_DEV_COREDUMP=y
|
CONFIG_ALLOW_DEV_COREDUMP=y
|
||||||
@@ -1087,12 +1138,12 @@ CONFIG_VIRTIO_BLK=y
|
|||||||
# CONFIG_DUMMY_IRQ is not set
|
# CONFIG_DUMMY_IRQ is not set
|
||||||
# CONFIG_IBM_ASM is not set
|
# CONFIG_IBM_ASM is not set
|
||||||
# CONFIG_PHANTOM is not set
|
# CONFIG_PHANTOM is not set
|
||||||
# CONFIG_SGI_IOC4 is not set
|
|
||||||
# CONFIG_TIFM_CORE is not set
|
# CONFIG_TIFM_CORE is not set
|
||||||
# CONFIG_ENCLOSURE_SERVICES is not set
|
# CONFIG_ENCLOSURE_SERVICES is not set
|
||||||
# CONFIG_HP_ILO is not set
|
# CONFIG_HP_ILO is not set
|
||||||
# CONFIG_SRAM is not set
|
# CONFIG_SRAM is not set
|
||||||
# CONFIG_PCI_ENDPOINT_TEST is not set
|
# CONFIG_PCI_ENDPOINT_TEST is not set
|
||||||
|
# CONFIG_XILINX_SDFEC is not set
|
||||||
# CONFIG_PVPANIC is not set
|
# CONFIG_PVPANIC is not set
|
||||||
# CONFIG_C2PORT is not set
|
# CONFIG_C2PORT is not set
|
||||||
|
|
||||||
@@ -1120,41 +1171,9 @@ CONFIG_VIRTIO_BLK=y
|
|||||||
#
|
#
|
||||||
# Intel MIC & related support
|
# Intel MIC & related support
|
||||||
#
|
#
|
||||||
|
|
||||||
#
|
|
||||||
# Intel MIC Bus Driver
|
|
||||||
#
|
|
||||||
# CONFIG_INTEL_MIC_BUS is not set
|
# CONFIG_INTEL_MIC_BUS is not set
|
||||||
|
|
||||||
#
|
|
||||||
# SCIF Bus Driver
|
|
||||||
#
|
|
||||||
# CONFIG_SCIF_BUS is not set
|
# CONFIG_SCIF_BUS is not set
|
||||||
|
|
||||||
#
|
|
||||||
# VOP Bus Driver
|
|
||||||
#
|
|
||||||
# CONFIG_VOP_BUS is not set
|
# 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
|
# end of Intel MIC & related support
|
||||||
|
|
||||||
# CONFIG_GENWQE is not set
|
# CONFIG_GENWQE is not set
|
||||||
@@ -1208,10 +1227,6 @@ CONFIG_VIRTIO_NET=y
|
|||||||
# CONFIG_NLMON is not set
|
# CONFIG_NLMON is not set
|
||||||
# CONFIG_ARCNET is not set
|
# CONFIG_ARCNET is not set
|
||||||
|
|
||||||
#
|
|
||||||
# CAIF transport drivers
|
|
||||||
#
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Distributed Switch Architecture drivers
|
# Distributed Switch Architecture drivers
|
||||||
#
|
#
|
||||||
@@ -1334,11 +1349,11 @@ CONFIG_SERIAL_8250_EXAR=y
|
|||||||
CONFIG_SERIAL_8250_NR_UARTS=1
|
CONFIG_SERIAL_8250_NR_UARTS=1
|
||||||
CONFIG_SERIAL_8250_RUNTIME_UARTS=1
|
CONFIG_SERIAL_8250_RUNTIME_UARTS=1
|
||||||
# CONFIG_SERIAL_8250_EXTENDED is not set
|
# CONFIG_SERIAL_8250_EXTENDED is not set
|
||||||
|
CONFIG_SERIAL_8250_DWLIB=y
|
||||||
# CONFIG_SERIAL_8250_DW is not set
|
# CONFIG_SERIAL_8250_DW is not set
|
||||||
# CONFIG_SERIAL_8250_RT288X is not set
|
# CONFIG_SERIAL_8250_RT288X is not set
|
||||||
CONFIG_SERIAL_8250_LPSS=y
|
CONFIG_SERIAL_8250_LPSS=y
|
||||||
CONFIG_SERIAL_8250_MID=y
|
CONFIG_SERIAL_8250_MID=y
|
||||||
# CONFIG_SERIAL_8250_MOXA is not set
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Non-8250 serial port support
|
# Non-8250 serial port support
|
||||||
@@ -1355,10 +1370,12 @@ CONFIG_SERIAL_ARC=y
|
|||||||
CONFIG_SERIAL_ARC_NR_PORTS=1
|
CONFIG_SERIAL_ARC_NR_PORTS=1
|
||||||
# CONFIG_SERIAL_RP2 is not set
|
# CONFIG_SERIAL_RP2 is not set
|
||||||
# CONFIG_SERIAL_FSL_LPUART is not set
|
# CONFIG_SERIAL_FSL_LPUART is not set
|
||||||
|
# CONFIG_SERIAL_FSL_LINFLEXUART is not set
|
||||||
# end of Serial drivers
|
# end of Serial drivers
|
||||||
|
|
||||||
CONFIG_SERIAL_DEV_BUS=y
|
CONFIG_SERIAL_DEV_BUS=y
|
||||||
CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
|
CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
|
||||||
|
# CONFIG_TTY_PRINTK is not set
|
||||||
CONFIG_HVC_DRIVER=y
|
CONFIG_HVC_DRIVER=y
|
||||||
CONFIG_VIRTIO_CONSOLE=y
|
CONFIG_VIRTIO_CONSOLE=y
|
||||||
# CONFIG_IPMI_HANDLER is not set
|
# CONFIG_IPMI_HANDLER is not set
|
||||||
@@ -1382,6 +1399,7 @@ CONFIG_DEVPORT=y
|
|||||||
# end of Character devices
|
# end of Character devices
|
||||||
|
|
||||||
# CONFIG_RANDOM_TRUST_CPU is not set
|
# CONFIG_RANDOM_TRUST_CPU is not set
|
||||||
|
# CONFIG_RANDOM_TRUST_BOOTLOADER is not set
|
||||||
|
|
||||||
#
|
#
|
||||||
# I2C support
|
# I2C support
|
||||||
@@ -1416,6 +1434,7 @@ CONFIG_PTP_1588_CLOCK=y
|
|||||||
# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks.
|
# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks.
|
||||||
#
|
#
|
||||||
CONFIG_PTP_1588_CLOCK_KVM=y
|
CONFIG_PTP_1588_CLOCK_KVM=y
|
||||||
|
# CONFIG_PTP_1588_CLOCK_IDTCM is not set
|
||||||
# end of PTP clock support
|
# end of PTP clock support
|
||||||
|
|
||||||
# CONFIG_PINCTRL is not set
|
# CONFIG_PINCTRL is not set
|
||||||
@@ -1437,7 +1456,6 @@ CONFIG_THERMAL_GOV_FAIR_SHARE=y
|
|||||||
CONFIG_THERMAL_GOV_STEP_WISE=y
|
CONFIG_THERMAL_GOV_STEP_WISE=y
|
||||||
# CONFIG_THERMAL_GOV_BANG_BANG is not set
|
# CONFIG_THERMAL_GOV_BANG_BANG is not set
|
||||||
CONFIG_THERMAL_GOV_USER_SPACE=y
|
CONFIG_THERMAL_GOV_USER_SPACE=y
|
||||||
# CONFIG_THERMAL_GOV_POWER_ALLOCATOR is not set
|
|
||||||
# CONFIG_THERMAL_EMULATION is not set
|
# CONFIG_THERMAL_EMULATION is not set
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -1464,7 +1482,6 @@ CONFIG_BCMA_POSSIBLE=y
|
|||||||
#
|
#
|
||||||
# Multifunction device drivers
|
# Multifunction device drivers
|
||||||
#
|
#
|
||||||
# CONFIG_MFD_CROS_EC is not set
|
|
||||||
# CONFIG_MFD_MADERA is not set
|
# CONFIG_MFD_MADERA is not set
|
||||||
# CONFIG_HTC_PASIC3 is not set
|
# CONFIG_HTC_PASIC3 is not set
|
||||||
# CONFIG_MFD_INTEL_QUARK_I2C_GPIO is not set
|
# CONFIG_MFD_INTEL_QUARK_I2C_GPIO is not set
|
||||||
@@ -1573,7 +1590,7 @@ CONFIG_LCD_CLASS_DEVICE=y
|
|||||||
CONFIG_BACKLIGHT_CLASS_DEVICE=y
|
CONFIG_BACKLIGHT_CLASS_DEVICE=y
|
||||||
CONFIG_BACKLIGHT_GENERIC=y
|
CONFIG_BACKLIGHT_GENERIC=y
|
||||||
# CONFIG_BACKLIGHT_APPLE is not set
|
# 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
|
# CONFIG_BACKLIGHT_SAHARA is not set
|
||||||
# end of Backlight & LCD device support
|
# end of Backlight & LCD device support
|
||||||
|
|
||||||
@@ -1637,7 +1654,6 @@ CONFIG_HID_GENERIC=y
|
|||||||
# CONFIG_HID_KENSINGTON is not set
|
# CONFIG_HID_KENSINGTON is not set
|
||||||
# CONFIG_HID_LCPOWER is not set
|
# CONFIG_HID_LCPOWER is not set
|
||||||
# CONFIG_HID_LENOVO is not set
|
# CONFIG_HID_LENOVO is not set
|
||||||
# CONFIG_HID_LOGITECH is not set
|
|
||||||
# CONFIG_HID_MAGICMOUSE is not set
|
# CONFIG_HID_MAGICMOUSE is not set
|
||||||
# CONFIG_HID_MALTRON is not set
|
# CONFIG_HID_MALTRON is not set
|
||||||
# CONFIG_HID_MAYFLASH is not set
|
# CONFIG_HID_MAYFLASH is not set
|
||||||
@@ -1681,7 +1697,6 @@ CONFIG_HID_REDRAGON=y
|
|||||||
|
|
||||||
CONFIG_USB_OHCI_LITTLE_ENDIAN=y
|
CONFIG_USB_OHCI_LITTLE_ENDIAN=y
|
||||||
# CONFIG_USB_SUPPORT is not set
|
# CONFIG_USB_SUPPORT is not set
|
||||||
# CONFIG_UWB is not set
|
|
||||||
# CONFIG_MMC is not set
|
# CONFIG_MMC is not set
|
||||||
# CONFIG_MEMSTICK is not set
|
# CONFIG_MEMSTICK is not set
|
||||||
# CONFIG_NEW_LEDS is not set
|
# CONFIG_NEW_LEDS is not set
|
||||||
@@ -1710,7 +1725,10 @@ CONFIG_DMA_ACPI=y
|
|||||||
CONFIG_DW_DMAC_CORE=y
|
CONFIG_DW_DMAC_CORE=y
|
||||||
# CONFIG_DW_DMAC is not set
|
# CONFIG_DW_DMAC is not set
|
||||||
# CONFIG_DW_DMAC_PCI is not set
|
# CONFIG_DW_DMAC_PCI is not set
|
||||||
|
# CONFIG_DW_EDMA is not set
|
||||||
|
# CONFIG_DW_EDMA_PCIE is not set
|
||||||
CONFIG_HSU_DMA=y
|
CONFIG_HSU_DMA=y
|
||||||
|
# CONFIG_SF_PDMA is not set
|
||||||
|
|
||||||
#
|
#
|
||||||
# DMA Clients
|
# DMA Clients
|
||||||
@@ -1735,6 +1753,17 @@ CONFIG_UIO_DMEM_GENIRQ=y
|
|||||||
# CONFIG_UIO_NETX is not set
|
# CONFIG_UIO_NETX is not set
|
||||||
# CONFIG_UIO_PRUSS is not set
|
# CONFIG_UIO_PRUSS is not set
|
||||||
# CONFIG_UIO_MF624 is not set
|
# CONFIG_UIO_MF624 is not set
|
||||||
|
CONFIG_VFIO_IOMMU_TYPE1=y
|
||||||
|
CONFIG_VFIO_VIRQFD=y
|
||||||
|
CONFIG_VFIO=y
|
||||||
|
# CONFIG_VFIO_NOIOMMU is not set
|
||||||
|
CONFIG_VFIO_PCI=y
|
||||||
|
# CONFIG_VFIO_PCI_VGA is not set
|
||||||
|
CONFIG_VFIO_PCI_MMAP=y
|
||||||
|
CONFIG_VFIO_PCI_INTX=y
|
||||||
|
# CONFIG_VFIO_PCI_IGD is not set
|
||||||
|
# CONFIG_VFIO_MDEV is not set
|
||||||
|
CONFIG_IRQ_BYPASS_MANAGER=y
|
||||||
# CONFIG_VIRT_DRIVERS is not set
|
# CONFIG_VIRT_DRIVERS is not set
|
||||||
CONFIG_VIRTIO=y
|
CONFIG_VIRTIO=y
|
||||||
CONFIG_VIRTIO_MENU=y
|
CONFIG_VIRTIO_MENU=y
|
||||||
@@ -1752,9 +1781,11 @@ CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
|
|||||||
# CONFIG_HYPERV is not set
|
# CONFIG_HYPERV is not set
|
||||||
# end of Microsoft Hyper-V guest support
|
# end of Microsoft Hyper-V guest support
|
||||||
|
|
||||||
|
# CONFIG_GREYBUS is not set
|
||||||
# CONFIG_STAGING is not set
|
# CONFIG_STAGING is not set
|
||||||
# CONFIG_X86_PLATFORM_DEVICES is not set
|
# CONFIG_X86_PLATFORM_DEVICES is not set
|
||||||
CONFIG_PMC_ATOM=y
|
CONFIG_PMC_ATOM=y
|
||||||
|
# CONFIG_MFD_CROS_EC is not set
|
||||||
# CONFIG_CHROME_PLATFORMS is not set
|
# CONFIG_CHROME_PLATFORMS is not set
|
||||||
# CONFIG_MELLANOX_PLATFORM is not set
|
# CONFIG_MELLANOX_PLATFORM is not set
|
||||||
CONFIG_CLKDEV_LOOKUP=y
|
CONFIG_CLKDEV_LOOKUP=y
|
||||||
@@ -1779,6 +1810,8 @@ CONFIG_CLKBLD_I8253=y
|
|||||||
CONFIG_MAILBOX=y
|
CONFIG_MAILBOX=y
|
||||||
CONFIG_PCC=y
|
CONFIG_PCC=y
|
||||||
# CONFIG_ALTERA_MBOX is not set
|
# CONFIG_ALTERA_MBOX is not set
|
||||||
|
CONFIG_IOMMU_IOVA=y
|
||||||
|
CONFIG_IOMMU_API=y
|
||||||
CONFIG_IOMMU_SUPPORT=y
|
CONFIG_IOMMU_SUPPORT=y
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -1787,9 +1820,13 @@ CONFIG_IOMMU_SUPPORT=y
|
|||||||
# end of Generic IOMMU Pagetable Support
|
# end of Generic IOMMU Pagetable Support
|
||||||
|
|
||||||
# CONFIG_IOMMU_DEBUGFS is not set
|
# CONFIG_IOMMU_DEBUGFS is not set
|
||||||
|
# CONFIG_IOMMU_DEFAULT_PASSTHROUGH is not set
|
||||||
|
CONFIG_IOMMU_DMA=y
|
||||||
# CONFIG_AMD_IOMMU is not set
|
# CONFIG_AMD_IOMMU is not set
|
||||||
# CONFIG_INTEL_IOMMU is not set
|
# CONFIG_INTEL_IOMMU is not set
|
||||||
# CONFIG_IRQ_REMAP is not set
|
# CONFIG_IRQ_REMAP is not set
|
||||||
|
CONFIG_VIRTIO_IOMMU=y
|
||||||
|
CONFIG_VIRTIO_IOMMU_TOPOLOGY=y
|
||||||
|
|
||||||
#
|
#
|
||||||
# Remoteproc drivers
|
# Remoteproc drivers
|
||||||
@@ -1835,13 +1872,6 @@ CONFIG_IOMMU_SUPPORT=y
|
|||||||
#
|
#
|
||||||
# end of i.MX SoC drivers
|
# 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
|
# Qualcomm SoC drivers
|
||||||
#
|
#
|
||||||
@@ -1867,12 +1897,10 @@ CONFIG_IOMMU_SUPPORT=y
|
|||||||
#
|
#
|
||||||
# IRQ chip support
|
# IRQ chip support
|
||||||
#
|
#
|
||||||
CONFIG_ARM_GIC_MAX_NR=1
|
|
||||||
# end of IRQ chip support
|
# end of IRQ chip support
|
||||||
|
|
||||||
# CONFIG_IPACK_BUS is not set
|
# CONFIG_IPACK_BUS is not set
|
||||||
# CONFIG_RESET_CONTROLLER is not set
|
# CONFIG_RESET_CONTROLLER is not set
|
||||||
# CONFIG_FMC is not set
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# PHY Subsystem
|
# PHY Subsystem
|
||||||
@@ -1906,6 +1934,9 @@ CONFIG_ND_BLK=y
|
|||||||
CONFIG_ND_CLAIM=y
|
CONFIG_ND_CLAIM=y
|
||||||
CONFIG_ND_BTT=y
|
CONFIG_ND_BTT=y
|
||||||
CONFIG_BTT=y
|
CONFIG_BTT=y
|
||||||
|
CONFIG_ND_PFN=y
|
||||||
|
CONFIG_NVDIMM_PFN=y
|
||||||
|
CONFIG_NVDIMM_DAX=y
|
||||||
CONFIG_DAX_DRIVER=y
|
CONFIG_DAX_DRIVER=y
|
||||||
CONFIG_DAX=y
|
CONFIG_DAX=y
|
||||||
# CONFIG_DEV_DAX is not set
|
# CONFIG_DEV_DAX is not set
|
||||||
@@ -1950,13 +1981,15 @@ CONFIG_FS_MBCACHE=y
|
|||||||
# CONFIG_BTRFS_FS is not set
|
# CONFIG_BTRFS_FS is not set
|
||||||
# CONFIG_NILFS2_FS is not set
|
# CONFIG_NILFS2_FS is not set
|
||||||
# CONFIG_F2FS_FS is not set
|
# CONFIG_F2FS_FS is not set
|
||||||
# CONFIG_FS_DAX is not set
|
CONFIG_FS_DAX=y
|
||||||
|
CONFIG_FS_DAX_PMD=y
|
||||||
CONFIG_FS_POSIX_ACL=y
|
CONFIG_FS_POSIX_ACL=y
|
||||||
CONFIG_EXPORTFS=y
|
CONFIG_EXPORTFS=y
|
||||||
# CONFIG_EXPORTFS_BLOCK_OPS is not set
|
# CONFIG_EXPORTFS_BLOCK_OPS is not set
|
||||||
CONFIG_FILE_LOCKING=y
|
CONFIG_FILE_LOCKING=y
|
||||||
CONFIG_MANDATORY_FILE_LOCKING=y
|
CONFIG_MANDATORY_FILE_LOCKING=y
|
||||||
CONFIG_FS_ENCRYPTION=y
|
CONFIG_FS_ENCRYPTION=y
|
||||||
|
# CONFIG_FS_VERITY is not set
|
||||||
CONFIG_FSNOTIFY=y
|
CONFIG_FSNOTIFY=y
|
||||||
CONFIG_DNOTIFY=y
|
CONFIG_DNOTIFY=y
|
||||||
CONFIG_INOTIFY_USER=y
|
CONFIG_INOTIFY_USER=y
|
||||||
@@ -2014,6 +2047,7 @@ CONFIG_PROC_KCORE=y
|
|||||||
CONFIG_PROC_SYSCTL=y
|
CONFIG_PROC_SYSCTL=y
|
||||||
CONFIG_PROC_PAGE_MONITOR=y
|
CONFIG_PROC_PAGE_MONITOR=y
|
||||||
CONFIG_PROC_CHILDREN=y
|
CONFIG_PROC_CHILDREN=y
|
||||||
|
CONFIG_PROC_PID_ARCH_STATUS=y
|
||||||
CONFIG_KERNFS=y
|
CONFIG_KERNFS=y
|
||||||
CONFIG_SYSFS=y
|
CONFIG_SYSFS=y
|
||||||
CONFIG_TMPFS=y
|
CONFIG_TMPFS=y
|
||||||
@@ -2082,12 +2116,14 @@ CONFIG_NLS_MAC_TURKISH=y
|
|||||||
CONFIG_NLS_UTF8=y
|
CONFIG_NLS_UTF8=y
|
||||||
# CONFIG_DLM is not set
|
# CONFIG_DLM is not set
|
||||||
# CONFIG_UNICODE is not set
|
# CONFIG_UNICODE is not set
|
||||||
|
CONFIG_IO_WQ=y
|
||||||
# end of File systems
|
# end of File systems
|
||||||
|
|
||||||
#
|
#
|
||||||
# Security options
|
# Security options
|
||||||
#
|
#
|
||||||
CONFIG_KEYS=y
|
CONFIG_KEYS=y
|
||||||
|
# CONFIG_KEYS_REQUEST_CACHE is not set
|
||||||
CONFIG_PERSISTENT_KEYRINGS=y
|
CONFIG_PERSISTENT_KEYRINGS=y
|
||||||
# CONFIG_BIG_KEYS is not set
|
# CONFIG_BIG_KEYS is not set
|
||||||
# CONFIG_ENCRYPTED_KEYS is not set
|
# CONFIG_ENCRYPTED_KEYS is not set
|
||||||
@@ -2111,6 +2147,8 @@ CONFIG_LSM="yama,loadpin,safesetid,integrity"
|
|||||||
# Memory initialization
|
# Memory initialization
|
||||||
#
|
#
|
||||||
CONFIG_INIT_STACK_NONE=y
|
CONFIG_INIT_STACK_NONE=y
|
||||||
|
# CONFIG_INIT_ON_ALLOC_DEFAULT_ON is not set
|
||||||
|
# CONFIG_INIT_ON_FREE_DEFAULT_ON is not set
|
||||||
# end of Memory initialization
|
# end of Memory initialization
|
||||||
# end of Kernel hardening options
|
# end of Kernel hardening options
|
||||||
# end of Security options
|
# end of Security options
|
||||||
@@ -2125,8 +2163,8 @@ CONFIG_CRYPTO_ALGAPI=y
|
|||||||
CONFIG_CRYPTO_ALGAPI2=y
|
CONFIG_CRYPTO_ALGAPI2=y
|
||||||
CONFIG_CRYPTO_AEAD=y
|
CONFIG_CRYPTO_AEAD=y
|
||||||
CONFIG_CRYPTO_AEAD2=y
|
CONFIG_CRYPTO_AEAD2=y
|
||||||
CONFIG_CRYPTO_BLKCIPHER=y
|
CONFIG_CRYPTO_SKCIPHER=y
|
||||||
CONFIG_CRYPTO_BLKCIPHER2=y
|
CONFIG_CRYPTO_SKCIPHER2=y
|
||||||
CONFIG_CRYPTO_HASH=y
|
CONFIG_CRYPTO_HASH=y
|
||||||
CONFIG_CRYPTO_HASH2=y
|
CONFIG_CRYPTO_HASH2=y
|
||||||
CONFIG_CRYPTO_RNG=y
|
CONFIG_CRYPTO_RNG=y
|
||||||
@@ -2144,7 +2182,6 @@ CONFIG_CRYPTO_GF128MUL=y
|
|||||||
CONFIG_CRYPTO_NULL=y
|
CONFIG_CRYPTO_NULL=y
|
||||||
CONFIG_CRYPTO_NULL2=y
|
CONFIG_CRYPTO_NULL2=y
|
||||||
# CONFIG_CRYPTO_PCRYPT is not set
|
# CONFIG_CRYPTO_PCRYPT is not set
|
||||||
CONFIG_CRYPTO_WORKQUEUE=y
|
|
||||||
CONFIG_CRYPTO_CRYPTD=y
|
CONFIG_CRYPTO_CRYPTD=y
|
||||||
# CONFIG_CRYPTO_AUTHENC is not set
|
# CONFIG_CRYPTO_AUTHENC is not set
|
||||||
CONFIG_CRYPTO_SIMD=y
|
CONFIG_CRYPTO_SIMD=y
|
||||||
@@ -2157,6 +2194,8 @@ CONFIG_CRYPTO_GLUE_HELPER_X86=y
|
|||||||
# CONFIG_CRYPTO_DH is not set
|
# CONFIG_CRYPTO_DH is not set
|
||||||
# CONFIG_CRYPTO_ECDH is not set
|
# CONFIG_CRYPTO_ECDH is not set
|
||||||
# CONFIG_CRYPTO_ECRDSA 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
|
# Authenticated Encryption with Associated Data
|
||||||
@@ -2165,16 +2204,7 @@ CONFIG_CRYPTO_GLUE_HELPER_X86=y
|
|||||||
CONFIG_CRYPTO_GCM=y
|
CONFIG_CRYPTO_GCM=y
|
||||||
# CONFIG_CRYPTO_CHACHA20POLY1305 is not set
|
# CONFIG_CRYPTO_CHACHA20POLY1305 is not set
|
||||||
# CONFIG_CRYPTO_AEGIS128 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_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_SEQIV=y
|
||||||
CONFIG_CRYPTO_ECHAINIV=y
|
CONFIG_CRYPTO_ECHAINIV=y
|
||||||
|
|
||||||
@@ -2194,6 +2224,7 @@ CONFIG_CRYPTO_XTS=y
|
|||||||
# CONFIG_CRYPTO_NHPOLY1305_SSE2 is not set
|
# CONFIG_CRYPTO_NHPOLY1305_SSE2 is not set
|
||||||
# CONFIG_CRYPTO_NHPOLY1305_AVX2 is not set
|
# CONFIG_CRYPTO_NHPOLY1305_AVX2 is not set
|
||||||
# CONFIG_CRYPTO_ADIANTUM is not set
|
# CONFIG_CRYPTO_ADIANTUM is not set
|
||||||
|
# CONFIG_CRYPTO_ESSIV is not set
|
||||||
|
|
||||||
#
|
#
|
||||||
# Hash modes
|
# Hash modes
|
||||||
@@ -2210,6 +2241,10 @@ CONFIG_CRYPTO_CRC32C=y
|
|||||||
CONFIG_CRYPTO_CRC32C_INTEL=y
|
CONFIG_CRYPTO_CRC32C_INTEL=y
|
||||||
# CONFIG_CRYPTO_CRC32 is not set
|
# CONFIG_CRYPTO_CRC32 is not set
|
||||||
# CONFIG_CRYPTO_CRC32_PCLMUL 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=y
|
||||||
# CONFIG_CRYPTO_CRCT10DIF_PCLMUL is not set
|
# CONFIG_CRYPTO_CRCT10DIF_PCLMUL is not set
|
||||||
CONFIG_CRYPTO_GHASH=y
|
CONFIG_CRYPTO_GHASH=y
|
||||||
@@ -2240,7 +2275,6 @@ CONFIG_CRYPTO_SHA512=y
|
|||||||
#
|
#
|
||||||
CONFIG_CRYPTO_AES=y
|
CONFIG_CRYPTO_AES=y
|
||||||
# CONFIG_CRYPTO_AES_TI is not set
|
# CONFIG_CRYPTO_AES_TI is not set
|
||||||
CONFIG_CRYPTO_AES_X86_64=y
|
|
||||||
CONFIG_CRYPTO_AES_NI_INTEL=y
|
CONFIG_CRYPTO_AES_NI_INTEL=y
|
||||||
# CONFIG_CRYPTO_ANUBIS is not set
|
# CONFIG_CRYPTO_ANUBIS is not set
|
||||||
CONFIG_CRYPTO_ARC4=y
|
CONFIG_CRYPTO_ARC4=y
|
||||||
@@ -2299,6 +2333,21 @@ CONFIG_CRYPTO_USER_API=y
|
|||||||
# CONFIG_CRYPTO_USER_API_SKCIPHER is not set
|
# CONFIG_CRYPTO_USER_API_SKCIPHER is not set
|
||||||
CONFIG_CRYPTO_USER_API_RNG=y
|
CONFIG_CRYPTO_USER_API_RNG=y
|
||||||
# CONFIG_CRYPTO_USER_API_AEAD is not set
|
# 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=4
|
||||||
|
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_CRYPTO_HW is not set
|
||||||
# CONFIG_ASYMMETRIC_KEY_TYPE is not set
|
# CONFIG_ASYMMETRIC_KEY_TYPE is not set
|
||||||
|
|
||||||
@@ -2346,6 +2395,7 @@ CONFIG_ZLIB_DEFLATE=y
|
|||||||
CONFIG_LZO_COMPRESS=y
|
CONFIG_LZO_COMPRESS=y
|
||||||
CONFIG_LZO_DECOMPRESS=y
|
CONFIG_LZO_DECOMPRESS=y
|
||||||
# CONFIG_XZ_DEC is not set
|
# CONFIG_XZ_DEC is not set
|
||||||
|
CONFIG_INTERVAL_TREE=y
|
||||||
CONFIG_XARRAY_MULTI=y
|
CONFIG_XARRAY_MULTI=y
|
||||||
CONFIG_ASSOCIATIVE_ARRAY=y
|
CONFIG_ASSOCIATIVE_ARRAY=y
|
||||||
CONFIG_HAS_IOMEM=y
|
CONFIG_HAS_IOMEM=y
|
||||||
@@ -2354,20 +2404,23 @@ CONFIG_HAS_DMA=y
|
|||||||
CONFIG_NEED_SG_DMA_LENGTH=y
|
CONFIG_NEED_SG_DMA_LENGTH=y
|
||||||
CONFIG_NEED_DMA_MAP_STATE=y
|
CONFIG_NEED_DMA_MAP_STATE=y
|
||||||
CONFIG_ARCH_DMA_ADDR_T_64BIT=y
|
CONFIG_ARCH_DMA_ADDR_T_64BIT=y
|
||||||
|
CONFIG_ARCH_HAS_FORCE_DMA_UNENCRYPTED=y
|
||||||
CONFIG_SWIOTLB=y
|
CONFIG_SWIOTLB=y
|
||||||
# CONFIG_DMA_API_DEBUG is not set
|
# CONFIG_DMA_API_DEBUG is not set
|
||||||
CONFIG_SGL_ALLOC=y
|
CONFIG_SGL_ALLOC=y
|
||||||
CONFIG_CPU_RMAP=y
|
CONFIG_CPU_RMAP=y
|
||||||
CONFIG_DQL=y
|
CONFIG_DQL=y
|
||||||
CONFIG_NLATTR=y
|
CONFIG_NLATTR=y
|
||||||
# CONFIG_DDR is not set
|
|
||||||
CONFIG_IRQ_POLL=y
|
CONFIG_IRQ_POLL=y
|
||||||
CONFIG_UCS2_STRING=y
|
CONFIG_UCS2_STRING=y
|
||||||
|
CONFIG_HAVE_GENERIC_VDSO=y
|
||||||
|
CONFIG_GENERIC_GETTIMEOFDAY=y
|
||||||
CONFIG_FONT_SUPPORT=y
|
CONFIG_FONT_SUPPORT=y
|
||||||
# CONFIG_FONTS is not set
|
# CONFIG_FONTS is not set
|
||||||
CONFIG_FONT_8x8=y
|
CONFIG_FONT_8x8=y
|
||||||
CONFIG_FONT_8x16=y
|
CONFIG_FONT_8x16=y
|
||||||
CONFIG_ARCH_HAS_PMEM_API=y
|
CONFIG_ARCH_HAS_PMEM_API=y
|
||||||
|
CONFIG_MEMREGION=y
|
||||||
CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE=y
|
CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE=y
|
||||||
CONFIG_ARCH_STACKWALK=y
|
CONFIG_ARCH_STACKWALK=y
|
||||||
CONFIG_SBITMAP=y
|
CONFIG_SBITMAP=y
|
||||||
@@ -2388,6 +2441,8 @@ CONFIG_CONSOLE_LOGLEVEL_QUIET=4
|
|||||||
CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4
|
CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4
|
||||||
# CONFIG_BOOT_PRINTK_DELAY is not set
|
# CONFIG_BOOT_PRINTK_DELAY is not set
|
||||||
CONFIG_DYNAMIC_DEBUG=y
|
CONFIG_DYNAMIC_DEBUG=y
|
||||||
|
CONFIG_SYMBOLIC_ERRNAME=y
|
||||||
|
# CONFIG_DEBUG_BUGVERBOSE is not set
|
||||||
# end of printk and dmesg options
|
# end of printk and dmesg options
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -2398,9 +2453,7 @@ CONFIG_DYNAMIC_DEBUG=y
|
|||||||
CONFIG_FRAME_WARN=2048
|
CONFIG_FRAME_WARN=2048
|
||||||
CONFIG_STRIP_ASM_SYMS=y
|
CONFIG_STRIP_ASM_SYMS=y
|
||||||
# CONFIG_READABLE_ASM is not set
|
# CONFIG_READABLE_ASM is not set
|
||||||
CONFIG_UNUSED_SYMBOLS=y
|
# CONFIG_HEADERS_INSTALL is not set
|
||||||
CONFIG_DEBUG_FS=y
|
|
||||||
# CONFIG_HEADERS_CHECK is not set
|
|
||||||
CONFIG_OPTIMIZE_INLINING=y
|
CONFIG_OPTIMIZE_INLINING=y
|
||||||
CONFIG_DEBUG_SECTION_MISMATCH=y
|
CONFIG_DEBUG_SECTION_MISMATCH=y
|
||||||
CONFIG_SECTION_MISMATCH_WARN_ONLY=y
|
CONFIG_SECTION_MISMATCH_WARN_ONLY=y
|
||||||
@@ -2409,9 +2462,20 @@ CONFIG_STACK_VALIDATION=y
|
|||||||
# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set
|
# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set
|
||||||
# end of Compile-time checks and compiler options
|
# end of Compile-time checks and compiler options
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generic Kernel Debugging Instruments
|
||||||
|
#
|
||||||
CONFIG_MAGIC_SYSRQ=y
|
CONFIG_MAGIC_SYSRQ=y
|
||||||
CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1
|
CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1
|
||||||
CONFIG_MAGIC_SYSRQ_SERIAL=y
|
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_KERNEL=y
|
||||||
CONFIG_DEBUG_MISC=y
|
CONFIG_DEBUG_MISC=y
|
||||||
|
|
||||||
@@ -2429,39 +2493,42 @@ CONFIG_DEBUG_MISC=y
|
|||||||
CONFIG_HAVE_DEBUG_KMEMLEAK=y
|
CONFIG_HAVE_DEBUG_KMEMLEAK=y
|
||||||
# CONFIG_DEBUG_KMEMLEAK is not set
|
# CONFIG_DEBUG_KMEMLEAK is not set
|
||||||
# CONFIG_DEBUG_STACK_USAGE 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_DEBUG_VM is not set
|
||||||
CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y
|
CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y
|
||||||
# CONFIG_DEBUG_VIRTUAL is not set
|
# CONFIG_DEBUG_VIRTUAL is not set
|
||||||
CONFIG_DEBUG_MEMORY_INIT=y
|
CONFIG_DEBUG_MEMORY_INIT=y
|
||||||
# CONFIG_DEBUG_PER_CPU_MAPS is not set
|
# CONFIG_DEBUG_PER_CPU_MAPS is not set
|
||||||
CONFIG_HAVE_ARCH_KASAN=y
|
CONFIG_HAVE_ARCH_KASAN=y
|
||||||
|
CONFIG_HAVE_ARCH_KASAN_VMALLOC=y
|
||||||
CONFIG_CC_HAS_KASAN_GENERIC=y
|
CONFIG_CC_HAS_KASAN_GENERIC=y
|
||||||
# CONFIG_KASAN is not set
|
# CONFIG_KASAN is not set
|
||||||
CONFIG_KASAN_STACK=1
|
CONFIG_KASAN_STACK=1
|
||||||
# end of Memory Debugging
|
# 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
|
# 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_SOFTLOCKUP_DETECTOR is not set
|
||||||
CONFIG_HARDLOCKUP_CHECK_TIMESTAMP=y
|
CONFIG_HARDLOCKUP_CHECK_TIMESTAMP=y
|
||||||
# CONFIG_HARDLOCKUP_DETECTOR is not set
|
# CONFIG_HARDLOCKUP_DETECTOR is not set
|
||||||
# CONFIG_DETECT_HUNG_TASK is not set
|
# CONFIG_DETECT_HUNG_TASK is not set
|
||||||
# CONFIG_WQ_WATCHDOG 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
|
# Scheduler Debugging
|
||||||
CONFIG_PANIC_TIMEOUT=0
|
#
|
||||||
# CONFIG_SCHED_DEBUG is not set
|
# CONFIG_SCHED_DEBUG is not set
|
||||||
CONFIG_SCHED_INFO=y
|
CONFIG_SCHED_INFO=y
|
||||||
# CONFIG_SCHEDSTATS is not set
|
# CONFIG_SCHEDSTATS is not set
|
||||||
# CONFIG_SCHED_STACK_END_CHECK is not set
|
# end of Scheduler Debugging
|
||||||
|
|
||||||
# CONFIG_DEBUG_TIMEKEEPING is not set
|
# CONFIG_DEBUG_TIMEKEEPING is not set
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -2485,11 +2552,17 @@ CONFIG_LOCK_DEBUGGING_SUPPORT=y
|
|||||||
CONFIG_STACKTRACE=y
|
CONFIG_STACKTRACE=y
|
||||||
# CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set
|
# CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set
|
||||||
# CONFIG_DEBUG_KOBJECT is not set
|
# CONFIG_DEBUG_KOBJECT is not set
|
||||||
CONFIG_DEBUG_BUGVERBOSE=y
|
|
||||||
|
#
|
||||||
|
# Debug kernel data structures
|
||||||
|
#
|
||||||
CONFIG_DEBUG_LIST=y
|
CONFIG_DEBUG_LIST=y
|
||||||
# CONFIG_DEBUG_PLIST is not set
|
# CONFIG_DEBUG_PLIST is not set
|
||||||
# CONFIG_DEBUG_SG is not set
|
# CONFIG_DEBUG_SG is not set
|
||||||
# CONFIG_DEBUG_NOTIFIERS 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
|
# CONFIG_DEBUG_CREDENTIALS is not set
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -2505,14 +2578,13 @@ CONFIG_RCU_CPU_STALL_TIMEOUT=59
|
|||||||
# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set
|
# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set
|
||||||
# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set
|
# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set
|
||||||
# CONFIG_CPU_HOTPLUG_STATE_CONTROL 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_LATENCYTOP is not set
|
||||||
CONFIG_USER_STACKTRACE_SUPPORT=y
|
CONFIG_USER_STACKTRACE_SUPPORT=y
|
||||||
CONFIG_HAVE_FUNCTION_TRACER=y
|
CONFIG_HAVE_FUNCTION_TRACER=y
|
||||||
CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y
|
CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y
|
||||||
CONFIG_HAVE_DYNAMIC_FTRACE=y
|
CONFIG_HAVE_DYNAMIC_FTRACE=y
|
||||||
CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y
|
CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y
|
||||||
|
CONFIG_HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y
|
||||||
CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
|
CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
|
||||||
CONFIG_HAVE_SYSCALL_TRACEPOINTS=y
|
CONFIG_HAVE_SYSCALL_TRACEPOINTS=y
|
||||||
CONFIG_HAVE_FENTRY=y
|
CONFIG_HAVE_FENTRY=y
|
||||||
@@ -2520,12 +2592,24 @@ CONFIG_HAVE_C_RECORDMCOUNT=y
|
|||||||
CONFIG_TRACING_SUPPORT=y
|
CONFIG_TRACING_SUPPORT=y
|
||||||
# CONFIG_FTRACE is not set
|
# CONFIG_FTRACE is not set
|
||||||
# CONFIG_PROVIDE_OHCI1394_DMA_INIT is not set
|
# CONFIG_PROVIDE_OHCI1394_DMA_INIT is not set
|
||||||
|
# CONFIG_KUNIT is not set
|
||||||
|
# CONFIG_NOTIFIER_ERROR_INJECTION is not set
|
||||||
|
# CONFIG_FAULT_INJECTION is not set
|
||||||
|
# end of Kernel hacking
|
||||||
|
|
||||||
|
#
|
||||||
|
# Kernel Testing and Coverage
|
||||||
|
#
|
||||||
|
CONFIG_ARCH_HAS_KCOV=y
|
||||||
|
CONFIG_CC_HAS_SANCOV_TRACE_PC=y
|
||||||
|
# CONFIG_KCOV is not set
|
||||||
CONFIG_RUNTIME_TESTING_MENU=y
|
CONFIG_RUNTIME_TESTING_MENU=y
|
||||||
# CONFIG_LKDTM is not set
|
# CONFIG_LKDTM is not set
|
||||||
# CONFIG_TEST_LIST_SORT is not set
|
# CONFIG_TEST_LIST_SORT is not set
|
||||||
# CONFIG_TEST_SORT is not set
|
# CONFIG_TEST_SORT is not set
|
||||||
# CONFIG_BACKTRACE_SELF_TEST is not set
|
# CONFIG_BACKTRACE_SELF_TEST is not set
|
||||||
# CONFIG_RBTREE_TEST is not set
|
# CONFIG_RBTREE_TEST is not set
|
||||||
|
# CONFIG_REED_SOLOMON_TEST is not set
|
||||||
# CONFIG_INTERVAL_TREE_TEST is not set
|
# CONFIG_INTERVAL_TREE_TEST is not set
|
||||||
# CONFIG_ATOMIC64_SELFTEST is not set
|
# CONFIG_ATOMIC64_SELFTEST is not set
|
||||||
# CONFIG_TEST_HEXDUMP is not set
|
# CONFIG_TEST_HEXDUMP is not set
|
||||||
@@ -2547,17 +2631,16 @@ CONFIG_RUNTIME_TESTING_MENU=y
|
|||||||
# CONFIG_TEST_UDELAY is not set
|
# CONFIG_TEST_UDELAY is not set
|
||||||
# CONFIG_TEST_MEMCAT_P is not set
|
# CONFIG_TEST_MEMCAT_P is not set
|
||||||
# CONFIG_TEST_STACKINIT is not set
|
# CONFIG_TEST_STACKINIT is not set
|
||||||
|
# CONFIG_TEST_MEMINIT is not set
|
||||||
# CONFIG_MEMTEST is not set
|
# CONFIG_MEMTEST is not set
|
||||||
CONFIG_BUG_ON_DATA_CORRUPTION=y
|
|
||||||
# CONFIG_SAMPLES is not set
|
# 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_ARCH_HAS_DEVMEM_IS_ALLOWED=y
|
||||||
CONFIG_STRICT_DEVMEM=y
|
CONFIG_STRICT_DEVMEM=y
|
||||||
# CONFIG_IO_STRICT_DEVMEM is not set
|
# CONFIG_IO_STRICT_DEVMEM is not set
|
||||||
|
|
||||||
|
#
|
||||||
|
# x86 Debugging
|
||||||
|
#
|
||||||
CONFIG_TRACE_IRQFLAGS_SUPPORT=y
|
CONFIG_TRACE_IRQFLAGS_SUPPORT=y
|
||||||
CONFIG_X86_VERBOSE_BOOTUP=y
|
CONFIG_X86_VERBOSE_BOOTUP=y
|
||||||
CONFIG_EARLY_PRINTK=y
|
CONFIG_EARLY_PRINTK=y
|
||||||
@@ -2569,15 +2652,11 @@ CONFIG_EARLY_PRINTK=y
|
|||||||
CONFIG_DOUBLEFAULT=y
|
CONFIG_DOUBLEFAULT=y
|
||||||
# CONFIG_DEBUG_TLBFLUSH is not set
|
# CONFIG_DEBUG_TLBFLUSH is not set
|
||||||
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
|
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
|
||||||
CONFIG_IO_DELAY_TYPE_0X80=0
|
# CONFIG_X86_DECODER_SELFTEST is not set
|
||||||
CONFIG_IO_DELAY_TYPE_0XED=1
|
|
||||||
CONFIG_IO_DELAY_TYPE_UDELAY=2
|
|
||||||
CONFIG_IO_DELAY_TYPE_NONE=3
|
|
||||||
CONFIG_IO_DELAY_0X80=y
|
CONFIG_IO_DELAY_0X80=y
|
||||||
# CONFIG_IO_DELAY_0XED is not set
|
# CONFIG_IO_DELAY_0XED is not set
|
||||||
# CONFIG_IO_DELAY_UDELAY is not set
|
# CONFIG_IO_DELAY_UDELAY is not set
|
||||||
# CONFIG_IO_DELAY_NONE is not set
|
# CONFIG_IO_DELAY_NONE is not set
|
||||||
CONFIG_DEFAULT_IO_DELAY_TYPE=0
|
|
||||||
# CONFIG_DEBUG_BOOT_PARAMS is not set
|
# CONFIG_DEBUG_BOOT_PARAMS is not set
|
||||||
# CONFIG_CPA_DEBUG is not set
|
# CONFIG_CPA_DEBUG is not set
|
||||||
# CONFIG_DEBUG_ENTRY is not set
|
# CONFIG_DEBUG_ENTRY is not set
|
||||||
@@ -2586,4 +2665,6 @@ CONFIG_DEFAULT_IO_DELAY_TYPE=0
|
|||||||
# CONFIG_PUNIT_ATOM_DEBUG is not set
|
# CONFIG_PUNIT_ATOM_DEBUG is not set
|
||||||
# CONFIG_UNWINDER_ORC is not set
|
# CONFIG_UNWINDER_ORC is not set
|
||||||
CONFIG_UNWINDER_FRAME_POINTER=y
|
CONFIG_UNWINDER_FRAME_POINTER=y
|
||||||
# end of Kernel hacking
|
# CONFIG_UNWINDER_GUESS is not set
|
||||||
|
# end of x86 Debugging
|
||||||
|
# end of Kernel Testing and Coverage
|
||||||
13
scripts/create-cloud-init.sh
Executable file
13
scripts/create-cloud-init.sh
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -x
|
||||||
|
|
||||||
|
rm /tmp/clear-cloudinit.img
|
||||||
|
mkdosfs -n config-2 -C /tmp/clear-cloudinit.img 8192
|
||||||
|
mcopy -oi /tmp/clear-cloudinit.img -s test_data/cloud-init/clear/openstack ::
|
||||||
|
|
||||||
|
rm /tmp/ubuntu-cloudinit.img
|
||||||
|
mkdosfs -n cidata -C /tmp/ubuntu-cloudinit.img 8192
|
||||||
|
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/user-data ::
|
||||||
|
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/meta-data ::
|
||||||
|
mcopy -oi /tmp/ubuntu-cloudinit.img -s test_data/cloud-init/ubuntu/network-config ::
|
||||||
|
|
||||||
314
scripts/dev_cli.sh
Executable file
314
scripts/dev_cli.sh
Executable file
@@ -0,0 +1,314 @@
|
|||||||
|
#!/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_TARGET="${CTR_CLH_ROOT_DIR}/build/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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 \
|
||||||
|
-ti \
|
||||||
|
--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[@]}"
|
||||||
|
|
||||||
|
ret=$?
|
||||||
|
|
||||||
|
# If `cargo build` was successful, let's copy the binaries to a more
|
||||||
|
# accessible location.
|
||||||
|
[ $ret -eq 0 ] && {
|
||||||
|
cargo_bin_dir="$CLH_CARGO_TARGET/$build"
|
||||||
|
say "Binaries placed under $cargo_bin_dir"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_clean() {
|
||||||
|
cargo_args=("$@")
|
||||||
|
|
||||||
|
$DOCKER_RUNTIME run \
|
||||||
|
-ti \
|
||||||
|
--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 \
|
||||||
|
-ti \
|
||||||
|
--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 "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$cargo" = true ] ; then
|
||||||
|
say "Running cargo tests..."
|
||||||
|
$DOCKER_RUNTIME run \
|
||||||
|
-ti \
|
||||||
|
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||||
|
--rm \
|
||||||
|
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||||
|
"$CTR_IMAGE" \
|
||||||
|
./scripts/run_cargo_tests.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$integration" = true ] ; then
|
||||||
|
say "Running integration tests..."
|
||||||
|
$DOCKER_RUNTIME run \
|
||||||
|
-ti \
|
||||||
|
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||||
|
--rm \
|
||||||
|
--privileged \
|
||||||
|
--volume /dev:/dev \
|
||||||
|
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||||
|
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||||
|
"$CTR_IMAGE" \
|
||||||
|
./scripts/run_integration_tests.sh "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
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,40 +6,73 @@ source $HOME/.cargo/env
|
|||||||
WORKLOADS_DIR="$HOME/workloads"
|
WORKLOADS_DIR="$HOME/workloads"
|
||||||
mkdir -p "$WORKLOADS_DIR"
|
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://.*[^ "]')
|
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"
|
FW="$WORKLOADS_DIR/hypervisor-fw"
|
||||||
if [ ! -f "$FW" ]; then
|
if [ ! -f "$FW" ]; then
|
||||||
pushd $WORKLOADS_DIR
|
pushd $WORKLOADS_DIR
|
||||||
wget --quiet $FW_URL
|
time wget --quiet $FW_URL
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
OVMF_URL="https://cdn.download.clearlinux.org/image/OVMF.fd"
|
CLEAR_OS_IMAGE_NAME="clear-31310-cloudguest.img"
|
||||||
OVMF="$WORKLOADS_DIR/OVMF.fd"
|
CLEAR_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$CLEAR_OS_IMAGE_NAME"
|
||||||
if [ ! -f "$OVMF" ]; then
|
CLEAR_OS_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_NAME"
|
||||||
|
if [ ! -f "$CLEAR_OS_IMAGE" ]; then
|
||||||
pushd $WORKLOADS_DIR
|
pushd $WORKLOADS_DIR
|
||||||
wget --quiet $OVMF_URL
|
time wget --quiet $CLEAR_OS_IMAGE_URL
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
OS_IMAGE_NAME="clear-29810-cloud.img"
|
CLEAR_OS_RAW_IMAGE_NAME="clear-31310-cloudguest-raw.img"
|
||||||
OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$OS_IMAGE_NAME.xz"
|
CLEAR_OS_RAW_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_RAW_IMAGE_NAME"
|
||||||
OS_IMAGE="$WORKLOADS_DIR/$OS_IMAGE_NAME"
|
if [ ! -f "$CLEAR_OS_RAW_IMAGE" ]; then
|
||||||
if [ ! -f "$OS_IMAGE" ]; then
|
|
||||||
pushd $WORKLOADS_DIR
|
pushd $WORKLOADS_DIR
|
||||||
wget --quiet $OS_IMAGE_URL
|
time qemu-img convert -p -f qcow2 -O raw $CLEAR_OS_IMAGE_NAME $CLEAR_OS_RAW_IMAGE_NAME
|
||||||
unxz $OS_IMAGE_NAME.xz
|
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
OS_RAW_IMAGE_NAME="clear-29810-cloud-raw.img"
|
BIONIC_OS_IMAGE_NAME="bionic-server-cloudimg-amd64.img"
|
||||||
OS_RAW_IMAGE="$WORKLOADS_DIR/$OS_RAW_IMAGE_NAME"
|
BIONIC_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$BIONIC_OS_IMAGE_NAME"
|
||||||
if [ ! -f "$OS_RAW_IMAGE" ]; then
|
BIONIC_OS_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_IMAGE_NAME"
|
||||||
|
if [ ! -f "$BIONIC_OS_IMAGE" ]; then
|
||||||
pushd $WORKLOADS_DIR
|
pushd $WORKLOADS_DIR
|
||||||
qemu-img convert -p -f qcow2 -O raw $OS_IMAGE_NAME $OS_RAW_IMAGE_NAME
|
time wget --quiet $BIONIC_OS_IMAGE_URL
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
time qemu-img convert -p -f qcow2 -O raw $BIONIC_OS_IMAGE_NAME $BIONIC_OS_RAW_IMAGE_NAME
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
EOAN_OS_IMAGE_NAME="eoan-server-cloudimg-amd64.img"
|
||||||
|
EOAN_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$EOAN_OS_IMAGE_NAME"
|
||||||
|
EOAN_OS_IMAGE="$WORKLOADS_DIR/$EOAN_OS_IMAGE_NAME"
|
||||||
|
if [ ! -f "$EOAN_OS_IMAGE" ]; then
|
||||||
|
pushd $WORKLOADS_DIR
|
||||||
|
time wget --quiet $EOAN_OS_IMAGE_URL
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
time qemu-img convert -p -f qcow2 -O raw $EOAN_OS_IMAGE_NAME $EOAN_OS_RAW_IMAGE_NAME
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
||||||
|
pushd $WORKLOADS_DIR
|
||||||
|
curl --silent "https://cloudhypervisorstorage.blob.core.windows.net/images/sha1sums" | sha1sum --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
|
# Build custom kernel based on virtio-pmem and virtio-fs upstream patches
|
||||||
VMLINUX_IMAGE="$WORKLOADS_DIR/vmlinux"
|
VMLINUX_IMAGE="$WORKLOADS_DIR/vmlinux"
|
||||||
@@ -50,25 +83,44 @@ LINUX_CUSTOM_DIR="linux-custom"
|
|||||||
if [ ! -f "$VMLINUX_IMAGE" ]; then
|
if [ ! -f "$VMLINUX_IMAGE" ]; then
|
||||||
SRCDIR=$PWD
|
SRCDIR=$PWD
|
||||||
pushd $WORKLOADS_DIR
|
pushd $WORKLOADS_DIR
|
||||||
git clone --depth 1 "https://github.com/sboeuf/linux.git" -b "virtio-pmem_and_virtio-fs" $LINUX_CUSTOM_DIR
|
time git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b "virtio-fs-virtio-iommu-topo-5.5-rc1" $LINUX_CUSTOM_DIR
|
||||||
pushd $LINUX_CUSTOM_DIR
|
pushd $LINUX_CUSTOM_DIR
|
||||||
cp $SRCDIR/resources/linux-virtio-pmem-and-virtio-fs-config .config
|
cp $SRCDIR/resources/linux-virtio-fs-virtio-iommu-config .config
|
||||||
make bzImage -j `nproc`
|
time make bzImage -j `nproc`
|
||||||
cp vmlinux $VMLINUX_IMAGE
|
cp vmlinux $VMLINUX_IMAGE
|
||||||
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE
|
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE
|
||||||
popd
|
popd
|
||||||
rm -r $LINUX_CUSTOM_DIR
|
rm -rf $LINUX_CUSTOM_DIR
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
VIRTIOFSD_URL="$(curl --silent https://api.github.com/repos/intel/nemu/releases/latest | grep "browser_download_url" | grep "virtiofsd-x86_64" | grep -o 'https://.*[^ "]')"
|
|
||||||
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
|
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
|
||||||
|
QEMU_DIR="qemu_build"
|
||||||
if [ ! -f "$VIRTIOFSD" ]; then
|
if [ ! -f "$VIRTIOFSD" ]; then
|
||||||
pushd $WORKLOADS_DIR
|
pushd $WORKLOADS_DIR
|
||||||
wget --quiet $VIRTIOFSD_URL -O "virtiofsd"
|
git clone --depth 1 "https://github.com/sboeuf/qemu.git" -b "virtio-fs" $QEMU_DIR
|
||||||
chmod +x "virtiofsd"
|
pushd $QEMU_DIR
|
||||||
sudo setcap cap_sys_admin+epi "virtiofsd"
|
time ./configure --prefix=$PWD --target-list=x86_64-softmmu
|
||||||
|
time make virtiofsd -j `nproc`
|
||||||
|
cp virtiofsd $VIRTIOFSD
|
||||||
popd
|
popd
|
||||||
|
rm -rf $QEMU_DIR
|
||||||
|
sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd"
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
||||||
|
BLK_IMAGE="$WORKLOADS_DIR/blk.img"
|
||||||
|
MNT_DIR="mount_image"
|
||||||
|
if [ ! -f "$BLK_IMAGE" ]; then
|
||||||
|
pushd $WORKLOADS_DIR
|
||||||
|
fallocate -l 16M $BLK_IMAGE
|
||||||
|
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 umount $BLK_IMAGE
|
||||||
|
rm -r $MNT_DIR
|
||||||
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SHARED_DIR="$WORKLOADS_DIR/shared_dir"
|
SHARED_DIR="$WORKLOADS_DIR/shared_dir"
|
||||||
@@ -79,18 +131,17 @@ if [ ! -d "$SHARED_DIR" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
VFIO_DIR="$WORKLOADS_DIR/vfio"
|
VFIO_DIR="$WORKLOADS_DIR/vfio"
|
||||||
if [ ! -d "$VFIO_DIR" ]; then
|
rm -rf $VFIO_DIR
|
||||||
mkdir -p $VFIO_DIR
|
mkdir -p $VFIO_DIR
|
||||||
cp $OS_IMAGE $VFIO_DIR
|
cp $CLEAR_OS_IMAGE $VFIO_DIR
|
||||||
cp $FW $VFIO_DIR
|
cp $FW $VFIO_DIR
|
||||||
cp $VMLINUX_IMAGE $VFIO_DIR
|
cp $VMLINUX_IMAGE $VFIO_DIR
|
||||||
fi
|
|
||||||
|
|
||||||
# VFIO test network setup.
|
# VFIO test network setup.
|
||||||
# We reserve a different IP class for it: 172.16.0.0/24.
|
# We reserve a different IP class for it: 172.17.0.0/24.
|
||||||
sudo ip link add name vfio-br0 type bridge
|
sudo ip link add name vfio-br0 type bridge
|
||||||
sudo ip link set vfio-br0 up
|
sudo ip link set vfio-br0 up
|
||||||
sudo ip addr add 172.16.0.1/24 dev vfio-br0
|
sudo ip addr add 172.17.0.1/24 dev vfio-br0
|
||||||
|
|
||||||
sudo ip tuntap add vfio-tap0 mode tap
|
sudo ip tuntap add vfio-tap0 mode tap
|
||||||
sudo ip link set vfio-tap0 master vfio-br0
|
sudo ip link set vfio-tap0 master vfio-br0
|
||||||
@@ -100,20 +151,56 @@ sudo ip tuntap add vfio-tap1 mode tap
|
|||||||
sudo ip link set vfio-tap1 master vfio-br0
|
sudo ip link set vfio-tap1 master vfio-br0
|
||||||
sudo ip link set vfio-tap1 up
|
sudo ip link set vfio-tap1 up
|
||||||
|
|
||||||
cargo build
|
sudo ip tuntap add vfio-tap2 mode tap
|
||||||
sudo setcap cap_net_admin+ep target/debug/cloud-hypervisor
|
sudo ip link set vfio-tap2 master vfio-br0
|
||||||
|
sudo ip link set vfio-tap2 up
|
||||||
|
|
||||||
|
cargo build --release
|
||||||
|
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor
|
||||||
|
sudo setcap cap_net_admin+ep target/release/vhost_user_net
|
||||||
|
sudo setcap cap_dac_override,cap_sys_admin+epi target/release/vhost_user_fs
|
||||||
|
|
||||||
# We always copy a fresh version of our binary for our L2 guest.
|
# 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
|
||||||
# We need qemu to have NET_ADMIN as well.
|
|
||||||
sudo setcap cap_net_admin+ep /usr/bin/qemu-system-x86_64
|
# 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
|
||||||
|
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
|
sudo adduser $USER kvm
|
||||||
newgrp kvm << EOF
|
newgrp kvm << EOF
|
||||||
cargo test --features "integration_tests"
|
export RUST_BACKTRACE=1
|
||||||
|
time cargo test --features "integration_tests" "$@" -- --nocapture
|
||||||
EOF
|
EOF
|
||||||
|
RES=$?
|
||||||
|
|
||||||
|
if [ $RES -eq 0 ]; then
|
||||||
|
# virtio-mmio based testing
|
||||||
|
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
|
||||||
|
time cargo test --features "integration_tests,mmio" "$@" -- --nocapture
|
||||||
|
EOF
|
||||||
|
|
||||||
|
RES=$?
|
||||||
|
fi
|
||||||
|
|
||||||
# Tear VFIO test network down
|
# Tear VFIO test network down
|
||||||
sudo ip link del vfio-br0
|
sudo ip link del vfio-br0
|
||||||
sudo ip link del vfio-tap0
|
sudo ip link del vfio-tap0
|
||||||
sudo ip link del vfio-tap1
|
sudo ip link del vfio-tap1
|
||||||
|
sudo ip link del vfio-tap2
|
||||||
|
|
||||||
|
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,19 +2,13 @@
|
|||||||
|
|
||||||
source $HOME/.cargo/env
|
source $HOME/.cargo/env
|
||||||
|
|
||||||
# More effective than just cargo test --all as it captures crates within crates
|
cargo test --workspace --no-run
|
||||||
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
|
|
||||||
|
|
||||||
pushd target/debug
|
pushd target/debug
|
||||||
ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep
|
ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep
|
||||||
popd
|
popd
|
||||||
|
|
||||||
for f in $(find . -name Cargo.toml -printf '%h\n' | sort -u); do
|
sudo adduser $USER kvm
|
||||||
pushd $f > /dev/null;
|
newgrp kvm << EOF || exit 1
|
||||||
cargo test || exit 1;
|
export RUST_BACKTRACE=1
|
||||||
popd > /dev/null;
|
cargo test --workspace "$@" || exit 1;
|
||||||
done
|
EOF
|
||||||
|
|||||||
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\"",
|
||||||
|
)
|
||||||
|
.takes_value(true)
|
||||||
|
.min_values(1),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
let backend_command = cmd_arguments.value_of("block-backend").unwrap();
|
||||||
|
start_block_backend(backend_command);
|
||||||
|
}
|
||||||
246
src/bin/vhost_user_fs.rs
Normal file
246
src/bin/vhost_user_fs.rs
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
// 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 libc::EFD_NONBLOCK;
|
||||||
|
use log::*;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::{convert, error, fmt, io, process};
|
||||||
|
|
||||||
|
use vhost_rs::vhost_user::message::*;
|
||||||
|
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
|
||||||
|
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 vm_memory::GuestMemoryMmap;
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
const QUEUE_SIZE: usize = 1024;
|
||||||
|
const NUM_QUEUES: usize = 2;
|
||||||
|
|
||||||
|
// 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 handle event other than input event.
|
||||||
|
HandleEventNotEpollIn,
|
||||||
|
/// Failed to handle unknown event.
|
||||||
|
HandleEventUnknownEvent,
|
||||||
|
/// No memory configured.
|
||||||
|
NoMemoryConfigured,
|
||||||
|
/// Processing queue failed.
|
||||||
|
ProcessQueue(VhostUserFsError),
|
||||||
|
}
|
||||||
|
|
||||||
|
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<GuestMemoryMmap>,
|
||||||
|
kill_evt: EventFd,
|
||||||
|
server: Arc<Server<F>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
|
||||||
|
fn new(fs: F) -> Result<Self> {
|
||||||
|
Ok(VhostUserFsBackend {
|
||||||
|
mem: None,
|
||||||
|
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
||||||
|
server: Arc::new(Server::new(fs)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_queue(&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 reader = Reader::new(mem, avail_desc.clone()).unwrap();
|
||||||
|
let writer = Writer::new(mem, avail_desc.clone()).unwrap();
|
||||||
|
|
||||||
|
let total = self
|
||||||
|
.server
|
||||||
|
.handle_message(reader, writer)
|
||||||
|
.map_err(Error::ProcessQueue)?;
|
||||||
|
|
||||||
|
used_desc_heads[used_count] = (head_index, total);
|
||||||
|
used_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn protocol_features(&self) -> VhostUserProtocolFeatures {
|
||||||
|
VhostUserProtocolFeatures::all()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
HIPRIO_QUEUE_EVENT => {
|
||||||
|
debug!("HIPRIO_QUEUE_EVENT");
|
||||||
|
}
|
||||||
|
REQ_QUEUE_EVENT => {
|
||||||
|
debug!("REQ_QUEUE_EVENT");
|
||||||
|
let mut vring = vrings[1].write().unwrap();
|
||||||
|
self.process_queue(&mut vring)?;
|
||||||
|
}
|
||||||
|
KILL_EVENT => {
|
||||||
|
debug!("KILL_EVENT");
|
||||||
|
self.kill_evt.read().unwrap();
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
.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");
|
||||||
|
|
||||||
|
// Convert into appropriate types
|
||||||
|
let sock = String::from(sock);
|
||||||
|
|
||||||
|
let fs_cfg = passthrough::Config {
|
||||||
|
root_dir: shared_dir.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let fs = PassthroughFs::new(fs_cfg).unwrap();
|
||||||
|
let fs_backend = Arc::new(RwLock::new(VhostUserFsBackend::new(fs).unwrap()));
|
||||||
|
|
||||||
|
let mut daemon = VhostUserDaemon::new(
|
||||||
|
String::from("vhost-user-fs-backend"),
|
||||||
|
sock,
|
||||||
|
fs_backend.clone(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let vring_worker = daemon.get_vring_worker();
|
||||||
|
|
||||||
|
if let Err(e) = vring_worker.register_listener(
|
||||||
|
fs_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = daemon.start() {
|
||||||
|
println!("Failed to start daemon: {:?}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = daemon.wait() {
|
||||||
|
println!("Waiting for daemon failed: {:?}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/bin/vhost_user_net.rs
Normal file
37
src/bin/vhost_user_net.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// 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_net;
|
||||||
|
|
||||||
|
use clap::{App, Arg};
|
||||||
|
use vhost_user_net::start_net_backend;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cmd_arguments = App::new("vhost-user-net backend")
|
||||||
|
.version(crate_version!())
|
||||||
|
.author(crate_authors!())
|
||||||
|
.about("Launch a vhost-user-net backend.")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("net-backend")
|
||||||
|
.long("net-backend")
|
||||||
|
.help(
|
||||||
|
"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>\"",
|
||||||
|
)
|
||||||
|
.takes_value(true)
|
||||||
|
.min_values(1),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
let backend_command = cmd_arguments.value_of("net-backend").unwrap();
|
||||||
|
start_net_backend(backend_command);
|
||||||
|
}
|
||||||
4827
src/main.rs
4827
src/main.rs
File diff suppressed because it is too large
Load Diff
69
test_data/cloud-init/clear/openstack/latest/user_data
Normal file
69
test_data/cloud-init/clear/openstack/latest/user_data
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#cloud-config
|
||||||
|
users:
|
||||||
|
- name: cloud
|
||||||
|
passwd: $6$7125787751a8d18a$sHwGySomUA1PawiNFWVCKYQN.Ec.Wzz0JtPPL1MvzFrkwmop2dq7.4CYf03A5oemPQ4pOFCCrtCelvFBEle/K.
|
||||||
|
sudo:
|
||||||
|
- ALL=(ALL) NOPASSWD:ALL
|
||||||
|
write_files:
|
||||||
|
-
|
||||||
|
path: /etc/systemd/network/00-static-l1.network
|
||||||
|
permissions: 0644
|
||||||
|
content: |
|
||||||
|
[Match]
|
||||||
|
MACAddress=12:34:56:78:90:ab
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
Address=192.168.2.2/24
|
||||||
|
Gateway=192.168.2.1
|
||||||
|
|
||||||
|
-
|
||||||
|
path: /etc/systemd/network/00-static-l2-1.network
|
||||||
|
permissions: 0644
|
||||||
|
content: |
|
||||||
|
[Match]
|
||||||
|
MACAddress=de:ad:be:ef:12:34
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
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/system/vfio.service
|
||||||
|
permissions: 0644
|
||||||
|
content: |
|
||||||
|
[Unit]
|
||||||
|
Description=VFIO test systemd service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/bin/bash /usr/bin/cloud-hypervisor-vfio.sh
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
-
|
||||||
|
path: /usr/bin/cloud-hypervisor-vfio.sh
|
||||||
|
permissions: 0755
|
||||||
|
content: |
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
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"
|
||||||
|
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=/dev/vda2 VFIOTAG" --disk path=/mnt/clear-31310-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/
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
#cloud-config
|
|
||||||
users:
|
|
||||||
- name: admin
|
|
||||||
passwd: $6$7125787751a8d18a$sHwGySomUA1PawiNFWVCKYQN.Ec.Wzz0JtPPL1MvzFrkwmop2dq7.4CYf03A5oemPQ4pOFCCrtCelvFBEle/K.
|
|
||||||
sudo:
|
|
||||||
- ALL=(ALL) NOPASSWD:ALL
|
|
||||||
write_files:
|
|
||||||
-
|
|
||||||
path: /etc/systemd/network/00-static-l1.network
|
|
||||||
permissions: 0644
|
|
||||||
content: |
|
|
||||||
[Match]
|
|
||||||
MACAddress=12:34:56:78:90:ab
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
Address=192.168.2.2/24
|
|
||||||
Gateway=192.168.2.1
|
|
||||||
|
|
||||||
-
|
|
||||||
path: /etc/systemd/network/00-static-l2.network
|
|
||||||
permissions: 0644
|
|
||||||
content: |
|
|
||||||
[Match]
|
|
||||||
MACAddress=de:ad:be:ef:12:34
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
Address=192.168.2.3/24
|
|
||||||
Gateway=192.168.2.1
|
|
||||||
|
|
||||||
-
|
|
||||||
path: /etc/systemd/system/vfio.service
|
|
||||||
permissions: 0644
|
|
||||||
content: |
|
|
||||||
[Unit]
|
|
||||||
Description=VFIO test systemd service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=/bin/bash /usr/bin/cloud-hypervisor-vfio.sh
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
|
|
||||||
-
|
|
||||||
path: /usr/bin/cloud-hypervisor-vfio.sh
|
|
||||||
permissions: 0755
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
mount -t 9p -o trans=virtio cloud_hypervisor /mnt -oversion=9p2000.L,posixacl,cache=loose
|
|
||||||
modprobe vfio_iommu_type1 allow_unsafe_interrupts
|
|
||||||
modprobe vfio_pci
|
|
||||||
bash -c "echo 0000:00:03.0 > /sys/bus/pci/devices/0000\:00\:03.0/driver/unbind"
|
|
||||||
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
|
|
||||||
|
|
||||||
/mnt/cloud-hypervisor --console off --serial tty --kernel /mnt/vmlinux --cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda2 VFIOTAG" --disk /mnt/clear-29810-cloud.img /mnt/cloudinit.img --cpus 1 --memory size=512M --rng --device /sys/bus/pci/devices/0000:00:03.0/
|
|
||||||
2
test_data/cloud-init/ubuntu/meta-data
Executable file
2
test_data/cloud-init/ubuntu/meta-data
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
instance-id: cloud
|
||||||
|
local-hostname: cloud
|
||||||
12
test_data/cloud-init/ubuntu/network-config
Normal file
12
test_data/cloud-init/ubuntu/network-config
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
network:
|
||||||
|
version: 1
|
||||||
|
config:
|
||||||
|
- type: physical
|
||||||
|
name: eth0
|
||||||
|
mac_address: 12:34:56:78:90:ab
|
||||||
|
subnets:
|
||||||
|
- type: static
|
||||||
|
address: 192.168.2.2/24
|
||||||
|
gateway: 192.168.2.1
|
||||||
|
dns_nameservers:
|
||||||
|
- 192.168.2.1
|
||||||
10
test_data/cloud-init/ubuntu/user-data
Executable file
10
test_data/cloud-init/ubuntu/user-data
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#cloud-config
|
||||||
|
users:
|
||||||
|
- name: cloud
|
||||||
|
passwd: $6$7125787751a8d18a$sHwGySomUA1PawiNFWVCKYQN.Ec.Wzz0JtPPL1MvzFrkwmop2dq7.4CYf03A5oemPQ4pOFCCrtCelvFBEle/K.
|
||||||
|
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||||
|
lock_passwd: False
|
||||||
|
inactive: False
|
||||||
|
shell: /bin/bash
|
||||||
|
|
||||||
|
ssh_pwauth: True
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "vfio-bindings"
|
|
||||||
version = "0.0.1"
|
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["v5_0_0"]
|
|
||||||
v5_0_0 = []
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
|
||||||
|
|
||||||
#![allow(non_upper_case_globals)]
|
|
||||||
#![allow(non_camel_case_types)]
|
|
||||||
#![allow(non_snake_case)]
|
|
||||||
|
|
||||||
// generated with bindgen linux/uapi/linux/vfio.h --constified-enum '*' --with-derive-default
|
|
||||||
#[cfg(feature = "v5_0_0")]
|
|
||||||
mod v5_0_0;
|
|
||||||
|
|
||||||
pub mod bindings {
|
|
||||||
#[cfg(feature = "v5_0_0")]
|
|
||||||
pub use super::v5_0_0::*;
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#![allow(clippy::all)]
|
|
||||||
#![allow(non_upper_case_globals)]
|
|
||||||
#![allow(non_camel_case_types)]
|
|
||||||
#![allow(non_snake_case)]
|
|
||||||
|
|
||||||
pub mod vfio;
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,17 +4,22 @@ version = "0.0.1"
|
|||||||
authors = ["The Cloud Hypervisor Authors"]
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = ">=1.2.1"
|
arc-swap = "0.4.4"
|
||||||
|
byteorder = "1.3.4"
|
||||||
devices = { path = "../devices" }
|
devices = { path = "../devices" }
|
||||||
kvm-bindings = "0.1"
|
kvm-bindings = "0.2.0"
|
||||||
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
|
kvm-ioctls = "0.5.0"
|
||||||
libc = ">=0.2.39"
|
libc = "0.2.66"
|
||||||
log = "*"
|
log = "0.4.8"
|
||||||
pci = { path = "../pci" }
|
pci = { path = "../pci" }
|
||||||
vfio-bindings = { path = "../vfio-bindings" }
|
|
||||||
vm-allocator = { path = "../vm-allocator" }
|
vm-allocator = { path = "../vm-allocator" }
|
||||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
vm-device = { path = "../vm-device" }
|
||||||
|
vmm-sys-util = "0.4.0"
|
||||||
|
|
||||||
[dependencies.vm-memory]
|
[dependencies.vm-memory]
|
||||||
git = "https://github.com/rust-vmm/vm-memory"
|
git = "https://github.com/rust-vmm/vm-memory"
|
||||||
features = ["backend-mmap"]
|
features = ["backend-mmap"]
|
||||||
|
|
||||||
|
[dependencies.vfio-bindings]
|
||||||
|
git = "https://github.com/rust-vmm/vfio-bindings"
|
||||||
|
features = ["fam-wrappers"]
|
||||||
|
|||||||
@@ -5,14 +5,17 @@
|
|||||||
|
|
||||||
//#![deny(missing_docs)]
|
//#![deny(missing_docs)]
|
||||||
//! Virtual Function I/O (VFIO) API
|
//! Virtual Function I/O (VFIO) API
|
||||||
|
extern crate arc_swap;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate devices;
|
extern crate devices;
|
||||||
extern crate kvm_bindings;
|
extern crate kvm_bindings;
|
||||||
extern crate kvm_ioctls;
|
extern crate kvm_ioctls;
|
||||||
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate pci;
|
extern crate pci;
|
||||||
extern crate vfio_bindings;
|
extern crate vfio_bindings;
|
||||||
extern crate vm_allocator;
|
extern crate vm_allocator;
|
||||||
|
extern crate vm_device;
|
||||||
extern crate vm_memory;
|
extern crate vm_memory;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate vmm_sys_util;
|
extern crate vmm_sys_util;
|
||||||
@@ -23,7 +26,7 @@ mod vfio_pci;
|
|||||||
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
pub use vfio_device::{VfioDevice, VfioError};
|
pub use vfio_device::{VfioContainer, VfioDevice, VfioDmaMapping, VfioError};
|
||||||
pub use vfio_pci::{VfioPciDevice, VfioPciError};
|
pub use vfio_pci::{VfioPciDevice, VfioPciError};
|
||||||
|
|
||||||
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.
|
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.
|
||||||
|
|||||||
@@ -3,24 +3,31 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||||
//
|
//
|
||||||
use crate::vec_with_array_field;
|
use crate::vec_with_array_field;
|
||||||
|
use arc_swap::ArcSwap;
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use kvm_ioctls::*;
|
use kvm_ioctls::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::num;
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||||
use std::os::unix::prelude::FileExt;
|
use std::os::unix::prelude::FileExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::u32;
|
use std::u32;
|
||||||
use vfio_bindings::bindings::vfio::*;
|
use vfio_bindings::bindings::vfio::*;
|
||||||
|
use vfio_bindings::bindings::IrqSet;
|
||||||
use vfio_ioctls::*;
|
use vfio_ioctls::*;
|
||||||
use vm_memory::{Address, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
|
use vm_device::{get_host_address_range, ExternalDmaMapping};
|
||||||
|
use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
use vmm_sys_util::fam::FamStruct;
|
||||||
use vmm_sys_util::ioctl::*;
|
use vmm_sys_util::ioctl::*;
|
||||||
use vmm_sys_util::EventFd;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum VfioError {
|
pub enum VfioError {
|
||||||
@@ -36,7 +43,7 @@ pub enum VfioError {
|
|||||||
UnsetContainer,
|
UnsetContainer,
|
||||||
ContainerSetIOMMU,
|
ContainerSetIOMMU,
|
||||||
GroupGetDeviceFD,
|
GroupGetDeviceFD,
|
||||||
KvmSetDeviceAttr(io::Error),
|
KvmSetDeviceAttr(kvm_ioctls::Error),
|
||||||
VfioDeviceGetInfo,
|
VfioDeviceGetInfo,
|
||||||
VfioDeviceGetRegionInfo,
|
VfioDeviceGetRegionInfo,
|
||||||
InvalidPath,
|
InvalidPath,
|
||||||
@@ -44,6 +51,8 @@ pub enum VfioError {
|
|||||||
IommuDmaUnmap,
|
IommuDmaUnmap,
|
||||||
VfioDeviceGetIrqInfo,
|
VfioDeviceGetIrqInfo,
|
||||||
VfioDeviceSetIrq,
|
VfioDeviceSetIrq,
|
||||||
|
ReadLink(io::Error),
|
||||||
|
ParseInt(num::ParseIntError),
|
||||||
}
|
}
|
||||||
pub type Result<T> = std::result::Result<T, VfioError>;
|
pub type Result<T> = std::result::Result<T, VfioError>;
|
||||||
|
|
||||||
@@ -92,6 +101,8 @@ impl fmt::Display for VfioError {
|
|||||||
}
|
}
|
||||||
VfioError::VfioDeviceGetIrqInfo => write!(f, "failed to get vfio device irq info"),
|
VfioError::VfioDeviceGetIrqInfo => write!(f, "failed to get vfio device irq info"),
|
||||||
VfioError::VfioDeviceSetIrq => write!(f, "failed to set vfio deviece irq"),
|
VfioError::VfioDeviceSetIrq => write!(f, "failed to set vfio deviece irq"),
|
||||||
|
VfioError::ReadLink(e) => write!(f, "failed to read link from path: {}", e),
|
||||||
|
VfioError::ParseInt(e) => write!(f, "failed to parse integer: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +114,7 @@ struct vfio_region_info_with_cap {
|
|||||||
cap_info: __IncompleteArrayField<u8>,
|
cap_info: __IncompleteArrayField<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VfioContainer {
|
pub struct VfioContainer {
|
||||||
container: File,
|
container: File,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +162,7 @@ impl VfioContainer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
pub fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
||||||
let dma_map = vfio_iommu_type1_dma_map {
|
let dma_map = vfio_iommu_type1_dma_map {
|
||||||
argsz: mem::size_of::<vfio_iommu_type1_dma_map>() as u32,
|
argsz: mem::size_of::<vfio_iommu_type1_dma_map>() as u32,
|
||||||
flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
|
flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
|
||||||
@@ -170,7 +181,7 @@ impl VfioContainer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
|
pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
|
||||||
let mut dma_unmap = vfio_iommu_type1_dma_unmap {
|
let mut dma_unmap = vfio_iommu_type1_dma_unmap {
|
||||||
argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
|
argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
@@ -198,7 +209,7 @@ impl AsRawFd for VfioContainer {
|
|||||||
struct VfioGroup {
|
struct VfioGroup {
|
||||||
group: File,
|
group: File,
|
||||||
device: Arc<DeviceFd>,
|
device: Arc<DeviceFd>,
|
||||||
container: VfioContainer,
|
container: Arc<VfioContainer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VfioGroup {
|
impl VfioGroup {
|
||||||
@@ -225,7 +236,7 @@ impl VfioGroup {
|
|||||||
return Err(VfioError::GroupViable);
|
return Err(VfioError::GroupViable);
|
||||||
}
|
}
|
||||||
|
|
||||||
let container = VfioContainer::new()?;
|
let container = Arc::new(VfioContainer::new()?);
|
||||||
if container.get_api_version() as u32 != VFIO_API_VERSION {
|
if container.get_api_version() as u32 != VFIO_API_VERSION {
|
||||||
return Err(VfioError::VfioApiVersion);
|
return Err(VfioError::VfioApiVersion);
|
||||||
}
|
}
|
||||||
@@ -266,7 +277,7 @@ impl VfioGroup {
|
|||||||
.map_err(VfioError::KvmSetDeviceAttr)
|
.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 = self.as_raw_fd();
|
||||||
let group_fd_ptr = &group_fd as *const i32;
|
let group_fd_ptr = &group_fd as *const i32;
|
||||||
let dev_attr = kvm_bindings::kvm_device_attr {
|
let dev_attr = kvm_bindings::kvm_device_attr {
|
||||||
@@ -433,7 +444,7 @@ impl VfioDeviceInfo {
|
|||||||
ioctl_with_mut_ref(&self.device, VFIO_DEVICE_GET_REGION_INFO(), &mut reg_info)
|
ioctl_with_mut_ref(&self.device, VFIO_DEVICE_GET_REGION_INFO(), &mut reg_info)
|
||||||
};
|
};
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
error!("Could not get region #{} info", i);
|
warn!("Could not get region #{} info", i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +470,7 @@ impl VfioDeviceInfo {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
error!("Could not get region #{} info", i);
|
warn!("Could not get region #{} info", i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// region_with_cap[0].cap_info may contain vfio_region_info_cap_sparse_mmap
|
// region_with_cap[0].cap_info may contain vfio_region_info_cap_sparse_mmap
|
||||||
@@ -506,6 +517,67 @@ impl VfioDeviceInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This structure implements the ExternalDmaMapping trait. It is meant to
|
||||||
|
/// be used when the caller tries to provide a way to update the mappings
|
||||||
|
/// associated with a specific VFIO container.
|
||||||
|
pub struct VfioDmaMapping {
|
||||||
|
container: Arc<VfioContainer>,
|
||||||
|
memory: Arc<ArcSwap<GuestMemoryMmap>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VfioDmaMapping {
|
||||||
|
pub fn new(container: Arc<VfioContainer>, memory: Arc<ArcSwap<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) = get_host_address_range(
|
||||||
|
&self.memory.load(),
|
||||||
|
GuestAddress(gpa),
|
||||||
|
size.try_into().unwrap(),
|
||||||
|
) {
|
||||||
|
addr as u64
|
||||||
|
} else {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!(
|
||||||
|
"failed to convert guest address 0x{:x} into \
|
||||||
|
host user virtual address",
|
||||||
|
gpa
|
||||||
|
),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
self.container
|
||||||
|
.vfio_dma_map(iova, size, user_addr)
|
||||||
|
.map_err(|e| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!(
|
||||||
|
"failed to map memory for VFIO container, \
|
||||||
|
iova 0x{:x}, gpa 0x{:x}, size 0x{:x}: {:?}",
|
||||||
|
iova, gpa, size, e
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmap(&self, iova: u64, size: u64) -> result::Result<(), io::Error> {
|
||||||
|
self.container.vfio_dma_unmap(iova, size).map_err(|e| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!(
|
||||||
|
"failed to unmap memory for VFIO container, \
|
||||||
|
iova 0x{:x}, size 0x{:x}: {:?}",
|
||||||
|
iova, size, e
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Vfio device for exposing regions which could be read/write to kernel vfio device.
|
/// Vfio device for exposing regions which could be read/write to kernel vfio device.
|
||||||
pub struct VfioDevice {
|
pub struct VfioDevice {
|
||||||
device: File,
|
device: File,
|
||||||
@@ -513,21 +585,25 @@ pub struct VfioDevice {
|
|||||||
group: VfioGroup,
|
group: VfioGroup,
|
||||||
regions: Vec<VfioRegion>,
|
regions: Vec<VfioRegion>,
|
||||||
irqs: HashMap<u32, VfioIrq>,
|
irqs: HashMap<u32, VfioIrq>,
|
||||||
mem: GuestMemoryMmap,
|
mem: Arc<ArcSwap<GuestMemoryMmap>>,
|
||||||
|
iommu_attached: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VfioDevice {
|
impl VfioDevice {
|
||||||
/// Create a new vfio device, then guest read/write on this device could be
|
/// Create a new vfio device, then guest read/write on this device could be
|
||||||
/// transfered into kernel vfio.
|
/// transfered into kernel vfio.
|
||||||
/// sysfspath specify the vfio device path in sys file system.
|
/// sysfspath specify the vfio device path in sys file system.
|
||||||
pub fn new(sysfspath: &Path, device_fd: Arc<DeviceFd>, mem: GuestMemoryMmap) -> Result<Self> {
|
pub fn new(
|
||||||
|
sysfspath: &Path,
|
||||||
|
device_fd: Arc<DeviceFd>,
|
||||||
|
mem: Arc<ArcSwap<GuestMemoryMmap>>,
|
||||||
|
iommu_attached: bool,
|
||||||
|
) -> Result<Self> {
|
||||||
let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect();
|
let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect();
|
||||||
let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?;
|
let group_path = uuid_path.read_link().map_err(VfioError::ReadLink)?;
|
||||||
let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
|
let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
|
||||||
let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?;
|
let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?;
|
||||||
let group_id = group_str
|
let group_id = group_str.parse::<u32>().map_err(VfioError::ParseInt)?;
|
||||||
.parse::<u32>()
|
|
||||||
.map_err(|_| VfioError::InvalidPath)?;
|
|
||||||
|
|
||||||
let group = VfioGroup::new(group_id, device_fd)?;
|
let group = VfioGroup::new(group_id, device_fd)?;
|
||||||
let device_info = group.get_device(sysfspath)?;
|
let device_info = group.get_device(sysfspath)?;
|
||||||
@@ -541,6 +617,7 @@ impl VfioDevice {
|
|||||||
regions,
|
regions,
|
||||||
irqs,
|
irqs,
|
||||||
mem,
|
mem,
|
||||||
|
iommu_attached,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,34 +647,25 @@ impl VfioDevice {
|
|||||||
return Err(VfioError::VfioDeviceSetIrq);
|
return Err(VfioError::VfioDeviceSetIrq);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(event_fds.len());
|
let mut irq_set_wrapper = IrqSet::new(event_fds.len() * mem::size_of::<u32>());
|
||||||
irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32
|
let mut irq_set = irq_set_wrapper.as_mut_fam_struct();
|
||||||
+ (event_fds.len() * mem::size_of::<u32>()) as u32;
|
let fds = irq_set.as_mut_slice();
|
||||||
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;
|
|
||||||
|
|
||||||
{
|
for (index, event_fd) in event_fds.iter().enumerate() {
|
||||||
// irq_set.data could be none, bool or fd according to flags, so irq_set.data
|
let fds_offset = index * mem::size_of::<u32>();
|
||||||
// is u8 default, here irq_set.data is a vector of fds as u32, so 4 default u8
|
let fd = &mut fds[fds_offset..fds_offset + mem::size_of::<u32>()];
|
||||||
// are combined together as u32 for each fd.
|
LittleEndian::write_u32(fd, event_fd.as_raw_fd() as u32);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// 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 {
|
if ret < 0 {
|
||||||
return Err(VfioError::VfioDeviceSetIrq);
|
return Err(VfioError::VfioDeviceSetIrq);
|
||||||
}
|
}
|
||||||
@@ -621,10 +689,10 @@ impl VfioDevice {
|
|||||||
|
|
||||||
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0);
|
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].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].index = irq_index;
|
||||||
irq_set[0].start = 0;
|
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
|
// 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[0]) };
|
||||||
@@ -758,6 +826,10 @@ impl VfioDevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_container(&self) -> Arc<VfioContainer> {
|
||||||
|
self.group.container.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
||||||
self.group.container.vfio_dma_map(iova, size, user_addr)
|
self.group.container.vfio_dma_map(iova, size, user_addr)
|
||||||
}
|
}
|
||||||
@@ -769,22 +841,26 @@ impl VfioDevice {
|
|||||||
/// Add all guest memory regions into vfio container's iommu table,
|
/// Add all guest memory regions into vfio container's iommu table,
|
||||||
/// then vfio kernel driver could access guest memory from gfn
|
/// then vfio kernel driver could access guest memory from gfn
|
||||||
pub fn setup_dma_map(&self) -> Result<()> {
|
pub fn setup_dma_map(&self) -> Result<()> {
|
||||||
self.mem.with_regions(|_index, region| {
|
if !self.iommu_attached {
|
||||||
self.vfio_dma_map(
|
self.mem.load().with_regions(|_index, region| {
|
||||||
region.start_addr().raw_value(),
|
self.vfio_dma_map(
|
||||||
region.len() as u64,
|
region.start_addr().raw_value(),
|
||||||
region.as_ptr() as u64,
|
region.len() as u64,
|
||||||
)
|
region.as_ptr() as u64,
|
||||||
})?;
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// remove all guest memory regions from vfio containers iommu table
|
/// remove all guest memory regions from vfio containers iommu table
|
||||||
/// then vfio kernel driver couldn't access this guest memory
|
/// then vfio kernel driver couldn't access this guest memory
|
||||||
pub fn unset_dma_map(&self) -> Result<()> {
|
pub fn unset_dma_map(&self) -> Result<()> {
|
||||||
self.mem.with_regions(|_index, region| {
|
if !self.iommu_attached {
|
||||||
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
|
self.mem.load().with_regions(|_index, region| {
|
||||||
})?;
|
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,36 +7,42 @@ extern crate devices;
|
|||||||
extern crate pci;
|
extern crate pci;
|
||||||
extern crate vm_allocator;
|
extern crate vm_allocator;
|
||||||
|
|
||||||
use crate::vec_with_array_field;
|
|
||||||
use crate::vfio_device::VfioDevice;
|
use crate::vfio_device::VfioDevice;
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use devices::BusDevice;
|
use devices::BusDevice;
|
||||||
use kvm_bindings::{
|
use kvm_bindings::kvm_userspace_memory_region;
|
||||||
kvm_irq_routing, kvm_irq_routing_entry, kvm_userspace_memory_region, KVM_IRQ_ROUTING_MSI,
|
|
||||||
};
|
|
||||||
use kvm_ioctls::*;
|
use kvm_ioctls::*;
|
||||||
use pci::{
|
use pci::{
|
||||||
MsiCap, MsixCap, MsixConfig, PciBarConfiguration, PciBarRegionType, PciCapabilityID,
|
msi_num_enabled_vectors, BarReprogrammingParams, MsiConfig, MsixCap, MsixConfig,
|
||||||
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
|
PciBarConfiguration, PciBarRegionType, PciCapabilityID, PciClassCode, PciConfiguration,
|
||||||
MSIX_TABLE_ENTRY_SIZE,
|
PciDevice, PciDeviceError, PciHeaderType, PciSubclass, MSIX_TABLE_ENTRY_SIZE,
|
||||||
};
|
};
|
||||||
|
use std::any::Any;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::ptr::null_mut;
|
use std::ptr::null_mut;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{fmt, io};
|
use std::{fmt, io, result};
|
||||||
use vfio_bindings::bindings::vfio::*;
|
use vfio_bindings::bindings::vfio::*;
|
||||||
use vm_allocator::SystemAllocator;
|
use vm_allocator::SystemAllocator;
|
||||||
|
use vm_device::interrupt::{
|
||||||
|
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
|
||||||
|
};
|
||||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||||
use vmm_sys_util::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum VfioPciError {
|
pub enum VfioPciError {
|
||||||
AllocateGsi,
|
AllocateGsi,
|
||||||
EventFd(io::Error),
|
EventFd(io::Error),
|
||||||
IrqFd(io::Error),
|
InterruptSourceGroupCreate(io::Error),
|
||||||
|
IrqFd(kvm_ioctls::Error),
|
||||||
NewVfioPciDevice,
|
NewVfioPciDevice,
|
||||||
MapRegionGuest(io::Error),
|
MapRegionGuest(kvm_ioctls::Error),
|
||||||
SetGsiRouting(io::Error),
|
SetGsiRouting(kvm_ioctls::Error),
|
||||||
|
MsiNotConfigured,
|
||||||
|
MsixNotConfigured,
|
||||||
|
UpdateMsiEventFd,
|
||||||
|
UpdateMsixEventFd,
|
||||||
}
|
}
|
||||||
pub type Result<T> = std::result::Result<T, VfioPciError>;
|
pub type Result<T> = std::result::Result<T, VfioPciError>;
|
||||||
|
|
||||||
@@ -45,12 +51,19 @@ impl fmt::Display for VfioPciError {
|
|||||||
match self {
|
match self {
|
||||||
VfioPciError::AllocateGsi => write!(f, "failed to allocate GSI"),
|
VfioPciError::AllocateGsi => write!(f, "failed to allocate GSI"),
|
||||||
VfioPciError::EventFd(e) => write!(f, "failed to create eventfd: {}", e),
|
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::IrqFd(e) => write!(f, "failed to register irqfd: {}", e),
|
||||||
VfioPciError::NewVfioPciDevice => write!(f, "failed to create VFIO PCI device"),
|
VfioPciError::NewVfioPciDevice => write!(f, "failed to create VFIO PCI device"),
|
||||||
VfioPciError::MapRegionGuest(e) => {
|
VfioPciError::MapRegionGuest(e) => {
|
||||||
write!(f, "failed to map VFIO PCI region into guest: {}", 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::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,
|
DisableMsix,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct VfioMsi {
|
struct VfioMsi {
|
||||||
cap: MsiCap,
|
cfg: MsiConfig,
|
||||||
cap_offset: u32,
|
cap_offset: u32,
|
||||||
|
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VfioMsi {
|
impl VfioMsi {
|
||||||
fn update(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> {
|
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 {
|
if !old_enabled && new_enabled {
|
||||||
return Some(InterruptUpdateAction::EnableMsi);
|
return Some(InterruptUpdateAction::EnableMsi);
|
||||||
@@ -103,18 +116,19 @@ struct VfioMsix {
|
|||||||
bar: MsixConfig,
|
bar: MsixConfig,
|
||||||
cap: MsixCap,
|
cap: MsixCap,
|
||||||
cap_offset: u32,
|
cap_offset: u32,
|
||||||
|
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VfioMsix {
|
impl VfioMsix {
|
||||||
fn update(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> {
|
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
|
// Update "Message Control" word
|
||||||
if offset == 2 && data.len() == 2 {
|
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 {
|
if !old_enabled && new_enabled {
|
||||||
return Some(InterruptUpdateAction::EnableMsix);
|
return Some(InterruptUpdateAction::EnableMsix);
|
||||||
@@ -163,7 +177,7 @@ impl Interrupt {
|
|||||||
fn accessed(&self, offset: u64) -> Option<(PciCapabilityID, u64)> {
|
fn accessed(&self, offset: u64) -> Option<(PciCapabilityID, u64)> {
|
||||||
if let Some(msi) = &self.msi {
|
if let Some(msi) = &self.msi {
|
||||||
if offset >= u64::from(msi.cap_offset)
|
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((
|
return Some((
|
||||||
PciCapabilityID::MessageSignalledInterrupts,
|
PciCapabilityID::MessageSignalledInterrupts,
|
||||||
@@ -181,22 +195,6 @@ impl Interrupt {
|
|||||||
None
|
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 {
|
fn msix_table_accessed(&self, bar_index: u32, offset: u64) -> bool {
|
||||||
if let Some(msix) = &self.msix {
|
if let Some(msix) = &self.msix {
|
||||||
return msix.table_accessed(bar_index, offset);
|
return msix.table_accessed(bar_index, offset);
|
||||||
@@ -207,52 +205,27 @@ impl Interrupt {
|
|||||||
|
|
||||||
fn msix_write_table(&mut self, offset: u64, data: &[u8]) {
|
fn msix_write_table(&mut self, offset: u64, data: &[u8]) {
|
||||||
if let Some(ref mut msix) = &mut self.msix {
|
if let Some(ref mut msix) = &mut self.msix {
|
||||||
|
let offset = offset - u64::from(msix.cap.table_offset());
|
||||||
msix.bar.write_table(offset, data)
|
msix.bar.write_table(offset, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn msix_read_table(&self, offset: u64, data: &mut [u8]) {
|
fn msix_read_table(&self, offset: u64, data: &mut [u8]) {
|
||||||
if let Some(msix) = &self.msix {
|
if let Some(msix) = &self.msix {
|
||||||
|
let offset = offset - u64::from(msix.cap.table_offset());
|
||||||
msix.bar.read_table(offset, data)
|
msix.bar.read_table(offset, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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)]
|
#[derive(Copy, Clone)]
|
||||||
struct MmioRegion {
|
struct MmioRegion {
|
||||||
start: GuestAddress,
|
start: GuestAddress,
|
||||||
length: GuestUsize,
|
length: GuestUsize,
|
||||||
index: u32,
|
index: u32,
|
||||||
|
mem_slot: Option<u32>,
|
||||||
|
host_addr: Option<u64>,
|
||||||
|
mmap_size: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VfioPciConfig {
|
struct VfioPciConfig {
|
||||||
@@ -308,15 +281,14 @@ pub struct VfioPciDevice {
|
|||||||
configuration: PciConfiguration,
|
configuration: PciConfiguration,
|
||||||
mmio_regions: Vec<MmioRegion>,
|
mmio_regions: Vec<MmioRegion>,
|
||||||
interrupt: Interrupt,
|
interrupt: Interrupt,
|
||||||
interrupt_routes: Vec<InterruptRoute>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VfioPciDevice {
|
impl VfioPciDevice {
|
||||||
/// Constructs a new Vfio Pci device for the given Vfio device
|
/// Constructs a new Vfio Pci device for the given Vfio device
|
||||||
pub fn new(
|
pub fn new(
|
||||||
vm_fd: &Arc<VmFd>,
|
vm_fd: &Arc<VmFd>,
|
||||||
allocator: &mut SystemAllocator,
|
|
||||||
device: VfioDevice,
|
device: VfioDevice,
|
||||||
|
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let device = Arc::new(device);
|
let device = Arc::new(device);
|
||||||
device.reset();
|
device.reset();
|
||||||
@@ -345,73 +317,18 @@ impl VfioPciDevice {
|
|||||||
msi: None,
|
msi: None,
|
||||||
msix: None,
|
msix: None,
|
||||||
},
|
},
|
||||||
interrupt_routes: Vec::new(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
vfio_pci_device.parse_capabilities();
|
vfio_pci_device.parse_capabilities(interrupt_manager);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(vfio_pci_device)
|
Ok(vfio_pci_device)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn irq_fds(&self) -> Result<Vec<&EventFd>> {
|
fn parse_msix_capabilities(
|
||||||
let mut irq_fds: Vec<&EventFd> = Vec::new();
|
&mut self,
|
||||||
|
cap: u8,
|
||||||
for r in &self.interrupt_routes {
|
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
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()
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
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) {
|
|
||||||
let msg_ctl = self
|
let msg_ctl = self
|
||||||
.vfio_pci_configuration
|
.vfio_pci_configuration
|
||||||
.read_config_word((cap + 2).into());
|
.read_config_word((cap + 2).into());
|
||||||
@@ -429,30 +346,53 @@ impl VfioPciDevice {
|
|||||||
table,
|
table,
|
||||||
pba,
|
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 {
|
self.interrupt.msix = Some(VfioMsix {
|
||||||
bar: msix_config,
|
bar: msix_config,
|
||||||
cap: msix_cap,
|
cap: msix_cap,
|
||||||
cap_offset: cap.into(),
|
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
|
let msg_ctl = self
|
||||||
.vfio_pci_configuration
|
.vfio_pci_configuration
|
||||||
.read_config_word((cap + 2).into());
|
.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 {
|
self.interrupt.msi = Some(VfioMsi {
|
||||||
cap: MsiCap {
|
cfg: msi_config,
|
||||||
msg_ctl,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
cap_offset: cap.into(),
|
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
|
let mut cap_next = self
|
||||||
.vfio_pci_configuration
|
.vfio_pci_configuration
|
||||||
.read_config_byte(PCI_CONFIG_CAPABILITY_OFFSET);
|
.read_config_byte(PCI_CONFIG_CAPABILITY_OFFSET);
|
||||||
@@ -464,10 +404,10 @@ impl VfioPciDevice {
|
|||||||
|
|
||||||
match PciCapabilityID::from(cap_id) {
|
match PciCapabilityID::from(cap_id) {
|
||||||
PciCapabilityID::MessageSignalledInterrupts => {
|
PciCapabilityID::MessageSignalledInterrupts => {
|
||||||
self.parse_msi_capabilities(cap_next);
|
self.parse_msi_capabilities(cap_next, interrupt_manager);
|
||||||
}
|
}
|
||||||
PciCapabilityID::MSIX => {
|
PciCapabilityID::MSIX => {
|
||||||
self.parse_msix_capabilities(cap_next);
|
self.parse_msix_capabilities(cap_next, interrupt_manager);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
@@ -478,67 +418,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<()> {
|
fn update_msi_capabilities(&mut self, offset: u64, data: &[u8]) -> Result<()> {
|
||||||
match self.interrupt.update_msi(offset, data) {
|
match self.interrupt.update_msi(offset, data) {
|
||||||
Some(InterruptUpdateAction::EnableMsi) => match self.irq_fds() {
|
Some(InterruptUpdateAction::EnableMsi) => {
|
||||||
Ok(fds) => {
|
if let Some(msi) = &self.interrupt.msi {
|
||||||
if let Err(e) = self.device.enable_msi(fds) {
|
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);
|
warn!("Could not enable MSI: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => warn!("Could not get IRQ fds: {}", e),
|
}
|
||||||
},
|
|
||||||
Some(InterruptUpdateAction::DisableMsi) => {
|
Some(InterruptUpdateAction::DisableMsi) => {
|
||||||
if let Err(e) = self.device.disable_msi() {
|
if let Err(e) = self.device.disable_msi() {
|
||||||
warn!("Could not disable MSI: {}", e);
|
warn!("Could not disable MSI: {}", e);
|
||||||
@@ -547,34 +446,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(())
|
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) {
|
match self.interrupt.update_msix(offset, data) {
|
||||||
Some(InterruptUpdateAction::EnableMsix) => match self.irq_fds() {
|
Some(InterruptUpdateAction::EnableMsix) => {
|
||||||
Ok(fds) => {
|
if let Some(msix) = &self.interrupt.msix {
|
||||||
if let Err(e) = self.device.enable_msix(fds) {
|
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);
|
warn!("Could not enable MSI-X: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => warn!("Could not get IRQ fds: {}", e),
|
}
|
||||||
},
|
|
||||||
Some(InterruptUpdateAction::DisableMsix) => {
|
Some(InterruptUpdateAction::DisableMsix) => {
|
||||||
if let Err(e) = self.device.disable_msix() {
|
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> {
|
fn find_region(&self, addr: u64) -> Option<MmioRegion> {
|
||||||
@@ -603,7 +506,7 @@ impl VfioPciDevice {
|
|||||||
let fd = self.device.as_raw_fd();
|
let fd = self.device.as_raw_fd();
|
||||||
let mut new_mem_slot = mem_slot;
|
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
|
// 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
|
// 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
|
// any access to the MSI-X table and update the GSI routing
|
||||||
@@ -658,22 +561,48 @@ impl VfioPciDevice {
|
|||||||
vm.set_user_memory_region(mem_region)
|
vm.set_user_memory_region(mem_region)
|
||||||
.map_err(VfioPciError::MapRegionGuest)?;
|
.map_err(VfioPciError::MapRegionGuest)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the region with memory mapped info.
|
||||||
|
region.mem_slot = Some(new_mem_slot);
|
||||||
|
region.host_addr = Some(host_addr as u64);
|
||||||
|
region.mmap_size = Some(mmap_size as usize);
|
||||||
|
|
||||||
new_mem_slot += 1;
|
new_mem_slot += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(new_mem_slot)
|
Ok(new_mem_slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unmap_mmio_regions(&mut self) {
|
||||||
|
for region in self.mmio_regions.iter() {
|
||||||
|
if let (Some(addr), Some(size)) = (region.host_addr, region.mmap_size) {
|
||||||
|
let ret = unsafe { libc::munmap(addr as *mut libc::c_void, size) };
|
||||||
|
if ret != 0 {
|
||||||
|
error!(
|
||||||
|
"Could not unmap regions, error:{}",
|
||||||
|
io::Error::last_os_error()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for VfioPciDevice {
|
impl Drop for VfioPciDevice {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.interrupt.msi.is_some() && self.device.disable_msi().is_err() {
|
self.unmap_mmio_regions();
|
||||||
error!("Could not disable MSI");
|
|
||||||
|
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() {
|
if let Some(msi) = &self.interrupt.msi {
|
||||||
error!("Could not disable MSI-X");
|
if msi.cfg.enabled() && self.device.disable_msi().is_err() {
|
||||||
|
error!("Could not disable MSI");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.device.unset_dma_map().is_err() {
|
if self.device.unset_dma_map().is_err() {
|
||||||
@@ -694,8 +623,6 @@ impl BusDevice for VfioPciDevice {
|
|||||||
|
|
||||||
// First BAR offset in the PCI config space.
|
// First BAR offset in the PCI config space.
|
||||||
const PCI_CONFIG_BAR_OFFSET: u32 = 0x10;
|
const PCI_CONFIG_BAR_OFFSET: u32 = 0x10;
|
||||||
// First BAR register index
|
|
||||||
const PCI_CONFIG_BAR0_INDEX: usize = 4;
|
|
||||||
// Capability register offset in the PCI config space.
|
// Capability register offset in the PCI config space.
|
||||||
const PCI_CONFIG_CAPABILITY_OFFSET: u32 = 0x34;
|
const PCI_CONFIG_CAPABILITY_OFFSET: u32 = 0x34;
|
||||||
// IO BAR when first BAR bit is 1.
|
// IO BAR when first BAR bit is 1.
|
||||||
@@ -708,6 +635,10 @@ const PCI_CONFIG_MEMORY_BAR_64BIT: u32 = 0x4;
|
|||||||
const PCI_CONFIG_REGISTER_SIZE: usize = 4;
|
const PCI_CONFIG_REGISTER_SIZE: usize = 4;
|
||||||
// Number of BARs for a PCI device
|
// Number of BARs for a PCI device
|
||||||
const BAR_NUMS: usize = 6;
|
const BAR_NUMS: usize = 6;
|
||||||
|
// PCI Header Type register index
|
||||||
|
const PCI_HEADER_TYPE_REG_INDEX: usize = 3;
|
||||||
|
// First BAR register index
|
||||||
|
const PCI_CONFIG_BAR0_INDEX: usize = 4;
|
||||||
// PCI ROM expansion BAR register index
|
// PCI ROM expansion BAR register index
|
||||||
const PCI_ROM_EXP_BAR_INDEX: usize = 12;
|
const PCI_ROM_EXP_BAR_INDEX: usize = 12;
|
||||||
// PCI interrupt pin and line register index
|
// PCI interrupt pin and line register index
|
||||||
@@ -726,14 +657,18 @@ impl PciDevice for VfioPciDevice {
|
|||||||
// We're not saving the BAR address to restore it, because we
|
// We're not saving the BAR address to restore it, because we
|
||||||
// are going to allocate a guest address for each BAR and write
|
// are going to allocate a guest address for each BAR and write
|
||||||
// that new address back.
|
// that new address back.
|
||||||
while bar_id < VFIO_PCI_ROM_REGION_INDEX {
|
while bar_id < VFIO_PCI_CONFIG_REGION_INDEX {
|
||||||
let mut lsb_size: u32 = 0xffff_ffff;
|
let mut lsb_size: u32 = 0xffff_ffff;
|
||||||
let mut msb_size = 0;
|
let mut msb_size = 0;
|
||||||
let mut region_size: u64;
|
let mut region_size: u64;
|
||||||
let bar_addr: GuestAddress;
|
let bar_addr: GuestAddress;
|
||||||
|
|
||||||
// Read the BAR size (Starts by all 1s to the BAR)
|
// Read the BAR size (Starts by all 1s to the BAR)
|
||||||
let bar_offset = PCI_CONFIG_BAR_OFFSET + bar_id * 4;
|
let bar_offset = if bar_id == VFIO_PCI_ROM_REGION_INDEX {
|
||||||
|
(PCI_ROM_EXP_BAR_INDEX * 4) as u32
|
||||||
|
} else {
|
||||||
|
PCI_CONFIG_BAR_OFFSET + bar_id * 4
|
||||||
|
};
|
||||||
|
|
||||||
self.vfio_pci_configuration
|
self.vfio_pci_configuration
|
||||||
.write_config_dword(lsb_size, bar_offset);
|
.write_config_dword(lsb_size, bar_offset);
|
||||||
@@ -748,15 +683,23 @@ impl PciDevice for VfioPciDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Is this an IO BAR?
|
// Is this an IO BAR?
|
||||||
let io_bar = match lsb_flag & PCI_CONFIG_IO_BAR {
|
let io_bar = if bar_id != VFIO_PCI_ROM_REGION_INDEX {
|
||||||
PCI_CONFIG_IO_BAR => true,
|
match lsb_flag & PCI_CONFIG_IO_BAR {
|
||||||
_ => false,
|
PCI_CONFIG_IO_BAR => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Is this a 64-bit BAR?
|
// Is this a 64-bit BAR?
|
||||||
let is_64bit_bar = match lsb_flag & PCI_CONFIG_MEMORY_BAR_64BIT {
|
let is_64bit_bar = if bar_id != VFIO_PCI_ROM_REGION_INDEX {
|
||||||
PCI_CONFIG_MEMORY_BAR_64BIT => true,
|
match lsb_flag & PCI_CONFIG_MEMORY_BAR_64BIT {
|
||||||
_ => false,
|
PCI_CONFIG_MEMORY_BAR_64BIT => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
// By default, the region type is 32 bits memory BAR.
|
// By default, the region type is 32 bits memory BAR.
|
||||||
@@ -786,7 +729,7 @@ impl PciDevice for VfioPciDevice {
|
|||||||
let msb_bar_offset: u32 = PCI_CONFIG_BAR_OFFSET + (bar_id + 1) * 4;
|
let msb_bar_offset: u32 = PCI_CONFIG_BAR_OFFSET + (bar_id + 1) * 4;
|
||||||
|
|
||||||
self.vfio_pci_configuration
|
self.vfio_pci_configuration
|
||||||
.write_config_dword(msb_bar_offset, msb_size);
|
.write_config_dword(msb_size, msb_bar_offset);
|
||||||
|
|
||||||
msb_size = self
|
msb_size = self
|
||||||
.vfio_pci_configuration
|
.vfio_pci_configuration
|
||||||
@@ -808,14 +751,15 @@ impl PciDevice for VfioPciDevice {
|
|||||||
// In case the BAR is mappable directly, this means it might be
|
// In case the BAR is mappable directly, this means it might be
|
||||||
// set as KVM user memory region, which expects to deal with 4K
|
// set as KVM user memory region, which expects to deal with 4K
|
||||||
// pages. Therefore, the aligment has to be set accordingly.
|
// pages. Therefore, the aligment has to be set accordingly.
|
||||||
let bar_alignment =
|
let bar_alignment = if (bar_id == VFIO_PCI_ROM_REGION_INDEX)
|
||||||
if self.device.get_region_flags(bar_id) & VFIO_REGION_INFO_FLAG_MMAP != 0 {
|
|| (self.device.get_region_flags(bar_id) & VFIO_REGION_INFO_FLAG_MMAP != 0)
|
||||||
// 4K alignment
|
{
|
||||||
0x1000
|
// 4K alignment
|
||||||
} else {
|
0x1000
|
||||||
// Default 16 bytes alignment
|
} else {
|
||||||
0x10
|
// Default 16 bytes alignment
|
||||||
};
|
0x10
|
||||||
|
};
|
||||||
if is_64bit_bar {
|
if is_64bit_bar {
|
||||||
bar_addr = allocator
|
bar_addr = allocator
|
||||||
.allocate_mmio_addresses(None, region_size, Some(bar_alignment))
|
.allocate_mmio_addresses(None, region_size, Some(bar_alignment))
|
||||||
@@ -827,22 +771,37 @@ impl PciDevice for VfioPciDevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let reg_idx = if bar_id == VFIO_PCI_ROM_REGION_INDEX {
|
||||||
|
PCI_ROM_EXP_BAR_INDEX
|
||||||
|
} else {
|
||||||
|
bar_id as usize
|
||||||
|
};
|
||||||
|
|
||||||
// We can now build our BAR configuration block.
|
// We can now build our BAR configuration block.
|
||||||
let config = PciBarConfiguration::default()
|
let config = PciBarConfiguration::default()
|
||||||
.set_register_index(bar_id as usize)
|
.set_register_index(reg_idx)
|
||||||
.set_address(bar_addr.raw_value())
|
.set_address(bar_addr.raw_value())
|
||||||
.set_size(region_size)
|
.set_size(region_size)
|
||||||
.set_region_type(region_type);
|
.set_region_type(region_type);
|
||||||
|
|
||||||
self.configuration
|
if bar_id == VFIO_PCI_ROM_REGION_INDEX {
|
||||||
.add_pci_bar(&config)
|
self.configuration
|
||||||
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
.add_pci_rom_bar(&config, lsb_flag & 0x1)
|
||||||
|
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||||
|
} else {
|
||||||
|
self.configuration
|
||||||
|
.add_pci_bar(&config)
|
||||||
|
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr.raw_value(), e))?;
|
||||||
|
}
|
||||||
|
|
||||||
ranges.push((bar_addr, region_size, region_type));
|
ranges.push((bar_addr, region_size, region_type));
|
||||||
self.mmio_regions.push(MmioRegion {
|
self.mmio_regions.push(MmioRegion {
|
||||||
start: bar_addr,
|
start: bar_addr,
|
||||||
length: region_size,
|
length: region_size,
|
||||||
index: bar_id as u32,
|
index: bar_id as u32,
|
||||||
|
mem_slot: None,
|
||||||
|
host_addr: None,
|
||||||
|
mmap_size: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
bar_id += 1;
|
bar_id += 1;
|
||||||
@@ -862,7 +821,9 @@ impl PciDevice for VfioPciDevice {
|
|||||||
// When the guest wants to write to a BAR, we trap it into
|
// When the guest wants to write to a BAR, we trap it into
|
||||||
// our local configuration space. We're not reprogramming
|
// our local configuration space. We're not reprogramming
|
||||||
// VFIO device.
|
// VFIO device.
|
||||||
if reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS {
|
if (reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS)
|
||||||
|
|| reg_idx == PCI_ROM_EXP_BAR_INDEX
|
||||||
|
{
|
||||||
// We keep our local cache updated with the BARs.
|
// We keep our local cache updated with the BARs.
|
||||||
// We'll read it back from there when the guest is asking
|
// We'll read it back from there when the guest is asking
|
||||||
// for BARs (see read_config_register()).
|
// for BARs (see read_config_register()).
|
||||||
@@ -872,8 +833,6 @@ impl PciDevice for VfioPciDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let reg = (reg_idx * PCI_CONFIG_REGISTER_SIZE) as u64;
|
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
|
// If the MSI or MSI-X capabilities are accessed, we need to
|
||||||
// update our local cache accordingly.
|
// update our local cache accordingly.
|
||||||
@@ -888,35 +847,50 @@ impl PciDevice for VfioPciDevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
PciCapabilityID::MSIX => {
|
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
|
// When reading the BARs, we trap it and return what comes
|
||||||
// from our local configuration space. We want the guest to
|
// from our local configuration space. We want the guest to
|
||||||
// use that and not the VFIO device BARs as it does not map
|
// use that and not the VFIO device BARs as it does not map
|
||||||
// with the guest address space.
|
// with the guest address space.
|
||||||
if reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS {
|
if (reg_idx >= PCI_CONFIG_BAR0_INDEX && reg_idx < PCI_CONFIG_BAR0_INDEX + BAR_NUMS)
|
||||||
|
|| reg_idx == PCI_ROM_EXP_BAR_INDEX
|
||||||
|
{
|
||||||
return self.configuration.read_reg(reg_idx);
|
return self.configuration.read_reg(reg_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the ROM expansion BAR is not yet handled by the code, it is
|
|
||||||
// more proper to expose it to the guest as being disabled.
|
|
||||||
if reg_idx == PCI_ROM_EXP_BAR_INDEX {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since we don't support INTx (only MSI and MSI-X), we should not
|
// Since we don't support INTx (only MSI and MSI-X), we should not
|
||||||
// expose an invalid Interrupt Pin to the guest. By using a specific
|
// expose an invalid Interrupt Pin to the guest. By using a specific
|
||||||
// mask in case the register being read correspond to the interrupt
|
// mask in case the register being read correspond to the interrupt
|
||||||
// register, this code makes sure to always expose an Interrupt Pin
|
// register, this code makes sure to always expose an Interrupt Pin
|
||||||
// value of 0, which stands for no interrupt pin support.
|
// value of 0, which stands for no interrupt pin support.
|
||||||
|
//
|
||||||
|
// Since we don't support passing multi-functions devices, we should
|
||||||
|
// mask the multi-function bit, bit 7 of the Header Type byte on the
|
||||||
|
// register 3.
|
||||||
let mask = if reg_idx == PCI_INTX_REG_INDEX {
|
let mask = if reg_idx == PCI_INTX_REG_INDEX {
|
||||||
0xffff_00ff
|
0xffff_00ff
|
||||||
|
} else if reg_idx == PCI_HEADER_TYPE_REG_INDEX {
|
||||||
|
0xff7f_ffff
|
||||||
} else {
|
} else {
|
||||||
0xffff_ffff
|
0xffff_ffff
|
||||||
};
|
};
|
||||||
@@ -927,13 +901,21 @@ impl PciDevice for VfioPciDevice {
|
|||||||
& mask
|
& 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]) {
|
fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) {
|
||||||
let addr = base + offset;
|
let addr = base + offset;
|
||||||
if let Some(region) = self.find_region(addr) {
|
if let Some(region) = self.find_region(addr) {
|
||||||
let offset = addr - region.start.raw_value();
|
let offset = addr - region.start.raw_value();
|
||||||
|
|
||||||
if self.interrupt.msix_table_accessed(region.index, offset) {
|
if self.interrupt.msix_table_accessed(region.index, offset) {
|
||||||
self.read_msix_table(offset, data);
|
self.interrupt.msix_read_table(offset, data);
|
||||||
} else {
|
} else {
|
||||||
self.device.region_read(region.index, data, offset);
|
self.device.region_read(region.index, data, offset);
|
||||||
}
|
}
|
||||||
@@ -947,12 +929,60 @@ impl PciDevice for VfioPciDevice {
|
|||||||
|
|
||||||
// If the MSI-X table is written to, we need to update our cache.
|
// If the MSI-X table is written to, we need to update our cache.
|
||||||
if self.interrupt.msix_table_accessed(region.index, offset) {
|
if self.interrupt.msix_table_accessed(region.index, offset) {
|
||||||
if let Err(e) = self.update_msix_table(offset, data) {
|
self.interrupt.msix_write_table(offset, data);
|
||||||
error!("Could not update MSI-X table: {}", e);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.device.region_write(region.index, data, offset);
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ vhost-user-master = []
|
|||||||
vhost-user-slave = []
|
vhost-user-slave = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = ">=1.0.1"
|
bitflags = "1.2.1"
|
||||||
libc = ">=0.2.39"
|
libc = "0.2.66"
|
||||||
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util" }
|
vmm-sys-util = "0.4.0"
|
||||||
|
|
||||||
[dependencies.vm-memory]
|
[dependencies.vm-memory]
|
||||||
git = "https://github.com/rust-vmm/vm-memory"
|
git = "https://github.com/rust-vmm/vm-memory"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.0.5"
|
tempfile = "3.1.0"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
use super::Result;
|
use super::Result;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use vmm_sys_util::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
/// Maximum number of memory regions supported.
|
/// Maximum number of memory regions supported.
|
||||||
pub const VHOST_MAX_MEMORY_REGIONS: usize = 255;
|
pub const VHOST_MAX_MEMORY_REGIONS: usize = 255;
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
|||||||
use std::ptr::null;
|
use std::ptr::null;
|
||||||
|
|
||||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestUsize};
|
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 vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref};
|
||||||
use vmm_sys_util::EventFd;
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Error, Result, VhostBackend, VhostUserMemoryRegionInfo, VringConfigData,
|
Error, Result, VhostBackend, VhostUserMemoryRegionInfo, VringConfigData,
|
||||||
|
|||||||
@@ -489,7 +489,6 @@ mod tests {
|
|||||||
|
|
||||||
use self::tempfile::tempfile;
|
use self::tempfile::tempfile;
|
||||||
use super::*;
|
use super::*;
|
||||||
use libc;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
use std::os::unix::io::FromRawFd;
|
use std::os::unix::io::FromRawFd;
|
||||||
@@ -513,18 +512,10 @@ mod tests {
|
|||||||
// accept on a fd without incoming connection
|
// accept on a fd without incoming connection
|
||||||
let conn = listener.accept().unwrap();
|
let conn = listener.accept().unwrap();
|
||||||
assert!(conn.is_none());
|
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]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn send_data() {
|
fn send_data() {
|
||||||
let listener = Listener::new(UNIX_SOCKET_DATA, true).unwrap();
|
let listener = Listener::new(UNIX_SOCKET_DATA, true).unwrap();
|
||||||
listener.set_nonblocking(true).unwrap();
|
listener.set_nonblocking(true).unwrap();
|
||||||
@@ -550,6 +541,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn send_fd() {
|
fn send_fd() {
|
||||||
let listener = Listener::new(UNIX_SOCKET_FD, true).unwrap();
|
let listener = Listener::new(UNIX_SOCKET_FD, true).unwrap();
|
||||||
listener.set_nonblocking(true).unwrap();
|
listener.set_nonblocking(true).unwrap();
|
||||||
@@ -703,6 +695,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn send_recv() {
|
fn send_recv() {
|
||||||
let listener = Listener::new(UNIX_SOCKET_SEND, true).unwrap();
|
let listener = Listener::new(UNIX_SOCKET_SEND, true).unwrap();
|
||||||
listener.set_nonblocking(true).unwrap();
|
listener.set_nonblocking(true).unwrap();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
|||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use vmm_sys_util::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
use super::connection::Endpoint;
|
use super::connection::Endpoint;
|
||||||
use super::message::*;
|
use super::message::*;
|
||||||
@@ -40,11 +40,12 @@ pub trait VhostUserMaster: VhostBackend {
|
|||||||
offset: u32,
|
offset: u32,
|
||||||
size: u32,
|
size: u32,
|
||||||
flags: VhostUserConfigFlags,
|
flags: VhostUserConfigFlags,
|
||||||
) -> Result<Vec<u8>>;
|
buf: &[u8],
|
||||||
|
) -> Result<(VhostUserConfig, VhostUserConfigPayload)>;
|
||||||
|
|
||||||
/// Change the virtio device configuration space. It also can be used for live migration on the
|
/// Change the virtio device configuration space. It also can be used for live migration on the
|
||||||
/// destination host to set readonly configuration space fields.
|
/// destination host to set readonly configuration space fields.
|
||||||
fn set_config(&mut self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()>;
|
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()>;
|
||||||
|
|
||||||
/// Setup slave communication channel.
|
/// Setup slave communication channel.
|
||||||
fn set_slave_request_fd(&mut self, fd: RawFd) -> Result<()>;
|
fn set_slave_request_fd(&mut self, fd: RawFd) -> Result<()>;
|
||||||
@@ -166,9 +167,12 @@ impl VhostBackend for Master {
|
|||||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
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<()> {
|
fn set_log_base(&mut self, base: u64, fd: Option<RawFd>) -> Result<()> {
|
||||||
let mut node = self.node.lock().unwrap();
|
let mut node = self.node.lock().unwrap();
|
||||||
let val = VhostUserU64::new(base);
|
let val = VhostUserU64::new(base);
|
||||||
|
|
||||||
if node.acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0
|
if node.acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0
|
||||||
&& fd.is_some()
|
&& fd.is_some()
|
||||||
{
|
{
|
||||||
@@ -344,7 +348,8 @@ impl VhostUserMaster for Master {
|
|||||||
offset: u32,
|
offset: u32,
|
||||||
size: u32,
|
size: u32,
|
||||||
flags: VhostUserConfigFlags,
|
flags: VhostUserConfigFlags,
|
||||||
) -> Result<Vec<u8>> {
|
buf: &[u8],
|
||||||
|
) -> Result<(VhostUserConfig, VhostUserConfigPayload)> {
|
||||||
let body = VhostUserConfig::new(offset, size, flags);
|
let body = VhostUserConfig::new(offset, size, flags);
|
||||||
if !body.is_valid() {
|
if !body.is_valid() {
|
||||||
return error_code(VhostUserError::InvalidParam);
|
return error_code(VhostUserError::InvalidParam);
|
||||||
@@ -352,28 +357,28 @@ impl VhostUserMaster for Master {
|
|||||||
|
|
||||||
let mut node = self.node.lock().unwrap();
|
let mut node = self.node.lock().unwrap();
|
||||||
// depends on VhostUserProtocolFeatures::CONFIG
|
// depends on VhostUserProtocolFeatures::CONFIG
|
||||||
if node.acked_virtio_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||||
return error_code(VhostUserError::InvalidOperation);
|
return error_code(VhostUserError::InvalidOperation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: vhost-user spec states that:
|
// vhost-user spec states that:
|
||||||
// "Master payload: virtio device config space"
|
// "Master payload: virtio device config space"
|
||||||
// But what content should the payload contains for a get_config() request?
|
// "Slave payload: virtio device config space"
|
||||||
// So current implementation doesn't conform to the spec.
|
let hdr = node.send_request_with_payload(MasterReq::GET_CONFIG, &body, buf, None)?;
|
||||||
let hdr = node.send_request_with_body(MasterReq::GET_CONFIG, &body, None)?;
|
let (body_reply, buf_reply, rfds) =
|
||||||
let (reply, buf, rfds) = node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
|
node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
|
||||||
if rfds.is_some() {
|
if rfds.is_some() {
|
||||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||||
return error_code(VhostUserError::InvalidMessage);
|
return error_code(VhostUserError::InvalidMessage);
|
||||||
} else if reply.size == 0 {
|
} else if body_reply.size == 0 {
|
||||||
return error_code(VhostUserError::SlaveInternalError);
|
return error_code(VhostUserError::SlaveInternalError);
|
||||||
} else if reply.size != body.size || reply.size as usize != buf.len() {
|
} else if body_reply.size != body.size || body_reply.size as usize != buf.len() {
|
||||||
return error_code(VhostUserError::InvalidMessage);
|
return error_code(VhostUserError::InvalidMessage);
|
||||||
}
|
}
|
||||||
Ok(buf)
|
Ok((body_reply, buf_reply))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_config(&mut self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()> {
|
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()> {
|
||||||
if buf.len() > MAX_MSG_SIZE {
|
if buf.len() > MAX_MSG_SIZE {
|
||||||
return error_code(VhostUserError::InvalidParam);
|
return error_code(VhostUserError::InvalidParam);
|
||||||
}
|
}
|
||||||
@@ -384,11 +389,11 @@ impl VhostUserMaster for Master {
|
|||||||
|
|
||||||
let mut node = self.node.lock().unwrap();
|
let mut node = self.node.lock().unwrap();
|
||||||
// depends on VhostUserProtocolFeatures::CONFIG
|
// depends on VhostUserProtocolFeatures::CONFIG
|
||||||
if node.acked_virtio_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||||
return error_code(VhostUserError::InvalidOperation);
|
return error_code(VhostUserError::InvalidOperation);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hdr = node.send_request_with_payload(MasterReq::GET_CONFIG, &body, buf, None)?;
|
let hdr = node.send_request_with_payload(MasterReq::SET_CONFIG, &body, buf, None)?;
|
||||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,12 +555,12 @@ impl MasterInternal {
|
|||||||
}
|
}
|
||||||
self.check_state()?;
|
self.check_state()?;
|
||||||
|
|
||||||
let mut buf = vec![0; MAX_MSG_SIZE - mem::size_of::<T>()];
|
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)?;
|
let (reply, body, bytes, rfds) = self.main_sock.recv_payload_into_buf::<T>(&mut buf)?;
|
||||||
if !reply.is_reply_for(hdr)
|
if !reply.is_reply_for(hdr)
|
||||||
|| reply.get_size() as usize != mem::size_of::<T>() + bytes
|
|| reply.get_size() as usize != mem::size_of::<T>() + bytes
|
||||||
|| rfds.is_some()
|
|| rfds.is_some()
|
||||||
|| body.is_valid()
|
|| !body.is_valid()
|
||||||
{
|
{
|
||||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||||
return Err(VhostUserError::InvalidMessage);
|
return Err(VhostUserError::InvalidMessage);
|
||||||
@@ -603,7 +608,7 @@ impl MasterInternal {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn new_request_header(request: MasterReq, size: u32) -> VhostUserMsgHeader<MasterReq> {
|
fn new_request_header(request: MasterReq, size: u32) -> VhostUserMsgHeader<MasterReq> {
|
||||||
// TODO: handle NEED_REPLY flag
|
// TODO: handle NEED_REPLY flag
|
||||||
VhostUserMsgHeader::new(request, 0, size)
|
VhostUserMsgHeader::new(request, 0x1, size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,6 +631,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn create_master() {
|
fn create_master() {
|
||||||
let listener = Listener::new(UNIX_SOCKET_MASTER, true).unwrap();
|
let listener = Listener::new(UNIX_SOCKET_MASTER, true).unwrap();
|
||||||
listener.set_nonblocking(true).unwrap();
|
listener.set_nonblocking(true).unwrap();
|
||||||
@@ -651,6 +657,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn test_create_failure() {
|
fn test_create_failure() {
|
||||||
let _ = Listener::new(UNIX_SOCKET_MASTER2, true).unwrap();
|
let _ = Listener::new(UNIX_SOCKET_MASTER2, true).unwrap();
|
||||||
let _ = Listener::new(UNIX_SOCKET_MASTER2, false).is_err();
|
let _ = Listener::new(UNIX_SOCKET_MASTER2, false).is_err();
|
||||||
@@ -665,6 +672,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn test_features() {
|
fn test_features() {
|
||||||
let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER3);
|
let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER3);
|
||||||
|
|
||||||
@@ -696,6 +704,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn test_protocol_features() {
|
fn test_protocol_features() {
|
||||||
let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER4);
|
let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER4);
|
||||||
|
|
||||||
|
|||||||
@@ -109,14 +109,14 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match hdr.get_code() {
|
let res = match hdr.get_code() {
|
||||||
SlaveReq::CONFIG_CHANGE_MSG => {
|
SlaveReq::CONFIG_CHANGE_MSG => {
|
||||||
self.check_msg_size(&hdr, size, 0)?;
|
self.check_msg_size(&hdr, size, 0)?;
|
||||||
self.backend
|
self.backend
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.handle_config_change()
|
.handle_config_change()
|
||||||
.map_err(Error::ReqHandlerError)?;
|
.map_err(Error::ReqHandlerError)
|
||||||
}
|
}
|
||||||
SlaveReq::FS_MAP => {
|
SlaveReq::FS_MAP => {
|
||||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||||
@@ -124,7 +124,7 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.fs_slave_map(msg, rfds.unwrap()[0])
|
.fs_slave_map(msg, rfds.unwrap()[0])
|
||||||
.map_err(Error::ReqHandlerError)?;
|
.map_err(Error::ReqHandlerError)
|
||||||
}
|
}
|
||||||
SlaveReq::FS_UNMAP => {
|
SlaveReq::FS_UNMAP => {
|
||||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||||
@@ -132,7 +132,7 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.fs_slave_unmap(msg)
|
.fs_slave_unmap(msg)
|
||||||
.map_err(Error::ReqHandlerError)?;
|
.map_err(Error::ReqHandlerError)
|
||||||
}
|
}
|
||||||
SlaveReq::FS_SYNC => {
|
SlaveReq::FS_SYNC => {
|
||||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||||
@@ -140,14 +140,14 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.fs_slave_sync(msg)
|
.fs_slave_sync(msg)
|
||||||
.map_err(Error::ReqHandlerError)?;
|
.map_err(Error::ReqHandlerError)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => Err(Error::InvalidMessage),
|
||||||
return Err(Error::InvalidMessage);
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
self.send_ack_message(&hdr, &res)?;
|
||||||
|
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_state(&self) -> Result<()> {
|
fn check_state(&self) -> Result<()> {
|
||||||
@@ -217,6 +217,38 @@ impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
|||||||
}
|
}
|
||||||
Ok(msg)
|
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> {
|
impl<S: VhostUserMasterReqHandler> AsRawFd for MasterReqHandler<S> {
|
||||||
|
|||||||
@@ -346,6 +346,8 @@ bitflags! {
|
|||||||
const SLAVE_SEND_FD = 0x0000_0400;
|
const SLAVE_SEND_FD = 0x0000_0400;
|
||||||
/// Allow the slave to register a host notifier.
|
/// Allow the slave to register a host notifier.
|
||||||
const HOST_NOTIFIER = 0x0000_0800;
|
const HOST_NOTIFIER = 0x0000_0800;
|
||||||
|
/// Support inflight shmfd.
|
||||||
|
const INFLIGHT_SHMFD = 0x0000_1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,12 +542,10 @@ impl VhostUserMsgValidator for VhostUserVringAddr {
|
|||||||
bitflags! {
|
bitflags! {
|
||||||
/// Flags for the device configuration message.
|
/// Flags for the device configuration message.
|
||||||
pub struct VhostUserConfigFlags: u32 {
|
pub struct VhostUserConfigFlags: u32 {
|
||||||
/// TODO: seems the vhost-user spec has refined the definition, EMPTY is removed.
|
/// Vhost master messages used for writeable fields.
|
||||||
const EMPTY = 0x0;
|
const WRITABLE = 0x0;
|
||||||
/// Vhost master messages used for writable fields
|
/// Vhost master messages used for live migration.
|
||||||
const WRITABLE = 0x1;
|
const LIVE_MIGRATION = 0x1;
|
||||||
/// Mark that message is part of an ongoing live-migration operation.
|
|
||||||
const LIVE_MIGRATION = 0x2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,10 +577,9 @@ impl VhostUserMsgValidator for VhostUserConfig {
|
|||||||
fn is_valid(&self) -> bool {
|
fn is_valid(&self) -> bool {
|
||||||
if (self.flags & !VhostUserConfigFlags::all().bits()) != 0 {
|
if (self.flags & !VhostUserConfigFlags::all().bits()) != 0 {
|
||||||
return false;
|
return false;
|
||||||
} else if self.offset < VHOST_USER_CONFIG_OFFSET
|
} else if self.offset >= VHOST_USER_CONFIG_SIZE
|
||||||
|| self.offset >= VHOST_USER_CONFIG_SIZE
|
|
||||||
|| self.size == 0
|
|| self.size == 0
|
||||||
|| self.size > (VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET)
|
|| self.size > VHOST_USER_CONFIG_SIZE
|
||||||
|| self.size + self.offset > VHOST_USER_CONFIG_SIZE
|
|| self.size + self.offset > VHOST_USER_CONFIG_SIZE
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -632,7 +631,7 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Max entries in one virtio-fs slave request.
|
/// Max entries in one virtio-fs slave request.
|
||||||
const VHOST_USER_FS_SLAVE_ENTRIES: usize = 8;
|
pub const VHOST_USER_FS_SLAVE_ENTRIES: usize = 8;
|
||||||
|
|
||||||
/// Slave request message to update the MMIO window.
|
/// Slave request message to update the MMIO window.
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
@@ -783,11 +782,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[ignore]
|
||||||
fn check_user_config_msg() {
|
fn check_user_config_msg() {
|
||||||
let mut msg = VhostUserConfig::new(
|
let mut msg = VhostUserConfig::new(
|
||||||
VHOST_USER_CONFIG_OFFSET,
|
VHOST_USER_CONFIG_OFFSET,
|
||||||
VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET,
|
VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET,
|
||||||
VhostUserConfigFlags::EMPTY,
|
VhostUserConfigFlags::WRITABLE,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(msg.is_valid());
|
assert!(msg.is_valid());
|
||||||
@@ -804,7 +804,7 @@ mod tests {
|
|||||||
msg.size = 2;
|
msg.size = 2;
|
||||||
assert!(!msg.is_valid());
|
assert!(!msg.is_valid());
|
||||||
msg.size = 1;
|
msg.size = 1;
|
||||||
msg.flags |= VhostUserConfigFlags::WRITABLE.bits();
|
msg.flags |= VhostUserConfigFlags::LIVE_MIGRATION.bits();
|
||||||
assert!(msg.is_valid());
|
assert!(msg.is_valid());
|
||||||
msg.flags |= 0x4;
|
msg.flags |= 0x4;
|
||||||
assert!(!msg.is_valid());
|
assert!(!msg.is_valid());
|
||||||
|
|||||||
@@ -116,10 +116,10 @@ impl Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<vmm_sys_util::Error> for Error {
|
impl std::convert::From<vmm_sys_util::errno::Error> for Error {
|
||||||
/// Convert raw socket errors into meaningful vhost-user errors.
|
/// Convert raw socket errors into meaningful vhost-user errors.
|
||||||
///
|
///
|
||||||
/// The vmm_sys_util::Error is a simple wrapper over the raw errno, which doesn't means much
|
/// 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
|
/// to the vhost-user connection manager. So convert it into meaningful errors to simplify
|
||||||
/// the connection manager logic.
|
/// the connection manager logic.
|
||||||
///
|
///
|
||||||
@@ -128,7 +128,7 @@ impl std::convert::From<vmm_sys_util::Error> for Error {
|
|||||||
/// * - Error::SocketBroken: the underline socket is broken.
|
/// * - Error::SocketBroken: the underline socket is broken.
|
||||||
/// * - Error::SocketError: other socket related errors.
|
/// * - Error::SocketError: other socket related errors.
|
||||||
#[allow(unreachable_patterns)] // EWOULDBLOCK equals to EGAIN on linux
|
#[allow(unreachable_patterns)] // EWOULDBLOCK equals to EGAIN on linux
|
||||||
fn from(err: vmm_sys_util::Error) -> Self {
|
fn from(err: vmm_sys_util::errno::Error) -> Self {
|
||||||
match err.errno() {
|
match err.errno() {
|
||||||
// The socket is marked nonblocking and the requested operation would block.
|
// The socket is marked nonblocking and the requested operation would block.
|
||||||
libc::EAGAIN => Error::SocketRetry(IOError::from_raw_os_error(libc::EAGAIN)),
|
libc::EAGAIN => Error::SocketRetry(IOError::from_raw_os_error(libc::EAGAIN)),
|
||||||
@@ -178,7 +178,7 @@ mod tests {
|
|||||||
backend: Arc<Mutex<S>>,
|
backend: Arc<Mutex<S>>,
|
||||||
) -> (Master, SlaveReqHandler<S>) {
|
) -> (Master, SlaveReqHandler<S>) {
|
||||||
let mut slave_listener = SlaveListener::new(path, true, backend).unwrap();
|
let mut slave_listener = SlaveListener::new(path, true, backend).unwrap();
|
||||||
let master = Master::connect(path).unwrap();
|
let master = Master::connect(path, 1).unwrap();
|
||||||
(master, slave_listener.accept().unwrap().unwrap())
|
(master, slave_listener.accept().unwrap().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -265,7 +265,6 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
|
|||||||
if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||||
return Err(Error::InvalidOperation);
|
return Err(Error::InvalidOperation);
|
||||||
}
|
}
|
||||||
self.check_request_size(&hdr, size, mem::size_of::<VhostUserConfig>())?;
|
|
||||||
self.get_config(&hdr, &buf)?;
|
self.get_config(&hdr, &buf)?;
|
||||||
}
|
}
|
||||||
MasterReq::SET_CONFIG => {
|
MasterReq::SET_CONFIG => {
|
||||||
@@ -341,6 +340,10 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
|
|||||||
if !msg.is_valid() {
|
if !msg.is_valid() {
|
||||||
return Err(Error::InvalidMessage);
|
return Err(Error::InvalidMessage);
|
||||||
}
|
}
|
||||||
|
let payload_offset = mem::size_of::<VhostUserConfig>();
|
||||||
|
if buf.len() - payload_offset != msg.size as usize {
|
||||||
|
return Err(Error::InvalidMessage);
|
||||||
|
}
|
||||||
let flags = match VhostUserConfigFlags::from_bits(msg.flags) {
|
let flags = match VhostUserConfigFlags::from_bits(msg.flags) {
|
||||||
Some(val) => val,
|
Some(val) => val,
|
||||||
None => return Err(Error::InvalidMessage),
|
None => return Err(Error::InvalidMessage),
|
||||||
@@ -519,6 +522,7 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
|
|||||||
fn new_reply_header<T: Sized>(
|
fn new_reply_header<T: Sized>(
|
||||||
&self,
|
&self,
|
||||||
req: &VhostUserMsgHeader<MasterReq>,
|
req: &VhostUserMsgHeader<MasterReq>,
|
||||||
|
payload_size: usize,
|
||||||
) -> Result<VhostUserMsgHeader<MasterReq>> {
|
) -> Result<VhostUserMsgHeader<MasterReq>> {
|
||||||
if mem::size_of::<T>() > MAX_MSG_SIZE {
|
if mem::size_of::<T>() > MAX_MSG_SIZE {
|
||||||
return Err(Error::InvalidParam);
|
return Err(Error::InvalidParam);
|
||||||
@@ -527,7 +531,7 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
|
|||||||
Ok(VhostUserMsgHeader::new(
|
Ok(VhostUserMsgHeader::new(
|
||||||
req.get_code(),
|
req.get_code(),
|
||||||
VhostUserHeaderFlag::REPLY.bits(),
|
VhostUserHeaderFlag::REPLY.bits(),
|
||||||
mem::size_of::<T>() as u32,
|
(mem::size_of::<T>() + payload_size) as u32,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,7 +541,7 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
|
|||||||
res: Result<()>,
|
res: Result<()>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if self.reply_ack_enabled {
|
if self.reply_ack_enabled {
|
||||||
let hdr = self.new_reply_header::<VhostUserU64>(req)?;
|
let hdr = self.new_reply_header::<VhostUserU64>(req, 0)?;
|
||||||
let val = match res {
|
let val = match res {
|
||||||
Ok(_) => 0,
|
Ok(_) => 0,
|
||||||
Err(_) => 1,
|
Err(_) => 1,
|
||||||
@@ -553,7 +557,7 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
|
|||||||
req: &VhostUserMsgHeader<MasterReq>,
|
req: &VhostUserMsgHeader<MasterReq>,
|
||||||
msg: &T,
|
msg: &T,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let hdr = self.new_reply_header::<T>(req)?;
|
let hdr = self.new_reply_header::<T>(req, 0)?;
|
||||||
self.main_sock.send_message(&hdr, msg, None)?;
|
self.main_sock.send_message(&hdr, msg, None)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -568,7 +572,7 @@ impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
|
|||||||
T: Sized,
|
T: Sized,
|
||||||
P: Sized,
|
P: Sized,
|
||||||
{
|
{
|
||||||
let hdr = self.new_reply_header::<T>(req)?;
|
let hdr = self.new_reply_header::<T>(req, payload.len())?;
|
||||||
self.main_sock
|
self.main_sock
|
||||||
.send_message_with_payload(&hdr, msg, payload, None)?;
|
.send_message_with_payload(&hdr, msg, payload, None)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use std::ptr::{copy_nonoverlapping, null_mut, write_unaligned};
|
|||||||
use libc::{
|
use libc::{
|
||||||
c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET,
|
c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET,
|
||||||
};
|
};
|
||||||
use vmm_sys_util::{Error, Result};
|
use vmm_sys_util::errno::{Error, Result};
|
||||||
|
|
||||||
// Each of the following macros performs the same function as their C counterparts. They are each
|
// 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.
|
// macros because they are used to size statically allocated arrays.
|
||||||
@@ -176,7 +176,7 @@ fn raw_recvmsg(fd: RawFd, iovecs: &mut [iovec], in_fds: &mut [RawFd]) -> Result<
|
|||||||
|
|
||||||
// Safe because the msghdr was properly constructed from valid (or null) pointers of the
|
// Safe because the msghdr was properly constructed from valid (or null) pointers of the
|
||||||
// indicated length and we check the return value.
|
// indicated length and we check the return value.
|
||||||
let total_read = unsafe { recvmsg(fd, &mut msg, 0) };
|
let total_read = unsafe { recvmsg(fd, &mut msg, libc::MSG_WAITALL) };
|
||||||
|
|
||||||
if total_read == -1 {
|
if total_read == -1 {
|
||||||
return Err(Error::last());
|
return Err(Error::last());
|
||||||
@@ -335,7 +335,7 @@ mod tests {
|
|||||||
|
|
||||||
use libc::cmsghdr;
|
use libc::cmsghdr;
|
||||||
|
|
||||||
use vmm_sys_util::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn buffer_len() {
|
fn buffer_len() {
|
||||||
|
|||||||
22
vhost_user_backend/Cargo.toml
Normal file
22
vhost_user_backend/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "vhost_user_backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
pci_support = ["vm-virtio/pci_support"]
|
||||||
|
mmio_support = ["vm-virtio/mmio_support"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
epoll = "4.1.0"
|
||||||
|
libc = "0.2.66"
|
||||||
|
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||||
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
|
vmm-sys-util = "0.4.0"
|
||||||
|
|
||||||
|
[dependencies.vhost_rs]
|
||||||
|
path = "../vhost_rs"
|
||||||
|
features = ["vhost-user-slave"]
|
||||||
|
|
||||||
738
vhost_user_backend/src/lib.rs
Normal file
738
vhost_user_backend/src/lib.rs
Normal file
@@ -0,0 +1,738 @@
|
|||||||
|
// Copyright 2019 Intel Corporation. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
// Copyright 2019 Alibaba Cloud Computing. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use std::error;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io;
|
||||||
|
use std::num::Wrapping;
|
||||||
|
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||||
|
use std::result;
|
||||||
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
use std::thread;
|
||||||
|
use vhost_rs::vhost_user::message::{
|
||||||
|
VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures,
|
||||||
|
VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState,
|
||||||
|
};
|
||||||
|
use vhost_rs::vhost_user::{
|
||||||
|
Error as VhostUserError, Result as VhostUserResult, SlaveListener, VhostUserSlaveReqHandler,
|
||||||
|
};
|
||||||
|
use vm_memory::guest_memory::FileOffset;
|
||||||
|
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||||
|
use vm_virtio::Queue;
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Errors related to vhost-user daemon.
|
||||||
|
pub enum Error {
|
||||||
|
/// Failed to create a new vhost-user handler.
|
||||||
|
NewVhostUserHandler(VhostUserHandlerError),
|
||||||
|
/// Failed creating vhost-user slave listener.
|
||||||
|
CreateSlaveListener(VhostUserError),
|
||||||
|
/// Failed creating vhost-user slave handler.
|
||||||
|
CreateSlaveReqHandler(VhostUserError),
|
||||||
|
/// Failed starting daemon thread.
|
||||||
|
StartDaemon(io::Error),
|
||||||
|
/// Failed waiting for daemon thread.
|
||||||
|
WaitDaemon(std::boxed::Box<dyn std::any::Any + std::marker::Send>),
|
||||||
|
/// Failed handling a vhost-user request.
|
||||||
|
HandleRequest(VhostUserError),
|
||||||
|
/// Failed to process queue.
|
||||||
|
ProcessQueue(VringEpollHandlerError),
|
||||||
|
/// Failed to register listener.
|
||||||
|
RegisterListener(io::Error),
|
||||||
|
/// Failed to unregister listener.
|
||||||
|
UnregisterListener(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of vhost-user daemon operations.
|
||||||
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// This trait must be implemented by the caller in order to provide backend
|
||||||
|
/// specific implementation.
|
||||||
|
pub trait VhostUserBackend: Send + Sync + 'static {
|
||||||
|
/// Number of queues.
|
||||||
|
fn num_queues(&self) -> usize;
|
||||||
|
|
||||||
|
/// Depth of each queue.
|
||||||
|
fn max_queue_size(&self) -> usize;
|
||||||
|
|
||||||
|
/// Virtio features.
|
||||||
|
fn features(&self) -> u64;
|
||||||
|
|
||||||
|
/// Virtio protocol features.
|
||||||
|
fn protocol_features(&self) -> VhostUserProtocolFeatures;
|
||||||
|
|
||||||
|
/// Update guest memory regions.
|
||||||
|
fn update_memory(&mut self, mem: GuestMemoryMmap) -> result::Result<(), io::Error>;
|
||||||
|
|
||||||
|
/// This function gets called if the backend registered some additional
|
||||||
|
/// listeners onto specific file descriptors. The library can handle
|
||||||
|
/// virtqueues on its own, but does not know what to do with events
|
||||||
|
/// happening on custom listeners.
|
||||||
|
fn handle_event(
|
||||||
|
&mut self,
|
||||||
|
device_event: u16,
|
||||||
|
evset: epoll::Events,
|
||||||
|
vrings: &[Arc<RwLock<Vring>>],
|
||||||
|
) -> result::Result<bool, io::Error>;
|
||||||
|
|
||||||
|
/// Get virtio device configuration.
|
||||||
|
/// A default implementation is provided as we cannot expect all backends
|
||||||
|
/// to implement this function.
|
||||||
|
fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set virtio device configuration.
|
||||||
|
/// A default implementation is provided as we cannot expect all backends
|
||||||
|
/// to implement this function.
|
||||||
|
fn set_config(&mut self, _offset: u32, _buf: &[u8]) -> result::Result<(), io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This structure is the public API the backend is allowed to interact with
|
||||||
|
/// in order to run a fully functional vhost-user daemon.
|
||||||
|
pub struct VhostUserDaemon<S: VhostUserBackend> {
|
||||||
|
name: String,
|
||||||
|
sock_path: String,
|
||||||
|
handler: Arc<Mutex<VhostUserHandler<S>>>,
|
||||||
|
main_thread: Option<thread::JoinHandle<Result<()>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: VhostUserBackend> VhostUserDaemon<S> {
|
||||||
|
/// Create the daemon instance, providing the backend implementation of
|
||||||
|
/// VhostUserBackend.
|
||||||
|
/// Under the hood, this will start a dedicated thread responsible for
|
||||||
|
/// listening onto registered event. Those events can be vring events or
|
||||||
|
/// custom events from the backend, but they get to be registered later
|
||||||
|
/// during the sequence.
|
||||||
|
pub fn new(name: String, sock_path: String, backend: Arc<RwLock<S>>) -> Result<Self> {
|
||||||
|
let handler = Arc::new(Mutex::new(
|
||||||
|
VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?,
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(VhostUserDaemon {
|
||||||
|
name,
|
||||||
|
sock_path,
|
||||||
|
handler,
|
||||||
|
main_thread: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connect to the vhost-user socket and run a dedicated thread handling
|
||||||
|
/// all requests coming through this socket. This runs in an infinite loop
|
||||||
|
/// that should be terminating once the other end of the socket (the VMM)
|
||||||
|
/// disconnects.
|
||||||
|
pub fn start(&mut self) -> Result<()> {
|
||||||
|
let mut slave_listener =
|
||||||
|
SlaveListener::new(self.sock_path.as_str(), true, self.handler.clone())
|
||||||
|
.map_err(Error::CreateSlaveListener)?;
|
||||||
|
let mut slave_handler = slave_listener
|
||||||
|
.accept()
|
||||||
|
.map_err(Error::CreateSlaveReqHandler)?
|
||||||
|
.unwrap();
|
||||||
|
let handle = thread::Builder::new()
|
||||||
|
.name(self.name.clone())
|
||||||
|
.spawn(move || loop {
|
||||||
|
slave_handler
|
||||||
|
.handle_request()
|
||||||
|
.map_err(Error::HandleRequest)?;
|
||||||
|
})
|
||||||
|
.map_err(Error::StartDaemon)?;
|
||||||
|
|
||||||
|
self.main_thread = Some(handle);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for the thread handling the vhost-user socket connection to
|
||||||
|
/// terminate.
|
||||||
|
pub fn wait(&mut self) -> Result<()> {
|
||||||
|
if let Some(handle) = self.main_thread.take() {
|
||||||
|
let _ = handle.join().map_err(Error::WaitDaemon)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the vring worker. This is necessary to perform further
|
||||||
|
/// actions like registering and unregistering some extra event file
|
||||||
|
/// descriptors.
|
||||||
|
pub fn get_vring_worker(&self) -> Arc<VringWorker> {
|
||||||
|
self.handler.lock().unwrap().get_vring_worker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AddrMapping {
|
||||||
|
vmm_addr: u64,
|
||||||
|
size: u64,
|
||||||
|
gpa_base: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Memory {
|
||||||
|
mappings: Vec<AddrMapping>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Vring {
|
||||||
|
queue: Queue,
|
||||||
|
kick: Option<EventFd>,
|
||||||
|
call: Option<EventFd>,
|
||||||
|
err: Option<EventFd>,
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vring {
|
||||||
|
fn new(max_queue_size: u16) -> Self {
|
||||||
|
Vring {
|
||||||
|
queue: Queue::new(max_queue_size),
|
||||||
|
kick: None,
|
||||||
|
call: None,
|
||||||
|
err: None,
|
||||||
|
enabled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mut_queue(&mut self) -> &mut Queue {
|
||||||
|
&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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Errors related to vring epoll handler.
|
||||||
|
pub enum VringEpollHandlerError {
|
||||||
|
/// Failed to process the queue from the backend.
|
||||||
|
ProcessQueueBackendProcessing(io::Error),
|
||||||
|
/// Failed to signal used queue.
|
||||||
|
SignalUsedQueue(io::Error),
|
||||||
|
/// Failed to read the event from kick EventFd.
|
||||||
|
HandleEventReadKick(io::Error),
|
||||||
|
/// Failed to handle the event from the backend.
|
||||||
|
HandleEventBackendHandling(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of vring epoll handler operations.
|
||||||
|
type VringEpollHandlerResult<T> = std::result::Result<T, VringEpollHandlerError>;
|
||||||
|
|
||||||
|
struct VringEpollHandler<S: VhostUserBackend> {
|
||||||
|
backend: Arc<RwLock<S>>,
|
||||||
|
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: VhostUserBackend> VringEpollHandler<S> {
|
||||||
|
fn handle_event(
|
||||||
|
&self,
|
||||||
|
device_event: u16,
|
||||||
|
evset: epoll::Events,
|
||||||
|
) -> VringEpollHandlerResult<bool> {
|
||||||
|
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 {
|
||||||
|
kick.read()
|
||||||
|
.map_err(VringEpollHandlerError::HandleEventReadKick)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the vring is not enabled, it should not be processed.
|
||||||
|
// The event is only read to be discarded.
|
||||||
|
if !self.vrings[device_event as usize].read().unwrap().enabled {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.backend
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.handle_event(device_event, evset, &self.vrings)
|
||||||
|
.map_err(VringEpollHandlerError::HandleEventBackendHandling)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Errors related to vring worker.
|
||||||
|
enum VringWorkerError {
|
||||||
|
/// Failed while waiting for events.
|
||||||
|
EpollWait(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of vring worker operations.
|
||||||
|
type VringWorkerResult<T> = std::result::Result<T, VringWorkerError>;
|
||||||
|
|
||||||
|
pub struct VringWorker {
|
||||||
|
epoll_fd: RawFd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VringWorker {
|
||||||
|
fn run<S: VhostUserBackend>(&self, handler: VringEpollHandler<S>) -> VringWorkerResult<()> {
|
||||||
|
const EPOLL_EVENTS_LEN: usize = 100;
|
||||||
|
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||||
|
|
||||||
|
'epoll: loop {
|
||||||
|
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() == io::ErrorKind::Interrupted {
|
||||||
|
// It's well defined from the epoll_wait() syscall
|
||||||
|
// documentation that the epoll loop can be interrupted
|
||||||
|
// before any of the requested events occurred or the
|
||||||
|
// timeout expired. In both those cases, epoll_wait()
|
||||||
|
// returns an error of type EINTR, but this should not
|
||||||
|
// be considered as a regular error. Instead it is more
|
||||||
|
// appropriate to retry, by calling into epoll_wait().
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Err(VringWorkerError::EpollWait(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for event in events.iter().take(num_events) {
|
||||||
|
let evset = match epoll::Events::from_bits(event.events) {
|
||||||
|
Some(evset) => evset,
|
||||||
|
None => {
|
||||||
|
let evbits = event.events;
|
||||||
|
println!("epoll: ignoring unknown event set: 0x{:x}", evbits);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
break 'epoll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a custom event only meaningful to the caller. When this event
|
||||||
|
/// is later triggered, and because only the caller knows what to do about
|
||||||
|
/// it, the backend implementation of `handle_event` will be called.
|
||||||
|
/// This lets entire control to the caller about what needs to be done for
|
||||||
|
/// this special event, without forcing it to run its own dedicated epoll
|
||||||
|
/// loop for it.
|
||||||
|
pub fn register_listener(
|
||||||
|
&self,
|
||||||
|
fd: RawFd,
|
||||||
|
ev_type: epoll::Events,
|
||||||
|
data: u64,
|
||||||
|
) -> result::Result<(), io::Error> {
|
||||||
|
epoll::ctl(
|
||||||
|
self.epoll_fd,
|
||||||
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
|
fd,
|
||||||
|
epoll::Event::new(ev_type, data),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unregister a custom event. If the custom event is triggered after this
|
||||||
|
/// function has been called, nothing will happen as it will be removed
|
||||||
|
/// from the list of file descriptors the epoll loop is listening to.
|
||||||
|
pub fn unregister_listener(
|
||||||
|
&self,
|
||||||
|
fd: RawFd,
|
||||||
|
ev_type: epoll::Events,
|
||||||
|
data: u64,
|
||||||
|
) -> result::Result<(), io::Error> {
|
||||||
|
epoll::ctl(
|
||||||
|
self.epoll_fd,
|
||||||
|
epoll::ControlOptions::EPOLL_CTL_DEL,
|
||||||
|
fd,
|
||||||
|
epoll::Event::new(ev_type, data),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Errors related to vhost-user handler.
|
||||||
|
pub enum VhostUserHandlerError {
|
||||||
|
/// Failed to create epoll file descriptor.
|
||||||
|
EpollCreateFd(io::Error),
|
||||||
|
/// Failed to spawn vring worker.
|
||||||
|
SpawnVringWorker(io::Error),
|
||||||
|
/// Could not find the mapping from memory regions.
|
||||||
|
MissingMemoryMapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for VhostUserHandlerError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
VhostUserHandlerError::EpollCreateFd(e) => write!(f, "failed creating epoll fd: {}", e),
|
||||||
|
VhostUserHandlerError::SpawnVringWorker(e) => {
|
||||||
|
write!(f, "failed spawning the vring worker: {}", e)
|
||||||
|
}
|
||||||
|
VhostUserHandlerError::MissingMemoryMapping => write!(f, "Missing memory mapping"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for VhostUserHandlerError {}
|
||||||
|
|
||||||
|
/// Result of vhost-user handler operations.
|
||||||
|
type VhostUserHandlerResult<T> = std::result::Result<T, VhostUserHandlerError>;
|
||||||
|
|
||||||
|
struct VhostUserHandler<S: VhostUserBackend> {
|
||||||
|
backend: Arc<RwLock<S>>,
|
||||||
|
worker: Arc<VringWorker>,
|
||||||
|
owned: bool,
|
||||||
|
features_acked: bool,
|
||||||
|
acked_features: u64,
|
||||||
|
acked_protocol_features: u64,
|
||||||
|
num_queues: usize,
|
||||||
|
max_queue_size: usize,
|
||||||
|
memory: Option<Memory>,
|
||||||
|
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||||
|
fn new(backend: Arc<RwLock<S>>) -> VhostUserHandlerResult<Self> {
|
||||||
|
let num_queues = backend.read().unwrap().num_queues();
|
||||||
|
let max_queue_size = backend.read().unwrap().max_queue_size();
|
||||||
|
|
||||||
|
let mut vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
|
||||||
|
for _ in 0..num_queues {
|
||||||
|
let vring = Arc::new(RwLock::new(Vring::new(max_queue_size as u16)));
|
||||||
|
vrings.push(vring);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)?;
|
||||||
|
|
||||||
|
Ok(VhostUserHandler {
|
||||||
|
backend,
|
||||||
|
worker,
|
||||||
|
owned: false,
|
||||||
|
features_acked: false,
|
||||||
|
acked_features: 0,
|
||||||
|
acked_protocol_features: 0,
|
||||||
|
num_queues,
|
||||||
|
max_queue_size,
|
||||||
|
memory: None,
|
||||||
|
vrings,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_vring_worker(&self) -> Arc<VringWorker> {
|
||||||
|
self.worker.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult<u64> {
|
||||||
|
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.gpa_base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(VhostUserHandlerError::MissingMemoryMapping)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
||||||
|
fn set_owner(&mut self) -> VhostUserResult<()> {
|
||||||
|
if self.owned {
|
||||||
|
return Err(VhostUserError::InvalidOperation);
|
||||||
|
}
|
||||||
|
self.owned = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_owner(&mut self) -> VhostUserResult<()> {
|
||||||
|
self.owned = false;
|
||||||
|
self.features_acked = false;
|
||||||
|
self.acked_features = 0;
|
||||||
|
self.acked_protocol_features = 0;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_features(&mut self) -> VhostUserResult<u64> {
|
||||||
|
Ok(self.backend.read().unwrap().features())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_features(&mut self, features: u64) -> VhostUserResult<()> {
|
||||||
|
if (features & !self.backend.read().unwrap().features()) != 0 {
|
||||||
|
return Err(VhostUserError::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 vring in self.vrings.iter_mut() {
|
||||||
|
vring.write().unwrap().enabled = vring_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_protocol_features(&mut self) -> VhostUserResult<VhostUserProtocolFeatures> {
|
||||||
|
Ok(self.backend.read().unwrap().protocol_features())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_protocol_features(&mut self, features: u64) -> VhostUserResult<()> {
|
||||||
|
// Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must
|
||||||
|
// support this message even before VHOST_USER_SET_FEATURES was
|
||||||
|
// called.
|
||||||
|
self.acked_protocol_features = features;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_mem_table(
|
||||||
|
&mut self,
|
||||||
|
ctx: &[VhostUserMemoryRegion],
|
||||||
|
fds: &[RawFd],
|
||||||
|
) -> VhostUserResult<()> {
|
||||||
|
// We need to create tuple of ranges from the list of VhostUserMemoryRegion
|
||||||
|
// that we get from the caller.
|
||||||
|
let mut regions: Vec<(GuestAddress, usize, Option<FileOffset>)> = Vec::new();
|
||||||
|
let mut mappings: Vec<AddrMapping> = Vec::new();
|
||||||
|
|
||||||
|
for (idx, region) in ctx.iter().enumerate() {
|
||||||
|
let g_addr = GuestAddress(region.guest_phys_addr);
|
||||||
|
let len = region.memory_size as usize;
|
||||||
|
let file = unsafe { File::from_raw_fd(fds[idx]) };
|
||||||
|
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,
|
||||||
|
gpa_base: region.guest_phys_addr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mem = GuestMemoryMmap::from_ranges_with_files(regions).map_err(|e| {
|
||||||
|
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||||
|
})?;
|
||||||
|
self.backend
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.update_memory(mem)
|
||||||
|
.map_err(|e| {
|
||||||
|
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||||
|
})?;
|
||||||
|
self.memory = Some(Memory { mappings });
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_queue_num(&mut self) -> VhostUserResult<u64> {
|
||||||
|
Ok(self.num_queues as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vring_num(&mut self, index: u32, num: u32) -> VhostUserResult<()> {
|
||||||
|
if index as usize >= self.num_queues || num == 0 || num as usize > self.max_queue_size {
|
||||||
|
return Err(VhostUserError::InvalidParam);
|
||||||
|
}
|
||||||
|
self.vrings[index as usize].write().unwrap().queue.size = num as u16;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vring_addr(
|
||||||
|
&mut self,
|
||||||
|
index: u32,
|
||||||
|
_flags: VhostUserVringAddrFlags,
|
||||||
|
descriptor: u64,
|
||||||
|
used: u64,
|
||||||
|
available: u64,
|
||||||
|
_log: u64,
|
||||||
|
) -> VhostUserResult<()> {
|
||||||
|
if index as usize >= self.num_queues {
|
||||||
|
return Err(VhostUserError::InvalidParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.memory.is_some() {
|
||||||
|
let desc_table = self.vmm_va_to_gpa(descriptor).map_err(|e| {
|
||||||
|
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||||
|
})?;
|
||||||
|
let avail_ring = self.vmm_va_to_gpa(available).map_err(|e| {
|
||||||
|
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||||
|
})?;
|
||||||
|
let used_ring = self.vmm_va_to_gpa(used).map_err(|e| {
|
||||||
|
VhostUserError::ReqHandlerError(io::Error::new(io::ErrorKind::Other, e))
|
||||||
|
})?;
|
||||||
|
self.vrings[index as usize]
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.queue
|
||||||
|
.desc_table = GuestAddress(desc_table);
|
||||||
|
self.vrings[index as usize]
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.queue
|
||||||
|
.avail_ring = GuestAddress(avail_ring);
|
||||||
|
self.vrings[index as usize].write().unwrap().queue.used_ring = GuestAddress(used_ring);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(VhostUserError::InvalidParam)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vring_base(&mut self, index: u32, base: u32) -> VhostUserResult<()> {
|
||||||
|
self.vrings[index as usize]
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.queue
|
||||||
|
.next_avail = Wrapping(base as u16);
|
||||||
|
self.vrings[index as usize].write().unwrap().queue.next_used = Wrapping(base as u16);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_vring_base(&mut self, index: u32) -> VhostUserResult<VhostUserVringState> {
|
||||||
|
if index as usize >= self.num_queues {
|
||||||
|
return Err(VhostUserError::InvalidParam);
|
||||||
|
}
|
||||||
|
// Quote from vhost-user specification:
|
||||||
|
// 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.vrings[index as usize].write().unwrap().queue.ready = false;
|
||||||
|
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||||
|
self.worker
|
||||||
|
.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
|
||||||
|
.map_err(VhostUserError::ReqHandlerError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_avail = self.vrings[index as usize]
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.queue
|
||||||
|
.next_avail
|
||||||
|
.0 as u16;
|
||||||
|
|
||||||
|
Ok(VhostUserVringState::new(index, u32::from(next_avail)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vring_kick(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||||
|
if index as usize >= self.num_queues {
|
||||||
|
return Err(VhostUserError::InvalidParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(kick) = self.vrings[index as usize].write().unwrap().kick.take() {
|
||||||
|
// Close file descriptor set by previous operations.
|
||||||
|
let _ = unsafe { libc::close(kick.as_raw_fd()) };
|
||||||
|
}
|
||||||
|
self.vrings[index as usize].write().unwrap().kick =
|
||||||
|
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
|
||||||
|
|
||||||
|
// Quote from vhost-user specification:
|
||||||
|
// 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.vrings[index as usize].write().unwrap().queue.ready = true;
|
||||||
|
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||||
|
self.worker
|
||||||
|
.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
|
||||||
|
.map_err(VhostUserError::ReqHandlerError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vring_call(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||||
|
if index as usize >= self.num_queues {
|
||||||
|
return Err(VhostUserError::InvalidParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(call) = self.vrings[index as usize].write().unwrap().call.take() {
|
||||||
|
// Close file descriptor set by previous operations.
|
||||||
|
let _ = unsafe { libc::close(call.as_raw_fd()) };
|
||||||
|
}
|
||||||
|
self.vrings[index as usize].write().unwrap().call =
|
||||||
|
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vring_err(&mut self, index: u8, fd: Option<RawFd>) -> VhostUserResult<()> {
|
||||||
|
if index as usize >= self.num_queues {
|
||||||
|
return Err(VhostUserError::InvalidParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(err) = self.vrings[index as usize].write().unwrap().err.take() {
|
||||||
|
// Close file descriptor set by previous operations.
|
||||||
|
let _ = unsafe { libc::close(err.as_raw_fd()) };
|
||||||
|
}
|
||||||
|
self.vrings[index as usize].write().unwrap().err =
|
||||||
|
fd.map(|x| unsafe { EventFd::from_raw_fd(x) });
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vring_enable(&mut self, index: u32, enable: bool) -> VhostUserResult<()> {
|
||||||
|
// 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(VhostUserError::InvalidOperation);
|
||||||
|
} else if index as usize >= self.num_queues {
|
||||||
|
return Err(VhostUserError::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.vrings[index as usize].write().unwrap().enabled = enable;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config(
|
||||||
|
&mut self,
|
||||||
|
offset: u32,
|
||||||
|
size: u32,
|
||||||
|
_flags: VhostUserConfigFlags,
|
||||||
|
) -> VhostUserResult<Vec<u8>> {
|
||||||
|
Ok(self.backend.read().unwrap().get_config(offset, size))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_config(
|
||||||
|
&mut self,
|
||||||
|
offset: u32,
|
||||||
|
buf: &[u8],
|
||||||
|
_flags: VhostUserConfigFlags,
|
||||||
|
) -> VhostUserResult<()> {
|
||||||
|
self.backend
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.set_config(offset, buf)
|
||||||
|
.map_err(VhostUserError::ReqHandlerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
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.2.1"
|
||||||
|
epoll = "4.1.0"
|
||||||
|
libc = "0.2.66"
|
||||||
|
log = "0.4.8"
|
||||||
|
qcow = { path = "../qcow" }
|
||||||
|
vhost_user_backend = { path = "../vhost_user_backend" }
|
||||||
|
vhost_rs = { path = "../vhost_rs" }
|
||||||
|
virtio-bindings = "0.1.0"
|
||||||
|
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||||
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
|
vmm-sys-util = "0.4.0"
|
||||||
336
vhost_user_block/src/lib.rs
Normal file
336
vhost_user_block/src/lib.rs
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
// 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 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::os::unix::fs::OpenOptionsExt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process;
|
||||||
|
use std::slice;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::vec::Vec;
|
||||||
|
use vhost_rs::vhost_user::message::*;
|
||||||
|
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
||||||
|
use virtio_bindings::bindings::virtio_blk::*;
|
||||||
|
use vm_memory::{Bytes, GuestMemoryError, GuestMemoryMmap};
|
||||||
|
use vm_virtio::block::{build_disk_image_id, Request};
|
||||||
|
|
||||||
|
const QUEUE_SIZE: usize = 1024;
|
||||||
|
const SECTOR_SHIFT: u8 = 9;
|
||||||
|
const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT;
|
||||||
|
const BLK_SIZE: u32 = 512;
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VhostUserBlkBackend {
|
||||||
|
pub fn new(image_path: String, num_queues: usize, rdonly: bool, direct: 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vring.mut_queue().add_used(mem, head.index, len);
|
||||||
|
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_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 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 {
|
||||||
|
warn!("invalid events operation");
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("event received: {:?}", device_event);
|
||||||
|
|
||||||
|
let mut vring = vrings[device_event as usize].write().unwrap();
|
||||||
|
if self.process_queue(&mut vring) {
|
||||||
|
debug!("signalling queue");
|
||||||
|
vring.signal_used_queue().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VhostUserBlkBackendConfig<'a> {
|
||||||
|
pub image: &'a str,
|
||||||
|
pub sock: &'a str,
|
||||||
|
pub num_queues: usize,
|
||||||
|
pub readonly: bool,
|
||||||
|
pub direct: 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;
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
.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() {
|
||||||
|
println!(
|
||||||
|
"failed to start daemon for vhost-user-blk with error: {:?}\n",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
blk_daemon.wait().unwrap();
|
||||||
|
}
|
||||||
12
vhost_user_fs/Cargo.toml
Normal file
12
vhost_user_fs/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "vhost_user_fs"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bitflags = "1.2.1"
|
||||||
|
libc = "0.2.66"
|
||||||
|
log = "0.4.8"
|
||||||
|
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||||
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
985
vhost_user_fs/src/descriptor_utils.rs
Normal file
985
vhost_user_fs/src/descriptor_utils.rs
Normal file
@@ -0,0 +1,985 @@
|
|||||||
|
// Copyright 2019 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.
|
||||||
|
|
||||||
|
use std::cmp;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::mem::{size_of, MaybeUninit};
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::ptr::copy_nonoverlapping;
|
||||||
|
use std::result;
|
||||||
|
|
||||||
|
use vm_memory::{
|
||||||
|
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap,
|
||||||
|
GuestMemoryRegion, Le16, Le32, Le64, VolatileMemory, VolatileMemoryError, VolatileSlice,
|
||||||
|
};
|
||||||
|
use vm_virtio::DescriptorChain;
|
||||||
|
|
||||||
|
use crate::file_traits::{FileReadWriteAtVolatile, FileReadWriteVolatile};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
DescriptorChainOverflow,
|
||||||
|
FindMemoryRegion,
|
||||||
|
GuestMemoryError(GuestMemoryError),
|
||||||
|
InvalidChain,
|
||||||
|
IoError(io::Error),
|
||||||
|
SplitOutOfBounds(usize),
|
||||||
|
VolatileMemoryError(VolatileMemoryError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::Error::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
DescriptorChainOverflow => write!(
|
||||||
|
f,
|
||||||
|
"the combined length of all the buffers in a `DescriptorChain` would overflow"
|
||||||
|
),
|
||||||
|
FindMemoryRegion => write!(f, "no memory region for this address range"),
|
||||||
|
GuestMemoryError(e) => write!(f, "descriptor guest memory error: {}", e),
|
||||||
|
InvalidChain => write!(f, "invalid descriptor chain"),
|
||||||
|
IoError(e) => write!(f, "descriptor I/O error: {}", e),
|
||||||
|
SplitOutOfBounds(off) => write!(f, "`DescriptorChain` split is out of bounds: {}", off),
|
||||||
|
VolatileMemoryError(e) => write!(f, "volatile memory error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct DescriptorChainConsumer<'a> {
|
||||||
|
buffers: VecDeque<VolatileSlice<'a>>,
|
||||||
|
bytes_consumed: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DescriptorChainConsumer<'a> {
|
||||||
|
fn available_bytes(&self) -> usize {
|
||||||
|
// This is guaranteed not to overflow because the total length of the chain
|
||||||
|
// is checked during all creations of `DescriptorChainConsumer` (see
|
||||||
|
// `Reader::new()` and `Writer::new()`).
|
||||||
|
self.buffers
|
||||||
|
.iter()
|
||||||
|
.fold(0usize, |count, vs| count + vs.len() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bytes_consumed(&self) -> usize {
|
||||||
|
self.bytes_consumed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes at most `count` bytes from the `DescriptorChain`. Callers must provide a function
|
||||||
|
/// that takes a `&[VolatileSlice]` and returns the total number of bytes consumed. This
|
||||||
|
/// function guarantees that the combined length of all the slices in the `&[VolatileSlice]` is
|
||||||
|
/// less than or equal to `count`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If the provided function returns any error then no bytes are consumed from the buffer and
|
||||||
|
/// the error is returned to the caller.
|
||||||
|
fn consume<F>(&mut self, count: usize, f: F) -> io::Result<usize>
|
||||||
|
where
|
||||||
|
F: FnOnce(&[VolatileSlice]) -> io::Result<usize>,
|
||||||
|
{
|
||||||
|
let mut buflen = 0;
|
||||||
|
let mut bufs = Vec::with_capacity(self.buffers.len());
|
||||||
|
for &vs in &self.buffers {
|
||||||
|
if buflen >= count {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufs.push(vs);
|
||||||
|
|
||||||
|
let rem = count - buflen;
|
||||||
|
if rem < vs.len() {
|
||||||
|
buflen += rem;
|
||||||
|
} else {
|
||||||
|
buflen += vs.len() as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bufs.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes_consumed = f(&*bufs)?;
|
||||||
|
|
||||||
|
// This can happen if a driver tricks a device into reading/writing more data than
|
||||||
|
// fits in a `usize`.
|
||||||
|
let total_bytes_consumed =
|
||||||
|
self.bytes_consumed
|
||||||
|
.checked_add(bytes_consumed)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
io::Error::new(io::ErrorKind::InvalidData, Error::DescriptorChainOverflow)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut rem = bytes_consumed;
|
||||||
|
while let Some(vs) = self.buffers.pop_front() {
|
||||||
|
if rem < vs.len() {
|
||||||
|
// Split the slice and push the remainder back into the buffer list. Safe because we
|
||||||
|
// know that `rem` is not out of bounds due to the check and we checked the bounds
|
||||||
|
// on `vs` when we added it to the buffer list.
|
||||||
|
self.buffers.push_front(vs.offset(rem).unwrap());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No need for checked math because we know that `vs.size() <= rem`.
|
||||||
|
rem -= vs.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bytes_consumed = total_bytes_consumed;
|
||||||
|
|
||||||
|
Ok(bytes_consumed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_at(&mut self, offset: usize) -> Result<DescriptorChainConsumer<'a>> {
|
||||||
|
let mut rem = offset;
|
||||||
|
let pos = self.buffers.iter().position(|vs| {
|
||||||
|
if rem < vs.len() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
rem -= vs.len();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(at) = pos {
|
||||||
|
let mut other = self.buffers.split_off(at);
|
||||||
|
|
||||||
|
if rem > 0 {
|
||||||
|
// There must be at least one element in `other` because we checked
|
||||||
|
// its `size` value in the call to `position` above.
|
||||||
|
let front = other.pop_front().expect("empty VecDeque after split");
|
||||||
|
self.buffers
|
||||||
|
.push_back(front.offset(rem).map_err(Error::VolatileMemoryError)?);
|
||||||
|
other.push_front(front.offset(rem).map_err(Error::VolatileMemoryError)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DescriptorChainConsumer {
|
||||||
|
buffers: other,
|
||||||
|
bytes_consumed: 0,
|
||||||
|
})
|
||||||
|
} else if rem == 0 {
|
||||||
|
Ok(DescriptorChainConsumer {
|
||||||
|
buffers: VecDeque::new(),
|
||||||
|
bytes_consumed: 0,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::SplitOutOfBounds(offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides high-level interface over the sequence of memory regions
|
||||||
|
/// defined by readable descriptors in the descriptor chain.
|
||||||
|
///
|
||||||
|
/// Note that virtio spec requires driver to place any device-writable
|
||||||
|
/// descriptors after any device-readable descriptors (2.6.4.2 in Virtio Spec v1.1).
|
||||||
|
/// Reader will skip iterating over descriptor chain when first writable
|
||||||
|
/// descriptor is encountered.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Reader<'a> {
|
||||||
|
buffer: DescriptorChainConsumer<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Reader<'a> {
|
||||||
|
/// Construct a new Reader wrapper over `desc_chain`.
|
||||||
|
pub fn new(mem: &'a GuestMemoryMmap, desc_chain: DescriptorChain<'a>) -> Result<Reader<'a>> {
|
||||||
|
let mut total_len: usize = 0;
|
||||||
|
let buffers = desc_chain
|
||||||
|
.into_iter()
|
||||||
|
.readable()
|
||||||
|
.map(|desc| {
|
||||||
|
// Verify that summing the descriptor sizes does not overflow.
|
||||||
|
// This can happen if a driver tricks a device into reading more data than
|
||||||
|
// fits in a `usize`.
|
||||||
|
total_len = total_len
|
||||||
|
.checked_add(desc.len as usize)
|
||||||
|
.ok_or(Error::DescriptorChainOverflow)?;
|
||||||
|
|
||||||
|
let region = mem.find_region(desc.addr).ok_or(Error::FindMemoryRegion)?;
|
||||||
|
let offset = desc
|
||||||
|
.addr
|
||||||
|
.checked_sub(region.start_addr().raw_value())
|
||||||
|
.unwrap();
|
||||||
|
region
|
||||||
|
.deref()
|
||||||
|
.get_slice(offset.raw_value() as usize, desc.len as usize)
|
||||||
|
.map_err(Error::VolatileMemoryError)
|
||||||
|
})
|
||||||
|
.collect::<Result<VecDeque<VolatileSlice<'a>>>>()?;
|
||||||
|
Ok(Reader {
|
||||||
|
buffer: DescriptorChainConsumer {
|
||||||
|
buffers,
|
||||||
|
bytes_consumed: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads an object from the descriptor chain buffer.
|
||||||
|
pub fn read_obj<T: ByteValued>(&mut self) -> io::Result<T> {
|
||||||
|
let mut obj = MaybeUninit::<T>::uninit();
|
||||||
|
|
||||||
|
// Safe because `MaybeUninit` guarantees that the pointer is valid for
|
||||||
|
// `size_of::<T>()` bytes.
|
||||||
|
let buf = unsafe {
|
||||||
|
::std::slice::from_raw_parts_mut(obj.as_mut_ptr() as *mut u8, size_of::<T>())
|
||||||
|
};
|
||||||
|
|
||||||
|
self.read_exact(buf)?;
|
||||||
|
|
||||||
|
// Safe because any type that implements `ByteValued` can be considered initialized
|
||||||
|
// even if it is filled with random data.
|
||||||
|
Ok(unsafe { obj.assume_init() })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads data from the descriptor chain buffer into a file descriptor.
|
||||||
|
/// Returns the number of bytes read from the descriptor chain buffer.
|
||||||
|
/// The number of bytes read can be less than `count` if there isn't
|
||||||
|
/// enough data in the descriptor chain buffer.
|
||||||
|
pub fn read_to<F: FileReadWriteVolatile>(
|
||||||
|
&mut self,
|
||||||
|
mut dst: F,
|
||||||
|
count: usize,
|
||||||
|
) -> io::Result<usize> {
|
||||||
|
self.buffer
|
||||||
|
.consume(count, |bufs| dst.write_vectored_volatile(bufs))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads data from the descriptor chain buffer into a File at offset `off`.
|
||||||
|
/// Returns the number of bytes read from the descriptor chain buffer.
|
||||||
|
/// The number of bytes read can be less than `count` if there isn't
|
||||||
|
/// enough data in the descriptor chain buffer.
|
||||||
|
pub fn read_to_at<F: FileReadWriteAtVolatile>(
|
||||||
|
&mut self,
|
||||||
|
mut dst: F,
|
||||||
|
count: usize,
|
||||||
|
off: u64,
|
||||||
|
) -> io::Result<usize> {
|
||||||
|
self.buffer
|
||||||
|
.consume(count, |bufs| dst.write_vectored_at_volatile(bufs, off))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_exact_to<F: FileReadWriteVolatile>(
|
||||||
|
&mut self,
|
||||||
|
mut dst: F,
|
||||||
|
mut count: usize,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
while count > 0 {
|
||||||
|
match self.read_to(&mut dst, count) {
|
||||||
|
Ok(0) => {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
"failed to fill whole buffer",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Ok(n) => count -= n,
|
||||||
|
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of bytes available for reading. May return an error if the combined
|
||||||
|
/// lengths of all the buffers in the DescriptorChain would cause an integer overflow.
|
||||||
|
pub fn available_bytes(&self) -> usize {
|
||||||
|
self.buffer.available_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of bytes already read from the descriptor chain buffer.
|
||||||
|
pub fn bytes_read(&self) -> usize {
|
||||||
|
self.buffer.bytes_consumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Splits this `Reader` into two at the given offset in the `DescriptorChain` buffer.
|
||||||
|
/// After the split, `self` will be able to read up to `offset` bytes while the returned
|
||||||
|
/// `Reader` can read up to `available_bytes() - offset` bytes. Returns an error if
|
||||||
|
/// `offset > self.available_bytes()`.
|
||||||
|
pub fn split_at(&mut self, offset: usize) -> Result<Reader<'a>> {
|
||||||
|
self.buffer.split_at(offset).map(|buffer| Reader { buffer })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> io::Read for Reader<'a> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.buffer.consume(buf.len(), |bufs| {
|
||||||
|
let mut rem = buf;
|
||||||
|
let mut total = 0;
|
||||||
|
for vs in bufs {
|
||||||
|
let copy_len = cmp::min(rem.len(), vs.len());
|
||||||
|
|
||||||
|
// Safe because we have already verified that `vs` points to valid memory.
|
||||||
|
unsafe {
|
||||||
|
copy_nonoverlapping(vs.as_ptr() as *const u8, rem.as_mut_ptr(), copy_len);
|
||||||
|
}
|
||||||
|
rem = &mut rem[copy_len..];
|
||||||
|
total += copy_len;
|
||||||
|
}
|
||||||
|
Ok(total)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides high-level interface over the sequence of memory regions
|
||||||
|
/// defined by writable descriptors in the descriptor chain.
|
||||||
|
///
|
||||||
|
/// Note that virtio spec requires driver to place any device-writable
|
||||||
|
/// descriptors after any device-readable descriptors (2.6.4.2 in Virtio Spec v1.1).
|
||||||
|
/// Writer will start iterating the descriptors from the first writable one and will
|
||||||
|
/// assume that all following descriptors are writable.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Writer<'a> {
|
||||||
|
buffer: DescriptorChainConsumer<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Writer<'a> {
|
||||||
|
/// Construct a new Writer wrapper over `desc_chain`.
|
||||||
|
pub fn new(mem: &'a GuestMemoryMmap, desc_chain: DescriptorChain<'a>) -> Result<Writer<'a>> {
|
||||||
|
let mut total_len: usize = 0;
|
||||||
|
let buffers = desc_chain
|
||||||
|
.into_iter()
|
||||||
|
.writable()
|
||||||
|
.map(|desc| {
|
||||||
|
// Verify that summing the descriptor sizes does not overflow.
|
||||||
|
// This can happen if a driver tricks a device into writing more data than
|
||||||
|
// fits in a `usize`.
|
||||||
|
total_len = total_len
|
||||||
|
.checked_add(desc.len as usize)
|
||||||
|
.ok_or(Error::DescriptorChainOverflow)?;
|
||||||
|
|
||||||
|
let region = mem.find_region(desc.addr).ok_or(Error::FindMemoryRegion)?;
|
||||||
|
let offset = desc
|
||||||
|
.addr
|
||||||
|
.checked_sub(region.start_addr().raw_value())
|
||||||
|
.unwrap();
|
||||||
|
region
|
||||||
|
.deref()
|
||||||
|
.get_slice(offset.raw_value() as usize, desc.len as usize)
|
||||||
|
.map_err(Error::VolatileMemoryError)
|
||||||
|
})
|
||||||
|
.collect::<Result<VecDeque<VolatileSlice<'a>>>>()?;
|
||||||
|
|
||||||
|
Ok(Writer {
|
||||||
|
buffer: DescriptorChainConsumer {
|
||||||
|
buffers,
|
||||||
|
bytes_consumed: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes an object to the descriptor chain buffer.
|
||||||
|
pub fn write_obj<T: ByteValued>(&mut self, val: T) -> io::Result<()> {
|
||||||
|
self.write_all(val.as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of bytes available for writing. May return an error if the combined
|
||||||
|
/// lengths of all the buffers in the DescriptorChain would cause an overflow.
|
||||||
|
pub fn available_bytes(&self) -> usize {
|
||||||
|
self.buffer.available_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes data to the descriptor chain buffer from a file descriptor.
|
||||||
|
/// Returns the number of bytes written to the descriptor chain buffer.
|
||||||
|
/// The number of bytes written can be less than `count` if
|
||||||
|
/// there isn't enough data in the descriptor chain buffer.
|
||||||
|
pub fn write_from<F: FileReadWriteVolatile>(
|
||||||
|
&mut self,
|
||||||
|
mut src: F,
|
||||||
|
count: usize,
|
||||||
|
) -> io::Result<usize> {
|
||||||
|
self.buffer
|
||||||
|
.consume(count, |bufs| src.read_vectored_volatile(bufs))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes data to the descriptor chain buffer from a File at offset `off`.
|
||||||
|
/// Returns the number of bytes written to the descriptor chain buffer.
|
||||||
|
/// The number of bytes written can be less than `count` if
|
||||||
|
/// there isn't enough data in the descriptor chain buffer.
|
||||||
|
pub fn write_from_at<F: FileReadWriteAtVolatile>(
|
||||||
|
&mut self,
|
||||||
|
mut src: F,
|
||||||
|
count: usize,
|
||||||
|
off: u64,
|
||||||
|
) -> io::Result<usize> {
|
||||||
|
self.buffer
|
||||||
|
.consume(count, |bufs| src.read_vectored_at_volatile(bufs, off))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_all_from<F: FileReadWriteVolatile>(
|
||||||
|
&mut self,
|
||||||
|
mut src: F,
|
||||||
|
mut count: usize,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
while count > 0 {
|
||||||
|
match self.write_from(&mut src, count) {
|
||||||
|
Ok(0) => {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::WriteZero,
|
||||||
|
"failed to write whole buffer",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Ok(n) => count -= n,
|
||||||
|
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of bytes already written to the descriptor chain buffer.
|
||||||
|
pub fn bytes_written(&self) -> usize {
|
||||||
|
self.buffer.bytes_consumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Splits this `Writer` into two at the given offset in the `DescriptorChain` buffer.
|
||||||
|
/// After the split, `self` will be able to write up to `offset` bytes while the returned
|
||||||
|
/// `Writer` can write up to `available_bytes() - offset` bytes. Returns an error if
|
||||||
|
/// `offset > self.available_bytes()`.
|
||||||
|
pub fn split_at(&mut self, offset: usize) -> Result<Writer<'a>> {
|
||||||
|
self.buffer.split_at(offset).map(|buffer| Writer { buffer })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> io::Write for Writer<'a> {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
self.buffer.consume(buf.len(), |bufs| {
|
||||||
|
let mut rem = buf;
|
||||||
|
let mut total = 0;
|
||||||
|
for vs in bufs {
|
||||||
|
let copy_len = cmp::min(rem.len(), vs.len());
|
||||||
|
|
||||||
|
// Safe because we have already verified that `vs` points to valid memory.
|
||||||
|
unsafe {
|
||||||
|
copy_nonoverlapping(rem.as_ptr(), vs.as_ptr(), copy_len);
|
||||||
|
}
|
||||||
|
rem = &rem[copy_len..];
|
||||||
|
total += copy_len;
|
||||||
|
}
|
||||||
|
Ok(total)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
// Nothing to flush since the writes go straight into the buffer.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const VIRTQ_DESC_F_NEXT: u16 = 0x1;
|
||||||
|
const VIRTQ_DESC_F_WRITE: u16 = 0x2;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum DescriptorType {
|
||||||
|
Readable,
|
||||||
|
Writable,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
#[repr(C)]
|
||||||
|
struct virtq_desc {
|
||||||
|
addr: Le64,
|
||||||
|
len: Le32,
|
||||||
|
flags: Le16,
|
||||||
|
next: Le16,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe because it only has data and has no implicit padding.
|
||||||
|
unsafe impl ByteValued for virtq_desc {}
|
||||||
|
|
||||||
|
/// Test utility function to create a descriptor chain in guest memory.
|
||||||
|
pub fn create_descriptor_chain(
|
||||||
|
memory: &GuestMemoryMmap,
|
||||||
|
descriptor_array_addr: GuestAddress,
|
||||||
|
mut buffers_start_addr: GuestAddress,
|
||||||
|
descriptors: Vec<(DescriptorType, u32)>,
|
||||||
|
spaces_between_regions: u32,
|
||||||
|
) -> Result<DescriptorChain> {
|
||||||
|
let descriptors_len = descriptors.len();
|
||||||
|
for (index, (type_, size)) in descriptors.into_iter().enumerate() {
|
||||||
|
let mut flags = 0;
|
||||||
|
if let DescriptorType::Writable = type_ {
|
||||||
|
flags |= VIRTQ_DESC_F_WRITE;
|
||||||
|
}
|
||||||
|
if index + 1 < descriptors_len {
|
||||||
|
flags |= VIRTQ_DESC_F_NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = index as u16;
|
||||||
|
let desc = virtq_desc {
|
||||||
|
addr: buffers_start_addr.raw_value().into(),
|
||||||
|
len: size.into(),
|
||||||
|
flags: flags.into(),
|
||||||
|
next: (index + 1).into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let offset = size + spaces_between_regions;
|
||||||
|
buffers_start_addr = buffers_start_addr
|
||||||
|
.checked_add(u64::from(offset))
|
||||||
|
.ok_or(Error::InvalidChain)?;
|
||||||
|
|
||||||
|
let _ = memory.write_obj(
|
||||||
|
desc,
|
||||||
|
descriptor_array_addr
|
||||||
|
.checked_add(u64::from(index) * std::mem::size_of::<virtq_desc>() as u64)
|
||||||
|
.ok_or(Error::InvalidChain)?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorChain::checked_new(memory, descriptor_array_addr, 0x100, 0, None)
|
||||||
|
.ok_or(Error::InvalidChain)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reader_test_simple_chain() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![
|
||||||
|
(Readable, 8),
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 18),
|
||||||
|
(Readable, 64),
|
||||||
|
],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
||||||
|
assert_eq!(reader.available_bytes(), 106);
|
||||||
|
assert_eq!(reader.bytes_read(), 0);
|
||||||
|
|
||||||
|
let mut buffer = [0 as u8; 64];
|
||||||
|
if let Err(_) = reader.read_exact(&mut buffer) {
|
||||||
|
panic!("read_exact should not fail here");
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(reader.available_bytes(), 42);
|
||||||
|
assert_eq!(reader.bytes_read(), 64);
|
||||||
|
|
||||||
|
match reader.read(&mut buffer) {
|
||||||
|
Err(_) => panic!("read should not fail here"),
|
||||||
|
Ok(length) => assert_eq!(length, 42),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(reader.available_bytes(), 0);
|
||||||
|
assert_eq!(reader.bytes_read(), 106);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn writer_test_simple_chain() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![
|
||||||
|
(Writable, 8),
|
||||||
|
(Writable, 16),
|
||||||
|
(Writable, 18),
|
||||||
|
(Writable, 64),
|
||||||
|
],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut writer = Writer::new(&memory, chain).expect("failed to create Writer");
|
||||||
|
assert_eq!(writer.available_bytes(), 106);
|
||||||
|
assert_eq!(writer.bytes_written(), 0);
|
||||||
|
|
||||||
|
let mut buffer = [0 as u8; 64];
|
||||||
|
if let Err(_) = writer.write_all(&mut buffer) {
|
||||||
|
panic!("write_all should not fail here");
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(writer.available_bytes(), 42);
|
||||||
|
assert_eq!(writer.bytes_written(), 64);
|
||||||
|
|
||||||
|
match writer.write(&mut buffer) {
|
||||||
|
Err(_) => panic!("write should not fail here"),
|
||||||
|
Ok(length) => assert_eq!(length, 42),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(writer.available_bytes(), 0);
|
||||||
|
assert_eq!(writer.bytes_written(), 106);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reader_test_incompatible_chain() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![(Writable, 8)],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
||||||
|
assert_eq!(reader.available_bytes(), 0);
|
||||||
|
assert_eq!(reader.bytes_read(), 0);
|
||||||
|
|
||||||
|
assert!(reader.read_obj::<u8>().is_err());
|
||||||
|
|
||||||
|
assert_eq!(reader.available_bytes(), 0);
|
||||||
|
assert_eq!(reader.bytes_read(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn writer_test_incompatible_chain() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![(Readable, 8)],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut writer = Writer::new(&memory, chain).expect("failed to create Writer");
|
||||||
|
assert_eq!(writer.available_bytes(), 0);
|
||||||
|
assert_eq!(writer.bytes_written(), 0);
|
||||||
|
|
||||||
|
assert!(writer.write_obj(0u8).is_err());
|
||||||
|
|
||||||
|
assert_eq!(writer.available_bytes(), 0);
|
||||||
|
assert_eq!(writer.bytes_written(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reader_writer_shared_chain() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 96),
|
||||||
|
(Writable, 64),
|
||||||
|
(Writable, 1),
|
||||||
|
(Writable, 3),
|
||||||
|
],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut reader = Reader::new(&memory, chain.clone()).expect("failed to create Reader");
|
||||||
|
let mut writer = Writer::new(&memory, chain).expect("failed to create Writer");
|
||||||
|
|
||||||
|
assert_eq!(reader.bytes_read(), 0);
|
||||||
|
assert_eq!(writer.bytes_written(), 0);
|
||||||
|
|
||||||
|
let mut buffer = Vec::with_capacity(200);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
reader
|
||||||
|
.read_to_end(&mut buffer)
|
||||||
|
.expect("read should not fail here"),
|
||||||
|
128
|
||||||
|
);
|
||||||
|
|
||||||
|
// The writable descriptors are only 68 bytes long.
|
||||||
|
writer
|
||||||
|
.write_all(&buffer[..68])
|
||||||
|
.expect("write should not fail here");
|
||||||
|
|
||||||
|
assert_eq!(reader.available_bytes(), 0);
|
||||||
|
assert_eq!(reader.bytes_read(), 128);
|
||||||
|
assert_eq!(writer.available_bytes(), 0);
|
||||||
|
assert_eq!(writer.bytes_written(), 68);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reader_writer_shattered_object() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let secret: Le32 = 0x12345678.into();
|
||||||
|
|
||||||
|
// Create a descriptor chain with memory regions that are properly separated.
|
||||||
|
let chain_writer = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![(Writable, 1), (Writable, 1), (Writable, 1), (Writable, 1)],
|
||||||
|
123,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut writer = Writer::new(&memory, chain_writer).expect("failed to create Writer");
|
||||||
|
if let Err(_) = writer.write_obj(secret) {
|
||||||
|
panic!("write_obj should not fail here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now create new descriptor chain pointing to the same memory and try to read it.
|
||||||
|
let chain_reader = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![(Readable, 1), (Readable, 1), (Readable, 1), (Readable, 1)],
|
||||||
|
123,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut reader = Reader::new(&memory, chain_reader).expect("failed to create Reader");
|
||||||
|
match reader.read_obj::<Le32>() {
|
||||||
|
Err(_) => panic!("read_obj should not fail here"),
|
||||||
|
Ok(read_secret) => assert_eq!(read_secret, secret),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reader_unexpected_eof() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![(Readable, 256), (Readable, 256)],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
|
||||||
|
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
||||||
|
|
||||||
|
let mut buf = Vec::with_capacity(1024);
|
||||||
|
buf.resize(1024, 0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
reader
|
||||||
|
.read_exact(&mut buf[..])
|
||||||
|
.expect_err("read more bytes than available")
|
||||||
|
.kind(),
|
||||||
|
io::ErrorKind::UnexpectedEof
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_border() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 96),
|
||||||
|
(Writable, 64),
|
||||||
|
(Writable, 1),
|
||||||
|
(Writable, 3),
|
||||||
|
],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
||||||
|
|
||||||
|
let other = reader.split_at(32).expect("failed to split Reader");
|
||||||
|
assert_eq!(reader.available_bytes(), 32);
|
||||||
|
assert_eq!(other.available_bytes(), 96);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_middle() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 96),
|
||||||
|
(Writable, 64),
|
||||||
|
(Writable, 1),
|
||||||
|
(Writable, 3),
|
||||||
|
],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
||||||
|
|
||||||
|
let other = reader.split_at(24).expect("failed to split Reader");
|
||||||
|
assert_eq!(reader.available_bytes(), 24);
|
||||||
|
assert_eq!(other.available_bytes(), 104);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_end() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 96),
|
||||||
|
(Writable, 64),
|
||||||
|
(Writable, 1),
|
||||||
|
(Writable, 3),
|
||||||
|
],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
||||||
|
|
||||||
|
let other = reader.split_at(128).expect("failed to split Reader");
|
||||||
|
assert_eq!(reader.available_bytes(), 128);
|
||||||
|
assert_eq!(other.available_bytes(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_beginning() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 96),
|
||||||
|
(Writable, 64),
|
||||||
|
(Writable, 1),
|
||||||
|
(Writable, 3),
|
||||||
|
],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
||||||
|
|
||||||
|
let other = reader.split_at(0).expect("failed to split Reader");
|
||||||
|
assert_eq!(reader.available_bytes(), 0);
|
||||||
|
assert_eq!(other.available_bytes(), 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_outofbounds() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 16),
|
||||||
|
(Readable, 96),
|
||||||
|
(Writable, 64),
|
||||||
|
(Writable, 1),
|
||||||
|
(Writable, 3),
|
||||||
|
],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
||||||
|
|
||||||
|
if let Ok(_) = reader.split_at(256) {
|
||||||
|
panic!("successfully split Reader with out of bounds offset");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_full() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![(Readable, 16), (Readable, 16), (Readable, 16)],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
||||||
|
|
||||||
|
let mut buf = vec![0u8; 64];
|
||||||
|
assert_eq!(
|
||||||
|
reader.read(&mut buf[..]).expect("failed to read to buffer"),
|
||||||
|
48
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_full() {
|
||||||
|
use DescriptorType::*;
|
||||||
|
|
||||||
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
|
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
|
let chain = create_descriptor_chain(
|
||||||
|
&memory,
|
||||||
|
GuestAddress(0x0),
|
||||||
|
GuestAddress(0x100),
|
||||||
|
vec![(Writable, 16), (Writable, 16), (Writable, 16)],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("create_descriptor_chain failed");
|
||||||
|
let mut writer = Writer::new(&memory, chain).expect("failed to create Writer");
|
||||||
|
|
||||||
|
let buf = vec![0xdeu8; 64];
|
||||||
|
assert_eq!(
|
||||||
|
writer.write(&buf[..]).expect("failed to write from buffer"),
|
||||||
|
48
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
409
vhost_user_fs/src/file_traits.rs
Normal file
409
vhost_user_fs/src/file_traits.rs
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Error, ErrorKind, Result};
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|
||||||
|
use vm_memory::VolatileSlice;
|
||||||
|
|
||||||
|
use libc::{
|
||||||
|
c_int, c_void, off64_t, pread64, preadv64, pwrite64, pwritev64, read, readv, size_t, write,
|
||||||
|
writev,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A trait for setting the size of a file.
|
||||||
|
/// This is equivalent to File's `set_len` method, but
|
||||||
|
/// wrapped in a trait so that it can be implemented for
|
||||||
|
/// other types.
|
||||||
|
pub trait FileSetLen {
|
||||||
|
// Set the size of this file.
|
||||||
|
// This is the moral equivalent of `ftruncate()`.
|
||||||
|
fn set_len(&self, _len: u64) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSetLen for File {
|
||||||
|
fn set_len(&self, len: u64) -> Result<()> {
|
||||||
|
File::set_len(self, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait similar to `Read` and `Write`, but uses volatile memory as buffers.
|
||||||
|
pub trait FileReadWriteVolatile {
|
||||||
|
/// Read bytes from this file into the given slice, returning the number of bytes read on
|
||||||
|
/// success.
|
||||||
|
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
|
||||||
|
|
||||||
|
/// Like `read_volatile`, except it reads to a slice of buffers. Data is copied to fill each
|
||||||
|
/// buffer in order, with the final buffer written to possibly being only partially filled. This
|
||||||
|
/// method must behave as a single call to `read_volatile` with the buffers concatenated would.
|
||||||
|
/// The default implementation calls `read_volatile` with either the first nonempty buffer
|
||||||
|
/// provided, or returns `Ok(0)` if none exists.
|
||||||
|
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||||
|
bufs.iter()
|
||||||
|
.find(|b| !b.is_empty())
|
||||||
|
.map(|&b| self.read_volatile(b))
|
||||||
|
.unwrap_or(Ok(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads bytes from this into the given slice until all bytes in the slice are written, or an
|
||||||
|
/// error is returned.
|
||||||
|
fn read_exact_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
|
||||||
|
while !slice.is_empty() {
|
||||||
|
let bytes_read = self.read_volatile(slice)?;
|
||||||
|
if bytes_read == 0 {
|
||||||
|
return Err(Error::from(ErrorKind::UnexpectedEof));
|
||||||
|
}
|
||||||
|
// Will panic if read_volatile read more bytes than we gave it, which would be worthy of
|
||||||
|
// a panic.
|
||||||
|
slice = slice.offset(bytes_read).unwrap();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write bytes from the slice to the given file, returning the number of bytes written on
|
||||||
|
/// success.
|
||||||
|
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
|
||||||
|
|
||||||
|
/// Like `write_volatile`, except that it writes from a slice of buffers. Data is copied from
|
||||||
|
/// each buffer in order, with the final buffer read from possibly being only partially
|
||||||
|
/// consumed. This method must behave as a call to `write_volatile` with the buffers
|
||||||
|
/// concatenated would. The default implementation calls `write_volatile` with either the first
|
||||||
|
/// nonempty buffer provided, or returns `Ok(0)` if none exists.
|
||||||
|
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||||
|
bufs.iter()
|
||||||
|
.find(|b| !b.is_empty())
|
||||||
|
.map(|&b| self.write_volatile(b))
|
||||||
|
.unwrap_or(Ok(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write bytes from the slice to the given file until all the bytes from the slice have been
|
||||||
|
/// written, or an error is returned.
|
||||||
|
fn write_all_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
|
||||||
|
while !slice.is_empty() {
|
||||||
|
let bytes_written = self.write_volatile(slice)?;
|
||||||
|
if bytes_written == 0 {
|
||||||
|
return Err(Error::from(ErrorKind::WriteZero));
|
||||||
|
}
|
||||||
|
// Will panic if read_volatile read more bytes than we gave it, which would be worthy of
|
||||||
|
// a panic.
|
||||||
|
slice = slice.offset(bytes_written).unwrap();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: FileReadWriteVolatile + ?Sized> FileReadWriteVolatile for &'a mut T {
|
||||||
|
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
|
||||||
|
(**self).read_volatile(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||||
|
(**self).read_vectored_volatile(bufs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_exact_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
|
||||||
|
(**self).read_exact_volatile(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
|
||||||
|
(**self).write_volatile(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||||
|
(**self).write_vectored_volatile(bufs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_all_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
|
||||||
|
(**self).write_all_volatile(slice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait similar to the unix `ReadExt` and `WriteExt` traits, but for volatile memory.
|
||||||
|
pub trait FileReadWriteAtVolatile {
|
||||||
|
/// Reads bytes from this file at `offset` into the given slice, returning the number of bytes
|
||||||
|
/// read on success.
|
||||||
|
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
|
||||||
|
|
||||||
|
/// Like `read_at_volatile`, except it reads to a slice of buffers. Data is copied to fill each
|
||||||
|
/// buffer in order, with the final buffer written to possibly being only partially filled. This
|
||||||
|
/// method must behave as a single call to `read_at_volatile` with the buffers concatenated
|
||||||
|
/// would. The default implementation calls `read_at_volatile` with either the first nonempty
|
||||||
|
/// buffer provided, or returns `Ok(0)` if none exists.
|
||||||
|
fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
|
||||||
|
if let Some(&slice) = bufs.first() {
|
||||||
|
self.read_at_volatile(slice, offset)
|
||||||
|
} else {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads bytes from this file at `offset` into the given slice until all bytes in the slice are
|
||||||
|
/// read, or an error is returned.
|
||||||
|
fn read_exact_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
|
||||||
|
while !slice.is_empty() {
|
||||||
|
match self.read_at_volatile(slice, offset) {
|
||||||
|
Ok(0) => return Err(Error::from(ErrorKind::UnexpectedEof)),
|
||||||
|
Ok(n) => {
|
||||||
|
slice = slice.offset(n).unwrap();
|
||||||
|
offset = offset.checked_add(n as u64).unwrap();
|
||||||
|
}
|
||||||
|
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes bytes from this file at `offset` into the given slice, returning the number of bytes
|
||||||
|
/// written on success.
|
||||||
|
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
|
||||||
|
|
||||||
|
/// Like `write_at_at_volatile`, except that it writes from a slice of buffers. Data is copied
|
||||||
|
/// from each buffer in order, with the final buffer read from possibly being only partially
|
||||||
|
/// consumed. This method must behave as a call to `write_at_volatile` with the buffers
|
||||||
|
/// concatenated would. The default implementation calls `write_at_volatile` with either the
|
||||||
|
/// first nonempty buffer provided, or returns `Ok(0)` if none exists.
|
||||||
|
fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
|
||||||
|
if let Some(&slice) = bufs.first() {
|
||||||
|
self.write_at_volatile(slice, offset)
|
||||||
|
} else {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes bytes from this file at `offset` into the given slice until all bytes in the slice
|
||||||
|
/// are written, or an error is returned.
|
||||||
|
fn write_all_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
|
||||||
|
while !slice.is_empty() {
|
||||||
|
match self.write_at_volatile(slice, offset) {
|
||||||
|
Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
|
||||||
|
Ok(n) => {
|
||||||
|
slice = slice.offset(n).unwrap();
|
||||||
|
offset = offset.checked_add(n as u64).unwrap();
|
||||||
|
}
|
||||||
|
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: FileReadWriteAtVolatile + ?Sized> FileReadWriteAtVolatile for &'a mut T {
|
||||||
|
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
|
||||||
|
(**self).read_at_volatile(slice, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
|
||||||
|
(**self).read_vectored_at_volatile(bufs, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_exact_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
|
||||||
|
(**self).read_exact_at_volatile(slice, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
|
||||||
|
(**self).write_at_volatile(slice, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
|
||||||
|
(**self).write_vectored_at_volatile(bufs, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_all_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
|
||||||
|
(**self).write_all_at_volatile(slice, offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! volatile_impl {
|
||||||
|
($ty:ty) => {
|
||||||
|
impl FileReadWriteVolatile for $ty {
|
||||||
|
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
|
||||||
|
// Safe because only bytes inside the slice are accessed and the kernel is expected
|
||||||
|
// to handle arbitrary memory for I/O.
|
||||||
|
let ret =
|
||||||
|
unsafe { read(self.as_raw_fd(), slice.as_ptr() as *mut c_void, slice.len()) };
|
||||||
|
if ret >= 0 {
|
||||||
|
Ok(ret as usize)
|
||||||
|
} else {
|
||||||
|
Err(Error::last_os_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||||
|
let iovecs: Vec<libc::iovec> = bufs
|
||||||
|
.iter()
|
||||||
|
.map(|s| libc::iovec {
|
||||||
|
iov_base: s.as_ptr() as *mut c_void,
|
||||||
|
iov_len: s.len() as size_t,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if iovecs.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe because only bytes inside the buffers are accessed and the kernel is
|
||||||
|
// expected to handle arbitrary memory for I/O.
|
||||||
|
let ret = unsafe { readv(self.as_raw_fd(), &iovecs[0], iovecs.len() as c_int) };
|
||||||
|
if ret >= 0 {
|
||||||
|
Ok(ret as usize)
|
||||||
|
} else {
|
||||||
|
Err(Error::last_os_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
|
||||||
|
// Safe because only bytes inside the slice are accessed and the kernel is expected
|
||||||
|
// to handle arbitrary memory for I/O.
|
||||||
|
let ret = unsafe {
|
||||||
|
write(
|
||||||
|
self.as_raw_fd(),
|
||||||
|
slice.as_ptr() as *const c_void,
|
||||||
|
slice.len(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret >= 0 {
|
||||||
|
Ok(ret as usize)
|
||||||
|
} else {
|
||||||
|
Err(Error::last_os_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
|
||||||
|
let iovecs: Vec<libc::iovec> = bufs
|
||||||
|
.iter()
|
||||||
|
.map(|s| libc::iovec {
|
||||||
|
iov_base: s.as_ptr() as *mut c_void,
|
||||||
|
iov_len: s.len() as size_t,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if iovecs.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe because only bytes inside the buffers are accessed and the kernel is
|
||||||
|
// expected to handle arbitrary memory for I/O.
|
||||||
|
let ret = unsafe { writev(self.as_raw_fd(), &iovecs[0], iovecs.len() as c_int) };
|
||||||
|
if ret >= 0 {
|
||||||
|
Ok(ret as usize)
|
||||||
|
} else {
|
||||||
|
Err(Error::last_os_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileReadWriteAtVolatile for $ty {
|
||||||
|
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
|
||||||
|
// Safe because only bytes inside the slice are accessed and the kernel is expected
|
||||||
|
// to handle arbitrary memory for I/O.
|
||||||
|
let ret = unsafe {
|
||||||
|
pread64(
|
||||||
|
self.as_raw_fd(),
|
||||||
|
slice.as_ptr() as *mut c_void,
|
||||||
|
slice.len(),
|
||||||
|
offset as off64_t,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if ret >= 0 {
|
||||||
|
Ok(ret as usize)
|
||||||
|
} else {
|
||||||
|
Err(Error::last_os_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_vectored_at_volatile(
|
||||||
|
&mut self,
|
||||||
|
bufs: &[VolatileSlice],
|
||||||
|
offset: u64,
|
||||||
|
) -> Result<usize> {
|
||||||
|
let iovecs: Vec<libc::iovec> = bufs
|
||||||
|
.iter()
|
||||||
|
.map(|s| libc::iovec {
|
||||||
|
iov_base: s.as_ptr() as *mut c_void,
|
||||||
|
iov_len: s.len() as size_t,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if iovecs.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe because only bytes inside the buffers are accessed and the kernel is
|
||||||
|
// expected to handle arbitrary memory for I/O.
|
||||||
|
let ret = unsafe {
|
||||||
|
preadv64(
|
||||||
|
self.as_raw_fd(),
|
||||||
|
&iovecs[0],
|
||||||
|
iovecs.len() as c_int,
|
||||||
|
offset as off64_t,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret >= 0 {
|
||||||
|
Ok(ret as usize)
|
||||||
|
} else {
|
||||||
|
Err(Error::last_os_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
|
||||||
|
// Safe because only bytes inside the slice are accessed and the kernel is expected
|
||||||
|
// to handle arbitrary memory for I/O.
|
||||||
|
let ret = unsafe {
|
||||||
|
pwrite64(
|
||||||
|
self.as_raw_fd(),
|
||||||
|
slice.as_ptr() as *const c_void,
|
||||||
|
slice.len(),
|
||||||
|
offset as off64_t,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if ret >= 0 {
|
||||||
|
Ok(ret as usize)
|
||||||
|
} else {
|
||||||
|
Err(Error::last_os_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_vectored_at_volatile(
|
||||||
|
&mut self,
|
||||||
|
bufs: &[VolatileSlice],
|
||||||
|
offset: u64,
|
||||||
|
) -> Result<usize> {
|
||||||
|
let iovecs: Vec<libc::iovec> = bufs
|
||||||
|
.iter()
|
||||||
|
.map(|s| libc::iovec {
|
||||||
|
iov_base: s.as_ptr() as *mut c_void,
|
||||||
|
iov_len: s.len() as size_t,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if iovecs.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe because only bytes inside the buffers are accessed and the kernel is
|
||||||
|
// expected to handle arbitrary memory for I/O.
|
||||||
|
let ret = unsafe {
|
||||||
|
pwritev64(
|
||||||
|
self.as_raw_fd(),
|
||||||
|
&iovecs[0],
|
||||||
|
iovecs.len() as c_int,
|
||||||
|
offset as off64_t,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret >= 0 {
|
||||||
|
Ok(ret as usize)
|
||||||
|
} else {
|
||||||
|
Err(Error::last_os_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
volatile_impl!(File);
|
||||||
1099
vhost_user_fs/src/filesystem.rs
Normal file
1099
vhost_user_fs/src/filesystem.rs
Normal file
File diff suppressed because it is too large
Load Diff
1047
vhost_user_fs/src/fuse.rs
Normal file
1047
vhost_user_fs/src/fuse.rs
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user