mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
884 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52b839694d | ||
|
|
776f8fc555 | ||
|
|
3f18f93f40 | ||
|
|
97a1e5e1d2 | ||
|
|
5cd1730bc4 | ||
|
|
917219fa92 | ||
|
|
b5f1c912d6 | ||
|
|
eeeb45bbb9 | ||
|
|
e9488846f1 | ||
|
|
5343b0ac18 | ||
|
|
bf37ebdcb6 | ||
|
|
abd6204d27 | ||
|
|
02ac1820b1 | ||
|
|
cc85d896a5 | ||
|
|
2ae547cf71 | ||
|
|
f3556279d5 | ||
|
|
dc034eb3b0 | ||
|
|
176d671609 | ||
|
|
083189e5a1 | ||
|
|
2334b521da | ||
|
|
99c99c2476 | ||
|
|
96a5e22bd6 | ||
|
|
653087d7a3 | ||
|
|
5f0b620148 | ||
|
|
09fd325963 | ||
|
|
5f9e079a03 | ||
|
|
625bab69bd | ||
|
|
313883f6e4 | ||
|
|
afe60808ac | ||
|
|
aa79a92c35 | ||
|
|
9b71ba20ac | ||
|
|
1f8b6fa947 | ||
|
|
929d70bc7f | ||
|
|
eda9bfc7a1 | ||
|
|
a8cdf2f070 | ||
|
|
90e7accf8b | ||
|
|
e436bbf3bb | ||
|
|
2d13751d7d | ||
|
|
5a18dd36e2 | ||
|
|
d605fda3f7 | ||
|
|
ce624a6dee | ||
|
|
c7d44b880e | ||
|
|
7c91dfaeef | ||
|
|
17c16e5c82 | ||
|
|
a2398742da | ||
|
|
b31fe72edc | ||
|
|
9649700420 | ||
|
|
eabf43fbf6 | ||
|
|
7dc4e91303 | ||
|
|
601d898f8a | ||
|
|
6ff107afe4 | ||
|
|
3336e80192 | ||
|
|
d24aa72d3e | ||
|
|
53ce529875 | ||
|
|
8f7dc73562 | ||
|
|
969e5e0b51 | ||
|
|
20cf21cd9d | ||
|
|
61aa4615e2 | ||
|
|
a4d377a066 | ||
|
|
f06970730b | ||
|
|
a5596020b3 | ||
|
|
cc51fdb8a7 | ||
|
|
fcc62efc41 | ||
|
|
2dbd11864e | ||
|
|
237cb184b4 | ||
|
|
36d072e69c | ||
|
|
3151b5d82a | ||
|
|
22be88d3fb | ||
|
|
6121f4626b | ||
|
|
b731e63a8c | ||
|
|
d2d5ccb163 | ||
|
|
a1b9131bc6 | ||
|
|
2571b27966 | ||
|
|
57f477efa1 | ||
|
|
8a08ea466a | ||
|
|
b8ae30d454 | ||
|
|
0a0fb2468a | ||
|
|
bc2921b22c | ||
|
|
917ad53024 | ||
|
|
aac87196d6 | ||
|
|
4c2e6054c0 | ||
|
|
c471ae944c | ||
|
|
c31ad72ee9 | ||
|
|
fbd1a6c5f1 | ||
|
|
0728bece0c | ||
|
|
3497eeff49 | ||
|
|
c1d15de7fc | ||
|
|
a4bb96d45c | ||
|
|
bfd52ad85f | ||
|
|
8f1f9d9e6b | ||
|
|
b32d3025f3 | ||
|
|
d588418053 | ||
|
|
83c18de56a | ||
|
|
7708b95ec7 | ||
|
|
749f2f0314 | ||
|
|
c98d6fd035 | ||
|
|
a9ca493b5d | ||
|
|
974c71389c | ||
|
|
321c479b03 | ||
|
|
4f5c8be38e | ||
|
|
1befae872d | ||
|
|
0090ec2dda | ||
|
|
af8292b623 | ||
|
|
9101bdd7a9 | ||
|
|
dc66eee8f0 | ||
|
|
10db2131bd | ||
|
|
b94d9a30d3 | ||
|
|
9d88ba7afb | ||
|
|
1fac263263 | ||
|
|
077a5c367c | ||
|
|
a813b57f59 | ||
|
|
8ae7a38da5 | ||
|
|
3947809c36 | ||
|
|
ca6edafbfe | ||
|
|
a7f236b896 | ||
|
|
f442c62bc5 | ||
|
|
f975998875 | ||
|
|
646d33fea3 | ||
|
|
02cbea546d | ||
|
|
4f89cb050a | ||
|
|
14db7b0ac0 | ||
|
|
9f2eddd9d8 | ||
|
|
7c3e19c65a | ||
|
|
35782bd9f8 | ||
|
|
039accc139 | ||
|
|
c8a081e459 | ||
|
|
b80a7d01c7 | ||
|
|
e6fd6d6360 | ||
|
|
95e3eddaf0 | ||
|
|
d760010c9e | ||
|
|
0cde08a78c | ||
|
|
3adfe3fbb9 | ||
|
|
85aadd1585 | ||
|
|
c764c21273 | ||
|
|
4366dd92ac | ||
|
|
5a55fc0737 | ||
|
|
391508f054 | ||
|
|
1b8b5ac179 | ||
|
|
11049401ce | ||
|
|
59e1361f5e | ||
|
|
68fc432978 | ||
|
|
badf826196 | ||
|
|
7b10f7320d | ||
|
|
4120a7dee9 | ||
|
|
6aa29bdb24 | ||
|
|
0fe223f00e | ||
|
|
6ec605a7fb | ||
|
|
c652625beb | ||
|
|
a3e8bea03c | ||
|
|
6aab0a5458 | ||
|
|
c4bf383fd7 | ||
|
|
fa844865a5 | ||
|
|
831cff3fee | ||
|
|
ba4ec7fce2 | ||
|
|
9e165c2c17 | ||
|
|
c566f1f024 | ||
|
|
7ffde29593 | ||
|
|
e9c2dbc866 | ||
|
|
9ccc7daa83 | ||
|
|
80aa0a753d | ||
|
|
88ec93d075 | ||
|
|
0f89f5eca0 | ||
|
|
ab3d374ab7 | ||
|
|
35b8992ede | ||
|
|
3415b11d79 | ||
|
|
6989bf054c | ||
|
|
2991fd2a48 | ||
|
|
c37da600e8 | ||
|
|
d0ae9d7ce6 | ||
|
|
5e9d254564 | ||
|
|
02bd50f6ab | ||
|
|
8a826ae24c | ||
|
|
98dac352b8 | ||
|
|
1e0ebb760f | ||
|
|
e577b64a58 | ||
|
|
36bffff22a | ||
|
|
b9ba81c30d | ||
|
|
16ac24d8f7 | ||
|
|
bb8d19bbd6 | ||
|
|
1c44e917f9 | ||
|
|
4cd2eccf2f | ||
|
|
308b790cfc | ||
|
|
6d59428641 | ||
|
|
e1701f11b1 | ||
|
|
376db31107 | ||
|
|
52ac3779df | ||
|
|
b57eeb9628 | ||
|
|
5016fcf8d5 | ||
|
|
592de97fbd | ||
|
|
f3f398eb44 | ||
|
|
3220292d45 | ||
|
|
0d2be3b6d4 | ||
|
|
9d8754c620 | ||
|
|
9bac13deb3 | ||
|
|
e8d4a13e7f | ||
|
|
d8f181c576 | ||
|
|
1e44ac515f | ||
|
|
c197bd6f82 | ||
|
|
475040b29e | ||
|
|
d809f2fe09 | ||
|
|
0d720cc3d8 | ||
|
|
74d88c4c8a | ||
|
|
9adc32a043 | ||
|
|
250f825f58 | ||
|
|
006da0405e | ||
|
|
54b3329f0c | ||
|
|
6fde2d18a6 | ||
|
|
a4d23c3cf3 | ||
|
|
12e00c0f45 | ||
|
|
31bde4f5da | ||
|
|
801e72ac6d | ||
|
|
91a4a2581e | ||
|
|
cd60de8f7f | ||
|
|
797cd13dcc | ||
|
|
f6a71bec36 | ||
|
|
64e01684f9 | ||
|
|
3b77be903d | ||
|
|
83ec716ec4 | ||
|
|
b91ab1e3a5 | ||
|
|
1be7037229 | ||
|
|
bc6084390f | ||
|
|
7fec020f53 | ||
|
|
14b379dec5 | ||
|
|
0805d458c4 | ||
|
|
daaeba5142 | ||
|
|
5c7df03efe | ||
|
|
2e6895d911 | ||
|
|
987f82152e | ||
|
|
9cb1e1cc6b | ||
|
|
adf297066d | ||
|
|
d39f91de02 | ||
|
|
89c2a5868c | ||
|
|
52c80cfcf5 | ||
|
|
5b408eec66 | ||
|
|
a6fde0bb1c | ||
|
|
b8841d7a82 | ||
|
|
fec97e0586 | ||
|
|
5109f914eb | ||
|
|
cb220ae184 | ||
|
|
eb3d9d15bf | ||
|
|
59b730342d | ||
|
|
dd0791d7c8 | ||
|
|
7660a104d9 | ||
|
|
327d67fadf | ||
|
|
bc318b64e9 | ||
|
|
5571c6af2d | ||
|
|
af3d080226 | ||
|
|
678855e858 | ||
|
|
2a16ce7e41 | ||
|
|
99e3a15014 | ||
|
|
96be82296f | ||
|
|
5115ad6e56 | ||
|
|
d5bfa2dfc8 | ||
|
|
2f0bc06bec | ||
|
|
aaba6e777f | ||
|
|
9ab4bb1ae2 | ||
|
|
06487131f9 | ||
|
|
eeb7e10d1f | ||
|
|
9d84ef5073 | ||
|
|
14350f5de4 | ||
|
|
556871570e | ||
|
|
052eff1ca7 | ||
|
|
354c2a4b3d | ||
|
|
46e0b3ff75 | ||
|
|
bb7fa71fcb | ||
|
|
ec5ff395cf | ||
|
|
9b53044aae | ||
|
|
1592a9292f | ||
|
|
2e91b73881 | ||
|
|
9eb7413fab | ||
|
|
be946caf4b | ||
|
|
ff9c8b847f | ||
|
|
8183141399 | ||
|
|
e4386c8bb7 | ||
|
|
75ddd2a244 | ||
|
|
eac350c454 | ||
|
|
6802ef5406 | ||
|
|
d71d52e9b0 | ||
|
|
b08fde5928 | ||
|
|
8031ac33c3 | ||
|
|
50134969b9 | ||
|
|
ce794f7858 | ||
|
|
33b0e15804 | ||
|
|
ad9374bd68 | ||
|
|
8cef35745b | ||
|
|
ce7678f29f | ||
|
|
12758d7fad | ||
|
|
86fcd19b8a | ||
|
|
a5de49558e | ||
|
|
9ed880d74e | ||
|
|
7e0ab6b56d | ||
|
|
3012975c17 | ||
|
|
6c2bca5f1b | ||
|
|
8de7448d44 | ||
|
|
bf09a1e695 | ||
|
|
a76cf0865f | ||
|
|
99422324a7 | ||
|
|
1d61c476a1 | ||
|
|
f8501a3bd3 | ||
|
|
6e049e0da1 | ||
|
|
10348f73e4 | ||
|
|
9d1f95a3cc | ||
|
|
30e2e51588 | ||
|
|
dd9d0d0440 | ||
|
|
cdc8493a05 | ||
|
|
f5debc4bc0 | ||
|
|
37dfb4cbb3 | ||
|
|
e1a07ce3c4 | ||
|
|
1df38daf74 | ||
|
|
7481e4d959 | ||
|
|
2ac6971a8b | ||
|
|
3f42f86d81 | ||
|
|
d6aa717913 | ||
|
|
3eaeba4b55 | ||
|
|
df14a68e87 | ||
|
|
e685854f16 | ||
|
|
c790bba905 | ||
|
|
e525af7a61 | ||
|
|
3e8a6ba0c0 | ||
|
|
9ebf052538 | ||
|
|
f6b150a34d | ||
|
|
9f08f537e0 | ||
|
|
9c7215d620 | ||
|
|
35744375e0 | ||
|
|
4fc75cf2b0 | ||
|
|
d41ce909a2 | ||
|
|
f626bd60c5 | ||
|
|
5a380a6918 | ||
|
|
f8ee89a514 | ||
|
|
49322c5ebe | ||
|
|
24c2b67aa4 | ||
|
|
03dd24978e | ||
|
|
cf707da1a8 | ||
|
|
c22fd39170 | ||
|
|
0a97c25464 | ||
|
|
b2de1cd523 | ||
|
|
fbcf3a7a7a | ||
|
|
b0353992d6 | ||
|
|
3fb0a02fa2 | ||
|
|
8b823e5e34 | ||
|
|
c23b48888d | ||
|
|
f68b08bfdb | ||
|
|
18f7789a81 | ||
|
|
c2abadc293 | ||
|
|
bb2139a408 | ||
|
|
d35e775ed9 | ||
|
|
49cc73a4ca | ||
|
|
187b1eec8b | ||
|
|
a484aa7be6 | ||
|
|
ac7178ef2a | ||
|
|
b6fdbf7a44 | ||
|
|
12fec55064 | ||
|
|
fd45e94510 | ||
|
|
b7faf4fdc1 | ||
|
|
0870028fde | ||
|
|
592cfbafb3 | ||
|
|
621ea837fa | ||
|
|
a2830da7c0 | ||
|
|
926a414b90 | ||
|
|
747f31d82c | ||
|
|
5eb903a509 | ||
|
|
97e2d5d266 | ||
|
|
b8cfdab8b6 | ||
|
|
9bd5ec8967 | ||
|
|
e7e0e8ac38 | ||
|
|
82d0cdff4f | ||
|
|
a517be4eac | ||
|
|
13c8283fbe | ||
|
|
a31f5f8106 | ||
|
|
e78e34b36a | ||
|
|
808586ece7 | ||
|
|
ea82632c70 | ||
|
|
6389418fa6 | ||
|
|
f9a0445c3d | ||
|
|
444e5c2a04 | ||
|
|
5bab9c3894 | ||
|
|
514491a051 | ||
|
|
2fa652aa4c | ||
|
|
476e4ce24f | ||
|
|
b38470df4b | ||
|
|
1beb62ed2d | ||
|
|
a8ec8f3326 | ||
|
|
6cc8248a37 | ||
|
|
8ff3633782 | ||
|
|
56207a0328 | ||
|
|
a216c2ebd3 | ||
|
|
72fdfff15d | ||
|
|
70ecd6bab4 | ||
|
|
0c6706a510 | ||
|
|
6565e478e6 | ||
|
|
1a0a2c0182 | ||
|
|
cfffb7edb0 | ||
|
|
b927dceed8 | ||
|
|
cd2b03f6ed | ||
|
|
d9eec0de14 | ||
|
|
40e4dc6339 | ||
|
|
8f434df1fb | ||
|
|
b1554642e4 | ||
|
|
886c0f9093 | ||
|
|
6c164c761b | ||
|
|
0071ac8c30 | ||
|
|
2b7fbcb99a | ||
|
|
d1155c7c7f | ||
|
|
28abfa9de5 | ||
|
|
c260640fd5 | ||
|
|
4617aefd60 | ||
|
|
7134f3129f | ||
|
|
0fc3936448 | ||
|
|
b9f193703a | ||
|
|
2d3f518c72 | ||
|
|
eeb7e2529d | ||
|
|
12edb24678 | ||
|
|
31928fb103 | ||
|
|
11dd609fa5 | ||
|
|
aaf382eee2 | ||
|
|
3b0da2d895 | ||
|
|
99b2ada4d0 | ||
|
|
0ea706faf5 | ||
|
|
8d9d22436a | ||
|
|
a517ca23a0 | ||
|
|
6712958f23 | ||
|
|
b2cdee80b6 | ||
|
|
d771223b2f | ||
|
|
ee5a041a0f | ||
|
|
be4e1e8712 | ||
|
|
f0ab002ef1 | ||
|
|
b9f9f01fcc | ||
|
|
6eb721301c | ||
|
|
53613319cc | ||
|
|
2cd0bc0a2c | ||
|
|
b55b83c6e8 | ||
|
|
1ed357cf34 | ||
|
|
20ba271b6c | ||
|
|
e606112cef | ||
|
|
50b3f008d1 | ||
|
|
f787c409c4 | ||
|
|
722f9b6628 | ||
|
|
13756490b5 | ||
|
|
a0d5dbce6c | ||
|
|
93d3abfd6e | ||
|
|
12b036a824 | ||
|
|
183529d024 | ||
|
|
22958261aa | ||
|
|
c7dfbd8a84 | ||
|
|
d8119fda13 | ||
|
|
1a10f16ad0 | ||
|
|
f449486b9b | ||
|
|
a4e0ce58c7 | ||
|
|
c731a943d4 | ||
|
|
37264cf21b | ||
|
|
8665898ff3 | ||
|
|
a85e2fa735 | ||
|
|
bed282b801 | ||
|
|
2ae3392d32 | ||
|
|
143d63c88e | ||
|
|
5ab58e743a | ||
|
|
233ad78b3a | ||
|
|
13dc637350 | ||
|
|
7a071c28db | ||
|
|
e4cd3072d4 | ||
|
|
708dbb973a | ||
|
|
057e71d266 | ||
|
|
218c780f67 | ||
|
|
a5747a843e | ||
|
|
8754720e2d | ||
|
|
224e3ddef4 | ||
|
|
9e10244716 | ||
|
|
e40ae6274b | ||
|
|
be32065aa4 | ||
|
|
f01bd7d56d | ||
|
|
746138039d | ||
|
|
929142bc2e | ||
|
|
68203ea414 | ||
|
|
9e6a2825ba | ||
|
|
9e7231cd69 | ||
|
|
1e20b5727d | ||
|
|
baf4850052 | ||
|
|
00230905ff | ||
|
|
447af8e702 | ||
|
|
c73c9b112c | ||
|
|
0646a90626 | ||
|
|
b584ec3fb3 | ||
|
|
ef2b11ee6c | ||
|
|
622f3f8fb6 | ||
|
|
164e810069 | ||
|
|
1a2c1f9751 | ||
|
|
3eb11069d0 | ||
|
|
c3a3490331 | ||
|
|
7a50646c02 | ||
|
|
8ba37a98a7 | ||
|
|
b3e4111e1d | ||
|
|
98741573e7 | ||
|
|
3ef1c00cfb | ||
|
|
dc97b67dac | ||
|
|
859a96181f | ||
|
|
35c0ea6c25 | ||
|
|
fe2d884605 | ||
|
|
8f300bed83 | ||
|
|
92c73c3b78 | ||
|
|
39d4f817f0 | ||
|
|
cf8f8ce93a | ||
|
|
452475c280 | ||
|
|
1b1a2175ca | ||
|
|
2d17f4384a | ||
|
|
bf135a29f7 | ||
|
|
da4aaee778 | ||
|
|
6a934c32ee | ||
|
|
3b891cd304 | ||
|
|
d5199ad91d | ||
|
|
6a0b4d7a7c | ||
|
|
31bbe0e509 | ||
|
|
7f098168ac | ||
|
|
00a1eced55 | ||
|
|
f24b74246d | ||
|
|
e4ea8b0bef | ||
|
|
33be24bd5a | ||
|
|
78b5cbc63a | ||
|
|
0c29c2ec49 | ||
|
|
9e18177654 | ||
|
|
cc67131ecc | ||
|
|
e4a034aef9 | ||
|
|
8fc7bf2953 | ||
|
|
785812d976 | ||
|
|
5157ba10d1 | ||
|
|
3b470d4f4b | ||
|
|
6cce7b9560 | ||
|
|
1f9bc68c54 | ||
|
|
4db252b418 | ||
|
|
0ce7de3ef5 | ||
|
|
4b110d5b73 | ||
|
|
f3f4d07595 | ||
|
|
c73c31b66e | ||
|
|
6244beb9d5 | ||
|
|
57c3fa4b1e | ||
|
|
f664cddec9 | ||
|
|
8f323e61d8 | ||
|
|
42a9896fe4 | ||
|
|
9df601a1df | ||
|
|
41d7b3a387 | ||
|
|
15d9ec0149 | ||
|
|
abbd69abee | ||
|
|
4a7a2cff8c | ||
|
|
e63f98182a | ||
|
|
e6b934a56a | ||
|
|
51d102c708 | ||
|
|
8d05fb86e3 | ||
|
|
0b0510108d | ||
|
|
44aef8f438 | ||
|
|
75878dd90a | ||
|
|
f6f4c68fb4 | ||
|
|
15de30f141 | ||
|
|
f7def621dd | ||
|
|
8c3ea8cd76 | ||
|
|
a7296bbb52 | ||
|
|
61e34331c2 | ||
|
|
4c9d15d44c | ||
|
|
96d4f1e39e | ||
|
|
05ce2dc820 | ||
|
|
eec1a32d95 | ||
|
|
82cad99c0b | ||
|
|
f2151b2734 | ||
|
|
164ec2b8e6 | ||
|
|
b3082c1984 | ||
|
|
2be703ca92 | ||
|
|
66da29d8dd | ||
|
|
ede288789a | ||
|
|
c1e6d0022b | ||
|
|
890582b5a2 | ||
|
|
4de258477e | ||
|
|
f2682463a4 | ||
|
|
5a5b3cf23b | ||
|
|
dd8debf201 | ||
|
|
0c9c72c583 | ||
|
|
c95851f48f | ||
|
|
68293fc0e8 | ||
|
|
d75e7456fc | ||
|
|
7ff82af4b2 | ||
|
|
e54f8ec8a5 | ||
|
|
bc874a9b6f | ||
|
|
93becca82a | ||
|
|
feb8d7ae90 | ||
|
|
5120c275a2 | ||
|
|
f1a23d712f | ||
|
|
db62cb3f4d | ||
|
|
cb98d90097 | ||
|
|
708f02dc26 | ||
|
|
18fbd303ab | ||
|
|
bbc385c3f3 | ||
|
|
2fc86ffea6 | ||
|
|
4b462a54ee | ||
|
|
8acc15a63c | ||
|
|
38ed560d3a | ||
|
|
9f67de4735 | ||
|
|
ebab8090ee | ||
|
|
c67e407620 | ||
|
|
bdcfe1efbd | ||
|
|
70986022d8 | ||
|
|
f7197e8415 | ||
|
|
d11a67b0fe | ||
|
|
7257e890ef | ||
|
|
03cb26cc70 | ||
|
|
3e9a39ca13 | ||
|
|
c503118d16 | ||
|
|
0788600702 | ||
|
|
477bc17f18 | ||
|
|
96be2dbeff | ||
|
|
5a335fc408 | ||
|
|
a31ffef085 | ||
|
|
87990f9e67 | ||
|
|
fb185fa839 | ||
|
|
462082c52f | ||
|
|
c821e96e2a | ||
|
|
5aa9abca5e | ||
|
|
6329219749 | ||
|
|
63eeed29cc | ||
|
|
0895bcb5dd | ||
|
|
0541f5aa54 | ||
|
|
51f51ea17d | ||
|
|
9cf67d133f | ||
|
|
ad354700c9 | ||
|
|
28a5f9dc19 | ||
|
|
5c1207c198 | ||
|
|
f61f78eac6 | ||
|
|
efb2447801 | ||
|
|
da084fade7 | ||
|
|
64941bfcad | ||
|
|
9e247c4e06 | ||
|
|
a22bc3559f | ||
|
|
840a9a97ff | ||
|
|
24f0e42e6a | ||
|
|
98b956886e | ||
|
|
6e6ef8348b | ||
|
|
291f1cef29 | ||
|
|
07cc73bddc | ||
|
|
710520e9a1 | ||
|
|
90309b5106 | ||
|
|
2294c2d2c9 | ||
|
|
48c4885b47 | ||
|
|
134e64c612 | ||
|
|
6ea85ca86c | ||
|
|
4579afa091 | ||
|
|
7e599b4450 | ||
|
|
477d924528 | ||
|
|
4f2469e054 | ||
|
|
8d785bbd5f | ||
|
|
04f2ccd16d | ||
|
|
02265bb589 | ||
|
|
40b38a4222 | ||
|
|
ca3b39c0be | ||
|
|
ee1ba56cfe | ||
|
|
3957d1ee27 | ||
|
|
536323d9fb | ||
|
|
401e1d2489 | ||
|
|
e0bdfe826e | ||
|
|
df2570a472 | ||
|
|
9ab648b8f7 | ||
|
|
03398532dc | ||
|
|
abccf766ce | ||
|
|
5c3ce9dd00 | ||
|
|
9a7d9c9465 | ||
|
|
0d53ba4395 | ||
|
|
babefbd9bf | ||
|
|
63c5d09537 | ||
|
|
8cbb6d0faf | ||
|
|
efba48dddb | ||
|
|
34412c9b41 | ||
|
|
18dc916380 | ||
|
|
9023444ad3 | ||
|
|
f4a956a60a | ||
|
|
432eb5b70a | ||
|
|
f0dff8b58c | ||
|
|
34d1f435f4 | ||
|
|
b8e1cf2d4e | ||
|
|
f3dc245c4f | ||
|
|
911a2d61d7 | ||
|
|
21160f7490 | ||
|
|
bb2d04b39d | ||
|
|
bde4f735ab | ||
|
|
6ed23bb3ab | ||
|
|
5edd812792 | ||
|
|
f7277140bb | ||
|
|
b50cbe5064 | ||
|
|
df71aaee3f | ||
|
|
e514b124ed | ||
|
|
81173bf4ab | ||
|
|
6cbdb9aa47 | ||
|
|
991f3bb5da | ||
|
|
6adebbc6a0 | ||
|
|
0e21c32c0b | ||
|
|
f8e2008e0e | ||
|
|
08604ac6a8 | ||
|
|
0f99d3f7cc | ||
|
|
13a61c4784 | ||
|
|
fcd605a227 | ||
|
|
30b69549e1 | ||
|
|
16fd506bb8 | ||
|
|
ba8cd4d55a | ||
|
|
06cd31cb2e | ||
|
|
7e941c9ecd | ||
|
|
df794993f8 | ||
|
|
7d75b1f938 | ||
|
|
841bf89639 | ||
|
|
86acdb9a12 | ||
|
|
4b328631b9 | ||
|
|
e518098688 | ||
|
|
be6f91d0d1 | ||
|
|
6341736286 | ||
|
|
e0419e988b | ||
|
|
f0a3e7c4a1 | ||
|
|
6539d4a873 | ||
|
|
cfbebd8852 | ||
|
|
42148063ec | ||
|
|
2baf5ab80d | ||
|
|
97affbebfe | ||
|
|
7b1d5c1ad9 | ||
|
|
a4cca5f60a | ||
|
|
689415e5ff | ||
|
|
09829c44b2 | ||
|
|
2dbb376175 | ||
|
|
9e915a0284 | ||
|
|
49268bff3b | ||
|
|
ca426cfdf4 | ||
|
|
7773812f58 | ||
|
|
d0820cc026 | ||
|
|
948f808da6 | ||
|
|
aa638ead42 | ||
|
|
1152b1a147 | ||
|
|
d47f733e51 | ||
|
|
c1af13efeb | ||
|
|
a86f4369a7 | ||
|
|
320fea0eaf | ||
|
|
00716f90a0 | ||
|
|
5902dfa403 | ||
|
|
d9c1b4396e | ||
|
|
02adc4061a | ||
|
|
3f396d87ef | ||
|
|
d0218e94a3 | ||
|
|
0e58741a09 | ||
|
|
0f1396acef | ||
|
|
65774e8a78 | ||
|
|
2eb26d4de4 | ||
|
|
8dbc84318c | ||
|
|
c62db97a81 | ||
|
|
4dc2a39f3a | ||
|
|
c3a0685e2d | ||
|
|
5a68d5b6a7 | ||
|
|
ead86bb1e4 | ||
|
|
22dd49d0b5 | ||
|
|
642b890b0f | ||
|
|
0223cf8c2b | ||
|
|
ed396b4d58 | ||
|
|
81c2294c11 | ||
|
|
5200bf3c59 | ||
|
|
65a38e6f70 | ||
|
|
d6e6901957 | ||
|
|
8f3720089f | ||
|
|
cc2d03d29f | ||
|
|
f5b37e3444 | ||
|
|
009f4d23d0 | ||
|
|
5ade9d4610 | ||
|
|
c98949bdd3 | ||
|
|
2f58fb8307 | ||
|
|
e817aa6824 | ||
|
|
559b70cf0a | ||
|
|
dae7608b1e | ||
|
|
6466ad2112 | ||
|
|
9f1ac24bed | ||
|
|
49e70c6203 | ||
|
|
67a5882415 | ||
|
|
8142c823ed | ||
|
|
531f4ff6b0 | ||
|
|
e52129efb4 | ||
|
|
0c5c470247 | ||
|
|
5b96dd5f70 | ||
|
|
d8d790bb7b | ||
|
|
1c5562b656 | ||
|
|
eae4f1d249 | ||
|
|
ea0bc240fd | ||
|
|
42937c9754 | ||
|
|
d7b0b9842d | ||
|
|
3cb4513077 | ||
|
|
9de3ace8c7 | ||
|
|
b77fdeba2d | ||
|
|
8423c0897a | ||
|
|
6315f16ca7 | ||
|
|
4cf89d373d | ||
|
|
f6b9445be7 | ||
|
|
77ee331be0 | ||
|
|
bba5ef3a59 | ||
|
|
374ac77c63 | ||
|
|
ffd816ebfa | ||
|
|
d04e0dc9e1 | ||
|
|
7da5b531a0 | ||
|
|
109c7f731d | ||
|
|
812a6b97d3 | ||
|
|
ad307912ab | ||
|
|
94f2fc3308 | ||
|
|
f190cb05b5 | ||
|
|
299eb28453 | ||
|
|
d2f1749edb | ||
|
|
378dd81204 | ||
|
|
056f5481ac | ||
|
|
4ebf01b344 | ||
|
|
b5755e9c33 | ||
|
|
c49e31a6d9 | ||
|
|
ebc6391bea | ||
|
|
9de755334d | ||
|
|
ed1e7817cc | ||
|
|
5c06b7f862 | ||
|
|
0e4e27ea9d | ||
|
|
1ef6996207 | ||
|
|
d17fa784bc | ||
|
|
793d4e7b8d | ||
|
|
ddf6caf955 | ||
|
|
af621be509 | ||
|
|
4970e2f703 | ||
|
|
59958f0a61 | ||
|
|
3f09eff6c5 | ||
|
|
956a84f73a | ||
|
|
269d660b0a | ||
|
|
be78c6da49 | ||
|
|
f7378bc092 | ||
|
|
1f6cbad01a | ||
|
|
4d60ef59bc | ||
|
|
7fabca3548 | ||
|
|
27247164b5 | ||
|
|
08a68f2f0f | ||
|
|
f21cd31b94 | ||
|
|
a94887e051 | ||
|
|
3edc2bd6ab | ||
|
|
968c90a6ce | ||
|
|
7485a0c1f7 | ||
|
|
cbc0ac3267 | ||
|
|
7fdb5ae4c4 | ||
|
|
0d748c5c2b | ||
|
|
6692fa60e9 | ||
|
|
f03602a4c9 | ||
|
|
bc75c1b4e1 | ||
|
|
d054dddcb3 | ||
|
|
b04eb4770b | ||
|
|
503887843f | ||
|
|
7c9e8b103f | ||
|
|
545ea9ea33 | ||
|
|
ebd83699dc | ||
|
|
bdb92f9ace | ||
|
|
2061f0d8b4 | ||
|
|
e8e4f43d52 | ||
|
|
296ada9441 | ||
|
|
287897d702 | ||
|
|
16614447eb | ||
|
|
96479dabe6 | ||
|
|
88c1683029 | ||
|
|
8d3e4f989d | ||
|
|
53481aabe5 | ||
|
|
4dd16c2686 | ||
|
|
8627656f55 | ||
|
|
6e5338d28f | ||
|
|
014844d0da | ||
|
|
779cbfe8bd | ||
|
|
700df9eb16 | ||
|
|
c33c38bd96 | ||
|
|
da7f31d4bc | ||
|
|
759a0be6cd | ||
|
|
b17bafb530 | ||
|
|
7ca691fc2f | ||
|
|
e619fe6b01 | ||
|
|
613f254908 | ||
|
|
97ab767a2f | ||
|
|
7f032c8bb3 | ||
|
|
99cb8dc0a4 | ||
|
|
710394b872 | ||
|
|
4f4c3d3ebe | ||
|
|
f1e19d6c96 | ||
|
|
80c9dc2e0c | ||
|
|
c706ca1522 | ||
|
|
0a1d6e1cb3 | ||
|
|
c8fa8092c5 | ||
|
|
26d8caedb6 | ||
|
|
572aaa7231 | ||
|
|
04cb35e3f4 | ||
|
|
9bf100c91c | ||
|
|
bfbca596ea | ||
|
|
6e6eb5b55e | ||
|
|
a5b053f81d |
29
.github/workflows/build.yaml
vendored
Normal file
29
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Cloud Hypervisor Build
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --target=${{ matrix.target }}
|
||||
34
.github/workflows/cross-build.yaml
vendored
Normal file
34
.github/workflows/cross-build.yaml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Cloud Hypervisor Cross Build
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
target:
|
||||
- aarch64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
- name: Install arm64 libfdt
|
||||
run: wget http://ftp.us.debian.org/debian/pool/main/d/device-tree-compiler/libfdt-dev_1.6.0-1_arm64.deb && dpkg-deb -xv libfdt-dev_1.6.0-1_arm64.deb ./tlibfdtdev && sudo mkdir /tmmmp && mkdir target && mkdir target/debug && mkdir target/debug/deps && sudo cp ./tlibfdtdev/usr/lib/aarch64-linux-gnu/libfdt.a target/debug/deps/libfdt.a && echo "libfdt installed"
|
||||
- name: Disable "with-serde" in kvm-bindings
|
||||
run: sed -i 's/"with-serde",\ //g' vmm/Cargo.toml
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --target=${{ matrix.target }} --no-default-features --features "mmio"
|
||||
67
.github/workflows/release.yaml
vendored
Normal file
67
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Cloud Hypervisor Release
|
||||
on: [create]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Rust toolchain (x86_64-unknown-linux-gnu)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- name: Install Rust toolchain (x86_64-unknown-linux-musl)
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: x86_64-unknown-linux-musl
|
||||
- name: Build
|
||||
run: cargo build --release --target=x86_64-unknown-linux-gnu
|
||||
- name: Static Build
|
||||
run: cargo build --release --target=x86_64-unknown-linux-musl
|
||||
- name: Strip cloud-hypervisor binaries
|
||||
run: strip target/*/release/cloud-hypervisor
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: true
|
||||
prerelease: true
|
||||
- name: Upload cloud-hypervisor
|
||||
id: upload-release-cloud-hypervisor
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-gnu/release/cloud-hypervisor
|
||||
asset_name: cloud-hypervisor
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload static cloud-hypervisor
|
||||
id: upload-release-static-cloud-hypervisor
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-musl/release/cloud-hypervisor
|
||||
asset_name: cloud-hypervisor-static
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload ch-remote
|
||||
id: upload-release-ch-remote
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: target/x86_64-unknown-linux-gnu/release/ch-remote
|
||||
asset_name: ch-remote
|
||||
asset_content_type: application/octet-stream
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/build
|
||||
/target
|
||||
**/*.rs.bk
|
||||
**/Cargo.lock
|
||||
|
||||
1
.rustfmt.toml
Normal file
1
.rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
edition = "2018"
|
||||
16
.travis.yml
16
.travis.yml
@@ -1,16 +0,0 @@
|
||||
language: rust
|
||||
|
||||
rust:
|
||||
- stable
|
||||
|
||||
script:
|
||||
- cargo build --release
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key: $GITHUB_OAUTH_TOKEN
|
||||
file: target/release/cloud-hypervisor
|
||||
skip_cleanup: true
|
||||
draft: true
|
||||
on:
|
||||
tags: true
|
||||
1455
Cargo.lock
generated
1455
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
34
Cargo.toml
34
Cargo.toml
@@ -1,39 +1,45 @@
|
||||
[package]
|
||||
name = "cloud-hypervisor"
|
||||
version = "0.5.0"
|
||||
version = "0.8.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
default-run = "cloud-hypervisor"
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
arc-swap = ">=0.4.4"
|
||||
clap = "2.33.0"
|
||||
clap = { version = "2.33.1", features=["wrap_help"] }
|
||||
epoll = ">=4.0.1"
|
||||
futures = { version = "0.3.5", features = ["thread-pool"] }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.66"
|
||||
log = { version = "0.4.10", features = ["std"] }
|
||||
libc = "0.2.71"
|
||||
log = { version = "0.4.8", features = ["std"] }
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.21.1" }
|
||||
serde_json = ">=1.0.9"
|
||||
vhost_user_backend = { path = "vhost_user_backend"}
|
||||
vhost_user_block = { path = "vhost_user_block"}
|
||||
vhost_user_fs = { path = "vhost_user_fs"}
|
||||
vhost_user_net = { path = "vhost_user_net"}
|
||||
virtio-bindings = "0.1.0"
|
||||
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
|
||||
vmm = { path = "vmm" }
|
||||
vm-device = { path = "vm-device" }
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vm-memory = "0.2.1"
|
||||
vmm-sys-util = "0.6.1"
|
||||
vm-virtio = { path = "vm-virtio" }
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||
|
||||
[patch.crates-io]
|
||||
vm-memory = { git = "https://github.com/cloud-hypervisor/vm-memory", branch = "ch" }
|
||||
|
||||
[dev-dependencies]
|
||||
ssh2 = "0.7.1"
|
||||
ssh2 = "0.8.1"
|
||||
dirs = "2.0.2"
|
||||
credibility = "0.1.3"
|
||||
tempdir= "0.3.7"
|
||||
lazy_static= "1.4.0"
|
||||
tempfile = "3.1.0"
|
||||
serde_json = ">=1.0.9"
|
||||
|
||||
[dependencies.vhost_rs]
|
||||
path = "vhost_rs"
|
||||
features = ["vhost-user-slave"]
|
||||
net_util = { path = "net_util" }
|
||||
|
||||
[features]
|
||||
default = ["acpi", "pci", "cmos"]
|
||||
@@ -41,6 +47,7 @@ acpi = ["vmm/acpi"]
|
||||
pci = ["vmm/pci_support"]
|
||||
mmio = ["vmm/mmio_support"]
|
||||
cmos = ["vmm/cmos"]
|
||||
fwdebug = ["vmm/fwdebug"]
|
||||
|
||||
# Integration tests require a special environment to run in
|
||||
integration_tests = []
|
||||
@@ -49,17 +56,16 @@ integration_tests = []
|
||||
members = [
|
||||
"arch",
|
||||
"devices",
|
||||
"vhost_rs",
|
||||
"qcow",
|
||||
"pci",
|
||||
"vmm",
|
||||
"vm-virtio",
|
||||
"vm-device",
|
||||
"vm-migration",
|
||||
"vhost_user_block",
|
||||
"vhost_user_backend",
|
||||
"vhost_user_fs",
|
||||
"vhost_user_net",
|
||||
"vfio",
|
||||
"net_util",
|
||||
"acpi_tables",
|
||||
"arch_gen",
|
||||
|
||||
108
Jenkinsfile
vendored
108
Jenkinsfile
vendored
@@ -1,7 +1,7 @@
|
||||
pipeline{
|
||||
agent none
|
||||
stages {
|
||||
stage ('Master build') {
|
||||
stage ('Early checks') {
|
||||
agent { node { label 'master' } }
|
||||
stages {
|
||||
stage ('Check for RFC/WIP builds') {
|
||||
@@ -14,54 +14,80 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Cancel older builds') {
|
||||
when { not { branch 'master' } }
|
||||
steps {
|
||||
cancelPreviousBuilds()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Worker build') {
|
||||
agent { node { label 'bionic' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
stage ('Build') {
|
||||
failFast true
|
||||
parallel {
|
||||
stage ('Master build') {
|
||||
agent { node { label 'master' } }
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Run Cargo tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --cargo"
|
||||
}
|
||||
}
|
||||
stage ('Run OpenAPI tests') {
|
||||
steps {
|
||||
sh "scripts/run_openapi_tests.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Worker build') {
|
||||
agent { node { label 'bionic' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Run unit tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --unit"
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Install system packages') {
|
||||
steps {
|
||||
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 ('Worker build (musl)') {
|
||||
agent { node { label 'bionic' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
}
|
||||
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"
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Run unit tests for musl') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --unit --libc musl"
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests for musl') {
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration --libc musl"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,4 +108,4 @@ def cancelPreviousBuilds() {
|
||||
build.doStop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,11 +165,11 @@ To build the kernel:
|
||||
|
||||
# Clone the Cloud Hypervisor Linux branch
|
||||
$ pushd $CLOUDH
|
||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b virtio-fs-virtio-iommu-5.5-rc1 linux-cloud-hypervisor
|
||||
$ git clone --depth 1 https://github.com/cloud-hypervisor/linux.git -b virtio-fs-virtio-iommu-virtio-mem-5.6-rc4 linux-cloud-hypervisor
|
||||
$ pushd linux-cloud-hypervisor
|
||||
|
||||
# Use the cloud-hypervisor kernel config to build your kernel
|
||||
$ cp $CLOUDH/cloud-hypervisor/resources/linux-virtio-fs-virtio-iommu-config .config
|
||||
$ cp $CLOUDH/cloud-hypervisor/resources/linux-config .config
|
||||
$ make bzImage -j `nproc`
|
||||
$ popd
|
||||
```
|
||||
@@ -229,10 +229,10 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
|
||||
`cloud-hypervisor` is in a very early, pre-alpha stage. Use at your own risk!
|
||||
|
||||
As of 2019-12-12, the following cloud images are supported:
|
||||
As of 2020-04-23, the following cloud images are supported:
|
||||
* [Clear Linux](https://download.clearlinux.org/current/) (cloudguest and kvm)
|
||||
* [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
|
||||
* [Ubuntu Eoan](https://cloud-images.ubuntu.com/eoan/current/) (cloudimg)
|
||||
* [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (cloudimg)
|
||||
|
||||
Direct kernel boot to userspace should work with most rootfs.
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vm-memory = "0.2.1"
|
||||
|
||||
|
||||
@@ -5,20 +5,18 @@ authors = ["The Chromium OS Authors"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
acpi = ["acpi_tables"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.4"
|
||||
kvm-bindings = "0.2.0"
|
||||
kvm-ioctls = "0.5.0"
|
||||
libc = "0.2.60"
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch" }
|
||||
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||
libc = "0.2.71"
|
||||
vm-memory = { version = "0.2.1", features = ["backend-mmap"] }
|
||||
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
arch_gen = { path = "../arch_gen" }
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
features = ["backend-mmap"]
|
||||
|
||||
[dependencies.linux-loader]
|
||||
git = "https://github.com/rust-vmm/linux-loader"
|
||||
features = ["elf", "bzimage"]
|
||||
|
||||
610
arch/src/aarch64/fdt.rs
Normal file
610
arch/src/aarch64/fdt.rs
Normal file
@@ -0,0 +1,610 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use libc::{c_char, c_int, c_void};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{CStr, CString, NulError};
|
||||
use std::fmt::Debug;
|
||||
use std::ptr::null;
|
||||
use std::{io, result};
|
||||
|
||||
use super::super::DeviceType;
|
||||
use super::super::InitramfsConfig;
|
||||
use super::get_fdt_addr;
|
||||
use super::gic::GICDevice;
|
||||
use super::layout::FDT_MAX_SIZE;
|
||||
use crate::aarch64::fdt::Error::CstringFDTTransform;
|
||||
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
|
||||
// This is a value for uniquely identifying the FDT node declaring the interrupt controller.
|
||||
const GIC_PHANDLE: u32 = 1;
|
||||
// This is a value for uniquely identifying the FDT node containing the clock definition.
|
||||
const CLOCK_PHANDLE: u32 = 2;
|
||||
// Read the documentation specified when appending the root node to the FDT.
|
||||
const ADDRESS_CELLS: u32 = 0x2;
|
||||
const SIZE_CELLS: u32 = 0x2;
|
||||
|
||||
// As per kvm tool and
|
||||
// https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.txt
|
||||
// Look for "The 1st cell..."
|
||||
const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
|
||||
const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
|
||||
|
||||
// From https://elixir.bootlin.com/linux/v4.9.62/source/include/dt-bindings/interrupt-controller/irq.h#L17
|
||||
const IRQ_TYPE_EDGE_RISING: u32 = 1;
|
||||
const IRQ_TYPE_LEVEL_HI: u32 = 4;
|
||||
|
||||
// This links to libfdt which handles the creation of the binary blob
|
||||
// flattened device tree (fdt) that is passed to the kernel and indicates
|
||||
// the hardware configuration of the machine.
|
||||
#[link(name = "fdt")]
|
||||
extern "C" {
|
||||
fn fdt_create(buf: *mut c_void, bufsize: c_int) -> c_int;
|
||||
fn fdt_finish_reservemap(fdt: *mut c_void) -> c_int;
|
||||
fn fdt_begin_node(fdt: *mut c_void, name: *const c_char) -> c_int;
|
||||
fn fdt_property(fdt: *mut c_void, name: *const c_char, val: *const c_void, len: c_int)
|
||||
-> c_int;
|
||||
fn fdt_end_node(fdt: *mut c_void) -> c_int;
|
||||
fn fdt_open_into(fdt: *const c_void, buf: *mut c_void, bufsize: c_int) -> c_int;
|
||||
fn fdt_finish(fdt: *const c_void) -> c_int;
|
||||
fn fdt_pack(fdt: *mut c_void) -> c_int;
|
||||
}
|
||||
|
||||
/// Trait for devices to be added to the Flattened Device Tree.
|
||||
pub trait DeviceInfoForFDT {
|
||||
/// Returns the address where this device will be loaded.
|
||||
fn addr(&self) -> u64;
|
||||
/// Returns the associated interrupt for this device.
|
||||
fn irq(&self) -> u32;
|
||||
/// Returns the amount of memory that needs to be reserved for this device.
|
||||
fn length(&self) -> u64;
|
||||
}
|
||||
|
||||
/// Errors thrown while configuring the Flattened Device Tree for aarch64.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to append node to the FDT.
|
||||
AppendFDTNode(io::Error),
|
||||
/// Failed to append a property to the FDT.
|
||||
AppendFDTProperty(io::Error),
|
||||
/// Syscall for creating FDT failed.
|
||||
CreateFDT(io::Error),
|
||||
/// Failed to obtain a C style string.
|
||||
CstringFDTTransform(NulError),
|
||||
/// Failure in calling syscall for terminating this FDT.
|
||||
FinishFDTReserveMap(io::Error),
|
||||
/// Failure in writing FDT in memory.
|
||||
WriteFDTToMemory(GuestMemoryError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Creates the flattened device tree for this aarch64 VM.
|
||||
pub fn create_fdt<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline: &CStr,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
device_info: &HashMap<(DeviceType, String), T>,
|
||||
gic_device: &Box<dyn GICDevice>,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
) -> Result<Vec<u8>> {
|
||||
// Alocate stuff necessary for the holding the blob.
|
||||
let mut fdt = vec![0; FDT_MAX_SIZE];
|
||||
|
||||
allocate_fdt(&mut fdt)?;
|
||||
|
||||
// For an explanation why these nodes were introduced in the blob take a look at
|
||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
|
||||
// Look for "Required nodes and properties".
|
||||
|
||||
// Header or the root node as per above mentioned documentation.
|
||||
append_begin_node(&mut fdt, "")?;
|
||||
append_property_string(&mut fdt, "compatible", "linux,dummy-virt")?;
|
||||
// For info on #address-cells and size-cells read "Note about cells and address representation"
|
||||
// from the above mentioned txt file.
|
||||
append_property_u32(&mut fdt, "#address-cells", ADDRESS_CELLS)?;
|
||||
append_property_u32(&mut fdt, "#size-cells", SIZE_CELLS)?;
|
||||
// This is not mandatory but we use it to point the root node to the node
|
||||
// containing description of the interrupt controller for this VM.
|
||||
append_property_u32(&mut fdt, "interrupt-parent", GIC_PHANDLE)?;
|
||||
create_cpu_nodes(&mut fdt, &vcpu_mpidr)?;
|
||||
create_memory_node(&mut fdt, guest_mem)?;
|
||||
create_chosen_node(&mut fdt, cmdline, initrd)?;
|
||||
create_gic_node(&mut fdt, gic_device)?;
|
||||
create_timer_node(&mut fdt)?;
|
||||
create_clock_node(&mut fdt)?;
|
||||
create_psci_node(&mut fdt)?;
|
||||
create_devices_node(&mut fdt, device_info)?;
|
||||
|
||||
// End Header node.
|
||||
append_end_node(&mut fdt)?;
|
||||
|
||||
// Allocate another buffer so we can format and then write fdt to guest.
|
||||
let mut fdt_final = vec![0; FDT_MAX_SIZE];
|
||||
finish_fdt(&mut fdt, &mut fdt_final)?;
|
||||
|
||||
// Write FDT to memory.
|
||||
let fdt_address = GuestAddress(get_fdt_addr(&guest_mem));
|
||||
guest_mem
|
||||
.write_slice(fdt_final.as_slice(), fdt_address)
|
||||
.map_err(Error::WriteFDTToMemory)?;
|
||||
Ok(fdt_final)
|
||||
}
|
||||
|
||||
// Following are auxiliary functions for allocating and finishing the FDT.
|
||||
fn allocate_fdt(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// Safe since we allocated this array with FDT_MAX_SIZE.
|
||||
let mut fdt_ret = unsafe { fdt_create(fdt.as_mut_ptr() as *mut c_void, FDT_MAX_SIZE as c_int) };
|
||||
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::CreateFDT(io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// The flattened device trees created with fdt_create() contains a list of
|
||||
// reserved memory areas. We need to call `fdt_finish_reservemap` so as to make sure that there is a
|
||||
// terminator in the reservemap list and whatever happened to be at the
|
||||
// start of the FDT data section would end up being interpreted as
|
||||
// reservemap entries.
|
||||
// Safe since we previously allocated this array.
|
||||
fdt_ret = unsafe { fdt_finish_reservemap(fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finish_fdt(from_fdt: &mut Vec<u8>, to_fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// Safe since we allocated `fdt_final` and previously passed in its size.
|
||||
let mut fdt_ret = unsafe { fdt_finish(from_fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Safe because we allocated both arrays with the correct size.
|
||||
fdt_ret = unsafe {
|
||||
fdt_open_into(
|
||||
from_fdt.as_mut_ptr() as *mut c_void,
|
||||
to_fdt.as_mut_ptr() as *mut c_void,
|
||||
FDT_MAX_SIZE as i32,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
// Safe since we allocated `to_fdt`.
|
||||
fdt_ret = unsafe { fdt_pack(to_fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::FinishFDTReserveMap(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Following are auxiliary functions for appending nodes to FDT.
|
||||
fn append_begin_node(fdt: &mut Vec<u8>, name: &str) -> Result<()> {
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
|
||||
// Safe because we allocated fdt and converted name to a CString
|
||||
let fdt_ret = unsafe { fdt_begin_node(fdt.as_mut_ptr() as *mut c_void, cstr_name.as_ptr()) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTNode(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_end_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// Safe because we allocated fdt.
|
||||
let fdt_ret = unsafe { fdt_end_node(fdt.as_mut_ptr() as *mut c_void) };
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTNode(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Following are auxiliary functions for appending property nodes to the nodes of the FDT.
|
||||
fn append_property_u32(fdt: &mut Vec<u8>, name: &str, val: u32) -> Result<()> {
|
||||
append_property(fdt, name, &to_be32(val))
|
||||
}
|
||||
|
||||
fn append_property_u64(fdt: &mut Vec<u8>, name: &str, val: u64) -> Result<()> {
|
||||
append_property(fdt, name, &to_be64(val))
|
||||
}
|
||||
|
||||
fn append_property_string(fdt: &mut Vec<u8>, name: &str, value: &str) -> Result<()> {
|
||||
let cstr_value = CString::new(value).map_err(CstringFDTTransform)?;
|
||||
append_property_cstring(fdt, name, &cstr_value)
|
||||
}
|
||||
|
||||
fn append_property_cstring(fdt: &mut Vec<u8>, name: &str, cstr_value: &CStr) -> Result<()> {
|
||||
let value_bytes = cstr_value.to_bytes_with_nul();
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
// Safe because we allocated fdt, converted name and value to CStrings
|
||||
let fdt_ret = unsafe {
|
||||
fdt_property(
|
||||
fdt.as_mut_ptr() as *mut c_void,
|
||||
cstr_name.as_ptr(),
|
||||
value_bytes.as_ptr() as *mut c_void,
|
||||
value_bytes.len() as i32,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_property_null(fdt: &mut Vec<u8>, name: &str) -> Result<()> {
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
|
||||
// Safe because we allocated fdt, converted name to a CString
|
||||
let fdt_ret = unsafe {
|
||||
fdt_property(
|
||||
fdt.as_mut_ptr() as *mut c_void,
|
||||
cstr_name.as_ptr(),
|
||||
null(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_property(fdt: &mut Vec<u8>, name: &str, val: &[u8]) -> Result<()> {
|
||||
let cstr_name = CString::new(name).map_err(CstringFDTTransform)?;
|
||||
let val_ptr = val.as_ptr() as *const c_void;
|
||||
|
||||
// Safe because we allocated fdt and converted name to a CString
|
||||
let fdt_ret = unsafe {
|
||||
fdt_property(
|
||||
fdt.as_mut_ptr() as *mut c_void,
|
||||
cstr_name.as_ptr(),
|
||||
val_ptr,
|
||||
val.len() as i32,
|
||||
)
|
||||
};
|
||||
if fdt_ret != 0 {
|
||||
return Err(Error::AppendFDTProperty(io::Error::last_os_error()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Auxiliary functions for writing u32/u64 numbers in big endian order.
|
||||
fn to_be32(input: u32) -> [u8; 4] {
|
||||
u32::to_be_bytes(input)
|
||||
}
|
||||
|
||||
fn to_be64(input: u64) -> [u8; 8] {
|
||||
u64::to_be_bytes(input)
|
||||
}
|
||||
|
||||
// Helper functions for generating a properly formatted byte vector using 32-bit/64-bit cells.
|
||||
fn generate_prop32(cells: &[u32]) -> Vec<u8> {
|
||||
let mut ret: Vec<u8> = Vec::new();
|
||||
for &e in cells {
|
||||
ret.extend(to_be32(e).iter());
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn generate_prop64(cells: &[u64]) -> Vec<u8> {
|
||||
let mut ret: Vec<u8> = Vec::new();
|
||||
for &e in cells {
|
||||
ret.extend(to_be64(e).iter());
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
// Following are the auxiliary function for creating the different nodes that we append to our FDT.
|
||||
fn create_cpu_nodes(fdt: &mut Vec<u8>, vcpu_mpidr: &Vec<u64>) -> Result<()> {
|
||||
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
|
||||
append_begin_node(fdt, "cpus")?;
|
||||
// As per documentation, on ARM v8 64-bit systems value should be set to 2.
|
||||
append_property_u32(fdt, "#address-cells", 0x02)?;
|
||||
append_property_u32(fdt, "#size-cells", 0x0)?;
|
||||
let num_cpus = vcpu_mpidr.len();
|
||||
|
||||
for cpu_index in 0..num_cpus {
|
||||
let cpu_name = format!("cpu@{:x}", cpu_index);
|
||||
append_begin_node(fdt, &cpu_name)?;
|
||||
append_property_string(fdt, "device_type", "cpu")?;
|
||||
append_property_string(fdt, "compatible", "arm,arm-v8")?;
|
||||
if num_cpus > 1 {
|
||||
// This is required on armv8 64-bit. See aforementioned documentation.
|
||||
append_property_string(fdt, "enable-method", "psci")?;
|
||||
}
|
||||
// Set the field to first 24 bits of the MPIDR - Multiprocessor Affinity Register.
|
||||
// See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0488c/BABHBJCI.html.
|
||||
append_property_u64(fdt, "reg", vcpu_mpidr[cpu_index] & 0x7FFFFF)?;
|
||||
append_end_node(fdt)?;
|
||||
}
|
||||
append_end_node(fdt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_memory_node(fdt: &mut Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let mem_size = guest_mem.last_addr().raw_value() - super::layout::RAM_64BIT_START + 1;
|
||||
// See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L960
|
||||
// for an explanation of this.
|
||||
let mem_reg_prop = generate_prop64(&[super::layout::RAM_64BIT_START as u64, mem_size as u64]);
|
||||
|
||||
append_begin_node(fdt, "memory")?;
|
||||
append_property_string(fdt, "device_type", "memory")?;
|
||||
append_property(fdt, "reg", &mem_reg_prop)?;
|
||||
append_end_node(fdt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_chosen_node(
|
||||
fdt: &mut Vec<u8>,
|
||||
cmdline: &CStr,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
) -> Result<()> {
|
||||
append_begin_node(fdt, "chosen")?;
|
||||
append_property_cstring(fdt, "bootargs", cmdline)?;
|
||||
|
||||
if let Some(initrd_config) = initrd {
|
||||
append_property_u64(
|
||||
fdt,
|
||||
"linux,initrd-start",
|
||||
initrd_config.address.raw_value() as u64,
|
||||
)?;
|
||||
append_property_u64(
|
||||
fdt,
|
||||
"linux,initrd-end",
|
||||
initrd_config.address.raw_value() + initrd_config.size as u64,
|
||||
)?;
|
||||
}
|
||||
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_gic_node(fdt: &mut Vec<u8>, gic_device: &Box<dyn GICDevice>) -> Result<()> {
|
||||
let gic_reg_prop = generate_prop64(gic_device.device_properties());
|
||||
|
||||
append_begin_node(fdt, "intc")?;
|
||||
append_property_string(fdt, "compatible", gic_device.fdt_compatibility())?;
|
||||
append_property_null(fdt, "interrupt-controller")?;
|
||||
// "interrupt-cells" field specifies the number of cells needed to encode an
|
||||
// interrupt source. The type shall be a <u32> and the value shall be 3 if no PPI affinity description
|
||||
// is required.
|
||||
append_property_u32(fdt, "#interrupt-cells", 3)?;
|
||||
append_property(fdt, "reg", &gic_reg_prop)?;
|
||||
append_property_u32(fdt, "phandle", GIC_PHANDLE)?;
|
||||
append_property_u32(fdt, "#address-cells", 2)?;
|
||||
append_property_u32(fdt, "#size-cells", 2)?;
|
||||
append_property_null(fdt, "ranges")?;
|
||||
let gic_intr = [
|
||||
GIC_FDT_IRQ_TYPE_PPI,
|
||||
gic_device.fdt_maint_irq(),
|
||||
IRQ_TYPE_LEVEL_HI,
|
||||
];
|
||||
let gic_intr_prop = generate_prop32(&gic_intr);
|
||||
|
||||
append_property(fdt, "interrupts", &gic_intr_prop)?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_clock_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// The Advanced Peripheral Bus (APB) is part of the Advanced Microcontroller Bus Architecture
|
||||
// (AMBA) protocol family. It defines a low-cost interface that is optimized for minimal power
|
||||
// consumption and reduced interface complexity.
|
||||
// PCLK is the clock source and this node defines exactly the clock for the APB.
|
||||
append_begin_node(fdt, "apb-pclk")?;
|
||||
append_property_string(fdt, "compatible", "fixed-clock")?;
|
||||
append_property_u32(fdt, "#clock-cells", 0x0)?;
|
||||
append_property_u32(fdt, "clock-frequency", 24000000)?;
|
||||
append_property_string(fdt, "clock-output-names", "clk24mhz")?;
|
||||
append_property_u32(fdt, "phandle", CLOCK_PHANDLE)?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_timer_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
// See
|
||||
// https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt
|
||||
// These are fixed interrupt numbers for the timer device.
|
||||
let irqs = [13, 14, 11, 10];
|
||||
let compatible = "arm,armv8-timer";
|
||||
|
||||
let mut timer_reg_cells: Vec<u32> = Vec::new();
|
||||
for &irq in irqs.iter() {
|
||||
timer_reg_cells.push(GIC_FDT_IRQ_TYPE_PPI);
|
||||
timer_reg_cells.push(irq);
|
||||
timer_reg_cells.push(IRQ_TYPE_LEVEL_HI);
|
||||
}
|
||||
let timer_reg_prop = generate_prop32(timer_reg_cells.as_slice());
|
||||
|
||||
append_begin_node(fdt, "timer")?;
|
||||
append_property_string(fdt, "compatible", compatible)?;
|
||||
append_property_null(fdt, "always-on")?;
|
||||
append_property(fdt, "interrupts", &timer_reg_prop)?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_psci_node(fdt: &mut Vec<u8>) -> Result<()> {
|
||||
let compatible = "arm,psci-0.2";
|
||||
append_begin_node(fdt, "psci")?;
|
||||
append_property_string(fdt, "compatible", compatible)?;
|
||||
// Two methods available: hvc and smc.
|
||||
// As per documentation, PSCI calls between a guest and hypervisor may use the HVC conduit instead of SMC.
|
||||
// So, since we are using kvm, we need to use hvc.
|
||||
append_property_string(fdt, "method", "hvc")?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_virtio_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut Vec<u8>,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
let device_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
|
||||
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING]);
|
||||
|
||||
append_begin_node(fdt, &format!("virtio_mmio@{:x}", dev_info.addr()))?;
|
||||
append_property_string(fdt, "compatible", "virtio,mmio")?;
|
||||
append_property(fdt, "reg", &device_reg_prop)?;
|
||||
append_property(fdt, "interrupts", &irq)?;
|
||||
append_property_u32(fdt, "interrupt-parent", GIC_PHANDLE)?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_serial_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut Vec<u8>,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
let serial_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
|
||||
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING]);
|
||||
|
||||
append_begin_node(fdt, &format!("uart@{:x}", dev_info.addr()))?;
|
||||
append_property_string(fdt, "compatible", "ns16550a")?;
|
||||
append_property(fdt, "reg", &serial_reg_prop)?;
|
||||
append_property_u32(fdt, "clocks", CLOCK_PHANDLE)?;
|
||||
append_property_string(fdt, "clock-names", "apb_pclk")?;
|
||||
append_property(fdt, "interrupts", &irq)?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_rtc_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut Vec<u8>,
|
||||
dev_info: &T,
|
||||
) -> Result<()> {
|
||||
let compatible = b"arm,pl031\0arm,primecell\0";
|
||||
let rtc_reg_prop = generate_prop64(&[dev_info.addr(), dev_info.length()]);
|
||||
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_LEVEL_HI]);
|
||||
append_begin_node(fdt, &format!("rtc@{:x}", dev_info.addr()))?;
|
||||
append_property(fdt, "compatible", compatible)?;
|
||||
append_property(fdt, "reg", &rtc_reg_prop)?;
|
||||
append_property(fdt, "interrupts", &irq)?;
|
||||
append_property_u32(fdt, "clocks", CLOCK_PHANDLE)?;
|
||||
append_property_string(fdt, "clock-names", "apb_pclk")?;
|
||||
append_end_node(fdt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_devices_node<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
fdt: &mut Vec<u8>,
|
||||
dev_info: &HashMap<(DeviceType, String), T>,
|
||||
) -> Result<()> {
|
||||
// Create one temp Vec to store all virtio devices
|
||||
let mut ordered_virtio_device: Vec<&T> = Vec::new();
|
||||
|
||||
for ((device_type, _device_id), info) in dev_info {
|
||||
match device_type {
|
||||
DeviceType::RTC => create_rtc_node(fdt, info)?,
|
||||
DeviceType::Serial => create_serial_node(fdt, info)?,
|
||||
DeviceType::Virtio(_) => {
|
||||
ordered_virtio_device.push(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort out virtio devices by address from low to high and insert them into fdt table.
|
||||
ordered_virtio_device.sort_by(|a, b| a.addr().cmp(&b.addr()));
|
||||
for ordered_device_info in ordered_virtio_device.drain(..) {
|
||||
create_virtio_node(fdt, ordered_device_info)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::aarch64::gic::create_gic;
|
||||
use crate::aarch64::layout;
|
||||
use kvm_ioctls::Kvm;
|
||||
|
||||
const LEN: u64 = 4096;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MMIODeviceInfo {
|
||||
addr: u64,
|
||||
irq: u32,
|
||||
}
|
||||
|
||||
impl DeviceInfoForFDT for MMIODeviceInfo {
|
||||
fn addr(&self) -> u64 {
|
||||
self.addr
|
||||
}
|
||||
fn irq(&self) -> u32 {
|
||||
self.irq
|
||||
}
|
||||
fn length(&self) -> u64 {
|
||||
LEN
|
||||
}
|
||||
}
|
||||
|
||||
// The `load` function from the `device_tree` will mistakenly check the actual size
|
||||
// of the buffer with the allocated size. This works around that.
|
||||
fn set_size(buf: &mut [u8], pos: usize, val: usize) {
|
||||
buf[pos] = ((val >> 24) & 0xff) as u8;
|
||||
buf[pos + 1] = ((val >> 16) & 0xff) as u8;
|
||||
buf[pos + 2] = ((val >> 8) & 0xff) as u8;
|
||||
buf[pos + 3] = (val & 0xff) as u8;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_fdt_with_devices() {
|
||||
let mut regions = Vec::new();
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(layout::FDT_MAX_SIZE + 0x1000) as usize,
|
||||
));
|
||||
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
|
||||
let dev_info: HashMap<(DeviceType, std::string::String), MMIODeviceInfo> = [
|
||||
(
|
||||
(DeviceType::Serial, DeviceType::Serial.to_string()),
|
||||
MMIODeviceInfo { addr: 0x00, irq: 1 },
|
||||
),
|
||||
(
|
||||
(DeviceType::Virtio(1), "virtio".to_string()),
|
||||
MMIODeviceInfo {
|
||||
addr: 0x00 + LEN,
|
||||
irq: 2,
|
||||
},
|
||||
),
|
||||
(
|
||||
(DeviceType::RTC, "rtc".to_string()),
|
||||
MMIODeviceInfo {
|
||||
addr: 0x00 + 2 * LEN,
|
||||
irq: 3,
|
||||
},
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let gic = create_gic(&vm, 1).unwrap();
|
||||
assert!(create_fdt(
|
||||
&mem,
|
||||
&CString::new("console=tty0").unwrap(),
|
||||
vec![0],
|
||||
&dev_info,
|
||||
&gic,
|
||||
&None,
|
||||
)
|
||||
.is_ok())
|
||||
}
|
||||
}
|
||||
159
arch/src/aarch64/gic.rs
Normal file
159
arch/src/aarch64/gic.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{boxed::Box, result};
|
||||
|
||||
use kvm_ioctls::{DeviceFd, VmFd};
|
||||
|
||||
use super::gicv2::GICv2;
|
||||
use super::gicv3::GICv3;
|
||||
|
||||
/// Errors thrown while setting up the GIC.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error while calling KVM ioctl for setting up the global interrupt controller.
|
||||
CreateGIC(kvm_ioctls::Error),
|
||||
/// Error while setting device attributes for the GIC.
|
||||
SetDeviceAttribute(kvm_ioctls::Error),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Trait for GIC devices.
|
||||
pub trait GICDevice: Send + Sync {
|
||||
/// Returns the file descriptor of the GIC device
|
||||
fn device_fd(&self) -> &DeviceFd;
|
||||
|
||||
/// Returns an array with GIC device properties
|
||||
fn device_properties(&self) -> &[u64];
|
||||
|
||||
/// Returns the number of vCPUs this GIC handles
|
||||
fn vcpu_count(&self) -> u64;
|
||||
|
||||
/// Returns the fdt compatibility property of the device
|
||||
fn fdt_compatibility(&self) -> &str;
|
||||
|
||||
/// Returns the maint_irq fdt property of the device
|
||||
fn fdt_maint_irq(&self) -> u32;
|
||||
|
||||
/// Returns the GIC version of the device
|
||||
fn version() -> u32
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Create the GIC device object
|
||||
fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box<dyn GICDevice>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Setup the device-specific attributes
|
||||
fn init_device_attributes(gic_device: &Box<dyn GICDevice>) -> Result<()>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Initialize a GIC device
|
||||
fn init_device(vm: &VmFd) -> Result<DeviceFd>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut gic_device = kvm_bindings::kvm_create_device {
|
||||
type_: Self::version(),
|
||||
fd: 0,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
vm.create_device(&mut gic_device).map_err(Error::CreateGIC)
|
||||
}
|
||||
|
||||
/// Set a GIC device attribute
|
||||
fn set_device_attribute(
|
||||
fd: &DeviceFd,
|
||||
group: u32,
|
||||
attr: u64,
|
||||
addr: u64,
|
||||
flags: u32,
|
||||
) -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let attr = kvm_bindings::kvm_device_attr {
|
||||
group: group,
|
||||
attr: attr,
|
||||
addr: addr,
|
||||
flags: flags,
|
||||
};
|
||||
fd.set_device_attr(&attr)
|
||||
.map_err(Error::SetDeviceAttribute)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finalize the setup of a GIC device
|
||||
fn finalize_device(gic_device: &Box<dyn GICDevice>) -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/* We need to tell the kernel how many irqs to support with this vgic.
|
||||
* See the `layout` module for details.
|
||||
*/
|
||||
let nr_irqs: u32 = super::layout::IRQ_MAX - super::layout::IRQ_BASE + 1;
|
||||
let nr_irqs_ptr = &nr_irqs as *const u32;
|
||||
Self::set_device_attribute(
|
||||
gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
||||
0,
|
||||
nr_irqs_ptr as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
/* Finalize the GIC.
|
||||
* See https://code.woboq.org/linux/linux/virt/kvm/arm/vgic/vgic-kvm-device.c.html#211.
|
||||
*/
|
||||
Self::set_device_attribute(
|
||||
gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
|
||||
u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_CTRL_INIT),
|
||||
0,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Method to initialize the GIC device
|
||||
fn new(vm: &VmFd, vcpu_count: u64) -> Result<Box<dyn GICDevice>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let vgic_fd = Self::init_device(vm)?;
|
||||
|
||||
let device = Self::create_device(vgic_fd, vcpu_count);
|
||||
|
||||
Self::init_device_attributes(&device)?;
|
||||
|
||||
Self::finalize_device(&device)?;
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a GIC device.
|
||||
///
|
||||
/// It will try to create by default a GICv3 device. If that fails it will try
|
||||
/// to fall-back to a GICv2 device.
|
||||
pub fn create_gic(vm: &VmFd, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
|
||||
GICv3::new(vm, vcpu_count).or_else(|_| GICv2::new(vm, vcpu_count))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use kvm_ioctls::Kvm;
|
||||
|
||||
#[test]
|
||||
fn test_create_gic() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
assert!(create_gic(&vm, 1).is_ok());
|
||||
}
|
||||
}
|
||||
114
arch/src/aarch64/gicv2.rs
Normal file
114
arch/src/aarch64/gicv2.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{boxed::Box, result};
|
||||
|
||||
use kvm_ioctls::DeviceFd;
|
||||
|
||||
use super::gic::{Error, GICDevice};
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Represent a GIC v2 device
|
||||
pub struct GICv2 {
|
||||
/// The file descriptor for the KVM device
|
||||
fd: DeviceFd,
|
||||
|
||||
/// GIC device properties, to be used for setting up the fdt entry
|
||||
properties: [u64; 4],
|
||||
|
||||
/// Number of CPUs handled by the device
|
||||
vcpu_count: u64,
|
||||
}
|
||||
|
||||
impl GICv2 {
|
||||
// Unfortunately bindgen omits defines that are based on other defines.
|
||||
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
|
||||
const KVM_VGIC_V2_DIST_SIZE: u64 = 0x1000;
|
||||
const KVM_VGIC_V2_CPU_SIZE: u64 = 0x2000;
|
||||
|
||||
// Device trees specific constants
|
||||
const ARCH_GIC_V2_MAINT_IRQ: u32 = 8;
|
||||
|
||||
/// Get the address of the GICv2 distributor.
|
||||
const fn get_dist_addr() -> u64 {
|
||||
super::layout::MAPPED_IO_START - GICv2::KVM_VGIC_V2_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC_v2 distributor.
|
||||
const fn get_dist_size() -> u64 {
|
||||
GICv2::KVM_VGIC_V2_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the address of the GIC_v2 CPU.
|
||||
const fn get_cpu_addr() -> u64 {
|
||||
GICv2::get_dist_addr() - GICv2::KVM_VGIC_V2_CPU_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC_v2 CPU.
|
||||
const fn get_cpu_size() -> u64 {
|
||||
GICv2::KVM_VGIC_V2_CPU_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for GICv2 {
|
||||
fn version() -> u32 {
|
||||
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V2
|
||||
}
|
||||
|
||||
fn device_fd(&self) -> &DeviceFd {
|
||||
&self.fd
|
||||
}
|
||||
|
||||
fn device_properties(&self) -> &[u64] {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
fn vcpu_count(&self) -> u64 {
|
||||
self.vcpu_count
|
||||
}
|
||||
|
||||
fn fdt_compatibility(&self) -> &str {
|
||||
"arm,gic-400"
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
GICv2::ARCH_GIC_V2_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box<dyn GICDevice> {
|
||||
Box::new(GICv2 {
|
||||
fd: fd,
|
||||
properties: [
|
||||
GICv2::get_dist_addr(),
|
||||
GICv2::get_dist_size(),
|
||||
GICv2::get_cpu_addr(),
|
||||
GICv2::get_cpu_size(),
|
||||
],
|
||||
vcpu_count: vcpu_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(gic_device: &Box<dyn GICDevice>) -> Result<()> {
|
||||
/* Setting up the distributor attribute.
|
||||
We are placing the GIC below 1GB so we need to substract the size of the distributor. */
|
||||
Self::set_device_attribute(
|
||||
&gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_DIST),
|
||||
&GICv2::get_dist_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
/* Setting up the CPU attribute. */
|
||||
Self::set_device_attribute(
|
||||
&gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_CPU),
|
||||
&GICv2::get_cpu_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
117
arch/src/aarch64/gicv3.rs
Normal file
117
arch/src/aarch64/gicv3.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{boxed::Box, result};
|
||||
|
||||
use kvm_ioctls::DeviceFd;
|
||||
|
||||
use super::gic::{Error, GICDevice};
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub struct GICv3 {
|
||||
/// The file descriptor for the KVM device
|
||||
fd: DeviceFd,
|
||||
|
||||
/// GIC device properties, to be used for setting up the fdt entry
|
||||
properties: [u64; 4],
|
||||
|
||||
/// Number of CPUs handled by the device
|
||||
vcpu_count: u64,
|
||||
}
|
||||
|
||||
impl GICv3 {
|
||||
// Unfortunately bindgen omits defines that are based on other defines.
|
||||
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
|
||||
const SZ_64K: u64 = 0x0001_0000;
|
||||
const KVM_VGIC_V3_DIST_SIZE: u64 = GICv3::SZ_64K;
|
||||
const KVM_VGIC_V3_REDIST_SIZE: u64 = (2 * GICv3::SZ_64K);
|
||||
|
||||
// Device trees specific constants
|
||||
const ARCH_GIC_V3_MAINT_IRQ: u32 = 9;
|
||||
|
||||
/// Get the address of the GIC distributor.
|
||||
fn get_dist_addr() -> u64 {
|
||||
super::layout::MAPPED_IO_START - GICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC distributor.
|
||||
fn get_dist_size() -> u64 {
|
||||
GICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the address of the GIC redistributors.
|
||||
fn get_redists_addr(vcpu_count: u64) -> u64 {
|
||||
GICv3::get_dist_addr() - GICv3::get_redists_size(vcpu_count)
|
||||
}
|
||||
|
||||
/// Get the size of the GIC redistributors.
|
||||
fn get_redists_size(vcpu_count: u64) -> u64 {
|
||||
vcpu_count * GICv3::KVM_VGIC_V3_REDIST_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for GICv3 {
|
||||
fn version() -> u32 {
|
||||
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3
|
||||
}
|
||||
|
||||
fn device_fd(&self) -> &DeviceFd {
|
||||
&self.fd
|
||||
}
|
||||
|
||||
fn device_properties(&self) -> &[u64] {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
fn vcpu_count(&self) -> u64 {
|
||||
self.vcpu_count
|
||||
}
|
||||
|
||||
fn fdt_compatibility(&self) -> &str {
|
||||
"arm,gic-v3"
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
GICv3::ARCH_GIC_V3_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn create_device(fd: DeviceFd, vcpu_count: u64) -> Box<dyn GICDevice> {
|
||||
Box::new(GICv3 {
|
||||
fd: fd,
|
||||
properties: [
|
||||
GICv3::get_dist_addr(),
|
||||
GICv3::get_dist_size(),
|
||||
GICv3::get_redists_addr(vcpu_count),
|
||||
GICv3::get_redists_size(vcpu_count),
|
||||
],
|
||||
vcpu_count: vcpu_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(gic_device: &Box<dyn GICDevice>) -> Result<()> {
|
||||
/* Setting up the distributor attribute.
|
||||
We are placing the GIC below 1GB so we need to substract the size of the distributor.
|
||||
*/
|
||||
Self::set_device_attribute(
|
||||
&gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_DIST),
|
||||
&GICv3::get_dist_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
/* Setting up the redistributors' attribute.
|
||||
We are calculating here the start of the redistributors address. We have one per CPU.
|
||||
*/
|
||||
Self::set_device_attribute(
|
||||
&gic_device.device_fd(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_REDIST),
|
||||
&GICv3::get_redists_addr(u64::from(gic_device.vcpu_count())) as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,86 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// Kernel command line start address.
|
||||
pub const CMDLINE_START: usize = 0x0;
|
||||
/// Kernel command line start address maximum size.
|
||||
pub const CMDLINE_MAX_SIZE: usize = 0x0;
|
||||
//
|
||||
// Memory layout of Aarch64 guest:
|
||||
//
|
||||
// Physical +---------------------------------------------------------------+
|
||||
// address | |
|
||||
// end | |
|
||||
// ~ ~ ~ ~
|
||||
// | |
|
||||
// | Highmem PCI MMIO space |
|
||||
// | |
|
||||
// RAM end +---------------------------------------------------------------+
|
||||
// (dynamic, | |
|
||||
// including | |
|
||||
// hotplug ~ ~ ~ ~
|
||||
// memory) | |
|
||||
// | DRAM |
|
||||
// | |
|
||||
// 2GB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | Reserved |
|
||||
// | |
|
||||
// 1G+256M +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | PCI MMCONFIG space |
|
||||
// | |
|
||||
// 1GB +---------------------------------------------------------------+
|
||||
// | |
|
||||
// | PCI MMIO space |
|
||||
// | |
|
||||
// 256 M +---------------------------------------------------------------|
|
||||
// | |
|
||||
// | Legacy devices space |
|
||||
// | |
|
||||
// 144 M +---------------------------------------------------------------|
|
||||
// | |
|
||||
// | Reserverd (now GIC is here) |
|
||||
// | |
|
||||
// 0GB +---------------------------------------------------------------+
|
||||
//
|
||||
//
|
||||
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
|
||||
pub const MAPPED_IO_START: u64 = 0x0900_0000;
|
||||
|
||||
/// Space 0x0900_0000 ~ 0x1000_0000 is reserved for legacy devices.
|
||||
pub const LEGACY_SERIAL_MAPPED_IO_START: u64 = 0x0900_0000;
|
||||
pub const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
||||
|
||||
/// Legacy space will be allocated at once whiling setting up legacy devices.
|
||||
pub const LEGACY_DEVICES_MAPPED_IO_SIZE: u64 = 0x0700_0000;
|
||||
|
||||
/// Starting from 0x1000_0000 (256MiB), the 768MiB (ends at 1 GiB) is used for PCIE MMIO
|
||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = GuestAddress(0x1000_0000);
|
||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x3000_0000;
|
||||
|
||||
/// PCI MMCONFIG space (start: after the device space at 1 GiB, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x4000_0000);
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
|
||||
|
||||
/// Start of RAM on 64 bit ARM.
|
||||
pub const RAM_64BIT_START: u64 = 0x8000_0000;
|
||||
|
||||
/// Kernel command line maximum size.
|
||||
/// As per `arch/arm64/include/uapi/asm/setup.h`.
|
||||
pub const CMDLINE_MAX_SIZE: usize = 2048;
|
||||
|
||||
/// Maximum size of the device tree blob as specified in https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||
pub const FDT_MAX_SIZE: usize = 0x20_0000;
|
||||
|
||||
// As per virt/kvm/arm/vgic/vgic-kvm-device.c we need
|
||||
// the number of interrupts our GIC will support to be:
|
||||
// * bigger than 32
|
||||
// * less than 1023 and
|
||||
// * a multiple of 32.
|
||||
// We are setting up our interrupt controller to support a maximum of 128 interrupts.
|
||||
/// First usable interrupt on aarch64.
|
||||
pub const IRQ_BASE: u32 = 32;
|
||||
|
||||
/// Last usable interrupt on aarch64.
|
||||
pub const IRQ_MAX: u32 = 159;
|
||||
|
||||
@@ -1,27 +1,251 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// Module for the flattened device tree.
|
||||
pub mod fdt;
|
||||
/// Module for the global interrupt controller configuration.
|
||||
pub mod gic;
|
||||
mod gicv2;
|
||||
mod gicv3;
|
||||
/// Layout for this aarch64 system.
|
||||
pub mod layout;
|
||||
/// Logic for configuring aarch64 registers.
|
||||
pub mod regs;
|
||||
|
||||
use memory_model::{GuestAddress, GuestMemory};
|
||||
pub use self::fdt::DeviceInfoForFDT;
|
||||
use crate::DeviceType;
|
||||
use crate::RegionType;
|
||||
use kvm_ioctls::*;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Debug;
|
||||
use vm_memory::{
|
||||
Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, GuestMemoryMmap,
|
||||
GuestUsize,
|
||||
};
|
||||
|
||||
/// Stub function that needs to be implemented when aarch64 functionality is added.
|
||||
pub fn arch_memory_regions(size: usize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
vec![(GuestAddress(0), size, RegionType::Ram)]
|
||||
/// Errors thrown while configuring aarch64 system.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to create a FDT.
|
||||
SetupFDT(fdt::Error),
|
||||
|
||||
/// Failed to create a GIC.
|
||||
SetupGIC(gic::Error),
|
||||
|
||||
/// Failed to compute the initrd address.
|
||||
InitrdAddress,
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
REGSConfiguration(regs::Error),
|
||||
|
||||
/// Error fetching prefered target
|
||||
VcpuArmPreferredTarget(kvm_ioctls::Error),
|
||||
|
||||
/// Error doing Vcpu Init on Arm.
|
||||
VcpuArmInit(kvm_ioctls::Error),
|
||||
}
|
||||
|
||||
/// Stub function that needs to be implemented when aarch64 functionality is added.
|
||||
pub fn configure_system(
|
||||
_guest_mem: &GuestMemory,
|
||||
_cmdline_addr: GuestAddress,
|
||||
_cmdline_size: usize,
|
||||
_num_cpus: u8,
|
||||
_rsdp_addr: Option<GuestAddress>,
|
||||
impl From<Error> for super::Error {
|
||||
fn from(e: Error) -> super::Error {
|
||||
super::Error::AArch64Setup(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
/// Specifies the entry point address where the guest must start
|
||||
/// executing code.
|
||||
pub struct EntryPoint {
|
||||
/// Address in guest memory where the guest must start execution
|
||||
pub entry_addr: GuestAddress,
|
||||
}
|
||||
|
||||
/// Configure the specified VCPU, and return its MPIDR.
|
||||
pub fn configure_vcpu(
|
||||
fd: &VcpuFd,
|
||||
id: u8,
|
||||
vm_fd: &VmFd,
|
||||
kernel_entry_point: Option<EntryPoint>,
|
||||
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
) -> super::Result<u64> {
|
||||
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
|
||||
|
||||
// This reads back the kernel's preferred target type.
|
||||
vm_fd
|
||||
.get_preferred_target(&mut kvi)
|
||||
.map_err(Error::VcpuArmPreferredTarget)?;
|
||||
// We already checked that the capability is supported.
|
||||
kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_PSCI_0_2;
|
||||
// Non-boot cpus are powered off initially.
|
||||
if id > 0 {
|
||||
kvi.features[0] |= 1 << kvm_bindings::KVM_ARM_VCPU_POWER_OFF;
|
||||
}
|
||||
|
||||
fd.vcpu_init(&kvi).map_err(Error::VcpuArmInit)?;
|
||||
if let Some(kernel_entry_point) = kernel_entry_point {
|
||||
regs::setup_regs(
|
||||
fd,
|
||||
id,
|
||||
kernel_entry_point.entry_addr.raw_value(),
|
||||
&vm_memory.memory(),
|
||||
)
|
||||
.map_err(Error::REGSConfiguration)?;
|
||||
}
|
||||
|
||||
let mpidr = regs::read_mpidr(fd).map_err(Error::REGSConfiguration)?;
|
||||
Ok(mpidr)
|
||||
}
|
||||
|
||||
pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, RegionType)> {
|
||||
let mut regions = Vec::new();
|
||||
// 0 ~ 256 MiB: Reserved
|
||||
regions.push((
|
||||
GuestAddress(0),
|
||||
layout::MEM_32BIT_DEVICES_START.0 as usize,
|
||||
RegionType::Reserved,
|
||||
));
|
||||
|
||||
// 256 MiB ~ 1 G: MMIO space
|
||||
regions.push((
|
||||
layout::MEM_32BIT_DEVICES_START,
|
||||
layout::MEM_32BIT_DEVICES_SIZE as usize,
|
||||
RegionType::SubRegion,
|
||||
));
|
||||
|
||||
// 1G ~ 2G: reserved. The leading 256M for PCIe MMCONFIG space
|
||||
regions.push((
|
||||
layout::PCI_MMCONFIG_START,
|
||||
(layout::RAM_64BIT_START - layout::PCI_MMCONFIG_START.0) as usize,
|
||||
RegionType::Reserved,
|
||||
));
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
size as usize,
|
||||
RegionType::Ram,
|
||||
));
|
||||
|
||||
regions
|
||||
}
|
||||
|
||||
/// Configures the system and should be called once per vm before starting vcpu threads.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `guest_mem` - The memory to be used by the guest.
|
||||
/// * `num_cpus` - Number of virtual CPUs the guest will have.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(unused_variables)]
|
||||
pub fn configure_system<T: DeviceInfoForFDT + Clone + Debug>(
|
||||
vm_fd: &VmFd,
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_cstring: &CStr,
|
||||
vcpu_count: u64,
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
device_info: &HashMap<(DeviceType, String), T>,
|
||||
initrd: &Option<super::InitramfsConfig>,
|
||||
) -> super::Result<()> {
|
||||
let gic_device = gic::create_gic(vm_fd, vcpu_count).map_err(Error::SetupGIC)?;
|
||||
|
||||
let dtb = fdt::create_fdt(
|
||||
guest_mem,
|
||||
cmdline_cstring,
|
||||
vcpu_mpidr,
|
||||
device_info,
|
||||
&gic_device,
|
||||
initrd,
|
||||
)
|
||||
.map_err(Error::SetupFDT)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stub function that needs to be implemented when aarch64 functionality is added.
|
||||
pub fn get_reserved_mem_addr() -> usize {
|
||||
0
|
||||
/// Returns the memory address where the kernel could be loaded.
|
||||
pub fn get_kernel_start() -> u64 {
|
||||
layout::RAM_64BIT_START
|
||||
}
|
||||
|
||||
// Auxiliary function to get the address where the device tree blob is loaded.
|
||||
fn get_fdt_addr(mem: &GuestMemoryMmap) -> u64 {
|
||||
// If the memory allocated is smaller than the size allocated for the FDT,
|
||||
// we return the start of the DRAM so that
|
||||
// we allow the code to try and load the FDT.
|
||||
|
||||
if let Some(addr) = mem.last_addr().checked_sub(layout::FDT_MAX_SIZE as u64 - 1) {
|
||||
if mem.address_in_range(addr) {
|
||||
return addr.raw_value();
|
||||
}
|
||||
}
|
||||
|
||||
layout::RAM_64BIT_START
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||
// The value returned here is used to determine the physical address space size
|
||||
// for a VM (IPA size).
|
||||
// In recent kernel versions, the maxium IPA size supported by the host can be
|
||||
// known by querying cap KVM_CAP_ARM_VM_IPA_SIZE. And the IPA size for a
|
||||
// guest can be configured smaller.
|
||||
// But in Cloud-Hypervisor we simply use the maxium value for the VM.
|
||||
// Reference https://lwn.net/Articles/766767/.
|
||||
//
|
||||
// The correct way to query KVM_CAP_ARM_VM_IPA_SIZE is via rust-vmm/kvm-ioctls,
|
||||
// which wraps all IOCTL's and provides easy interface to user hypervisors.
|
||||
// For now the cap hasn't been supported. A separate patch will be submitted to
|
||||
// rust-vmm to add it.
|
||||
// So a hardcoded value is used here as a temporary solution.
|
||||
// It will be replace once rust-vmm/kvm-ioctls is ready.
|
||||
//
|
||||
40
|
||||
}
|
||||
|
||||
pub fn check_required_kvm_extensions(kvm: &Kvm) -> super::Result<()> {
|
||||
if !kvm.check_extension(Cap::SignalMsi) {
|
||||
return Err(super::Error::CapabilityMissing(Cap::SignalMsi));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_arch_memory_regions_dram() {
|
||||
let regions = arch_memory_regions((1usize << 32) as u64); //4GB
|
||||
assert_eq!(4, regions.len());
|
||||
assert_eq!(GuestAddress(layout::RAM_64BIT_START), regions[3].0);
|
||||
assert_eq!(1usize << 32, regions[3].1);
|
||||
assert_eq!(RegionType::Ram, regions[3].2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_fdt_addr() {
|
||||
let mut regions = Vec::new();
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(layout::FDT_MAX_SIZE - 0x1000) as usize,
|
||||
));
|
||||
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START);
|
||||
regions.clear();
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(layout::FDT_MAX_SIZE) as usize,
|
||||
));
|
||||
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
assert_eq!(get_fdt_addr(&mem), layout::RAM_64BIT_START);
|
||||
regions.clear();
|
||||
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(layout::FDT_MAX_SIZE + 0x1000) as usize,
|
||||
));
|
||||
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
assert_eq!(get_fdt_addr(&mem), 0x1000 + layout::RAM_64BIT_START);
|
||||
regions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
198
arch/src/aarch64/regs.rs
Normal file
198
arch/src/aarch64/regs.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use std::{mem, result};
|
||||
|
||||
use super::get_fdt_addr;
|
||||
use kvm_bindings::{
|
||||
user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM64_SYSREG, KVM_REG_ARM64_SYSREG_CRM_MASK,
|
||||
KVM_REG_ARM64_SYSREG_CRM_SHIFT, KVM_REG_ARM64_SYSREG_CRN_MASK, KVM_REG_ARM64_SYSREG_CRN_SHIFT,
|
||||
KVM_REG_ARM64_SYSREG_OP0_MASK, KVM_REG_ARM64_SYSREG_OP0_SHIFT, KVM_REG_ARM64_SYSREG_OP1_MASK,
|
||||
KVM_REG_ARM64_SYSREG_OP1_SHIFT, KVM_REG_ARM64_SYSREG_OP2_MASK, KVM_REG_ARM64_SYSREG_OP2_SHIFT,
|
||||
KVM_REG_ARM_CORE, KVM_REG_SIZE_U64,
|
||||
};
|
||||
use kvm_ioctls::VcpuFd;
|
||||
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
|
||||
/// Errors thrown while setting aarch64 registers.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failed to set core register (PC, PSTATE or general purpose ones).
|
||||
SetCoreRegister(kvm_ioctls::Error),
|
||||
/// Failed to get a system register.
|
||||
GetSysRegister(kvm_ioctls::Error),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
// PSR (Processor State Register) bits.
|
||||
// Taken from arch/arm64/include/uapi/asm/ptrace.h.
|
||||
const PSR_MODE_EL1h: u64 = 0x0000_0005;
|
||||
const PSR_F_BIT: u64 = 0x0000_0040;
|
||||
const PSR_I_BIT: u64 = 0x0000_0080;
|
||||
const PSR_A_BIT: u64 = 0x0000_0100;
|
||||
const PSR_D_BIT: u64 = 0x0000_0200;
|
||||
// Taken from arch/arm64/kvm/inject_fault.c.
|
||||
const PSTATE_FAULT_BITS_64: u64 = PSR_MODE_EL1h | PSR_A_BIT | PSR_F_BIT | PSR_I_BIT | PSR_D_BIT;
|
||||
|
||||
// Following are macros that help with getting the ID of a aarch64 core register.
|
||||
// The core register are represented by the user_pt_regs structure. Look for it in
|
||||
// arch/arm64/include/uapi/asm/ptrace.h.
|
||||
|
||||
// This macro gets the offset of a structure (i.e `str`) member (i.e `field`) without having
|
||||
// an instance of that structure.
|
||||
// It uses a null pointer to retrieve the offset to the field.
|
||||
// Inspired by C solution: `#define offsetof(str, f) ((size_t)(&((str *)0)->f))`.
|
||||
// Doing `offset__of!(user_pt_regs, pstate)` in our rust code will trigger the following:
|
||||
// unsafe { &(*(0 as *const user_pt_regs)).pstate as *const _ as usize }
|
||||
// The dereference expression produces an lvalue, but that lvalue is not actually read from,
|
||||
// we're just doing pointer math on it, so in theory, it should safe.
|
||||
macro_rules! offset__of {
|
||||
($str:ty, $field:ident) => {
|
||||
unsafe { &(*(0 as *const $str)).$field as *const _ as usize }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! arm64_core_reg {
|
||||
($reg: tt) => {
|
||||
// As per `kvm_arm_copy_reg_indices`, the id of a core register can be obtained like this:
|
||||
// `const u64 core_reg = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE | i`, where i is obtained with:
|
||||
// `for (i = 0; i < sizeof(struct kvm_regs) / sizeof(__u32); i++) {`
|
||||
// We are using here `user_pt_regs` since this structure contains the core register and it is at
|
||||
// the start of `kvm_regs`.
|
||||
// struct kvm_regs {
|
||||
// struct user_pt_regs regs; /* sp = sp_el0 */
|
||||
//
|
||||
// __u64 sp_el1;
|
||||
// __u64 elr_el1;
|
||||
//
|
||||
// __u64 spsr[KVM_NR_SPSR];
|
||||
//
|
||||
// struct user_fpsimd_state fp_regs;
|
||||
//};
|
||||
// struct user_pt_regs {
|
||||
// __u64 regs[31];
|
||||
// __u64 sp;
|
||||
// __u64 pc;
|
||||
// __u64 pstate;
|
||||
//};
|
||||
// In our implementation we need: pc, pstate and user_pt_regs->regs[0].
|
||||
KVM_REG_ARM64 as u64
|
||||
| KVM_REG_SIZE_U64 as u64
|
||||
| u64::from(KVM_REG_ARM_CORE)
|
||||
| ((offset__of!(user_pt_regs, $reg) / mem::size_of::<u32>()) as u64)
|
||||
};
|
||||
}
|
||||
|
||||
// This macro computes the ID of a specific ARM64 system register similar to how
|
||||
// the kernel C macro does.
|
||||
// https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/uapi/asm/kvm.h#L203
|
||||
macro_rules! arm64_sys_reg {
|
||||
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
|
||||
const $name: u64 = KVM_REG_ARM64 as u64
|
||||
| KVM_REG_SIZE_U64 as u64
|
||||
| KVM_REG_ARM64_SYSREG as u64
|
||||
| ((($op0 as u64) << KVM_REG_ARM64_SYSREG_OP0_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_OP0_MASK as u64)
|
||||
| ((($op1 as u64) << KVM_REG_ARM64_SYSREG_OP1_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_OP1_MASK as u64)
|
||||
| ((($crn as u64) << KVM_REG_ARM64_SYSREG_CRN_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_CRN_MASK as u64)
|
||||
| ((($crm as u64) << KVM_REG_ARM64_SYSREG_CRM_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_CRM_MASK as u64)
|
||||
| ((($op2 as u64) << KVM_REG_ARM64_SYSREG_OP2_SHIFT)
|
||||
& KVM_REG_ARM64_SYSREG_OP2_MASK as u64);
|
||||
};
|
||||
}
|
||||
|
||||
// Constant imported from the Linux kernel:
|
||||
// https://elixir.bootlin.com/linux/v4.20.17/source/arch/arm64/include/asm/sysreg.h#L135
|
||||
arm64_sys_reg!(MPIDR_EL1, 3, 0, 0, 0, 5);
|
||||
|
||||
/// Configure core registers for a given CPU.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
/// * `cpu_id` - Index of current vcpu.
|
||||
/// * `boot_ip` - Starting instruction pointer.
|
||||
/// * `mem` - Reserved DRAM for current VM.
|
||||
pub fn setup_regs(vcpu: &VcpuFd, cpu_id: u8, boot_ip: u64, mem: &GuestMemoryMmap) -> Result<()> {
|
||||
// Get the register index of the PSTATE (Processor State) register.
|
||||
vcpu.set_one_reg(arm64_core_reg!(pstate), PSTATE_FAULT_BITS_64)
|
||||
.map_err(Error::SetCoreRegister)?;
|
||||
|
||||
// Other vCPUs are powered off initially awaiting PSCI wakeup.
|
||||
if cpu_id == 0 {
|
||||
// Setting the PC (Processor Counter) to the current program address (kernel address).
|
||||
vcpu.set_one_reg(arm64_core_reg!(pc), boot_ip)
|
||||
.map_err(Error::SetCoreRegister)?;
|
||||
|
||||
// Last mandatory thing to set -> the address pointing to the FDT (also called DTB).
|
||||
// "The device tree blob (dtb) must be placed on an 8-byte boundary and must
|
||||
// not exceed 2 megabytes in size." -> https://www.kernel.org/doc/Documentation/arm64/booting.txt.
|
||||
// We are choosing to place it the end of DRAM. See `get_fdt_addr`.
|
||||
vcpu.set_one_reg(arm64_core_reg!(regs), get_fdt_addr(mem) as u64)
|
||||
.map_err(Error::SetCoreRegister)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read the MPIDR - Multiprocessor Affinity Register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn read_mpidr(vcpu: &VcpuFd) -> Result<u64> {
|
||||
vcpu.get_one_reg(MPIDR_EL1).map_err(Error::GetSysRegister)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::aarch64::layout;
|
||||
use kvm_ioctls::Kvm;
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
#[test]
|
||||
fn test_setup_regs() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let mut regions = Vec::new();
|
||||
regions.push((
|
||||
GuestAddress(layout::RAM_64BIT_START),
|
||||
(layout::FDT_MAX_SIZE + 0x1000) as usize,
|
||||
));
|
||||
let mem = GuestMemoryMmap::from_ranges(®ions).expect("Cannot initialize memory");
|
||||
|
||||
match setup_regs(&vcpu, 0, 0x0, &mem).unwrap_err() {
|
||||
Error::SetCoreRegister(ref e) => assert_eq!(e.errno(), libc::ENOEXEC),
|
||||
_ => panic!("Expected to receive Error::SetCoreRegister"),
|
||||
}
|
||||
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
|
||||
vm.get_preferred_target(&mut kvi).unwrap();
|
||||
vcpu.vcpu_init(&kvi).unwrap();
|
||||
|
||||
assert!(setup_regs(&vcpu, 0, 0x0, &mem).is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_read_mpidr() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let vcpu = vm.create_vcpu(0).unwrap();
|
||||
let mut kvi: kvm_bindings::kvm_vcpu_init = kvm_bindings::kvm_vcpu_init::default();
|
||||
vm.get_preferred_target(&mut kvi).unwrap();
|
||||
|
||||
// Must fail when vcpu is not initialized yet.
|
||||
assert!(read_mpidr(&vcpu).is_err());
|
||||
|
||||
vcpu.vcpu_init(&kvi).unwrap();
|
||||
assert_eq!(read_mpidr(&vcpu).unwrap(), 0x80000000);
|
||||
}
|
||||
}
|
||||
106
arch/src/lib.rs
106
arch/src/lib.rs
@@ -1,6 +1,11 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Implements platform specific functionality.
|
||||
//! Supported platforms: x86_64, aarch64.
|
||||
#![allow(
|
||||
clippy::unreadable_literal,
|
||||
clippy::redundant_static_lifetimes,
|
||||
@@ -11,30 +16,56 @@
|
||||
|
||||
extern crate byteorder;
|
||||
extern crate kvm_bindings;
|
||||
extern crate kvm_ioctls;
|
||||
extern crate libc;
|
||||
|
||||
extern crate vm_memory;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
extern crate acpi_tables;
|
||||
extern crate arch_gen;
|
||||
extern crate kvm_ioctls;
|
||||
extern crate linux_loader;
|
||||
extern crate vm_memory;
|
||||
|
||||
use kvm_ioctls::*;
|
||||
use std::fmt;
|
||||
use std::result;
|
||||
|
||||
/// Type for returning error code.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
/// X86_64 specific error triggered during system configuration.
|
||||
X86_64Setup(x86_64::Error),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
/// AArch64 specific error triggered during system configuration.
|
||||
AArch64Setup(aarch64::Error),
|
||||
/// The zero page extends past the end of guest_mem.
|
||||
ZeroPagePastRamEnd,
|
||||
/// Error writing the zero page of guest memory.
|
||||
ZeroPageSetup(vm_memory::GuestMemoryError),
|
||||
/// The memory map table extends past the end of guest memory.
|
||||
MemmapTablePastRamEnd,
|
||||
/// Error writing memory map table to guest memory.
|
||||
MemmapTableSetup,
|
||||
/// The hvm_start_info structure extends past the end of guest memory.
|
||||
StartInfoPastRamEnd,
|
||||
/// Error writing hvm_start_info to guest memory.
|
||||
StartInfoSetup,
|
||||
/// Failed to compute initramfs address.
|
||||
InitramfsAddress,
|
||||
/// Error writing module entry to guest memory.
|
||||
ModlistSetup(vm_memory::GuestMemoryError),
|
||||
/// RSDP Beyond Guest Memory
|
||||
RSDPPastRamEnd,
|
||||
/// Capability missing
|
||||
CapabilityMissing(Cap),
|
||||
}
|
||||
|
||||
/// Type for returning public functions outcome.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
/// Type for memory region types.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum RegionType {
|
||||
/// RAM type
|
||||
Ram,
|
||||
@@ -52,13 +83,15 @@ pub enum RegionType {
|
||||
Reserved,
|
||||
}
|
||||
|
||||
/// Module for aarch64 related functionality.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use aarch64::{
|
||||
arch_memory_regions, configure_system, get_reserved_mem_addr, layout::CMDLINE_MAX_SIZE,
|
||||
layout::CMDLINE_START,
|
||||
arch_memory_regions, check_required_kvm_extensions, configure_system, configure_vcpu,
|
||||
fdt::DeviceInfoForFDT, get_host_cpu_phys_bits, get_kernel_start, layout,
|
||||
layout::CMDLINE_MAX_SIZE, layout::IRQ_BASE, layout::IRQ_MAX, EntryPoint,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -66,5 +99,66 @@ pub mod x86_64;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
||||
arch_memory_regions, check_required_kvm_extensions, configure_system, configure_vcpu,
|
||||
get_host_cpu_phys_bits, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE,
|
||||
layout::CMDLINE_START, regs, BootProtocol, CpuidPatch, CpuidReg, EntryPoint,
|
||||
};
|
||||
|
||||
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[inline(always)]
|
||||
fn pagesize() -> usize {
|
||||
// Trivially safe
|
||||
unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
|
||||
}
|
||||
|
||||
/// Type for passing information about the initramfs in the guest memory.
|
||||
pub struct InitramfsConfig {
|
||||
/// Load address of initramfs in guest memory
|
||||
pub address: vm_memory::GuestAddress,
|
||||
/// Size of initramfs in guest memory
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
/// Types of devices that can get attached to this platform.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
|
||||
pub enum DeviceType {
|
||||
/// Device Type: Virtio.
|
||||
Virtio(u32),
|
||||
/// Device Type: Serial.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
Serial,
|
||||
/// Device Type: RTC.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
RTC,
|
||||
}
|
||||
|
||||
/// Default (smallest) memory page size for the supported architectures.
|
||||
pub const PAGE_SIZE: usize = 4096;
|
||||
|
||||
impl fmt::Display for DeviceType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure to describe MMIO device information
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub struct MMIODeviceInfo {
|
||||
pub addr: u64,
|
||||
pub irq: u32,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
impl DeviceInfoForFDT for MMIODeviceInfo {
|
||||
fn addr(&self) -> u64 {
|
||||
self.addr
|
||||
}
|
||||
fn irq(&self) -> u32 {
|
||||
self.irq
|
||||
}
|
||||
fn length(&self) -> u64 {
|
||||
4096 as u64
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -11,21 +13,47 @@ use kvm_bindings::kvm_segment;
|
||||
|
||||
/// Constructor for a conventional segment GDT (or LDT) entry. Derived from the kernel's segment.h.
|
||||
pub fn gdt_entry(flags: u16, base: u32, limit: u32) -> u64 {
|
||||
((((base as u64) & 0xff000000u64) << (56 - 24))
|
||||
(((base as u64) & 0xff000000u64) << (56 - 24))
|
||||
| (((flags as u64) & 0x0000f0ffu64) << 40)
|
||||
| (((limit as u64) & 0x000f0000u64) << (48 - 16))
|
||||
| (((base as u64) & 0x00ffffffu64) << 16)
|
||||
| ((limit as u64) & 0x0000ffffu64))
|
||||
| ((limit as u64) & 0x0000ffffu64)
|
||||
}
|
||||
|
||||
fn get_base(entry: u64) -> u64 {
|
||||
((((entry) & 0xFF00000000000000) >> 32)
|
||||
(((entry) & 0xFF00000000000000) >> 32)
|
||||
| (((entry) & 0x000000FF00000000) >> 16)
|
||||
| (((entry) & 0x00000000FFFF0000) >> 16))
|
||||
| (((entry) & 0x00000000FFFF0000) >> 16)
|
||||
}
|
||||
|
||||
// Extract the segment limit from the GDT segment descriptor.
|
||||
//
|
||||
// In a segment descriptor, the limit field is 20 bits, so it can directly describe
|
||||
// a range from 0 to 0xFFFFF (1MByte). When G flag is set (4-KByte page granularity) it
|
||||
// scales the value in the limit field by a factor of 2^12 (4Kbytes), making the effective
|
||||
// limit range from 0xFFF (4 KBytes) to 0xFFFF_FFFF (4 GBytes).
|
||||
//
|
||||
// However, the limit field in the VMCS definition is a 32 bit field, and the limit value is not
|
||||
// automatically scaled using the G flag. This means that for a desired range of 4GB for a
|
||||
// given segment, its limit must be specified as 0xFFFF_FFFF. Therefore the method of obtaining
|
||||
// the limit from the GDT entry is not sufficient, since it only provides 20 bits when 32 bits
|
||||
// are necessary. Fortunately, we can check if the G flag is set when extracting the limit since
|
||||
// the full GDT entry is passed as an argument, and perform the scaling of the limit value to
|
||||
// return the full 32 bit value.
|
||||
//
|
||||
// The scaling mentioned above is required when using PVH boot, since the guest boots in protected
|
||||
// (32-bit) mode and must be able to access the entire 32-bit address space. It does not cause issues
|
||||
// for the case of direct boot to 64-bit (long) mode, since in 64-bit mode the processor does not
|
||||
// perform runtime limit checking on code or data segments.
|
||||
fn get_limit(entry: u64) -> u32 {
|
||||
((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32
|
||||
let limit: u32 =
|
||||
((((entry) & 0x000F000000000000) >> 32) | ((entry) & 0x000000000000FFFF)) as u32;
|
||||
|
||||
// Perform manual limit scaling if G flag is set
|
||||
match get_g(entry) {
|
||||
0 => limit,
|
||||
_ => ((limit << 12) | 0xFFF), // G flag is either 0 or 1
|
||||
}
|
||||
}
|
||||
|
||||
fn get_g(entry: u64) -> u8 {
|
||||
@@ -109,7 +137,7 @@ mod tests {
|
||||
assert_eq!(0xB, seg.type_);
|
||||
// base and limit
|
||||
assert_eq!(0x100000, seg.base);
|
||||
assert_eq!(0xfffff, seg.limit);
|
||||
assert_eq!(0xffffffff, seg.limit);
|
||||
assert_eq!(0x0, seg.unusable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ fn set_klapic_reg(klapic: &mut kvm_lapic_state, reg_offset: usize, value: u32) {
|
||||
}
|
||||
|
||||
fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
|
||||
(((reg) & !0x700) | ((mode) << 8))
|
||||
((reg) & !0x700) | ((mode) << 8)
|
||||
}
|
||||
|
||||
/// Configures LAPICs. LAPIC0 is set for external interrupts, LAPIC1 is set for NMI.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -26,6 +28,17 @@ pub const LOW_RAM_START: GuestAddress = GuestAddress(0x0);
|
||||
pub const BOOT_GDT_START: GuestAddress = GuestAddress(0x500);
|
||||
pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
||||
|
||||
/// Address for the hvm_start_info struct used in PVH boot
|
||||
pub const PVH_INFO_START: GuestAddress = GuestAddress(0x6000);
|
||||
|
||||
/// Starting address of array of modules of hvm_modlist_entry type.
|
||||
/// Used to enable initrd support using the PVH boot ABI.
|
||||
pub const MODLIST_START: GuestAddress = GuestAddress(0x6040);
|
||||
|
||||
/// Address of memory map table used in PVH boot. Can overlap
|
||||
/// with the zero page address since they are mutually exclusive.
|
||||
pub const MEMMAP_START: GuestAddress = GuestAddress(0x7000);
|
||||
|
||||
/// The 'zero page', a.k.a linux kernel bootparams.
|
||||
pub const ZERO_PAGE_START: GuestAddress = GuestAddress(0x7000);
|
||||
|
||||
@@ -34,9 +47,10 @@ pub const BOOT_STACK_START: GuestAddress = GuestAddress(0x8000);
|
||||
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);
|
||||
pub const PML5_START: GuestAddress = GuestAddress(0x9000);
|
||||
pub const PML4_START: GuestAddress = GuestAddress(0xa000);
|
||||
pub const PDPTE_START: GuestAddress = GuestAddress(0xb000);
|
||||
pub const PDE_START: GuestAddress = GuestAddress(0xc000);
|
||||
|
||||
/// Kernel command line start address.
|
||||
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
||||
@@ -65,18 +79,18 @@ pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
|
||||
|
||||
// ** 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);
|
||||
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);
|
||||
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);
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
|
||||
|
||||
// IOAPIC
|
||||
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -8,19 +10,75 @@
|
||||
mod gdt;
|
||||
pub mod interrupts;
|
||||
pub mod layout;
|
||||
#[cfg(not(feature = "acpi"))]
|
||||
mod mptable;
|
||||
pub mod regs;
|
||||
|
||||
use crate::InitramfsConfig;
|
||||
use crate::RegionType;
|
||||
use kvm_bindings::CpuId;
|
||||
use kvm_ioctls::*;
|
||||
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||
use linux_loader::loader::elf::start_info::{
|
||||
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
||||
};
|
||||
use std::mem;
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||
GuestMemoryMmap, GuestMemoryRegion, GuestUsize,
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum BootProtocol {
|
||||
LinuxBoot,
|
||||
PvhBoot,
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for BootProtocol {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
match self {
|
||||
BootProtocol::LinuxBoot => write!(f, "Linux 64-bit boot protocol"),
|
||||
BootProtocol::PvhBoot => write!(f, "PVH boot protocol"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
/// Specifies the entry point address where the guest must start
|
||||
/// executing code, as well as which of the supported boot protocols
|
||||
/// is to be used to configure the guest initial state.
|
||||
pub struct EntryPoint {
|
||||
/// Address in guest memory where the guest must start execution
|
||||
pub entry_addr: GuestAddress,
|
||||
/// Specifies which boot protocol to use
|
||||
pub protocol: BootProtocol,
|
||||
/// This field is used for bzImage to fill zero page
|
||||
pub setup_header: Option<setup_header>,
|
||||
}
|
||||
|
||||
const E820_RAM: u32 = 1;
|
||||
const E820_RESERVED: u32 = 2;
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
// * all of the parameters being passed to the trait (if there are any) are also foreign
|
||||
// is prohibited.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct StartInfoWrapper(hvm_start_info);
|
||||
|
||||
// It is safe to initialize StartInfoWrapper which is a wrapper over `hvm_start_info` (a series of ints).
|
||||
unsafe impl ByteValued for StartInfoWrapper {}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct MemmapTableEntryWrapper(hvm_memmap_table_entry);
|
||||
|
||||
unsafe impl ByteValued for MemmapTableEntryWrapper {}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct ModlistEntryWrapper(hvm_modlist_entry);
|
||||
|
||||
unsafe impl ByteValued for ModlistEntryWrapper {}
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `DataInit`) where:
|
||||
// * the type that is implementing the trait is foreign or
|
||||
@@ -36,8 +94,27 @@ unsafe impl ByteValued for BootParamsWrapper {}
|
||||
pub enum Error {
|
||||
/// Invalid e820 setup params.
|
||||
E820Configuration,
|
||||
#[cfg(not(feature = "acpi"))]
|
||||
/// Error writing MP table to memory.
|
||||
MpTableSetup(mptable::Error),
|
||||
|
||||
/// Error configuring the general purpose registers
|
||||
REGSConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the special registers
|
||||
SREGSConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the floating point related registers
|
||||
FPUConfiguration(regs::Error),
|
||||
|
||||
/// Error configuring the MSR registers
|
||||
MSRSConfiguration(regs::Error),
|
||||
|
||||
/// The call to KVM_SET_CPUID2 failed.
|
||||
SetSupportedCpusFailed(kvm_ioctls::Error),
|
||||
|
||||
/// Cannot set the local interruption due to bad configuration.
|
||||
LocalIntConfiguration(interrupts::Error),
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
@@ -46,6 +123,113 @@ impl From<Error> for super::Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum CpuidReg {
|
||||
EAX,
|
||||
EBX,
|
||||
ECX,
|
||||
EDX,
|
||||
}
|
||||
|
||||
pub struct CpuidPatch {
|
||||
pub function: u32,
|
||||
pub index: u32,
|
||||
pub flags_bit: Option<u8>,
|
||||
pub eax_bit: Option<u8>,
|
||||
pub ebx_bit: Option<u8>,
|
||||
pub ecx_bit: Option<u8>,
|
||||
pub edx_bit: Option<u8>,
|
||||
}
|
||||
|
||||
impl CpuidPatch {
|
||||
pub fn set_cpuid_reg(
|
||||
cpuid: &mut CpuId,
|
||||
function: u32,
|
||||
index: Option<u32>,
|
||||
reg: CpuidReg,
|
||||
value: u32,
|
||||
) {
|
||||
let entries = cpuid.as_mut_slice();
|
||||
|
||||
for entry in entries.iter_mut() {
|
||||
if entry.function == function && (index == None || index.unwrap() == entry.index) {
|
||||
match reg {
|
||||
CpuidReg::EAX => {
|
||||
entry.eax = value;
|
||||
}
|
||||
CpuidReg::EBX => {
|
||||
entry.ebx = value;
|
||||
}
|
||||
CpuidReg::ECX => {
|
||||
entry.ecx = value;
|
||||
}
|
||||
CpuidReg::EDX => {
|
||||
entry.edx = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn patch_cpuid(cpuid: &mut CpuId, patches: Vec<CpuidPatch>) {
|
||||
let entries = cpuid.as_mut_slice();
|
||||
|
||||
for entry in entries.iter_mut() {
|
||||
for patch in patches.iter() {
|
||||
if entry.function == patch.function && entry.index == patch.index {
|
||||
if let Some(flags_bit) = patch.flags_bit {
|
||||
entry.flags |= 1 << flags_bit;
|
||||
}
|
||||
if let Some(eax_bit) = patch.eax_bit {
|
||||
entry.eax |= 1 << eax_bit;
|
||||
}
|
||||
if let Some(ebx_bit) = patch.ebx_bit {
|
||||
entry.ebx |= 1 << ebx_bit;
|
||||
}
|
||||
if let Some(ecx_bit) = patch.ecx_bit {
|
||||
entry.ecx |= 1 << ecx_bit;
|
||||
}
|
||||
if let Some(edx_bit) = patch.edx_bit {
|
||||
entry.edx |= 1 << edx_bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure_vcpu(
|
||||
fd: &VcpuFd,
|
||||
id: u8,
|
||||
kernel_entry_point: Option<EntryPoint>,
|
||||
vm_memory: &GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
cpuid: CpuId,
|
||||
) -> super::Result<()> {
|
||||
let mut cpuid = cpuid;
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, u32::from(id));
|
||||
fd.set_cpuid2(&cpuid)
|
||||
.map_err(Error::SetSupportedCpusFailed)?;
|
||||
|
||||
regs::setup_msrs(fd).map_err(Error::MSRSConfiguration)?;
|
||||
if let Some(kernel_entry_point) = kernel_entry_point {
|
||||
// Safe to unwrap because this method is called after the VM is configured
|
||||
regs::setup_regs(
|
||||
fd,
|
||||
kernel_entry_point.entry_addr.raw_value(),
|
||||
layout::BOOT_STACK_POINTER.raw_value(),
|
||||
layout::ZERO_PAGE_START.raw_value(),
|
||||
kernel_entry_point.protocol,
|
||||
)
|
||||
.map_err(Error::REGSConfiguration)?;
|
||||
regs::setup_fpu(fd).map_err(Error::FPUConfiguration)?;
|
||||
regs::setup_sregs(&vm_memory.memory(), fd, kernel_entry_point.protocol)
|
||||
.map_err(Error::SREGSConfiguration)?;
|
||||
}
|
||||
interrupts::set_lint(fd).map_err(Error::LocalIntConfiguration)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a Vec of the valid memory addresses.
|
||||
/// 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
|
||||
@@ -106,7 +290,184 @@ pub fn configure_system(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
num_cpus: u8,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
_num_cpus: u8,
|
||||
setup_hdr: Option<setup_header>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
boot_prot: BootProtocol,
|
||||
) -> super::Result<()> {
|
||||
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
||||
#[cfg(not(feature = "acpi"))]
|
||||
mptable::setup_mptable(guest_mem, _num_cpus).map_err(Error::MpTableSetup)?;
|
||||
|
||||
// Check that the RAM is not smaller than the RSDP start address
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
if rsdp_addr.0 > guest_mem.last_addr().0 {
|
||||
return Err(super::Error::RSDPPastRamEnd);
|
||||
}
|
||||
}
|
||||
|
||||
match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
configure_pvh(guest_mem, cmdline_addr, initramfs, rsdp_addr)?;
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
configure_64bit_boot(
|
||||
guest_mem,
|
||||
cmdline_addr,
|
||||
cmdline_size,
|
||||
initramfs,
|
||||
setup_hdr,
|
||||
rsdp_addr,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_pvh(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
) -> super::Result<()> {
|
||||
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
||||
|
||||
let mut start_info: StartInfoWrapper = StartInfoWrapper(hvm_start_info::default());
|
||||
|
||||
start_info.0.magic = XEN_HVM_START_MAGIC_VALUE;
|
||||
start_info.0.version = 1; // pvh has version 1
|
||||
start_info.0.nr_modules = 0;
|
||||
start_info.0.cmdline_paddr = cmdline_addr.raw_value() as u64;
|
||||
start_info.0.memmap_paddr = layout::MEMMAP_START.raw_value();
|
||||
|
||||
if let Some(rsdp_addr) = rsdp_addr {
|
||||
start_info.0.rsdp_paddr = rsdp_addr.0;
|
||||
}
|
||||
|
||||
if let Some(initramfs_config) = initramfs {
|
||||
// The initramfs has been written to guest memory already, here we just need to
|
||||
// create the module structure that describes it.
|
||||
let ramdisk_mod: ModlistEntryWrapper = ModlistEntryWrapper(hvm_modlist_entry {
|
||||
paddr: initramfs_config.address.raw_value(),
|
||||
size: initramfs_config.size as u64,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
start_info.0.nr_modules += 1;
|
||||
start_info.0.modlist_paddr = layout::MODLIST_START.raw_value();
|
||||
|
||||
// Write the modlist struct to guest memory.
|
||||
guest_mem
|
||||
.write_obj(ramdisk_mod, layout::MODLIST_START)
|
||||
.map_err(super::Error::ModlistSetup)?;
|
||||
}
|
||||
|
||||
// Vector to hold the memory maps which needs to be written to guest memory
|
||||
// at MEMMAP_START after all of the mappings are recorded.
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
|
||||
// Create the memory map entries.
|
||||
add_memmap_entry(&mut memmap, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
|
||||
let mem_end = guest_mem.last_addr();
|
||||
|
||||
if mem_end < layout::MEM_32BIT_RESERVED_START {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
} else {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
|
||||
E820_RAM,
|
||||
)?;
|
||||
if mem_end > layout::RAM_64BIT_START {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::RAM_64BIT_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::PCI_MMCONFIG_START.0,
|
||||
layout::PCI_MMCONFIG_SIZE,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
|
||||
start_info.0.memmap_entries = memmap.len() as u32;
|
||||
|
||||
// Copy the vector with the memmap table to the MEMMAP_START address
|
||||
// which is already saved in the memmap_paddr field of hvm_start_info struct.
|
||||
let mut memmap_start_addr = layout::MEMMAP_START;
|
||||
|
||||
guest_mem
|
||||
.checked_offset(
|
||||
memmap_start_addr,
|
||||
mem::size_of::<hvm_memmap_table_entry>() * start_info.0.memmap_entries as usize,
|
||||
)
|
||||
.ok_or(super::Error::MemmapTablePastRamEnd)?;
|
||||
|
||||
// For every entry in the memmap vector, create a MemmapTableEntryWrapper
|
||||
// and write it to guest memory.
|
||||
for memmap_entry in memmap {
|
||||
let map_entry_wrapper: MemmapTableEntryWrapper = MemmapTableEntryWrapper(memmap_entry);
|
||||
|
||||
guest_mem
|
||||
.write_obj(map_entry_wrapper, memmap_start_addr)
|
||||
.map_err(|_| super::Error::MemmapTableSetup)?;
|
||||
memmap_start_addr =
|
||||
memmap_start_addr.unchecked_add(mem::size_of::<hvm_memmap_table_entry>() as u64);
|
||||
}
|
||||
|
||||
// The hvm_start_info struct itself must be stored at PVH_START_INFO
|
||||
// address, and %rbx will be initialized to contain PVH_INFO_START prior to
|
||||
// starting the guest, as required by the PVH ABI.
|
||||
let start_info_addr = layout::PVH_INFO_START;
|
||||
|
||||
guest_mem
|
||||
.checked_offset(start_info_addr, mem::size_of::<hvm_start_info>())
|
||||
.ok_or(super::Error::StartInfoPastRamEnd)?;
|
||||
|
||||
// Write the start_info struct to guest memory.
|
||||
guest_mem
|
||||
.write_obj(start_info, start_info_addr)
|
||||
.map_err(|_| super::Error::StartInfoSetup)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_memmap_entry(
|
||||
memmap: &mut Vec<hvm_memmap_table_entry>,
|
||||
addr: u64,
|
||||
size: u64,
|
||||
mem_type: u32,
|
||||
) -> Result<(), Error> {
|
||||
// Add the table entry to the vector
|
||||
memmap.push(hvm_memmap_table_entry {
|
||||
addr,
|
||||
size,
|
||||
type_: mem_type,
|
||||
reserved: 0,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_64bit_boot(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
cmdline_addr: GuestAddress,
|
||||
cmdline_size: usize,
|
||||
initramfs: &Option<InitramfsConfig>,
|
||||
setup_hdr: Option<setup_header>,
|
||||
rsdp_addr: Option<GuestAddress>,
|
||||
) -> super::Result<()> {
|
||||
@@ -115,24 +476,29 @@ pub fn configure_system(
|
||||
const KERNEL_LOADER_OTHER: u8 = 0xff;
|
||||
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
|
||||
|
||||
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
||||
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?;
|
||||
|
||||
let mut params: BootParamsWrapper = BootParamsWrapper(boot_params::default());
|
||||
|
||||
if let Some(hdr) = setup_hdr {
|
||||
// We should use the header if the loader provides one (e.g. from a bzImage).
|
||||
params.0.hdr = hdr;
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
} else {
|
||||
params.0.hdr.type_of_loader = KERNEL_LOADER_OTHER;
|
||||
params.0.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
|
||||
params.0.hdr.header = KERNEL_HDR_MAGIC;
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
|
||||
};
|
||||
|
||||
// Common bootparams settings
|
||||
if params.0.hdr.type_of_loader == 0 {
|
||||
params.0.hdr.type_of_loader = KERNEL_LOADER_OTHER;
|
||||
}
|
||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||
|
||||
if let Some(initramfs_config) = initramfs {
|
||||
params.0.hdr.ramdisk_image = initramfs_config.address.raw_value() as u32;
|
||||
params.0.hdr.ramdisk_size = initramfs_config.size as u32;
|
||||
}
|
||||
|
||||
add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
|
||||
let mem_end = guest_mem.last_addr();
|
||||
@@ -202,6 +568,66 @@ fn add_e820_entry(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the memory address where the initramfs could be loaded.
|
||||
pub fn initramfs_load_addr(
|
||||
guest_mem: &GuestMemoryMmap,
|
||||
initramfs_size: usize,
|
||||
) -> super::Result<u64> {
|
||||
let first_region = guest_mem
|
||||
.find_region(GuestAddress::new(0))
|
||||
.ok_or(super::Error::InitramfsAddress)?;
|
||||
// It's safe to cast to usize because the size of a region can't be greater than usize.
|
||||
let lowmem_size = first_region.len() as usize;
|
||||
|
||||
if lowmem_size < initramfs_size {
|
||||
return Err(super::Error::InitramfsAddress);
|
||||
}
|
||||
|
||||
let aligned_addr: u64 = ((lowmem_size - initramfs_size) & !(crate::pagesize() - 1)) as u64;
|
||||
Ok(aligned_addr)
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||
use std::arch::x86_64;
|
||||
unsafe {
|
||||
let leaf = x86_64::__cpuid(0x8000_0000);
|
||||
|
||||
// Detect and handle AMD SME (Secure Memory Encryption) properly.
|
||||
// Some physical address bits may become reserved when the feature is enabled.
|
||||
// See AMD64 Architecture Programmer's Manual Volume 2, Section 7.10.1
|
||||
let reduced = if leaf.eax >= 0x8000_001f
|
||||
&& leaf.ebx == 0x6874_7541 // Vendor ID: AuthenticAMD
|
||||
&& leaf.ecx == 0x444d_4163
|
||||
&& leaf.edx == 0x6974_6e65
|
||||
&& x86_64::__cpuid(0x8000_001f).eax & 0x1 != 0
|
||||
{
|
||||
(x86_64::__cpuid(0x8000_001f).ebx >> 6) & 0x3f
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if leaf.eax >= 0x8000_0008 {
|
||||
let leaf = x86_64::__cpuid(0x8000_0008);
|
||||
((leaf.eax & 0xff) - reduced) as u8
|
||||
} else {
|
||||
36
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_required_kvm_extensions(kvm: &Kvm) -> super::Result<()> {
|
||||
if !kvm.check_extension(Cap::SignalMsi) {
|
||||
return Err(super::Error::CapabilityMissing(Cap::SignalMsi));
|
||||
}
|
||||
if !kvm.check_extension(Cap::TscDeadlineTimer) {
|
||||
return Err(super::Error::CapabilityMissing(Cap::TscDeadlineTimer));
|
||||
}
|
||||
if !kvm.check_extension(Cap::SplitIrqchip) {
|
||||
return Err(super::Error::CapabilityMissing(Cap::SplitIrqchip));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -227,7 +653,16 @@ mod tests {
|
||||
fn test_system_configuration() {
|
||||
let no_vcpus = 4;
|
||||
let gm = GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let config_err = configure_system(&gm, GuestAddress(0), 0, 1, None, None);
|
||||
let config_err = configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
1,
|
||||
None,
|
||||
Some(layout::RSDP_POINTER),
|
||||
BootProtocol::LinuxBoot,
|
||||
);
|
||||
assert!(config_err.is_err());
|
||||
|
||||
// Now assigning some memory that falls before the 32bit memory hole.
|
||||
@@ -239,7 +674,29 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Now assigning some memory that is equal to the start of the 32bit memory hole.
|
||||
let mem_size = 3328 << 20;
|
||||
@@ -250,7 +707,29 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Now assigning some memory that falls after the 32bit memory hole.
|
||||
let mem_size = 3330 << 20;
|
||||
@@ -261,7 +740,29 @@ mod tests {
|
||||
.map(|r| (r.0, r.1))
|
||||
.collect();
|
||||
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
|
||||
configure_system(&gm, GuestAddress(0), 0, no_vcpus, None, None).unwrap();
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::LinuxBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
0,
|
||||
&None,
|
||||
no_vcpus,
|
||||
None,
|
||||
None,
|
||||
BootProtocol::PvhBoot,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -303,4 +804,29 @@ mod tests {
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_memmap_entry() {
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
|
||||
let expected_memmap = vec![
|
||||
hvm_memmap_table_entry {
|
||||
addr: 0x0,
|
||||
size: 0x1000,
|
||||
type_: E820_RAM,
|
||||
..Default::default()
|
||||
},
|
||||
hvm_memmap_table_entry {
|
||||
addr: 0x10000,
|
||||
size: 0xa000,
|
||||
type_: E820_RESERVED,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
|
||||
add_memmap_entry(&mut memmap, 0, 0x1000, E820_RAM).unwrap();
|
||||
add_memmap_entry(&mut memmap, 0x10000, 0xa000, E820_RESERVED).unwrap();
|
||||
|
||||
assert_eq!(format!("{:?}", memmap), format!("{:?}", expected_memmap));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright © 2020, Oracle and/or its affiliates.
|
||||
//
|
||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -8,10 +10,13 @@
|
||||
use std::{mem, result};
|
||||
|
||||
use super::gdt::{gdt_entry, kvm_segment_from_gdt};
|
||||
use super::BootProtocol;
|
||||
use arch_gen::x86::msr_index;
|
||||
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_regs, kvm_sregs, Msrs};
|
||||
use kvm_ioctls::VcpuFd;
|
||||
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START};
|
||||
use layout::{
|
||||
BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PML5_START, PVH_INFO_START,
|
||||
};
|
||||
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
|
||||
// MTRR constants
|
||||
@@ -42,6 +47,8 @@ pub enum Error {
|
||||
WritePDEAddress(GuestMemoryError),
|
||||
/// Writing PML4 to RAM failed.
|
||||
WritePML4Address(GuestMemoryError),
|
||||
/// Writing PML5 to RAM failed.
|
||||
WritePML5Address(GuestMemoryError),
|
||||
}
|
||||
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
@@ -67,7 +74,7 @@ pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
|
||||
///
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
||||
vcpu.set_msrs(&create_msr_entries())
|
||||
vcpu.set_msrs(&boot_msr_entries())
|
||||
.map_err(Error::SetModelSpecificRegisters)?;
|
||||
|
||||
Ok(())
|
||||
@@ -81,16 +88,31 @@ pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
||||
/// * `boot_ip` - Starting instruction pointer.
|
||||
/// * `boot_sp` - Starting stack pointer.
|
||||
/// * `boot_si` - Must point to zero page address per Linux ABI.
|
||||
pub fn setup_regs(vcpu: &VcpuFd, boot_ip: u64, boot_sp: u64, boot_si: u64) -> Result<()> {
|
||||
let regs: kvm_regs = kvm_regs {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rip: boot_ip,
|
||||
rsp: boot_sp,
|
||||
rbp: boot_sp,
|
||||
rsi: boot_si,
|
||||
..Default::default()
|
||||
pub fn setup_regs(
|
||||
vcpu: &VcpuFd,
|
||||
boot_ip: u64,
|
||||
boot_sp: u64,
|
||||
boot_si: u64,
|
||||
boot_prot: BootProtocol,
|
||||
) -> Result<()> {
|
||||
let regs: kvm_regs = match boot_prot {
|
||||
// Configure regs as required by PVH boot protocol.
|
||||
BootProtocol::PvhBoot => kvm_regs {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rbx: PVH_INFO_START.raw_value(),
|
||||
rip: boot_ip,
|
||||
..Default::default()
|
||||
},
|
||||
// Configure regs as required by Linux 64-bit boot protocol.
|
||||
BootProtocol::LinuxBoot => kvm_regs {
|
||||
rflags: 0x0000000000000002u64,
|
||||
rip: boot_ip,
|
||||
rsp: boot_sp,
|
||||
rbp: boot_sp,
|
||||
rsi: boot_si,
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
vcpu.set_regs(®s).map_err(Error::SetBaseRegisters)
|
||||
}
|
||||
|
||||
@@ -100,11 +122,14 @@ pub fn setup_regs(vcpu: &VcpuFd, boot_ip: u64, boot_sp: u64, boot_si: u64) -> Re
|
||||
///
|
||||
/// * `mem` - The memory that will be passed to the guest.
|
||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &VcpuFd) -> Result<()> {
|
||||
pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &VcpuFd, boot_prot: BootProtocol) -> Result<()> {
|
||||
let mut sregs: kvm_sregs = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
|
||||
|
||||
configure_segments_and_sregs(mem, &mut sregs)?;
|
||||
setup_page_tables(mem, &mut sregs)?; // TODO(dgreid) - Can this be done once per system instead?
|
||||
configure_segments_and_sregs(mem, &mut sregs, boot_prot)?;
|
||||
|
||||
if let BootProtocol::LinuxBoot = boot_prot {
|
||||
setup_page_tables(mem, &mut sregs)?; // TODO(dgreid) - Can this be done once per system instead?
|
||||
}
|
||||
|
||||
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
|
||||
}
|
||||
@@ -117,6 +142,7 @@ const EFER_LME: u64 = 0x100;
|
||||
const X86_CR0_PE: u64 = 0x1;
|
||||
const X86_CR0_PG: u64 = 0x80000000;
|
||||
const X86_CR4_PAE: u64 = 0x20;
|
||||
const X86_CR4_LA57: u64 = 0x1000;
|
||||
|
||||
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let boot_gdt_addr = BOOT_GDT_START;
|
||||
@@ -136,13 +162,31 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
.map_err(Error::WriteIDT)
|
||||
}
|
||||
|
||||
fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> {
|
||||
let gdt_table: [u64; BOOT_GDT_MAX as usize] = [
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xa09b, 0, 0xfffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xfffff), // DATA
|
||||
gdt_entry(0x808b, 0, 0xfffff), // TSS
|
||||
];
|
||||
fn configure_segments_and_sregs(
|
||||
mem: &GuestMemoryMmap,
|
||||
sregs: &mut kvm_sregs,
|
||||
boot_prot: BootProtocol,
|
||||
) -> Result<()> {
|
||||
let gdt_table: [u64; BOOT_GDT_MAX as usize] = match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
// Configure GDT entries as specified by PVH boot protocol
|
||||
[
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xc09b, 0, 0xffffffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xffffffff), // DATA
|
||||
gdt_entry(0x008b, 0, 0x67), // TSS
|
||||
]
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
// Configure GDT entries as specified by Linux 64bit boot protocol
|
||||
[
|
||||
gdt_entry(0, 0, 0), // NULL
|
||||
gdt_entry(0xa09b, 0, 0xfffff), // CODE
|
||||
gdt_entry(0xc093, 0, 0xfffff), // DATA
|
||||
gdt_entry(0x808b, 0, 0xfffff), // TSS
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let code_seg = kvm_segment_from_gdt(gdt_table[1], 1);
|
||||
let data_seg = kvm_segment_from_gdt(gdt_table[2], 2);
|
||||
@@ -165,15 +209,34 @@ fn configure_segments_and_sregs(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) ->
|
||||
sregs.ss = data_seg;
|
||||
sregs.tr = tss_seg;
|
||||
|
||||
/* 64-bit protected mode */
|
||||
sregs.cr0 |= X86_CR0_PE;
|
||||
sregs.efer |= EFER_LME | EFER_LMA;
|
||||
match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
sregs.cr0 = X86_CR0_PE;
|
||||
sregs.cr4 = 0;
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
/* 64-bit protected mode */
|
||||
sregs.cr0 |= X86_CR0_PE;
|
||||
sregs.efer |= EFER_LME | EFER_LMA;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> {
|
||||
// Puts PML4 right after zero page but aligned to 4k.
|
||||
// Puts PML5 or PML4 right after zero page but aligned to 4k.
|
||||
|
||||
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
|
||||
// Entry covering VA [0..256TB)
|
||||
mem.write_obj(PML4_START.raw_value() | 0x03, PML5_START)
|
||||
.map_err(Error::WritePML5Address)?;
|
||||
|
||||
sregs.cr3 = PML5_START.raw_value();
|
||||
sregs.cr4 |= X86_CR4_LA57;
|
||||
} else {
|
||||
sregs.cr3 = PML4_START.raw_value();
|
||||
}
|
||||
|
||||
// Entry covering VA [0..512GB)
|
||||
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
|
||||
@@ -182,6 +245,7 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
|
||||
// Entry covering VA [0..1GB)
|
||||
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
|
||||
.map_err(Error::WritePDPTEAddress)?;
|
||||
|
||||
// 512 2MB entries together covering VA [0..1GB). Note we are assuming
|
||||
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
|
||||
for i in 0..512 {
|
||||
@@ -189,74 +253,49 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
|
||||
.map_err(Error::WritePDEAddress)?;
|
||||
}
|
||||
|
||||
sregs.cr3 = PML4_START.raw_value();
|
||||
sregs.cr4 |= X86_CR4_PAE;
|
||||
sregs.cr0 |= X86_CR0_PG;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_msr_entries() -> Msrs {
|
||||
let mut entries = Vec::<kvm_msr_entry>::new();
|
||||
macro_rules! kvm_msr {
|
||||
($msr:expr) => {
|
||||
kvm_msr_entry {
|
||||
index: $msr,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_SYSENTER_CS,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_SYSENTER_ESP,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_SYSENTER_EIP,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
// x86_64 specific msrs, we only run on x86_64 not x86.
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_STAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_CSTAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_KERNEL_GS_BASE,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_SYSCALL_MASK,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_LSTAR,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
// end of x86_64 specific code
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_TSC,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_IA32_MISC_ENABLE,
|
||||
data: msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64,
|
||||
..Default::default()
|
||||
});
|
||||
entries.push(kvm_msr_entry {
|
||||
index: msr_index::MSR_MTRRdefType,
|
||||
data: MTRR_ENABLE | MTRR_MEM_TYPE_WB,
|
||||
..Default::default()
|
||||
});
|
||||
macro_rules! kvm_msr_data {
|
||||
($msr:expr, $data:expr) => {
|
||||
kvm_msr_entry {
|
||||
index: $msr,
|
||||
data: $data,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Msrs::from_entries(&entries)
|
||||
pub fn boot_msr_entries() -> Msrs {
|
||||
Msrs::from_entries(&[
|
||||
kvm_msr!(msr_index::MSR_IA32_SYSENTER_CS),
|
||||
kvm_msr!(msr_index::MSR_IA32_SYSENTER_ESP),
|
||||
kvm_msr!(msr_index::MSR_IA32_SYSENTER_EIP),
|
||||
kvm_msr!(msr_index::MSR_STAR),
|
||||
kvm_msr!(msr_index::MSR_CSTAR),
|
||||
kvm_msr!(msr_index::MSR_LSTAR),
|
||||
kvm_msr!(msr_index::MSR_KERNEL_GS_BASE),
|
||||
kvm_msr!(msr_index::MSR_SYSCALL_MASK),
|
||||
kvm_msr!(msr_index::MSR_IA32_TSC),
|
||||
kvm_msr_data!(
|
||||
msr_index::MSR_IA32_MISC_ENABLE,
|
||||
msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64
|
||||
),
|
||||
kvm_msr_data!(msr_index::MSR_MTRRdefType, MTRR_ENABLE | MTRR_MEM_TYPE_WB),
|
||||
])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -280,7 +319,7 @@ mod tests {
|
||||
fn segments_and_sregs() {
|
||||
let mut sregs: kvm_sregs = Default::default();
|
||||
let gm = create_guest_mem();
|
||||
configure_segments_and_sregs(&gm, &mut sregs).unwrap();
|
||||
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::LinuxBoot).unwrap();
|
||||
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
@@ -298,16 +337,46 @@ mod tests {
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
||||
|
||||
assert_eq!(0, sregs.cs.base);
|
||||
assert_eq!(0xfffff, sregs.ds.limit);
|
||||
assert_eq!(0xffffffff, sregs.ds.limit);
|
||||
assert_eq!(0x10, sregs.es.selector);
|
||||
assert_eq!(1, sregs.fs.present);
|
||||
assert_eq!(1, sregs.gs.g);
|
||||
assert_eq!(0, sregs.ss.avl);
|
||||
assert_eq!(0, sregs.tr.base);
|
||||
assert_eq!(0xfffff, sregs.tr.limit);
|
||||
assert_eq!(0xffffffff, sregs.tr.limit);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert_eq!(X86_CR0_PE, sregs.cr0);
|
||||
assert_eq!(EFER_LME | EFER_LMA, sregs.efer);
|
||||
|
||||
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::PvhBoot).unwrap();
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
|
||||
assert_eq!(
|
||||
0xcf9b000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
|
||||
);
|
||||
assert_eq!(
|
||||
0xcf93000000ffff,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
|
||||
);
|
||||
assert_eq!(
|
||||
0x8b0000000067,
|
||||
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
|
||||
);
|
||||
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
|
||||
|
||||
assert_eq!(0, sregs.cs.base);
|
||||
assert_eq!(0xffffffff, sregs.ds.limit);
|
||||
assert_eq!(0x10, sregs.es.selector);
|
||||
assert_eq!(1, sregs.fs.present);
|
||||
assert_eq!(1, sregs.gs.g);
|
||||
assert_eq!(0, sregs.ss.avl);
|
||||
assert_eq!(0, sregs.tr.base);
|
||||
assert_eq!(0, sregs.tr.g);
|
||||
assert_eq!(0x67, sregs.tr.limit);
|
||||
assert_eq!(0xb, sregs.tr.type_);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert_eq!(X86_CR0_PE, sregs.cr0);
|
||||
assert_eq!(0, sregs.cr4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -316,8 +385,11 @@ mod tests {
|
||||
let gm = create_guest_mem();
|
||||
setup_page_tables(&gm, &mut sregs).unwrap();
|
||||
|
||||
assert_eq!(0xa003, read_u64(&gm, PML4_START));
|
||||
assert_eq!(0xb003, read_u64(&gm, PDPTE_START));
|
||||
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
|
||||
assert_eq!(0xa003, read_u64(&gm, PML5_START));
|
||||
}
|
||||
assert_eq!(0xb003, read_u64(&gm, PML4_START));
|
||||
assert_eq!(0xc003, read_u64(&gm, PDPTE_START));
|
||||
for i in 0..512 {
|
||||
assert_eq!(
|
||||
(i << 21) + 0x83u64,
|
||||
@@ -325,7 +397,11 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(PML4_START.raw_value(), sregs.cr3);
|
||||
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
|
||||
assert_eq!(PML5_START.raw_value(), sregs.cr3);
|
||||
} else {
|
||||
assert_eq!(PML4_START.raw_value(), sregs.cr3);
|
||||
}
|
||||
assert_eq!(X86_CR4_PAE, sregs.cr4);
|
||||
assert_eq!(X86_CR0_PG, sregs.cr0);
|
||||
}
|
||||
@@ -374,7 +450,7 @@ mod tests {
|
||||
// Official entries that were setup when we did setup_msrs. We need to assert that the
|
||||
// tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we
|
||||
// expect.
|
||||
let entry_vec = create_msr_entries();
|
||||
let entry_vec = boot_msr_entries();
|
||||
assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]);
|
||||
}
|
||||
|
||||
@@ -398,6 +474,7 @@ mod tests {
|
||||
expected_regs.rip,
|
||||
expected_regs.rsp,
|
||||
expected_regs.rsi,
|
||||
BootProtocol::LinuxBoot,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -413,10 +490,10 @@ mod tests {
|
||||
|
||||
let mut expected_sregs: kvm_sregs = vcpu.get_sregs().unwrap();
|
||||
let gm = create_guest_mem();
|
||||
configure_segments_and_sregs(&gm, &mut expected_sregs).unwrap();
|
||||
configure_segments_and_sregs(&gm, &mut expected_sregs, BootProtocol::LinuxBoot).unwrap();
|
||||
setup_page_tables(&gm, &mut expected_sregs).unwrap();
|
||||
|
||||
setup_sregs(&gm, &vcpu).unwrap();
|
||||
setup_sregs(&gm, &vcpu, BootProtocol::LinuxBoot).unwrap();
|
||||
let actual_sregs: kvm_sregs = vcpu.get_sregs().unwrap();
|
||||
assert_eq!(expected_sregs, actual_sregs);
|
||||
}
|
||||
|
||||
22
build.rs
Normal file
22
build.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let git_out = Command::new("git")
|
||||
.args(&["describe", "--dirty"])
|
||||
.output()
|
||||
.expect("Expect to get git describe output");
|
||||
|
||||
// This println!() has a special behavior, as it will set the environment
|
||||
// variable BUILT_VERSION, so that it can be reused from the binary.
|
||||
// Particularly, this is used from src/main.rs to display the exact
|
||||
// version.
|
||||
println!(
|
||||
"cargo:rustc-env=BUILT_VERSION={}",
|
||||
String::from_utf8(git_out.stdout).unwrap()
|
||||
);
|
||||
}
|
||||
@@ -4,13 +4,19 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bitflags = ">=1.2.1"
|
||||
byteorder = "1.3.4"
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.60"
|
||||
libc = "0.2.71"
|
||||
log = "0.4.8"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
vm-memory = "0.2.1"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -18,5 +24,6 @@ tempfile = "3.1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
acpi = []
|
||||
acpi = ["acpi_tables"]
|
||||
cmos = []
|
||||
fwdebug = []
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use acpi_tables::{aml, aml::Aml};
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
@@ -94,3 +95,57 @@ impl BusDevice for AcpiGEDDevice {
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
impl Aml for AcpiGEDDevice {
|
||||
fn to_aml_bytes(&self) -> Vec<u8> {
|
||||
aml::Device::new(
|
||||
"_SB_.GED_".into(),
|
||||
vec![
|
||||
&aml::Name::new("_HID".into(), &"ACPI0013"),
|
||||
&aml::Name::new("_UID".into(), &aml::ZERO),
|
||||
&aml::Name::new(
|
||||
"_CRS".into(),
|
||||
&aml::ResourceTemplate::new(vec![&aml::Interrupt::new(
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
self.ged_irq,
|
||||
)]),
|
||||
),
|
||||
&aml::OpRegion::new("GDST".into(), aml::OpRegionSpace::SystemIO, 0xb000, 0x1),
|
||||
&aml::Field::new(
|
||||
"GDST".into(),
|
||||
aml::FieldAccessType::Byte,
|
||||
aml::FieldUpdateRule::WriteAsZeroes,
|
||||
vec![aml::FieldEntry::Named(*b"GDAT", 8)],
|
||||
),
|
||||
&aml::Method::new(
|
||||
"_EVT".into(),
|
||||
1,
|
||||
true,
|
||||
vec![
|
||||
&aml::Store::new(&aml::Local(0), &aml::Path::new("GDAT")),
|
||||
&aml::And::new(&aml::Local(1), &aml::Local(0), &aml::ONE),
|
||||
&aml::If::new(
|
||||
&aml::Equal::new(&aml::Local(1), &aml::ONE),
|
||||
vec![&aml::MethodCall::new("\\_SB_.CPUS.CSCN".into(), vec![])],
|
||||
),
|
||||
&aml::And::new(&aml::Local(1), &aml::Local(0), &2usize),
|
||||
&aml::If::new(
|
||||
&aml::Equal::new(&aml::Local(1), &2usize),
|
||||
vec![&aml::MethodCall::new("\\_SB_.MHPC.MSCN".into(), vec![])],
|
||||
),
|
||||
&aml::And::new(&aml::Local(1), &aml::Local(0), &4usize),
|
||||
&aml::If::new(
|
||||
&aml::Equal::new(&aml::Local(1), &4usize),
|
||||
vec![&aml::MethodCall::new("\\_SB_.PCI0.PCNT".into(), vec![])],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
.to_aml_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
|
||||
use std::collections::btree_map::BTreeMap;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::sync::{Arc, Mutex, RwLock, Weak};
|
||||
use std::{convert, error, fmt, io, result};
|
||||
|
||||
/// Trait for devices that respond to reads or writes in an arbitrary address space.
|
||||
@@ -95,7 +95,7 @@ impl PartialOrd for BusRange {
|
||||
/// only restriction is that no two devices can overlap in this address space.
|
||||
#[derive(Default)]
|
||||
pub struct Bus {
|
||||
devices: RwLock<BTreeMap<BusRange, Arc<Mutex<dyn BusDevice>>>>,
|
||||
devices: RwLock<BTreeMap<BusRange, Weak<Mutex<dyn BusDevice>>>>,
|
||||
}
|
||||
|
||||
impl Bus {
|
||||
@@ -112,7 +112,7 @@ impl Bus {
|
||||
.range(..=BusRange { base: addr, len: 1 })
|
||||
.rev()
|
||||
.next()?;
|
||||
Some((*range, dev.clone()))
|
||||
Some((*range, dev.upgrade().unwrap().clone()))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -147,7 +147,7 @@ impl Bus {
|
||||
.devices
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(BusRange { base, len }, device)
|
||||
.insert(BusRange { base, len }, Arc::downgrade(&device))
|
||||
.is_some()
|
||||
{
|
||||
return Err(Error::Overlap);
|
||||
@@ -171,6 +171,24 @@ impl Bus {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes all entries referencing the given device.
|
||||
pub fn remove_by_device(&self, device: &Arc<Mutex<dyn BusDevice>>) -> Result<()> {
|
||||
let mut device_list = self.devices.write().unwrap();
|
||||
let mut remove_key_list = Vec::new();
|
||||
|
||||
for (key, value) in device_list.iter() {
|
||||
if Arc::ptr_eq(&value.upgrade().unwrap(), device) {
|
||||
remove_key_list.push(*key);
|
||||
}
|
||||
}
|
||||
|
||||
for key in remove_key_list.iter() {
|
||||
device_list.remove(key);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the address range for an existing device.
|
||||
pub fn update_range(
|
||||
&self,
|
||||
@@ -312,9 +330,8 @@ mod tests {
|
||||
|
||||
let bus = Bus::new();
|
||||
let mut data = [1, 2, 3, 4];
|
||||
assert!(bus
|
||||
.insert(Arc::new(Mutex::new(DummyDevice)), 0x10, 0x10)
|
||||
.is_ok());
|
||||
let device = Arc::new(Mutex::new(DummyDevice));
|
||||
assert!(bus.insert(device.clone(), 0x10, 0x10).is_ok());
|
||||
assert!(bus.write(0x10, &mut data));
|
||||
assert!(bus.read(0x10, &mut data));
|
||||
assert_eq!(data, [1, 2, 3, 4]);
|
||||
|
||||
85
devices/src/gic.rs
Normal file
85
devices/src/gic.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2020, ARM Limited.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
|
||||
};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
// Reserve 32 IRQs (GSI 32 ~ 64) for legacy device.
|
||||
// GsiAllocator should allocate beyond this: from 64 on
|
||||
pub const IRQ_LEGACY_COUNT: usize = 32;
|
||||
pub const IRQ_SPI_OFFSET: usize = 32;
|
||||
|
||||
// This Gic struct implements InterruptController to provide interrupt delivery service.
|
||||
// The Gic source files in arch/ folder maintain the Aarch64 specific Gic device.
|
||||
// The 2 Gic instances could be merged together.
|
||||
// Leave this refactoring to future. Two options may be considered:
|
||||
// 1. Move Gic*.rs from arch/ folder here.
|
||||
// 2. Move this file and ioapic.rs to arch/, as they are architecture specific.
|
||||
pub struct Gic {
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
impl Gic {
|
||||
pub fn new(
|
||||
_vcpu_count: u8,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
) -> Result<Gic> {
|
||||
let interrupt_source_group = interrupt_manager
|
||||
.create_group(MsiIrqGroupConfig {
|
||||
base: IRQ_SPI_OFFSET as InterruptIndex,
|
||||
count: IRQ_LEGACY_COUNT as InterruptIndex,
|
||||
})
|
||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||
|
||||
Ok(Gic {
|
||||
interrupt_source_group,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptController for Gic {
|
||||
fn enable(&self) -> Result<()> {
|
||||
&self
|
||||
.interrupt_source_group
|
||||
.enable()
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This should be called anytime an interrupt needs to be injected into the
|
||||
// running guest.
|
||||
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const GIC_SNAPSHOT_ID: &str = "gic";
|
||||
impl Snapshottable for Gic {
|
||||
fn id(&self) -> String {
|
||||
GIC_SNAPSHOT_ID.to_string()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn restore(&mut self, _snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Gic {}
|
||||
impl Transportable for Gic {}
|
||||
impl Migratable for Gic {}
|
||||
61
devices/src/interrupt_controller.rs
Normal file
61
devices/src/interrupt_controller.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2020, ARM Limited.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use std::io;
|
||||
use std::result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid destination mode.
|
||||
InvalidDestinationMode,
|
||||
/// Invalid trigger mode.
|
||||
InvalidTriggerMode,
|
||||
/// Invalid delivery mode.
|
||||
InvalidDeliveryMode,
|
||||
/// Failed creating the interrupt source group.
|
||||
CreateInterruptSourceGroup(io::Error),
|
||||
/// Failed triggering the interrupt.
|
||||
TriggerInterrupt(io::Error),
|
||||
/// Failed masking the interrupt.
|
||||
MaskInterrupt(io::Error),
|
||||
/// Failed unmasking the interrupt.
|
||||
UnmaskInterrupt(io::Error),
|
||||
/// Failed updating the interrupt.
|
||||
UpdateInterrupt(io::Error),
|
||||
/// Failed enabling the interrupt.
|
||||
EnableInterrupt(io::Error),
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub struct MsiMessage {
|
||||
// Message Address Register
|
||||
// 31-20: Base address. Fixed value (0x0FEE)
|
||||
// 19-12: Destination ID
|
||||
// 11-4: Reserved
|
||||
// 3: Redirection Hint indication
|
||||
// 2: Destination Mode
|
||||
// 1-0: Reserved
|
||||
pub addr: u32,
|
||||
// Message Data Register
|
||||
// 32-16: Reserved
|
||||
// 15: Trigger Mode. 0 = Edge, 1 = Level
|
||||
// 14: Level. 0 = Deassert, 1 = Assert
|
||||
// 13-11: Reserved
|
||||
// 10-8: Delivery Mode
|
||||
// 7-0: Vector
|
||||
pub data: u32,
|
||||
}
|
||||
|
||||
// Introduce trait InterruptController to uniform the interrupt
|
||||
// service provided for devices.
|
||||
// Device manager uses this trait without caring whether it is a
|
||||
// IOAPIC (X86) or GIC (Arm).
|
||||
pub trait InterruptController: Send {
|
||||
fn service_irq(&mut self, irq: usize) -> Result<()>;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn enable(&self) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn end_of_interrupt(&mut self, vec: u8);
|
||||
}
|
||||
@@ -9,9 +9,10 @@
|
||||
// Implementation of an intel 82093AA Input/Output Advanced Programmable Interrupt Controller
|
||||
// See https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf for a specification.
|
||||
|
||||
use super::interrupt_controller::{Error, InterruptController};
|
||||
use crate::BusDevice;
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::io;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{
|
||||
@@ -19,28 +20,14 @@ use vm_device::interrupt::{
|
||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||
};
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid destination mode.
|
||||
InvalidDestinationMode,
|
||||
/// Invalid trigger mode.
|
||||
InvalidTriggerMode,
|
||||
/// Invalid delivery mode.
|
||||
InvalidDeliveryMode,
|
||||
/// Failed creating the interrupt source group.
|
||||
CreateInterruptSourceGroup(io::Error),
|
||||
/// Failed triggering the interrupt.
|
||||
TriggerInterrupt(io::Error),
|
||||
/// Failed masking the interrupt.
|
||||
MaskInterrupt(io::Error),
|
||||
/// Failed unmasking the interrupt.
|
||||
UnmaskInterrupt(io::Error),
|
||||
/// Failed updating the interrupt.
|
||||
UpdateInterrupt(io::Error),
|
||||
/// Failed enabling the interrupt.
|
||||
EnableInterrupt(io::Error),
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "GuestAddress")]
|
||||
pub struct GuestAddressDef(pub u64);
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -99,25 +86,6 @@ fn set_remote_irr(entry: &mut RedirectionTableEntry, val: u8) {
|
||||
*entry |= u64::from(val & 0x1) << 14;
|
||||
}
|
||||
|
||||
pub struct MsiMessage {
|
||||
// Message Address Register
|
||||
// 31-20: Base address. Fixed value (0x0FEE)
|
||||
// 19-12: Destination ID
|
||||
// 11-4: Reserved
|
||||
// 3: Redirection Hint indication
|
||||
// 2: Destination Mode
|
||||
// 1-0: Reserved
|
||||
pub addr: u32,
|
||||
// Message Data Register
|
||||
// 32-16: Reserved
|
||||
// 15: Trigger Mode. 0 = Edge, 1 = Level
|
||||
// 14: Level. 0 = Deassert, 1 = Assert
|
||||
// 13-11: Reserved
|
||||
// 10-8: Delivery Mode
|
||||
// 7-0: Vector
|
||||
pub data: u32,
|
||||
}
|
||||
|
||||
pub const NUM_IOAPIC_PINS: usize = 24;
|
||||
const IOAPIC_VERSION_ID: u32 = 0x0017_0011;
|
||||
|
||||
@@ -166,13 +134,25 @@ fn decode_irq_from_selector(selector: u8) -> (usize, bool) {
|
||||
}
|
||||
|
||||
pub struct Ioapic {
|
||||
id: u32,
|
||||
id: String,
|
||||
id_reg: u32,
|
||||
reg_sel: u32,
|
||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||
apic_address: GuestAddress,
|
||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct IoapicState {
|
||||
id_reg: u32,
|
||||
reg_sel: u32,
|
||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||
#[serde(with = "GuestAddressDef")]
|
||||
apic_address: GuestAddress,
|
||||
}
|
||||
|
||||
impl BusDevice for Ioapic {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
assert!(data.len() == 4);
|
||||
@@ -210,6 +190,7 @@ impl BusDevice for Ioapic {
|
||||
|
||||
impl Ioapic {
|
||||
pub fn new(
|
||||
id: String,
|
||||
apic_address: GuestAddress,
|
||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
) -> Result<Ioapic> {
|
||||
@@ -225,43 +206,87 @@ impl Ioapic {
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
|
||||
Ok(Ioapic {
|
||||
id: 0,
|
||||
id,
|
||||
id_reg: 0,
|
||||
reg_sel: 0,
|
||||
reg_entries: [0; NUM_IOAPIC_PINS],
|
||||
used_entries: [false; NUM_IOAPIC_PINS],
|
||||
apic_address,
|
||||
interrupt_source_group,
|
||||
})
|
||||
}
|
||||
|
||||
// The ioapic must be informed about EOIs in order to deassert interrupts
|
||||
// already sent.
|
||||
pub fn end_of_interrupt(&mut self, vec: u8) {
|
||||
for i in 0..NUM_IOAPIC_PINS {
|
||||
let entry = &mut self.reg_entries[i];
|
||||
// Clear Remote IRR bit
|
||||
if vector(*entry) == vec && trigger_mode(*entry) == 1 {
|
||||
set_remote_irr(entry, 0);
|
||||
fn ioapic_write(&mut self, val: u32) {
|
||||
debug!("IOAPIC_W reg 0x{:x}, val 0x{:x}", self.reg_sel, val);
|
||||
|
||||
match self.reg_sel as u8 {
|
||||
IOAPIC_REG_ID => self.id_reg = (val >> 24) & 0xf,
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if is_high_bits {
|
||||
self.reg_entries[index] &= 0xffff_ffff;
|
||||
self.reg_entries[index] |= u64::from(val) << 32;
|
||||
} else {
|
||||
// Ensure not to override read-only bits:
|
||||
// - Delivery Status (bit 12)
|
||||
// - Remote IRR (bit 14)
|
||||
self.reg_entries[index] &= 0xffff_ffff_0000_5000;
|
||||
self.reg_entries[index] |= u64::from(val) & 0xffff_afff;
|
||||
}
|
||||
// The entry must be updated through the interrupt source
|
||||
// group.
|
||||
if let Err(e) = self.update_entry(index) {
|
||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
||||
}
|
||||
// Store the information this IRQ is now being used.
|
||||
self.used_entries[index] = true;
|
||||
}
|
||||
_ => error!("IOAPIC: invalid write to register offset"),
|
||||
}
|
||||
}
|
||||
|
||||
fn ioapic_read(&self) -> u32 {
|
||||
debug!("IOAPIC_R reg 0x{:x}", self.reg_sel);
|
||||
|
||||
match self.reg_sel as u8 {
|
||||
IOAPIC_REG_VERSION => IOAPIC_VERSION_ID,
|
||||
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => (self.id_reg & 0xf) << 24,
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if is_high_bits {
|
||||
(self.reg_entries[index] >> 32) as u32
|
||||
} else {
|
||||
(self.reg_entries[index] & 0xffff_ffff) as u32
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("IOAPIC: invalid read from register offset");
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should be called anytime an interrupt needs to be injected into the
|
||||
// running guest.
|
||||
pub fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||
let entry = &mut self.reg_entries[irq];
|
||||
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
debug!("Interrupt successfully delivered");
|
||||
|
||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||
// It will be cleared when the EOI is received.
|
||||
if trigger_mode(*entry) == 1 {
|
||||
set_remote_irr(entry, 1);
|
||||
fn state(&self) -> IoapicState {
|
||||
IoapicState {
|
||||
id_reg: self.id_reg,
|
||||
reg_sel: self.reg_sel,
|
||||
reg_entries: self.reg_entries,
|
||||
used_entries: self.used_entries,
|
||||
apic_address: self.apic_address,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &IoapicState) -> Result<()> {
|
||||
self.id_reg = state.id_reg;
|
||||
self.reg_sel = state.reg_sel;
|
||||
self.reg_entries = state.reg_entries;
|
||||
self.used_entries = state.used_entries;
|
||||
self.apic_address = state.apic_address;
|
||||
for (irq, entry) in self.used_entries.iter().enumerate() {
|
||||
if *entry {
|
||||
self.update_entry(irq)?;
|
||||
}
|
||||
}
|
||||
// Clear the Delivery Status bit
|
||||
set_delivery_status(entry, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -337,52 +362,84 @@ impl Ioapic {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn ioapic_write(&mut self, val: u32) {
|
||||
debug!("IOAPIC_W reg 0x{:x}, val 0x{:x}", self.reg_sel, val);
|
||||
|
||||
match self.reg_sel as u8 {
|
||||
IOAPIC_REG_ID => self.id = (val >> 24) & 0xf,
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if is_high_bits {
|
||||
self.reg_entries[index] &= 0xffff_ffff;
|
||||
self.reg_entries[index] |= u64::from(val) << 32;
|
||||
} else {
|
||||
// Ensure not to override read-only bits:
|
||||
// - Delivery Status (bit 12)
|
||||
// - Remote IRR (bit 14)
|
||||
self.reg_entries[index] &= 0xffff_ffff_0000_5000;
|
||||
self.reg_entries[index] |= u64::from(val) & 0xffff_afff;
|
||||
}
|
||||
// The entry must be updated through the interrupt source
|
||||
// group.
|
||||
if let Err(e) = self.update_entry(index) {
|
||||
error!("Failed updating IOAPIC entry: {:?}", e);
|
||||
}
|
||||
impl InterruptController for Ioapic {
|
||||
// The ioapic must be informed about EOIs in order to deassert interrupts
|
||||
// already sent.
|
||||
fn end_of_interrupt(&mut self, vec: u8) {
|
||||
for i in 0..NUM_IOAPIC_PINS {
|
||||
let entry = &mut self.reg_entries[i];
|
||||
// Clear Remote IRR bit
|
||||
if vector(*entry) == vec && trigger_mode(*entry) == 1 {
|
||||
set_remote_irr(entry, 0);
|
||||
}
|
||||
_ => error!("IOAPIC: invalid write to register offset"),
|
||||
}
|
||||
}
|
||||
|
||||
fn ioapic_read(&self) -> u32 {
|
||||
debug!("IOAPIC_R reg 0x{:x}", self.reg_sel);
|
||||
// This should be called anytime an interrupt needs to be injected into the
|
||||
// running guest.
|
||||
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||
let entry = &mut self.reg_entries[irq];
|
||||
|
||||
match self.reg_sel as u8 {
|
||||
IOAPIC_REG_VERSION => IOAPIC_VERSION_ID,
|
||||
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => (self.id & 0xf) << 24,
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
if is_high_bits {
|
||||
(self.reg_entries[index] >> 32) as u32
|
||||
} else {
|
||||
(self.reg_entries[index] & 0xffff_ffff) as u32
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("IOAPIC: invalid read from register offset");
|
||||
0
|
||||
}
|
||||
self.interrupt_source_group
|
||||
.trigger(irq as InterruptIndex)
|
||||
.map_err(Error::TriggerInterrupt)?;
|
||||
debug!("Interrupt successfully delivered");
|
||||
|
||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||
// It will be cleared when the EOI is received.
|
||||
if trigger_mode(*entry) == 1 {
|
||||
set_remote_irr(entry, 1);
|
||||
}
|
||||
// Clear the Delivery Status bit
|
||||
set_delivery_status(entry, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Ioapic {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut ioapic_snapshot = Snapshot::new(self.id.as_str());
|
||||
ioapic_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(ioapic_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(ioapic_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let ioapic_state = match serde_json::from_slice(&ioapic_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize IOAPIC {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return self.set_state(&ioapic_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore IOAPIC state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find IOAPIC snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Ioapic {}
|
||||
impl Transportable for Ioapic {}
|
||||
impl Migratable for Ioapic {}
|
||||
|
||||
40
devices/src/legacy/fwdebug.rs
Normal file
40
devices/src/legacy/fwdebug.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
//
|
||||
|
||||
use BusDevice;
|
||||
|
||||
/// Provides firmware debug output via I/O port controls
|
||||
#[derive(Default)]
|
||||
pub struct FwDebugDevice {}
|
||||
|
||||
impl FwDebugDevice {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
/// FwDebugDevice sits on the I/O bus as 0x402 and receives ASCII characters
|
||||
impl BusDevice for FwDebugDevice {
|
||||
/// Upon read return the magic value to indicate that there is a debug port
|
||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||
if data.len() == 1 {
|
||||
data[0] = 0xe9
|
||||
} else {
|
||||
error!("Invalid read size on debug port: {}", data.len())
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
|
||||
if data.len() == 1 {
|
||||
print!("{}", data[0] as char);
|
||||
} else {
|
||||
error!("Invalid write size on debug port: {}", data.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,19 @@
|
||||
|
||||
#[cfg(feature = "cmos")]
|
||||
mod cmos;
|
||||
#[cfg(feature = "fwdebug")]
|
||||
mod fwdebug;
|
||||
mod i8042;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod rtc_pl031;
|
||||
mod serial;
|
||||
|
||||
#[cfg(feature = "cmos")]
|
||||
pub use self::cmos::Cmos;
|
||||
#[cfg(feature = "fwdebug")]
|
||||
pub use self::fwdebug::FwDebugDevice;
|
||||
pub use self::i8042::I8042Device;
|
||||
pub use self::serial::Serial;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use self::rtc_pl031::RTC;
|
||||
|
||||
626
devices/src/legacy/rtc_pl031.rs
Normal file
626
devices/src/legacy/rtc_pl031.rs
Normal file
@@ -0,0 +1,626 @@
|
||||
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! ARM PL031 Real Time Clock
|
||||
//!
|
||||
//! This module implements a PL031 Real Time Clock (RTC) that provides to provides long time base counter.
|
||||
//! This is achieved by generating an interrupt signal after counting for a programmed number of cycles of
|
||||
//! a real-time clock input.
|
||||
//!
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use std::{io, result};
|
||||
|
||||
use crate::BusDevice;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
|
||||
// As you can see in https://static.docs.arm.com/ddi0224/c/real_time_clock_pl031_r1p3_technical_reference_manual_DDI0224C.pdf
|
||||
// at section 3.2 Summary of RTC registers, the total size occupied by this device is 0x000 -> 0xFFC + 4 = 0x1000.
|
||||
// From 0x0 to 0x1C we have following registers:
|
||||
const RTCDR: u64 = 0x0; // Data Register.
|
||||
const RTCMR: u64 = 0x4; // Match Register.
|
||||
const RTCLR: u64 = 0x8; // Load Regiser.
|
||||
const RTCCR: u64 = 0xc; // Control Register.
|
||||
const RTCIMSC: u64 = 0x10; // Interrupt Mask Set or Clear Register.
|
||||
const RTCRIS: u64 = 0x14; // Raw Interrupt Status.
|
||||
const RTCMIS: u64 = 0x18; // Masked Interrupt Status.
|
||||
const RTCICR: u64 = 0x1c; // Interrupt Clear Register.
|
||||
// From 0x020 to 0xFDC => reserved space.
|
||||
// From 0xFE0 to 0x1000 => Peripheral and PrimeCell Identification Registers which are Read Only registers.
|
||||
// AMBA standard devices have CIDs (Cell IDs) and PIDs (Peripheral IDs). The linux kernel will look for these in order to assert the identity
|
||||
// of these devices (i.e look at the `amba_device_try_add` function).
|
||||
// We are putting the expected values (look at 'Reset value' column from above mentioned document) in an array.
|
||||
const PL031_ID: [u8; 8] = [0x31, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
|
||||
// We are only interested in the margins.
|
||||
const AMBA_ID_LOW: u64 = 0xFE0;
|
||||
const AMBA_ID_HIGH: u64 = 0x1000;
|
||||
/// Constant to convert seconds to nanoseconds.
|
||||
pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! generate_read_fn {
|
||||
($fn_name: ident, $data_type: ty, $byte_type: ty, $type_size: expr, $endian_type: ident) => {
|
||||
#[allow(dead_code)]
|
||||
pub fn $fn_name(input: &[$byte_type]) -> $data_type {
|
||||
assert!($type_size == std::mem::size_of::<$data_type>());
|
||||
let mut array = [0u8; $type_size];
|
||||
for (byte, read) in array.iter_mut().zip(input.iter().cloned()) {
|
||||
*byte = read as u8;
|
||||
}
|
||||
<$data_type>::$endian_type(array)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! generate_write_fn {
|
||||
($fn_name: ident, $data_type: ty, $byte_type: ty, $endian_type: ident) => {
|
||||
#[allow(dead_code)]
|
||||
pub fn $fn_name(buf: &mut [$byte_type], n: $data_type) {
|
||||
for (byte, read) in buf
|
||||
.iter_mut()
|
||||
.zip(<$data_type>::$endian_type(n).iter().cloned())
|
||||
{
|
||||
*byte = read as $byte_type;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_read_fn!(read_le_u16, u16, u8, 2, from_le_bytes);
|
||||
generate_read_fn!(read_le_u32, u32, u8, 4, from_le_bytes);
|
||||
generate_read_fn!(read_le_u64, u64, u8, 8, from_le_bytes);
|
||||
generate_read_fn!(read_le_i32, i32, i8, 4, from_le_bytes);
|
||||
|
||||
generate_read_fn!(read_be_u16, u16, u8, 2, from_be_bytes);
|
||||
generate_read_fn!(read_be_u32, u32, u8, 4, from_be_bytes);
|
||||
|
||||
generate_write_fn!(write_le_u16, u16, u8, to_le_bytes);
|
||||
generate_write_fn!(write_le_u32, u32, u8, to_le_bytes);
|
||||
generate_write_fn!(write_le_u64, u64, u8, to_le_bytes);
|
||||
generate_write_fn!(write_le_i32, i32, i8, to_le_bytes);
|
||||
|
||||
generate_write_fn!(write_be_u16, u16, u8, to_be_bytes);
|
||||
generate_write_fn!(write_be_u32, u32, u8, to_be_bytes);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
BadWriteOffset(u64),
|
||||
InterruptFailure(io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::BadWriteOffset(offset) => write!(f, "Bad Write Offset: {}", offset),
|
||||
Error::InterruptFailure(e) => write!(f, "Failed to trigger interrupt: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Wrapper over `libc::clockid_t` to specify Linux Kernel clock source.
|
||||
pub enum ClockType {
|
||||
/// Equivalent to `libc::CLOCK_MONOTONIC`.
|
||||
Monotonic,
|
||||
/// Equivalent to `libc::CLOCK_REALTIME`.
|
||||
#[allow(dead_code)]
|
||||
Real,
|
||||
/// Equivalent to `libc::CLOCK_PROCESS_CPUTIME_ID`.
|
||||
ProcessCpu,
|
||||
/// Equivalent to `libc::CLOCK_THREAD_CPUTIME_ID`.
|
||||
#[allow(dead_code)]
|
||||
ThreadCpu,
|
||||
}
|
||||
|
||||
impl Into<libc::clockid_t> for ClockType {
|
||||
fn into(self) -> libc::clockid_t {
|
||||
match self {
|
||||
ClockType::Monotonic => libc::CLOCK_MONOTONIC,
|
||||
ClockType::Real => libc::CLOCK_REALTIME,
|
||||
ClockType::ProcessCpu => libc::CLOCK_PROCESS_CPUTIME_ID,
|
||||
ClockType::ThreadCpu => libc::CLOCK_THREAD_CPUTIME_ID,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure representing the date in local time with nanosecond precision.
|
||||
pub struct LocalTime {
|
||||
/// Seconds in current minute.
|
||||
sec: i32,
|
||||
/// Minutes in current hour.
|
||||
min: i32,
|
||||
/// Hours in current day, 24H format.
|
||||
hour: i32,
|
||||
/// Days in current month.
|
||||
mday: i32,
|
||||
/// Months in current year.
|
||||
mon: i32,
|
||||
/// Years passed since 1900 BC.
|
||||
year: i32,
|
||||
/// Nanoseconds in current second.
|
||||
nsec: i64,
|
||||
}
|
||||
|
||||
impl LocalTime {
|
||||
/// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
|
||||
#[allow(dead_code)]
|
||||
pub fn now() -> LocalTime {
|
||||
let mut timespec = libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
let mut tm: libc::tm = libc::tm {
|
||||
tm_sec: 0,
|
||||
tm_min: 0,
|
||||
tm_hour: 0,
|
||||
tm_mday: 0,
|
||||
tm_mon: 0,
|
||||
tm_year: 0,
|
||||
tm_wday: 0,
|
||||
tm_yday: 0,
|
||||
tm_isdst: 0,
|
||||
tm_gmtoff: 0,
|
||||
tm_zone: std::ptr::null(),
|
||||
};
|
||||
|
||||
// Safe because the parameters are valid.
|
||||
unsafe {
|
||||
libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
|
||||
libc::localtime_r(×pec.tv_sec, &mut tm);
|
||||
}
|
||||
|
||||
LocalTime {
|
||||
sec: tm.tm_sec,
|
||||
min: tm.tm_min,
|
||||
hour: tm.tm_hour,
|
||||
mday: tm.tm_mday,
|
||||
mon: tm.tm_mon,
|
||||
year: tm.tm_year,
|
||||
nsec: timespec.tv_nsec,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LocalTime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
|
||||
self.year + 1900,
|
||||
self.mon + 1,
|
||||
self.mday,
|
||||
self.hour,
|
||||
self.min,
|
||||
self.sec,
|
||||
self.nsec
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds a micro-second resolution timestamp with both the real time and cpu time.
|
||||
#[derive(Clone)]
|
||||
pub struct TimestampUs {
|
||||
/// Real time in microseconds.
|
||||
pub time_us: u64,
|
||||
/// Cpu time in microseconds.
|
||||
pub cputime_us: u64,
|
||||
}
|
||||
|
||||
impl Default for TimestampUs {
|
||||
fn default() -> TimestampUs {
|
||||
TimestampUs {
|
||||
time_us: get_time(ClockType::Monotonic) / 1000,
|
||||
cputime_us: get_time(ClockType::ProcessCpu) / 1000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a timestamp in nanoseconds from a monotonic clock.
|
||||
///
|
||||
/// Uses `_rdstc` on `x86_64` and [`get_time`](fn.get_time.html) on other architectures.
|
||||
#[allow(dead_code)]
|
||||
pub fn timestamp_cycles() -> u64 {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
// Safe because there's nothing that can go wrong with this call.
|
||||
unsafe {
|
||||
std::arch::x86_64::_rdtsc() as u64
|
||||
}
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
{
|
||||
get_time(ClockType::Monotonic)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a timestamp in nanoseconds based on the provided clock type.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `clock_type` - Identifier of the Linux Kernel clock on which to act.
|
||||
pub fn get_time(clock_type: ClockType) -> u64 {
|
||||
let mut time_struct = libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
// Safe because the parameters are valid.
|
||||
unsafe { libc::clock_gettime(clock_type.into(), &mut time_struct) };
|
||||
seconds_to_nanoseconds(time_struct.tv_sec).unwrap() as u64 + (time_struct.tv_nsec as u64)
|
||||
}
|
||||
|
||||
/// Converts a timestamp in seconds to an equivalent one in nanoseconds.
|
||||
/// Returns `None` if the conversion overflows.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - Timestamp in seconds.
|
||||
pub fn seconds_to_nanoseconds(value: i64) -> Option<i64> {
|
||||
value.checked_mul(NANOS_PER_SECOND as i64)
|
||||
}
|
||||
|
||||
/// A RTC device following the PL031 specification..
|
||||
pub struct RTC {
|
||||
previous_now: Instant,
|
||||
tick_offset: i64,
|
||||
// This is used for implementing the RTC alarm. However, in Firecracker we do not need it.
|
||||
match_value: u32,
|
||||
// Writes to this register load an update value into the RTC.
|
||||
load: u32,
|
||||
imsc: u32,
|
||||
ris: u32,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
}
|
||||
|
||||
impl RTC {
|
||||
/// Constructs an AMBA PL031 RTC device.
|
||||
pub fn new(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> RTC {
|
||||
RTC {
|
||||
// This is used only for duration measuring purposes.
|
||||
previous_now: Instant::now(),
|
||||
tick_offset: get_time(ClockType::Real) as i64,
|
||||
match_value: 0,
|
||||
load: 0,
|
||||
imsc: 0,
|
||||
ris: 0,
|
||||
interrupt,
|
||||
}
|
||||
}
|
||||
|
||||
fn trigger_interrupt(&mut self) -> Result<()> {
|
||||
self.interrupt.trigger(0).map_err(Error::InterruptFailure)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_time(&self) -> u32 {
|
||||
let ts = (self.tick_offset as i128)
|
||||
+ (Instant::now().duration_since(self.previous_now).as_nanos() as i128);
|
||||
(ts / NANOS_PER_SECOND as i128) as u32
|
||||
}
|
||||
|
||||
fn handle_write(&mut self, offset: u64, val: u32) -> Result<()> {
|
||||
match offset {
|
||||
RTCMR => {
|
||||
// The MR register is used for implementing the RTC alarm. A real time clock alarm is
|
||||
// a feature that can be used to allow a computer to 'wake up' after shut down to execute
|
||||
// tasks every day or on a certain day. It can sometimes be found in the 'Power Management'
|
||||
// section of a motherboard's BIOS setup. This is functionality that extends beyond
|
||||
// Firecracker intended use. However, we increment a metric just in case.
|
||||
self.match_value = val;
|
||||
}
|
||||
RTCLR => {
|
||||
self.load = val;
|
||||
self.previous_now = Instant::now();
|
||||
// If the unwrap fails, then the internal value of the clock has been corrupted and
|
||||
// we want to terminate the execution of the process.
|
||||
self.tick_offset = seconds_to_nanoseconds(i64::from(val)).unwrap();
|
||||
}
|
||||
RTCIMSC => {
|
||||
self.imsc = val & 1;
|
||||
self.trigger_interrupt()?;
|
||||
}
|
||||
RTCICR => {
|
||||
// As per above mentioned doc, the interrupt is cleared by writing any data value to
|
||||
// the Interrupt Clear Register.
|
||||
self.ris = 0;
|
||||
self.trigger_interrupt()?;
|
||||
}
|
||||
RTCCR => (), // ignore attempts to turn off the timer.
|
||||
o => {
|
||||
return Err(Error::BadWriteOffset(o));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for RTC {
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
let v;
|
||||
let mut read_ok = true;
|
||||
|
||||
if offset < AMBA_ID_HIGH && offset >= AMBA_ID_LOW {
|
||||
let index = ((offset - AMBA_ID_LOW) >> 2) as usize;
|
||||
v = u32::from(PL031_ID[index]);
|
||||
} else {
|
||||
v = match offset {
|
||||
RTCDR => self.get_time(),
|
||||
RTCMR => {
|
||||
// Even though we are not implementing RTC alarm we return the last value
|
||||
self.match_value
|
||||
}
|
||||
RTCLR => self.load,
|
||||
RTCCR => 1, // RTC is always enabled.
|
||||
RTCIMSC => self.imsc,
|
||||
RTCRIS => self.ris,
|
||||
RTCMIS => self.ris & self.imsc,
|
||||
_ => {
|
||||
read_ok = false;
|
||||
0
|
||||
}
|
||||
};
|
||||
}
|
||||
if read_ok && data.len() <= 4 {
|
||||
write_le_u32(data, v);
|
||||
} else {
|
||||
warn!(
|
||||
"Invalid RTC PL031 read: offset {}, data length {}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
if data.len() <= 4 {
|
||||
let v = read_le_u32(&data[..]);
|
||||
if let Err(e) = self.handle_write(offset, v) {
|
||||
warn!("Failed to write to RTC PL031 device: {}", e);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Invalid RTC PL031 write: offset {}, data length {}",
|
||||
offset,
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const LEGACY_RTC_MAPPED_IO_START: u64 = 0x0901_0000;
|
||||
|
||||
#[test]
|
||||
fn test_get_time() {
|
||||
for _ in 0..1000 {
|
||||
assert!(get_time(ClockType::Monotonic) <= get_time(ClockType::Monotonic));
|
||||
}
|
||||
|
||||
for _ in 0..1000 {
|
||||
assert!(get_time(ClockType::ProcessCpu) <= get_time(ClockType::ProcessCpu));
|
||||
}
|
||||
|
||||
for _ in 0..1000 {
|
||||
assert!(get_time(ClockType::ThreadCpu) <= get_time(ClockType::ThreadCpu));
|
||||
}
|
||||
|
||||
assert_ne!(get_time(ClockType::Real), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_time_display() {
|
||||
let local_time = LocalTime {
|
||||
sec: 30,
|
||||
min: 15,
|
||||
hour: 10,
|
||||
mday: 4,
|
||||
mon: 6,
|
||||
year: 119,
|
||||
nsec: 123_456_789,
|
||||
};
|
||||
assert_eq!(
|
||||
String::from("2019-07-04T10:15:30.123456789"),
|
||||
local_time.to_string()
|
||||
);
|
||||
|
||||
let local_time = LocalTime {
|
||||
sec: 5,
|
||||
min: 5,
|
||||
hour: 5,
|
||||
mday: 23,
|
||||
mon: 7,
|
||||
year: 44,
|
||||
nsec: 123,
|
||||
};
|
||||
assert_eq!(
|
||||
String::from("1944-08-23T05:05:05.000000123"),
|
||||
local_time.to_string()
|
||||
);
|
||||
|
||||
let local_time = LocalTime::now();
|
||||
assert!(local_time.mon >= 0 && local_time.mon <= 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seconds_to_nanoseconds() {
|
||||
assert_eq!(
|
||||
seconds_to_nanoseconds(100).unwrap() as u64,
|
||||
100 * NANOS_PER_SECOND
|
||||
);
|
||||
|
||||
assert!(seconds_to_nanoseconds(9_223_372_037).is_none());
|
||||
}
|
||||
|
||||
struct TestInterrupt {
|
||||
event_fd: EventFd,
|
||||
}
|
||||
|
||||
impl InterruptSourceGroup for TestInterrupt {
|
||||
fn trigger(&self, _index: InterruptIndex) -> result::Result<(), std::io::Error> {
|
||||
self.event_fd.write(1)
|
||||
}
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
_index: InterruptIndex,
|
||||
_config: InterruptSourceConfig,
|
||||
) -> result::Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self, _index: InterruptIndex) -> Option<&EventFd> {
|
||||
Some(&self.event_fd)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestInterrupt {
|
||||
fn new(event_fd: EventFd) -> Self {
|
||||
TestInterrupt { event_fd }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rtc_read_write_and_event() {
|
||||
let intr_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap();
|
||||
|
||||
let mut rtc = RTC::new(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
let mut data = [0; 4];
|
||||
|
||||
// Read and write to the MR register.
|
||||
write_le_u32(&mut data, 123);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCMR, &mut data);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCMR, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
assert_eq!(v, 123);
|
||||
|
||||
// Read and write to the LR register.
|
||||
let v = get_time(ClockType::Real);
|
||||
write_le_u32(&mut data, (v / NANOS_PER_SECOND) as u32);
|
||||
let previous_now_before = rtc.previous_now;
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCLR, &mut data);
|
||||
|
||||
assert!(rtc.previous_now > previous_now_before);
|
||||
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCLR, &mut data);
|
||||
let v_read = read_le_u32(&data[..]);
|
||||
assert_eq!((v / NANOS_PER_SECOND) as u32, v_read);
|
||||
|
||||
// Read and write to IMSC register.
|
||||
// Test with non zero value.
|
||||
let non_zero = 1;
|
||||
write_le_u32(&mut data, non_zero);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||
// The interrupt line should be on.
|
||||
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() == 1);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
assert_eq!(non_zero & 1, v);
|
||||
|
||||
// Now test with 0.
|
||||
write_le_u32(&mut data, 0);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCIMSC, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
assert_eq!(0, v);
|
||||
|
||||
// Read and write to the ICR register.
|
||||
write_le_u32(&mut data, 1);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data);
|
||||
// The interrupt line should be on.
|
||||
assert!(rtc.interrupt.notifier(0).unwrap().read().unwrap() > 1);
|
||||
let v_before = read_le_u32(&data[..]);
|
||||
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCICR, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
// ICR is a write only register. Data received should stay equal to data sent.
|
||||
assert_eq!(v, v_before);
|
||||
|
||||
// Attempts to turn off the RTC should not go through.
|
||||
write_le_u32(&mut data, 0);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, RTCCR, &mut data);
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, RTCCR, &mut data);
|
||||
let v = read_le_u32(&data[..]);
|
||||
assert_eq!(v, 1);
|
||||
|
||||
// Attempts to write beyond the writable space. Using here the space used to read
|
||||
// the CID and PID from.
|
||||
write_le_u32(&mut data, 0);
|
||||
rtc.write(LEGACY_RTC_MAPPED_IO_START, AMBA_ID_LOW, &mut data);
|
||||
// However, reading from the AMBA_ID_LOW should succeed upon read.
|
||||
|
||||
let mut data = [0; 4];
|
||||
rtc.read(LEGACY_RTC_MAPPED_IO_START, AMBA_ID_LOW, &mut data);
|
||||
let index = AMBA_ID_LOW + 3;
|
||||
assert_eq!(data[0], PL031_ID[((index - AMBA_ID_LOW) >> 2) as usize]);
|
||||
}
|
||||
|
||||
macro_rules! byte_order_test_read_write {
|
||||
($test_name: ident, $write_fn_name: ident, $read_fn_name: ident, $is_be: expr, $data_type: ty) => {
|
||||
#[test]
|
||||
fn $test_name() {
|
||||
#[allow(overflowing_literals)]
|
||||
let test_cases = [
|
||||
(
|
||||
0x0123_4567_89AB_CDEF as u64,
|
||||
[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef],
|
||||
),
|
||||
(
|
||||
0x0000_0000_0000_0000 as u64,
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||||
),
|
||||
(
|
||||
0x1923_2345_ABF3_CCD4 as u64,
|
||||
[0x19, 0x23, 0x23, 0x45, 0xAB, 0xF3, 0xCC, 0xD4],
|
||||
),
|
||||
(
|
||||
0x0FF0_0FF0_0FF0_0FF0 as u64,
|
||||
[0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0],
|
||||
),
|
||||
(
|
||||
0xFFFF_FFFF_FFFF_FFFF as u64,
|
||||
[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
|
||||
),
|
||||
(
|
||||
0x89AB_12D4_C2D2_09BB as u64,
|
||||
[0x89, 0xAB, 0x12, 0xD4, 0xC2, 0xD2, 0x09, 0xBB],
|
||||
),
|
||||
];
|
||||
|
||||
let type_size = std::mem::size_of::<$data_type>();
|
||||
for (test_val, v_arr) in &test_cases {
|
||||
let v = *test_val as $data_type;
|
||||
let cmp_iter: Box<dyn Iterator<Item = _>> = if $is_be {
|
||||
Box::new(v_arr[(8 - type_size)..].iter())
|
||||
} else {
|
||||
Box::new(v_arr.iter().rev())
|
||||
};
|
||||
// test write
|
||||
let mut write_arr = vec![Default::default(); type_size];
|
||||
$write_fn_name(&mut write_arr, v);
|
||||
for (cmp, cur) in cmp_iter.zip(write_arr.iter()) {
|
||||
assert_eq!(*cmp, *cur as u8)
|
||||
}
|
||||
// test read
|
||||
let read_val = $read_fn_name(&write_arr);
|
||||
assert_eq!(v, read_val);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
byte_order_test_read_write!(test_le_u16, write_le_u16, read_le_u16, false, u16);
|
||||
byte_order_test_read_write!(test_le_u32, write_le_u32, read_le_u32, false, u32);
|
||||
byte_order_test_read_write!(test_le_u64, write_le_u64, read_le_u64, false, u64);
|
||||
byte_order_test_read_write!(test_le_i32, write_le_i32, read_le_i32, false, i32);
|
||||
byte_order_test_read_write!(test_be_u16, write_be_u16, read_be_u16, true, u16);
|
||||
byte_order_test_read_write!(test_be_u32, write_be_u32, read_be_u32, true, u32);
|
||||
}
|
||||
@@ -6,10 +6,15 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use crate::BusDevice;
|
||||
use anyhow::anyhow;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::{io, result};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::errno::Result;
|
||||
|
||||
const LOOP_SIZE: usize = 0x40;
|
||||
@@ -55,6 +60,7 @@ const DEFAULT_BAUD_DIVISOR: u16 = 12; // 9600 bps
|
||||
/// This can optionally write the guest's output to a Write trait object. To send input to the
|
||||
/// guest, use `queue_input_bytes`.
|
||||
pub struct Serial {
|
||||
id: String,
|
||||
interrupt_enable: u8,
|
||||
interrupt_identification: u8,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
@@ -68,12 +74,27 @@ pub struct Serial {
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SerialState {
|
||||
interrupt_enable: u8,
|
||||
interrupt_identification: u8,
|
||||
line_control: u8,
|
||||
line_status: u8,
|
||||
modem_control: u8,
|
||||
modem_status: u8,
|
||||
scratch: u8,
|
||||
baud_divisor: u16,
|
||||
in_buffer: VecDeque<u8>,
|
||||
}
|
||||
|
||||
impl Serial {
|
||||
pub fn new(
|
||||
id: String,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
out: Option<Box<dyn io::Write + Send>>,
|
||||
) -> Serial {
|
||||
Serial {
|
||||
id,
|
||||
interrupt_enable: 0,
|
||||
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
||||
interrupt,
|
||||
@@ -90,15 +111,16 @@ impl Serial {
|
||||
|
||||
/// Constructs a Serial port ready for output.
|
||||
pub fn new_out(
|
||||
id: String,
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
out: Box<dyn io::Write + Send>,
|
||||
) -> Serial {
|
||||
Self::new(interrupt, Some(out))
|
||||
Self::new(id, interrupt, Some(out))
|
||||
}
|
||||
|
||||
/// Constructs a Serial port with no connected output.
|
||||
pub fn new_sink(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
|
||||
Self::new(interrupt, None)
|
||||
pub fn new_sink(id: String, interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
|
||||
Self::new(id, interrupt, None)
|
||||
}
|
||||
|
||||
/// Queues raw bytes for the guest to read and signals the interrupt if the line status would
|
||||
@@ -194,6 +216,32 @@ impl Serial {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn state(&self) -> SerialState {
|
||||
SerialState {
|
||||
interrupt_enable: self.interrupt_enable,
|
||||
interrupt_identification: self.interrupt_identification,
|
||||
line_control: self.line_control,
|
||||
line_status: self.line_status,
|
||||
modem_control: self.modem_control,
|
||||
modem_status: self.modem_status,
|
||||
scratch: self.scratch,
|
||||
baud_divisor: self.baud_divisor,
|
||||
in_buffer: self.in_buffer.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &SerialState) {
|
||||
self.interrupt_enable = state.interrupt_enable;
|
||||
self.interrupt_identification = state.interrupt_identification;
|
||||
self.line_control = state.line_control;
|
||||
self.line_status = state.line_status;
|
||||
self.modem_control = state.modem_control;
|
||||
self.modem_status = state.modem_status;
|
||||
self.scratch = state.scratch;
|
||||
self.baud_divisor = state.baud_divisor;
|
||||
self.in_buffer = state.in_buffer.clone();
|
||||
}
|
||||
}
|
||||
|
||||
impl BusDevice for Serial {
|
||||
@@ -236,6 +284,51 @@ impl BusDevice for Serial {
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Serial {
|
||||
fn id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut serial_snapshot = Snapshot::new(self.id.as_str());
|
||||
serial_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(serial_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(serial_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||
let serial_state = match serde_json::from_slice(&serial_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize SERIAL {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.set_state(&serial_state);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find the serial snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Serial {}
|
||||
impl Transportable for Serial {}
|
||||
impl Migratable for Serial {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -244,6 +337,8 @@ mod tests {
|
||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const SERIAL_NAME: &str = "serial";
|
||||
|
||||
struct TestInterrupt {
|
||||
event_fd: EventFd,
|
||||
}
|
||||
@@ -294,6 +389,7 @@ mod tests {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let serial_out = SharedBuffer::new();
|
||||
let mut serial = Serial::new_out(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
Box::new(serial_out.clone()),
|
||||
);
|
||||
@@ -313,6 +409,7 @@ mod tests {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let serial_out = SharedBuffer::new();
|
||||
let mut serial = Serial::new_out(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
Box::new(serial_out.clone()),
|
||||
);
|
||||
@@ -350,9 +447,10 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_thr() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
@@ -371,9 +469,10 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_dlab() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]);
|
||||
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
|
||||
@@ -391,9 +490,10 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_modem() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
|
||||
serial.write(0, DATA as u64, &['a' as u8]);
|
||||
@@ -416,9 +516,10 @@ mod tests {
|
||||
#[test]
|
||||
fn serial_scratch() {
|
||||
let intr_evt = EventFd::new(0).unwrap();
|
||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
||||
intr_evt.try_clone().unwrap(),
|
||||
))));
|
||||
let mut serial = Serial::new_sink(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
serial.write(0, SCR as u64, &[0x12 as u8]);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
//! Emulates virtual and hardware devices.
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
extern crate byteorder;
|
||||
@@ -13,9 +14,16 @@ extern crate epoll;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[cfg(feature = "acpi")]
|
||||
extern crate acpi_tables;
|
||||
extern crate serde;
|
||||
extern crate vm_device;
|
||||
extern crate vm_memory;
|
||||
extern crate vm_migration;
|
||||
extern crate vmm_sys_util;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
@@ -23,6 +31,10 @@ use std::io;
|
||||
#[cfg(feature = "acpi")]
|
||||
mod acpi;
|
||||
mod bus;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod gic;
|
||||
pub mod interrupt_controller;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod ioapic;
|
||||
pub mod legacy;
|
||||
|
||||
@@ -73,5 +85,6 @@ bitflags! {
|
||||
const NO_DEVICES_CHANGED = 0;
|
||||
const CPU_DEVICES_CHANGED = 0b1;
|
||||
const MEMORY_DEVICES_CHANGED = 0b10;
|
||||
const PCI_DEVICES_CHANGED = 0b100;
|
||||
}
|
||||
}
|
||||
|
||||
36
docs/api.md
36
docs/api.md
@@ -78,18 +78,25 @@ Shut the VMM down | `/vmm.shutdown` | N/A | N/A
|
||||
|
||||
#### 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
|
||||
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 | N/A
|
||||
Boot the VM | `/vm.boot` | N/A | N/A | The VM is created but not booted
|
||||
Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted
|
||||
Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted
|
||||
Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted
|
||||
Resume the VM | `/vm.resume` | N/A | N/A | The VM is paused
|
||||
Add/remove CPUs to/from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted
|
||||
Remove memory from the VM | `/vm.resize` | `/schemas/VmResize` | N/A | The VM is booted
|
||||
Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created
|
||||
Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | N/A | The VM is booted
|
||||
Remove VFIO PCI device from the VM | `/vm.remove-device` | `/schemas/VmRemoveDevice` | N/A | The VM is booted
|
||||
Add disk device to the VM | `/vm.add-disk` | `/schemas/DiskConfig` | N/A | The VM is booted
|
||||
Add fs device to the VM | `/vm.add-fs` | `/schemas/FsConfig` | N/A | The VM is booted
|
||||
Add pmem device to the VM | `/vm.add-pmem` | `/schemas/PmemConfig` | N/A | The VM is booted
|
||||
Add network device to the VM | `/vm.add-net` | `/schemas/NetConfig` | N/A | The VM is booted
|
||||
Add vsock device to the VM | `/vm.add-vsock` | `/schemas/VsockConfig` | N/A | The VM is booted
|
||||
|
||||
### REST API Examples
|
||||
|
||||
@@ -267,7 +274,7 @@ 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.
|
||||
end of an [MPSC](https://doc.rust-lang.org/std/sync/mpsc/) channel.
|
||||
|
||||
The sender of any internal API command is therefore responsible for:
|
||||
|
||||
@@ -347,7 +354,7 @@ APIs work together, let's look at a complete VM creation flow, from the
|
||||
} else {
|
||||
Err(ApiError::VmAlreadyCreated)
|
||||
};
|
||||
|
||||
|
||||
sender.send(response).map_err(Error::ApiResponseSend)?;
|
||||
}
|
||||
```
|
||||
@@ -368,4 +375,3 @@ APIs work together, let's look at a complete VM creation flow, from the
|
||||
user. This is abstracted by the
|
||||
[micro_http](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
|
||||
crate.
|
||||
|
||||
|
||||
64
docs/arm64.md
Normal file
64
docs/arm64.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# How to build and run Cloud-hypervisor on Arm64
|
||||
|
||||
Cloud-hypervisor is partially enabled on AArch64 architecture.
|
||||
Although all features are not ready yet, you can begin to test Cloud-hypervisor on a Arm64 host by following this guide.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
On Arm64 machines, Cloud-hypervisor depends on an external library `libfdt-dev` for generating Flatted Device Tree (FDT).
|
||||
|
||||
The long-term plan is to replace `libfdt-dev` with some pure-Rust component to get rid of such dependency.
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install libfdt-dev
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
Before building, a hack trick need to be performed to get rid of some build error in vmm component. See [this](https://github.com/cloud-hypervisor/kvm-bindings/pull/1) for more info about this temporary workaround.
|
||||
|
||||
```bash
|
||||
sed -i 's/"with-serde",\ //g' vmm/Cargo.toml
|
||||
```
|
||||
|
||||
The support of AArch64 is in very early stage, only Virtio devices with MMIO tranport is available.
|
||||
|
||||
```bash
|
||||
cargo build --no-default-features --features "mmio"
|
||||
```
|
||||
|
||||
## Image
|
||||
|
||||
Download kernel binary and rootfs image from AWS.
|
||||
|
||||
```bash
|
||||
wget https://s3.amazonaws.com/spec.ccfc.min/img/aarch64/ubuntu_with_ssh/fsfiles/xenial.rootfs.ext4 -O rootfs.img
|
||||
wget https://s3.amazonaws.com/spec.ccfc.min/img/aarch64/ubuntu_with_ssh/kernel/vmlinux.bin -O kernel.bin
|
||||
```
|
||||
|
||||
## Containerized build
|
||||
|
||||
If you want to build and test Cloud Hypervisor without having to install all the required dependencies, you can also turn to the development script: dev_cli.sh.
|
||||
|
||||
To build the development container:
|
||||
|
||||
```bash
|
||||
./scripts/dev_cli.sh build-container
|
||||
```
|
||||
|
||||
To build Cloud-hypervisor in the container:
|
||||
|
||||
```bash
|
||||
./scripts/dev_cli.sh build
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
Assuming you have built Cloud-hypervisor with the development container, a VM can be started with command:
|
||||
|
||||
```bash
|
||||
sudo target/debug/cloud-hypervisor --kernel kernel.bin --disk path=rootfs.ext4 --cmdline "keep_bootcon console=hvc0 reboot=k panic=1 pci=off root=/dev/vda rw" --cpus boot=4 --memory size=512M --seccomp false --serial file=serial.log --log-file log.log -vvv
|
||||
```
|
||||
|
||||
If the build was done out of the container, replace the binary path with `build/cargo_target/aarch64-unknown-linux-gnu/debug/cloud-hypervisor`.
|
||||
@@ -45,7 +45,7 @@ sudo swupd bundle-add os-installer
|
||||
wget https://download.clearlinux.org/current/config/image/cloudguest.yaml
|
||||
sed -i '/size: \"864M\"/d' cloudguest.yaml
|
||||
sed -i 's/\"800M\"/\"2G\"/g' cloudguest.yaml
|
||||
sed -i 's/bootloader,/bootloader,\n iperf,/g' cloudguest.yaml
|
||||
sed -i 's/bootloader,/bootloader,\n curl,\n iperf,/g' cloudguest.yaml
|
||||
sed -i 's/systemd-networkd-autostart/sysadmin-basic,\n systemd-networkd-autostart/g' cloudguest.yaml
|
||||
# Create the custom cloudguest image
|
||||
clr-installer -c cloudguest.yaml
|
||||
|
||||
@@ -59,21 +59,21 @@ Assuming you have `clear-kvm.img` and `custom-vmlinux.bin` on your system, here
|
||||
--memory "size=512,file=/dev/shm" \
|
||||
--disk path=clear-kvm.img \
|
||||
--kernel custom-vmlinux.bin \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||
--fs tag=myfs,sock=/tmp/virtiofs,num_queues=1,queue_size=512
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||
--fs tag=myfs,socket=/tmp/virtiofs,num_queues=1,queue_size=512
|
||||
```
|
||||
|
||||
By default, DAX is enabled with a cache window of 8GiB. You can specify a custom size (let's say 4GiB for this example) for the cache by explicitly setting DAX and the cache size:
|
||||
|
||||
```bash
|
||||
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512,dax=on,cache_size=4G
|
||||
--fs tag=virtiofs,socket=/tmp/virtiofs,num_queues=1,queue_size=512,dax=on,cache_size=4G
|
||||
|
||||
```
|
||||
|
||||
In case you don't want to use a shared window of cache to pass the shared files content, this means you will have to explicitly disable DAX with `dax=off`. Note that in this case, the `cache_size` parameter will be ignored.
|
||||
|
||||
```bash
|
||||
--fs tag=virtiofs,sock=/tmp/virtiofs,num_queues=1,queue_size=512,dax=off
|
||||
--fs tag=virtiofs,socket=/tmp/virtiofs,num_queues=1,queue_size=512,dax=off
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ Before issuing the API request it is necessary to run the following command insi
|
||||
root@ch-guest ~ # echo online | sudo tee /sys/devices/system/memory/auto_online_blocks
|
||||
```
|
||||
|
||||
To ask the VMM to add expand the RAM for the VM:
|
||||
To ask the VMM to add expand the RAM for the VM (request is in bytes):
|
||||
|
||||
```shell
|
||||
curl -H "Accept: application/json" -H "Content-Type: application/json" -i -XPUT --unix-socket /tmp/ch-socket -d "{ \"desired_vcpus\": 4, \"desired_ram\" : 3221225472}" http://localhost/api/v1/vm.resize
|
||||
|
||||
@@ -51,6 +51,11 @@ If the tap device is pre-created on host before guest boot up. To use multiple q
|
||||
[root@localhost ~]# ip tuntap add name ich0 mode tap multi_queue
|
||||
```
|
||||
|
||||
And the `--net` device should specify support for multiple queues. `num_queues` must be a multiple of 2 starting at least from 4 since multiple queues really means multiple queue pairs. We need at least 2 pairs for this configuration to be correct:
|
||||
|
||||
```bash
|
||||
--net tap=ich0,mac=a4:a1:c2:00:00:01,ip=192.168.4.2,mask=255.255.255.0,num_queues=4,queue_size=256
|
||||
```
|
||||
|
||||
## Configure the tap devices
|
||||
|
||||
|
||||
104
docs/vhost-user-blk-testing.md
Normal file
104
docs/vhost-user-blk-testing.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# How to test vhost-user-blk with SPDK
|
||||
|
||||
The purpose of this document is to illustrate how to test vhost-user-blk in Cloud Hypervisor with SPDK as the backend.
|
||||
|
||||
## Framework
|
||||
|
||||
It's a simple test to validate the block read/write between VM and block backend.
|
||||
```
|
||||
+----+----------+ +-------------+-----------+
|
||||
| | | | | |
|
||||
| |vhost-user|----------| vhost-user | dpdk |
|
||||
| |blk device| | port 1 | |
|
||||
| | | | | |
|
||||
| +----------+ +-------------+-----------+
|
||||
| | | |
|
||||
| vm | | spdk |
|
||||
| | | |
|
||||
+--+----------------------------------------------------+--+
|
||||
| | hugepages | |
|
||||
| +----------------------------------------------------+ |
|
||||
| |
|
||||
| host |
|
||||
| |
|
||||
+----------------------------------------------------------+
|
||||
```
|
||||
## Prerequisites
|
||||
|
||||
Prior to running the test, the following steps need to be performed.
|
||||
- Enable hugepages
|
||||
- Install SPDK
|
||||
|
||||
Here are some good references for detailing them.
|
||||
- spdk
|
||||
* https://spdk.io/doc/
|
||||
|
||||
## Test environment
|
||||
|
||||
The below test environment is based on ubuntu release(16.04.1 LTS), as for other system, please check related document.
|
||||
The test runs with multiple queue (MQ) support enabled, using 4 queues defined for both SPDK and the virtual machine.
|
||||
Here are the details on how the test can be run.
|
||||
|
||||
### The hugepages settings in host linux
|
||||
Add "default_hugepagesz=1G hugepagesz=1G hugepages=2" into host linux cmdline.
|
||||
As for how to change Ubuntu linux cmdline in grub file, please ref below link:
|
||||
https://www.ostechnix.com/configure-grub-2-boot-loader-settings-ubuntu-16-04/
|
||||
reboot Ubuntu
|
||||
sudo mount -t hugetlbfs -o pagesize=1G none /dev/hugepages
|
||||
|
||||
### Download the SPDK code
|
||||
git clone https://github.com/spdk/spdk
|
||||
cd spdk
|
||||
git submodule update --init
|
||||
|
||||
### Create the build dep
|
||||
./scripts/pkgdep.sh
|
||||
|
||||
### Build spdk
|
||||
./configure
|
||||
make
|
||||
|
||||
### Set the SPDk environment
|
||||
sudo HUGEMEM=2048 scripts/setup.sh
|
||||
sudo ./app/vhost/vhost -S /var/tmp -s 1024 -m 0x3 &
|
||||
|
||||
### Create 512M block device
|
||||
sudo scripts/rpc.py bdev_malloc_create 512 512 -b Malloc0
|
||||
sudo scripts/rpc.py vhost_create_blk_controller --cpumask 0x1 vhost.1 Malloc0
|
||||
|
||||
_Launch the VM_
|
||||
|
||||
VMs run in client mode. They connect to the socket created by the `dpdkvhostuser` in the SPDK backend.
|
||||
```bash
|
||||
# From the test terminal. We need to create one vhost-user-blk device for the --disk.
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=4 \
|
||||
--memory size=1024M,file=/dev/hugepages \
|
||||
--kernel linux/arch/x86/boot/compressed/vmlinux.bin \
|
||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3 iommu=off" \
|
||||
--disk "path=images/clear-kvm.img" "num_queues=4,queue_size=128,vhost_user=true,socket=/var/tmp/vhost.1" \
|
||||
--console off \
|
||||
--serial tty \
|
||||
--rng
|
||||
```
|
||||
|
||||
```bash
|
||||
# How to test the vhost-user-blk device with SPDK backend
|
||||
login in guest
|
||||
|
||||
# Use lsblk command to find out vhost-user-blk device
|
||||
lsblk
|
||||
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
|
||||
vda 253:0 0 8.5G 0 disk
|
||||
├─vda1 253:1 0 511M 0 part
|
||||
├─vda2 253:2 0 32M 0 part [SWAP]
|
||||
└─vda3 253:3 0 8G 0 part /
|
||||
vdb 253:16 0 512M 0 disk
|
||||
|
||||
The vhost-user-blk device is /dev/vdb
|
||||
|
||||
# How to do simple read/write test
|
||||
dd if=/dev/vdb of=/dev/null bs=2M iflag=direct
|
||||
dd of=/dev/vdb if=/dev/zero bs=2M oflag=direct count=256
|
||||
|
||||
If you want to do fio test, please install fio binary into guest. The detailed info is not listed here.
|
||||
132
docs/vhost-user-net-testing.md
Normal file
132
docs/vhost-user-net-testing.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# How to test Vhost-user net with OpenVSwitch/DPDK
|
||||
|
||||
The purpose of this document is to illustrate how to test vhost-user-net in cloud-hypervisor with OVS/DPDK as the backend.
|
||||
|
||||
## Framework
|
||||
|
||||
It's a simple test to validate the communication between two virtual machine, connecting them to vhost-user ports respectively provided by `OVS/DPDK`.
|
||||
```
|
||||
+----+----------+ +-------------+-----------+-------------+ +----------+----+
|
||||
| | | | | | | | | |
|
||||
| |vhost-user|----------| vhost-user | ovs | vhost-user |----------|vhost-user| |
|
||||
| |net device| | port 1 | | port 2 | |net device| |
|
||||
| | | | | | | | | |
|
||||
| +----------+ +-------------+-----------+-------------+ +----------+ |
|
||||
| | | | | |
|
||||
|vm1 | | dpdk | | vm2 |
|
||||
| | | | | |
|
||||
+--+---------------------------------------------------------------------------------------------+--+
|
||||
| | hugepages | |
|
||||
| +---------------------------------------------------------------------------------------------+ |
|
||||
| |
|
||||
| host |
|
||||
| |
|
||||
+---------------------------------------------------------------------------------------------------+
|
||||
```
|
||||
## Prerequisites
|
||||
|
||||
Prior to running the test, the following steps need to be performed.
|
||||
- Enable hugepages
|
||||
- Install DPDK
|
||||
- Install OVS
|
||||
|
||||
Here are some good references for detailing them.
|
||||
- Red Hat
|
||||
* https://wiki.qemu.org/Documentation/vhost-user-ovs-dpdk
|
||||
- Ubuntu server
|
||||
* https://help.ubuntu.com/lts/serverguide/DPDK.html
|
||||
* https://software.intel.com/en-us/articles/set-up-open-vswitch-with-dpdk-on-ubuntu-server
|
||||
|
||||
## Test
|
||||
The test runs with multiple queue (MQ) support enabled, using 2 pairs of TX/RX queues defined for both OVS and the virtual machine. Here are the details on how the test can be run.
|
||||
|
||||
_Setup OVS_
|
||||
|
||||
`ovs_test.sh` is created to setup and start OVS. OVS will provide the `dpdkvhostuser` backend running in server mode.
|
||||
```bash
|
||||
mkdir -p /var/run/openvswitch
|
||||
modprobe openvswitch
|
||||
killall ovsdb-server ovs-vswitchd
|
||||
rm -f /var/run/openvswitch/vhost-user*
|
||||
rm -f /etc/openvswitch/conf.db
|
||||
export DB_SOCK=/var/run/openvswitch/db.sock
|
||||
ovsdb-tool create /etc/openvswitch/conf.db /usr/share/openvswitch/vswitch.ovsschema
|
||||
ovsdb-server --remote=punix:$DB_SOCK --remote=db:Open_vSwitch,Open_vSwitch,manager_options --pidfile --detach
|
||||
ovs-vsctl --no-wait init
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-lcore-mask=0xf
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-socket-mem=1024
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true
|
||||
ovs-vsctl --no-wait set Open_vSwitch . other_config:pmd-cpu-mask=0xf
|
||||
ovs-vswitchd unix:$DB_SOCK --pidfile --detach --log-file=/var/log/openvswitch/ovs-vswitchd.log
|
||||
ovs-vsctl add-br ovsbr0 -- set bridge ovsbr0 datapath_type=netdev
|
||||
ovs-vsctl add-port ovsbr0 vhost-user1 -- set Interface vhost-user1 type=dpdkvhostuser
|
||||
ovs-vsctl add-port ovsbr0 vhost-user2 -- set Interface vhost-user2 type=dpdkvhostuser
|
||||
ovs-vsctl set Interface vhost-user1 options:n_rxq=2
|
||||
ovs-vsctl set Interface vhost-user2 options:n_rxq=2
|
||||
```
|
||||
_Run ovs_test.sh_
|
||||
```bash
|
||||
./ovs_test.sh
|
||||
```
|
||||
|
||||
_Launch the VMs_
|
||||
|
||||
VMs run in client mode. They connect to the socket created by the `dpdkvhostuser` backend.
|
||||
```bash
|
||||
# From one terminal. We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=2 \
|
||||
--memory size=512M,file=/dev/hugepages \
|
||||
--kernel vmlinux \
|
||||
--cmdline "reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--disk path=clear-kvm.img \
|
||||
--net "mac=52:54:00:02:d9:01,vhost_user=true,socket=/var/run/openvswitch/vhost-user1,num_queues=4"
|
||||
|
||||
# From another terminal. We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
|
||||
./cloud-hypervisor \
|
||||
--cpus boot=2 \
|
||||
--memory size=512M,file=/dev/hugepages \
|
||||
--kernel vmlinux \
|
||||
--cmdline "reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=/dev/vda3" \
|
||||
--disk path=clear-kvm.img \
|
||||
--net "mac=52:54:20:11:C5:02,vhost_user=true,socket=/var/run/openvswitch/vhost-user2,num_queues=4"
|
||||
```
|
||||
|
||||
_Setup VM1_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ip addr add 172.100.0.1/24 dev enp0s3
|
||||
```
|
||||
|
||||
_Setup VM2_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ip addr add 172.100.0.2/24 dev enp0s3
|
||||
```
|
||||
|
||||
_Ping VM1 from VM2_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ping 172.100.0.1
|
||||
```
|
||||
|
||||
_Ping VM2 from VM1_
|
||||
```bash
|
||||
# From inside the guest
|
||||
sudo ping 172.100.0.2
|
||||
```
|
||||
|
||||
__Result:__ At this point, VM1 and VM2 can ping each other successfully. We can now run `iperf3` test.
|
||||
|
||||
_Run VM1 as server_
|
||||
```bash
|
||||
# From inside the guest
|
||||
iperf3 -s -p 4444
|
||||
```
|
||||
|
||||
_Run VM2 as client_
|
||||
```bash
|
||||
# From inside the guest
|
||||
iperf3 -c 172.100.0.1 -t 30 -p 4444 &
|
||||
```
|
||||
|
||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.60"
|
||||
libc = "0.2.71"
|
||||
rand = "0.7.3"
|
||||
serde = "1.0.98"
|
||||
serde = "1.0.111"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
net_gen = { path = "../net_gen" }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.3.0"
|
||||
pnet = "0.25.0"
|
||||
serde_json = "1.0.46"
|
||||
pnet = "0.26.0"
|
||||
serde_json = "1.0.55"
|
||||
|
||||
@@ -9,6 +9,7 @@ use rand::Rng;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::result::Result;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Error};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
@@ -120,6 +121,18 @@ impl<'de> Deserialize<'de> for MacAddr {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MacAddrParseError {
|
||||
InvalidValue(String),
|
||||
}
|
||||
|
||||
impl FromStr for MacAddr {
|
||||
type Err = MacAddrParseError;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
MacAddr::parse_str(s).map_err(|_| MacAddrParseError::InvalidValue(s.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate serde_json;
|
||||
|
||||
@@ -5,28 +5,33 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use super::{create_sockaddr, create_socket, Error as NetUtilError, MacAddr};
|
||||
use mac::MAC_ADDR_LEN;
|
||||
use net_gen;
|
||||
use std::fs::File;
|
||||
use std::io::{Error as IoError, Read, Result as IoResult, Write};
|
||||
use std::net;
|
||||
use std::os::raw::*;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
|
||||
use super::{create_sockaddr, create_socket, Error as NetUtilError};
|
||||
use libc;
|
||||
use net_gen;
|
||||
use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Couldn't open /dev/net/tun.
|
||||
OpenTun(IoError),
|
||||
/// Unable to create tap interface.
|
||||
CreateTap(IoError),
|
||||
/// Unable to configure tap interface.
|
||||
ConfigureTap(IoError),
|
||||
/// Unable to retrieve features.
|
||||
GetFeatures(IoError),
|
||||
/// Missing multiqueue support in the kernel.
|
||||
MultiQueueKernelSupport,
|
||||
/// ioctl failed.
|
||||
IoctlError(IoError),
|
||||
/// Failed to create a socket.
|
||||
NetUtil(NetUtilError),
|
||||
InvalidIfname,
|
||||
/// Error parsing MAC data
|
||||
MacParsing(()),
|
||||
}
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
@@ -96,6 +101,20 @@ impl Tap {
|
||||
// We just checked that the fd is valid.
|
||||
let tuntap = unsafe { File::from_raw_fd(fd) };
|
||||
|
||||
// Let's validate some features before going any further.
|
||||
// ioctl is safe since we call it with a valid tap fd and check the return
|
||||
// value.
|
||||
let mut features = 0;
|
||||
let ret = unsafe { ioctl_with_mut_ref(&tuntap, net_gen::TUNGETFEATURES(), &mut features) };
|
||||
if ret < 0 {
|
||||
return Err(Error::GetFeatures(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
// Check if the user parameters match the kernel support for MQ
|
||||
if (features & net_gen::IFF_MULTI_QUEUE == 0) && num_queue_pairs > 1 {
|
||||
return Err(Error::MultiQueueKernelSupport);
|
||||
}
|
||||
|
||||
// This is pretty messy because of the unions used by ifreq. Since we
|
||||
// don't call as_mut on the same union field more than once, this block
|
||||
// is safe.
|
||||
@@ -115,9 +134,8 @@ impl Tap {
|
||||
// ioctl is safe since we call it with a valid tap fd and check the return
|
||||
// value.
|
||||
let ret = unsafe { ioctl_with_mut_ref(&tuntap, net_gen::TUNSETIFF(), &mut ifreq) };
|
||||
|
||||
if ret < 0 {
|
||||
return Err(Error::CreateTap(IoError::last_os_error()));
|
||||
return Err(Error::ConfigureTap(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
let if_name_temp = unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() };
|
||||
@@ -159,6 +177,72 @@ impl Tap {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set mac addr for tap interface.
|
||||
pub fn set_mac_addr(&self, addr: MacAddr) -> Result<()> {
|
||||
// Checking if the mac address already matches the desired one
|
||||
// is useful to avoid making the "set ioctl" in the case where
|
||||
// the VMM is running without the privilege to do that.
|
||||
// In practice this comes from a reboot after the configuration
|
||||
// has been update with the kernel generated address.
|
||||
if self.get_mac_addr()? == addr {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||
|
||||
let mut ifreq = self.get_ifreq();
|
||||
|
||||
// ioctl is safe. Called with a valid sock fd, and we check the return.
|
||||
#[allow(clippy::cast_lossless)]
|
||||
let ret =
|
||||
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFHWADDR as c_ulong, &ifreq) };
|
||||
if ret < 0 {
|
||||
return Err(Error::IoctlError(IoError::last_os_error()));
|
||||
}
|
||||
// We only access one field of the ifru union, hence this is safe.
|
||||
unsafe {
|
||||
let ifru_hwaddr = ifreq.ifr_ifru.ifru_hwaddr.as_mut();
|
||||
for (i, v) in addr.get_bytes().iter().enumerate() {
|
||||
ifru_hwaddr.sa_data[i] = *v as c_char;
|
||||
}
|
||||
}
|
||||
|
||||
// ioctl is safe. Called with a valid sock fd, and we check the return.
|
||||
#[allow(clippy::cast_lossless)]
|
||||
let ret =
|
||||
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFHWADDR as c_ulong, &ifreq) };
|
||||
if ret < 0 {
|
||||
return Err(Error::IoctlError(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get mac addr for tap interface.
|
||||
pub fn get_mac_addr(&self) -> Result<MacAddr> {
|
||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||
|
||||
let ifreq = self.get_ifreq();
|
||||
|
||||
// ioctl is safe. Called with a valid sock fd, and we check the return.
|
||||
#[allow(clippy::cast_lossless)]
|
||||
let ret =
|
||||
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFHWADDR as c_ulong, &ifreq) };
|
||||
if ret < 0 {
|
||||
return Err(Error::IoctlError(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
// We only access one field of the ifru union, hence this is safe.
|
||||
let addr = unsafe {
|
||||
let ifru_hwaddr = ifreq.ifr_ifru.ifru_hwaddr.as_ref();
|
||||
MacAddr::from_bytes(
|
||||
&*(&ifru_hwaddr.sa_data[0..MAC_ADDR_LEN] as *const _ as *const [u8]),
|
||||
)
|
||||
.map_err(Error::MacParsing)?
|
||||
};
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
/// Set the netmask for the subnet that the tap interface will exist on.
|
||||
pub fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()> {
|
||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||
@@ -202,9 +286,26 @@ impl Tap {
|
||||
|
||||
let mut ifreq = self.get_ifreq();
|
||||
|
||||
#[allow(clippy::cast_lossless)]
|
||||
let ret =
|
||||
unsafe { ioctl_with_ref(&sock, net_gen::sockios::SIOCGIFFLAGS as c_ulong, &ifreq) };
|
||||
if ret < 0 {
|
||||
return Err(Error::IoctlError(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
// If TAP device is already up don't try and enable it
|
||||
let ifru_flags = unsafe { ifreq.ifr_ifru.ifru_flags.as_ref() };
|
||||
if ifru_flags
|
||||
& (net_gen::net_device_flags_IFF_UP | net_gen::net_device_flags_IFF_RUNNING) as i16
|
||||
== (net_gen::net_device_flags_IFF_UP | net_gen::net_device_flags_IFF_RUNNING) as i16
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// We only access one field of the ifru union, hence this is safe.
|
||||
unsafe {
|
||||
let ifru_flags = ifreq.ifr_ifru.ifru_flags.as_mut();
|
||||
|
||||
*ifru_flags =
|
||||
(net_gen::net_device_flags_IFF_UP | net_gen::net_device_flags_IFF_RUNNING) as i16;
|
||||
}
|
||||
@@ -426,7 +527,7 @@ mod tests {
|
||||
let interface = interfaces.into_iter().find(interface_name_matches).unwrap();
|
||||
|
||||
if let Ok(Ethernet(tx, rx)) = datalink::channel(&interface, Default::default()) {
|
||||
(interface.mac_address(), tx, rx)
|
||||
(interface.mac.unwrap(), tx, rx)
|
||||
} else {
|
||||
panic!("datalink channel error or unhandled channel type");
|
||||
}
|
||||
|
||||
@@ -5,10 +5,23 @@ authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
anyhow = "1.0"
|
||||
byteorder = "1.3.4"
|
||||
devices = { path = "../devices" }
|
||||
libc = "0.2.60"
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch" }
|
||||
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||
vfio-ioctls = { git = "https://github.com/cloud-hypervisor/vfio-ioctls", branch = "ch" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
libc = "0.2.71"
|
||||
log = "0.4.8"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
|
||||
vm-memory = "0.2.1"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
|
||||
[dependencies.vfio-bindings]
|
||||
git = "https://github.com/rust-vmm/vfio-bindings"
|
||||
features = ["fam-wrappers"]
|
||||
|
||||
143
pci/src/bus.rs
143
pci/src/bus.rs
@@ -8,14 +8,15 @@ use crate::configuration::{
|
||||
use crate::device::{DeviceRelocation, Error as PciDeviceError, PciDevice};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use devices::BusDevice;
|
||||
use std;
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||
|
||||
const VENDOR_ID_INTEL: u16 = 0x8086;
|
||||
const DEVICE_ID_INTEL_VIRT_PCIE_HOST: u16 = 0x0d57;
|
||||
const NUM_DEVICE_IDS: usize = 32;
|
||||
|
||||
/// Errors for device manager.
|
||||
#[derive(Debug)]
|
||||
@@ -28,6 +29,12 @@ pub enum PciRootError {
|
||||
PioInsert(devices::BusError),
|
||||
/// Could not add a device to the mmio bus.
|
||||
MmioInsert(devices::BusError),
|
||||
/// Could not find an available device slot on the PCI bus.
|
||||
NoPciDeviceSlotAvailable,
|
||||
/// Invalid PCI device identifier provided.
|
||||
InvalidPciDeviceSlot(usize),
|
||||
/// Valid PCI device identifier but already used.
|
||||
AlreadyInUsePciDeviceSlot(usize),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, PciRootError>;
|
||||
|
||||
@@ -47,6 +54,7 @@ impl PciRoot {
|
||||
config: PciConfiguration::new(
|
||||
VENDOR_ID_INTEL,
|
||||
DEVICE_ID_INTEL_VIRT_PCIE_HOST,
|
||||
0,
|
||||
PciClassCode::BridgeDevice,
|
||||
&PciBridgeSubclass::HostBridge,
|
||||
None,
|
||||
@@ -79,19 +87,23 @@ impl PciDevice for PciRoot {
|
||||
pub struct PciBus {
|
||||
/// Devices attached to this bus.
|
||||
/// Device 0 is host bridge.
|
||||
devices: Vec<Arc<Mutex<dyn PciDevice>>>,
|
||||
device_reloc: Weak<dyn DeviceRelocation>,
|
||||
devices: HashMap<u32, Arc<Mutex<dyn PciDevice>>>,
|
||||
device_reloc: Arc<dyn DeviceRelocation>,
|
||||
device_ids: Vec<bool>,
|
||||
}
|
||||
|
||||
impl PciBus {
|
||||
pub fn new(pci_root: PciRoot, device_reloc: Weak<dyn DeviceRelocation>) -> Self {
|
||||
let mut devices: Vec<Arc<Mutex<dyn PciDevice>>> = Vec::new();
|
||||
pub fn new(pci_root: PciRoot, device_reloc: Arc<dyn DeviceRelocation>) -> Self {
|
||||
let mut devices: HashMap<u32, Arc<Mutex<dyn PciDevice>>> = HashMap::new();
|
||||
let mut device_ids: Vec<bool> = vec![false; NUM_DEVICE_IDS];
|
||||
|
||||
devices.push(Arc::new(Mutex::new(pci_root)));
|
||||
devices.insert(0, Arc::new(Mutex::new(pci_root)));
|
||||
device_ids[0] = true;
|
||||
|
||||
PciBus {
|
||||
devices,
|
||||
device_reloc,
|
||||
device_ids,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,13 +131,51 @@ impl PciBus {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_device(&mut self, device: Arc<Mutex<dyn PciDevice>>) -> Result<()> {
|
||||
self.devices.push(device);
|
||||
pub fn add_device(
|
||||
&mut self,
|
||||
pci_device_bdf: u32,
|
||||
device: Arc<Mutex<dyn PciDevice>>,
|
||||
) -> Result<()> {
|
||||
self.devices.insert(pci_device_bdf >> 3, device);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_device_id(&self) -> u32 {
|
||||
self.devices.len() as u32
|
||||
pub fn remove_by_device(&mut self, device: &Arc<Mutex<dyn PciDevice>>) -> Result<()> {
|
||||
self.devices.retain(|_, dev| !Arc::ptr_eq(dev, device));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_device_id(&mut self) -> Result<u32> {
|
||||
for (idx, device_id) in self.device_ids.iter_mut().enumerate() {
|
||||
if !(*device_id) {
|
||||
*device_id = true;
|
||||
return Ok(idx as u32);
|
||||
}
|
||||
}
|
||||
|
||||
Err(PciRootError::NoPciDeviceSlotAvailable)
|
||||
}
|
||||
|
||||
pub fn get_device_id(&mut self, id: usize) -> Result<()> {
|
||||
if id < NUM_DEVICE_IDS {
|
||||
if !self.device_ids[id] {
|
||||
self.device_ids[id] = true;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PciRootError::AlreadyInUsePciDeviceSlot(id))
|
||||
}
|
||||
} else {
|
||||
Err(PciRootError::InvalidPciDeviceSlot(id))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put_device_id(&mut self, id: usize) -> Result<()> {
|
||||
if id < NUM_DEVICE_IDS {
|
||||
self.device_ids[id] = false;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PciRootError::InvalidPciDeviceSlot(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +200,7 @@ impl PciConfigIo {
|
||||
}
|
||||
|
||||
let (bus, device, function, register) =
|
||||
parse_config_address(self.config_address & !0x8000_0000);
|
||||
parse_io_config_address(self.config_address & !0x8000_0000);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
@@ -166,7 +216,7 @@ impl PciConfigIo {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.devices
|
||||
.get(device)
|
||||
.get(&(device as u32))
|
||||
.map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
@@ -183,7 +233,7 @@ impl PciConfigIo {
|
||||
}
|
||||
|
||||
let (bus, device, _function, register) =
|
||||
parse_config_address(self.config_address & !0x8000_0000);
|
||||
parse_io_config_address(self.config_address & !0x8000_0000);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
@@ -191,20 +241,23 @@ impl PciConfigIo {
|
||||
}
|
||||
|
||||
let pci_bus = self.pci_bus.lock().unwrap();
|
||||
if let Some(d) = pci_bus.devices.get(device) {
|
||||
if let Some(d) = pci_bus.devices.get(&(device as u32)) {
|
||||
let mut device = d.lock().unwrap();
|
||||
|
||||
// Find out if one of the device's BAR is being reprogrammed, and
|
||||
// reprogram it if needed.
|
||||
if let Some(params) = device.detect_bar_reprogramming(register, data) {
|
||||
if let Err(e) = pci_bus.device_reloc.upgrade().unwrap().move_bar(
|
||||
if let Err(e) = pci_bus.device_reloc.move_bar(
|
||||
params.old_base,
|
||||
params.new_base,
|
||||
params.len,
|
||||
device.deref_mut(),
|
||||
params.region_type,
|
||||
) {
|
||||
error!("Failed moving device BAR: {}", e);
|
||||
error!(
|
||||
"Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x})",
|
||||
e, params.old_base, params.new_base, params.len
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +330,7 @@ impl PciConfigMmio {
|
||||
}
|
||||
|
||||
fn config_space_read(&self, config_address: u32) -> u32 {
|
||||
let (bus, device, _function, register) = parse_config_address(config_address);
|
||||
let (bus, device, _function, register) = parse_mmio_config_address(config_address);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
@@ -288,7 +341,7 @@ impl PciConfigMmio {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.devices
|
||||
.get(device)
|
||||
.get(&(device as u32))
|
||||
.map_or(0xffff_ffff, |d| {
|
||||
d.lock().unwrap().read_config_register(register)
|
||||
})
|
||||
@@ -299,7 +352,7 @@ impl PciConfigMmio {
|
||||
return;
|
||||
}
|
||||
|
||||
let (bus, device, _function, register) = parse_config_address(config_address);
|
||||
let (bus, device, _function, register) = parse_mmio_config_address(config_address);
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
@@ -307,20 +360,23 @@ impl PciConfigMmio {
|
||||
}
|
||||
|
||||
let pci_bus = self.pci_bus.lock().unwrap();
|
||||
if let Some(d) = pci_bus.devices.get(device) {
|
||||
if let Some(d) = pci_bus.devices.get(&(device as u32)) {
|
||||
let mut device = d.lock().unwrap();
|
||||
|
||||
// Find out if one of the device's BAR is being reprogrammed, and
|
||||
// reprogram it if needed.
|
||||
if let Some(params) = device.detect_bar_reprogramming(register, data) {
|
||||
if let Err(e) = pci_bus.device_reloc.upgrade().unwrap().move_bar(
|
||||
if let Err(e) = pci_bus.device_reloc.move_bar(
|
||||
params.old_base,
|
||||
params.new_base,
|
||||
params.len,
|
||||
device.deref_mut(),
|
||||
params.region_type,
|
||||
) {
|
||||
error!("Failed moving device BAR: {}", e);
|
||||
error!(
|
||||
"Failed moving device BAR: {}: 0x{:x}->0x{:x}(0x{:x})",
|
||||
e, params.old_base, params.new_base, params.len
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,8 +412,33 @@ impl BusDevice for PciConfigMmio {
|
||||
}
|
||||
}
|
||||
|
||||
fn shift_and_mask(value: u32, offset: usize, mask: u32) -> usize {
|
||||
((value >> offset) & mask) as usize
|
||||
}
|
||||
|
||||
// Parse the MMIO address offset to a (bus, device, function, register) tuple.
|
||||
// See section 7.2.2 PCI Express Enhanced Configuration Access Mechanism (ECAM)
|
||||
// from the Pci Express Base Specification Revision 5.0 Version 1.0.
|
||||
fn parse_mmio_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
const BUS_NUMBER_OFFSET: usize = 20;
|
||||
const BUS_NUMBER_MASK: u32 = 0x00ff;
|
||||
const DEVICE_NUMBER_OFFSET: usize = 15;
|
||||
const DEVICE_NUMBER_MASK: u32 = 0x1f;
|
||||
const FUNCTION_NUMBER_OFFSET: usize = 12;
|
||||
const FUNCTION_NUMBER_MASK: u32 = 0x07;
|
||||
const REGISTER_NUMBER_OFFSET: usize = 2;
|
||||
const REGISTER_NUMBER_MASK: u32 = 0x3ff;
|
||||
|
||||
(
|
||||
shift_and_mask(config_address, BUS_NUMBER_OFFSET, BUS_NUMBER_MASK),
|
||||
shift_and_mask(config_address, DEVICE_NUMBER_OFFSET, DEVICE_NUMBER_MASK),
|
||||
shift_and_mask(config_address, FUNCTION_NUMBER_OFFSET, FUNCTION_NUMBER_MASK),
|
||||
shift_and_mask(config_address, REGISTER_NUMBER_OFFSET, REGISTER_NUMBER_MASK),
|
||||
)
|
||||
}
|
||||
|
||||
// Parse the CONFIG_ADDRESS register to a (bus, device, function, register) tuple.
|
||||
fn parse_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
fn parse_io_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
const BUS_NUMBER_OFFSET: usize = 16;
|
||||
const BUS_NUMBER_MASK: u32 = 0x00ff;
|
||||
const DEVICE_NUMBER_OFFSET: usize = 11;
|
||||
@@ -367,12 +448,10 @@ fn parse_config_address(config_address: u32) -> (usize, usize, usize, usize) {
|
||||
const REGISTER_NUMBER_OFFSET: usize = 2;
|
||||
const REGISTER_NUMBER_MASK: u32 = 0x3f;
|
||||
|
||||
let bus_number = ((config_address >> BUS_NUMBER_OFFSET) & BUS_NUMBER_MASK) as usize;
|
||||
let device_number = ((config_address >> DEVICE_NUMBER_OFFSET) & DEVICE_NUMBER_MASK) as usize;
|
||||
let function_number =
|
||||
((config_address >> FUNCTION_NUMBER_OFFSET) & FUNCTION_NUMBER_MASK) as usize;
|
||||
let register_number =
|
||||
((config_address >> REGISTER_NUMBER_OFFSET) & REGISTER_NUMBER_MASK) as usize;
|
||||
|
||||
(bus_number, device_number, function_number, register_number)
|
||||
(
|
||||
shift_and_mask(config_address, BUS_NUMBER_OFFSET, BUS_NUMBER_MASK),
|
||||
shift_and_mask(config_address, DEVICE_NUMBER_OFFSET, DEVICE_NUMBER_MASK),
|
||||
shift_and_mask(config_address, FUNCTION_NUMBER_OFFSET, FUNCTION_NUMBER_MASK),
|
||||
shift_and_mask(config_address, REGISTER_NUMBER_OFFSET, REGISTER_NUMBER_MASK),
|
||||
)
|
||||
}
|
||||
|
||||
190
pci/src/configuration.rs
Executable file → Normal file
190
pci/src/configuration.rs
Executable file → Normal file
@@ -2,15 +2,16 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::device::BarReprogrammingParams;
|
||||
use crate::{MsixConfig, PciInterruptPin};
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::fmt::{self, Display};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vm_migration::{MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable};
|
||||
|
||||
// The number of 32bit registers in the config space, 256 bytes.
|
||||
const NUM_CONFIGURATION_REGISTERS: usize = 64;
|
||||
// The number of 32bit registers in the config space, 4096 bytes.
|
||||
const NUM_CONFIGURATION_REGISTERS: usize = 1024;
|
||||
|
||||
const STATUS_REG: usize = 1;
|
||||
const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
|
||||
@@ -244,6 +245,53 @@ pub trait PciCapability {
|
||||
fn id(&self) -> PciCapabilityID;
|
||||
}
|
||||
|
||||
fn encode_32_bits_bar_size(bar_size: u32) -> Option<u32> {
|
||||
if bar_size > 0 {
|
||||
return Some(!(bar_size - 1));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn decode_32_bits_bar_size(bar_size: u32) -> Option<u32> {
|
||||
if bar_size > 0 {
|
||||
return Some(!bar_size + 1);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn encode_64_bits_bar_size(bar_size: u64) -> Option<(u32, u32)> {
|
||||
if bar_size > 0 {
|
||||
let result = !(bar_size - 1);
|
||||
let result_hi = (result >> 32) as u32;
|
||||
let result_lo = (result & 0xffff_ffff) as u32;
|
||||
return Some((result_hi, result_lo));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn decode_64_bits_bar_size(bar_size_hi: u32, bar_size_lo: u32) -> Option<u64> {
|
||||
let bar_size: u64 = ((bar_size_hi as u64) << 32) | (bar_size_lo as u64);
|
||||
if bar_size > 0 {
|
||||
return Some(!bar_size + 1);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct PciConfigurationState {
|
||||
registers: Vec<u32>,
|
||||
writable_bits: Vec<u32>,
|
||||
bar_addr: Vec<u32>,
|
||||
bar_size: Vec<u32>,
|
||||
bar_used: Vec<bool>,
|
||||
bar_type: Vec<Option<PciBarRegionType>>,
|
||||
rom_bar_addr: u32,
|
||||
rom_bar_size: u32,
|
||||
rom_bar_used: bool,
|
||||
last_capability: Option<(usize, usize)>,
|
||||
msix_cap_reg_idx: Option<usize>,
|
||||
}
|
||||
|
||||
/// Contains the configuration space of a PCI node.
|
||||
/// See the [specification](https://en.wikipedia.org/wiki/PCI_configuration_space).
|
||||
/// The configuration space is accessed with DWORD reads and writes from the guest.
|
||||
@@ -264,7 +312,7 @@ pub struct PciConfiguration {
|
||||
}
|
||||
|
||||
/// See pci_regs.h in kernel
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum PciBarRegionType {
|
||||
Memory32BitRegion = 0,
|
||||
IORegion = 0x01,
|
||||
@@ -297,6 +345,10 @@ pub enum Error {
|
||||
CapabilityEmpty,
|
||||
CapabilityLengthInvalid(usize),
|
||||
CapabilitySpaceFull(usize),
|
||||
Decode32BarSize,
|
||||
Decode64BarSize,
|
||||
Encode32BarSize,
|
||||
Encode64BarSize,
|
||||
RomBarAddressInvalid(u64, u64),
|
||||
RomBarInUse(usize),
|
||||
RomBarInvalid(usize),
|
||||
@@ -324,6 +376,10 @@ impl Display for Error {
|
||||
CapabilityEmpty => write!(f, "empty capabilities are invalid"),
|
||||
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
|
||||
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
|
||||
Decode32BarSize => write!(f, "failed to decode 32 bits BAR size"),
|
||||
Decode64BarSize => write!(f, "failed to decode 64 bits BAR size"),
|
||||
Encode32BarSize => write!(f, "failed to encode 32 bits BAR size"),
|
||||
Encode64BarSize => write!(f, "failed to encode 64 bits BAR size"),
|
||||
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),
|
||||
@@ -337,6 +393,7 @@ impl PciConfiguration {
|
||||
pub fn new(
|
||||
vendor_id: u16,
|
||||
device_id: u16,
|
||||
revision_id: u8,
|
||||
class_code: PciClassCode,
|
||||
subclass: &dyn PciSubclass,
|
||||
programming_interface: Option<&dyn PciProgrammingInterface>,
|
||||
@@ -359,7 +416,8 @@ impl PciConfiguration {
|
||||
};
|
||||
registers[2] = u32::from(class_code.get_register_value()) << 24
|
||||
| u32::from(subclass.get_register_value()) << 16
|
||||
| u32::from(pi) << 8;
|
||||
| u32::from(pi) << 8
|
||||
| u32::from(revision_id);
|
||||
writable_bits[3] = 0x0000_00ff; // Cacheline size (r/w)
|
||||
match header_type {
|
||||
PciHeaderType::Device => {
|
||||
@@ -390,6 +448,37 @@ impl PciConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> PciConfigurationState {
|
||||
PciConfigurationState {
|
||||
registers: self.registers.to_vec(),
|
||||
writable_bits: self.writable_bits.to_vec(),
|
||||
bar_addr: self.bar_addr.to_vec(),
|
||||
bar_size: self.bar_size.to_vec(),
|
||||
bar_used: self.bar_used.to_vec(),
|
||||
bar_type: self.bar_type.to_vec(),
|
||||
rom_bar_addr: self.rom_bar_addr,
|
||||
rom_bar_size: self.rom_bar_size,
|
||||
rom_bar_used: self.rom_bar_used,
|
||||
last_capability: self.last_capability,
|
||||
msix_cap_reg_idx: self.msix_cap_reg_idx,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &PciConfigurationState) {
|
||||
self.registers.clone_from_slice(state.registers.as_slice());
|
||||
self.writable_bits
|
||||
.clone_from_slice(state.writable_bits.as_slice());
|
||||
self.bar_addr.clone_from_slice(state.bar_addr.as_slice());
|
||||
self.bar_size.clone_from_slice(state.bar_size.as_slice());
|
||||
self.bar_used.clone_from_slice(state.bar_used.as_slice());
|
||||
self.bar_type.clone_from_slice(state.bar_type.as_slice());
|
||||
self.rom_bar_addr = state.rom_bar_addr;
|
||||
self.rom_bar_size = state.rom_bar_size;
|
||||
self.rom_bar_used = state.rom_bar_used;
|
||||
self.last_capability = state.last_capability;
|
||||
self.msix_cap_reg_idx = state.msix_cap_reg_idx;
|
||||
}
|
||||
|
||||
/// Reads a 32bit register from `reg_idx` in the register map.
|
||||
pub fn read_reg(&self, reg_idx: usize) -> u32 {
|
||||
*(self.registers.get(reg_idx).unwrap_or(&0xffff_ffff))
|
||||
@@ -403,14 +492,14 @@ impl PciConfiguration {
|
||||
// Handle very specific case where the BAR is being written with
|
||||
// all 1's to retrieve the BAR size during next BAR reading.
|
||||
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;
|
||||
mask &= self.rom_bar_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,6 +583,11 @@ impl PciConfiguration {
|
||||
if end_addr > u64::from(u32::max_value()) {
|
||||
return Err(Error::BarAddressInvalid(config.addr, config.size));
|
||||
}
|
||||
|
||||
// Encode the BAR size as expected by the software running in
|
||||
// the guest.
|
||||
self.bar_size[config.reg_idx] =
|
||||
encode_32_bits_bar_size(config.size as u32).ok_or(Error::Encode32BarSize)?;
|
||||
}
|
||||
PciBarRegionType::Memory64BitRegion => {
|
||||
if config.reg_idx + 1 >= NUM_BAR_REGS {
|
||||
@@ -508,10 +602,16 @@ impl PciConfiguration {
|
||||
return Err(Error::BarInUse64(config.reg_idx));
|
||||
}
|
||||
|
||||
// Encode the BAR size as expected by the software running in
|
||||
// the guest.
|
||||
let (bar_size_hi, bar_size_lo) =
|
||||
encode_64_bits_bar_size(config.size).ok_or(Error::Encode64BarSize)?;
|
||||
|
||||
self.registers[bar_idx + 1] = (config.addr >> 32) as u32;
|
||||
self.writable_bits[bar_idx + 1] = 0xffff_ffff;
|
||||
self.bar_addr[config.reg_idx + 1] = self.registers[bar_idx + 1];
|
||||
self.bar_size[config.reg_idx + 1] = (config.size >> 32) as u32;
|
||||
self.bar_size[config.reg_idx] = bar_size_lo;
|
||||
self.bar_size[config.reg_idx + 1] = bar_size_hi;
|
||||
self.bar_used[config.reg_idx + 1] = true;
|
||||
}
|
||||
}
|
||||
@@ -527,7 +627,6 @@ impl PciConfiguration {
|
||||
self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
|
||||
self.writable_bits[bar_idx] = mask;
|
||||
self.bar_addr[config.reg_idx] = self.registers[bar_idx];
|
||||
self.bar_size[config.reg_idx] = config.size as u32;
|
||||
self.bar_used[config.reg_idx] = true;
|
||||
self.bar_type[config.reg_idx] = Some(config.region_type);
|
||||
Ok(config.reg_idx)
|
||||
@@ -559,7 +658,8 @@ impl PciConfiguration {
|
||||
self.registers[config.reg_idx] = (config.addr as u32) | active;
|
||||
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
|
||||
self.rom_bar_addr = self.registers[config.reg_idx];
|
||||
self.rom_bar_size = config.size as u32;
|
||||
self.rom_bar_size =
|
||||
encode_32_bits_bar_size(config.size as u32).ok_or(Error::Encode32BarSize)?;
|
||||
self.rom_bar_used = true;
|
||||
Ok(config.reg_idx)
|
||||
}
|
||||
@@ -697,7 +797,11 @@ impl PciConfiguration {
|
||||
);
|
||||
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 len = u64::from(
|
||||
decode_32_bits_bar_size(self.bar_size[bar_idx])
|
||||
.ok_or(Error::Decode32BarSize)
|
||||
.unwrap(),
|
||||
);
|
||||
let region_type = bar_type;
|
||||
|
||||
self.bar_addr[bar_idx] = value;
|
||||
@@ -733,8 +837,10 @@ impl PciConfiguration {
|
||||
| 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 len =
|
||||
decode_64_bits_bar_size(self.bar_size[bar_idx], self.bar_size[bar_idx - 1])
|
||||
.ok_or(Error::Decode64BarSize)
|
||||
.unwrap();
|
||||
let region_type = PciBarRegionType::Memory64BitRegion;
|
||||
|
||||
self.bar_addr[bar_idx] = value;
|
||||
@@ -760,7 +866,11 @@ impl PciConfiguration {
|
||||
);
|
||||
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 len = u64::from(
|
||||
decode_32_bits_bar_size(self.rom_bar_size)
|
||||
.ok_or(Error::Decode32BarSize)
|
||||
.unwrap(),
|
||||
);
|
||||
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||
|
||||
self.rom_bar_addr = value;
|
||||
@@ -777,6 +887,54 @@ impl PciConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for PciConfiguration {}
|
||||
|
||||
impl Snapshottable for PciConfiguration {
|
||||
fn id(&self) -> String {
|
||||
String::from("pci_configuration")
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut config_snapshot = Snapshot::new(self.id().as_str());
|
||||
config_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id()),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(config_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(config_section) = snapshot
|
||||
.snapshot_data
|
||||
.get(&format!("{}-section", self.id()))
|
||||
{
|
||||
let config_state = match serde_json::from_slice(&config_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize {}: {}",
|
||||
self.id(),
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
self.set_state(&config_state);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find {} snapshot section",
|
||||
self.id()
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PciBarConfiguration {
|
||||
fn default() -> Self {
|
||||
PciBarConfiguration {
|
||||
@@ -862,6 +1020,7 @@ mod tests {
|
||||
let mut cfg = PciConfiguration::new(
|
||||
0x1234,
|
||||
0x5678,
|
||||
0x1,
|
||||
PciClassCode::MultimediaController,
|
||||
&PciMultimediaSubclass::AudioController,
|
||||
None,
|
||||
@@ -917,6 +1076,7 @@ mod tests {
|
||||
let cfg = PciConfiguration::new(
|
||||
0x1234,
|
||||
0x5678,
|
||||
0x1,
|
||||
PciClassCode::MultimediaController,
|
||||
&PciMultimediaSubclass::AudioController,
|
||||
Some(&TestPI::Test),
|
||||
|
||||
5
pci/src/device.rs
Executable file → Normal file
5
pci/src/device.rs
Executable file → Normal file
@@ -55,6 +55,11 @@ pub trait PciDevice: BusDevice {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Frees the PCI BARs previously allocated with a call to allocate_bars().
|
||||
fn free_bars(&mut self, _allocator: &mut SystemAllocator) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets a register in the configuration space.
|
||||
/// * `reg_idx` - The index of the config register to modify.
|
||||
/// * `offset` - Offset in to the register.
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate devices;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate vm_memory;
|
||||
|
||||
mod bus;
|
||||
@@ -13,6 +17,7 @@ mod configuration;
|
||||
mod device;
|
||||
mod msi;
|
||||
mod msix;
|
||||
mod vfio;
|
||||
|
||||
pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
|
||||
pub use self::configuration::{
|
||||
@@ -25,6 +30,7 @@ pub use self::device::{
|
||||
};
|
||||
pub use self::msi::{msi_num_enabled_vectors, MsiCap, MsiConfig};
|
||||
pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE};
|
||||
pub use self::vfio::{VfioPciDevice, VfioPciError};
|
||||
|
||||
/// PCI has four interrupt pins A->D.
|
||||
#[derive(Copy, Clone)]
|
||||
|
||||
@@ -214,6 +214,8 @@ impl MsiConfig {
|
||||
if let Err(e) = self.interrupt_source_group.mask(idx as InterruptIndex) {
|
||||
error!("Failed masking vector: {:?}", e);
|
||||
}
|
||||
} else if let Err(e) = self.interrupt_source_group.unmask(idx as InterruptIndex) {
|
||||
error!("Failed unmasking vector: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
114
pci/src/msix.rs
114
pci/src/msix.rs
@@ -6,14 +6,17 @@
|
||||
extern crate byteorder;
|
||||
extern crate vm_memory;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{PciCapability, PciCapabilityID};
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::io;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig,
|
||||
};
|
||||
use vm_memory::ByteValued;
|
||||
use vm_migration::{MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable};
|
||||
|
||||
const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048;
|
||||
const MSIX_TABLE_ENTRIES_MODULO: u64 = 16;
|
||||
@@ -25,7 +28,15 @@ const FUNCTION_MASK_MASK: u16 = (1 << FUNCTION_MASK_BIT) as u16;
|
||||
const MSIX_ENABLE_MASK: u16 = (1 << MSIX_ENABLE_BIT) as u16;
|
||||
pub const MSIX_TABLE_ENTRY_SIZE: usize = 16;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Failed enabling the interrupt route.
|
||||
EnableInterruptRoute(io::Error),
|
||||
/// Failed updating the interrupt route.
|
||||
UpdateInterruptRoute(io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MsixTableEntry {
|
||||
pub msg_addr_lo: u32,
|
||||
pub msg_addr_hi: u32,
|
||||
@@ -50,6 +61,14 @@ impl Default for MsixTableEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct MsixConfigState {
|
||||
table_entries: Vec<MsixTableEntry>,
|
||||
pba_entries: Vec<u64>,
|
||||
masked: bool,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
pub struct MsixConfig {
|
||||
pub table_entries: Vec<MsixTableEntry>,
|
||||
pub pba_entries: Vec<u64>,
|
||||
@@ -80,6 +99,46 @@ impl MsixConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> MsixConfigState {
|
||||
MsixConfigState {
|
||||
table_entries: self.table_entries.clone(),
|
||||
pba_entries: self.pba_entries.clone(),
|
||||
masked: self.masked,
|
||||
enabled: self.enabled,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: &MsixConfigState) -> result::Result<(), Error> {
|
||||
self.table_entries = state.table_entries.clone();
|
||||
self.pba_entries = state.pba_entries.clone();
|
||||
self.masked = state.masked;
|
||||
self.enabled = state.enabled;
|
||||
|
||||
if self.enabled && !self.masked {
|
||||
for (idx, table_entry) in self.table_entries.iter().enumerate() {
|
||||
if table_entry.masked() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let config = MsiIrqSourceConfig {
|
||||
high_addr: table_entry.msg_addr_hi,
|
||||
low_addr: table_entry.msg_addr_lo,
|
||||
data: table_entry.msg_data,
|
||||
};
|
||||
|
||||
self.interrupt_source_group
|
||||
.update(idx as InterruptIndex, InterruptSourceConfig::MsiIrq(config))
|
||||
.map_err(Error::UpdateInterruptRoute)?;
|
||||
|
||||
self.interrupt_source_group
|
||||
.enable()
|
||||
.map_err(Error::EnableInterruptRoute)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn masked(&self) -> bool {
|
||||
self.masked
|
||||
}
|
||||
@@ -116,6 +175,9 @@ impl MsixConfig {
|
||||
if let Err(e) = self.interrupt_source_group.mask(idx as InterruptIndex) {
|
||||
error!("Failed masking vector: {:?}", e);
|
||||
}
|
||||
} else if let Err(e) = self.interrupt_source_group.unmask(idx as InterruptIndex)
|
||||
{
|
||||
error!("Failed unmasking vector: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,6 +424,52 @@ impl MsixConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for MsixConfig {}
|
||||
|
||||
impl Snapshottable for MsixConfig {
|
||||
fn id(&self) -> String {
|
||||
String::from("msix_config")
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let snapshot =
|
||||
serde_json::to_vec(&self.state()).map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut msix_snapshot = Snapshot::new(self.id().as_str());
|
||||
msix_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id()),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(msix_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(msix_section) = snapshot
|
||||
.snapshot_data
|
||||
.get(&format!("{}-section", self.id()))
|
||||
{
|
||||
let msix_state = match serde_json::from_slice(&msix_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize MSI-X {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
return self.set_state(&msix_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore MSI-X state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find MSI-X snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
|
||||
@@ -4,30 +4,33 @@
|
||||
//
|
||||
|
||||
extern crate devices;
|
||||
extern crate pci;
|
||||
extern crate vm_allocator;
|
||||
|
||||
use crate::vfio_device::VfioDevice;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use devices::BusDevice;
|
||||
use kvm_bindings::kvm_userspace_memory_region;
|
||||
use kvm_ioctls::*;
|
||||
use pci::{
|
||||
use crate::{
|
||||
msi_num_enabled_vectors, BarReprogrammingParams, MsiConfig, MsixCap, MsixConfig,
|
||||
PciBarConfiguration, PciBarRegionType, PciCapabilityID, PciClassCode, PciConfiguration,
|
||||
PciDevice, PciDeviceError, PciHeaderType, PciSubclass, MSIX_TABLE_ENTRY_SIZE,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use devices::BusDevice;
|
||||
use kvm_bindings::kvm_userspace_memory_region;
|
||||
use kvm_ioctls::*;
|
||||
use std::any::Any;
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::ptr::null_mut;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, io, result};
|
||||
use vfio_bindings::bindings::vfio::*;
|
||||
use vfio_ioctls::{VfioDevice, VfioError};
|
||||
use vm_allocator::SystemAllocator;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
|
||||
};
|
||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||
use vm_memory::{
|
||||
Address, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, GuestRegionMmap,
|
||||
GuestUsize,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -41,6 +44,7 @@ pub enum VfioPciError {
|
||||
SetGsiRouting(kvm_ioctls::Error),
|
||||
MsiNotConfigured,
|
||||
MsixNotConfigured,
|
||||
UpdateMemory(VfioError),
|
||||
UpdateMsiEventFd,
|
||||
UpdateMsixEventFd,
|
||||
}
|
||||
@@ -62,6 +66,7 @@ impl fmt::Display for VfioPciError {
|
||||
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::UpdateMemory(e) => write!(f, "failed to update memory: {}", e),
|
||||
VfioPciError::UpdateMsiEventFd => write!(f, "failed to update MSI eventfd"),
|
||||
VfioPciError::UpdateMsixEventFd => write!(f, "failed to update MSI-X eventfd"),
|
||||
}
|
||||
@@ -222,6 +227,7 @@ impl Interrupt {
|
||||
struct MmioRegion {
|
||||
start: GuestAddress,
|
||||
length: GuestUsize,
|
||||
type_: PciBarRegionType,
|
||||
index: u32,
|
||||
mem_slot: Option<u32>,
|
||||
host_addr: Option<u64>,
|
||||
@@ -281,6 +287,7 @@ pub struct VfioPciDevice {
|
||||
configuration: PciConfiguration,
|
||||
mmio_regions: Vec<MmioRegion>,
|
||||
interrupt: Interrupt,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
}
|
||||
|
||||
impl VfioPciDevice {
|
||||
@@ -289,11 +296,13 @@ impl VfioPciDevice {
|
||||
vm_fd: &Arc<VmFd>,
|
||||
device: VfioDevice,
|
||||
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||
) -> Result<Self> {
|
||||
let device = Arc::new(device);
|
||||
device.reset();
|
||||
|
||||
let configuration = PciConfiguration::new(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
PciClassCode::Other,
|
||||
@@ -317,6 +326,7 @@ impl VfioPciDevice {
|
||||
msi: None,
|
||||
msix: None,
|
||||
},
|
||||
mem,
|
||||
};
|
||||
|
||||
vfio_pci_device.parse_capabilities(interrupt_manager);
|
||||
@@ -502,9 +512,11 @@ impl VfioPciDevice {
|
||||
/// # Return value
|
||||
///
|
||||
/// This function returns the updated KVM memory slot id.
|
||||
pub fn map_mmio_regions(&mut self, vm: &Arc<VmFd>, mem_slot: u32) -> Result<u32> {
|
||||
pub fn map_mmio_regions<F>(&mut self, vm: &Arc<VmFd>, mem_slot: F) -> Result<()>
|
||||
where
|
||||
F: Fn() -> u32,
|
||||
{
|
||||
let fd = self.device.as_raw_fd();
|
||||
let mut new_mem_slot = mem_slot;
|
||||
|
||||
for region in self.mmio_regions.iter_mut() {
|
||||
// We want to skip the mapping of the BAR containing the MSI-X
|
||||
@@ -548,8 +560,9 @@ impl VfioPciDevice {
|
||||
continue;
|
||||
}
|
||||
|
||||
let slot = mem_slot();
|
||||
let mem_region = kvm_userspace_memory_region {
|
||||
slot: new_mem_slot as u32,
|
||||
slot,
|
||||
guest_phys_addr: region.start.raw_value() + mmap_offset,
|
||||
memory_size: mmap_size as u64,
|
||||
userspace_addr: host_addr as u64,
|
||||
@@ -563,30 +576,55 @@ impl VfioPciDevice {
|
||||
}
|
||||
|
||||
// Update the region with memory mapped info.
|
||||
region.mem_slot = Some(new_mem_slot);
|
||||
region.mem_slot = Some(slot);
|
||||
region.host_addr = Some(host_addr as u64);
|
||||
region.mmap_size = Some(mmap_size as usize);
|
||||
|
||||
new_mem_slot += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(new_mem_slot)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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 let (Some(host_addr), Some(mmap_size), Some(mem_slot)) =
|
||||
(region.host_addr, region.mmap_size, region.mem_slot)
|
||||
{
|
||||
let (mmap_offset, _) = self.device.get_region_mmap(region.index);
|
||||
|
||||
// Remove region from KVM
|
||||
let kvm_region = kvm_userspace_memory_region {
|
||||
slot: mem_slot,
|
||||
guest_phys_addr: region.start.raw_value() + mmap_offset,
|
||||
memory_size: 0,
|
||||
userspace_addr: host_addr,
|
||||
flags: 0,
|
||||
};
|
||||
// Safe because the guest regions are guaranteed not to overlap.
|
||||
if let Err(e) = unsafe { self.vm_fd.set_user_memory_region(kvm_region) } {
|
||||
error!(
|
||||
"Could not remove the userspace memory region from KVM: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
let ret = unsafe { libc::munmap(host_addr as *mut libc::c_void, mmap_size) };
|
||||
if ret != 0 {
|
||||
error!(
|
||||
"Could not unmap regions, error:{}",
|
||||
"Could not unmap region {}, error:{}",
|
||||
region.index,
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_memory(&self, new_region: &Arc<GuestRegionMmap>) -> Result<()> {
|
||||
self.device
|
||||
.extend_dma_map(new_region)
|
||||
.map_err(VfioPciError::UpdateMemory)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VfioPciDevice {
|
||||
@@ -605,7 +643,11 @@ impl Drop for VfioPciDevice {
|
||||
}
|
||||
}
|
||||
|
||||
if self.device.unset_dma_map().is_err() {
|
||||
if self
|
||||
.device
|
||||
.setup_dma_map(self.mem.memory().deref())
|
||||
.is_err()
|
||||
{
|
||||
error!("failed to remove all guest memory regions from iommu table");
|
||||
}
|
||||
}
|
||||
@@ -798,6 +840,7 @@ impl PciDevice for VfioPciDevice {
|
||||
self.mmio_regions.push(MmioRegion {
|
||||
start: bar_addr,
|
||||
length: region_size,
|
||||
type_: region_type,
|
||||
index: bar_id as u32,
|
||||
mem_slot: None,
|
||||
host_addr: None,
|
||||
@@ -810,13 +853,37 @@ impl PciDevice for VfioPciDevice {
|
||||
}
|
||||
}
|
||||
|
||||
if self.device.setup_dma_map().is_err() {
|
||||
if self
|
||||
.device
|
||||
.setup_dma_map(self.mem.memory().deref())
|
||||
.is_err()
|
||||
{
|
||||
error!("failed to add all guest memory regions into iommu table");
|
||||
}
|
||||
|
||||
Ok(ranges)
|
||||
}
|
||||
|
||||
fn free_bars(
|
||||
&mut self,
|
||||
allocator: &mut SystemAllocator,
|
||||
) -> std::result::Result<(), PciDeviceError> {
|
||||
for region in self.mmio_regions.iter() {
|
||||
match region.type_ {
|
||||
PciBarRegionType::IORegion => {
|
||||
allocator.free_io_addresses(region.start, region.length);
|
||||
}
|
||||
PciBarRegionType::Memory32BitRegion => {
|
||||
allocator.free_mmio_hole_addresses(region.start, region.length);
|
||||
}
|
||||
PciBarRegionType::Memory64BitRegion => {
|
||||
allocator.free_mmio_addresses(region.start, region.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
|
||||
// When the guest wants to write to a BAR, we trap it into
|
||||
// our local configuration space. We're not reprogramming
|
||||
@@ -10,7 +10,7 @@ path = "src/qcow.rs"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3.4"
|
||||
libc = "0.2.60"
|
||||
libc = "0.2.71"
|
||||
log = "0.4.8"
|
||||
remain = "0.2.1"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
0
qcow/src/qcow.rs
Executable file → Normal file
0
qcow/src/qcow.rs
Executable file → Normal file
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use std;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io;
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ impl<T: Cacheable> CacheMap<T> {
|
||||
{
|
||||
if self.map.len() == self.capacity {
|
||||
// TODO(dgreid) - smarter eviction strategy.
|
||||
let to_evict = *self.map.iter().nth(0).unwrap().0;
|
||||
let to_evict = *self.map.iter().next().unwrap().0;
|
||||
if let Some(evicted) = self.map.remove(&to_evict) {
|
||||
if evicted.dirty() {
|
||||
write_callback(to_evict, evicted)?;
|
||||
|
||||
366
release-notes.md
366
release-notes.md
@@ -1,44 +1,338 @@
|
||||
- [v0.8.0](#v080)
|
||||
- [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support)
|
||||
- [Experimental ARM64 Support](#experimental-arm64-support)
|
||||
- [Support for Using 5-level Paging in Guests](#support-for-using-5-level-paging-in-guests)
|
||||
- [Virtio Device Interrupt Suppression for Network Devices](#virtio-device-interrupt-suppression-for-network-devices)
|
||||
- [`vhost_user_fs` Improvements](#vhost_user_fs-improvements)
|
||||
- [Notable Bug Fixes](#notable-bug-fixes)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes)
|
||||
- [Contributors](#contributors)
|
||||
- [v0.7.0](#v070)
|
||||
- [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug)
|
||||
- [Alternative `libc` Support](#alternative-libc-support)
|
||||
- [Multithreaded Multi Queued `vhost-user` Backends](#multithreaded-multi-queued-vhost-user-backends)
|
||||
- [Initial RamFS Support](#initial-ramfs-support)
|
||||
- [Alternative Memory Hotplug: `virtio-mem`](#alternative-memory-hotplug-virtio-mem)
|
||||
- [`Seccomp` Sandboxing](#seccomp-sandboxing)
|
||||
- [Updated Distribution Support](#updated-distribution-support)
|
||||
- [Command Line and API Changes](#command-line-and-api-changes-1)
|
||||
- [Contributors](#contributors-1)
|
||||
- [v0.6.0](#v060)
|
||||
- [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug)
|
||||
- [Shared Filesystem Improvements](#shared-filesystem-improvements)
|
||||
- [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading)
|
||||
- [Command Line Interface](#command-line-interface)
|
||||
- [PVH Boot](#pvh-boot)
|
||||
- [Contributors](#contributors-2)
|
||||
- [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)
|
||||
- [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-3)
|
||||
- [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)
|
||||
- [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-4)
|
||||
- [v0.3.0](#v030)
|
||||
+ [Block device offloading](#block-device-offloading)
|
||||
+ [Network device backend](#network-device-backend)
|
||||
+ [Virtual sockets](#virtual-sockets)
|
||||
+ [HTTP based API](#http-based-api)
|
||||
+ [Memory mapped virtio transport](#memory-mapped-virtio-transport)
|
||||
+ [Paravirtualized IOMMU](#paravirtualized-iommu)
|
||||
+ [Ubuntu 19.10](#ubuntu-1910)
|
||||
+ [Guest large memory](#guest-large-memory)
|
||||
- [Block device offloading](#block-device-offloading)
|
||||
- [Network device backend](#network-device-backend)
|
||||
- [Virtual sockets](#virtual-sockets)
|
||||
- [HTTP based API](#http-based-api)
|
||||
- [Memory mapped virtio transport](#memory-mapped-virtio-transport)
|
||||
- [Paravirtualized IOMMU](#paravirtualized-iommu)
|
||||
- [Ubuntu 19.10](#ubuntu-1910)
|
||||
- [Large memory guests](#large-memory-guests)
|
||||
- [v0.2.0](#v020)
|
||||
+ [Network device offloading](#network-device-offloading)
|
||||
+ [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi)
|
||||
+ [Debug I/O port](#debug-i-o-port)
|
||||
+ [Improved direct device assignment](#improved-direct-device-assignment)
|
||||
+ [Improved shared filesystem](#improved-shared-filesystem)
|
||||
+ [Ubuntu bionic based CI](#ubuntu-bionic-based-ci)
|
||||
- [Network device offloading](#network-device-offloading)
|
||||
- [Minimal hardware-reduced ACPI](#minimal-hardware-reduced-acpi)
|
||||
- [Debug I/O port](#debug-io-port)
|
||||
- [Improved direct device assignment](#improved-direct-device-assignment)
|
||||
- [Improved shared filesystem](#improved-shared-filesystem)
|
||||
- [Ubuntu bionic based CI](#ubuntu-bionic-based-ci)
|
||||
- [v0.1.0](#v010)
|
||||
+ [Shared filesystem](#shared-filesystem)
|
||||
+ [Initial direct device assignment support](#initial-direct-device-assignment-support)
|
||||
+ [Userspace IOAPIC](#userspace-ioapic)
|
||||
+ [Virtual persistent memory](#virtual-persistent-memory)
|
||||
+ [Linux kernel bzImage](#linux-kernel-bzimage)
|
||||
+ [Console over virtio](#console-over-virtio)
|
||||
+ [Unit testing](#unit-testing)
|
||||
+ [Integration tests parallelization](#integration-tests-parallelization)
|
||||
- [Shared filesystem](#shared-filesystem)
|
||||
- [Initial direct device assignment support](#initial-direct-device-assignment-support)
|
||||
- [Userspace IOAPIC](#userspace-ioapic)
|
||||
- [Virtual persistent memory](#virtual-persistent-memory)
|
||||
- [Linux kernel bzImage](#linux-kernel-bzimage)
|
||||
- [Console over virtio](#console-over-virtio)
|
||||
- [Unit testing](#unit-testing)
|
||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
||||
|
||||
# v0.8.0
|
||||
|
||||
This release has been tracked through the [0.8.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/10).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.8.0 include:
|
||||
|
||||
### Experimental Snapshot and Restore Support
|
||||
|
||||
This release includes the first version of the snapshot and restore feature.
|
||||
This allows a VM to be paused and then subsequently snapshotted. At a later
|
||||
point that snapshot may be restored into a new running VM identical to the
|
||||
original VM at the point it was paused.
|
||||
|
||||
This feature can be used for offline migration from one VM host to another, to
|
||||
allow the upgrading or rebooting of the host machine transparently to the guest
|
||||
or for templating the VM. This is an experimental feature and cannot be used on
|
||||
a VM using passthrough (VFIO) devices. Issues with SMP have also been observed
|
||||
(#1176).
|
||||
|
||||
### Experimental ARM64 Support
|
||||
|
||||
Included in this release is experimental support for running on ARM64.
|
||||
Currently only `virtio-mmio` devices and a serial port are supported. Full
|
||||
details can be found in the [ARM64 documentation](docs/arm64.md).
|
||||
|
||||
### Support for Using 5-level Paging in Guests
|
||||
|
||||
If the host supports it the guest is now enabled for 5-level paging (aka LA57).
|
||||
This works when booting the Linux kernel with a vmlinux, bzImage or firmware
|
||||
based boot. However booting an ELF kernel built with `CONFIG_PVH=y` does not
|
||||
work due to current limitations in the PVH boot process.
|
||||
|
||||
### Virtio Device Interrupt Suppression for Network Devices
|
||||
|
||||
With `virtio-net` and `vhost-user-net` devices the guest can suppress
|
||||
interrupts from the VMM by using the `VIRTIO_RING_F_EVENT_IDX` feature. This
|
||||
can lead to an improvement in performance by reducing the number of interrupts
|
||||
the guest must service.
|
||||
|
||||
### `vhost_user_fs` Improvements
|
||||
|
||||
The implementation in Cloud Hypervisor of the VirtioFS server now supports sandboxing itself with `seccomp`.
|
||||
|
||||
|
||||
### Notable Bug Fixes
|
||||
|
||||
* VMs that have not yet been booted can now be deleted (#1110).
|
||||
* By creating the `tap` device ahead of creating the VM it is not required to
|
||||
run the `cloud-hypervisor` binary with `CAP_NET_ADMIN` (#1273).
|
||||
* Block I/O via `virtio-block` or `vhost-user-block` now correctly adheres to
|
||||
the specification and synchronizes to the underlying filesystem as required
|
||||
based on guest feature negotiation. This avoids potential data loss (#399,
|
||||
#1216).
|
||||
* When booting with a large number of vCPUs then the ACPI table would be
|
||||
overwritten by the SMP `MPTABLE`. When compiled with the `acpi` feature the
|
||||
`MPTABLE` will no longer be generated (#1132).
|
||||
* Shutting down VMs that have been paused is now supported (#816).
|
||||
* Created socket files are deleted on shutdown (#1083).
|
||||
* Trying to use passthrough devices (VFIO) will be rejected on `mmio` builds
|
||||
(#751).
|
||||
|
||||
### Command Line and API Changes
|
||||
|
||||
This is non exhaustive list of HTTP API and command line changes:
|
||||
|
||||
* All user visible socket parameters are now consistently called `socket`
|
||||
rather than `sock` in some cases.
|
||||
* The `ch-remote` tool now shows any error message generated by the VMM
|
||||
* The `wce` parameter has been removed from `--disk` as the feature is always
|
||||
offered for negotiation.
|
||||
* `--net` has gained a `host_mac` option that allows the setting of the MAC
|
||||
address for the `tap` device on the host.
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our 0.8.0 release including some new faces.
|
||||
|
||||
* Anatol Belski <ab@php.net>
|
||||
* Arron Wang <arron.wang@intel.com>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Dr. David Alan Gilbert <dgilbert@redhat.com>
|
||||
* Henry Wang <Henry.Wang@arm.com>
|
||||
* Hui Zhu <teawater@antfin.com>
|
||||
* LiYa'nan <oliverliyn@gmail.com>
|
||||
* Michael Zhao <michael.zhao@arm.com>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Samuel Ortiz <sameo@linux.intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Sergio Lopez <slp@redhat.com>
|
||||
|
||||
# v0.7.0
|
||||
|
||||
This release has been tracked through the [0.7.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.7.0 include:
|
||||
|
||||
### Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug
|
||||
|
||||
Further to our effort to support modifying a running guest we now support
|
||||
hotplug and unplug of the following virtio backed devices: block, network,
|
||||
pmem, virtio-fs and vsock. This functionality is available on the (default) PCI
|
||||
based tranport and is exposed through the HTTP API. The `ch-remote` utility
|
||||
provides a CLI for adding or removing these device types after the VM has
|
||||
booted. User can use the `id` parameter on the devices to choose names for
|
||||
devices to ease their removal.
|
||||
|
||||
### Alternative `libc` Support
|
||||
|
||||
Cloud Hypervisor can now be compiled with the `musl` C library and this release
|
||||
contains a static binary compiled using that toolchain.
|
||||
|
||||
### Multithreaded Multi Queued `vhost-user` Backends
|
||||
|
||||
The `vhost-user` backends for network and block support that are shipped by
|
||||
Cloud Hypervisor have been enhanced to support multiple threads and queues to
|
||||
improve throughput. These backends are used automatically if `vhost_user=true`
|
||||
is passed when the devices are created.
|
||||
|
||||
### Initial RamFS Support
|
||||
|
||||
By passing the `--initramfs` command line option the user can specify a file to
|
||||
be loaded into the guest memory to be used as the kernel initial filesystem.
|
||||
This is usually used to allow the loading of drivers needed to be able to
|
||||
access the real root filesystem but it can also be used standalone for a very
|
||||
minimal image.
|
||||
|
||||
### Alternative Memory Hotplug: `virtio-mem`
|
||||
|
||||
As well as supporting ACPI based hotplug Cloud Hypervisor now supports using
|
||||
the `virtio-mem` hotplug alternative. This can be controlled by the
|
||||
`hotplug_method` parameter on the `--memory` command line option. It currently
|
||||
requires kernel patches to be able to support it.
|
||||
|
||||
### `Seccomp` Sandboxing
|
||||
|
||||
Cloud Hypervisor now has support for restricting the system calls that the
|
||||
process can use via the `seccomp` security API. This on by default and is
|
||||
controlled by the `--seccomp` command line option.
|
||||
|
||||
### Updated Distribution Support
|
||||
|
||||
With the release of Ubuntu 20.04 we have added that to the list of supported
|
||||
distributions and is part of our regular testing programme.
|
||||
|
||||
### Command Line and API Changes
|
||||
|
||||
This is non exhaustive list of HTTP API and command line changes
|
||||
|
||||
* New `id` fields added for devices to allow them to be named to ease removal.
|
||||
If no name is specified the VMM chooses one.
|
||||
* Use `--memory`'s `shared` and `hugepages` controls for determining backing
|
||||
memory instead of providing a path.
|
||||
* The `--vsock` parameter only takes one device as the Linux kernel only
|
||||
supports a single Vsock device. The REST API has removed the vector for this
|
||||
option and replaced it with a single optional field.
|
||||
* There is enhanced validation of the command line and API provided
|
||||
configurations to ensure that the provided options are compatible e.g. that
|
||||
shared memory is in use if any attempt is made to used a `vhost-user` backed
|
||||
device.
|
||||
* `ch-remote` has added `add-disk`, `add-fs`, `add-net`, `add-pmem` and
|
||||
`add-vsock` subcommands. For removal `remove-device` is used. The REST API
|
||||
has appropriate new HTTP endpoints too.
|
||||
* Specifying a `size` with `--pmem` is no longer required and instead the size
|
||||
will be obtained from the file. A `discard_writes` option has also been added
|
||||
to provide the equivalent of a read-only file.
|
||||
* The parameters to `--block-backend` have been changed to more closely align
|
||||
with those used by `--disk`.
|
||||
|
||||
### Contributors
|
||||
|
||||
Many thanks to everyone who has contributed to our 0.7.0 release including some new faces.
|
||||
|
||||
* Alejandro Jimenez <alejandro.j.jimenez@oracle.com>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Cathy Zhang <cathy.zhang@intel.com>
|
||||
* Damjan Georgievski <gdamjan@gmail.com>
|
||||
* Dean Sheather <dean@coder.com>
|
||||
* Eryu Guan <eguan@linux.alibaba.com>
|
||||
* Hui Zhu <teawater@antfin.com>
|
||||
* Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
|
||||
* Martin Xu <martin.xu@intel.com>
|
||||
* Muminul Islam <muislam@microsoft.com>
|
||||
* 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>
|
||||
* Yi Sun <yi.y.sun@linux.intel.com>
|
||||
|
||||
# v0.6.0
|
||||
|
||||
This release has been tracked through the [0.6.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).
|
||||
|
||||
Highlights for `cloud-hypervisor` version 0.6.0 include:
|
||||
|
||||
### Directly Assigned Devices Hotplug
|
||||
|
||||
We continued our efforts around supporting dynamically changing the guest
|
||||
resources. After adding support for CPU and memory hotplug, Cloud Hypervisor
|
||||
now supports hot plugging and hot unplugging directly assigned (a.k.a. `VFIO`)
|
||||
devices into an already running guest. This closes the features gap for
|
||||
providing a complete Kata Containers workloads support with Cloud Hypervisor.
|
||||
|
||||
### Shared Filesystem Improvements
|
||||
|
||||
We enhanced our shared filesystem support through many `virtio-fs` improvements.
|
||||
By adding support for DAX, parallel processing of multiple requests, `FS_IO`,
|
||||
`LSEEK` and the `MMIO` virtio transport layer to our `vhost_user_fs` daemon, we
|
||||
improved our filesystem sharing performance, but also made it more stable and
|
||||
compatible with other `virtio-fs` implementations.
|
||||
|
||||
### Block and Networking IO Self Offloading
|
||||
|
||||
When choosing to offload the paravirtualized block and networking I/O to an
|
||||
external process (through the `vhost-user` protocol), Cloud Hypervisor now
|
||||
automatically spawns its default `vhost-user-blk` and `vhost-user-net` backends
|
||||
into their own, separate processes.
|
||||
This provides a seamless parvirtualized I/O user experience for those who want
|
||||
to run their guest I/O into separate executions contexts.
|
||||
|
||||
### Command Line Interface
|
||||
|
||||
More and more Cloud Hypervisor services are exposed through the
|
||||
[Rest API](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/vmm/src/api/openapi/cloud-hypervisor.yaml)
|
||||
and thus only accessible via relatively cumbersome HTTP calls. In order
|
||||
to abstract those calls into a more user friendly tool, we created a Cloud
|
||||
Hypervisor Command Line Interface (CLI) called `ch-remote`.
|
||||
The `ch-remote` binary is created with each build and available e.g. at
|
||||
`cloud-hypervisor/target/debug/ch-remote` when doing a debug build.
|
||||
|
||||
Please check `ch-remote --help` for a complete description of all available
|
||||
commands.
|
||||
|
||||
### PVH Boot
|
||||
|
||||
In addition to the traditional Linux boot protocol, Cloud Hypervisor now
|
||||
supports direct kernel booting through the [PVH ABI](https://xenbits.xen.org/docs/unstable/misc/pvh.html).
|
||||
|
||||
### Contributors
|
||||
|
||||
With the 0.6.0 release, we are welcoming a few new contributors. Many thanks
|
||||
to them and to everyone that contributed to this release:
|
||||
|
||||
* Alejandro Jimenez <alejandro.j.jimenez@oracle.com>
|
||||
* Arron Wang <arron.wang@intel.com>
|
||||
* Bin Liu <liubin0329@gmail.com>
|
||||
* Bo Chen <chen.bo@intel.com>
|
||||
* Cathy Zhang <cathy.zhang@intel.com>
|
||||
* Eryu Guan <eguan@linux.alibaba.com>
|
||||
* Jose Carlos Venegas Munoz <jose.carlos.venegas.munoz@intel.com>
|
||||
* Liu Bo <bo.liu@linux.alibaba.com>
|
||||
* Qiu Wenbo <qiuwenbo@phytium.com.cn>
|
||||
* Rob Bradford <robert.bradford@intel.com>
|
||||
* Samuel Ortiz <sameo@linux.intel.com>
|
||||
* Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
* Sergio Lopez <slp@redhat.com>
|
||||
|
||||
# v0.5.1
|
||||
|
||||
This is a bugfix release branched off v0.5.0. It contains the following fixes:
|
||||
|
||||
* Update DiskConfig to contain missing disk control features (#790) - Samuel Ortiz and Sergio Lopez
|
||||
* Prevent memory overcommit via virtio-fs (#763) - Sebastien Boeuf
|
||||
* Fixed error reporting for resize command - Samuel Ortiz
|
||||
* Double reboot workaround (#783) - Rob Bradford
|
||||
* Various CI and development tooling fixes - Sebastien Boeuf, Samuel Ortiz, Rob Bradford
|
||||
|
||||
# v0.5.0
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
FROM ubuntu:18.04 as dev
|
||||
|
||||
ARG RUST_TOOLCHAIN="1.40.0"
|
||||
ARG TARGETARCH="x86_64"
|
||||
ARG RUST_TOOLCHAIN="1.43.0"
|
||||
ARG CLH_SRC_DIR="/cloud-hypervisor"
|
||||
ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build"
|
||||
ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry"
|
||||
@@ -11,9 +12,9 @@ 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 \
|
||||
RUN apt-get update \
|
||||
&& apt-get -yq upgrade \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
build-essential \
|
||||
bc \
|
||||
docker.io \
|
||||
@@ -21,6 +22,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
wget \
|
||||
sudo \
|
||||
mtools \
|
||||
musl-tools \
|
||||
libssl-dev \
|
||||
pkg-config \
|
||||
flex \
|
||||
@@ -34,13 +36,31 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
libcap-ng-dev \
|
||||
socat \
|
||||
dosfstools \
|
||||
cpio \
|
||||
bsdtar \
|
||||
libfdt-dev \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN if [ "$TARGETARCH" = "x86_64" ]; then \
|
||||
apt-get update \
|
||||
&& apt-get -yq upgrade \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq gcc-multilib \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*; fi
|
||||
|
||||
# Fix the libssl-dev install
|
||||
RUN cp /usr/include/"$TARGETARCH"-linux-gnu/openssl/opensslconf.h /usr/include/openssl/
|
||||
ENV OPENSSL_DIR=/usr/lib/"$TARGETARCH"-linux-gnu/
|
||||
ENV OPENSSL_LIB_DIR=/usr/lib/"$TARGETARCH"-linux-gnu/
|
||||
ENV OPENSSL_INCLUDE_DIR=/usr/include/
|
||||
|
||||
# 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 \
|
||||
&& rustup target add $TARGETARCH-unknown-linux-musl --toolchain "$RUST_TOOLCHAIN" \
|
||||
&& if [ "$TARGETARCH" = "x86_64" ]; then rustup toolchain add $RUST_TOOLCHAIN-x86_64-unknown-linux-musl; fi \
|
||||
&& if [ "$TARGETARCH" = "x86_64" ]; then rustup component add rustfmt; fi \
|
||||
&& if [ "$TARGETARCH" = "x86_64" ]; then rustup component add clippy; fi \
|
||||
&& cargo install cargo-audit \
|
||||
&& rm -rf "$CARGO_HOME/registry" \
|
||||
&& ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Automatically generated file; DO NOT EDIT.
|
||||
# Linux/x86 5.5.0-rc1 Kernel Configuration
|
||||
# Linux/x86 5.6.0-rc4 Kernel Configuration
|
||||
#
|
||||
|
||||
#
|
||||
@@ -14,7 +14,7 @@ CONFIG_CC_HAS_ASM_GOTO=y
|
||||
CONFIG_CC_HAS_ASM_INLINE=y
|
||||
CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y
|
||||
CONFIG_IRQ_WORK=y
|
||||
CONFIG_BUILDTIME_EXTABLE_SORT=y
|
||||
CONFIG_BUILDTIME_TABLE_SORT=y
|
||||
CONFIG_THREAD_INFO_IN_TASK=y
|
||||
|
||||
#
|
||||
@@ -169,6 +169,7 @@ CONFIG_CGROUP_BPF=y
|
||||
CONFIG_SOCK_CGROUP_DATA=y
|
||||
CONFIG_NAMESPACES=y
|
||||
CONFIG_UTS_NS=y
|
||||
# CONFIG_TIME_NS is not set
|
||||
CONFIG_IPC_NS=y
|
||||
CONFIG_USER_NS=y
|
||||
CONFIG_PID_NS=y
|
||||
@@ -177,7 +178,15 @@ CONFIG_NET_NS=y
|
||||
CONFIG_SCHED_AUTOGROUP=y
|
||||
# CONFIG_SYSFS_DEPRECATED is not set
|
||||
CONFIG_RELAY=y
|
||||
# CONFIG_BLK_DEV_INITRD is not set
|
||||
CONFIG_BLK_DEV_INITRD=y
|
||||
CONFIG_INITRAMFS_SOURCE=""
|
||||
CONFIG_RD_GZIP=y
|
||||
CONFIG_RD_BZIP2=y
|
||||
CONFIG_RD_LZMA=y
|
||||
CONFIG_RD_XZ=y
|
||||
CONFIG_RD_LZO=y
|
||||
CONFIG_RD_LZ4=y
|
||||
# CONFIG_BOOT_CONFIG is not set
|
||||
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
|
||||
# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
|
||||
CONFIG_SYSCTL=y
|
||||
@@ -212,6 +221,7 @@ CONFIG_KALLSYMS=y
|
||||
CONFIG_KALLSYMS_ABSOLUTE_PERCPU=y
|
||||
CONFIG_KALLSYMS_BASE_RELATIVE=y
|
||||
CONFIG_BPF_SYSCALL=y
|
||||
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
|
||||
CONFIG_USERFAULTFD=y
|
||||
CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y
|
||||
CONFIG_RSEQ=y
|
||||
@@ -276,7 +286,7 @@ CONFIG_X86_64_SMP=y
|
||||
CONFIG_ARCH_SUPPORTS_UPROBES=y
|
||||
CONFIG_FIX_EARLYCON_MEM=y
|
||||
CONFIG_DYNAMIC_PHYSICAL_MASK=y
|
||||
CONFIG_PGTABLE_LEVELS=4
|
||||
CONFIG_PGTABLE_LEVELS=5
|
||||
CONFIG_CC_HAS_SANE_STACKPROTECTOR=y
|
||||
|
||||
#
|
||||
@@ -320,6 +330,8 @@ CONFIG_X86_CMPXCHG64=y
|
||||
CONFIG_X86_CMOV=y
|
||||
CONFIG_X86_MINIMUM_CPU_FAMILY=64
|
||||
CONFIG_X86_DEBUGCTLMSR=y
|
||||
CONFIG_IA32_FEAT_CTL=y
|
||||
CONFIG_X86_VMX_FEATURE_NAMES=y
|
||||
# CONFIG_PROCESSOR_SELECT is not set
|
||||
CONFIG_CPU_SUP_INTEL=y
|
||||
CONFIG_CPU_SUP_AMD=y
|
||||
@@ -359,7 +371,7 @@ CONFIG_X86_VSYSCALL_EMULATION=y
|
||||
# CONFIG_MICROCODE is not set
|
||||
CONFIG_X86_MSR=y
|
||||
CONFIG_X86_CPUID=y
|
||||
# CONFIG_X86_5LEVEL is not set
|
||||
CONFIG_X86_5LEVEL=y
|
||||
CONFIG_X86_DIRECT_GBPAGES=y
|
||||
# CONFIG_X86_CPA_STATISTICS is not set
|
||||
CONFIG_AMD_MEM_ENCRYPT=y
|
||||
@@ -389,7 +401,6 @@ CONFIG_ARCH_USES_PG_UNCACHED=y
|
||||
CONFIG_ARCH_RANDOM=y
|
||||
CONFIG_X86_SMAP=y
|
||||
# CONFIG_X86_UMIP is not set
|
||||
# CONFIG_X86_INTEL_MPX 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
|
||||
@@ -411,8 +422,12 @@ CONFIG_ARCH_HAS_KEXEC_PURGATORY=y
|
||||
# CONFIG_CRASH_DUMP is not set
|
||||
CONFIG_PHYSICAL_START=0x1000000
|
||||
CONFIG_RELOCATABLE=y
|
||||
# CONFIG_RANDOMIZE_BASE is not set
|
||||
CONFIG_RANDOMIZE_BASE=y
|
||||
CONFIG_X86_NEED_RELOCS=y
|
||||
CONFIG_PHYSICAL_ALIGN=0x1000000
|
||||
CONFIG_DYNAMIC_MEMORY_LAYOUT=y
|
||||
CONFIG_RANDOMIZE_MEMORY=y
|
||||
CONFIG_RANDOMIZE_MEMORY_PHYSICAL_PADDING=0xa
|
||||
CONFIG_HOTPLUG_CPU=y
|
||||
# CONFIG_BOOTPARAM_HOTPLUG_CPU0 is not set
|
||||
# CONFIG_DEBUG_HOTPLUG_CPU0 is not set
|
||||
@@ -477,6 +492,7 @@ CONFIG_ACPI_HOTPLUG_CPU=y
|
||||
# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set
|
||||
CONFIG_ACPI_THERMAL=y
|
||||
CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
|
||||
CONFIG_ACPI_TABLE_UPGRADE=y
|
||||
# CONFIG_ACPI_DEBUG is not set
|
||||
# CONFIG_ACPI_PCI_SLOT is not set
|
||||
CONFIG_ACPI_CONTAINER=y
|
||||
@@ -589,6 +605,7 @@ CONFIG_EFI_RUNTIME_WRAPPERS=y
|
||||
# CONFIG_APPLE_PROPERTIES is not set
|
||||
# CONFIG_RESET_ATTACK_MITIGATION is not set
|
||||
# CONFIG_EFI_RCI2_TABLE is not set
|
||||
# CONFIG_EFI_DISABLE_PCI_DMA is not set
|
||||
# end of EFI (Extensible Firmware Interface) Support
|
||||
|
||||
CONFIG_EFI_EARLYCON=y
|
||||
@@ -614,6 +631,7 @@ CONFIG_HAVE_KVM_IRQ_BYPASS=y
|
||||
CONFIG_HAVE_KVM_NO_POLL=y
|
||||
CONFIG_VIRTUALIZATION=y
|
||||
CONFIG_KVM=y
|
||||
# CONFIG_KVM_WERROR is not set
|
||||
CONFIG_KVM_INTEL=y
|
||||
# CONFIG_KVM_AMD is not set
|
||||
# CONFIG_VHOST_NET is not set
|
||||
@@ -662,7 +680,8 @@ CONFIG_HAVE_PERF_REGS=y
|
||||
CONFIG_HAVE_PERF_USER_STACK_DUMP=y
|
||||
CONFIG_HAVE_ARCH_JUMP_LABEL=y
|
||||
CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE=y
|
||||
CONFIG_HAVE_RCU_TABLE_FREE=y
|
||||
CONFIG_MMU_GATHER_TABLE_FREE=y
|
||||
CONFIG_MMU_GATHER_RCU_TABLE_FREE=y
|
||||
CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG=y
|
||||
CONFIG_HAVE_ALIGNED_STRUCT_PAGE=y
|
||||
CONFIG_HAVE_CMPXCHG_LOCAL=y
|
||||
@@ -907,6 +926,7 @@ CONFIG_TCP_CONG_CUBIC=y
|
||||
CONFIG_DEFAULT_TCP_CONG="cubic"
|
||||
# CONFIG_TCP_MD5SIG is not set
|
||||
# CONFIG_IPV6 is not set
|
||||
# CONFIG_MPTCP is not set
|
||||
# CONFIG_NETWORK_SECMARK is not set
|
||||
CONFIG_NET_PTP_CLASSIFY=y
|
||||
# CONFIG_NETWORK_PHY_TIMESTAMPING is not set
|
||||
@@ -936,6 +956,7 @@ CONFIG_HAVE_NET_DSA=y
|
||||
# CONFIG_OPENVSWITCH is not set
|
||||
CONFIG_VSOCKETS=y
|
||||
CONFIG_VSOCKETS_DIAG=y
|
||||
# CONFIG_VSOCKETS_LOOPBACK is not set
|
||||
CONFIG_VIRTIO_VSOCKETS=y
|
||||
CONFIG_VIRTIO_VSOCKETS_COMMON=y
|
||||
# CONFIG_NETLINK_DIAG is not set
|
||||
@@ -981,6 +1002,7 @@ CONFIG_STREAM_PARSER=y
|
||||
CONFIG_GRO_CELLS=y
|
||||
CONFIG_NET_SOCK_MSG=y
|
||||
CONFIG_FAILOVER=y
|
||||
# CONFIG_ETHTOOL_NETLINK is not set
|
||||
CONFIG_HAVE_EBPF_JIT=y
|
||||
|
||||
#
|
||||
@@ -992,6 +1014,7 @@ CONFIG_HAVE_PCI=y
|
||||
CONFIG_PCI=y
|
||||
CONFIG_PCI_DOMAINS=y
|
||||
CONFIG_PCIEPORTBUS=y
|
||||
# CONFIG_HOTPLUG_PCI_PCIE is not set
|
||||
CONFIG_PCIEAER=y
|
||||
# CONFIG_PCIEAER_INJECT is not set
|
||||
# CONFIG_PCIE_ECRC is not set
|
||||
@@ -1015,7 +1038,11 @@ CONFIG_PCI_LOCKLESS_CONFIG=y
|
||||
# CONFIG_PCI_PASID is not set
|
||||
# CONFIG_PCI_P2PDMA is not set
|
||||
CONFIG_PCI_LABEL=y
|
||||
# CONFIG_HOTPLUG_PCI is not set
|
||||
CONFIG_HOTPLUG_PCI=y
|
||||
CONFIG_HOTPLUG_PCI_ACPI=y
|
||||
# CONFIG_HOTPLUG_PCI_ACPI_IBM is not set
|
||||
# CONFIG_HOTPLUG_PCI_CPCI is not set
|
||||
# CONFIG_HOTPLUG_PCI_SHPC is not set
|
||||
|
||||
#
|
||||
# PCI controller drivers
|
||||
@@ -1120,7 +1147,6 @@ CONFIG_BLK_DEV_RAM_SIZE=16384
|
||||
# CONFIG_CDROM_PKTCDVD is not set
|
||||
# CONFIG_ATA_OVER_ETH is not set
|
||||
CONFIG_VIRTIO_BLK=y
|
||||
# CONFIG_VIRTIO_BLK_SCSI is not set
|
||||
# CONFIG_BLK_DEV_RBD is not set
|
||||
# CONFIG_BLK_DEV_RSXX is not set
|
||||
|
||||
@@ -1211,6 +1237,7 @@ CONFIG_NETDEVICES=y
|
||||
CONFIG_NET_CORE=y
|
||||
# CONFIG_BONDING is not set
|
||||
# CONFIG_DUMMY is not set
|
||||
# CONFIG_WIREGUARD is not set
|
||||
# CONFIG_EQUALIZER is not set
|
||||
# CONFIG_NET_TEAM is not set
|
||||
# CONFIG_MACVLAN is not set
|
||||
@@ -1341,6 +1368,7 @@ CONFIG_SERIAL_EARLYCON=y
|
||||
CONFIG_SERIAL_8250=y
|
||||
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
|
||||
CONFIG_SERIAL_8250_PNP=y
|
||||
# CONFIG_SERIAL_8250_16550A_VARIANTS is not set
|
||||
# CONFIG_SERIAL_8250_FINTEK is not set
|
||||
CONFIG_SERIAL_8250_CONSOLE=y
|
||||
CONFIG_SERIAL_8250_DMA=y
|
||||
@@ -1434,7 +1462,6 @@ CONFIG_PTP_1588_CLOCK=y
|
||||
# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks.
|
||||
#
|
||||
CONFIG_PTP_1588_CLOCK_KVM=y
|
||||
# CONFIG_PTP_1588_CLOCK_IDTCM is not set
|
||||
# end of PTP clock support
|
||||
|
||||
# CONFIG_PINCTRL is not set
|
||||
@@ -1451,7 +1478,6 @@ CONFIG_THERMAL_WRITABLE_TRIPS=y
|
||||
CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y
|
||||
# CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set
|
||||
# CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE is not set
|
||||
# CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR is not set
|
||||
CONFIG_THERMAL_GOV_FAIR_SHARE=y
|
||||
CONFIG_THERMAL_GOV_STEP_WISE=y
|
||||
# CONFIG_THERMAL_GOV_BANG_BANG is not set
|
||||
@@ -1514,7 +1540,6 @@ CONFIG_VGA_ARB=y
|
||||
CONFIG_VGA_ARB_MAX_GPUS=16
|
||||
# CONFIG_VGA_SWITCHEROO is not set
|
||||
# CONFIG_DRM is not set
|
||||
# CONFIG_DRM_DP_CEC is not set
|
||||
|
||||
#
|
||||
# ARM devices
|
||||
@@ -1719,7 +1744,9 @@ CONFIG_DMA_VIRTUAL_CHANNELS=y
|
||||
CONFIG_DMA_ACPI=y
|
||||
# CONFIG_ALTERA_MSGDMA is not set
|
||||
# CONFIG_INTEL_IDMA64 is not set
|
||||
# CONFIG_INTEL_IDXD is not set
|
||||
# CONFIG_INTEL_IOATDMA is not set
|
||||
# CONFIG_PLX_DMA is not set
|
||||
# CONFIG_QCOM_HIDMA_MGMT is not set
|
||||
# CONFIG_QCOM_HIDMA is not set
|
||||
CONFIG_DW_DMAC_CORE=y
|
||||
@@ -1740,6 +1767,7 @@ CONFIG_HSU_DMA=y
|
||||
# DMABUF options
|
||||
#
|
||||
# CONFIG_SYNC_FILE is not set
|
||||
# CONFIG_DMABUF_HEAPS is not set
|
||||
# end of DMABUF options
|
||||
|
||||
# CONFIG_AUXDISPLAY is not set
|
||||
@@ -1771,6 +1799,7 @@ CONFIG_VIRTIO_PCI=y
|
||||
CONFIG_VIRTIO_PCI_LEGACY=y
|
||||
CONFIG_VIRTIO_PMEM=y
|
||||
CONFIG_VIRTIO_BALLOON=y
|
||||
CONFIG_VIRTIO_MEM=y
|
||||
CONFIG_VIRTIO_INPUT=y
|
||||
CONFIG_VIRTIO_MMIO=y
|
||||
CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
|
||||
@@ -1909,6 +1938,7 @@ CONFIG_VIRTIO_IOMMU_TOPOLOGY=y
|
||||
# CONFIG_BCM_KONA_USB2_PHY is not set
|
||||
# CONFIG_PHY_PXA_28NM_HSIC is not set
|
||||
# CONFIG_PHY_PXA_28NM_USB2 is not set
|
||||
# CONFIG_PHY_INTEL_EMMC is not set
|
||||
# end of PHY Subsystem
|
||||
|
||||
# CONFIG_POWERCAP is not set
|
||||
@@ -1920,7 +1950,7 @@ CONFIG_VIRTIO_IOMMU_TOPOLOGY=y
|
||||
# end of Performance monitor support
|
||||
|
||||
CONFIG_RAS=y
|
||||
# CONFIG_THUNDERBOLT is not set
|
||||
# CONFIG_USB4 is not set
|
||||
|
||||
#
|
||||
# Android
|
||||
@@ -1950,6 +1980,7 @@ CONFIG_DAX=y
|
||||
# end of HW tracing support
|
||||
|
||||
# CONFIG_FPGA is not set
|
||||
# CONFIG_TEE is not set
|
||||
# CONFIG_UNISYS_VISORBUS is not set
|
||||
# CONFIG_SIOX is not set
|
||||
# CONFIG_SLIMBUS is not set
|
||||
@@ -1989,6 +2020,7 @@ CONFIG_EXPORTFS=y
|
||||
CONFIG_FILE_LOCKING=y
|
||||
CONFIG_MANDATORY_FILE_LOCKING=y
|
||||
CONFIG_FS_ENCRYPTION=y
|
||||
CONFIG_FS_ENCRYPTION_ALGS=y
|
||||
# CONFIG_FS_VERITY is not set
|
||||
CONFIG_FSNOTIFY=y
|
||||
CONFIG_DNOTIFY=y
|
||||
@@ -2343,7 +2375,7 @@ CONFIG_CRYPTO_LIB_ARC4=y
|
||||
# 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_RSIZE=11
|
||||
CONFIG_CRYPTO_LIB_POLY1305_GENERIC=y
|
||||
# CONFIG_CRYPTO_LIB_POLY1305 is not set
|
||||
# CONFIG_CRYPTO_LIB_CHACHA20POLY1305 is not set
|
||||
@@ -2394,7 +2426,22 @@ CONFIG_ZLIB_INFLATE=y
|
||||
CONFIG_ZLIB_DEFLATE=y
|
||||
CONFIG_LZO_COMPRESS=y
|
||||
CONFIG_LZO_DECOMPRESS=y
|
||||
# CONFIG_XZ_DEC is not set
|
||||
CONFIG_LZ4_DECOMPRESS=y
|
||||
CONFIG_XZ_DEC=y
|
||||
CONFIG_XZ_DEC_X86=y
|
||||
CONFIG_XZ_DEC_POWERPC=y
|
||||
CONFIG_XZ_DEC_IA64=y
|
||||
CONFIG_XZ_DEC_ARM=y
|
||||
CONFIG_XZ_DEC_ARMTHUMB=y
|
||||
CONFIG_XZ_DEC_SPARC=y
|
||||
CONFIG_XZ_DEC_BCJ=y
|
||||
# CONFIG_XZ_DEC_TEST is not set
|
||||
CONFIG_DECOMPRESS_GZIP=y
|
||||
CONFIG_DECOMPRESS_BZIP2=y
|
||||
CONFIG_DECOMPRESS_LZMA=y
|
||||
CONFIG_DECOMPRESS_XZ=y
|
||||
CONFIG_DECOMPRESS_LZO=y
|
||||
CONFIG_DECOMPRESS_LZ4=y
|
||||
CONFIG_INTERVAL_TREE=y
|
||||
CONFIG_XARRAY_MULTI=y
|
||||
CONFIG_ASSOCIATIVE_ARRAY=y
|
||||
@@ -2415,6 +2462,7 @@ CONFIG_IRQ_POLL=y
|
||||
CONFIG_UCS2_STRING=y
|
||||
CONFIG_HAVE_GENERIC_VDSO=y
|
||||
CONFIG_GENERIC_GETTIMEOFDAY=y
|
||||
CONFIG_GENERIC_VDSO_TIME_NS=y
|
||||
CONFIG_FONT_SUPPORT=y
|
||||
# CONFIG_FONTS is not set
|
||||
CONFIG_FONT_8x8=y
|
||||
@@ -2487,6 +2535,8 @@ CONFIG_DEBUG_MISC=y
|
||||
# CONFIG_PAGE_OWNER is not set
|
||||
# CONFIG_PAGE_POISONING is not set
|
||||
# CONFIG_DEBUG_RODATA_TEST is not set
|
||||
CONFIG_GENERIC_PTDUMP=y
|
||||
# CONFIG_PTDUMP_DEBUGFS is not set
|
||||
# CONFIG_DEBUG_OBJECTS is not set
|
||||
# CONFIG_SLUB_DEBUG_ON is not set
|
||||
# CONFIG_SLUB_STATS is not set
|
||||
@@ -2592,14 +2642,46 @@ CONFIG_HAVE_C_RECORDMCOUNT=y
|
||||
CONFIG_TRACING_SUPPORT=y
|
||||
# CONFIG_FTRACE 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
|
||||
# CONFIG_SAMPLES is not set
|
||||
CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y
|
||||
CONFIG_STRICT_DEVMEM=y
|
||||
# CONFIG_IO_STRICT_DEVMEM is not set
|
||||
|
||||
#
|
||||
# x86 Debugging
|
||||
#
|
||||
CONFIG_TRACE_IRQFLAGS_SUPPORT=y
|
||||
CONFIG_X86_VERBOSE_BOOTUP=y
|
||||
CONFIG_EARLY_PRINTK=y
|
||||
# CONFIG_EARLY_PRINTK_DBGP is not set
|
||||
# CONFIG_EARLY_PRINTK_USB_XDBC is not set
|
||||
# CONFIG_EFI_PGT_DUMP is not set
|
||||
# CONFIG_DEBUG_WX is not set
|
||||
CONFIG_DOUBLEFAULT=y
|
||||
# CONFIG_DEBUG_TLBFLUSH is not set
|
||||
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
|
||||
# CONFIG_X86_DECODER_SELFTEST is not set
|
||||
CONFIG_IO_DELAY_0X80=y
|
||||
# CONFIG_IO_DELAY_0XED is not set
|
||||
# CONFIG_IO_DELAY_UDELAY is not set
|
||||
# CONFIG_IO_DELAY_NONE is not set
|
||||
# CONFIG_DEBUG_BOOT_PARAMS is not set
|
||||
# CONFIG_CPA_DEBUG is not set
|
||||
# CONFIG_DEBUG_ENTRY is not set
|
||||
# CONFIG_DEBUG_NMI_SELFTEST is not set
|
||||
# CONFIG_X86_DEBUG_FPU is not set
|
||||
# CONFIG_PUNIT_ATOM_DEBUG is not set
|
||||
# CONFIG_UNWINDER_ORC is not set
|
||||
CONFIG_UNWINDER_FRAME_POINTER=y
|
||||
# CONFIG_UNWINDER_GUESS is not set
|
||||
# end of x86 Debugging
|
||||
|
||||
#
|
||||
# Kernel Testing and Coverage
|
||||
#
|
||||
# CONFIG_KUNIT is not set
|
||||
# CONFIG_NOTIFIER_ERROR_INJECTION is not set
|
||||
# CONFIG_FAULT_INJECTION is not set
|
||||
CONFIG_ARCH_HAS_KCOV=y
|
||||
CONFIG_CC_HAS_SANCOV_TRACE_PC=y
|
||||
# CONFIG_KCOV is not set
|
||||
@@ -2633,38 +2715,5 @@ CONFIG_RUNTIME_TESTING_MENU=y
|
||||
# CONFIG_TEST_STACKINIT is not set
|
||||
# CONFIG_TEST_MEMINIT is not set
|
||||
# CONFIG_MEMTEST is not set
|
||||
# CONFIG_SAMPLES is not set
|
||||
CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y
|
||||
CONFIG_STRICT_DEVMEM=y
|
||||
# CONFIG_IO_STRICT_DEVMEM is not set
|
||||
|
||||
#
|
||||
# x86 Debugging
|
||||
#
|
||||
CONFIG_TRACE_IRQFLAGS_SUPPORT=y
|
||||
CONFIG_X86_VERBOSE_BOOTUP=y
|
||||
CONFIG_EARLY_PRINTK=y
|
||||
# CONFIG_EARLY_PRINTK_DBGP is not set
|
||||
# CONFIG_EARLY_PRINTK_USB_XDBC is not set
|
||||
# CONFIG_X86_PTDUMP is not set
|
||||
# CONFIG_EFI_PGT_DUMP is not set
|
||||
# CONFIG_DEBUG_WX is not set
|
||||
CONFIG_DOUBLEFAULT=y
|
||||
# CONFIG_DEBUG_TLBFLUSH is not set
|
||||
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
|
||||
# CONFIG_X86_DECODER_SELFTEST is not set
|
||||
CONFIG_IO_DELAY_0X80=y
|
||||
# CONFIG_IO_DELAY_0XED is not set
|
||||
# CONFIG_IO_DELAY_UDELAY is not set
|
||||
# CONFIG_IO_DELAY_NONE is not set
|
||||
# CONFIG_DEBUG_BOOT_PARAMS is not set
|
||||
# CONFIG_CPA_DEBUG is not set
|
||||
# CONFIG_DEBUG_ENTRY is not set
|
||||
# CONFIG_DEBUG_NMI_SELFTEST is not set
|
||||
# CONFIG_X86_DEBUG_FPU is not set
|
||||
# CONFIG_PUNIT_ATOM_DEBUG is not set
|
||||
# CONFIG_UNWINDER_ORC is not set
|
||||
CONFIG_UNWINDER_FRAME_POINTER=y
|
||||
# CONFIG_UNWINDER_GUESS is not set
|
||||
# end of x86 Debugging
|
||||
# end of Kernel Testing and Coverage
|
||||
# end of Kernel hacking
|
||||
@@ -7,7 +7,7 @@
|
||||
CLI_NAME="Cloud Hypervisor"
|
||||
|
||||
CTR_IMAGE_TAG="cloudhypervisor/dev"
|
||||
CTR_IMAGE_VERSION="v1"
|
||||
CTR_IMAGE_VERSION="v3"
|
||||
CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}"
|
||||
|
||||
DOCKER_RUNTIME="docker"
|
||||
@@ -23,7 +23,8 @@ 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_CARGO_BUILT_DIR="${CTR_CLH_ROOT_DIR}/build"
|
||||
CTR_CLH_CARGO_TARGET="${CTR_CLH_CARGO_BUILT_DIR}/cargo_target"
|
||||
CTR_CLH_INTEGRATION_WORKLOADS="/root/workloads"
|
||||
|
||||
# Cargo paths
|
||||
@@ -112,6 +113,32 @@ ensure_build_dir() {
|
||||
done
|
||||
}
|
||||
|
||||
# Make sure we're using the latest dev container, by just pulling it.
|
||||
ensure_latest_ctr() {
|
||||
$DOCKER_RUNTIME pull "$CTR_IMAGE"
|
||||
|
||||
ok_or_die "Error pulling container image. Aborting."
|
||||
}
|
||||
|
||||
# Fix main directory permissions after a container ran as root.
|
||||
# Since the container ran as root, any files it creates will be owned by root.
|
||||
# This fixes that by recursively changing the ownership of /cloud-hypervisor to the
|
||||
# current user.
|
||||
#
|
||||
fix_dir_perms() {
|
||||
# Yes, running Docker to get elevated privileges, just to chown some files
|
||||
# is a dirty hack.
|
||||
$DOCKER_RUNTIME run \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
"$CTR_IMAGE" \
|
||||
chown -R "$(id -u):$(id -g)" "$CTR_CLH_ROOT_DIR"
|
||||
|
||||
return $1
|
||||
}
|
||||
|
||||
cmd_help() {
|
||||
echo ""
|
||||
echo "Cloud Hypervisor $(basename $0)"
|
||||
@@ -119,16 +146,18 @@ cmd_help() {
|
||||
echo ""
|
||||
echo "Available commands:"
|
||||
echo ""
|
||||
echo " build [--debug|--release] [-- [<cargo args>]]"
|
||||
echo " build [--debug|--release] [--libc musl|gnu] [-- [<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 " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
|
||||
echo ""
|
||||
echo " tests [--unit|--cargo|--all] [-- [<cargo test args>]]"
|
||||
echo " tests [--unit|--cargo|--all] [--libc musl|gnu] [-- [<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 " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
|
||||
echo " --all Run all tests."
|
||||
echo ""
|
||||
echo " build-container [--type]"
|
||||
@@ -145,12 +174,19 @@ cmd_help() {
|
||||
|
||||
cmd_build() {
|
||||
build="debug"
|
||||
libc="gnu"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--debug") { build="debug"; } ;;
|
||||
"--release") { build="release"; } ;;
|
||||
"--libc")
|
||||
shift
|
||||
[[ "$1" =~ ^(musl|gnu)$ ]] || \
|
||||
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
|
||||
libc="$1"
|
||||
;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown build argument: $1. Please use --help for help."
|
||||
@@ -159,35 +195,42 @@ cmd_build() {
|
||||
shift
|
||||
done
|
||||
|
||||
target="$(uname -m)-unknown-linux-${libc}"
|
||||
|
||||
cargo_args=("$@")
|
||||
[ $build = "release" ] && cargo_args+=("--release")
|
||||
cargo_args+=(--target "$target")
|
||||
[ $(uname -m) = "aarch64" ] && cargo_args+=("--no-default-features")
|
||||
[ $(uname -m) = "aarch64" ] && cargo_args+=(--features "mmio")
|
||||
|
||||
rustflags=""
|
||||
if [ $(uname -m) = "aarch64" ] && [ $libc = "musl" ] ; then
|
||||
rustflags="-C link-arg=-lgcc"
|
||||
fi
|
||||
|
||||
# A workaround on Arm64 to avoid build errors in kvm-bindings
|
||||
if [ $(uname -m) = "aarch64" ]; then
|
||||
sed -i 's/"with-serde",\ //g' "$CLH_ROOT_DIR"/vmm/Cargo.toml
|
||||
fi
|
||||
|
||||
$DOCKER_RUNTIME run \
|
||||
-ti \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
--env RUSTFLAGS="$rustflags" \
|
||||
"$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"
|
||||
}
|
||||
"${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$target/$build"
|
||||
}
|
||||
|
||||
cmd_clean() {
|
||||
cargo_args=("$@")
|
||||
|
||||
$DOCKER_RUNTIME run \
|
||||
-ti \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
@@ -201,15 +244,22 @@ cmd_tests() {
|
||||
unit=false
|
||||
cargo=false
|
||||
integration=false
|
||||
libc="gnu"
|
||||
|
||||
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; } ;;
|
||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||
"--unit") { unit=true; } ;;
|
||||
"--cargo") { cargo=true; } ;;
|
||||
"--integration") { integration=true; } ;;
|
||||
"--libc")
|
||||
shift
|
||||
[[ "$1" =~ ^(musl|gnu)$ ]] || \
|
||||
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
|
||||
libc="$1"
|
||||
;;
|
||||
"--all") { cargo=true; unit=true; integration=true; } ;;
|
||||
"--") { shift; break; } ;;
|
||||
*)
|
||||
die "Unknown tests argument: $1. Please use --help for help."
|
||||
;;
|
||||
@@ -217,43 +267,60 @@ cmd_tests() {
|
||||
shift
|
||||
done
|
||||
|
||||
target="$(uname -m)-unknown-linux-${libc}"
|
||||
cflags=""
|
||||
target_cc=""
|
||||
if [[ "$target" == "x86_64-unknown-linux-musl" ]]; then
|
||||
target_cc="musl-gcc"
|
||||
cflags="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
|
||||
fi
|
||||
|
||||
if [ "$unit" = true ] ; then
|
||||
say "Running unit tests..."
|
||||
say "Running unit tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
-ti \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--volume /dev:/dev \
|
||||
--device /dev/kvm \
|
||||
--device /dev/net/tun \
|
||||
--cap-add net_admin \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
--env BUILD_TARGET="$target" \
|
||||
--env CFLAGS="$cflags" \
|
||||
--env TARGET_CC="$target_cc" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_unit_tests.sh "$@"
|
||||
./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||
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
|
||||
./scripts/run_cargo_tests.sh || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
if [ "$integration" = true ] ; then
|
||||
say "Running integration tests..."
|
||||
say "Running integration tests for $target..."
|
||||
$DOCKER_RUNTIME run \
|
||||
-ti \
|
||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||
--rm \
|
||||
--privileged \
|
||||
--security-opt seccomp=unconfined \
|
||||
--ipc=host \
|
||||
--net=host \
|
||||
--mount type=tmpfs,destination=/tmp \
|
||||
--volume /dev:/dev \
|
||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||
--env USER="root" \
|
||||
--env CH_LIBC="${libc}" \
|
||||
"$CTR_IMAGE" \
|
||||
./scripts/run_integration_tests.sh "$@"
|
||||
./scripts/run_integration_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||
fi
|
||||
|
||||
fix_dir_perms $?
|
||||
}
|
||||
|
||||
cmd_build-container() {
|
||||
@@ -280,6 +347,7 @@ cmd_build-container() {
|
||||
--target $container_type \
|
||||
-t $CTR_IMAGE \
|
||||
-f $BUILD_DIR/Dockerfile \
|
||||
--build-arg TARGETARCH="$(uname -m)" \
|
||||
$BUILD_DIR
|
||||
}
|
||||
|
||||
@@ -309,5 +377,13 @@ cmd=cmd_$1
|
||||
shift
|
||||
|
||||
ensure_build_dir
|
||||
if [ $(uname -m) = "x86_64" ]; then
|
||||
ensure_latest_ctr
|
||||
fi
|
||||
|
||||
# Before a public image for AArch64 ready, we build the container if needed.
|
||||
if [ $(uname -m) = "aarch64" ]; then
|
||||
cmd_build-container
|
||||
fi
|
||||
|
||||
$cmd "$@"
|
||||
|
||||
@@ -3,31 +3,35 @@ set -x
|
||||
|
||||
source $HOME/.cargo/env
|
||||
|
||||
export BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
|
||||
|
||||
WORKLOADS_DIR="$HOME/workloads"
|
||||
mkdir -p "$WORKLOADS_DIR"
|
||||
|
||||
cp scripts/sha1sums $WORKLOADS_DIR
|
||||
|
||||
FW_URL=$(curl --silent https://api.github.com/repos/cloud-hypervisor/rust-hypervisor-firmware/releases/latest | grep "browser_download_url" | grep -o 'https://.*[^ "]')
|
||||
FW="$WORKLOADS_DIR/hypervisor-fw"
|
||||
if [ ! -f "$FW" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time wget --quiet $FW_URL
|
||||
time wget --quiet $FW_URL || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
CLEAR_OS_IMAGE_NAME="clear-31310-cloudguest.img"
|
||||
CLEAR_OS_IMAGE_NAME="clear-31311-cloudguest.img"
|
||||
CLEAR_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$CLEAR_OS_IMAGE_NAME"
|
||||
CLEAR_OS_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_NAME"
|
||||
if [ ! -f "$CLEAR_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time wget --quiet $CLEAR_OS_IMAGE_URL
|
||||
time wget --quiet $CLEAR_OS_IMAGE_URL || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
CLEAR_OS_RAW_IMAGE_NAME="clear-31310-cloudguest-raw.img"
|
||||
CLEAR_OS_RAW_IMAGE_NAME="clear-31311-cloudguest-raw.img"
|
||||
CLEAR_OS_RAW_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$CLEAR_OS_RAW_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time qemu-img convert -p -f qcow2 -O raw $CLEAR_OS_IMAGE_NAME $CLEAR_OS_RAW_IMAGE_NAME
|
||||
time qemu-img convert -p -f qcow2 -O raw $CLEAR_OS_IMAGE_NAME $CLEAR_OS_RAW_IMAGE_NAME || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -36,7 +40,7 @@ BIONIC_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images
|
||||
BIONIC_OS_IMAGE="$WORKLOADS_DIR/$BIONIC_OS_IMAGE_NAME"
|
||||
if [ ! -f "$BIONIC_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time wget --quiet $BIONIC_OS_IMAGE_URL
|
||||
time wget --quiet $BIONIC_OS_IMAGE_URL || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -44,30 +48,56 @@ 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
|
||||
time qemu-img convert -p -f qcow2 -O raw $BIONIC_OS_IMAGE_NAME $BIONIC_OS_RAW_IMAGE_NAME || exit 1
|
||||
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
|
||||
FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64.img"
|
||||
FOCAL_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$FOCAL_OS_IMAGE_NAME"
|
||||
FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME"
|
||||
if [ ! -f "$FOCAL_OS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time wget --quiet $EOAN_OS_IMAGE_URL
|
||||
time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1
|
||||
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
|
||||
FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-raw.img"
|
||||
FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME"
|
||||
if [ ! -f "$FOCAL_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
|
||||
time qemu-img convert -p -f qcow2 -O raw $FOCAL_OS_IMAGE_NAME $FOCAL_OS_RAW_IMAGE_NAME || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
ALPINE_MINIROOTFS_URL="http://dl-cdn.alpinelinux.org/alpine/v3.11/releases/x86_64/alpine-minirootfs-3.11.3-x86_64.tar.gz"
|
||||
ALPINE_MINIROOTFS_TARBALL="$WORKLOADS_DIR/alpine-minirootfs-x86_64.tar.gz"
|
||||
if [ ! -f "$ALPINE_MINIROOTFS_TARBALL" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
time wget --quiet $ALPINE_MINIROOTFS_URL -O $ALPINE_MINIROOTFS_TARBALL || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
ALPINE_INITRAMFS_IMAGE="$WORKLOADS_DIR/alpine_initramfs.img"
|
||||
if [ ! -f "$ALPINE_INITRAMFS_IMAGE" ]; then
|
||||
pushd $WORKLOADS_DIR
|
||||
mkdir alpine-minirootfs
|
||||
tar xf "$ALPINE_MINIROOTFS_TARBALL" -C alpine-minirootfs
|
||||
cat > alpine-minirootfs/init <<-EOF
|
||||
#! /bin/sh
|
||||
mount -t devtmpfs dev /dev
|
||||
echo \$TEST_STRING > /dev/console
|
||||
poweroff -f
|
||||
EOF
|
||||
chmod +x alpine-minirootfs/init
|
||||
cd alpine-minirootfs
|
||||
find . -print0 |
|
||||
cpio --null --create --verbose --owner root:root --format=newc > "$ALPINE_INITRAMFS_IMAGE"
|
||||
popd
|
||||
fi
|
||||
|
||||
pushd $WORKLOADS_DIR
|
||||
curl --silent "https://cloudhypervisorstorage.blob.core.windows.net/images/sha1sums" | sha1sum --check
|
||||
sha1sum sha1sums --check
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "sha1sum validation of images failed, remove invalid images to fix the issue."
|
||||
exit 1
|
||||
@@ -76,22 +106,38 @@ popd
|
||||
|
||||
# Build custom kernel based on virtio-pmem and virtio-fs upstream patches
|
||||
VMLINUX_IMAGE="$WORKLOADS_DIR/vmlinux"
|
||||
VMLINUX_PVH_IMAGE="$WORKLOADS_DIR/vmlinux.pvh"
|
||||
BZIMAGE_IMAGE="$WORKLOADS_DIR/bzImage"
|
||||
|
||||
LINUX_CUSTOM_DIR="linux-custom"
|
||||
LINUX_CUSTOM_DIR="$WORKLOADS_DIR/linux-custom"
|
||||
|
||||
if [ ! -f "$VMLINUX_IMAGE" ]; then
|
||||
if [ ! -f "$VMLINUX_IMAGE" ] || [ ! -f "$VMLINUX_PVH_IMAGE" ]; then
|
||||
SRCDIR=$PWD
|
||||
pushd $WORKLOADS_DIR
|
||||
time git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b "virtio-fs-virtio-iommu-topo-5.5-rc1" $LINUX_CUSTOM_DIR
|
||||
time git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b "virtio-fs-virtio-iommu-virtio-mem-5.6-rc4" $LINUX_CUSTOM_DIR
|
||||
cp $SRCDIR/resources/linux-config $LINUX_CUSTOM_DIR/.config
|
||||
popd
|
||||
fi
|
||||
|
||||
if [ ! -f "$VMLINUX_IMAGE" ]; then
|
||||
pushd $LINUX_CUSTOM_DIR
|
||||
cp $SRCDIR/resources/linux-virtio-fs-virtio-iommu-config .config
|
||||
scripts/config --disable "CONFIG_PVH"
|
||||
time make bzImage -j `nproc`
|
||||
cp vmlinux $VMLINUX_IMAGE
|
||||
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE
|
||||
cp vmlinux $VMLINUX_IMAGE || exit 1
|
||||
cp arch/x86/boot/bzImage $BZIMAGE_IMAGE || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
if [ ! -f "$VMLINUX_PVH_IMAGE" ]; then
|
||||
pushd $LINUX_CUSTOM_DIR
|
||||
scripts/config --enable "CONFIG_PVH"
|
||||
time make bzImage -j `nproc`
|
||||
cp vmlinux $VMLINUX_PVH_IMAGE || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
if [ -d "$LINUX_CUSTOM_DIR" ]; then
|
||||
rm -rf $LINUX_CUSTOM_DIR
|
||||
popd
|
||||
fi
|
||||
|
||||
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
|
||||
@@ -102,10 +148,10 @@ if [ ! -f "$VIRTIOFSD" ]; then
|
||||
pushd $QEMU_DIR
|
||||
time ./configure --prefix=$PWD --target-list=x86_64-softmmu
|
||||
time make virtiofsd -j `nproc`
|
||||
cp virtiofsd $VIRTIOFSD
|
||||
cp virtiofsd $VIRTIOFSD || exit 1
|
||||
popd
|
||||
rm -rf $QEMU_DIR
|
||||
sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd"
|
||||
sudo setcap cap_dac_override,cap_sys_admin+epi "virtiofsd" || exit 1
|
||||
popd
|
||||
fi
|
||||
|
||||
@@ -117,7 +163,7 @@ if [ ! -f "$BLK_IMAGE" ]; then
|
||||
mkfs.ext4 -j $BLK_IMAGE
|
||||
mkdir $MNT_DIR
|
||||
sudo mount -t ext4 $BLK_IMAGE $MNT_DIR
|
||||
sudo bash -c "echo bar > $MNT_DIR/foo"
|
||||
sudo bash -c "echo bar > $MNT_DIR/foo" || exit 1
|
||||
sudo umount $BLK_IMAGE
|
||||
rm -r $MNT_DIR
|
||||
popd
|
||||
@@ -127,16 +173,15 @@ SHARED_DIR="$WORKLOADS_DIR/shared_dir"
|
||||
if [ ! -d "$SHARED_DIR" ]; then
|
||||
mkdir -p $SHARED_DIR
|
||||
echo "foo" > "$SHARED_DIR/file1"
|
||||
echo "bar" > "$SHARED_DIR/file3"
|
||||
echo "bar" > "$SHARED_DIR/file3" || exit 1
|
||||
fi
|
||||
|
||||
VFIO_DIR="$WORKLOADS_DIR/vfio"
|
||||
if [ ! -d "$VFIO_DIR" ]; then
|
||||
mkdir -p $VFIO_DIR
|
||||
cp $CLEAR_OS_IMAGE $VFIO_DIR
|
||||
cp $FW $VFIO_DIR
|
||||
cp $VMLINUX_IMAGE $VFIO_DIR
|
||||
fi
|
||||
rm -rf $VFIO_DIR
|
||||
mkdir -p $VFIO_DIR
|
||||
cp $CLEAR_OS_IMAGE $VFIO_DIR
|
||||
cp $FW $VFIO_DIR
|
||||
cp $VMLINUX_IMAGE $VFIO_DIR || exit 1
|
||||
|
||||
# VFIO test network setup.
|
||||
# We reserve a different IP class for it: 172.17.0.0/24.
|
||||
@@ -156,13 +201,37 @@ sudo ip tuntap add vfio-tap2 mode tap
|
||||
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
|
||||
sudo ip tuntap add vfio-tap3 mode tap
|
||||
sudo ip link set vfio-tap3 master vfio-br0
|
||||
sudo ip link set vfio-tap3 up
|
||||
|
||||
# Create tap interface without multipe queues support for vhost_user_net test.
|
||||
sudo ip tuntap add name vunet-tap0 mode tap
|
||||
# Create tap interface with multipe queues support for vhost_user_net test.
|
||||
sudo ip tuntap add name vunet-tap1 mode tap multi_queue
|
||||
|
||||
BUILD_TARGET="$(uname -m)-unknown-linux-${CH_LIBC}"
|
||||
CFLAGS=""
|
||||
TARGET_CC=""
|
||||
if [[ "${BUILD_TARGET}" == "x86_64-unknown-linux-musl" ]]; then
|
||||
TARGET_CC="musl-gcc"
|
||||
CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
|
||||
fi
|
||||
|
||||
cargo build --release --target $BUILD_TARGET
|
||||
strip target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
strip target/$BUILD_TARGET/release/vhost_user_net
|
||||
strip target/$BUILD_TARGET/release/ch-remote
|
||||
|
||||
# Copy for non-privileged net test
|
||||
cp target/$BUILD_TARGET/release/cloud-hypervisor target/$BUILD_TARGET/release/cloud-hypervisor-unprivileged
|
||||
|
||||
sudo setcap cap_net_admin+ep target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
sudo setcap cap_net_admin+ep target/$BUILD_TARGET/release/vhost_user_net
|
||||
|
||||
# We always copy a fresh version of our binary for our L2 guest.
|
||||
cp target/release/cloud-hypervisor $VFIO_DIR
|
||||
cp target/$BUILD_TARGET/release/cloud-hypervisor $VFIO_DIR
|
||||
cp target/$BUILD_TARGET/release/ch-remote $VFIO_DIR
|
||||
|
||||
# Enable KSM with some reasonable parameters so that it won't take too long
|
||||
# for the memory to be merged between two processes.
|
||||
@@ -171,9 +240,13 @@ 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
|
||||
time cargo test --no-run --features "integration_tests" -- --nocapture || exit 1
|
||||
ls target/debug/deps/cloud_hypervisor-* | xargs -n 1 sudo setcap cap_net_admin+ep
|
||||
|
||||
# test_vfio relies on hugepages
|
||||
echo 4096 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
sudo chmod a+rwX /dev/hugepages
|
||||
|
||||
sudo adduser $USER kvm
|
||||
newgrp kvm << EOF
|
||||
export RUST_BACKTRACE=1
|
||||
@@ -183,8 +256,16 @@ 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
|
||||
cargo build --release --target $BUILD_TARGET --no-default-features --features "mmio"
|
||||
strip target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
strip target/$BUILD_TARGET/release/vhost_user_net
|
||||
strip target/$BUILD_TARGET/release/ch-remote
|
||||
|
||||
sudo setcap cap_net_admin+ep target/$BUILD_TARGET/release/cloud-hypervisor
|
||||
|
||||
# Ensure test binary has the same caps as the cloud-hypervisor one
|
||||
time cargo test --no-run --features "integration_tests,mmio" -- --nocapture || exit 1
|
||||
ls target/debug/deps/cloud_hypervisor-* | xargs -n 1 sudo setcap cap_net_admin+ep
|
||||
|
||||
newgrp kvm << EOF
|
||||
export RUST_BACKTRACE=1
|
||||
@@ -199,5 +280,10 @@ sudo ip link del vfio-br0
|
||||
sudo ip link del vfio-tap0
|
||||
sudo ip link del vfio-tap1
|
||||
sudo ip link del vfio-tap2
|
||||
sudo ip link del vfio-tap3
|
||||
|
||||
# Tear vhost_user_net test network down
|
||||
sudo ip link del vunet-tap0
|
||||
sudo ip link del vunet-tap1
|
||||
|
||||
exit $RES
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
source $HOME/.cargo/env
|
||||
|
||||
cargo test --workspace --no-run
|
||||
pushd target/debug
|
||||
BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
|
||||
|
||||
cargo test --target $BUILD_TARGET --workspace --no-run
|
||||
pushd target/$BUILD_TARGET/debug
|
||||
ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep
|
||||
popd
|
||||
|
||||
sudo adduser $USER kvm
|
||||
newgrp kvm << EOF || exit 1
|
||||
export RUST_BACKTRACE=1
|
||||
cargo test --workspace "$@" || exit 1;
|
||||
cargo test --target $BUILD_TARGET --workspace "$@" || exit 1;
|
||||
EOF
|
||||
|
||||
7
scripts/sha1sums
Normal file
7
scripts/sha1sums
Normal file
@@ -0,0 +1,7 @@
|
||||
cf7cfa783082fc4d6b4d1c0a53e4402648c14b82 clear-31311-cloudguest.img
|
||||
142a410546b592ff9536b46bb410faf8ac11edee clear-31311-cloudguest-raw.img
|
||||
27f3b17962ace69b51f0ddc2012095e3109e6ed8 bionic-server-cloudimg-amd64.img
|
||||
8db9cc58b01452ce2d06c313177e6e74d8582d93 bionic-server-cloudimg-amd64-raw.img
|
||||
d4a44acc6014d5f83dea1c625c43d677a95fa75f alpine-minirootfs-x86_64.tar.gz
|
||||
3cab64475048383e70a38550734e98961cfe0fd6 focal-server-cloudimg-amd64-raw.img
|
||||
c74a95665b189f93cb16a2c1770820c6a6a43fb0 focal-server-cloudimg-amd64.img
|
||||
548
src/bin/ch-remote.rs
Normal file
548
src/bin/ch-remote.rs
Normal file
@@ -0,0 +1,548 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#[macro_use(crate_authors)]
|
||||
extern crate clap;
|
||||
extern crate serde_json;
|
||||
extern crate vmm;
|
||||
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use std::fmt;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::process;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
Socket(std::io::Error),
|
||||
StatusCodeParsing(std::num::ParseIntError),
|
||||
MissingProtocol,
|
||||
ContentLengthParsing(std::num::ParseIntError),
|
||||
ServerResponse(StatusCode, Option<String>),
|
||||
InvalidCPUCount(std::num::ParseIntError),
|
||||
InvalidMemorySize(std::num::ParseIntError),
|
||||
AddDeviceConfig(vmm::config::Error),
|
||||
AddDiskConfig(vmm::config::Error),
|
||||
AddFsConfig(vmm::config::Error),
|
||||
AddPmemConfig(vmm::config::Error),
|
||||
AddNetConfig(vmm::config::Error),
|
||||
AddVsockConfig(vmm::config::Error),
|
||||
Restore(vmm::config::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use Error::*;
|
||||
match self {
|
||||
Socket(e) => write!(f, "Error writing to HTTP socket: {}", e),
|
||||
StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {}", e),
|
||||
MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
|
||||
ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {}", e),
|
||||
ServerResponse(s, o) => {
|
||||
if let Some(o) = o {
|
||||
write!(f, "Server responded with an error: {:?}: {}", s, o)
|
||||
} else {
|
||||
write!(f, "Server responded with an error: {:?}", s)
|
||||
}
|
||||
}
|
||||
InvalidCPUCount(e) => write!(f, "Error parsing CPU count: {}", e),
|
||||
InvalidMemorySize(e) => write!(f, "Error parsing memory size: {}", e),
|
||||
AddDeviceConfig(e) => write!(f, "Error parsing device syntax: {}", e),
|
||||
AddDiskConfig(e) => write!(f, "Error parsing disk syntax: {}", e),
|
||||
AddFsConfig(e) => write!(f, "Error parsing filesystem syntax: {}", e),
|
||||
AddPmemConfig(e) => write!(f, "Error parsing persistent memory syntax: {}", e),
|
||||
AddNetConfig(e) => write!(f, "Error parsing network syntax: {}", e),
|
||||
AddVsockConfig(e) => write!(f, "Error parsing vsock syntax: {}", e),
|
||||
Restore(e) => write!(f, "Error parsing restore syntax: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum StatusCode {
|
||||
Continue,
|
||||
OK,
|
||||
NoContent,
|
||||
BadRequest,
|
||||
NotFound,
|
||||
InternalServerError,
|
||||
NotImplemented,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl StatusCode {
|
||||
fn from_raw(code: usize) -> StatusCode {
|
||||
match code {
|
||||
100 => StatusCode::Continue,
|
||||
200 => StatusCode::OK,
|
||||
204 => StatusCode::NoContent,
|
||||
400 => StatusCode::BadRequest,
|
||||
404 => StatusCode::NotFound,
|
||||
500 => StatusCode::InternalServerError,
|
||||
501 => StatusCode::NotImplemented,
|
||||
_ => StatusCode::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(code: &str) -> Result<StatusCode, Error> {
|
||||
Ok(StatusCode::from_raw(
|
||||
code.trim().parse().map_err(Error::StatusCodeParsing)?,
|
||||
))
|
||||
}
|
||||
|
||||
fn is_server_error(self) -> bool {
|
||||
match self {
|
||||
StatusCode::OK | StatusCode::Continue | StatusCode::NoContent => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
|
||||
let header_str = format!("{}: ", header);
|
||||
if let Some(o) = res.find(&header_str) {
|
||||
Some(&res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_status_code(res: &str) -> Result<StatusCode, Error> {
|
||||
if let Some(o) = res.find("HTTP/1.1") {
|
||||
Ok(StatusCode::parse(
|
||||
&res[o + "HTTP/1.1 ".len()..res[o..].find('\r').unwrap()],
|
||||
)?)
|
||||
} else {
|
||||
Err(Error::MissingProtocol)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_http_response(socket: &mut UnixStream) -> Result<Option<String>, Error> {
|
||||
let mut res = String::new();
|
||||
let mut body_offset = None;
|
||||
let mut content_length: Option<usize> = None;
|
||||
loop {
|
||||
let mut bytes = vec![0; 256];
|
||||
let count = socket.read(&mut bytes).map_err(Error::Socket)?;
|
||||
res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
|
||||
|
||||
// End of headers
|
||||
if let Some(o) = res.find("\r\n\r\n") {
|
||||
body_offset = Some(o + "\r\n\r\n".len());
|
||||
|
||||
// With all headers available we can see if there is any body
|
||||
content_length = if let Some(length) = get_header(&res, "Content-Length") {
|
||||
Some(length.trim().parse().map_err(Error::ContentLengthParsing)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if content_length.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(body_offset) = body_offset {
|
||||
if let Some(content_length) = content_length {
|
||||
if res.len() >= content_length + body_offset {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let body_string = content_length.and(Some(String::from(&res[body_offset.unwrap()..])));
|
||||
let status_code = get_status_code(&res)?;
|
||||
|
||||
if status_code.is_server_error() {
|
||||
Err(Error::ServerResponse(status_code, body_string))
|
||||
} else {
|
||||
Ok(body_string)
|
||||
}
|
||||
}
|
||||
|
||||
fn simple_api_command(
|
||||
socket: &mut UnixStream,
|
||||
method: &str,
|
||||
c: &str,
|
||||
request_body: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
socket
|
||||
.write_all(
|
||||
format!(
|
||||
"{} /api/v1/vm.{} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n",
|
||||
method, c
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.map_err(Error::Socket)?;
|
||||
|
||||
if let Some(request_body) = request_body {
|
||||
socket
|
||||
.write_all(format!("Content-Length: {}\r\n", request_body.len()).as_bytes())
|
||||
.map_err(Error::Socket)?;
|
||||
}
|
||||
|
||||
socket.write_all(b"\r\n").map_err(Error::Socket)?;
|
||||
|
||||
if let Some(request_body) = request_body {
|
||||
socket
|
||||
.write_all(request_body.as_bytes())
|
||||
.map_err(Error::Socket)?;
|
||||
}
|
||||
|
||||
socket.flush().map_err(Error::Socket)?;
|
||||
|
||||
if let Some(body) = parse_http_response(socket)? {
|
||||
println!("{}", body);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resize_api_command(
|
||||
socket: &mut UnixStream,
|
||||
cpus: Option<&str>,
|
||||
memory: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
let desired_vcpus: Option<u8> = if let Some(cpus) = cpus {
|
||||
Some(cpus.parse().map_err(Error::InvalidCPUCount)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let desired_ram: Option<u64> = if let Some(memory) = memory {
|
||||
Some(memory.parse().map_err(Error::InvalidMemorySize)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let resize = vmm::api::VmResizeData {
|
||||
desired_vcpus,
|
||||
desired_ram,
|
||||
};
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"resize",
|
||||
Some(&serde_json::to_string(&resize).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_device_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let device_config = vmm::config::DeviceConfig::parse(config).map_err(Error::AddDeviceConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-device",
|
||||
Some(&serde_json::to_string(&device_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn remove_device_api_command(socket: &mut UnixStream, id: &str) -> Result<(), Error> {
|
||||
let remove_device_data = vmm::api::VmRemoveDeviceData { id: id.to_owned() };
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"remove-device",
|
||||
Some(&serde_json::to_string(&remove_device_data).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_disk_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let disk_config = vmm::config::DiskConfig::parse(config).map_err(Error::AddDiskConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-disk",
|
||||
Some(&serde_json::to_string(&disk_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_fs_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let fs_config = vmm::config::FsConfig::parse(config).map_err(Error::AddFsConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-fs",
|
||||
Some(&serde_json::to_string(&fs_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_pmem_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let pmem_config = vmm::config::PmemConfig::parse(config).map_err(Error::AddPmemConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-pmem",
|
||||
Some(&serde_json::to_string(&pmem_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_net_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let net_config = vmm::config::NetConfig::parse(config).map_err(Error::AddNetConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-net",
|
||||
Some(&serde_json::to_string(&net_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn add_vsock_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let vsock_config = vmm::config::VsockConfig::parse(config).map_err(Error::AddVsockConfig)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"add-vsock",
|
||||
Some(&serde_json::to_string(&vsock_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn snapshot_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> {
|
||||
let snapshot_config = vmm::api::VmSnapshotConfig {
|
||||
destination_url: String::from(url),
|
||||
};
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"snapshot",
|
||||
Some(&serde_json::to_string(&snapshot_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn restore_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Error> {
|
||||
let restore_config = vmm::config::RestoreConfig::parse(config).map_err(Error::Restore)?;
|
||||
|
||||
simple_api_command(
|
||||
socket,
|
||||
"PUT",
|
||||
"restore",
|
||||
Some(&serde_json::to_string(&restore_config).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
fn do_command(matches: &ArgMatches) -> Result<(), Error> {
|
||||
let mut socket =
|
||||
UnixStream::connect(matches.value_of("api-socket").unwrap()).map_err(Error::Socket)?;
|
||||
|
||||
match matches.subcommand_name() {
|
||||
Some("info") => simple_api_command(&mut socket, "GET", "info", None),
|
||||
Some("resize") => resize_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("resize")
|
||||
.unwrap()
|
||||
.value_of("cpus"),
|
||||
matches
|
||||
.subcommand_matches("resize")
|
||||
.unwrap()
|
||||
.value_of("memory"),
|
||||
),
|
||||
Some("add-device") => add_device_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-device")
|
||||
.unwrap()
|
||||
.value_of("device_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("remove-device") => remove_device_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("remove-device")
|
||||
.unwrap()
|
||||
.value_of("id")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("add-disk") => add_disk_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-disk")
|
||||
.unwrap()
|
||||
.value_of("disk_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("add-fs") => add_fs_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-fs")
|
||||
.unwrap()
|
||||
.value_of("fs_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("add-pmem") => add_pmem_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-pmem")
|
||||
.unwrap()
|
||||
.value_of("pmem_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("add-net") => add_net_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-net")
|
||||
.unwrap()
|
||||
.value_of("net_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("add-vsock") => add_vsock_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("add-vsock")
|
||||
.unwrap()
|
||||
.value_of("vsock_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("snapshot") => snapshot_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("snapshot")
|
||||
.unwrap()
|
||||
.value_of("snapshot_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some("restore") => restore_api_command(
|
||||
&mut socket,
|
||||
matches
|
||||
.subcommand_matches("restore")
|
||||
.unwrap()
|
||||
.value_of("restore_config")
|
||||
.unwrap(),
|
||||
),
|
||||
Some(c) => simple_api_command(&mut socket, "PUT", c, None),
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = App::new("ch-remote")
|
||||
.author(crate_authors!())
|
||||
.setting(AppSettings::SubcommandRequired)
|
||||
.about("Remotely control a cloud-hypervisor VMM.")
|
||||
.arg(
|
||||
Arg::with_name("api-socket")
|
||||
.long("api-socket")
|
||||
.help("HTTP API socket path (UNIX domain socket).")
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.required(true),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-device")
|
||||
.about("Add VFIO device")
|
||||
.arg(
|
||||
Arg::with_name("device_config")
|
||||
.index(1)
|
||||
.help(vmm::config::DeviceConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-disk")
|
||||
.about("Add block device")
|
||||
.arg(
|
||||
Arg::with_name("disk_config")
|
||||
.index(1)
|
||||
.help(vmm::config::DiskConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-fs")
|
||||
.about("Add virtio-fs backed fs device")
|
||||
.arg(
|
||||
Arg::with_name("fs_config")
|
||||
.index(1)
|
||||
.help(vmm::config::FsConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-pmem")
|
||||
.about("Add persistent memory device")
|
||||
.arg(
|
||||
Arg::with_name("pmem_config")
|
||||
.index(1)
|
||||
.help(vmm::config::PmemConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-net")
|
||||
.about("Add network device")
|
||||
.arg(
|
||||
Arg::with_name("net_config")
|
||||
.index(1)
|
||||
.help(vmm::config::NetConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("add-vsock")
|
||||
.about("Add vsock device")
|
||||
.arg(
|
||||
Arg::with_name("vsock_config")
|
||||
.index(1)
|
||||
.help(vmm::config::VsockConfig::SYNTAX),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("remove-device")
|
||||
.about("Remove VFIO device")
|
||||
.arg(Arg::with_name("id").index(1).help("<device_id>")),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("info").about("Info on the VM"))
|
||||
.subcommand(SubCommand::with_name("pause").about("Pause the VM"))
|
||||
.subcommand(SubCommand::with_name("reboot").about("Reboot the VM"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("resize")
|
||||
.about("Resize the VM")
|
||||
.arg(
|
||||
Arg::with_name("cpus")
|
||||
.long("cpus")
|
||||
.help("New vCPUs count")
|
||||
.takes_value(true)
|
||||
.number_of_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("memory")
|
||||
.long("memory")
|
||||
.help("New memory size (in MiB)")
|
||||
.takes_value(true)
|
||||
.number_of_values(1),
|
||||
),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("resume").about("Resume the VM"))
|
||||
.subcommand(SubCommand::with_name("shutdown").about("Shutdown the VM"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("snapshot")
|
||||
.about("Create a snapshot from VM")
|
||||
.arg(
|
||||
Arg::with_name("snapshot_config")
|
||||
.index(1)
|
||||
.help("<destination_url>"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("restore")
|
||||
.about("Restore VM from a snapshot")
|
||||
.arg(
|
||||
Arg::with_name("restore_config")
|
||||
.index(1)
|
||||
.help(vmm::config::RestoreConfig::SYNTAX),
|
||||
),
|
||||
);
|
||||
|
||||
let matches = app.get_matches();
|
||||
|
||||
if let Err(e) = do_command(&matches) {
|
||||
eprintln!("Error running command: {}", e);
|
||||
process::exit(1)
|
||||
};
|
||||
}
|
||||
@@ -23,11 +23,7 @@ fn main() {
|
||||
.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\"",
|
||||
)
|
||||
.help(vhost_user_block::SYNTAX)
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
|
||||
@@ -10,26 +10,36 @@ extern crate vhost_user_backend;
|
||||
extern crate vm_virtio;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use epoll;
|
||||
use futures::executor::{ThreadPool, ThreadPoolBuilder};
|
||||
use libc::EFD_NONBLOCK;
|
||||
use log::*;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use seccomp::SeccompAction;
|
||||
use std::num::Wrapping;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::{convert, error, fmt, io, process};
|
||||
|
||||
use vhost_rs::vhost_user::message::*;
|
||||
use vhost_rs::vhost_user::{Listener, SlaveFsCacheReq};
|
||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
|
||||
use vhost_user_fs::descriptor_utils::Error as VufDescriptorError;
|
||||
use vhost_user_fs::descriptor_utils::{Reader, Writer};
|
||||
use vhost_user_fs::filesystem::FileSystem;
|
||||
use vhost_user_fs::passthrough::{self, PassthroughFs};
|
||||
use vhost_user_fs::sandbox::Sandbox;
|
||||
use vhost_user_fs::seccomp::enable_seccomp;
|
||||
use vhost_user_fs::server::Server;
|
||||
use vhost_user_fs::Error as VhostUserFsError;
|
||||
use virtio_bindings::bindings::virtio_net::*;
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
use virtio_bindings::bindings::virtio_ring::{
|
||||
VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC,
|
||||
};
|
||||
use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vm_virtio::queue::DescriptorChain;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const QUEUE_SIZE: usize = 1024;
|
||||
const NUM_QUEUES: usize = 2;
|
||||
const THREAD_POOL_SIZE: usize = 64;
|
||||
|
||||
// The guest queued an available buffer for the high priority queue.
|
||||
const HIPRIO_QUEUE_EVENT: u16 = 0;
|
||||
@@ -45,6 +55,8 @@ type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||
enum Error {
|
||||
/// Failed to create kill eventfd.
|
||||
CreateKillEventFd(io::Error),
|
||||
/// Failed to create thread pool.
|
||||
CreateThreadPool(io::Error),
|
||||
/// Failed to handle event other than input event.
|
||||
HandleEventNotEpollIn,
|
||||
/// Failed to handle unknown event.
|
||||
@@ -53,6 +65,10 @@ enum Error {
|
||||
NoMemoryConfigured,
|
||||
/// Processing queue failed.
|
||||
ProcessQueue(VhostUserFsError),
|
||||
/// Creating a queue reader failed.
|
||||
QueueReader(VufDescriptorError),
|
||||
/// Creating a queue writer failed.
|
||||
QueueWriter(VufDescriptorError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
@@ -69,58 +85,108 @@ impl convert::From<Error> for io::Error {
|
||||
}
|
||||
}
|
||||
|
||||
struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
|
||||
mem: Option<GuestMemoryMmap>,
|
||||
struct VhostUserFsThread<F: FileSystem + Send + Sync + 'static> {
|
||||
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
kill_evt: EventFd,
|
||||
server: Arc<Server<F>>,
|
||||
// handle request from slave to master
|
||||
vu_req: Option<SlaveFsCacheReq>,
|
||||
event_idx: bool,
|
||||
pool: ThreadPool,
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsBackend<F> {
|
||||
impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsThread<F> {
|
||||
fn clone(&self) -> Self {
|
||||
VhostUserFsBackend {
|
||||
VhostUserFsThread {
|
||||
mem: self.mem.clone(),
|
||||
kill_evt: self.kill_evt.try_clone().unwrap(),
|
||||
server: self.server.clone(),
|
||||
vu_req: self.vu_req.clone(),
|
||||
event_idx: self.event_idx,
|
||||
pool: self.pool.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
|
||||
fn new(fs: F) -> Result<Self> {
|
||||
Ok(VhostUserFsBackend {
|
||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsThread<F> {
|
||||
fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
|
||||
Ok(VhostUserFsThread {
|
||||
mem: None,
|
||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
||||
server: Arc::new(Server::new(fs)),
|
||||
vu_req: None,
|
||||
event_idx: false,
|
||||
pool: ThreadPoolBuilder::new()
|
||||
.pool_size(thread_pool_size)
|
||||
.create()
|
||||
.map_err(Error::CreateThreadPool)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn process_queue(&mut self, vring: &mut Vring) -> Result<()> {
|
||||
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
|
||||
fn process_queue(&mut self, vring_lock: Arc<RwLock<Vring>>) -> Result<bool> {
|
||||
let mut used_any = false;
|
||||
let (atomic_mem, mem) = match &self.mem {
|
||||
Some(m) => (m, m.memory()),
|
||||
None => return Err(Error::NoMemoryConfigured),
|
||||
};
|
||||
let mut vring = vring_lock.write().unwrap();
|
||||
|
||||
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();
|
||||
used_any = true;
|
||||
|
||||
let total = self
|
||||
.server
|
||||
.handle_message(reader, writer)
|
||||
.map_err(Error::ProcessQueue)?;
|
||||
// Prepare a set of objects that can be moved to the worker thread.
|
||||
let desc_head = avail_desc.get_head();
|
||||
let atomic_mem = atomic_mem.clone();
|
||||
let server = self.server.clone();
|
||||
let mut vu_req = self.vu_req.clone();
|
||||
let event_idx = self.event_idx;
|
||||
let vring_lock = vring_lock.clone();
|
||||
|
||||
used_desc_heads[used_count] = (head_index, total);
|
||||
used_count += 1;
|
||||
self.pool.spawn_ok(async move {
|
||||
let mem = atomic_mem.memory();
|
||||
let desc = DescriptorChain::new_from_head(&mem, desc_head).unwrap();
|
||||
let head_index = desc.index;
|
||||
|
||||
let reader = Reader::new(&mem, desc.clone())
|
||||
.map_err(Error::QueueReader)
|
||||
.unwrap();
|
||||
let writer = Writer::new(&mem, desc.clone())
|
||||
.map_err(Error::QueueWriter)
|
||||
.unwrap();
|
||||
|
||||
server
|
||||
.handle_message(reader, writer, vu_req.as_mut())
|
||||
.map_err(Error::ProcessQueue)
|
||||
.unwrap();
|
||||
|
||||
let mut vring = vring_lock.write().unwrap();
|
||||
|
||||
if event_idx {
|
||||
let queue = vring.mut_queue();
|
||||
if let Some(used_idx) = queue.add_used(&mem, head_index, 0) {
|
||||
if queue.needs_notification(&mem, Wrapping(used_idx)) {
|
||||
vring.signal_used_queue().unwrap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vring.mut_queue().add_used(&mem, head_index, 0);
|
||||
vring.signal_used_queue().unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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(used_any)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
|
||||
thread: Mutex<VhostUserFsThread<F>>,
|
||||
}
|
||||
|
||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
|
||||
fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
|
||||
let thread = Mutex::new(VhostUserFsThread::new(fs, thread_pool_size)?);
|
||||
Ok(VhostUserFsBackend { thread })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,47 +200,86 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
|
||||
}
|
||||
|
||||
fn features(&self) -> u64 {
|
||||
1 << VIRTIO_F_VERSION_1 | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||
1 << VIRTIO_F_VERSION_1
|
||||
| 1 << VIRTIO_RING_F_INDIRECT_DESC
|
||||
| 1 << VIRTIO_RING_F_EVENT_IDX
|
||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||
}
|
||||
|
||||
fn protocol_features(&self) -> VhostUserProtocolFeatures {
|
||||
VhostUserProtocolFeatures::all()
|
||||
VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::SLAVE_REQ
|
||||
}
|
||||
|
||||
fn set_event_idx(&mut self, enabled: bool) {
|
||||
self.thread.lock().unwrap().event_idx = enabled;
|
||||
}
|
||||
|
||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||
self.mem = Some(mem);
|
||||
self.thread.lock().unwrap().mem = Some(GuestMemoryAtomic::new(mem));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
&self,
|
||||
device_event: u16,
|
||||
evset: epoll::Events,
|
||||
vrings: &[Arc<RwLock<Vring>>],
|
||||
_thread_id: usize,
|
||||
) -> VhostUserBackendResult<bool> {
|
||||
if evset != epoll::Events::EPOLLIN {
|
||||
return Err(Error::HandleEventNotEpollIn.into());
|
||||
}
|
||||
|
||||
match device_event {
|
||||
let mut thread = self.thread.lock().unwrap();
|
||||
let mem = match &thread.mem {
|
||||
Some(m) => m.memory(),
|
||||
None => return Err(Error::NoMemoryConfigured.into()),
|
||||
};
|
||||
|
||||
let vring_lock = match device_event {
|
||||
HIPRIO_QUEUE_EVENT => {
|
||||
debug!("HIPRIO_QUEUE_EVENT");
|
||||
vrings[0].clone()
|
||||
}
|
||||
REQ_QUEUE_EVENT => {
|
||||
debug!("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);
|
||||
debug!("QUEUE_EVENT");
|
||||
vrings[1].clone()
|
||||
}
|
||||
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
||||
};
|
||||
|
||||
if thread.event_idx {
|
||||
// vm-virtio's Queue implementation only checks avail_index
|
||||
// once, so to properly support EVENT_IDX we need to keep
|
||||
// calling process_queue() until it stops finding new
|
||||
// requests on the queue.
|
||||
loop {
|
||||
{
|
||||
let mut vring = vring_lock.write().unwrap();
|
||||
vring.mut_queue().update_avail_event(&mem);
|
||||
}
|
||||
if !thread.process_queue(vring_lock.clone())? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Without EVENT_IDX, a single call is enough.
|
||||
thread.process_queue(vring_lock)?;
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||
Some((
|
||||
self.thread.lock().unwrap().kill_evt.try_clone().unwrap(),
|
||||
Some(KILL_EVENT),
|
||||
))
|
||||
}
|
||||
|
||||
fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) {
|
||||
self.thread.lock().unwrap().vu_req = Some(vu_req);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -192,55 +297,127 @@ fn main() {
|
||||
.arg(
|
||||
Arg::with_name("sock")
|
||||
.long("sock")
|
||||
.help("vhost-user socket path (deprecated)")
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("socket")
|
||||
.long("socket")
|
||||
.help("vhost-user socket path")
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("thread-pool-size")
|
||||
.long("thread-pool-size")
|
||||
.help("thread pool size (default 64)")
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("disable-xattr")
|
||||
.long("disable-xattr")
|
||||
.help("Disable support for extended attributes"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("disable-sandbox")
|
||||
.long("disable-sandbox")
|
||||
.help("Don't set up a sandbox for the daemon"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("seccomp")
|
||||
.long("seccomp")
|
||||
.help("Disable/debug seccomp security")
|
||||
.possible_values(&["kill", "log", "trap", "none"])
|
||||
.default_value("kill"),
|
||||
)
|
||||
.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 socket = match cmd_arguments.value_of("socket") {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
println!("warning: use of deprecated parameter '--sock': Please use the '--socket' option instead.");
|
||||
cmd_arguments
|
||||
.value_of("sock")
|
||||
.expect("Failed to retrieve vhost-user socket path")
|
||||
}
|
||||
};
|
||||
|
||||
let thread_pool_size: usize = match cmd_arguments.value_of("thread-pool-size") {
|
||||
Some(size) => size.parse().expect("Invalid argument for thread-pool-size"),
|
||||
None => THREAD_POOL_SIZE,
|
||||
};
|
||||
let xattr: bool = !cmd_arguments.is_present("disable-xattr");
|
||||
let create_sandbox: bool = !cmd_arguments.is_present("disable-sandbox");
|
||||
let seccomp_mode: SeccompAction = match cmd_arguments.value_of("seccomp").unwrap() {
|
||||
"none" => SeccompAction::Allow, // i.e. no seccomp
|
||||
"kill" => SeccompAction::Kill,
|
||||
"log" => SeccompAction::Log,
|
||||
"trap" => SeccompAction::Trap,
|
||||
_ => unreachable!(), // We told Arg possible_values
|
||||
};
|
||||
|
||||
let listener = Listener::new(socket, true).unwrap();
|
||||
|
||||
let fs_cfg = if create_sandbox {
|
||||
let mut sandbox = Sandbox::new(shared_dir.to_string());
|
||||
match sandbox.enter().unwrap() {
|
||||
Some(child_pid) => {
|
||||
unsafe { libc::waitpid(child_pid, std::ptr::null_mut(), 0) };
|
||||
return;
|
||||
}
|
||||
None => passthrough::Config {
|
||||
root_dir: "/".to_string(),
|
||||
xattr,
|
||||
proc_sfd_rawfd: sandbox.get_proc_self_fd(),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
} else {
|
||||
passthrough::Config {
|
||||
root_dir: shared_dir.to_string(),
|
||||
xattr,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
// Must happen before we start the thread pool
|
||||
if seccomp_mode != SeccompAction::Allow {
|
||||
enable_seccomp(seccomp_mode).unwrap();
|
||||
};
|
||||
|
||||
let fs = PassthroughFs::new(fs_cfg).unwrap();
|
||||
let fs_backend = Arc::new(RwLock::new(VhostUserFsBackend::new(fs).unwrap()));
|
||||
let fs_backend = Arc::new(RwLock::new(
|
||||
VhostUserFsBackend::new(fs, thread_pool_size).unwrap(),
|
||||
));
|
||||
|
||||
let mut daemon = VhostUserDaemon::new(
|
||||
String::from("vhost-user-fs-backend"),
|
||||
sock,
|
||||
fs_backend.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut daemon =
|
||||
VhostUserDaemon::new(String::from("vhost-user-fs-backend"), 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);
|
||||
if let Err(e) = daemon.start(listener) {
|
||||
error!("Failed to start daemon: {:?}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(e) = daemon.wait() {
|
||||
println!("Waiting for daemon failed: {:?}", e);
|
||||
process::exit(1);
|
||||
error!("Waiting for daemon failed: {:?}", e);
|
||||
}
|
||||
|
||||
let kill_evt = fs_backend
|
||||
.read()
|
||||
.unwrap()
|
||||
.thread
|
||||
.lock()
|
||||
.unwrap()
|
||||
.kill_evt
|
||||
.try_clone()
|
||||
.unwrap();
|
||||
if let Err(e) = kill_evt.write(1) {
|
||||
error!("Error shutting down worker thread: {:?}", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,7 @@ fn main() {
|
||||
.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>\"",
|
||||
)
|
||||
.help(vhost_user_net::SYNTAX)
|
||||
.takes_value(true)
|
||||
.min_values(1),
|
||||
)
|
||||
|
||||
4345
src/main.rs
Executable file → Normal file
4345
src/main.rs
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,17 @@ write_files:
|
||||
Address=192.168.2.4/24
|
||||
Gateway=192.168.2.1
|
||||
|
||||
-
|
||||
path: /etc/systemd/network/00-static-l2-3.network
|
||||
permissions: 0644
|
||||
content: |
|
||||
[Match]
|
||||
MACAddress=de:ad:be:ef:56:78
|
||||
|
||||
[Network]
|
||||
Address=192.168.2.5/24
|
||||
Gateway=192.168.2.1
|
||||
|
||||
-
|
||||
path: /etc/systemd/system/vfio.service
|
||||
permissions: 0644
|
||||
@@ -63,5 +74,7 @@ write_files:
|
||||
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"
|
||||
|
||||
/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 --device path=/sys/bus/pci/devices/0000:00:05.0/ path=/sys/bus/pci/devices/0000:00:06.0/
|
||||
# 1G ram requires 512 pages
|
||||
echo 512 | sudo tee /proc/sys/vm/nr_hugepages
|
||||
sudo chmod a+rwX /dev/hugepages
|
||||
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=PARTUUID=6fb4d1a8-6c8c-4dd7-9f7c-1fe0b9f2574c VFIOTAG" --disk path=/mnt/clear-31311-cloudguest.img path=/mnt/cloudinit.img --cpus boot=1 --memory size=512M,hotplug_size=1G,file=/dev/hugepages --device path=/sys/bus/pci/devices/0000:00:05.0/ path=/sys/bus/pci/devices/0000:00:06.0/ --api-socket /tmp/ch_api.sock
|
||||
|
||||
0
test_data/cloud-init/ubuntu/meta-data
Executable file → Normal file
0
test_data/cloud-init/ubuntu/meta-data
Executable file → Normal file
0
test_data/cloud-init/ubuntu/user-data
Executable file → Normal file
0
test_data/cloud-init/ubuntu/user-data
Executable file → Normal file
4546
tests/integration.rs
Normal file
4546
tests/integration.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
[package]
|
||||
name = "vfio"
|
||||
version = "0.0.1"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
|
||||
[dependencies]
|
||||
arc-swap = ">=0.4.4"
|
||||
byteorder = "1.3.4"
|
||||
devices = { path = "../devices" }
|
||||
kvm-bindings = "0.2.0"
|
||||
kvm-ioctls = "0.5.0"
|
||||
libc = "0.2.60"
|
||||
log = "0.4.8"
|
||||
pci = { path = "../pci" }
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
features = ["backend-mmap"]
|
||||
|
||||
[dependencies.vfio-bindings]
|
||||
git = "https://github.com/rust-vmm/vfio-bindings"
|
||||
features = ["fam-wrappers"]
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
|
||||
//#![deny(missing_docs)]
|
||||
//! Virtual Function I/O (VFIO) API
|
||||
extern crate arc_swap;
|
||||
extern crate byteorder;
|
||||
extern crate devices;
|
||||
extern crate kvm_bindings;
|
||||
extern crate kvm_ioctls;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate pci;
|
||||
extern crate vfio_bindings;
|
||||
extern crate vm_allocator;
|
||||
extern crate vm_device;
|
||||
extern crate vm_memory;
|
||||
#[macro_use]
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
mod vfio_device;
|
||||
mod vfio_ioctls;
|
||||
mod vfio_pci;
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
pub use vfio_device::{VfioContainer, VfioDevice, VfioDmaMapping, VfioError};
|
||||
pub use vfio_pci::{VfioPciDevice, VfioPciError};
|
||||
|
||||
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.
|
||||
fn vec_with_size_in_bytes<T: Default>(size_in_bytes: usize) -> Vec<T> {
|
||||
let rounded_size = (size_in_bytes + size_of::<T>() - 1) / size_of::<T>();
|
||||
let mut v = Vec::with_capacity(rounded_size);
|
||||
for _ in 0..rounded_size {
|
||||
v.push(T::default())
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
// The kvm API has many structs that resemble the following `Foo` structure:
|
||||
//
|
||||
// ```
|
||||
// #[repr(C)]
|
||||
// struct Foo {
|
||||
// some_data: u32
|
||||
// entries: __IncompleteArrayField<__u32>,
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// In order to allocate such a structure, `size_of::<Foo>()` would be too small because it would not
|
||||
// include any space for `entries`. To make the allocation large enough while still being aligned
|
||||
// for `Foo`, a `Vec<Foo>` is created. Only the first element of `Vec<Foo>` would actually be used
|
||||
// as a `Foo`. The remaining memory in the `Vec<Foo>` is for `entries`, which must be contiguous
|
||||
// with `Foo`. This function is used to make the `Vec<Foo>` with enough space for `count` entries.
|
||||
pub fn vec_with_array_field<T: Default, F>(count: usize) -> Vec<T> {
|
||||
let element_space = count * size_of::<F>();
|
||||
let vec_size_bytes = size_of::<T>() + element_space;
|
||||
vec_with_size_in_bytes(vec_size_bytes)
|
||||
}
|
||||
@@ -1,893 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
use crate::vec_with_array_field;
|
||||
use arc_swap::ArcSwap;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use kvm_ioctls::*;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CString;
|
||||
use std::fmt;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::num;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::unix::prelude::FileExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use std::u32;
|
||||
use vfio_bindings::bindings::vfio::*;
|
||||
use vfio_bindings::bindings::IrqSet;
|
||||
use vfio_ioctls::*;
|
||||
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::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VfioError {
|
||||
OpenContainer(io::Error),
|
||||
OpenGroup(io::Error),
|
||||
GetGroupStatus,
|
||||
GroupViable,
|
||||
VfioApiVersion,
|
||||
VfioExtension,
|
||||
VfioInvalidType,
|
||||
VfioType1V2,
|
||||
GroupSetContainer,
|
||||
UnsetContainer,
|
||||
ContainerSetIOMMU,
|
||||
GroupGetDeviceFD,
|
||||
KvmSetDeviceAttr(kvm_ioctls::Error),
|
||||
VfioDeviceGetInfo,
|
||||
VfioDeviceGetRegionInfo,
|
||||
InvalidPath,
|
||||
IommuDmaMap,
|
||||
IommuDmaUnmap,
|
||||
VfioDeviceGetIrqInfo,
|
||||
VfioDeviceSetIrq,
|
||||
ReadLink(io::Error),
|
||||
ParseInt(num::ParseIntError),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, VfioError>;
|
||||
|
||||
impl fmt::Display for VfioError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
VfioError::OpenContainer(e) => {
|
||||
write!(f, "failed to open /dev/vfio/vfio container: {}", e)
|
||||
}
|
||||
VfioError::OpenGroup(e) => {
|
||||
write!(f, "failed to open /dev/vfio/$group_num group: {}", e)
|
||||
}
|
||||
VfioError::GetGroupStatus => write!(f, "failed to get Group Status"),
|
||||
VfioError::GroupViable => write!(f, "group is inviable"),
|
||||
VfioError::VfioApiVersion => write!(
|
||||
f,
|
||||
"vfio API version doesn't match with VFIO_API_VERSION defined in vfio-bindings"
|
||||
),
|
||||
VfioError::VfioExtension => write!(f, "failed to check VFIO extension"),
|
||||
VfioError::VfioInvalidType => write!(f, "invalid VFIO type"),
|
||||
VfioError::VfioType1V2 => {
|
||||
write!(f, "container dones't support VfioType1V2 IOMMU driver type")
|
||||
}
|
||||
VfioError::GroupSetContainer => {
|
||||
write!(f, "failed to add vfio group into vfio container")
|
||||
}
|
||||
VfioError::UnsetContainer => write!(f, "failed to unset vfio container"),
|
||||
VfioError::ContainerSetIOMMU => write!(
|
||||
f,
|
||||
"failed to set container's IOMMU driver type as VfioType1V2"
|
||||
),
|
||||
VfioError::GroupGetDeviceFD => write!(f, "failed to get vfio device fd"),
|
||||
VfioError::KvmSetDeviceAttr(e) => {
|
||||
write!(f, "failed to set KVM vfio device's attribute: {}", e)
|
||||
}
|
||||
VfioError::VfioDeviceGetInfo => {
|
||||
write!(f, "failed to get vfio device's info or info doesn't match")
|
||||
}
|
||||
VfioError::VfioDeviceGetRegionInfo => {
|
||||
write!(f, "failed to get vfio device's region info")
|
||||
}
|
||||
VfioError::InvalidPath => write!(f, "invalid file path"),
|
||||
VfioError::IommuDmaMap => write!(f, "failed to add guest memory map into iommu table"),
|
||||
VfioError::IommuDmaUnmap => {
|
||||
write!(f, "failed to remove guest memory map from iommu table")
|
||||
}
|
||||
VfioError::VfioDeviceGetIrqInfo => write!(f, "failed to get vfio device irq info"),
|
||||
VfioError::VfioDeviceSetIrq => write!(f, "failed to set vfio deviece irq"),
|
||||
VfioError::ReadLink(e) => write!(f, "failed to read link from path: {}", e),
|
||||
VfioError::ParseInt(e) => write!(f, "failed to parse integer: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
struct vfio_region_info_with_cap {
|
||||
region_info: vfio_region_info,
|
||||
cap_info: __IncompleteArrayField<u8>,
|
||||
}
|
||||
|
||||
pub struct VfioContainer {
|
||||
container: File,
|
||||
}
|
||||
|
||||
impl VfioContainer {
|
||||
fn new() -> Result<Self> {
|
||||
let container = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/dev/vfio/vfio")
|
||||
.map_err(VfioError::OpenContainer)?;
|
||||
|
||||
Ok(VfioContainer { container })
|
||||
}
|
||||
|
||||
fn get_api_version(&self) -> i32 {
|
||||
// Safe as file is vfio container fd and ioctl is defined by kernel.
|
||||
unsafe { ioctl(self, VFIO_GET_API_VERSION()) }
|
||||
}
|
||||
|
||||
fn check_extension(&self, val: u32) -> Result<()> {
|
||||
if val != VFIO_TYPE1_IOMMU && val != VFIO_TYPE1v2_IOMMU {
|
||||
return Err(VfioError::VfioInvalidType);
|
||||
}
|
||||
|
||||
// Safe as file is vfio container and make sure val is valid.
|
||||
let ret = unsafe { ioctl_with_val(self, VFIO_CHECK_EXTENSION(), val.into()) };
|
||||
if ret != 1 {
|
||||
return Err(VfioError::VfioExtension);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_iommu(&self, val: u32) -> Result<()> {
|
||||
if val != VFIO_TYPE1_IOMMU && val != VFIO_TYPE1v2_IOMMU {
|
||||
return Err(VfioError::VfioInvalidType);
|
||||
}
|
||||
|
||||
// Safe as file is vfio container and make sure val is valid.
|
||||
let ret = unsafe { ioctl_with_val(self, VFIO_SET_IOMMU(), val.into()) };
|
||||
if ret < 0 {
|
||||
return Err(VfioError::ContainerSetIOMMU);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
||||
let dma_map = vfio_iommu_type1_dma_map {
|
||||
argsz: mem::size_of::<vfio_iommu_type1_dma_map>() as u32,
|
||||
flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
|
||||
vaddr: user_addr,
|
||||
iova,
|
||||
size,
|
||||
};
|
||||
|
||||
// Safe as file is vfio container, dma_map is constructed by us, and
|
||||
// we check the return value
|
||||
let ret = unsafe { ioctl_with_ref(self, VFIO_IOMMU_MAP_DMA(), &dma_map) };
|
||||
if ret != 0 {
|
||||
return Err(VfioError::IommuDmaMap);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
|
||||
let mut dma_unmap = vfio_iommu_type1_dma_unmap {
|
||||
argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
|
||||
flags: 0,
|
||||
iova,
|
||||
size,
|
||||
};
|
||||
|
||||
// Safe as file is vfio container, dma_unmap is constructed by us, and
|
||||
// we check the return value
|
||||
let ret = unsafe { ioctl_with_mut_ref(self, VFIO_IOMMU_UNMAP_DMA(), &mut dma_unmap) };
|
||||
if ret != 0 || dma_unmap.size != size {
|
||||
return Err(VfioError::IommuDmaUnmap);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for VfioContainer {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.container.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
struct VfioGroup {
|
||||
group: File,
|
||||
device: Arc<DeviceFd>,
|
||||
container: Arc<VfioContainer>,
|
||||
}
|
||||
|
||||
impl VfioGroup {
|
||||
fn new(id: u32, device: Arc<DeviceFd>) -> Result<Self> {
|
||||
let group_path = Path::new("/dev/vfio").join(id.to_string());
|
||||
let group = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&group_path)
|
||||
.map_err(VfioError::OpenGroup)?;
|
||||
|
||||
let mut group_status = vfio_group_status {
|
||||
argsz: mem::size_of::<vfio_group_status>() as u32,
|
||||
flags: 0,
|
||||
};
|
||||
// Safe as we are the owner of group and group_status which are valid value.
|
||||
let mut ret =
|
||||
unsafe { ioctl_with_mut_ref(&group, VFIO_GROUP_GET_STATUS(), &mut group_status) };
|
||||
if ret < 0 {
|
||||
return Err(VfioError::GetGroupStatus);
|
||||
}
|
||||
|
||||
if group_status.flags != VFIO_GROUP_FLAGS_VIABLE {
|
||||
return Err(VfioError::GroupViable);
|
||||
}
|
||||
|
||||
let container = Arc::new(VfioContainer::new()?);
|
||||
if container.get_api_version() as u32 != VFIO_API_VERSION {
|
||||
return Err(VfioError::VfioApiVersion);
|
||||
}
|
||||
|
||||
container.check_extension(VFIO_TYPE1v2_IOMMU)?;
|
||||
|
||||
// Safe as we are the owner of group and container_raw_fd which are valid value,
|
||||
// and we verify the ret value
|
||||
let container_raw_fd = container.as_raw_fd();
|
||||
ret = unsafe { ioctl_with_ref(&group, VFIO_GROUP_SET_CONTAINER(), &container_raw_fd) };
|
||||
if ret < 0 {
|
||||
return Err(VfioError::GroupSetContainer);
|
||||
}
|
||||
|
||||
container.set_iommu(VFIO_TYPE1v2_IOMMU)?;
|
||||
|
||||
Self::kvm_device_add_group(&device, &group)?;
|
||||
|
||||
Ok(VfioGroup {
|
||||
group,
|
||||
device,
|
||||
container,
|
||||
})
|
||||
}
|
||||
|
||||
fn kvm_device_add_group(device_fd: &Arc<DeviceFd>, group: &File) -> Result<()> {
|
||||
let group_fd = group.as_raw_fd();
|
||||
let group_fd_ptr = &group_fd as *const i32;
|
||||
let dev_attr = kvm_bindings::kvm_device_attr {
|
||||
flags: 0,
|
||||
group: kvm_bindings::KVM_DEV_VFIO_GROUP,
|
||||
attr: u64::from(kvm_bindings::KVM_DEV_VFIO_GROUP_ADD),
|
||||
addr: group_fd_ptr as u64,
|
||||
};
|
||||
|
||||
device_fd
|
||||
.set_device_attr(&dev_attr)
|
||||
.map_err(VfioError::KvmSetDeviceAttr)
|
||||
}
|
||||
|
||||
fn kvm_device_del_group(&self) -> std::result::Result<(), kvm_ioctls::Error> {
|
||||
let group_fd = self.as_raw_fd();
|
||||
let group_fd_ptr = &group_fd as *const i32;
|
||||
let dev_attr = kvm_bindings::kvm_device_attr {
|
||||
flags: 0,
|
||||
group: kvm_bindings::KVM_DEV_VFIO_GROUP,
|
||||
attr: u64::from(kvm_bindings::KVM_DEV_VFIO_GROUP_DEL),
|
||||
addr: group_fd_ptr as u64,
|
||||
};
|
||||
|
||||
self.device.set_device_attr(&dev_attr)
|
||||
}
|
||||
|
||||
fn unset_container(&self) -> std::result::Result<(), io::Error> {
|
||||
let container_raw_fd = self.container.as_raw_fd();
|
||||
|
||||
// Safe as we are the owner of self and container_raw_fd which are valid value.
|
||||
let ret = unsafe { ioctl_with_ref(self, VFIO_GROUP_UNSET_CONTAINER(), &container_raw_fd) };
|
||||
if ret < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_device(&self, name: &Path) -> Result<VfioDeviceInfo> {
|
||||
let uuid_osstr = name.file_name().ok_or(VfioError::InvalidPath)?;
|
||||
let uuid_str = uuid_osstr.to_str().ok_or(VfioError::InvalidPath)?;
|
||||
let path: CString = CString::new(uuid_str.as_bytes()).expect("CString::new() failed");
|
||||
let path_ptr = path.as_ptr();
|
||||
|
||||
// Safe as we are the owner of self and path_ptr which are valid value.
|
||||
let fd = unsafe { ioctl_with_ptr(self, VFIO_GROUP_GET_DEVICE_FD(), path_ptr) };
|
||||
if fd < 0 {
|
||||
return Err(VfioError::GroupGetDeviceFD);
|
||||
}
|
||||
|
||||
// Safe as fd is valid FD
|
||||
let device = unsafe { File::from_raw_fd(fd) };
|
||||
|
||||
let mut dev_info = vfio_device_info {
|
||||
argsz: mem::size_of::<vfio_device_info>() as u32,
|
||||
flags: 0,
|
||||
num_regions: 0,
|
||||
num_irqs: 0,
|
||||
};
|
||||
// Safe as we are the owner of dev and dev_info which are valid value,
|
||||
// and we verify the return value.
|
||||
let ret = unsafe { ioctl_with_mut_ref(&device, VFIO_DEVICE_GET_INFO(), &mut dev_info) };
|
||||
if ret < 0
|
||||
|| (dev_info.flags & VFIO_DEVICE_FLAGS_PCI) == 0
|
||||
|| dev_info.num_regions < VFIO_PCI_CONFIG_REGION_INDEX + 1
|
||||
|| dev_info.num_irqs < VFIO_PCI_MSIX_IRQ_INDEX + 1
|
||||
{
|
||||
return Err(VfioError::VfioDeviceGetInfo);
|
||||
}
|
||||
|
||||
Ok(VfioDeviceInfo {
|
||||
device,
|
||||
flags: dev_info.flags,
|
||||
num_regions: dev_info.num_regions,
|
||||
num_irqs: dev_info.num_irqs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for VfioGroup {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.group.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VfioGroup {
|
||||
fn drop(&mut self) {
|
||||
match self.kvm_device_del_group() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("Could not delete VFIO group: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
match self.unset_container() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("Could not unset container: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VfioRegion {
|
||||
flags: u32,
|
||||
size: u64,
|
||||
offset: u64,
|
||||
mmap: (u64, u64),
|
||||
}
|
||||
|
||||
struct VfioIrq {
|
||||
flags: u32,
|
||||
index: u32,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
struct VfioDeviceInfo {
|
||||
device: File,
|
||||
flags: u32,
|
||||
num_regions: u32,
|
||||
num_irqs: u32,
|
||||
}
|
||||
|
||||
impl VfioDeviceInfo {
|
||||
fn get_irqs(&self) -> Result<HashMap<u32, VfioIrq>> {
|
||||
let mut irqs: HashMap<u32, VfioIrq> = HashMap::new();
|
||||
|
||||
for index in 0..self.num_irqs {
|
||||
let mut irq_info = vfio_irq_info {
|
||||
argsz: mem::size_of::<vfio_irq_info>() as u32,
|
||||
flags: 0,
|
||||
index,
|
||||
count: 0,
|
||||
};
|
||||
|
||||
let ret = unsafe {
|
||||
ioctl_with_mut_ref(&self.device, VFIO_DEVICE_GET_IRQ_INFO(), &mut irq_info)
|
||||
};
|
||||
if ret < 0 {
|
||||
warn!("Could not get VFIO IRQ info for index {:}", index);
|
||||
continue;
|
||||
}
|
||||
|
||||
let irq = VfioIrq {
|
||||
flags: irq_info.flags,
|
||||
index,
|
||||
count: irq_info.count,
|
||||
};
|
||||
|
||||
debug!("IRQ #{}", index);
|
||||
debug!("\tflag 0x{:x}", irq.flags);
|
||||
debug!("\tindex {}", irq.index);
|
||||
debug!("\tcount {}", irq.count);
|
||||
|
||||
irqs.insert(index, irq);
|
||||
}
|
||||
|
||||
Ok(irqs)
|
||||
}
|
||||
|
||||
fn get_regions(&self) -> Result<Vec<VfioRegion>> {
|
||||
let mut regions: Vec<VfioRegion> = Vec::new();
|
||||
|
||||
for i in VFIO_PCI_BAR0_REGION_INDEX..self.num_regions {
|
||||
let argsz: u32 = mem::size_of::<vfio_region_info>() as u32;
|
||||
|
||||
let mut reg_info = vfio_region_info {
|
||||
argsz,
|
||||
flags: 0,
|
||||
index: i,
|
||||
cap_offset: 0,
|
||||
size: 0,
|
||||
offset: 0,
|
||||
};
|
||||
// Safe as we are the owner of dev and reg_info which are valid value,
|
||||
// and we verify the return value.
|
||||
let mut ret = unsafe {
|
||||
ioctl_with_mut_ref(&self.device, VFIO_DEVICE_GET_REGION_INFO(), &mut reg_info)
|
||||
};
|
||||
if ret < 0 {
|
||||
warn!("Could not get region #{} info", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut mmap_size: u64 = reg_info.size;
|
||||
let mut mmap_offset: u64 = 0;
|
||||
if reg_info.flags & VFIO_REGION_INFO_FLAG_CAPS != 0 && reg_info.argsz > argsz {
|
||||
let cap_len: usize = (reg_info.argsz - argsz) as usize;
|
||||
let mut region_with_cap =
|
||||
vec_with_array_field::<vfio_region_info_with_cap, u8>(cap_len);
|
||||
region_with_cap[0].region_info.argsz = reg_info.argsz;
|
||||
region_with_cap[0].region_info.flags = 0;
|
||||
region_with_cap[0].region_info.index = i;
|
||||
region_with_cap[0].region_info.cap_offset = 0;
|
||||
region_with_cap[0].region_info.size = 0;
|
||||
region_with_cap[0].region_info.offset = 0;
|
||||
// Safe as we are the owner of dev and region_info which are valid value,
|
||||
// and we verify the return value.
|
||||
ret = unsafe {
|
||||
ioctl_with_mut_ref(
|
||||
&self.device,
|
||||
VFIO_DEVICE_GET_REGION_INFO(),
|
||||
&mut (region_with_cap[0].region_info),
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
warn!("Could not get region #{} info", i);
|
||||
continue;
|
||||
}
|
||||
// region_with_cap[0].cap_info may contain vfio_region_info_cap_sparse_mmap
|
||||
// struct or vfio_region_info_cap_type struct. Both of them begin with
|
||||
// vfio_info_cap_header.
|
||||
// so safe to convert cap_info into vfio_info_cap_header pointer first, and
|
||||
// safe to access its elments through this poiner.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let cap_header =
|
||||
unsafe { region_with_cap[0].cap_info.as_ptr() as *const vfio_info_cap_header };
|
||||
if unsafe { u32::from((*cap_header).id) } == VFIO_REGION_INFO_CAP_SPARSE_MMAP {
|
||||
// cap_info is vfio_region_sparse_mmap here
|
||||
// so safe to convert cap_info into vfio_info_region_sparse_mmap pointer, and
|
||||
// safe to access its elements through this pointer.
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
let sparse_mmap = unsafe {
|
||||
region_with_cap[0].cap_info.as_ptr()
|
||||
as *const vfio_region_info_cap_sparse_mmap
|
||||
};
|
||||
let mmap_area = unsafe {
|
||||
(*sparse_mmap).areas.as_ptr() as *const vfio_region_sparse_mmap_area
|
||||
};
|
||||
mmap_size = unsafe { (*mmap_area).size };
|
||||
mmap_offset = unsafe { (*mmap_area).offset };
|
||||
}
|
||||
}
|
||||
|
||||
let region = VfioRegion {
|
||||
flags: reg_info.flags,
|
||||
size: reg_info.size,
|
||||
offset: reg_info.offset,
|
||||
mmap: (mmap_offset, mmap_size),
|
||||
};
|
||||
|
||||
debug!("Region #{}", i);
|
||||
debug!("\tflag 0x{:x}", region.flags);
|
||||
debug!("\tsize 0x{:x}", region.size);
|
||||
debug!("\toffset 0x{:x}", region.offset);
|
||||
|
||||
regions.push(region);
|
||||
}
|
||||
|
||||
Ok(regions)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub struct VfioDevice {
|
||||
device: File,
|
||||
flags: u32,
|
||||
group: VfioGroup,
|
||||
regions: Vec<VfioRegion>,
|
||||
irqs: HashMap<u32, VfioIrq>,
|
||||
mem: Arc<ArcSwap<GuestMemoryMmap>>,
|
||||
iommu_attached: bool,
|
||||
}
|
||||
|
||||
impl VfioDevice {
|
||||
/// Create a new vfio device, then guest read/write on this device could be
|
||||
/// transfered into kernel vfio.
|
||||
/// sysfspath specify the vfio device path in sys file system.
|
||||
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 group_path = uuid_path.read_link().map_err(VfioError::ReadLink)?;
|
||||
let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
|
||||
let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?;
|
||||
let group_id = group_str.parse::<u32>().map_err(VfioError::ParseInt)?;
|
||||
|
||||
let group = VfioGroup::new(group_id, device_fd)?;
|
||||
let device_info = group.get_device(sysfspath)?;
|
||||
let regions = device_info.get_regions()?;
|
||||
let irqs = device_info.get_irqs()?;
|
||||
|
||||
Ok(VfioDevice {
|
||||
device: device_info.device,
|
||||
flags: device_info.flags,
|
||||
group,
|
||||
regions,
|
||||
irqs,
|
||||
mem,
|
||||
iommu_attached,
|
||||
})
|
||||
}
|
||||
|
||||
/// VFIO device reset.
|
||||
/// Only if the device supports being reset.
|
||||
pub fn reset(&self) {
|
||||
if self.flags & VFIO_DEVICE_FLAGS_RESET != 0 {
|
||||
unsafe { ioctl(self, VFIO_DEVICE_RESET()) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables a VFIO device IRQs.
|
||||
/// This maps a vector of EventFds to all VFIO managed interrupts. In other words, this
|
||||
/// tells VFIO which EventFd to write into whenever one of the device interrupt vector
|
||||
/// is triggered.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `irq_index` - The type (INTX, MSI or MSI-X) of interrupts to enable.
|
||||
/// * `event_fds` - The EventFds vector that matches all the supported VFIO interrupts.
|
||||
pub fn enable_irq(&self, irq_index: u32, event_fds: Vec<&EventFd>) -> Result<()> {
|
||||
let irq = self
|
||||
.irqs
|
||||
.get(&irq_index)
|
||||
.ok_or(VfioError::VfioDeviceSetIrq)?;
|
||||
if irq.count == 0 {
|
||||
return Err(VfioError::VfioDeviceSetIrq);
|
||||
}
|
||||
|
||||
let mut irq_set_wrapper = IrqSet::new(event_fds.len() * mem::size_of::<u32>());
|
||||
let mut irq_set = irq_set_wrapper.as_mut_fam_struct();
|
||||
let fds = irq_set.as_mut_slice();
|
||||
|
||||
for (index, event_fd) in event_fds.iter().enumerate() {
|
||||
let fds_offset = index * mem::size_of::<u32>();
|
||||
let fd = &mut fds[fds_offset..fds_offset + mem::size_of::<u32>()];
|
||||
LittleEndian::write_u32(fd, event_fd.as_raw_fd() as u32);
|
||||
}
|
||||
|
||||
irq_set.argsz = mem::size_of::<vfio_irq_set>() as u32
|
||||
+ (event_fds.len() * mem::size_of::<u32>()) as u32;
|
||||
irq_set.flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
|
||||
irq_set.index = irq_index;
|
||||
irq_set.start = 0;
|
||||
irq_set.count = irq.count;
|
||||
|
||||
// Safe as we are the owner of self and irq_set which are valid value
|
||||
let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), irq_set) };
|
||||
if ret < 0 {
|
||||
return Err(VfioError::VfioDeviceSetIrq);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Disables a VFIO device IRQs
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `irq_index` - The type (INTX, MSI or MSI-X) of interrupts to disable.
|
||||
pub fn disable_irq(&self, irq_index: u32) -> Result<()> {
|
||||
let irq = self
|
||||
.irqs
|
||||
.get(&irq_index)
|
||||
.ok_or(VfioError::VfioDeviceSetIrq)?;
|
||||
if irq.count == 0 {
|
||||
return Err(VfioError::VfioDeviceSetIrq);
|
||||
}
|
||||
|
||||
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0);
|
||||
irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32;
|
||||
irq_set[0].flags = VFIO_IRQ_SET_ACTION_TRIGGER | VFIO_IRQ_SET_DATA_NONE;
|
||||
irq_set[0].index = irq_index;
|
||||
irq_set[0].start = 0;
|
||||
irq_set[0].count = 0;
|
||||
|
||||
// Safe as we are the owner of self and irq_set which are valid value
|
||||
let ret = unsafe { ioctl_with_ref(self, VFIO_DEVICE_SET_IRQS(), &irq_set[0]) };
|
||||
if ret < 0 {
|
||||
return Err(VfioError::VfioDeviceSetIrq);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Wrapper to enable MSI IRQs.
|
||||
pub fn enable_msi(&self, fds: Vec<&EventFd>) -> Result<()> {
|
||||
self.enable_irq(VFIO_PCI_MSI_IRQ_INDEX, fds)
|
||||
}
|
||||
|
||||
/// Wrapper to disable MSI IRQs.
|
||||
pub fn disable_msi(&self) -> Result<()> {
|
||||
self.disable_irq(VFIO_PCI_MSI_IRQ_INDEX)
|
||||
}
|
||||
|
||||
/// Wrapper to enable MSI-X IRQs.
|
||||
pub fn enable_msix(&self, fds: Vec<&EventFd>) -> Result<()> {
|
||||
self.enable_irq(VFIO_PCI_MSIX_IRQ_INDEX, fds)
|
||||
}
|
||||
|
||||
/// Wrapper to disable MSI-X IRQs.
|
||||
pub fn disable_msix(&self) -> Result<()> {
|
||||
self.disable_irq(VFIO_PCI_MSIX_IRQ_INDEX)
|
||||
}
|
||||
|
||||
/// get a region's flag
|
||||
pub fn get_region_flags(&self, index: u32) -> u32 {
|
||||
match self.regions.get(index as usize) {
|
||||
Some(v) => v.flags,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// get a region's offset
|
||||
pub fn get_region_offset(&self, index: u32) -> u64 {
|
||||
match self.regions.get(index as usize) {
|
||||
Some(v) => v.offset,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// get a region's mmap info
|
||||
pub fn get_region_mmap(&self, index: u32) -> (u64, u64) {
|
||||
match self.regions.get(index as usize) {
|
||||
Some(v) => v.mmap,
|
||||
None => {
|
||||
warn!("get_region_mmap with invalid index: {}", index);
|
||||
(0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// get a region's size
|
||||
pub fn get_region_size(&self, index: u32) -> u64 {
|
||||
match self.regions.get(index as usize) {
|
||||
Some(v) => v.size,
|
||||
None => {
|
||||
warn!("get_region_size with invalid index: {}", index);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read region's data from VFIO device into buf
|
||||
/// index: region num
|
||||
/// buf: data destination and buf length is read size
|
||||
/// addr: offset in the region
|
||||
pub fn region_read(&self, index: u32, buf: &mut [u8], addr: u64) {
|
||||
let region: &VfioRegion;
|
||||
match self.regions.get(index as usize) {
|
||||
Some(v) => region = v,
|
||||
None => {
|
||||
warn!("region read with invalid index: {}", index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let size = buf.len() as u64;
|
||||
if size > region.size || addr + size > region.size {
|
||||
warn!(
|
||||
"region read with invalid parameter, add: {}, size: {}",
|
||||
addr, size
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(e) = self.device.read_exact_at(buf, region.offset + addr) {
|
||||
warn!(
|
||||
"Failed to read region in index: {}, addr: {}, error: {}",
|
||||
index, addr, e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// write the data from buf into a vfio device region
|
||||
/// index: region num
|
||||
/// buf: data src and buf length is write size
|
||||
/// addr: offset in the region
|
||||
pub fn region_write(&self, index: u32, buf: &[u8], addr: u64) {
|
||||
let stub: &VfioRegion;
|
||||
match self.regions.get(index as usize) {
|
||||
Some(v) => stub = v,
|
||||
None => {
|
||||
warn!("region write with invalid index: {}", index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let size = buf.len() as u64;
|
||||
if size > stub.size
|
||||
|| addr + size > stub.size
|
||||
|| (stub.flags & VFIO_REGION_INFO_FLAG_WRITE) == 0
|
||||
{
|
||||
warn!(
|
||||
"region write with invalid parameter, add: {}, size: {}",
|
||||
addr, size
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(e) = self.device.write_all_at(buf, stub.offset + addr) {
|
||||
warn!(
|
||||
"Failed to write region in index: {}, addr: {}, error: {}",
|
||||
index, addr, e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_container(&self) -> Arc<VfioContainer> {
|
||||
self.group.container.clone()
|
||||
}
|
||||
|
||||
fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
|
||||
self.group.container.vfio_dma_map(iova, size, user_addr)
|
||||
}
|
||||
|
||||
fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
|
||||
self.group.container.vfio_dma_unmap(iova, size)
|
||||
}
|
||||
|
||||
/// Add all guest memory regions into vfio container's iommu table,
|
||||
/// then vfio kernel driver could access guest memory from gfn
|
||||
pub fn setup_dma_map(&self) -> Result<()> {
|
||||
if !self.iommu_attached {
|
||||
self.mem.load().with_regions(|_index, region| {
|
||||
self.vfio_dma_map(
|
||||
region.start_addr().raw_value(),
|
||||
region.len() as u64,
|
||||
region.as_ptr() as u64,
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// remove all guest memory regions from vfio containers iommu table
|
||||
/// then vfio kernel driver couldn't access this guest memory
|
||||
pub fn unset_dma_map(&self) -> Result<()> {
|
||||
if !self.iommu_attached {
|
||||
self.mem.load().with_regions(|_index, region| {
|
||||
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len() as u64)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the maximum numner of interrupts a VFIO device can request.
|
||||
/// This is used for pre-allocating the VFIO PCI routes.
|
||||
pub fn max_interrupts(&self) -> u32 {
|
||||
let mut max_interrupts = 0;
|
||||
let irq_indexes = vec![
|
||||
VFIO_PCI_INTX_IRQ_INDEX,
|
||||
VFIO_PCI_MSI_IRQ_INDEX,
|
||||
VFIO_PCI_MSIX_IRQ_INDEX,
|
||||
];
|
||||
|
||||
for index in irq_indexes {
|
||||
if let Some(irq_info) = self.irqs.get(&index) {
|
||||
if irq_info.count > max_interrupts {
|
||||
max_interrupts = irq_info.count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
max_interrupts
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for VfioDevice {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.device.as_raw_fd()
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use vfio_bindings::bindings::vfio::*;
|
||||
|
||||
ioctl_io_nr!(VFIO_GET_API_VERSION, VFIO_TYPE, VFIO_BASE);
|
||||
ioctl_io_nr!(VFIO_CHECK_EXTENSION, VFIO_TYPE, VFIO_BASE + 1);
|
||||
ioctl_io_nr!(VFIO_SET_IOMMU, VFIO_TYPE, VFIO_BASE + 2);
|
||||
ioctl_io_nr!(VFIO_GROUP_GET_STATUS, VFIO_TYPE, VFIO_BASE + 3);
|
||||
ioctl_io_nr!(VFIO_GROUP_SET_CONTAINER, VFIO_TYPE, VFIO_BASE + 4);
|
||||
ioctl_io_nr!(VFIO_GROUP_UNSET_CONTAINER, VFIO_TYPE, VFIO_BASE + 5);
|
||||
ioctl_io_nr!(VFIO_GROUP_GET_DEVICE_FD, VFIO_TYPE, VFIO_BASE + 6);
|
||||
ioctl_io_nr!(VFIO_DEVICE_GET_INFO, VFIO_TYPE, VFIO_BASE + 7);
|
||||
ioctl_io_nr!(VFIO_DEVICE_GET_REGION_INFO, VFIO_TYPE, VFIO_BASE + 8);
|
||||
ioctl_io_nr!(VFIO_DEVICE_GET_IRQ_INFO, VFIO_TYPE, VFIO_BASE + 9);
|
||||
ioctl_io_nr!(VFIO_DEVICE_SET_IRQS, VFIO_TYPE, VFIO_BASE + 10);
|
||||
ioctl_io_nr!(VFIO_DEVICE_RESET, VFIO_TYPE, VFIO_BASE + 11);
|
||||
ioctl_io_nr!(
|
||||
VFIO_DEVICE_GET_PCI_HOT_RESET_INFO,
|
||||
VFIO_TYPE,
|
||||
VFIO_BASE + 12
|
||||
);
|
||||
ioctl_io_nr!(VFIO_DEVICE_PCI_HOT_RESET, VFIO_TYPE, VFIO_BASE + 13);
|
||||
ioctl_io_nr!(VFIO_DEVICE_QUERY_GFX_PLANE, VFIO_TYPE, VFIO_BASE + 14);
|
||||
ioctl_io_nr!(VFIO_DEVICE_GET_GFX_DMABUF, VFIO_TYPE, VFIO_BASE + 15);
|
||||
ioctl_io_nr!(VFIO_DEVICE_IOEVENTFD, VFIO_TYPE, VFIO_BASE + 16);
|
||||
ioctl_io_nr!(VFIO_IOMMU_GET_INFO, VFIO_TYPE, VFIO_BASE + 12);
|
||||
ioctl_io_nr!(VFIO_IOMMU_MAP_DMA, VFIO_TYPE, VFIO_BASE + 13);
|
||||
ioctl_io_nr!(VFIO_IOMMU_UNMAP_DMA, VFIO_TYPE, VFIO_BASE + 14);
|
||||
ioctl_io_nr!(VFIO_IOMMU_ENABLE, VFIO_TYPE, VFIO_BASE + 15);
|
||||
ioctl_io_nr!(VFIO_IOMMU_DISABLE, VFIO_TYPE, VFIO_BASE + 16);
|
||||
@@ -1,25 +0,0 @@
|
||||
[package]
|
||||
name = "vhost_rs"
|
||||
version = "0.1.0"
|
||||
authors = ["Liu Jiang <gerry@linux.alibaba.com>"]
|
||||
repository = "https://github.com/rust-vmm/vhost"
|
||||
license = "Apache-2.0 or BSD-3-Clause"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
vhost-vsock = []
|
||||
vhost-kern = ["vm-memory"]
|
||||
vhost-user-master = []
|
||||
vhost-user-slave = []
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.1.0"
|
||||
libc = "0.2.60"
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dependencies.vm-memory]
|
||||
git = "https://github.com/rust-vmm/vm-memory"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
202
vhost_rs/LICENSE
202
vhost_rs/LICENSE
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,24 +0,0 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# vHost
|
||||
A crate to support vhost backend drivers for virtio devices.
|
||||
|
||||
## Kernel-based vHost Backend Drivers
|
||||
The vhost drivers in Linux provide in-kernel virtio device emulation. Normally the hypervisor userspace process emulates I/O accesses from the guest. Vhost puts virtio emulation code into the kernel, taking hypervisor userspace out of the picture. This allows device emulation code to directly call into kernel subsystems instead of performing system calls from userspace. The hypervisor relies on ioctl based interfaces to control those in-kernel vhost drivers, such as vhost-net, vhost-scsi and vhost-vsock etc.
|
||||
|
||||
## vHost-user Backend Drivers
|
||||
The vhost-user protocol is aiming to implement vhost backend drivers in userspace, which complements the ioctl interface used to control the vhost implementation in the Linux kernel. It implements the control plane needed to establish virtqueue sharing with a user space process on the same host. It uses communication over a Unix domain socket to share file descriptors in the ancillary data of the message.
|
||||
|
||||
The protocol defines two sides of the communication, master and slave. Master is the application that shares its virtqueues, slave is the consumer of the virtqueues. Master and slave can be either a client (i.e. connecting) or server (listening) in the socket communication.
|
||||
@@ -1,130 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD file.
|
||||
|
||||
//! Common traits and structs for vhost-kern and vhost-user backend drivers.
|
||||
|
||||
use super::Result;
|
||||
use std::os::unix::io::RawFd;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// Maximum number of memory regions supported.
|
||||
pub const VHOST_MAX_MEMORY_REGIONS: usize = 255;
|
||||
|
||||
/// Vring/virtque configuration data.
|
||||
pub struct VringConfigData {
|
||||
/// Maximum queue size supported by the driver.
|
||||
pub queue_max_size: u16,
|
||||
/// Actual queue size negotiated by the driver.
|
||||
pub queue_size: u16,
|
||||
/// Bitmask of vring flags.
|
||||
pub flags: u32,
|
||||
/// Descriptor table address.
|
||||
pub desc_table_addr: u64,
|
||||
/// Used ring buffer address.
|
||||
pub used_ring_addr: u64,
|
||||
/// Available ring buffer address.
|
||||
pub avail_ring_addr: u64,
|
||||
/// Optional address for logging.
|
||||
pub log_addr: Option<u64>,
|
||||
}
|
||||
|
||||
/// Memory region configuration data.
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct VhostUserMemoryRegionInfo {
|
||||
/// Guest physical address of the memory region.
|
||||
pub guest_phys_addr: u64,
|
||||
/// Size of the memory region.
|
||||
pub memory_size: u64,
|
||||
/// Virtual address in the current process.
|
||||
pub userspace_addr: u64,
|
||||
/// Optional offset where region starts in the mapped memory.
|
||||
pub mmap_offset: u64,
|
||||
/// Optional file diescriptor for mmap
|
||||
pub mmap_handle: RawFd,
|
||||
}
|
||||
|
||||
/// An interface for setting up vhost-based backend drivers.
|
||||
///
|
||||
/// Vhost-based virtio devices are different from regular virtio devices because the the vhost
|
||||
/// backend takes care of handling all the data transfer. The device itself only needs to deal with
|
||||
/// setting up the the backend driver and managing the control channel.
|
||||
pub trait VhostBackend: std::marker::Sized {
|
||||
/// Get a bitmask of supported virtio/vhost features.
|
||||
fn get_features(&mut self) -> Result<u64>;
|
||||
|
||||
/// Inform the vhost subsystem which features to enable.
|
||||
/// This should be a subset of supported features from get_features().
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `features` - Bitmask of features to set.
|
||||
fn set_features(&mut self, features: u64) -> Result<()>;
|
||||
|
||||
/// Set the current process as the owner of the vhost backend.
|
||||
/// This must be run before any other vhost commands.
|
||||
fn set_owner(&mut self) -> Result<()>;
|
||||
|
||||
/// Used to be sent to request disabling all rings
|
||||
/// This is no longer used.
|
||||
fn reset_owner(&mut self) -> Result<()>;
|
||||
|
||||
/// Set the guest memory mappings for vhost to use.
|
||||
fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>;
|
||||
|
||||
/// Set base address for page modification logging.
|
||||
fn set_log_base(&mut self, base: u64, fd: Option<RawFd>) -> Result<()>;
|
||||
|
||||
/// Specify an eventfd file descriptor to signal on log write.
|
||||
fn set_log_fd(&mut self, fd: RawFd) -> Result<()>;
|
||||
|
||||
/// Set the number of descriptors in the vring.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to set descriptor count for.
|
||||
/// * `num` - Number of descriptors in the queue.
|
||||
fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()>;
|
||||
|
||||
/// Set the addresses for a given vring.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to set addresses for.
|
||||
/// * `config_data` - Configuration data for a vring.
|
||||
fn set_vring_addr(&mut self, queue_index: usize, config_data: &VringConfigData) -> Result<()>;
|
||||
|
||||
/// Set the first index to look for available descriptors.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `num` - Index where available descriptors start.
|
||||
fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()>;
|
||||
|
||||
/// Get the available vring base offset.
|
||||
fn get_vring_base(&mut self, queue_index: usize) -> Result<u32>;
|
||||
|
||||
/// Set the eventfd to trigger when buffers have been used by the host.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd to trigger.
|
||||
fn set_vring_call(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;
|
||||
|
||||
/// Set the eventfd that will be signaled by the guest when buffers are
|
||||
/// available for the host to process.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd that will be signaled from guest.
|
||||
fn set_vring_kick(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;
|
||||
|
||||
/// Set the eventfd that will be signaled by the guest when error happens.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd that will be signaled from guest.
|
||||
fn set_vring_err(&mut self, queue_index: usize, fd: &EventFd) -> Result<()>;
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD file.
|
||||
|
||||
//! Virtio Vhost Backend Drivers
|
||||
//!
|
||||
//! Virtio devices use virtqueues to transport data efficiently. Virtqueue is a set of three
|
||||
//! different single-producer, single-consumer ring structures designed to store generic
|
||||
//! scatter-gather I/O.
|
||||
//!
|
||||
//! Vhost is a mechanism to improve performance of Virtio devices by delegate data plane operations
|
||||
//! to dedicated IO service processes. Only the configuration, I/O submission notification, and I/O
|
||||
//! completion interruption are piped through the hypervisor.
|
||||
//! It uses the same virtqueue layout as Virtio to allow Vhost devices to be mapped directly to
|
||||
//! Virtio devices. This allows a Vhost device to be accessed directly by a guest OS inside a
|
||||
//! hypervisor process with an existing Virtio (PCI) driver.
|
||||
//!
|
||||
//! The initial vhost implementation is a part of the Linux kernel and uses ioctl interface to
|
||||
//! communicate with userspace applications. Dedicated kernel worker threads are created to handle
|
||||
//! IO requests from the guest.
|
||||
//!
|
||||
//! Later Vhost-user protocol is introduced to complement the ioctl interface used to control the
|
||||
//! vhost implementation in the Linux kernel. It implements the control plane needed to establish
|
||||
//! virtqueues sharing with a user space process on the same host. It uses communication over a
|
||||
//! Unix domain socket to share file descriptors in the ancillary data of the message.
|
||||
//! The protocol defines 2 sides of the communication, master and slave. Master is the application
|
||||
//! that shares its virtqueues. Slave is the consumer of the virtqueues. Master and slave can be
|
||||
//! either a client (i.e. connecting) or server (listening) in the socket communication.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[cfg_attr(
|
||||
any(feature = "vhost-user-master", feature = "vhost-user-slave"),
|
||||
macro_use
|
||||
)]
|
||||
extern crate bitflags;
|
||||
extern crate libc;
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
extern crate vm_memory;
|
||||
#[cfg_attr(feature = "vhost-kern", macro_use)]
|
||||
extern crate vmm_sys_util;
|
||||
|
||||
mod backend;
|
||||
pub use backend::*;
|
||||
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
pub mod vhost_kern;
|
||||
#[cfg(any(feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
pub mod vhost_user;
|
||||
#[cfg(feature = "vhost-vsock")]
|
||||
pub mod vsock;
|
||||
|
||||
/// Error codes for vhost operations
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid operations.
|
||||
InvalidOperation,
|
||||
/// Invalid guest memory.
|
||||
InvalidGuestMemory,
|
||||
/// Invalid guest memory region.
|
||||
InvalidGuestMemoryRegion,
|
||||
/// Invalid queue.
|
||||
InvalidQueue,
|
||||
/// Invalid descriptor table address.
|
||||
DescriptorTableAddress,
|
||||
/// Invalid used address.
|
||||
UsedAddress,
|
||||
/// Invalid available address.
|
||||
AvailAddress,
|
||||
/// Invalid log address.
|
||||
LogAddress,
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
/// Error opening the vhost backend driver.
|
||||
VhostOpen(std::io::Error),
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
/// Error while running ioctl.
|
||||
IoctlError(std::io::Error),
|
||||
/// Error from IO subsystem.
|
||||
IOError(std::io::Error),
|
||||
#[cfg(any(feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
/// Error from the vhost-user subsystem.
|
||||
VhostUserProtocol(vhost_user::Error),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::InvalidOperation => write!(f, "invalid vhost operations"),
|
||||
Error::InvalidGuestMemory => write!(f, "invalid guest memory object"),
|
||||
Error::InvalidGuestMemoryRegion => write!(f, "invalid guest memory region"),
|
||||
Error::InvalidQueue => write!(f, "invalid virtque"),
|
||||
Error::DescriptorTableAddress => write!(f, "invalid virtque descriptor talbe address"),
|
||||
Error::UsedAddress => write!(f, "invalid virtque used talbe address"),
|
||||
Error::AvailAddress => write!(f, "invalid virtque available talbe address"),
|
||||
Error::LogAddress => write!(f, "invalid virtque log address"),
|
||||
Error::IOError(e) => write!(f, "IO error: {}", e),
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
Error::VhostOpen(e) => write!(f, "failure in opening vhost file: {}", e),
|
||||
#[cfg(feature = "vhost-kern")]
|
||||
Error::IoctlError(e) => write!(f, "failure in vhost ioctl: {}", e),
|
||||
#[cfg(any(feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
Error::VhostUserProtocol(e) => write!(f, "vhost-user: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
impl std::convert::From<vhost_user::Error> for Error {
|
||||
fn from(err: vhost_user::Error) -> Self {
|
||||
Error::VhostUserProtocol(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of vhost operations
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -1,320 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD file.
|
||||
|
||||
//! Traits and structs to control Linux in-kernel vhost drivers.
|
||||
//!
|
||||
//! The initial vhost implementation is a part of the Linux kernel and uses ioctl interface to
|
||||
//! communicate with userspace applications. This sub module provides ioctl based interfaces to
|
||||
//! control the in-kernel net, scsi, vsock vhost drivers.
|
||||
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::ptr::null;
|
||||
|
||||
use vm_memory::{Address, GuestAddress, GuestMemory, GuestUsize};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use vmm_sys_util::ioctl::{ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref};
|
||||
|
||||
use super::{
|
||||
Error, Result, VhostBackend, VhostUserMemoryRegionInfo, VringConfigData,
|
||||
VHOST_MAX_MEMORY_REGIONS,
|
||||
};
|
||||
|
||||
pub mod vhost_binding;
|
||||
use self::vhost_binding::*;
|
||||
|
||||
#[cfg(feature = "vhost-vsock")]
|
||||
pub mod vsock;
|
||||
|
||||
#[inline]
|
||||
fn ioctl_result<T>(rc: i32, res: T) -> Result<T> {
|
||||
if rc < 0 {
|
||||
Err(Error::IoctlError(std::io::Error::last_os_error()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
fn guest_addr(addr: u64) -> GuestAddress {
|
||||
GuestAddress::new(addr)
|
||||
}
|
||||
|
||||
/// Represent an in-kernel vhost device backend.
|
||||
pub trait VhostKernBackend<'a>: AsRawFd {
|
||||
/// Associated type to access the guest's memory.
|
||||
type M: GuestMemory<'a>;
|
||||
|
||||
/// Get the object to access the guest's memory.
|
||||
fn mem(&self) -> &Self::M;
|
||||
|
||||
/// Check whether the ring configuration is valid.
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
#[allow(clippy::needless_bool)]
|
||||
fn is_valid(
|
||||
&self,
|
||||
queue_max_size: u16,
|
||||
queue_size: u16,
|
||||
desc_addr: GuestAddress,
|
||||
avail_addr: GuestAddress,
|
||||
used_addr: GuestAddress,
|
||||
) -> bool {
|
||||
let desc_table_size = 16 * u64::from(queue_size) as GuestUsize;
|
||||
let avail_ring_size = 6 + 2 * u64::from(queue_size) as GuestUsize;
|
||||
let used_ring_size = 6 + 8 * u64::from(queue_size) as GuestUsize;
|
||||
if queue_size > queue_max_size || queue_size == 0 || (queue_size & (queue_size - 1)) != 0 {
|
||||
false
|
||||
} else if desc_addr
|
||||
.checked_add(desc_table_size)
|
||||
.map_or(true, |v| !self.mem().address_in_range(v))
|
||||
{
|
||||
false
|
||||
} else if avail_addr
|
||||
.checked_add(avail_ring_size)
|
||||
.map_or(true, |v| !self.mem().address_in_range(v))
|
||||
{
|
||||
false
|
||||
} else if used_addr
|
||||
.checked_add(used_ring_size)
|
||||
.map_or(true, |v| !self.mem().address_in_range(v))
|
||||
{
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: VhostKernBackend<'a>> VhostBackend for T {
|
||||
/// Set the current process as the owner of this file descriptor.
|
||||
/// This must be run before any other vhost ioctls.
|
||||
fn set_owner(&mut self) -> Result<()> {
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl(self, VHOST_SET_OWNER()) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
fn reset_owner(&mut self) -> Result<()> {
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl(self, VHOST_RESET_OWNER()) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Get a bitmask of supported virtio/vhost features.
|
||||
fn get_features(&mut self) -> Result<u64> {
|
||||
let mut avail_features: u64 = 0;
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_mut_ref(self, VHOST_GET_FEATURES(), &mut avail_features) };
|
||||
ioctl_result(ret, avail_features)
|
||||
}
|
||||
|
||||
/// Inform the vhost subsystem which features to enable. This should be a subset of
|
||||
/// supported features from VHOST_GET_FEATURES.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `features` - Bitmask of features to set.
|
||||
fn set_features(&mut self, features: u64) -> Result<()> {
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_FEATURES(), &features) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the guest memory mappings for vhost to use.
|
||||
fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
|
||||
if regions.is_empty() || regions.len() > VHOST_MAX_MEMORY_REGIONS {
|
||||
return Err(Error::InvalidGuestMemory);
|
||||
}
|
||||
|
||||
let mut vhost_memory = VhostMemory::new(regions.len() as u16);
|
||||
for (index, region) in regions.iter().enumerate() {
|
||||
vhost_memory.set_region(
|
||||
index as u32,
|
||||
&vhost_memory_region {
|
||||
guest_phys_addr: region.guest_phys_addr,
|
||||
memory_size: region.memory_size,
|
||||
userspace_addr: region.userspace_addr,
|
||||
flags_padding: 0u64,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
// This ioctl is called with a pointer that is valid for the lifetime
|
||||
// of this function. The kernel will make its own copy of the memory
|
||||
// tables. As always, check the return value.
|
||||
let ret = unsafe { ioctl_with_ptr(self, VHOST_SET_MEM_TABLE(), vhost_memory.as_ptr()) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set base address for page modification logging.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base` - Base address for page modification logging.
|
||||
fn set_log_base(&mut self, base: u64, fd: Option<RawFd>) -> Result<()> {
|
||||
if fd.is_some() {
|
||||
return Err(Error::LogAddress);
|
||||
}
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_LOG_BASE(), &base) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Specify an eventfd file descriptor to signal on log write.
|
||||
fn set_log_fd(&mut self, fd: RawFd) -> Result<()> {
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let val: i32 = fd;
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_LOG_FD(), &val) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the number of descriptors in the vring.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to set descriptor count for.
|
||||
/// * `num` - Number of descriptors in the queue.
|
||||
fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()> {
|
||||
let vring_state = vhost_vring_state {
|
||||
index: queue_index as u32,
|
||||
num: u32::from(num),
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_NUM(), &vring_state) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the addresses for a given vring.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_max_size` - Maximum queue size supported by the device.
|
||||
/// * `queue_size` - Actual queue size negotiated by the driver.
|
||||
/// * `queue_index` - Index of the queue to set addresses for.
|
||||
/// * `flags` - Bitmask of vring flags.
|
||||
/// * `desc_table_addr` - Descriptor table address.
|
||||
/// * `used_ring_addr` - Used ring buffer address.
|
||||
/// * `avail_ring_addr` - Available ring buffer address.
|
||||
/// * `log_addr` - Optional address for logging.
|
||||
fn set_vring_addr(&mut self, queue_index: usize, config_data: &VringConfigData) -> Result<()> {
|
||||
if !self.is_valid(
|
||||
config_data.queue_max_size,
|
||||
config_data.queue_size,
|
||||
guest_addr(config_data.desc_table_addr),
|
||||
guest_addr(config_data.used_ring_addr),
|
||||
guest_addr(config_data.avail_ring_addr),
|
||||
) {
|
||||
return Err(Error::InvalidQueue);
|
||||
}
|
||||
|
||||
let desc_addr = self
|
||||
.mem()
|
||||
.get_host_address(guest_addr(config_data.desc_table_addr))
|
||||
.ok_or(Error::DescriptorTableAddress)?;
|
||||
let used_addr = self
|
||||
.mem()
|
||||
.get_host_address(guest_addr(config_data.used_ring_addr))
|
||||
.ok_or(Error::UsedAddress)?;
|
||||
let avail_addr = self
|
||||
.mem()
|
||||
.get_host_address(guest_addr(config_data.avail_ring_addr))
|
||||
.ok_or(Error::AvailAddress)?;
|
||||
let log_addr = match config_data.log_addr {
|
||||
None => null(),
|
||||
Some(a) => self
|
||||
.mem()
|
||||
.get_host_address(guest_addr(a))
|
||||
.ok_or(Error::LogAddress)?,
|
||||
};
|
||||
|
||||
let vring_addr = vhost_vring_addr {
|
||||
index: queue_index as u32,
|
||||
flags: config_data.flags,
|
||||
desc_user_addr: desc_addr as u64,
|
||||
used_user_addr: used_addr as u64,
|
||||
avail_user_addr: avail_addr as u64,
|
||||
log_guest_addr: log_addr as u64,
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its
|
||||
// return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_ADDR(), &vring_addr) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the first index to look for available descriptors.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `num` - Index where available descriptors start.
|
||||
fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()> {
|
||||
let vring_state = vhost_vring_state {
|
||||
index: queue_index as u32,
|
||||
num: u32::from(base),
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_BASE(), &vring_state) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Get a bitmask of supported virtio/vhost features.
|
||||
fn get_vring_base(&mut self, queue_index: usize) -> Result<u32> {
|
||||
let vring_state = vhost_vring_state {
|
||||
index: queue_index as u32,
|
||||
num: 0,
|
||||
};
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_GET_VRING_BASE(), &vring_state) };
|
||||
ioctl_result(ret, vring_state.num)
|
||||
}
|
||||
|
||||
/// Set the eventfd to trigger when buffers have been used by the host.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd to trigger.
|
||||
fn set_vring_call(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let vring_file = vhost_vring_file {
|
||||
index: queue_index as u32,
|
||||
fd: fd.as_raw_fd(),
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_CALL(), &vring_file) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the eventfd that will be signaled by the guest when buffers are
|
||||
/// available for the host to process.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd that will be signaled from guest.
|
||||
fn set_vring_kick(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let vring_file = vhost_vring_file {
|
||||
index: queue_index as u32,
|
||||
fd: fd.as_raw_fd(),
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_KICK(), &vring_file) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Set the eventfd to signal an error from the vhost backend.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `queue_index` - Index of the queue to modify.
|
||||
/// * `fd` - EventFd that will be signaled from the backend.
|
||||
fn set_vring_err(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let vring_file = vhost_vring_file {
|
||||
index: queue_index as u32,
|
||||
fd: fd.as_raw_fd(),
|
||||
};
|
||||
|
||||
// This ioctl is called on a valid vhost fd and has its return value checked.
|
||||
let ret = unsafe { ioctl_with_ref(self, VHOST_SET_VRING_ERR(), &vring_file) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD file.
|
||||
|
||||
/* Auto-generated by bindgen then manually edited for simplicity */
|
||||
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::os::raw;
|
||||
use {Error, Result};
|
||||
|
||||
pub const VHOST: raw::c_uint = 0xaf;
|
||||
pub const VHOST_VRING_F_LOG: raw::c_uint = 0;
|
||||
pub const VHOST_ACCESS_RO: raw::c_uint = 1;
|
||||
pub const VHOST_ACCESS_WO: raw::c_uint = 2;
|
||||
pub const VHOST_ACCESS_RW: raw::c_uint = 3;
|
||||
pub const VHOST_IOTLB_MISS: raw::c_uint = 1;
|
||||
pub const VHOST_IOTLB_UPDATE: raw::c_uint = 2;
|
||||
pub const VHOST_IOTLB_INVALIDATE: raw::c_uint = 3;
|
||||
pub const VHOST_IOTLB_ACCESS_FAIL: raw::c_uint = 4;
|
||||
pub const VHOST_IOTLB_MSG: raw::c_uint = 1;
|
||||
pub const VHOST_PAGE_SIZE: raw::c_uint = 4096;
|
||||
pub const VHOST_VIRTIO: raw::c_uint = 175;
|
||||
pub const VHOST_VRING_LITTLE_ENDIAN: raw::c_uint = 0;
|
||||
pub const VHOST_VRING_BIG_ENDIAN: raw::c_uint = 1;
|
||||
pub const VHOST_F_LOG_ALL: raw::c_uint = 26;
|
||||
pub const VHOST_NET_F_VIRTIO_NET_HDR: raw::c_uint = 27;
|
||||
pub const VHOST_SCSI_ABI_VERSION: raw::c_uint = 1;
|
||||
|
||||
ioctl_ior_nr!(VHOST_GET_FEATURES, VHOST, 0x00, raw::c_ulonglong);
|
||||
ioctl_iow_nr!(VHOST_SET_FEATURES, VHOST, 0x00, raw::c_ulonglong);
|
||||
ioctl_io_nr!(VHOST_SET_OWNER, VHOST, 0x01);
|
||||
ioctl_io_nr!(VHOST_RESET_OWNER, VHOST, 0x02);
|
||||
ioctl_iow_nr!(VHOST_SET_MEM_TABLE, VHOST, 0x03, vhost_memory);
|
||||
ioctl_iow_nr!(VHOST_SET_LOG_BASE, VHOST, 0x04, raw::c_ulonglong);
|
||||
ioctl_iow_nr!(VHOST_SET_LOG_FD, VHOST, 0x07, raw::c_int);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_NUM, VHOST, 0x10, vhost_vring_state);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_ADDR, VHOST, 0x11, vhost_vring_addr);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_BASE, VHOST, 0x12, vhost_vring_state);
|
||||
ioctl_iowr_nr!(VHOST_GET_VRING_BASE, VHOST, 0x12, vhost_vring_state);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_KICK, VHOST, 0x20, vhost_vring_file);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_CALL, VHOST, 0x21, vhost_vring_file);
|
||||
ioctl_iow_nr!(VHOST_SET_VRING_ERR, VHOST, 0x22, vhost_vring_file);
|
||||
ioctl_iow_nr!(vhost_SET_BACKEND, VHOST, 0x30, vhost_vring_file);
|
||||
ioctl_iow_nr!(VHOST_SCSI_SET_ENDPOINT, VHOST, 0x40, vhost_scsi_target);
|
||||
ioctl_iow_nr!(VHOST_SCSI_CLEAR_ENDPOINT, VHOST, 0x41, vhost_scsi_target);
|
||||
ioctl_iow_nr!(VHOST_SCSI_GET_ABI_VERSION, VHOST, 0x42, raw::c_int);
|
||||
ioctl_iow_nr!(VHOST_SCSI_SET_EVENTS_MISSED, VHOST, 0x43, raw::c_uint);
|
||||
ioctl_iow_nr!(VHOST_SCSI_GET_EVENTS_MISSED, VHOST, 0x44, raw::c_uint);
|
||||
ioctl_iow_nr!(VHOST_VSOCK_SET_GUEST_CID, VHOST, 0x60, raw::c_ulonglong);
|
||||
ioctl_iow_nr!(VHOST_VSOCK_SET_RUNNING, VHOST, 0x61, raw::c_int);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);
|
||||
|
||||
impl<T> __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
__IncompleteArrayField(::std::marker::PhantomData)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
#[allow(clippy::useless_transmute)]
|
||||
pub unsafe fn as_ptr(&self) -> *const T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(clippy::useless_transmute)]
|
||||
pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
|
||||
::std::mem::transmute(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
|
||||
::std::slice::from_raw_parts(self.as_ptr(), len)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
|
||||
::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
|
||||
fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
fmt.write_str("__IncompleteArrayField")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct vhost_vring_state {
|
||||
pub index: raw::c_uint,
|
||||
pub num: raw::c_uint,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct vhost_vring_file {
|
||||
pub index: raw::c_uint,
|
||||
pub fd: raw::c_int,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct vhost_vring_addr {
|
||||
pub index: raw::c_uint,
|
||||
pub flags: raw::c_uint,
|
||||
pub desc_user_addr: raw::c_ulonglong,
|
||||
pub used_user_addr: raw::c_ulonglong,
|
||||
pub avail_user_addr: raw::c_ulonglong,
|
||||
pub log_guest_addr: raw::c_ulonglong,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct vhost_iotlb_msg {
|
||||
pub iova: raw::c_ulonglong,
|
||||
pub size: raw::c_ulonglong,
|
||||
pub uaddr: raw::c_ulonglong,
|
||||
pub perm: raw::c_uchar,
|
||||
pub type_: raw::c_uchar,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct vhost_msg {
|
||||
pub type_: raw::c_int,
|
||||
pub __bindgen_anon_1: vhost_msg__bindgen_ty_1,
|
||||
}
|
||||
|
||||
impl Default for vhost_msg {
|
||||
fn default() -> Self {
|
||||
unsafe { ::std::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union vhost_msg__bindgen_ty_1 {
|
||||
pub iotlb: vhost_iotlb_msg,
|
||||
pub padding: [raw::c_uchar; 64usize],
|
||||
_bindgen_union_align: [u64; 8usize],
|
||||
}
|
||||
|
||||
impl Default for vhost_msg__bindgen_ty_1 {
|
||||
fn default() -> Self {
|
||||
unsafe { ::std::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct vhost_memory_region {
|
||||
pub guest_phys_addr: raw::c_ulonglong,
|
||||
pub memory_size: raw::c_ulonglong,
|
||||
pub userspace_addr: raw::c_ulonglong,
|
||||
pub flags_padding: raw::c_ulonglong,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct vhost_memory {
|
||||
pub nregions: raw::c_uint,
|
||||
pub padding: raw::c_uint,
|
||||
pub regions: __IncompleteArrayField<vhost_memory_region>,
|
||||
__force_alignment: [u64; 0],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct vhost_scsi_target {
|
||||
pub abi_version: raw::c_int,
|
||||
pub vhost_wwpn: [raw::c_char; 224usize],
|
||||
pub vhost_tpgt: raw::c_ushort,
|
||||
pub reserved: raw::c_ushort,
|
||||
}
|
||||
|
||||
impl Default for vhost_scsi_target {
|
||||
fn default() -> Self {
|
||||
unsafe { ::std::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to support vhost::set_mem_table()
|
||||
pub struct VhostMemory {
|
||||
buf: Vec<vhost_memory>,
|
||||
}
|
||||
|
||||
impl VhostMemory {
|
||||
// Limit number of regions to u16 to simplify error handling
|
||||
pub fn new(entries: u16) -> Self {
|
||||
let size = std::mem::size_of::<vhost_memory_region>() * entries as usize;
|
||||
let count = (size + 2 * std::mem::size_of::<vhost_memory>() - 1)
|
||||
/ std::mem::size_of::<vhost_memory>();
|
||||
let mut buf: Vec<vhost_memory> = vec![Default::default(); count];
|
||||
buf[0].nregions = u32::from(entries);
|
||||
VhostMemory { buf }
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *const char {
|
||||
&self.buf[0] as *const vhost_memory as *const char
|
||||
}
|
||||
|
||||
pub fn get_header(&self) -> &vhost_memory {
|
||||
&self.buf[0]
|
||||
}
|
||||
|
||||
pub fn get_region(&self, index: u32) -> Option<&vhost_memory_region> {
|
||||
if index >= self.buf[0].nregions {
|
||||
return None;
|
||||
}
|
||||
// Safe because we have allocated enough space nregions
|
||||
let regions = unsafe { self.buf[0].regions.as_slice(self.buf[0].nregions as usize) };
|
||||
Some(®ions[index as usize])
|
||||
}
|
||||
|
||||
pub fn set_region(&mut self, index: u32, region: &vhost_memory_region) -> Result<()> {
|
||||
if index >= self.buf[0].nregions {
|
||||
return Err(Error::InvalidGuestMemory);
|
||||
}
|
||||
// Safe because we have allocated enough space nregions and checked the index.
|
||||
let regions = unsafe { self.buf[0].regions.as_mut_slice(index as usize + 1) };
|
||||
regions[index as usize] = *region;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_vring_state() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_vring_state>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(vhost_vring_state))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_vring_state>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(vhost_vring_state))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_vring_file() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_vring_file>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(vhost_vring_file))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_vring_file>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(vhost_vring_file))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_vring_addr() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_vring_addr>(),
|
||||
40usize,
|
||||
concat!("Size of: ", stringify!(vhost_vring_addr))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_vring_addr>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_vring_addr))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_msg__bindgen_ty_1() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_msg__bindgen_ty_1>(),
|
||||
64usize,
|
||||
concat!("Size of: ", stringify!(vhost_msg__bindgen_ty_1))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_msg__bindgen_ty_1>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_msg__bindgen_ty_1))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_msg() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_msg>(),
|
||||
72usize,
|
||||
concat!("Size of: ", stringify!(vhost_msg))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_msg>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_msg))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_memory_region() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_memory_region>(),
|
||||
32usize,
|
||||
concat!("Size of: ", stringify!(vhost_memory_region))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_memory_region>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_memory_region))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_memory() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_memory>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(vhost_memory))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_memory>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_memory))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_iotlb_msg() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_iotlb_msg>(),
|
||||
32usize,
|
||||
concat!("Size of: ", stringify!(vhost_iotlb_msg))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_iotlb_msg>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(vhost_iotlb_msg))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bindgen_test_layout_vhost_scsi_target() {
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<vhost_scsi_target>(),
|
||||
232usize,
|
||||
concat!("Size of: ", stringify!(vhost_scsi_target))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<vhost_scsi_target>(),
|
||||
4usize,
|
||||
concat!("Alignment of ", stringify!(vhost_scsi_target))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vhostmemory() {
|
||||
let mut obj = VhostMemory::new(2);
|
||||
let region = vhost_memory_region {
|
||||
guest_phys_addr: 0x1000u64,
|
||||
memory_size: 0x2000u64,
|
||||
userspace_addr: 0x300000u64,
|
||||
flags_padding: 0u64,
|
||||
};
|
||||
assert!(obj.get_region(2).is_none());
|
||||
|
||||
{
|
||||
let header = obj.get_header();
|
||||
assert_eq!(header.nregions, 2u32);
|
||||
}
|
||||
{
|
||||
assert!(obj.set_region(0, ®ion).is_ok());
|
||||
assert!(obj.set_region(1, ®ion).is_ok());
|
||||
assert!(obj.set_region(2, ®ion).is_err());
|
||||
}
|
||||
|
||||
let region1 = obj.get_region(1).unwrap();
|
||||
assert_eq!(region1.guest_phys_addr, 0x1000u64);
|
||||
assert_eq!(region1.memory_size, 0x2000u64);
|
||||
assert_eq!(region1.userspace_addr, 0x300000u64);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
//
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
//! Kernel-based vsock vhost backend.
|
||||
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::marker::PhantomData;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use super::vhost_binding::{VHOST_VSOCK_SET_GUEST_CID, VHOST_VSOCK_SET_RUNNING};
|
||||
use super::{ioctl_result, Error, Result, VhostKernBackend};
|
||||
use libc;
|
||||
use vm_memory::GuestMemory;
|
||||
use vmm_sys_util::ioctl::ioctl_with_ref;
|
||||
|
||||
const VHOST_PATH: &str = "/dev/vhost-vsock";
|
||||
|
||||
/// Handle for running VHOST_VSOCK ioctls.
|
||||
pub struct Vsock<'a, M: GuestMemory<'a>> {
|
||||
fd: File,
|
||||
mem: M,
|
||||
_phatomdata: PhantomData<&'a M>, // Get rid of unused type parameter `a
|
||||
}
|
||||
|
||||
impl<'a, M: GuestMemory<'a>> Vsock<'a, M> {
|
||||
/// Open a handle to a new VHOST-VSOCK instance.
|
||||
pub fn new(mem: &M) -> Result<Self> {
|
||||
Ok(Vsock {
|
||||
fd: OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
|
||||
.open(VHOST_PATH)
|
||||
.map_err(Error::VhostOpen)?,
|
||||
mem: mem.clone(),
|
||||
_phatomdata: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the CID for the guest. This number is used for routing all data destined for
|
||||
/// running in the guest. Each guest on a hypervisor must have an unique CID
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `cid` - CID to assign to the guest
|
||||
pub fn set_guest_cid(&self, cid: u64) -> Result<()> {
|
||||
let ret = unsafe { ioctl_with_ref(&self.fd, VHOST_VSOCK_SET_GUEST_CID(), &cid) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
|
||||
/// Tell the VHOST driver to start performing data transfer.
|
||||
pub fn start(&self) -> Result<()> {
|
||||
self.set_running(true)
|
||||
}
|
||||
|
||||
/// Tell the VHOST driver to stop performing data transfer.
|
||||
pub fn stop(&self) -> Result<()> {
|
||||
self.set_running(false)
|
||||
}
|
||||
|
||||
fn set_running(&self, running: bool) -> Result<()> {
|
||||
let on: ::std::os::raw::c_int = if running { 1 } else { 0 };
|
||||
let ret = unsafe { ioctl_with_ref(&self.fd, VHOST_VSOCK_SET_RUNNING(), &on) };
|
||||
ioctl_result(ret, ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M: GuestMemory<'a>> VhostKernBackend<'a> for Vsock<'a, M> {
|
||||
type M = M;
|
||||
|
||||
fn mem(&self) -> &Self::M {
|
||||
&self.mem
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M: GuestMemory<'a>> AsRawFd for Vsock<'a, M> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
@@ -1,740 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Structs for Unix Domain Socket listener and endpoint.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use libc::{c_void, iovec};
|
||||
use std::io::ErrorKind;
|
||||
use std::marker::PhantomData;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::{mem, slice};
|
||||
|
||||
use super::message::*;
|
||||
use super::sock_ctrl_msg::ScmSocket;
|
||||
use super::{Error, Result};
|
||||
|
||||
/// Unix domain socket listener for accepting incoming connections.
|
||||
pub struct Listener {
|
||||
fd: UnixListener,
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
/// Create a unix domain socket listener.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - the new Listener object on success.
|
||||
/// * - SocketError: failed to create listener socket.
|
||||
pub fn new(path: &str, unlink: bool) -> Result<Self> {
|
||||
if unlink {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
let fd = UnixListener::bind(path).map_err(Error::SocketError)?;
|
||||
Ok(Listener {
|
||||
fd,
|
||||
path: path.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Accept an incoming connection.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - Some(UnixStream): new UnixStream object if new incoming connection is available.
|
||||
/// * - None: no incoming connection available.
|
||||
/// * - SocketError: errors from accept().
|
||||
pub fn accept(&self) -> Result<Option<UnixStream>> {
|
||||
loop {
|
||||
match self.fd.accept() {
|
||||
Ok((socket, _addr)) => return Ok(Some(socket)),
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
// No incoming connection available.
|
||||
ErrorKind::WouldBlock => return Ok(None),
|
||||
// New connection closed by peer.
|
||||
ErrorKind::ConnectionAborted => return Ok(None),
|
||||
// Interrupted by signals, retry
|
||||
ErrorKind::Interrupted => continue,
|
||||
_ => return Err(Error::SocketError(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Change blocking status on the listener.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - () on success.
|
||||
/// * - SocketError: failure from set_nonblocking().
|
||||
pub fn set_nonblocking(&self, block: bool) -> Result<()> {
|
||||
self.fd.set_nonblocking(block).map_err(Error::SocketError)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Listener {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Listener {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_file(self.path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Unix domain socket endpoint for vhost-user connection.
|
||||
pub(super) struct Endpoint<R: Req> {
|
||||
sock: UnixStream,
|
||||
_r: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<R: Req> Endpoint<R> {
|
||||
/// Create a new stream by connecting to server at `str`.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - the new Endpoint object on success.
|
||||
/// * - SocketConnect: failed to connect to peer.
|
||||
pub fn connect(path: &str) -> Result<Self> {
|
||||
let sock = UnixStream::connect(path).map_err(Error::SocketConnect)?;
|
||||
Ok(Self::from_stream(sock))
|
||||
}
|
||||
|
||||
/// Create an endpoint from a stream object.
|
||||
pub fn from_stream(sock: UnixStream) -> Self {
|
||||
Endpoint {
|
||||
sock,
|
||||
_r: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends bytes from scatter-gather vectors over the socket with optional attached file
|
||||
/// descriptors.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
pub fn send_iovec(&mut self, iovs: &[&[u8]], fds: Option<&[RawFd]>) -> Result<usize> {
|
||||
let rfds = match fds {
|
||||
Some(rfds) => rfds,
|
||||
_ => &[],
|
||||
};
|
||||
self.sock.send_with_fds(iovs, rfds).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Sends bytes from a slice over the socket with optional attached file descriptors.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
pub fn send_slice(&mut self, data: &[u8], fds: Option<&[RawFd]>) -> Result<usize> {
|
||||
self.send_iovec(&[data], fds)
|
||||
}
|
||||
|
||||
/// Sends a header-only message with optional attached file descriptors.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
pub fn send_header(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<R>,
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> Result<()> {
|
||||
// Safe because there can't be other mutable referance to hdr.
|
||||
let iovs = unsafe {
|
||||
[slice::from_raw_parts(
|
||||
hdr as *const VhostUserMsgHeader<R> as *const u8,
|
||||
mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
)]
|
||||
};
|
||||
let bytes = self.send_iovec(&iovs[..], fds)?;
|
||||
if bytes != mem::size_of::<VhostUserMsgHeader<R>>() {
|
||||
return Err(Error::PartialMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a message with header and body. Optional file descriptors may be attached to
|
||||
/// the message.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
pub fn send_message<T: Sized>(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<R>,
|
||||
body: &T,
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> Result<()> {
|
||||
// Safe because there can't be other mutable referance to hdr and body.
|
||||
let iovs = unsafe {
|
||||
[
|
||||
slice::from_raw_parts(
|
||||
hdr as *const VhostUserMsgHeader<R> as *const u8,
|
||||
mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
),
|
||||
slice::from_raw_parts(body as *const T as *const u8, mem::size_of::<T>()),
|
||||
]
|
||||
};
|
||||
let bytes = self.send_iovec(&iovs[..], fds)?;
|
||||
if bytes != mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>() {
|
||||
return Err(Error::PartialMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a message with header, body and payload. Optional file descriptors
|
||||
/// may also be attached to the message.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - number of bytes sent on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - OversizedMsg: message size is too big.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
/// * - IncorrectFds: wrong number of attached fds.
|
||||
pub fn send_message_with_payload<T: Sized, P: Sized>(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<R>,
|
||||
body: &T,
|
||||
payload: &[P],
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> Result<()> {
|
||||
let len = payload.len() * mem::size_of::<P>();
|
||||
if len > MAX_MSG_SIZE - mem::size_of::<T>() {
|
||||
return Err(Error::OversizedMsg);
|
||||
}
|
||||
if let Some(fd_arr) = fds {
|
||||
if fd_arr.len() > MAX_ATTACHED_FD_ENTRIES {
|
||||
return Err(Error::IncorrectFds);
|
||||
}
|
||||
}
|
||||
|
||||
// Safe because there can't be other mutable reference to hdr, body and payload.
|
||||
let iovs = unsafe {
|
||||
[
|
||||
slice::from_raw_parts(
|
||||
hdr as *const VhostUserMsgHeader<R> as *const u8,
|
||||
mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
),
|
||||
slice::from_raw_parts(body as *const T as *const u8, mem::size_of::<T>()),
|
||||
slice::from_raw_parts(payload.as_ptr() as *const u8, len),
|
||||
]
|
||||
};
|
||||
let total = mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>() + len;
|
||||
let len = self.send_iovec(&iovs, fds)?;
|
||||
if len != total {
|
||||
return Err(Error::PartialMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads bytes from the socket into the given scatter/gather vectors.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (number of bytes received, buf) on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
pub fn recv_data(&mut self, len: usize) -> Result<(usize, Vec<u8>)> {
|
||||
let mut rbuf = vec![0u8; len];
|
||||
let mut iovs = [iovec {
|
||||
iov_base: rbuf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: len,
|
||||
}];
|
||||
let (bytes, _) = self.sock.recv_with_fds(&mut iovs, &mut [])?;
|
||||
Ok((bytes, rbuf))
|
||||
}
|
||||
|
||||
/// Reads bytes from the socket into the given scatter/gather vectors with optional attached
|
||||
/// file descriptors.
|
||||
///
|
||||
/// The underlying communication channel is a Unix domain socket in STREAM mode. It's a little
|
||||
/// tricky to pass file descriptors through such a communication channel. Let's assume that a
|
||||
/// sender sending a message with some file descriptors attached. To successfully receive those
|
||||
/// attached file descriptors, the receiver must obey following rules:
|
||||
/// 1) file descriptors are attached to a message.
|
||||
/// 2) message(packet) boundaries must be respected on the receive side.
|
||||
/// In other words, recvmsg() operations must not cross the packet boundary, otherwise the
|
||||
/// attached file descriptors will get lost.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (number of bytes received, [received fds]) on success
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
pub fn recv_into_iovec(&mut self, iovs: &mut [iovec]) -> Result<(usize, Option<Vec<RawFd>>)> {
|
||||
let mut fd_array = vec![0; MAX_ATTACHED_FD_ENTRIES];
|
||||
let (bytes, fds) = self.sock.recv_with_fds(iovs, &mut fd_array)?;
|
||||
let rfds = match fds {
|
||||
0 => None,
|
||||
n => {
|
||||
let mut fds = Vec::with_capacity(n);
|
||||
fds.extend_from_slice(&fd_array[0..n]);
|
||||
Some(fds)
|
||||
}
|
||||
};
|
||||
|
||||
Ok((bytes, rfds))
|
||||
}
|
||||
|
||||
/// Reads bytes from the socket into a new buffer with optional attached
|
||||
/// file descriptors. Received file descriptors are set close-on-exec.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (number of bytes received, buf, [received fds]) on success.
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
pub fn recv_into_buf(
|
||||
&mut self,
|
||||
buf_size: usize,
|
||||
) -> Result<(usize, Vec<u8>, Option<Vec<RawFd>>)> {
|
||||
let mut buf = vec![0u8; buf_size];
|
||||
let (bytes, rfds) = {
|
||||
let mut iovs = [iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf_size,
|
||||
}];
|
||||
self.recv_into_iovec(&mut iovs)?
|
||||
};
|
||||
Ok((bytes, buf, rfds))
|
||||
}
|
||||
|
||||
/// Receive a header-only message with optional attached file descriptors.
|
||||
/// Note, only the first MAX_ATTACHED_FD_ENTRIES file descriptors will be
|
||||
/// accepted and all other file descriptor will be discard silently.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (message header, [received fds]) on success.
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
/// * - InvalidMessage: received a invalid message.
|
||||
pub fn recv_header(&mut self) -> Result<(VhostUserMsgHeader<R>, Option<Vec<RawFd>>)> {
|
||||
let mut hdr = VhostUserMsgHeader::default();
|
||||
let mut iovs = [iovec {
|
||||
iov_base: (&mut hdr as *mut VhostUserMsgHeader<R>) as *mut c_void,
|
||||
iov_len: mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
}];
|
||||
let (bytes, rfds) = self.recv_into_iovec(&mut iovs[..])?;
|
||||
|
||||
if bytes != mem::size_of::<VhostUserMsgHeader<R>>() {
|
||||
return Err(Error::PartialMessage);
|
||||
} else if !hdr.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
Ok((hdr, rfds))
|
||||
}
|
||||
|
||||
/// Receive a message with optional attached file descriptors.
|
||||
/// Note, only the first MAX_ATTACHED_FD_ENTRIES file descriptors will be
|
||||
/// accepted and all other file descriptor will be discard silently.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (message header, message body, [received fds]) on success.
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
/// * - InvalidMessage: received a invalid message.
|
||||
pub fn recv_body<T: Sized + Default + VhostUserMsgValidator>(
|
||||
&mut self,
|
||||
) -> Result<(VhostUserMsgHeader<R>, T, Option<Vec<RawFd>>)> {
|
||||
let mut hdr = VhostUserMsgHeader::default();
|
||||
let mut body: T = Default::default();
|
||||
let mut iovs = [
|
||||
iovec {
|
||||
iov_base: (&mut hdr as *mut VhostUserMsgHeader<R>) as *mut c_void,
|
||||
iov_len: mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
},
|
||||
iovec {
|
||||
iov_base: (&mut body as *mut T) as *mut c_void,
|
||||
iov_len: mem::size_of::<T>(),
|
||||
},
|
||||
];
|
||||
let (bytes, rfds) = self.recv_into_iovec(&mut iovs[..])?;
|
||||
|
||||
let total = mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>();
|
||||
if bytes != total {
|
||||
return Err(Error::PartialMessage);
|
||||
} else if !hdr.is_valid() || !body.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
Ok((hdr, body, rfds))
|
||||
}
|
||||
|
||||
/// Receive a message with header and optional content. Callers need to
|
||||
/// pre-allocate a big enough buffer to receive the message body and
|
||||
/// optional payload. If there are attached file descriptor associated
|
||||
/// with the message, the first MAX_ATTACHED_FD_ENTRIES file descriptors
|
||||
/// will be accepted and all other file descriptor will be discard
|
||||
/// silently.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (message header, message size, [received fds]) on success.
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
/// * - InvalidMessage: received a invalid message.
|
||||
pub fn recv_body_into_buf(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
) -> Result<(VhostUserMsgHeader<R>, usize, Option<Vec<RawFd>>)> {
|
||||
let mut hdr = VhostUserMsgHeader::default();
|
||||
let mut iovs = [
|
||||
iovec {
|
||||
iov_base: (&mut hdr as *mut VhostUserMsgHeader<R>) as *mut c_void,
|
||||
iov_len: mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
},
|
||||
iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf.len(),
|
||||
},
|
||||
];
|
||||
let (bytes, rfds) = self.recv_into_iovec(&mut iovs[..])?;
|
||||
|
||||
if bytes < mem::size_of::<VhostUserMsgHeader<R>>() {
|
||||
return Err(Error::PartialMessage);
|
||||
} else if !hdr.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
Ok((hdr, bytes - mem::size_of::<VhostUserMsgHeader<R>>(), rfds))
|
||||
}
|
||||
|
||||
/// Receive a message with optional payload and attached file descriptors.
|
||||
/// Note, only the first MAX_ATTACHED_FD_ENTRIES file descriptors will be
|
||||
/// accepted and all other file descriptor will be discard silently.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - (message header, message body, size of payload, [received fds]) on success.
|
||||
/// * - SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - SocketBroken: the underline socket is broken.
|
||||
/// * - SocketError: other socket related errors.
|
||||
/// * - PartialMessage: received a partial message.
|
||||
/// * - InvalidMessage: received a invalid message.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
|
||||
pub fn recv_payload_into_buf<T: Sized + Default + VhostUserMsgValidator>(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
) -> Result<(VhostUserMsgHeader<R>, T, usize, Option<Vec<RawFd>>)> {
|
||||
let mut hdr = VhostUserMsgHeader::default();
|
||||
let mut body: T = Default::default();
|
||||
let mut iovs = [
|
||||
iovec {
|
||||
iov_base: (&mut hdr as *mut VhostUserMsgHeader<R>) as *mut c_void,
|
||||
iov_len: mem::size_of::<VhostUserMsgHeader<R>>(),
|
||||
},
|
||||
iovec {
|
||||
iov_base: (&mut body as *mut T) as *mut c_void,
|
||||
iov_len: mem::size_of::<T>(),
|
||||
},
|
||||
iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf.len(),
|
||||
},
|
||||
];
|
||||
let (bytes, rfds) = self.recv_into_iovec(&mut iovs[..])?;
|
||||
|
||||
let total = mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>();
|
||||
if bytes < total {
|
||||
return Err(Error::PartialMessage);
|
||||
} else if !hdr.is_valid() || !body.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
Ok((hdr, body, bytes - total, rfds))
|
||||
}
|
||||
|
||||
/// Close all raw file descriptors.
|
||||
pub fn close_rfds(rfds: Option<Vec<RawFd>>) {
|
||||
if let Some(fds) = rfds {
|
||||
for fd in fds {
|
||||
// safe because the rawfds are valid and we don't care about the result.
|
||||
let _ = unsafe { libc::close(fd) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Req> AsRawFd for Endpoint<T> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.sock.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate tempfile;
|
||||
|
||||
use self::tempfile::tempfile;
|
||||
use super::*;
|
||||
use libc;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
const UNIX_SOCKET_LISTENER: &'static str = "/tmp/vhost_user_test_rust_listener";
|
||||
const UNIX_SOCKET_CONNECTION: &'static str = "/tmp/vhost_user_test_rust_connection";
|
||||
const UNIX_SOCKET_DATA: &'static str = "/tmp/vhost_user_test_rust_data";
|
||||
const UNIX_SOCKET_FD: &'static str = "/tmp/vhost_user_test_rust_fd";
|
||||
const UNIX_SOCKET_SEND: &'static str = "/tmp/vhost_user_test_rust_send";
|
||||
|
||||
#[test]
|
||||
fn create_listener() {
|
||||
let _ = Listener::new(UNIX_SOCKET_LISTENER, true).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accept_connection() {
|
||||
let listener = Listener::new(UNIX_SOCKET_CONNECTION, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
|
||||
// accept on a fd without incoming connection
|
||||
let conn = listener.accept().unwrap();
|
||||
assert!(conn.is_none());
|
||||
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
|
||||
// accept on a closed fd
|
||||
unsafe {
|
||||
libc::close(listener.as_raw_fd());
|
||||
}
|
||||
let conn2 = listener.accept();
|
||||
assert!(conn2.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn send_data() {
|
||||
let listener = Listener::new(UNIX_SOCKET_DATA, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
let mut master = Endpoint::<MasterReq>::connect(UNIX_SOCKET_DATA).unwrap();
|
||||
let sock = listener.accept().unwrap().unwrap();
|
||||
let mut slave = Endpoint::<MasterReq>::from_stream(sock);
|
||||
|
||||
let buf1 = vec![0x1, 0x2, 0x3, 0x4];
|
||||
let mut len = master.send_slice(&buf1[..], None).unwrap();
|
||||
assert_eq!(len, 4);
|
||||
let (bytes, buf2, _) = slave.recv_into_buf(0x1000).unwrap();
|
||||
assert_eq!(bytes, 4);
|
||||
assert_eq!(&buf1[..], &buf2[..bytes]);
|
||||
|
||||
len = master.send_slice(&buf1[..], None).unwrap();
|
||||
assert_eq!(len, 4);
|
||||
let (bytes, buf2, _) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[..2], &buf2[..]);
|
||||
let (bytes, buf2, _) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[2..], &buf2[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn send_fd() {
|
||||
let listener = Listener::new(UNIX_SOCKET_FD, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
let mut master = Endpoint::<MasterReq>::connect(UNIX_SOCKET_FD).unwrap();
|
||||
let sock = listener.accept().unwrap().unwrap();
|
||||
let mut slave = Endpoint::<MasterReq>::from_stream(sock);
|
||||
|
||||
let mut fd = tempfile().unwrap();
|
||||
write!(fd, "test").unwrap();
|
||||
|
||||
// Normal case for sending/receiving file descriptors
|
||||
let buf1 = vec![0x1, 0x2, 0x3, 0x4];
|
||||
let len = master
|
||||
.send_slice(&buf1[..], Some(&[fd.as_raw_fd()]))
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(4).unwrap();
|
||||
assert_eq!(bytes, 4);
|
||||
assert_eq!(&buf1[..], &buf2[..]);
|
||||
assert!(rfds.is_some());
|
||||
let fds = rfds.unwrap();
|
||||
{
|
||||
assert_eq!(fds.len(), 1);
|
||||
let mut file = unsafe { File::from_raw_fd(fds[0]) };
|
||||
let mut content = String::new();
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
file.read_to_string(&mut content).unwrap();
|
||||
assert_eq!(content, "test");
|
||||
}
|
||||
|
||||
// Following communication pattern should work:
|
||||
// Sending side: data(header, body) with fds
|
||||
// Receiving side: data(header) with fds, data(body)
|
||||
let len = master
|
||||
.send_slice(
|
||||
&buf1[..],
|
||||
Some(&[fd.as_raw_fd(), fd.as_raw_fd(), fd.as_raw_fd()]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[..2], &buf2[..]);
|
||||
assert!(rfds.is_some());
|
||||
let fds = rfds.unwrap();
|
||||
{
|
||||
assert_eq!(fds.len(), 3);
|
||||
let mut file = unsafe { File::from_raw_fd(fds[1]) };
|
||||
let mut content = String::new();
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
file.read_to_string(&mut content).unwrap();
|
||||
assert_eq!(content, "test");
|
||||
}
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[2..], &buf2[..]);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
// Following communication pattern should not work:
|
||||
// Sending side: data(header, body) with fds
|
||||
// Receiving side: data(header), data(body) with fds
|
||||
let len = master
|
||||
.send_slice(
|
||||
&buf1[..],
|
||||
Some(&[fd.as_raw_fd(), fd.as_raw_fd(), fd.as_raw_fd()]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, buf4) = slave.recv_data(2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[..2], &buf4[..]);
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[2..], &buf2[..]);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
// Following communication pattern should work:
|
||||
// Sending side: data, data with fds
|
||||
// Receiving side: data, data with fds
|
||||
let len = master.send_slice(&buf1[..], None).unwrap();
|
||||
assert_eq!(len, 4);
|
||||
let len = master
|
||||
.send_slice(
|
||||
&buf1[..],
|
||||
Some(&[fd.as_raw_fd(), fd.as_raw_fd(), fd.as_raw_fd()]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x4).unwrap();
|
||||
assert_eq!(bytes, 4);
|
||||
assert_eq!(&buf1[..], &buf2[..]);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[..2], &buf2[..]);
|
||||
assert!(rfds.is_some());
|
||||
let fds = rfds.unwrap();
|
||||
{
|
||||
assert_eq!(fds.len(), 3);
|
||||
let mut file = unsafe { File::from_raw_fd(fds[1]) };
|
||||
let mut content = String::new();
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
file.read_to_string(&mut content).unwrap();
|
||||
assert_eq!(content, "test");
|
||||
}
|
||||
let (bytes, buf2, rfds) = slave.recv_into_buf(0x2).unwrap();
|
||||
assert_eq!(bytes, 2);
|
||||
assert_eq!(&buf1[2..], &buf2[..]);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
// Following communication pattern should not work:
|
||||
// Sending side: data1, data2 with fds
|
||||
// Receiving side: data + partial of data2, left of data2 with fds
|
||||
let len = master.send_slice(&buf1[..], None).unwrap();
|
||||
assert_eq!(len, 4);
|
||||
let len = master
|
||||
.send_slice(
|
||||
&buf1[..],
|
||||
Some(&[fd.as_raw_fd(), fd.as_raw_fd(), fd.as_raw_fd()]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, _) = slave.recv_data(5).unwrap();
|
||||
assert_eq!(bytes, 5);
|
||||
|
||||
let (bytes, _, rfds) = slave.recv_into_buf(0x4).unwrap();
|
||||
assert_eq!(bytes, 3);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
// If the target fd array is too small, extra file descriptors will get lost.
|
||||
let len = master
|
||||
.send_slice(
|
||||
&buf1[..],
|
||||
Some(&[fd.as_raw_fd(), fd.as_raw_fd(), fd.as_raw_fd()]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(len, 4);
|
||||
|
||||
let (bytes, _, rfds) = slave.recv_into_buf(0x4).unwrap();
|
||||
assert_eq!(bytes, 4);
|
||||
assert!(rfds.is_some());
|
||||
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
Endpoint::<MasterReq>::close_rfds(None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn send_recv() {
|
||||
let listener = Listener::new(UNIX_SOCKET_SEND, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
let mut master = Endpoint::<MasterReq>::connect(UNIX_SOCKET_SEND).unwrap();
|
||||
let sock = listener.accept().unwrap().unwrap();
|
||||
let mut slave = Endpoint::<MasterReq>::from_stream(sock);
|
||||
|
||||
let mut hdr1 =
|
||||
VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0, mem::size_of::<u64>() as u32);
|
||||
hdr1.set_need_reply(true);
|
||||
let features1 = 0x1u64;
|
||||
master.send_message(&hdr1, &features1, None).unwrap();
|
||||
|
||||
let mut features2 = 0u64;
|
||||
let slice = unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
(&mut features2 as *mut u64) as *mut u8,
|
||||
mem::size_of::<u64>(),
|
||||
)
|
||||
};
|
||||
let (hdr2, bytes, rfds) = slave.recv_body_into_buf(slice).unwrap();
|
||||
assert_eq!(hdr1, hdr2);
|
||||
assert_eq!(bytes, 8);
|
||||
assert_eq!(features1, features2);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
master.send_header(&hdr1, None).unwrap();
|
||||
let (hdr2, rfds) = slave.recv_header().unwrap();
|
||||
assert_eq!(hdr1, hdr2);
|
||||
assert!(rfds.is_none());
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::message::*;
|
||||
use super::*;
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
pub const MAX_QUEUE_NUM: usize = 2;
|
||||
pub const MAX_VRING_NUM: usize = 256;
|
||||
pub const VIRTIO_FEATURES: u64 = 0x40000003;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DummySlaveReqHandler {
|
||||
pub owned: bool,
|
||||
pub features_acked: bool,
|
||||
pub acked_features: u64,
|
||||
pub acked_protocol_features: u64,
|
||||
pub queue_num: usize,
|
||||
pub vring_num: [u32; MAX_QUEUE_NUM],
|
||||
pub vring_base: [u32; MAX_QUEUE_NUM],
|
||||
pub call_fd: [Option<RawFd>; MAX_QUEUE_NUM],
|
||||
pub kick_fd: [Option<RawFd>; MAX_QUEUE_NUM],
|
||||
pub err_fd: [Option<RawFd>; MAX_QUEUE_NUM],
|
||||
pub vring_started: [bool; MAX_QUEUE_NUM],
|
||||
pub vring_enabled: [bool; MAX_QUEUE_NUM],
|
||||
}
|
||||
|
||||
impl DummySlaveReqHandler {
|
||||
pub fn new() -> Self {
|
||||
DummySlaveReqHandler {
|
||||
queue_num: MAX_QUEUE_NUM,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserSlaveReqHandler for DummySlaveReqHandler {
|
||||
fn set_owner(&mut self) -> Result<()> {
|
||||
if self.owned {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
self.owned = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_owner(&mut self) -> Result<()> {
|
||||
self.owned = false;
|
||||
self.features_acked = false;
|
||||
self.acked_features = 0;
|
||||
self.acked_protocol_features = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_features(&mut self) -> Result<u64> {
|
||||
Ok(VIRTIO_FEATURES)
|
||||
}
|
||||
|
||||
fn set_features(&mut self, features: u64) -> Result<()> {
|
||||
if !self.owned {
|
||||
return Err(Error::InvalidOperation);
|
||||
} else if self.features_acked {
|
||||
return Err(Error::InvalidOperation);
|
||||
} else if (features & !VIRTIO_FEATURES) != 0 {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
|
||||
self.acked_features = features;
|
||||
self.features_acked = true;
|
||||
|
||||
// If VHOST_USER_F_PROTOCOL_FEATURES has not been negotiated,
|
||||
// the ring is initialized in an enabled state.
|
||||
// If VHOST_USER_F_PROTOCOL_FEATURES has been negotiated,
|
||||
// the ring is initialized in a disabled state. Client must not
|
||||
// pass data to/from the backend until ring is enabled by
|
||||
// VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has
|
||||
// been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0.
|
||||
let vring_enabled =
|
||||
self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0;
|
||||
for enabled in &mut self.vring_enabled {
|
||||
*enabled = vring_enabled;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures> {
|
||||
Ok(VhostUserProtocolFeatures::all())
|
||||
}
|
||||
|
||||
fn set_protocol_features(&mut self, features: u64) -> Result<()> {
|
||||
// Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must
|
||||
// support this message even before VHOST_USER_SET_FEATURES was
|
||||
// called.
|
||||
// What happens if the master calls set_features() with
|
||||
// VHOST_USER_F_PROTOCOL_FEATURES cleared after calling this
|
||||
// interface?
|
||||
self.acked_protocol_features = features;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_mem_table(&mut self, _ctx: &[VhostUserMemoryRegion], _fds: &[RawFd]) -> Result<()> {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_queue_num(&mut self) -> Result<u64> {
|
||||
Ok(MAX_QUEUE_NUM as u64)
|
||||
}
|
||||
|
||||
fn set_vring_num(&mut self, index: u32, num: u32) -> Result<()> {
|
||||
if index as usize >= self.queue_num || num == 0 || num as usize > MAX_VRING_NUM {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.vring_num[index as usize] = num;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_addr(
|
||||
&mut self,
|
||||
index: u32,
|
||||
_flags: VhostUserVringAddrFlags,
|
||||
_descriptor: u64,
|
||||
_used: u64,
|
||||
_available: u64,
|
||||
_log: u64,
|
||||
) -> Result<()> {
|
||||
if index as usize >= self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_base(&mut self, index: u32, base: u32) -> Result<()> {
|
||||
if index as usize >= self.queue_num || base as usize >= MAX_VRING_NUM {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.vring_base[index as usize] = base;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_vring_base(&mut self, index: u32) -> Result<VhostUserVringState> {
|
||||
if index as usize >= self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
// Quotation from vhost-user spec:
|
||||
// Client must start ring upon receiving a kick (that is, detecting
|
||||
// that file descriptor is readable) on the descriptor specified by
|
||||
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
self.vring_started[index as usize] = false;
|
||||
Ok(VhostUserVringState::new(
|
||||
index,
|
||||
self.vring_base[index as usize],
|
||||
))
|
||||
}
|
||||
|
||||
fn set_vring_kick(&mut self, index: u8, fd: Option<RawFd>) -> Result<()> {
|
||||
if index as usize >= self.queue_num || index as usize > self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
if self.kick_fd[index as usize].is_some() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(self.kick_fd[index as usize].unwrap()) };
|
||||
}
|
||||
self.kick_fd[index as usize] = fd;
|
||||
|
||||
// Quotation from vhost-user spec:
|
||||
// Client must start ring upon receiving a kick (that is, detecting
|
||||
// that file descriptor is readable) on the descriptor specified by
|
||||
// VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
|
||||
// VHOST_USER_GET_VRING_BASE.
|
||||
//
|
||||
// So we should add fd to event monitor(select, poll, epoll) here.
|
||||
self.vring_started[index as usize] = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_call(&mut self, index: u8, fd: Option<RawFd>) -> Result<()> {
|
||||
if index as usize >= self.queue_num || index as usize > self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
if self.call_fd[index as usize].is_some() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(self.call_fd[index as usize].unwrap()) };
|
||||
}
|
||||
self.call_fd[index as usize] = fd;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_err(&mut self, index: u8, fd: Option<RawFd>) -> Result<()> {
|
||||
if index as usize >= self.queue_num || index as usize > self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
if self.err_fd[index as usize].is_some() {
|
||||
// Close file descriptor set by previous operations.
|
||||
let _ = unsafe { libc::close(self.err_fd[index as usize].unwrap()) };
|
||||
}
|
||||
self.err_fd[index as usize] = fd;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_vring_enable(&mut self, index: u32, enable: bool) -> Result<()> {
|
||||
// This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES
|
||||
// has been negotiated.
|
||||
if self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
} else if index as usize >= self.queue_num || index as usize > self.queue_num {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
|
||||
// Slave must not pass data to/from the backend until ring is
|
||||
// enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1,
|
||||
// or after it has been disabled by VHOST_USER_SET_VRING_ENABLE
|
||||
// with parameter 0.
|
||||
self.vring_enabled[index as usize] = enable;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
_flags: VhostUserConfigFlags,
|
||||
) -> Result<Vec<u8>> {
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
Ok(vec![0xa5; size as usize])
|
||||
}
|
||||
|
||||
fn set_config(&mut self, offset: u32, buf: &[u8], _flags: VhostUserConfigFlags) -> Result<()> {
|
||||
let size = buf.len() as u32;
|
||||
if self.acked_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
} else if offset < VHOST_USER_CONFIG_OFFSET
|
||||
|| offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
|
||||
|| size + offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,766 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Traits and Struct for vhost-user master.
|
||||
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use super::connection::Endpoint;
|
||||
use super::message::*;
|
||||
use super::{Error as VhostUserError, Result as VhostUserResult};
|
||||
use crate::backend::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData};
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// Trait for vhost-user master to provide extra methods not covered by the VhostBackend yet.
|
||||
pub trait VhostUserMaster: VhostBackend {
|
||||
/// Get the protocol feature bitmask from the underlying vhost implementation.
|
||||
fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures>;
|
||||
|
||||
/// Enable protocol features in the underlying vhost implementation.
|
||||
fn set_protocol_features(&mut self, features: VhostUserProtocolFeatures) -> Result<()>;
|
||||
|
||||
/// Query how many queues the backend supports.
|
||||
fn get_queue_num(&mut self) -> Result<u64>;
|
||||
|
||||
/// Signal slave to enable or disable corresponding vring.
|
||||
///
|
||||
/// Slave must not pass data to/from the backend until ring is enabled by
|
||||
/// VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has been
|
||||
/// disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0.
|
||||
fn set_vring_enable(&mut self, queue_index: usize, enable: bool) -> Result<()>;
|
||||
|
||||
/// Fetch the contents of the virtio device configuration space.
|
||||
fn get_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
flags: VhostUserConfigFlags,
|
||||
buf: &[u8],
|
||||
) -> Result<(VhostUserConfig, VhostUserConfigPayload)>;
|
||||
|
||||
/// Change the virtio device configuration space. It also can be used for live migration on the
|
||||
/// destination host to set readonly configuration space fields.
|
||||
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()>;
|
||||
|
||||
/// Setup slave communication channel.
|
||||
fn set_slave_request_fd(&mut self, fd: RawFd) -> Result<()>;
|
||||
}
|
||||
|
||||
fn error_code<T>(err: VhostUserError) -> Result<T> {
|
||||
Err(Error::VhostUserProtocol(err))
|
||||
}
|
||||
|
||||
/// Struct for the vhost-user master endpoint.
|
||||
#[derive(Clone)]
|
||||
pub struct Master {
|
||||
node: Arc<Mutex<MasterInternal>>,
|
||||
}
|
||||
|
||||
impl Master {
|
||||
/// Create a new instance.
|
||||
fn new(ep: Endpoint<MasterReq>, max_queue_num: u64) -> Self {
|
||||
Master {
|
||||
node: Arc::new(Mutex::new(MasterInternal {
|
||||
main_sock: ep,
|
||||
virtio_features: 0,
|
||||
acked_virtio_features: 0,
|
||||
protocol_features: 0,
|
||||
acked_protocol_features: 0,
|
||||
protocol_features_ready: false,
|
||||
max_queue_num,
|
||||
error: None,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new instance from a Unix stream socket.
|
||||
pub fn from_stream(sock: UnixStream, max_queue_num: u64) -> Self {
|
||||
Self::new(Endpoint::<MasterReq>::from_stream(sock), max_queue_num)
|
||||
}
|
||||
|
||||
/// Create a new vhost-user master endpoint.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `path` - path of Unix domain socket listener to connect to
|
||||
pub fn connect(path: &str, max_queue_num: u64) -> Result<Self> {
|
||||
Ok(Self::new(
|
||||
Endpoint::<MasterReq>::connect(path)?,
|
||||
max_queue_num,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostBackend for Master {
|
||||
/// Get from the underlying vhost implementation the feature bitmask.
|
||||
fn get_features(&mut self) -> Result<u64> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let hdr = node.send_request_header(MasterReq::GET_FEATURES, None)?;
|
||||
let val = node.recv_reply::<VhostUserU64>(&hdr)?;
|
||||
node.virtio_features = val.value;
|
||||
Ok(node.virtio_features)
|
||||
}
|
||||
|
||||
/// Enable features in the underlying vhost implementation using a bitmask.
|
||||
fn set_features(&mut self, features: u64) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let val = VhostUserU64::new(features);
|
||||
let _ = node.send_request_with_body(MasterReq::SET_FEATURES, &val, None)?;
|
||||
// Don't wait for ACK here because the protocol feature negotiation process hasn't been
|
||||
// completed yet.
|
||||
node.acked_virtio_features = features & node.virtio_features;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the current Master as an owner of the session.
|
||||
fn set_owner(&mut self) -> Result<()> {
|
||||
// We unwrap() the return value to assert that we are not expecting threads to ever fail
|
||||
// while holding the lock.
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let _ = node.send_request_header(MasterReq::SET_OWNER, None)?;
|
||||
// Don't wait for ACK here because the protocol feature negotiation process hasn't been
|
||||
// completed yet.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_owner(&mut self) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let _ = node.send_request_header(MasterReq::RESET_OWNER, None)?;
|
||||
// Don't wait for ACK here because the protocol feature negotiation process hasn't been
|
||||
// completed yet.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the memory map regions on the slave so it can translate the vring
|
||||
/// addresses. In the ancillary data there is an array of file descriptors
|
||||
fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
|
||||
if regions.is_empty() || regions.len() > MAX_ATTACHED_FD_ENTRIES {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let mut ctx = VhostUserMemoryContext::new();
|
||||
for region in regions.iter() {
|
||||
if region.memory_size == 0 || region.mmap_handle < 0 {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
let reg = VhostUserMemoryRegion {
|
||||
guest_phys_addr: region.guest_phys_addr,
|
||||
memory_size: region.memory_size,
|
||||
user_addr: region.userspace_addr,
|
||||
mmap_offset: region.mmap_offset,
|
||||
};
|
||||
ctx.append(®, region.mmap_handle);
|
||||
}
|
||||
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let body = VhostUserMemory::new(ctx.regions.len() as u32);
|
||||
let hdr = node.send_request_with_payload(
|
||||
MasterReq::SET_MEM_TABLE,
|
||||
&body,
|
||||
ctx.regions.as_slice(),
|
||||
Some(ctx.fds.as_slice()),
|
||||
)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
// Clippy doesn't seem to know that if let with && is still experimental
|
||||
#[allow(clippy::unnecessary_unwrap)]
|
||||
fn set_log_base(&mut self, base: u64, fd: Option<RawFd>) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let val = VhostUserU64::new(base);
|
||||
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0
|
||||
&& fd.is_some()
|
||||
{
|
||||
let fds = [fd.unwrap()];
|
||||
let _ = node.send_request_with_body(MasterReq::SET_LOG_BASE, &val, Some(&fds))?;
|
||||
} else {
|
||||
let _ = node.send_request_with_body(MasterReq::SET_LOG_BASE, &val, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_log_fd(&mut self, fd: RawFd) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let fds = [fd];
|
||||
node.send_request_header(MasterReq::SET_LOG_FD, Some(&fds))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the size of the queue.
|
||||
fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let val = VhostUserVringState::new(queue_index as u32, num.into());
|
||||
let hdr = node.send_request_with_body(MasterReq::SET_VRING_NUM, &val, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Sets the addresses of the different aspects of the vring.
|
||||
fn set_vring_addr(&mut self, queue_index: usize, config_data: &VringConfigData) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num
|
||||
|| config_data.flags & !(VhostUserVringAddrFlags::all().bits()) != 0
|
||||
{
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let val = VhostUserVringAddr::from_config_data(queue_index as u32, config_data);
|
||||
let hdr = node.send_request_with_body(MasterReq::SET_VRING_ADDR, &val, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Sets the base offset in the available vring.
|
||||
fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let val = VhostUserVringState::new(queue_index as u32, base.into());
|
||||
let hdr = node.send_request_with_body(MasterReq::SET_VRING_BASE, &val, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_vring_base(&mut self, queue_index: usize) -> Result<u32> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let req = VhostUserVringState::new(queue_index as u32, 0);
|
||||
let hdr = node.send_request_with_body(MasterReq::GET_VRING_BASE, &req, None)?;
|
||||
let reply = node.recv_reply::<VhostUserVringState>(&hdr)?;
|
||||
Ok(reply.num)
|
||||
}
|
||||
|
||||
/// Set the event file descriptor to signal when buffers are used.
|
||||
/// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag
|
||||
/// is set when there is no file descriptor in the ancillary data. This signals that polling
|
||||
/// will be used instead of waiting for the call.
|
||||
fn set_vring_call(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
node.send_fd_for_vring(MasterReq::SET_VRING_CALL, queue_index, fd.as_raw_fd())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the event file descriptor for adding buffers to the vring.
|
||||
/// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag
|
||||
/// is set when there is no file descriptor in the ancillary data. This signals that polling
|
||||
/// should be used instead of waiting for a kick.
|
||||
fn set_vring_kick(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
node.send_fd_for_vring(MasterReq::SET_VRING_KICK, queue_index, fd.as_raw_fd())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the event file descriptor to signal when error occurs.
|
||||
/// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag
|
||||
/// is set when there is no file descriptor in the ancillary data.
|
||||
fn set_vring_err(&mut self, queue_index: usize, fd: &EventFd) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
node.send_fd_for_vring(MasterReq::SET_VRING_ERR, queue_index, fd.as_raw_fd())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMaster for Master {
|
||||
fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let flag = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
if node.virtio_features & flag == 0 || node.acked_virtio_features & flag == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
let hdr = node.send_request_header(MasterReq::GET_PROTOCOL_FEATURES, None)?;
|
||||
let val = node.recv_reply::<VhostUserU64>(&hdr)?;
|
||||
node.protocol_features = val.value;
|
||||
// Should we support forward compatibility?
|
||||
// If so just mask out unrecognized flags instead of return errors.
|
||||
match VhostUserProtocolFeatures::from_bits(node.protocol_features) {
|
||||
Some(val) => Ok(val),
|
||||
None => error_code(VhostUserError::InvalidMessage),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_protocol_features(&mut self, features: VhostUserProtocolFeatures) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
let flag = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
if node.virtio_features & flag == 0 || node.acked_virtio_features & flag == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
let val = VhostUserU64::new(features.bits());
|
||||
let _ = node.send_request_with_body(MasterReq::SET_PROTOCOL_FEATURES, &val, None)?;
|
||||
// Don't wait for ACK here because the protocol feature negotiation process hasn't been
|
||||
// completed yet.
|
||||
node.acked_protocol_features = features.bits();
|
||||
node.protocol_features_ready = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_queue_num(&mut self) -> Result<u64> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if !node.is_feature_mq_available() {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
let hdr = node.send_request_header(MasterReq::GET_QUEUE_NUM, None)?;
|
||||
let val = node.recv_reply::<VhostUserU64>(&hdr)?;
|
||||
if val.value > VHOST_USER_MAX_VRINGS {
|
||||
return error_code(VhostUserError::InvalidMessage);
|
||||
}
|
||||
node.max_queue_num = val.value;
|
||||
Ok(node.max_queue_num)
|
||||
}
|
||||
|
||||
fn set_vring_enable(&mut self, queue_index: usize, enable: bool) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
// set_vring_enable() is supported only when PROTOCOL_FEATURES has been enabled.
|
||||
if node.acked_virtio_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
} else if queue_index as u64 >= node.max_queue_num {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let flag = if enable { 1 } else { 0 };
|
||||
let val = VhostUserVringState::new(queue_index as u32, flag);
|
||||
let hdr = node.send_request_with_body(MasterReq::SET_VRING_ENABLE, &val, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
flags: VhostUserConfigFlags,
|
||||
buf: &[u8],
|
||||
) -> Result<(VhostUserConfig, VhostUserConfigPayload)> {
|
||||
let body = VhostUserConfig::new(offset, size, flags);
|
||||
if !body.is_valid() {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let mut node = self.node.lock().unwrap();
|
||||
// depends on VhostUserProtocolFeatures::CONFIG
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
// vhost-user spec states that:
|
||||
// "Master payload: virtio device config space"
|
||||
// "Slave payload: virtio device config space"
|
||||
let hdr = node.send_request_with_payload(MasterReq::GET_CONFIG, &body, buf, None)?;
|
||||
let (body_reply, buf_reply, rfds) =
|
||||
node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
|
||||
if rfds.is_some() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return error_code(VhostUserError::InvalidMessage);
|
||||
} else if body_reply.size == 0 {
|
||||
return error_code(VhostUserError::SlaveInternalError);
|
||||
} else if body_reply.size != body.size || body_reply.size as usize != buf.len() {
|
||||
return error_code(VhostUserError::InvalidMessage);
|
||||
}
|
||||
Ok((body_reply, buf_reply))
|
||||
}
|
||||
|
||||
fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()> {
|
||||
if buf.len() > MAX_MSG_SIZE {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
let body = VhostUserConfig::new(offset, buf.len() as u32, flags);
|
||||
if !body.is_valid() {
|
||||
return error_code(VhostUserError::InvalidParam);
|
||||
}
|
||||
|
||||
let mut node = self.node.lock().unwrap();
|
||||
// depends on VhostUserProtocolFeatures::CONFIG
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
let hdr = node.send_request_with_payload(MasterReq::SET_CONFIG, &body, buf, None)?;
|
||||
node.wait_for_ack(&hdr).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn set_slave_request_fd(&mut self, fd: RawFd) -> Result<()> {
|
||||
let mut node = self.node.lock().unwrap();
|
||||
if node.acked_protocol_features & VhostUserProtocolFeatures::SLAVE_REQ.bits() == 0 {
|
||||
return error_code(VhostUserError::InvalidOperation);
|
||||
}
|
||||
|
||||
let fds = [fd];
|
||||
node.send_request_header(MasterReq::SET_SLAVE_REQ_FD, Some(&fds))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for Master {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
let node = self.node.lock().unwrap();
|
||||
node.main_sock.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
/// Context object to pass guest memory configuration to VhostUserMaster::set_mem_table().
|
||||
struct VhostUserMemoryContext {
|
||||
regions: VhostUserMemoryPayload,
|
||||
fds: Vec<RawFd>,
|
||||
}
|
||||
|
||||
impl VhostUserMemoryContext {
|
||||
/// Create a context object.
|
||||
pub fn new() -> Self {
|
||||
VhostUserMemoryContext {
|
||||
regions: VhostUserMemoryPayload::new(),
|
||||
fds: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a user memory region and corresponding RawFd into the context object.
|
||||
pub fn append(&mut self, region: &VhostUserMemoryRegion, fd: RawFd) {
|
||||
self.regions.push(*region);
|
||||
self.fds.push(fd);
|
||||
}
|
||||
}
|
||||
|
||||
struct MasterInternal {
|
||||
// Used to send requests to the slave.
|
||||
main_sock: Endpoint<MasterReq>,
|
||||
// Cached virtio features from the slave.
|
||||
virtio_features: u64,
|
||||
// Cached acked virtio features from the driver.
|
||||
acked_virtio_features: u64,
|
||||
// Cached vhost-user protocol features from the slave.
|
||||
protocol_features: u64,
|
||||
// Cached vhost-user protocol features.
|
||||
acked_protocol_features: u64,
|
||||
// Cached vhost-user protocol features are ready to use.
|
||||
protocol_features_ready: bool,
|
||||
// Cached maxinum number of queues supported from the slave.
|
||||
max_queue_num: u64,
|
||||
// Internal flag to mark failure state.
|
||||
error: Option<i32>,
|
||||
}
|
||||
|
||||
impl MasterInternal {
|
||||
fn send_request_header(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
|
||||
self.check_state()?;
|
||||
let hdr = Self::new_request_header(code, 0);
|
||||
self.main_sock.send_header(&hdr, fds)?;
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn send_request_with_body<T: Sized>(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
msg: &T,
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let hdr = Self::new_request_header(code, mem::size_of::<T>() as u32);
|
||||
self.main_sock.send_message(&hdr, msg, fds)?;
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn send_request_with_payload<T: Sized, P: Sized>(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
msg: &T,
|
||||
payload: &[P],
|
||||
fds: Option<&[RawFd]>,
|
||||
) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
|
||||
let len = mem::size_of::<T>() + payload.len() * mem::size_of::<P>();
|
||||
if len > MAX_MSG_SIZE {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
if let Some(ref fd_arr) = fds {
|
||||
if fd_arr.len() > MAX_ATTACHED_FD_ENTRIES {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let hdr = Self::new_request_header(code, len as u32);
|
||||
self.main_sock
|
||||
.send_message_with_payload(&hdr, msg, payload, fds)?;
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn send_fd_for_vring(
|
||||
&mut self,
|
||||
code: MasterReq,
|
||||
queue_index: usize,
|
||||
fd: RawFd,
|
||||
) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
|
||||
if queue_index as u64 >= self.max_queue_num {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag.
|
||||
// This flag is set when there is no file descriptor in the ancillary data. This signals
|
||||
// that polling will be used instead of waiting for the call.
|
||||
let msg = VhostUserU64::new(queue_index as u64);
|
||||
let hdr = Self::new_request_header(code, mem::size_of::<VhostUserU64>() as u32);
|
||||
self.main_sock.send_message(&hdr, &msg, Some(&[fd]))?;
|
||||
Ok(hdr)
|
||||
}
|
||||
|
||||
fn recv_reply<T: Sized + Default + VhostUserMsgValidator>(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
) -> VhostUserResult<T> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE || hdr.is_reply() {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let (reply, body, rfds) = self.main_sock.recv_body::<T>()?;
|
||||
if !reply.is_reply_for(&hdr) || rfds.is_some() || !body.is_valid() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(VhostUserError::InvalidMessage);
|
||||
}
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
fn recv_reply_with_payload<T: Sized + Default + VhostUserMsgValidator>(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
) -> VhostUserResult<(T, Vec<u8>, Option<Vec<RawFd>>)> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE || hdr.is_reply() {
|
||||
return Err(VhostUserError::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let mut buf: Vec<u8> = vec![0; hdr.get_size() as usize - mem::size_of::<T>()];
|
||||
let (reply, body, bytes, rfds) = self.main_sock.recv_payload_into_buf::<T>(&mut buf)?;
|
||||
if !reply.is_reply_for(hdr)
|
||||
|| reply.get_size() as usize != mem::size_of::<T>() + bytes
|
||||
|| rfds.is_some()
|
||||
|| !body.is_valid()
|
||||
{
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(VhostUserError::InvalidMessage);
|
||||
} else if bytes > MAX_MSG_SIZE - mem::size_of::<T>() {
|
||||
return Err(VhostUserError::InvalidMessage);
|
||||
} else if bytes < buf.len() {
|
||||
// It's safe because we have checked the buffer size
|
||||
unsafe { buf.set_len(bytes) };
|
||||
}
|
||||
Ok((body, buf, rfds))
|
||||
}
|
||||
|
||||
fn wait_for_ack(&mut self, hdr: &VhostUserMsgHeader<MasterReq>) -> VhostUserResult<()> {
|
||||
if self.acked_protocol_features & VhostUserProtocolFeatures::REPLY_ACK.bits() == 0
|
||||
|| !hdr.is_need_reply()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
self.check_state()?;
|
||||
|
||||
let (reply, body, rfds) = self.main_sock.recv_body::<VhostUserU64>()?;
|
||||
if !reply.is_reply_for(&hdr) || rfds.is_some() || !body.is_valid() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(VhostUserError::InvalidMessage);
|
||||
}
|
||||
if body.value != 0 {
|
||||
return Err(VhostUserError::SlaveInternalError);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_feature_mq_available(&self) -> bool {
|
||||
self.acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() != 0
|
||||
}
|
||||
|
||||
fn check_state(&self) -> VhostUserResult<()> {
|
||||
match self.error {
|
||||
Some(e) => Err(VhostUserError::SocketBroken(
|
||||
std::io::Error::from_raw_os_error(e),
|
||||
)),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_request_header(request: MasterReq, size: u32) -> VhostUserMsgHeader<MasterReq> {
|
||||
// TODO: handle NEED_REPLY flag
|
||||
VhostUserMsgHeader::new(request, 0x1, size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::connection::Listener;
|
||||
use super::*;
|
||||
|
||||
const UNIX_SOCKET_MASTER: &'static str = "/tmp/vhost_user_test_rust_master";
|
||||
const UNIX_SOCKET_MASTER2: &'static str = "/tmp/vhost_user_test_rust_master2";
|
||||
const UNIX_SOCKET_MASTER3: &'static str = "/tmp/vhost_user_test_rust_master3";
|
||||
const UNIX_SOCKET_MASTER4: &'static str = "/tmp/vhost_user_test_rust_master4";
|
||||
|
||||
fn create_pair(path: &str) -> (Master, Endpoint<MasterReq>) {
|
||||
let listener = Listener::new(path, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
let master = Master::connect(path, 2).unwrap();
|
||||
let slave = listener.accept().unwrap().unwrap();
|
||||
(master, Endpoint::from_stream(slave))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn create_master() {
|
||||
let listener = Listener::new(UNIX_SOCKET_MASTER, true).unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
|
||||
let mut master = Master::connect(UNIX_SOCKET_MASTER, 2).unwrap();
|
||||
let mut slave = Endpoint::<MasterReq>::from_stream(listener.accept().unwrap().unwrap());
|
||||
|
||||
// Send two messages continuously
|
||||
master.set_owner().unwrap();
|
||||
master.reset_owner().unwrap();
|
||||
|
||||
let (hdr, rfds) = slave.recv_header().unwrap();
|
||||
assert_eq!(hdr.get_code(), MasterReq::SET_OWNER);
|
||||
assert_eq!(hdr.get_size(), 0);
|
||||
assert_eq!(hdr.get_version(), 0x1);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
let (hdr, rfds) = slave.recv_header().unwrap();
|
||||
assert_eq!(hdr.get_code(), MasterReq::RESET_OWNER);
|
||||
assert_eq!(hdr.get_size(), 0);
|
||||
assert_eq!(hdr.get_version(), 0x1);
|
||||
assert!(rfds.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_create_failure() {
|
||||
let _ = Listener::new(UNIX_SOCKET_MASTER2, true).unwrap();
|
||||
let _ = Listener::new(UNIX_SOCKET_MASTER2, false).is_err();
|
||||
assert!(Master::connect(UNIX_SOCKET_MASTER2, 2).is_err());
|
||||
|
||||
let listener = Listener::new(UNIX_SOCKET_MASTER2, true).unwrap();
|
||||
assert!(Listener::new(UNIX_SOCKET_MASTER2, false).is_err());
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
|
||||
let _master = Master::connect(UNIX_SOCKET_MASTER2, 2).unwrap();
|
||||
let _slave = listener.accept().unwrap().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_features() {
|
||||
let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER3);
|
||||
|
||||
master.set_owner().unwrap();
|
||||
let (hdr, rfds) = peer.recv_header().unwrap();
|
||||
assert_eq!(hdr.get_code(), MasterReq::SET_OWNER);
|
||||
assert_eq!(hdr.get_size(), 0);
|
||||
assert_eq!(hdr.get_version(), 0x1);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
let hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0x4, 8);
|
||||
let msg = VhostUserU64::new(0x15);
|
||||
peer.send_message(&hdr, &msg, None).unwrap();
|
||||
let features = master.get_features().unwrap();
|
||||
assert_eq!(features, 0x15u64);
|
||||
let (_hdr, rfds) = peer.recv_header().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
|
||||
master.set_features(0x15).unwrap();
|
||||
let (_hdr, msg, rfds) = peer.recv_body::<VhostUserU64>().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
let val = msg.value;
|
||||
assert_eq!(val, 0x15);
|
||||
|
||||
let hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0x4, 8);
|
||||
let msg = 0x15u32;
|
||||
peer.send_message(&hdr, &msg, None).unwrap();
|
||||
assert!(master.get_features().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_protocol_features() {
|
||||
let (mut master, mut peer) = create_pair(UNIX_SOCKET_MASTER4);
|
||||
|
||||
master.set_owner().unwrap();
|
||||
let (hdr, rfds) = peer.recv_header().unwrap();
|
||||
assert_eq!(hdr.get_code(), MasterReq::SET_OWNER);
|
||||
assert!(rfds.is_none());
|
||||
|
||||
assert!(master.get_protocol_features().is_err());
|
||||
assert!(master
|
||||
.set_protocol_features(VhostUserProtocolFeatures::all())
|
||||
.is_err());
|
||||
|
||||
let vfeatures = 0x15 | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
let hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0x4, 8);
|
||||
let msg = VhostUserU64::new(vfeatures);
|
||||
peer.send_message(&hdr, &msg, None).unwrap();
|
||||
let features = master.get_features().unwrap();
|
||||
assert_eq!(features, vfeatures);
|
||||
let (_hdr, rfds) = peer.recv_header().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
|
||||
master.set_features(vfeatures).unwrap();
|
||||
let (_hdr, msg, rfds) = peer.recv_body::<VhostUserU64>().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
let val = msg.value;
|
||||
assert_eq!(val, vfeatures);
|
||||
|
||||
let pfeatures = VhostUserProtocolFeatures::all();
|
||||
let hdr = VhostUserMsgHeader::new(MasterReq::GET_PROTOCOL_FEATURES, 0x4, 8);
|
||||
let msg = VhostUserU64::new(pfeatures.bits());
|
||||
peer.send_message(&hdr, &msg, None).unwrap();
|
||||
let features = master.get_protocol_features().unwrap();
|
||||
assert_eq!(features, pfeatures);
|
||||
let (_hdr, rfds) = peer.recv_header().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
|
||||
master.set_protocol_features(pfeatures).unwrap();
|
||||
let (_hdr, msg, rfds) = peer.recv_body::<VhostUserU64>().unwrap();
|
||||
assert!(rfds.is_none());
|
||||
let val = msg.value;
|
||||
assert_eq!(val, pfeatures.bits());
|
||||
|
||||
let hdr = VhostUserMsgHeader::new(MasterReq::SET_PROTOCOL_FEATURES, 0x4, 8);
|
||||
let msg = VhostUserU64::new(pfeatures.bits());
|
||||
peer.send_message(&hdr, &msg, None).unwrap();
|
||||
assert!(master.get_protocol_features().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_mem_table() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_ring_num() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Traits and Structs to handle vhost-user requests from the slave to the master.
|
||||
|
||||
use libc;
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::connection::Endpoint;
|
||||
use super::message::*;
|
||||
use super::{Error, HandlerResult, Result};
|
||||
|
||||
/// Trait to handle vhost-user requests from the slave to the master.
|
||||
pub trait VhostUserMasterReqHandler {
|
||||
// fn handle_iotlb_msg(&mut self, iotlb: VhostUserIotlb);
|
||||
// fn handle_vring_host_notifier(&mut self, area: VhostUserVringArea, fd: RawFd);
|
||||
|
||||
/// Handle device configuration change notifications from the slave.
|
||||
fn handle_config_change(&mut self) -> HandlerResult<()> {
|
||||
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
}
|
||||
|
||||
/// Handle virtio-fs map file requests from the slave.
|
||||
fn fs_slave_map(&mut self, _fs: &VhostUserFSSlaveMsg, fd: RawFd) -> HandlerResult<()> {
|
||||
// Safe because we have just received the rawfd from kernel.
|
||||
unsafe { libc::close(fd) };
|
||||
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
}
|
||||
|
||||
/// Handle virtio-fs unmap file requests from the slave.
|
||||
fn fs_slave_unmap(&mut self, _fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
|
||||
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
}
|
||||
|
||||
/// Handle virtio-fs sync file requests from the slave.
|
||||
fn fs_slave_sync(&mut self, _fs: &VhostUserFSSlaveMsg) -> HandlerResult<()> {
|
||||
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
|
||||
}
|
||||
}
|
||||
|
||||
/// A vhost-user master request endpoint which relays all received requests from the slave to the
|
||||
/// provided request handler.
|
||||
pub struct MasterReqHandler<S: VhostUserMasterReqHandler> {
|
||||
// underlying Unix domain socket for communication
|
||||
sub_sock: Endpoint<SlaveReq>,
|
||||
tx_sock: UnixStream,
|
||||
// the VirtIO backend device object
|
||||
backend: Arc<Mutex<S>>,
|
||||
// whether the endpoint has encountered any failure
|
||||
error: Option<i32>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
|
||||
/// Create a vhost-user slave request handler.
|
||||
/// This opens a pair of connected anonymous sockets.
|
||||
/// Returns Self and the socket that must be sent to the slave via SET_SLAVE_REQ_FD.
|
||||
pub fn new(backend: Arc<Mutex<S>>) -> Result<Self> {
|
||||
let (tx, rx) = UnixStream::pair().map_err(Error::SocketError)?;
|
||||
|
||||
Ok(MasterReqHandler {
|
||||
sub_sock: Endpoint::<SlaveReq>::from_stream(rx),
|
||||
tx_sock: tx,
|
||||
backend,
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the raw fd to send to the slave as slave communication channel.
|
||||
pub fn get_tx_raw_fd(&self) -> RawFd {
|
||||
self.tx_sock.as_raw_fd()
|
||||
}
|
||||
|
||||
/// Mark endpoint as failed or normal state.
|
||||
pub fn set_failed(&mut self, error: i32) {
|
||||
self.error = Some(error);
|
||||
}
|
||||
|
||||
/// Receive and handle one incoming request message from the slave.
|
||||
/// The caller needs to:
|
||||
/// . serialize calls to this function
|
||||
/// . decide what to do when errer happens
|
||||
/// . optional recover from failure
|
||||
pub fn handle_request(&mut self) -> Result<()> {
|
||||
// Return error if the endpoint is already in failed state.
|
||||
self.check_state()?;
|
||||
|
||||
// The underlying communication channel is a Unix domain socket in
|
||||
// stream mode, and recvmsg() is a little tricky here. To successfully
|
||||
// receive attached file descriptors, we need to receive messages and
|
||||
// corresponding attached file descriptors in this way:
|
||||
// . recv messsage header and optional attached file
|
||||
// . validate message header
|
||||
// . recv optional message body and payload according size field in
|
||||
// message header
|
||||
// . validate message body and optional payload
|
||||
let (hdr, rfds) = self.sub_sock.recv_header()?;
|
||||
let rfds = self.check_attached_rfds(&hdr, rfds)?;
|
||||
let (size, buf) = match hdr.get_size() {
|
||||
0 => (0, vec![0u8; 0]),
|
||||
len => {
|
||||
let (size2, rbuf) = self.sub_sock.recv_data(len as usize)?;
|
||||
if size2 != len as usize {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
(size2, rbuf)
|
||||
}
|
||||
};
|
||||
|
||||
let res = match hdr.get_code() {
|
||||
SlaveReq::CONFIG_CHANGE_MSG => {
|
||||
self.check_msg_size(&hdr, size, 0)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.handle_config_change()
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_MAP => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_map(msg, rfds.unwrap()[0])
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_UNMAP => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_unmap(msg)
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
SlaveReq::FS_SYNC => {
|
||||
let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.fs_slave_sync(msg)
|
||||
.map_err(Error::ReqHandlerError)
|
||||
}
|
||||
_ => Err(Error::InvalidMessage),
|
||||
};
|
||||
|
||||
self.send_ack_message(&hdr, &res)?;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn check_state(&self) -> Result<()> {
|
||||
match self.error {
|
||||
Some(e) => Err(Error::SocketBroken(std::io::Error::from_raw_os_error(e))),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_msg_size(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<SlaveReq>,
|
||||
size: usize,
|
||||
expected: usize,
|
||||
) -> Result<()> {
|
||||
if hdr.get_size() as usize != expected
|
||||
|| hdr.is_reply()
|
||||
|| hdr.get_version() != 0x1
|
||||
|| size != expected
|
||||
{
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_attached_rfds(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<SlaveReq>,
|
||||
rfds: Option<Vec<RawFd>>,
|
||||
) -> Result<Option<Vec<RawFd>>> {
|
||||
match hdr.get_code() {
|
||||
SlaveReq::FS_MAP => {
|
||||
// Expect an fd set with a single fd.
|
||||
match rfds {
|
||||
None => Err(Error::InvalidMessage),
|
||||
Some(fds) => {
|
||||
if fds.len() != 1 {
|
||||
Endpoint::<SlaveReq>::close_rfds(Some(fds));
|
||||
Err(Error::InvalidMessage)
|
||||
} else {
|
||||
Ok(Some(fds))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if rfds.is_some() {
|
||||
Endpoint::<SlaveReq>::close_rfds(rfds);
|
||||
Err(Error::InvalidMessage)
|
||||
} else {
|
||||
Ok(rfds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_msg_body<'a, T: Sized + VhostUserMsgValidator>(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<SlaveReq>,
|
||||
size: usize,
|
||||
buf: &'a [u8],
|
||||
) -> Result<&'a T> {
|
||||
self.check_msg_size(hdr, size, mem::size_of::<T>())?;
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const T) };
|
||||
if !msg.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
fn new_reply_header<T: Sized>(
|
||||
&self,
|
||||
req: &VhostUserMsgHeader<SlaveReq>,
|
||||
) -> Result<VhostUserMsgHeader<SlaveReq>> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
Ok(VhostUserMsgHeader::new(
|
||||
req.get_code(),
|
||||
VhostUserHeaderFlag::REPLY.bits(),
|
||||
mem::size_of::<T>() as u32,
|
||||
))
|
||||
}
|
||||
|
||||
fn send_ack_message(
|
||||
&mut self,
|
||||
req: &VhostUserMsgHeader<SlaveReq>,
|
||||
res: &Result<()>,
|
||||
) -> Result<()> {
|
||||
if req.is_need_reply() {
|
||||
let hdr = self.new_reply_header::<VhostUserU64>(req)?;
|
||||
let val = match res {
|
||||
Ok(_) => 0,
|
||||
Err(_) => 1,
|
||||
};
|
||||
let msg = VhostUserU64::new(val);
|
||||
self.sub_sock.send_message(&hdr, &msg, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserMasterReqHandler> AsRawFd for MasterReqHandler<S> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.sub_sock.as_raw_fd()
|
||||
}
|
||||
}
|
||||
@@ -1,812 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Define communication messages for the vhost-user protocol.
|
||||
//!
|
||||
//! For message definition, please refer to the [vhost-user spec](https://github.com/qemu/qemu/blob/f7526eece29cd2e36a63b6703508b24453095eb8/docs/interop/vhost-user.txt).
|
||||
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use VringConfigData;
|
||||
|
||||
/// The vhost-user specification uses a field of u32 to store message length.
|
||||
/// On the other hand, preallocated buffers are needed to receive messages from the Unix domain
|
||||
/// socket. To preallocating a 4GB buffer for each vhost-user message is really just an overhead.
|
||||
/// Among all defined vhost-user messages, only the VhostUserConfig and VhostUserMemory has variable
|
||||
/// message size. For the VhostUserConfig, a maximum size of 4K is enough because the user
|
||||
/// configuration space for virtio devices is (4K - 0x100) bytes at most. For the VhostUserMemory,
|
||||
/// 4K should be enough too because it can support 255 memory regions at most.
|
||||
pub const MAX_MSG_SIZE: usize = 0x1000;
|
||||
|
||||
/// The VhostUserMemory message has variable message size and variable number of attached file
|
||||
/// descriptors. Each user memory region entry in the message payload occupies 32 bytes,
|
||||
/// so setting maximum number of attached file descriptors based on the maximum message size.
|
||||
/// But rust only implements Default and AsMut traits for arrays with 0 - 32 entries, so further
|
||||
/// reduce the maximum number...
|
||||
// pub const MAX_ATTACHED_FD_ENTRIES: usize = (MAX_MSG_SIZE - 8) / 32;
|
||||
pub const MAX_ATTACHED_FD_ENTRIES: usize = 32;
|
||||
|
||||
/// Starting position (inclusion) of the device configuration space in virtio devices.
|
||||
pub const VHOST_USER_CONFIG_OFFSET: u32 = 0x100;
|
||||
|
||||
/// Ending position (exclusion) of the device configuration space in virtio devices.
|
||||
pub const VHOST_USER_CONFIG_SIZE: u32 = 0x1000;
|
||||
|
||||
/// Maximum number of vrings supported.
|
||||
pub const VHOST_USER_MAX_VRINGS: u64 = 0xFFu64;
|
||||
|
||||
pub(super) trait Req:
|
||||
Clone + Copy + Debug + PartialEq + Eq + PartialOrd + Ord + Into<u32>
|
||||
{
|
||||
fn is_valid(&self) -> bool;
|
||||
}
|
||||
|
||||
/// Type of requests sending from masters to slaves.
|
||||
#[repr(u32)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum MasterReq {
|
||||
/// Null operation.
|
||||
NOOP = 0,
|
||||
/// Get from the underlying vhost implementation the features bit mask.
|
||||
GET_FEATURES = 1,
|
||||
/// Enable features in the underlying vhost implementation using a bit mask.
|
||||
SET_FEATURES = 2,
|
||||
/// Set the current Master as an owner of the session.
|
||||
SET_OWNER = 3,
|
||||
/// No longer used.
|
||||
RESET_OWNER = 4,
|
||||
/// Set the memory map regions on the slave so it can translate the vring addresses.
|
||||
SET_MEM_TABLE = 5,
|
||||
/// Set logging shared memory space.
|
||||
SET_LOG_BASE = 6,
|
||||
/// Set the logging file descriptor, which is passed as ancillary data.
|
||||
SET_LOG_FD = 7,
|
||||
/// Set the size of the queue.
|
||||
SET_VRING_NUM = 8,
|
||||
/// Set the addresses of the different aspects of the vring.
|
||||
SET_VRING_ADDR = 9,
|
||||
/// Set the base offset in the available vring.
|
||||
SET_VRING_BASE = 10,
|
||||
/// Get the available vring base offset.
|
||||
GET_VRING_BASE = 11,
|
||||
/// Set the event file descriptor for adding buffers to the vring.
|
||||
SET_VRING_KICK = 12,
|
||||
/// Set the event file descriptor to signal when buffers are used.
|
||||
SET_VRING_CALL = 13,
|
||||
/// Set the event file descriptor to signal when error occurs.
|
||||
SET_VRING_ERR = 14,
|
||||
/// Get the protocol feature bit mask from the underlying vhost implementation.
|
||||
GET_PROTOCOL_FEATURES = 15,
|
||||
/// Enable protocol features in the underlying vhost implementation.
|
||||
SET_PROTOCOL_FEATURES = 16,
|
||||
/// Query how many queues the backend supports.
|
||||
GET_QUEUE_NUM = 17,
|
||||
/// Signal slave to enable or disable corresponding vring.
|
||||
SET_VRING_ENABLE = 18,
|
||||
/// Ask vhost user backend to broadcast a fake RARP to notify the migration is terminated
|
||||
/// for guest that does not support GUEST_ANNOUNCE.
|
||||
SEND_RARP = 19,
|
||||
/// Set host MTU value exposed to the guest.
|
||||
NET_SET_MTU = 20,
|
||||
/// Set the socket file descriptor for slave initiated requests.
|
||||
SET_SLAVE_REQ_FD = 21,
|
||||
/// Send IOTLB messages with struct vhost_iotlb_msg as payload.
|
||||
IOTLB_MSG = 22,
|
||||
/// Set the endianness of a VQ for legacy devices.
|
||||
SET_VRING_ENDIAN = 23,
|
||||
/// Fetch the contents of the virtio device configuration space.
|
||||
GET_CONFIG = 24,
|
||||
/// Change the contents of the virtio device configuration space.
|
||||
SET_CONFIG = 25,
|
||||
/// Create a session for crypto operation.
|
||||
CREATE_CRYPTO_SESSION = 26,
|
||||
/// Close a session for crypto operation.
|
||||
CLOSE_CRYPTO_SESSION = 27,
|
||||
/// Advise slave that a migration with postcopy enabled is underway.
|
||||
POSTCOPY_ADVISE = 28,
|
||||
/// Advise slave that a transition to postcopy mode has happened.
|
||||
POSTCOPY_LISTEN = 29,
|
||||
/// Advise that postcopy migration has now completed.
|
||||
POSTCOPY_END = 30,
|
||||
/// Get a shared buffer from slave.
|
||||
GET_INFLIGHT_FD = 31,
|
||||
/// Send the shared inflight buffer back to slave
|
||||
SET_INFLIGHT_FD = 32,
|
||||
/// Upper bound of valid commands.
|
||||
MAX_CMD = 33,
|
||||
}
|
||||
|
||||
impl Into<u32> for MasterReq {
|
||||
fn into(self) -> u32 {
|
||||
self as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl Req for MasterReq {
|
||||
fn is_valid(&self) -> bool {
|
||||
(*self > MasterReq::NOOP) && (*self < MasterReq::MAX_CMD)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of requests sending from slaves to masters.
|
||||
#[repr(u32)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SlaveReq {
|
||||
/// Null operation.
|
||||
NOOP = 0,
|
||||
/// Send IOTLB messages with struct vhost_iotlb_msg as payload.
|
||||
IOTLB_MSG = 1,
|
||||
/// Notify that the virtio device's configuration space has changed.
|
||||
CONFIG_CHANGE_MSG = 2,
|
||||
/// Set host notifier for a specified queue.
|
||||
VRING_HOST_NOTIFIER_MSG = 3,
|
||||
/// Virtio-fs draft: map file content into the window.
|
||||
FS_MAP = 4,
|
||||
/// Virtio-fs draft: unmap file content from the window.
|
||||
FS_UNMAP = 5,
|
||||
/// Virtio-fs draft: sync file content.
|
||||
FS_SYNC = 6,
|
||||
/// Upper bound of valid commands.
|
||||
MAX_CMD = 7,
|
||||
}
|
||||
|
||||
impl Into<u32> for SlaveReq {
|
||||
fn into(self) -> u32 {
|
||||
self as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl Req for SlaveReq {
|
||||
fn is_valid(&self) -> bool {
|
||||
(*self > SlaveReq::NOOP) && (*self < SlaveReq::MAX_CMD)
|
||||
}
|
||||
}
|
||||
|
||||
/// Vhost message Validator.
|
||||
pub trait VhostUserMsgValidator {
|
||||
/// Validate message syntax only.
|
||||
/// It doesn't validate message semantics such as protocol version number and dependency
|
||||
/// on feature flags etc.
|
||||
fn is_valid(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Common message flags for vhost-user requests and replies.
|
||||
pub struct VhostUserHeaderFlag: u32 {
|
||||
/// Bits[0..2] is message version number.
|
||||
const VERSION = 0x3;
|
||||
/// Mark message as reply.
|
||||
const REPLY = 0x4;
|
||||
/// Sender anticipates a reply message from the peer.
|
||||
const NEED_REPLY = 0x8;
|
||||
/// All valid bits.
|
||||
const ALL_FLAGS = 0xc;
|
||||
/// All reserved bits.
|
||||
const RESERVED_BITS = !0xf;
|
||||
}
|
||||
}
|
||||
|
||||
/// Common message header for vhost-user requests and replies.
|
||||
/// A vhost-user message consists of 3 header fields and an optional payload. All numbers are in the
|
||||
/// machine native byte order.
|
||||
#[allow(safe_packed_borrows)]
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(super) struct VhostUserMsgHeader<R: Req> {
|
||||
request: u32,
|
||||
flags: u32,
|
||||
size: u32,
|
||||
_r: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<R: Req> VhostUserMsgHeader<R> {
|
||||
/// Create a new instance of `VhostUserMsgHeader`.
|
||||
pub fn new(request: R, flags: u32, size: u32) -> Self {
|
||||
// Default to protocol version 1
|
||||
let fl = (flags & VhostUserHeaderFlag::ALL_FLAGS.bits()) | 0x1;
|
||||
VhostUserMsgHeader {
|
||||
request: request.into(),
|
||||
flags: fl,
|
||||
size,
|
||||
_r: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get message type.
|
||||
pub fn get_code(&self) -> R {
|
||||
// It's safe because R is marked as repr(u32).
|
||||
unsafe { std::mem::transmute_copy::<u32, R>(&self.request) }
|
||||
}
|
||||
|
||||
/// Set message type.
|
||||
pub fn set_code(&mut self, request: R) {
|
||||
self.request = request.into();
|
||||
}
|
||||
|
||||
/// Get message version number.
|
||||
pub fn get_version(&self) -> u32 {
|
||||
self.flags & 0x3
|
||||
}
|
||||
|
||||
/// Set message version number.
|
||||
pub fn set_version(&mut self, ver: u32) {
|
||||
self.flags &= !0x3;
|
||||
self.flags |= ver & 0x3;
|
||||
}
|
||||
|
||||
/// Check whether it's a reply message.
|
||||
pub fn is_reply(&self) -> bool {
|
||||
(self.flags & VhostUserHeaderFlag::REPLY.bits()) != 0
|
||||
}
|
||||
|
||||
/// Mark message as reply.
|
||||
pub fn set_reply(&mut self, is_reply: bool) {
|
||||
if is_reply {
|
||||
self.flags |= VhostUserHeaderFlag::REPLY.bits();
|
||||
} else {
|
||||
self.flags &= !VhostUserHeaderFlag::REPLY.bits();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether reply for this message is requested.
|
||||
pub fn is_need_reply(&self) -> bool {
|
||||
(self.flags & VhostUserHeaderFlag::NEED_REPLY.bits()) != 0
|
||||
}
|
||||
|
||||
/// Mark that reply for this message is needed.
|
||||
pub fn set_need_reply(&mut self, need_reply: bool) {
|
||||
if need_reply {
|
||||
self.flags |= VhostUserHeaderFlag::NEED_REPLY.bits();
|
||||
} else {
|
||||
self.flags &= !VhostUserHeaderFlag::NEED_REPLY.bits();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether it's the reply message for the request `req`.
|
||||
pub fn is_reply_for(&self, req: &VhostUserMsgHeader<R>) -> bool {
|
||||
self.is_reply() && !req.is_reply() && self.get_code() == req.get_code()
|
||||
}
|
||||
|
||||
/// Get message size.
|
||||
pub fn get_size(&self) -> u32 {
|
||||
self.size
|
||||
}
|
||||
|
||||
/// Set message size.
|
||||
pub fn set_size(&mut self, size: u32) {
|
||||
self.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Req> Default for VhostUserMsgHeader<R> {
|
||||
fn default() -> Self {
|
||||
VhostUserMsgHeader {
|
||||
request: 0,
|
||||
flags: 0x1,
|
||||
size: 0,
|
||||
_r: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Req> VhostUserMsgValidator for VhostUserMsgHeader<T> {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn is_valid(&self) -> bool {
|
||||
if !self.get_code().is_valid() {
|
||||
return false;
|
||||
} else if self.size as usize > MAX_MSG_SIZE {
|
||||
return false;
|
||||
} else if self.get_version() != 0x1 {
|
||||
return false;
|
||||
} else if (self.flags & VhostUserHeaderFlag::RESERVED_BITS.bits()) != 0 {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Transport specific flags in VirtIO feature set defined by vhost-user.
|
||||
pub struct VhostUserVirtioFeatures: u64 {
|
||||
/// Feature flag for the protocol feature.
|
||||
const PROTOCOL_FEATURES = 0x4000_0000;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Vhost-user protocol feature flags.
|
||||
pub struct VhostUserProtocolFeatures: u64 {
|
||||
/// Support multiple queues.
|
||||
const MQ = 0x0000_0001;
|
||||
/// Support logging through shared memory fd.
|
||||
const LOG_SHMFD = 0x0000_0002;
|
||||
/// Support broadcasting fake RARP packet.
|
||||
const RARP = 0x0000_0004;
|
||||
/// Support sending reply messages for requests with NEED_REPLY flag set.
|
||||
const REPLY_ACK = 0x0000_0008;
|
||||
/// Support setting MTU for virtio-net devices.
|
||||
const MTU = 0x0000_0010;
|
||||
/// Allow the slave to send requests to the master by an optional communication channel.
|
||||
const SLAVE_REQ = 0x0000_0020;
|
||||
/// Support setting slave endian by SET_VRING_ENDIAN.
|
||||
const CROSS_ENDIAN = 0x0000_0040;
|
||||
/// Support crypto operations.
|
||||
const CRYPTO_SESSION = 0x0000_0080;
|
||||
/// Support sending userfault_fd from slaves to masters.
|
||||
const PAGEFAULT = 0x0000_0100;
|
||||
/// Support Virtio device configuration.
|
||||
const CONFIG = 0x0000_0200;
|
||||
/// Allow the slave to send fds (at most 8 descriptors in each message) to the master.
|
||||
const SLAVE_SEND_FD = 0x0000_0400;
|
||||
/// Allow the slave to register a host notifier.
|
||||
const HOST_NOTIFIER = 0x0000_0800;
|
||||
/// Support inflight shmfd.
|
||||
const INFLIGHT_SHMFD = 0x0000_1000;
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic message to encapsulate a 64-bit value.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserU64 {
|
||||
/// The encapsulated 64-bit common value.
|
||||
pub value: u64,
|
||||
}
|
||||
|
||||
impl VhostUserU64 {
|
||||
/// Create a new instance.
|
||||
pub fn new(value: u64) -> Self {
|
||||
VhostUserU64 { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserU64 {}
|
||||
|
||||
/// Memory region descriptor for the SET_MEM_TABLE request.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserMemory {
|
||||
/// Number of memory regions in the payload.
|
||||
pub num_regions: u32,
|
||||
/// Padding for alignment.
|
||||
pub padding1: u32,
|
||||
}
|
||||
|
||||
impl VhostUserMemory {
|
||||
/// Create a new instance.
|
||||
pub fn new(cnt: u32) -> Self {
|
||||
VhostUserMemory {
|
||||
num_regions: cnt,
|
||||
padding1: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserMemory {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn is_valid(&self) -> bool {
|
||||
if self.padding1 != 0 {
|
||||
return false;
|
||||
} else if self.num_regions == 0 || self.num_regions > MAX_ATTACHED_FD_ENTRIES as u32 {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory region descriptors as payload for the SET_MEM_TABLE request.
|
||||
#[repr(packed)]
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct VhostUserMemoryRegion {
|
||||
/// Guest physical address of the memory region.
|
||||
pub guest_phys_addr: u64,
|
||||
/// Size of the memory region.
|
||||
pub memory_size: u64,
|
||||
/// Virtual address in the current process.
|
||||
pub user_addr: u64,
|
||||
/// Offset where region starts in the mapped memory.
|
||||
pub mmap_offset: u64,
|
||||
}
|
||||
|
||||
impl VhostUserMemoryRegion {
|
||||
/// Create a new instance.
|
||||
pub fn new(guest_phys_addr: u64, memory_size: u64, user_addr: u64, mmap_offset: u64) -> Self {
|
||||
VhostUserMemoryRegion {
|
||||
guest_phys_addr,
|
||||
memory_size,
|
||||
user_addr,
|
||||
mmap_offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserMemoryRegion {
|
||||
fn is_valid(&self) -> bool {
|
||||
if self.memory_size == 0
|
||||
|| self.guest_phys_addr.checked_add(self.memory_size).is_none()
|
||||
|| self.user_addr.checked_add(self.memory_size).is_none()
|
||||
|| self.mmap_offset.checked_add(self.memory_size).is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload of the VhostUserMemory message.
|
||||
pub type VhostUserMemoryPayload = Vec<VhostUserMemoryRegion>;
|
||||
|
||||
/// Vring state descriptor.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserVringState {
|
||||
/// Vring index.
|
||||
pub index: u32,
|
||||
/// A common 32bit value to encapsulate vring state etc.
|
||||
pub num: u32,
|
||||
}
|
||||
|
||||
impl VhostUserVringState {
|
||||
/// Create a new instance.
|
||||
pub fn new(index: u32, num: u32) -> Self {
|
||||
VhostUserVringState { index, num }
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserVringState {}
|
||||
|
||||
bitflags! {
|
||||
/// Flags for vring address.
|
||||
pub struct VhostUserVringAddrFlags: u32 {
|
||||
/// Support log of vring operations.
|
||||
/// Modifications to "used" vring should be logged.
|
||||
const VHOST_VRING_F_LOG = 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Vring address descriptor.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserVringAddr {
|
||||
/// Vring index.
|
||||
pub index: u32,
|
||||
/// Vring flags defined by VhostUserVringAddrFlags.
|
||||
pub flags: u32,
|
||||
/// Ring address of the vring descriptor table.
|
||||
pub descriptor: u64,
|
||||
/// Ring address of the vring used ring.
|
||||
pub used: u64,
|
||||
/// Ring address of the vring available ring.
|
||||
pub available: u64,
|
||||
/// Guest address for logging.
|
||||
pub log: u64,
|
||||
}
|
||||
|
||||
impl VhostUserVringAddr {
|
||||
/// Create a new instance.
|
||||
pub fn new(
|
||||
index: u32,
|
||||
flags: VhostUserVringAddrFlags,
|
||||
descriptor: u64,
|
||||
used: u64,
|
||||
available: u64,
|
||||
log: u64,
|
||||
) -> Self {
|
||||
VhostUserVringAddr {
|
||||
index,
|
||||
flags: flags.bits(),
|
||||
descriptor,
|
||||
used,
|
||||
available,
|
||||
log,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new instance from `VringConfigData`.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::identity_conversion))]
|
||||
pub fn from_config_data(index: u32, config_data: &VringConfigData) -> Self {
|
||||
let log_addr = config_data.log_addr.unwrap_or(0);
|
||||
VhostUserVringAddr {
|
||||
index,
|
||||
flags: config_data.flags,
|
||||
descriptor: config_data.desc_table_addr,
|
||||
used: config_data.used_ring_addr,
|
||||
available: config_data.avail_ring_addr,
|
||||
log: log_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserVringAddr {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn is_valid(&self) -> bool {
|
||||
if (self.flags & !VhostUserVringAddrFlags::all().bits()) != 0 {
|
||||
return false;
|
||||
} else if self.descriptor & 0xf != 0 {
|
||||
return false;
|
||||
} else if self.available & 0x1 != 0 {
|
||||
return false;
|
||||
} else if self.used & 0x3 != 0 {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Flags for the device configuration message.
|
||||
pub struct VhostUserConfigFlags: u32 {
|
||||
/// Vhost master messages used for writeable fields.
|
||||
const WRITABLE = 0x0;
|
||||
/// Vhost master messages used for live migration.
|
||||
const LIVE_MIGRATION = 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Message to read/write device configuration space.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserConfig {
|
||||
/// Offset of virtio device's configuration space.
|
||||
pub offset: u32,
|
||||
/// Configuration space access size in bytes.
|
||||
pub size: u32,
|
||||
/// Flags for the device configuration operation.
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
impl VhostUserConfig {
|
||||
/// Create a new instance.
|
||||
pub fn new(offset: u32, size: u32, flags: VhostUserConfigFlags) -> Self {
|
||||
VhostUserConfig {
|
||||
offset,
|
||||
size,
|
||||
flags: flags.bits(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserConfig {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn is_valid(&self) -> bool {
|
||||
if (self.flags & !VhostUserConfigFlags::all().bits()) != 0 {
|
||||
return false;
|
||||
} else if self.offset >= VHOST_USER_CONFIG_SIZE
|
||||
|| self.size == 0
|
||||
|| self.size > VHOST_USER_CONFIG_SIZE
|
||||
|| self.size + self.offset > VHOST_USER_CONFIG_SIZE
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload for the VhostUserConfig message.
|
||||
pub type VhostUserConfigPayload = Vec<u8>;
|
||||
|
||||
/*
|
||||
* TODO: support dirty log, live migration and IOTLB operations.
|
||||
#[repr(packed)]
|
||||
pub struct VhostUserVringArea {
|
||||
pub index: u32,
|
||||
pub flags: u32,
|
||||
pub size: u64,
|
||||
pub offset: u64,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct VhostUserLog {
|
||||
pub size: u64,
|
||||
pub offset: u64,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct VhostUserIotlb {
|
||||
pub iova: u64,
|
||||
pub size: u64,
|
||||
pub user_addr: u64,
|
||||
pub permission: u8,
|
||||
pub optype: u8,
|
||||
}
|
||||
*/
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
/// Flags for virtio-fs slave messages.
|
||||
pub struct VhostUserFSSlaveMsgFlags: u64 {
|
||||
/// Empty permission.
|
||||
const EMPTY = 0x0;
|
||||
/// Read permission.
|
||||
const MAP_R = 0x1;
|
||||
/// Write permission.
|
||||
const MAP_W = 0x2;
|
||||
}
|
||||
}
|
||||
|
||||
/// Max entries in one virtio-fs slave request.
|
||||
pub const VHOST_USER_FS_SLAVE_ENTRIES: usize = 8;
|
||||
|
||||
/// Slave request message to update the MMIO window.
|
||||
#[repr(packed)]
|
||||
#[derive(Default)]
|
||||
pub struct VhostUserFSSlaveMsg {
|
||||
/// TODO:
|
||||
pub fd_offset: [u64; VHOST_USER_FS_SLAVE_ENTRIES],
|
||||
/// TODO:
|
||||
pub cache_offset: [u64; VHOST_USER_FS_SLAVE_ENTRIES],
|
||||
/// Size of region to map.
|
||||
pub len: [u64; VHOST_USER_FS_SLAVE_ENTRIES],
|
||||
/// Flags for the mmap operation
|
||||
pub flags: [VhostUserFSSlaveMsgFlags; VHOST_USER_FS_SLAVE_ENTRIES],
|
||||
}
|
||||
|
||||
impl VhostUserMsgValidator for VhostUserFSSlaveMsg {
|
||||
fn is_valid(&self) -> bool {
|
||||
for i in 0..VHOST_USER_FS_SLAVE_ENTRIES {
|
||||
if ({ self.flags[i] }.bits() & !VhostUserFSSlaveMsgFlags::all().bits()) != 0
|
||||
|| self.fd_offset[i].checked_add(self.len[i]).is_none()
|
||||
|| self.cache_offset[i].checked_add(self.len[i]).is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::mem;
|
||||
|
||||
#[test]
|
||||
fn check_request_code() {
|
||||
let code = MasterReq::NOOP;
|
||||
assert!(!code.is_valid());
|
||||
let code = MasterReq::MAX_CMD;
|
||||
assert!(!code.is_valid());
|
||||
let code = MasterReq::GET_FEATURES;
|
||||
assert!(code.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn msg_header_ops() {
|
||||
let mut hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0, 0x100);
|
||||
assert_eq!(hdr.get_code(), MasterReq::GET_FEATURES);
|
||||
hdr.set_code(MasterReq::SET_FEATURES);
|
||||
assert_eq!(hdr.get_code(), MasterReq::SET_FEATURES);
|
||||
|
||||
assert_eq!(hdr.get_version(), 0x1);
|
||||
|
||||
assert_eq!(hdr.is_reply(), false);
|
||||
hdr.set_reply(true);
|
||||
assert_eq!(hdr.is_reply(), true);
|
||||
hdr.set_reply(false);
|
||||
|
||||
assert_eq!(hdr.is_need_reply(), false);
|
||||
hdr.set_need_reply(true);
|
||||
assert_eq!(hdr.is_need_reply(), true);
|
||||
hdr.set_need_reply(false);
|
||||
|
||||
assert_eq!(hdr.get_size(), 0x100);
|
||||
hdr.set_size(0x200);
|
||||
assert_eq!(hdr.get_size(), 0x200);
|
||||
|
||||
assert_eq!(hdr.is_need_reply(), false);
|
||||
assert_eq!(hdr.is_reply(), false);
|
||||
assert_eq!(hdr.get_version(), 0x1);
|
||||
|
||||
// Check message length
|
||||
assert!(hdr.is_valid());
|
||||
hdr.set_size(0x2000);
|
||||
assert!(!hdr.is_valid());
|
||||
hdr.set_size(0x100);
|
||||
assert_eq!(hdr.get_size(), 0x100);
|
||||
assert!(hdr.is_valid());
|
||||
hdr.set_size((MAX_MSG_SIZE - mem::size_of::<VhostUserMsgHeader<MasterReq>>()) as u32);
|
||||
assert!(hdr.is_valid());
|
||||
hdr.set_size(0x0);
|
||||
assert!(hdr.is_valid());
|
||||
|
||||
// Check version
|
||||
hdr.set_version(0x0);
|
||||
assert!(!hdr.is_valid());
|
||||
hdr.set_version(0x2);
|
||||
assert!(!hdr.is_valid());
|
||||
hdr.set_version(0x1);
|
||||
assert!(hdr.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_user_memory() {
|
||||
let mut msg = VhostUserMemory::new(1);
|
||||
assert!(msg.is_valid());
|
||||
msg.num_regions = MAX_ATTACHED_FD_ENTRIES as u32;
|
||||
assert!(msg.is_valid());
|
||||
|
||||
msg.num_regions += 1;
|
||||
assert!(!msg.is_valid());
|
||||
msg.num_regions = 0xFFFFFFFF;
|
||||
assert!(!msg.is_valid());
|
||||
msg.num_regions = MAX_ATTACHED_FD_ENTRIES as u32;
|
||||
msg.padding1 = 1;
|
||||
assert!(!msg.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_user_memory_region() {
|
||||
let mut msg = VhostUserMemoryRegion {
|
||||
guest_phys_addr: 0,
|
||||
memory_size: 0x1000,
|
||||
user_addr: 0,
|
||||
mmap_offset: 0,
|
||||
};
|
||||
assert!(msg.is_valid());
|
||||
msg.guest_phys_addr = 0xFFFFFFFFFFFFEFFF;
|
||||
assert!(msg.is_valid());
|
||||
msg.guest_phys_addr = 0xFFFFFFFFFFFFF000;
|
||||
assert!(!msg.is_valid());
|
||||
msg.guest_phys_addr = 0xFFFFFFFFFFFF0000;
|
||||
msg.memory_size = 0;
|
||||
assert!(!msg.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_user_vring_addr() {
|
||||
let mut msg =
|
||||
VhostUserVringAddr::new(0, VhostUserVringAddrFlags::all(), 0x0, 0x0, 0x0, 0x0);
|
||||
assert!(msg.is_valid());
|
||||
|
||||
msg.descriptor = 1;
|
||||
assert!(!msg.is_valid());
|
||||
msg.descriptor = 0;
|
||||
|
||||
msg.available = 1;
|
||||
assert!(!msg.is_valid());
|
||||
msg.available = 0;
|
||||
|
||||
msg.used = 1;
|
||||
assert!(!msg.is_valid());
|
||||
msg.used = 0;
|
||||
|
||||
msg.flags |= 0x80000000;
|
||||
assert!(!msg.is_valid());
|
||||
msg.flags &= !0x80000000;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn check_user_config_msg() {
|
||||
let mut msg = VhostUserConfig::new(
|
||||
VHOST_USER_CONFIG_OFFSET,
|
||||
VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET,
|
||||
VhostUserConfigFlags::WRITABLE,
|
||||
);
|
||||
|
||||
assert!(msg.is_valid());
|
||||
msg.size = 0;
|
||||
assert!(!msg.is_valid());
|
||||
msg.size = 1;
|
||||
assert!(msg.is_valid());
|
||||
msg.offset = 0;
|
||||
assert!(!msg.is_valid());
|
||||
msg.offset = VHOST_USER_CONFIG_SIZE;
|
||||
assert!(!msg.is_valid());
|
||||
msg.offset = VHOST_USER_CONFIG_SIZE - 1;
|
||||
assert!(msg.is_valid());
|
||||
msg.size = 2;
|
||||
assert!(!msg.is_valid());
|
||||
msg.size = 1;
|
||||
msg.flags |= VhostUserConfigFlags::LIVE_MIGRATION.bits();
|
||||
assert!(msg.is_valid());
|
||||
msg.flags |= 0x4;
|
||||
assert!(!msg.is_valid());
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! The protocol for vhost-user is based on the existing implementation of vhost for the Linux
|
||||
//! Kernel. The protocol defines two sides of the communication, master and slave. Master is
|
||||
//! the application that shares its virtqueues. Slave is the consumer of the virtqueues.
|
||||
//!
|
||||
//! The communication channel between the master and the slave includes two sub channels. One is
|
||||
//! used to send requests from the master to the slave and optional replies from the slave to the
|
||||
//! master. This sub channel is created on master startup by connecting to the slave service
|
||||
//! endpoint. The other is used to send requests from the slave to the master and optional replies
|
||||
//! from the master to the slave. This sub channel is created by the master issuing a
|
||||
//! VHOST_USER_SET_SLAVE_REQ_FD request to the slave with an auxiliary file descriptor.
|
||||
//!
|
||||
//! Unix domain socket is used as the underlying communication channel because the master needs to
|
||||
//! send file descriptors to the slave.
|
||||
//!
|
||||
//! Most messages that can be sent via the Unix domain socket implementing vhost-user have an
|
||||
//! equivalent ioctl to the kernel implementation.
|
||||
|
||||
use libc;
|
||||
use std::io::Error as IOError;
|
||||
|
||||
mod connection;
|
||||
pub mod message;
|
||||
pub use self::connection::Listener;
|
||||
#[cfg(feature = "vhost-user-master")]
|
||||
mod master;
|
||||
#[cfg(feature = "vhost-user-master")]
|
||||
pub use self::master::{Master, VhostUserMaster};
|
||||
#[cfg(feature = "vhost-user-master")]
|
||||
mod master_req_handler;
|
||||
#[cfg(feature = "vhost-user-master")]
|
||||
pub use self::master_req_handler::{MasterReqHandler, VhostUserMasterReqHandler};
|
||||
|
||||
#[cfg(feature = "vhost-user-slave")]
|
||||
mod slave;
|
||||
#[cfg(feature = "vhost-user-slave")]
|
||||
pub use self::slave::SlaveListener;
|
||||
#[cfg(feature = "vhost-user-slave")]
|
||||
mod slave_req_handler;
|
||||
#[cfg(feature = "vhost-user-slave")]
|
||||
pub use self::slave_req_handler::{SlaveReqHandler, VhostUserSlaveReqHandler};
|
||||
|
||||
pub mod sock_ctrl_msg;
|
||||
|
||||
/// Errors for vhost-user operations
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid parameters.
|
||||
InvalidParam,
|
||||
/// Unsupported operations due to that the protocol feature hasn't been negotiated.
|
||||
InvalidOperation,
|
||||
/// Invalid message format, flag or content.
|
||||
InvalidMessage,
|
||||
/// Only part of a message have been sent or received successfully
|
||||
PartialMessage,
|
||||
/// Message is too large
|
||||
OversizedMsg,
|
||||
/// Fd array in question is too big or too small
|
||||
IncorrectFds,
|
||||
/// Can't connect to peer.
|
||||
SocketConnect(std::io::Error),
|
||||
/// Generic socket errors.
|
||||
SocketError(std::io::Error),
|
||||
/// The socket is broken or has been closed.
|
||||
SocketBroken(std::io::Error),
|
||||
/// Should retry the socket operation again.
|
||||
SocketRetry(std::io::Error),
|
||||
/// Failure from the slave side.
|
||||
SlaveInternalError,
|
||||
/// Virtio/protocol features mismatch.
|
||||
FeatureMismatch,
|
||||
/// Error from request handler
|
||||
ReqHandlerError(IOError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::InvalidParam => write!(f, "invalid parameters"),
|
||||
Error::InvalidOperation => write!(f, "invalid operation"),
|
||||
Error::InvalidMessage => write!(f, "invalid message"),
|
||||
Error::PartialMessage => write!(f, "partial message"),
|
||||
Error::OversizedMsg => write!(f, "oversized message"),
|
||||
Error::IncorrectFds => write!(f, "wrong number of attached fds"),
|
||||
Error::SocketError(e) => write!(f, "socket error: {}", e),
|
||||
Error::SocketConnect(e) => write!(f, "can't connect to peer: {}", e),
|
||||
Error::SocketBroken(e) => write!(f, "socket is broken: {}", e),
|
||||
Error::SocketRetry(e) => write!(f, "temporary socket error: {}", e),
|
||||
Error::SlaveInternalError => write!(f, "slave internal error"),
|
||||
Error::FeatureMismatch => write!(f, "virtio/protocol features mismatch"),
|
||||
Error::ReqHandlerError(e) => write!(f, "handler failed to handle request: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Determine whether to rebuild the underline communication channel.
|
||||
pub fn should_reconnect(&self) -> bool {
|
||||
match *self {
|
||||
// Should reconnect because it may be caused by temporary network errors.
|
||||
Error::PartialMessage => true,
|
||||
// Should reconnect because the underline socket is broken.
|
||||
Error::SocketBroken(_) => true,
|
||||
// Slave internal error, hope it recovers on reconnect.
|
||||
Error::SlaveInternalError => true,
|
||||
// Should just retry the IO operation instead of rebuilding the underline connection.
|
||||
Error::SocketRetry(_) => false,
|
||||
Error::InvalidParam | Error::InvalidOperation => false,
|
||||
Error::InvalidMessage | Error::IncorrectFds | Error::OversizedMsg => false,
|
||||
Error::SocketError(_) | Error::SocketConnect(_) => false,
|
||||
Error::FeatureMismatch => false,
|
||||
Error::ReqHandlerError(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<vmm_sys_util::errno::Error> for Error {
|
||||
/// Convert raw socket errors into meaningful vhost-user errors.
|
||||
///
|
||||
/// The vmm_sys_util::errno::Error is a simple wrapper over the raw errno, which doesn't means much
|
||||
/// to the vhost-user connection manager. So convert it into meaningful errors to simplify
|
||||
/// the connection manager logic.
|
||||
///
|
||||
/// # Return:
|
||||
/// * - Error::SocketRetry: temporary error caused by signals or short of resources.
|
||||
/// * - Error::SocketBroken: the underline socket is broken.
|
||||
/// * - Error::SocketError: other socket related errors.
|
||||
#[allow(unreachable_patterns)] // EWOULDBLOCK equals to EGAIN on linux
|
||||
fn from(err: vmm_sys_util::errno::Error) -> Self {
|
||||
match err.errno() {
|
||||
// The socket is marked nonblocking and the requested operation would block.
|
||||
libc::EAGAIN => Error::SocketRetry(IOError::from_raw_os_error(libc::EAGAIN)),
|
||||
// The socket is marked nonblocking and the requested operation would block.
|
||||
libc::EWOULDBLOCK => Error::SocketRetry(IOError::from_raw_os_error(libc::EWOULDBLOCK)),
|
||||
// A signal occurred before any data was transmitted
|
||||
libc::EINTR => Error::SocketRetry(IOError::from_raw_os_error(libc::EINTR)),
|
||||
// The output queue for a network interface was full. This generally indicates
|
||||
// that the interface has stopped sending, but may be caused by transient congestion.
|
||||
libc::ENOBUFS => Error::SocketRetry(IOError::from_raw_os_error(libc::ENOBUFS)),
|
||||
// No memory available.
|
||||
libc::ENOMEM => Error::SocketRetry(IOError::from_raw_os_error(libc::ENOMEM)),
|
||||
// Connection reset by peer.
|
||||
libc::ECONNRESET => Error::SocketBroken(IOError::from_raw_os_error(libc::ECONNRESET)),
|
||||
// The local end has been shut down on a connection oriented socket. In this case the
|
||||
// process will also receive a SIGPIPE unless MSG_NOSIGNAL is set.
|
||||
libc::EPIPE => Error::SocketBroken(IOError::from_raw_os_error(libc::EPIPE)),
|
||||
// Write permission is denied on the destination socket file, or search permission is
|
||||
// denied for one of the directories the path prefix.
|
||||
libc::EACCES => Error::SocketConnect(IOError::from_raw_os_error(libc::EACCES)),
|
||||
// Catch all other errors
|
||||
e => Error::SocketError(IOError::from_raw_os_error(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of vhost-user operations
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Result of request handler.
|
||||
pub type HandlerResult<T> = std::result::Result<T, IOError>;
|
||||
|
||||
#[cfg(all(test, feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
mod dummy_slave;
|
||||
|
||||
#[cfg(all(test, feature = "vhost-user-master", feature = "vhost-user-slave"))]
|
||||
mod tests {
|
||||
use super::dummy_slave::{DummySlaveReqHandler, VIRTIO_FEATURES};
|
||||
use super::message::*;
|
||||
use super::*;
|
||||
use crate::backend::VhostBackend;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use std::thread;
|
||||
|
||||
fn create_slave<S: VhostUserSlaveReqHandler>(
|
||||
path: &str,
|
||||
backend: Arc<Mutex<S>>,
|
||||
) -> (Master, SlaveReqHandler<S>) {
|
||||
let mut slave_listener = SlaveListener::new(path, true, backend).unwrap();
|
||||
let master = Master::connect(path, 1).unwrap();
|
||||
(master, slave_listener.accept().unwrap().unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_dummy_slave() {
|
||||
let mut slave = DummySlaveReqHandler::new();
|
||||
|
||||
slave.set_owner().unwrap();
|
||||
assert!(slave.set_owner().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_owner() {
|
||||
let slave_be = Arc::new(Mutex::new(DummySlaveReqHandler::new()));
|
||||
let (mut master, mut slave) =
|
||||
create_slave("/tmp/vhost_user_lib_unit_test_owner", slave_be.clone());
|
||||
|
||||
assert_eq!(slave_be.lock().unwrap().owned, false);
|
||||
master.set_owner().unwrap();
|
||||
slave.handle_request().unwrap();
|
||||
assert_eq!(slave_be.lock().unwrap().owned, true);
|
||||
master.set_owner().unwrap();
|
||||
assert!(slave.handle_request().is_err());
|
||||
assert_eq!(slave_be.lock().unwrap().owned, true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_features() {
|
||||
let mbar = Arc::new(Barrier::new(2));
|
||||
let sbar = mbar.clone();
|
||||
let slave_be = Arc::new(Mutex::new(DummySlaveReqHandler::new()));
|
||||
let (mut master, mut slave) =
|
||||
create_slave("/tmp/vhost_user_lib_unit_test_feature", slave_be.clone());
|
||||
|
||||
thread::spawn(move || {
|
||||
slave.handle_request().unwrap();
|
||||
assert_eq!(slave_be.lock().unwrap().owned, true);
|
||||
|
||||
slave.handle_request().unwrap();
|
||||
slave.handle_request().unwrap();
|
||||
assert_eq!(
|
||||
slave_be.lock().unwrap().acked_features,
|
||||
VIRTIO_FEATURES & !0x1
|
||||
);
|
||||
|
||||
slave.handle_request().unwrap();
|
||||
slave.handle_request().unwrap();
|
||||
assert_eq!(
|
||||
slave_be.lock().unwrap().acked_protocol_features,
|
||||
VhostUserProtocolFeatures::all().bits()
|
||||
);
|
||||
|
||||
sbar.wait();
|
||||
});
|
||||
|
||||
master.set_owner().unwrap();
|
||||
|
||||
// set virtio features
|
||||
let features = master.get_features().unwrap();
|
||||
assert_eq!(features, VIRTIO_FEATURES);
|
||||
master.set_features(VIRTIO_FEATURES & !0x1).unwrap();
|
||||
|
||||
// set vhost protocol features
|
||||
let features = master.get_protocol_features().unwrap();
|
||||
assert_eq!(features.bits(), VhostUserProtocolFeatures::all().bits());
|
||||
master.set_protocol_features(features).unwrap();
|
||||
|
||||
mbar.wait();
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Traits and Structs for vhost-user slave.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::connection::{Endpoint, Listener};
|
||||
use super::message::*;
|
||||
use super::{Result, SlaveReqHandler, VhostUserSlaveReqHandler};
|
||||
|
||||
/// Vhost-user slave side connection listener.
|
||||
pub struct SlaveListener<S: VhostUserSlaveReqHandler> {
|
||||
listener: Listener,
|
||||
backend: Option<Arc<Mutex<S>>>,
|
||||
}
|
||||
|
||||
/// Sets up a listener for incoming master connections, and handles construction
|
||||
/// of a Slave on success.
|
||||
impl<S: VhostUserSlaveReqHandler> SlaveListener<S> {
|
||||
/// Create a unix domain socket for incoming master connections.
|
||||
///
|
||||
/// Be careful, the file at `path` will be unlinked if unlink is true
|
||||
pub fn new(path: &str, unlink: bool, backend: Arc<Mutex<S>>) -> Result<Self> {
|
||||
Ok(SlaveListener {
|
||||
listener: Listener::new(path, unlink)?,
|
||||
backend: Some(backend),
|
||||
})
|
||||
}
|
||||
|
||||
/// Accept an incoming connection from the master, returning Some(Slave) on
|
||||
/// success, or None if the socket is nonblocking and no incoming connection
|
||||
/// was detected
|
||||
pub fn accept(&mut self) -> Result<Option<SlaveReqHandler<S>>> {
|
||||
if let Some(fd) = self.listener.accept()? {
|
||||
return Ok(Some(SlaveReqHandler::new(
|
||||
Endpoint::<MasterReq>::from_stream(fd),
|
||||
self.backend.take().unwrap(),
|
||||
)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Change blocking status on the listener.
|
||||
pub fn set_nonblocking(&self, block: bool) -> Result<()> {
|
||||
self.listener.set_nonblocking(block)
|
||||
}
|
||||
}
|
||||
@@ -1,586 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Traits and Structs to handle vhost-user requests from the master to the slave.
|
||||
|
||||
use std::mem;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::slice;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::connection::Endpoint;
|
||||
use super::message::*;
|
||||
use super::{Error, Result};
|
||||
|
||||
/// Trait to handle vhost-user requests from the master to the slave.
|
||||
#[allow(missing_docs)]
|
||||
pub trait VhostUserSlaveReqHandler {
|
||||
fn set_owner(&mut self) -> Result<()>;
|
||||
fn reset_owner(&mut self) -> Result<()>;
|
||||
fn get_features(&mut self) -> Result<u64>;
|
||||
fn set_features(&mut self, features: u64) -> Result<()>;
|
||||
fn set_mem_table(&mut self, ctx: &[VhostUserMemoryRegion], fds: &[RawFd]) -> Result<()>;
|
||||
fn set_vring_num(&mut self, index: u32, num: u32) -> Result<()>;
|
||||
fn set_vring_addr(
|
||||
&mut self,
|
||||
index: u32,
|
||||
flags: VhostUserVringAddrFlags,
|
||||
descriptor: u64,
|
||||
used: u64,
|
||||
available: u64,
|
||||
log: u64,
|
||||
) -> Result<()>;
|
||||
fn set_vring_base(&mut self, index: u32, base: u32) -> Result<()>;
|
||||
fn get_vring_base(&mut self, index: u32) -> Result<VhostUserVringState>;
|
||||
fn set_vring_kick(&mut self, index: u8, fd: Option<RawFd>) -> Result<()>;
|
||||
fn set_vring_call(&mut self, index: u8, fd: Option<RawFd>) -> Result<()>;
|
||||
fn set_vring_err(&mut self, index: u8, fd: Option<RawFd>) -> Result<()>;
|
||||
|
||||
fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures>;
|
||||
fn set_protocol_features(&mut self, features: u64) -> Result<()>;
|
||||
fn get_queue_num(&mut self) -> Result<u64>;
|
||||
fn set_vring_enable(&mut self, index: u32, enable: bool) -> Result<()>;
|
||||
fn get_config(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
size: u32,
|
||||
flags: VhostUserConfigFlags,
|
||||
) -> Result<Vec<u8>>;
|
||||
fn set_config(&mut self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()>;
|
||||
}
|
||||
|
||||
/// A vhost-user slave endpoint which relays all received requests from the
|
||||
/// master to the virtio backend device object.
|
||||
///
|
||||
/// The lifetime of the SlaveReqHandler object should be the same as the underline Unix Domain
|
||||
/// Socket, so it gets simpler to recover from disconnect.
|
||||
pub struct SlaveReqHandler<S: VhostUserSlaveReqHandler> {
|
||||
// underlying Unix domain socket for communication
|
||||
main_sock: Endpoint<MasterReq>,
|
||||
// the vhost-user backend device object
|
||||
backend: Arc<Mutex<S>>,
|
||||
|
||||
virtio_features: u64,
|
||||
acked_virtio_features: u64,
|
||||
protocol_features: VhostUserProtocolFeatures,
|
||||
acked_protocol_features: u64,
|
||||
|
||||
// sending ack for messages without payload
|
||||
reply_ack_enabled: bool,
|
||||
// whether the endpoint has encountered any failure
|
||||
error: Option<i32>,
|
||||
}
|
||||
|
||||
impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S> {
|
||||
/// Create a vhost-user slave endpoint.
|
||||
pub(super) fn new(main_sock: Endpoint<MasterReq>, backend: Arc<Mutex<S>>) -> Self {
|
||||
SlaveReqHandler {
|
||||
main_sock,
|
||||
backend,
|
||||
virtio_features: 0,
|
||||
acked_virtio_features: 0,
|
||||
protocol_features: VhostUserProtocolFeatures::empty(),
|
||||
acked_protocol_features: 0,
|
||||
reply_ack_enabled: false,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new vhost-user slave endpoint.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * - `path` - path of Unix domain socket listener to connect to
|
||||
/// * - `backend` - handler for requests from the master to the slave
|
||||
pub fn connect(path: &str, backend: Arc<Mutex<S>>) -> Result<Self> {
|
||||
Ok(Self::new(Endpoint::<MasterReq>::connect(path)?, backend))
|
||||
}
|
||||
|
||||
/// Mark endpoint as failed with specified error code.
|
||||
pub fn set_failed(&mut self, error: i32) {
|
||||
self.error = Some(error);
|
||||
}
|
||||
|
||||
/// Receive and handle one incoming request message from the master.
|
||||
/// The caller needs to:
|
||||
/// . serialize calls to this function
|
||||
/// . decide what to do when error happens
|
||||
/// . optional recover from failure
|
||||
pub fn handle_request(&mut self) -> Result<()> {
|
||||
// Return error if the endpoint is already in failed state.
|
||||
self.check_state()?;
|
||||
|
||||
// The underlying communication channel is a Unix domain socket in
|
||||
// stream mode, and recvmsg() is a little tricky here. To successfully
|
||||
// receive attached file descriptors, we need to receive messages and
|
||||
// corresponding attached file descriptors in this way:
|
||||
// . recv messsage header and optional attached file
|
||||
// . validate message header
|
||||
// . recv optional message body and payload according size field in
|
||||
// message header
|
||||
// . validate message body and optional payload
|
||||
let (hdr, rfds) = self.main_sock.recv_header()?;
|
||||
let rfds = self.check_attached_rfds(&hdr, rfds)?;
|
||||
let (size, buf) = match hdr.get_size() {
|
||||
0 => (0, vec![0u8; 0]),
|
||||
len => {
|
||||
let (size2, rbuf) = self.main_sock.recv_data(len as usize)?;
|
||||
if size2 != len as usize {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
(size2, rbuf)
|
||||
}
|
||||
};
|
||||
|
||||
match hdr.get_code() {
|
||||
MasterReq::SET_OWNER => {
|
||||
self.check_request_size(&hdr, size, 0)?;
|
||||
self.backend.lock().unwrap().set_owner()?;
|
||||
}
|
||||
MasterReq::RESET_OWNER => {
|
||||
self.check_request_size(&hdr, size, 0)?;
|
||||
self.backend.lock().unwrap().reset_owner()?;
|
||||
}
|
||||
MasterReq::GET_FEATURES => {
|
||||
self.check_request_size(&hdr, size, 0)?;
|
||||
let features = self.backend.lock().unwrap().get_features()?;
|
||||
let msg = VhostUserU64::new(features);
|
||||
self.send_reply_message(&hdr, &msg)?;
|
||||
self.virtio_features = features;
|
||||
self.update_reply_ack_flag();
|
||||
}
|
||||
MasterReq::SET_FEATURES => {
|
||||
let msg = self.extract_request_body::<VhostUserU64>(&hdr, size, &buf)?;
|
||||
self.backend.lock().unwrap().set_features(msg.value)?;
|
||||
self.acked_virtio_features = msg.value;
|
||||
self.update_reply_ack_flag();
|
||||
}
|
||||
MasterReq::SET_MEM_TABLE => {
|
||||
let res = self.set_mem_table(&hdr, size, &buf, rfds);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::SET_VRING_NUM => {
|
||||
let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
|
||||
let res = self
|
||||
.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_vring_num(msg.index, msg.num);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::SET_VRING_ADDR => {
|
||||
let msg = self.extract_request_body::<VhostUserVringAddr>(&hdr, size, &buf)?;
|
||||
let flags = match VhostUserVringAddrFlags::from_bits(msg.flags) {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::InvalidMessage),
|
||||
};
|
||||
let res = self.backend.lock().unwrap().set_vring_addr(
|
||||
msg.index,
|
||||
flags,
|
||||
msg.descriptor,
|
||||
msg.used,
|
||||
msg.available,
|
||||
msg.log,
|
||||
);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::SET_VRING_BASE => {
|
||||
let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
|
||||
let res = self
|
||||
.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_vring_base(msg.index, msg.num);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::GET_VRING_BASE => {
|
||||
let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
|
||||
let reply = self.backend.lock().unwrap().get_vring_base(msg.index)?;
|
||||
self.send_reply_message(&hdr, &reply)?;
|
||||
}
|
||||
MasterReq::SET_VRING_CALL => {
|
||||
self.check_request_size(&hdr, size, mem::size_of::<VhostUserU64>())?;
|
||||
let (index, rfds) = self.handle_vring_fd_request(&buf, rfds)?;
|
||||
let res = self.backend.lock().unwrap().set_vring_call(index, rfds);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::SET_VRING_KICK => {
|
||||
self.check_request_size(&hdr, size, mem::size_of::<VhostUserU64>())?;
|
||||
let (index, rfds) = self.handle_vring_fd_request(&buf, rfds)?;
|
||||
let res = self.backend.lock().unwrap().set_vring_kick(index, rfds);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::SET_VRING_ERR => {
|
||||
self.check_request_size(&hdr, size, mem::size_of::<VhostUserU64>())?;
|
||||
let (index, rfds) = self.handle_vring_fd_request(&buf, rfds)?;
|
||||
let res = self.backend.lock().unwrap().set_vring_err(index, rfds);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::GET_PROTOCOL_FEATURES => {
|
||||
self.check_request_size(&hdr, size, 0)?;
|
||||
let features = self.backend.lock().unwrap().get_protocol_features()?;
|
||||
let msg = VhostUserU64::new(features.bits());
|
||||
self.send_reply_message(&hdr, &msg)?;
|
||||
self.protocol_features = features;
|
||||
self.update_reply_ack_flag();
|
||||
}
|
||||
MasterReq::SET_PROTOCOL_FEATURES => {
|
||||
let msg = self.extract_request_body::<VhostUserU64>(&hdr, size, &buf)?;
|
||||
self.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_protocol_features(msg.value)?;
|
||||
self.acked_protocol_features = msg.value;
|
||||
self.update_reply_ack_flag();
|
||||
}
|
||||
MasterReq::GET_QUEUE_NUM => {
|
||||
if self.acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
self.check_request_size(&hdr, size, 0)?;
|
||||
let num = self.backend.lock().unwrap().get_queue_num()?;
|
||||
let msg = VhostUserU64::new(num);
|
||||
self.send_reply_message(&hdr, &msg)?;
|
||||
}
|
||||
MasterReq::SET_VRING_ENABLE => {
|
||||
let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
|
||||
if self.acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() == 0
|
||||
&& msg.index > 0
|
||||
{
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
let enable = match msg.num {
|
||||
1 => true,
|
||||
0 => false,
|
||||
_ => return Err(Error::InvalidParam),
|
||||
};
|
||||
|
||||
let res = self
|
||||
.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_vring_enable(msg.index, enable);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
}
|
||||
MasterReq::GET_CONFIG => {
|
||||
if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
self.get_config(&hdr, &buf)?;
|
||||
}
|
||||
MasterReq::SET_CONFIG => {
|
||||
if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
self.check_request_size(&hdr, size, hdr.get_size() as usize)?;
|
||||
self.set_config(&hdr, size, &buf)?;
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_mem_table(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
size: usize,
|
||||
buf: &[u8],
|
||||
rfds: Option<Vec<RawFd>>,
|
||||
) -> Result<()> {
|
||||
self.check_request_size(&hdr, size, hdr.get_size() as usize)?;
|
||||
|
||||
// check message size is consistent
|
||||
let hdrsize = mem::size_of::<VhostUserMemory>();
|
||||
if size < hdrsize {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const VhostUserMemory) };
|
||||
if !msg.is_valid() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
if size != hdrsize + msg.num_regions as usize * mem::size_of::<VhostUserMemoryRegion>() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
// validate number of fds matching number of memory regions
|
||||
let fds = match rfds {
|
||||
None => return Err(Error::InvalidMessage),
|
||||
Some(fds) => {
|
||||
if fds.len() != msg.num_regions as usize {
|
||||
Endpoint::<MasterReq>::close_rfds(Some(fds));
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
fds
|
||||
}
|
||||
};
|
||||
|
||||
// Validate memory regions
|
||||
let regions = unsafe {
|
||||
slice::from_raw_parts(
|
||||
buf.as_ptr().add(hdrsize) as *const VhostUserMemoryRegion,
|
||||
msg.num_regions as usize,
|
||||
)
|
||||
};
|
||||
for region in regions.iter() {
|
||||
if !region.is_valid() {
|
||||
Endpoint::<MasterReq>::close_rfds(Some(fds));
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
}
|
||||
|
||||
self.backend.lock().unwrap().set_mem_table(®ions, &fds)
|
||||
}
|
||||
|
||||
fn get_config(&mut self, hdr: &VhostUserMsgHeader<MasterReq>, buf: &[u8]) -> Result<()> {
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const VhostUserConfig) };
|
||||
if !msg.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
let 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) {
|
||||
Some(val) => val,
|
||||
None => return Err(Error::InvalidMessage),
|
||||
};
|
||||
let res = self
|
||||
.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_config(msg.offset, msg.size, flags);
|
||||
|
||||
// vhost-user slave's payload size MUST match master's request
|
||||
// on success, uses zero length of payload to indicate an error
|
||||
// to vhost-user master.
|
||||
match res {
|
||||
Ok(ref buf) if buf.len() == msg.size as usize => {
|
||||
let reply = VhostUserConfig::new(msg.offset, buf.len() as u32, flags);
|
||||
self.send_reply_with_payload(&hdr, &reply, buf.as_slice())?;
|
||||
}
|
||||
Ok(_) => {
|
||||
let reply = VhostUserConfig::new(msg.offset, 0, flags);
|
||||
self.send_reply_message(&hdr, &reply)?;
|
||||
}
|
||||
Err(_) => {
|
||||
let reply = VhostUserConfig::new(msg.offset, 0, flags);
|
||||
self.send_reply_message(&hdr, &reply)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_config(
|
||||
&mut self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
size: usize,
|
||||
buf: &[u8],
|
||||
) -> Result<()> {
|
||||
if size < mem::size_of::<VhostUserConfig>() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const VhostUserConfig) };
|
||||
if !msg.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
if size - mem::size_of::<VhostUserConfig>() != msg.size as usize {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
let flags: VhostUserConfigFlags;
|
||||
match VhostUserConfigFlags::from_bits(msg.flags) {
|
||||
Some(val) => flags = val,
|
||||
None => return Err(Error::InvalidMessage),
|
||||
}
|
||||
|
||||
let res = self
|
||||
.backend
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_config(msg.offset, buf, flags);
|
||||
self.send_ack_message(&hdr, res)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_vring_fd_request(
|
||||
&mut self,
|
||||
buf: &[u8],
|
||||
rfds: Option<Vec<RawFd>>,
|
||||
) -> Result<(u8, Option<RawFd>)> {
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const VhostUserU64) };
|
||||
if !msg.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
|
||||
// Bits (0-7) of the payload contain the vring index. Bit 8 is the
|
||||
// invalid FD flag. This flag is set when there is no file descriptor
|
||||
// in the ancillary data. This signals that polling will be used
|
||||
// instead of waiting for the call.
|
||||
let nofd = match msg.value & 0x100u64 {
|
||||
0x100u64 => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let mut rfd = None;
|
||||
match rfds {
|
||||
Some(fds) => {
|
||||
if !nofd && fds.len() == 1 {
|
||||
rfd = Some(fds[0]);
|
||||
} else if (nofd && !fds.is_empty()) || (!nofd && fds.len() != 1) {
|
||||
Endpoint::<MasterReq>::close_rfds(Some(fds));
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if !nofd {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((msg.value as u8, rfd))
|
||||
}
|
||||
|
||||
fn check_state(&self) -> Result<()> {
|
||||
match self.error {
|
||||
Some(e) => Err(Error::SocketBroken(std::io::Error::from_raw_os_error(e))),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_request_size(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
size: usize,
|
||||
expected: usize,
|
||||
) -> Result<()> {
|
||||
if hdr.get_size() as usize != expected
|
||||
|| hdr.is_reply()
|
||||
|| hdr.get_version() != 0x1
|
||||
|| size != expected
|
||||
{
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_attached_rfds(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
rfds: Option<Vec<RawFd>>,
|
||||
) -> Result<Option<Vec<RawFd>>> {
|
||||
match hdr.get_code() {
|
||||
MasterReq::SET_MEM_TABLE => Ok(rfds),
|
||||
MasterReq::SET_VRING_CALL => Ok(rfds),
|
||||
MasterReq::SET_VRING_KICK => Ok(rfds),
|
||||
MasterReq::SET_VRING_ERR => Ok(rfds),
|
||||
MasterReq::SET_LOG_BASE => Ok(rfds),
|
||||
MasterReq::SET_LOG_FD => Ok(rfds),
|
||||
MasterReq::SET_SLAVE_REQ_FD => Ok(rfds),
|
||||
MasterReq::SET_INFLIGHT_FD => Ok(rfds),
|
||||
_ => {
|
||||
if rfds.is_some() {
|
||||
Endpoint::<MasterReq>::close_rfds(rfds);
|
||||
Err(Error::InvalidMessage)
|
||||
} else {
|
||||
Ok(rfds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_request_body<'a, T: Sized + VhostUserMsgValidator>(
|
||||
&self,
|
||||
hdr: &VhostUserMsgHeader<MasterReq>,
|
||||
size: usize,
|
||||
buf: &'a [u8],
|
||||
) -> Result<&'a T> {
|
||||
self.check_request_size(hdr, size, mem::size_of::<T>())?;
|
||||
let msg = unsafe { &*(buf.as_ptr() as *const T) };
|
||||
if !msg.is_valid() {
|
||||
return Err(Error::InvalidMessage);
|
||||
}
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
fn update_reply_ack_flag(&mut self) {
|
||||
let vflag = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||
let pflag = VhostUserProtocolFeatures::REPLY_ACK;
|
||||
if (self.virtio_features & vflag) != 0
|
||||
&& (self.acked_virtio_features & vflag) != 0
|
||||
&& self.protocol_features.contains(pflag)
|
||||
&& (self.acked_protocol_features & pflag.bits()) != 0
|
||||
{
|
||||
self.reply_ack_enabled = true;
|
||||
} else {
|
||||
self.reply_ack_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn new_reply_header<T: Sized>(
|
||||
&self,
|
||||
req: &VhostUserMsgHeader<MasterReq>,
|
||||
payload_size: usize,
|
||||
) -> Result<VhostUserMsgHeader<MasterReq>> {
|
||||
if mem::size_of::<T>() > MAX_MSG_SIZE {
|
||||
return Err(Error::InvalidParam);
|
||||
}
|
||||
self.check_state()?;
|
||||
Ok(VhostUserMsgHeader::new(
|
||||
req.get_code(),
|
||||
VhostUserHeaderFlag::REPLY.bits(),
|
||||
(mem::size_of::<T>() + payload_size) as u32,
|
||||
))
|
||||
}
|
||||
|
||||
fn send_ack_message(
|
||||
&mut self,
|
||||
req: &VhostUserMsgHeader<MasterReq>,
|
||||
res: Result<()>,
|
||||
) -> Result<()> {
|
||||
if self.reply_ack_enabled {
|
||||
let hdr = self.new_reply_header::<VhostUserU64>(req, 0)?;
|
||||
let val = match res {
|
||||
Ok(_) => 0,
|
||||
Err(_) => 1,
|
||||
};
|
||||
let msg = VhostUserU64::new(val);
|
||||
self.main_sock.send_message(&hdr, &msg, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_reply_message<T>(
|
||||
&mut self,
|
||||
req: &VhostUserMsgHeader<MasterReq>,
|
||||
msg: &T,
|
||||
) -> Result<()> {
|
||||
let hdr = self.new_reply_header::<T>(req, 0)?;
|
||||
self.main_sock.send_message(&hdr, msg, None)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_reply_with_payload<T, P>(
|
||||
&mut self,
|
||||
req: &VhostUserMsgHeader<MasterReq>,
|
||||
msg: &T,
|
||||
payload: &[P],
|
||||
) -> Result<()>
|
||||
where
|
||||
T: Sized,
|
||||
P: Sized,
|
||||
{
|
||||
let hdr = self.new_reply_header::<T>(req, payload.len())?;
|
||||
self.main_sock
|
||||
.send_message_with_payload(&hdr, msg, payload, None)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: VhostUserSlaveReqHandler> AsRawFd for SlaveReqHandler<S> {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.main_sock.as_raw_fd()
|
||||
}
|
||||
}
|
||||
@@ -1,464 +0,0 @@
|
||||
// Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Used to send and receive messages with file descriptors on sockets that accept control messages
|
||||
//! (e.g. Unix domain sockets).
|
||||
|
||||
// TODO: move this file into the vmm-sys-util crate
|
||||
|
||||
use std::fs::File;
|
||||
use std::mem::size_of;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::unix::net::{UnixDatagram, UnixStream};
|
||||
use std::ptr::{copy_nonoverlapping, null_mut, write_unaligned};
|
||||
|
||||
use libc::{
|
||||
c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET,
|
||||
};
|
||||
use vmm_sys_util::errno::{Error, Result};
|
||||
|
||||
// Each of the following macros performs the same function as their C counterparts. They are each
|
||||
// macros because they are used to size statically allocated arrays.
|
||||
|
||||
macro_rules! CMSG_ALIGN {
|
||||
($len:expr) => {
|
||||
(($len) + size_of::<c_long>() - 1) & !(size_of::<c_long>() - 1)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! CMSG_SPACE {
|
||||
($len:expr) => {
|
||||
size_of::<cmsghdr>() + CMSG_ALIGN!($len)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! CMSG_LEN {
|
||||
($len:expr) => {
|
||||
size_of::<cmsghdr>() + ($len)
|
||||
};
|
||||
}
|
||||
|
||||
// This function (macro in the C version) is not used in any compile time constant slots, so is just
|
||||
// an ordinary function. The returned pointer is hard coded to be RawFd because that's all that this
|
||||
// module supports.
|
||||
#[allow(non_snake_case)]
|
||||
#[inline(always)]
|
||||
fn CMSG_DATA(cmsg_buffer: *mut cmsghdr) -> *mut RawFd {
|
||||
// Essentially returns a pointer to just past the header.
|
||||
cmsg_buffer.wrapping_offset(1) as *mut RawFd
|
||||
}
|
||||
|
||||
// This function is like CMSG_NEXT, but safer because it reads only from references, although it
|
||||
// does some pointer arithmetic on cmsg_ptr.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
|
||||
fn get_next_cmsg(msghdr: &msghdr, cmsg: &cmsghdr, cmsg_ptr: *mut cmsghdr) -> *mut cmsghdr {
|
||||
let next_cmsg = (cmsg_ptr as *mut u8).wrapping_add(CMSG_ALIGN!(cmsg.cmsg_len)) as *mut cmsghdr;
|
||||
if next_cmsg
|
||||
.wrapping_offset(1)
|
||||
.wrapping_sub(msghdr.msg_control as usize) as usize
|
||||
> msghdr.msg_controllen
|
||||
{
|
||||
null_mut()
|
||||
} else {
|
||||
next_cmsg
|
||||
}
|
||||
}
|
||||
|
||||
const CMSG_BUFFER_INLINE_CAPACITY: usize = CMSG_SPACE!(size_of::<RawFd>() * 32);
|
||||
|
||||
enum CmsgBuffer {
|
||||
Inline([u64; (CMSG_BUFFER_INLINE_CAPACITY + 7) / 8]),
|
||||
Heap(Box<[cmsghdr]>),
|
||||
}
|
||||
|
||||
impl CmsgBuffer {
|
||||
fn with_capacity(capacity: usize) -> CmsgBuffer {
|
||||
let cap_in_cmsghdr_units =
|
||||
(capacity.checked_add(size_of::<cmsghdr>()).unwrap() - 1) / size_of::<cmsghdr>();
|
||||
if capacity <= CMSG_BUFFER_INLINE_CAPACITY {
|
||||
CmsgBuffer::Inline([0u64; (CMSG_BUFFER_INLINE_CAPACITY + 7) / 8])
|
||||
} else {
|
||||
CmsgBuffer::Heap(
|
||||
vec![
|
||||
cmsghdr {
|
||||
cmsg_len: 0,
|
||||
cmsg_level: 0,
|
||||
cmsg_type: 0,
|
||||
};
|
||||
cap_in_cmsghdr_units
|
||||
]
|
||||
.into_boxed_slice(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn as_mut_ptr(&mut self) -> *mut cmsghdr {
|
||||
match self {
|
||||
CmsgBuffer::Inline(a) => a.as_mut_ptr() as *mut cmsghdr,
|
||||
CmsgBuffer::Heap(a) => a.as_mut_ptr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_sendmsg<D: IntoIovec>(fd: RawFd, out_data: &[D], out_fds: &[RawFd]) -> Result<usize> {
|
||||
let cmsg_capacity = CMSG_SPACE!(size_of::<RawFd>() * out_fds.len());
|
||||
let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity);
|
||||
|
||||
let mut iovecs = Vec::with_capacity(out_data.len());
|
||||
for data in out_data {
|
||||
iovecs.push(iovec {
|
||||
iov_base: data.as_ptr() as *mut c_void,
|
||||
iov_len: data.size(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut msg = msghdr {
|
||||
msg_name: null_mut(),
|
||||
msg_namelen: 0,
|
||||
msg_iov: iovecs.as_mut_ptr(),
|
||||
msg_iovlen: iovecs.len(),
|
||||
msg_control: null_mut(),
|
||||
msg_controllen: 0,
|
||||
msg_flags: 0,
|
||||
};
|
||||
|
||||
if !out_fds.is_empty() {
|
||||
let cmsg = cmsghdr {
|
||||
cmsg_len: CMSG_LEN!(size_of::<RawFd>() * out_fds.len()),
|
||||
cmsg_level: SOL_SOCKET,
|
||||
cmsg_type: SCM_RIGHTS,
|
||||
};
|
||||
unsafe {
|
||||
// Safe because cmsg_buffer was allocated to be large enough to contain cmsghdr.
|
||||
write_unaligned(cmsg_buffer.as_mut_ptr() as *mut cmsghdr, cmsg);
|
||||
// Safe because the cmsg_buffer was allocated to be large enough to hold out_fds.len()
|
||||
// file descriptors.
|
||||
copy_nonoverlapping(
|
||||
out_fds.as_ptr(),
|
||||
CMSG_DATA(cmsg_buffer.as_mut_ptr()),
|
||||
out_fds.len(),
|
||||
);
|
||||
}
|
||||
|
||||
msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
|
||||
msg.msg_controllen = cmsg_capacity;
|
||||
}
|
||||
|
||||
// Safe because the msghdr was properly constructed from valid (or null) pointers of the
|
||||
// indicated length and we check the return value.
|
||||
let write_count = unsafe { sendmsg(fd, &msg, MSG_NOSIGNAL) };
|
||||
|
||||
if write_count == -1 {
|
||||
Err(Error::last())
|
||||
} else {
|
||||
Ok(write_count as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_recvmsg(fd: RawFd, iovecs: &mut [iovec], in_fds: &mut [RawFd]) -> Result<(usize, usize)> {
|
||||
let cmsg_capacity = CMSG_SPACE!(size_of::<RawFd>() * in_fds.len());
|
||||
let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity);
|
||||
let mut msg = msghdr {
|
||||
msg_name: null_mut(),
|
||||
msg_namelen: 0,
|
||||
msg_iov: iovecs.as_mut_ptr(),
|
||||
msg_iovlen: iovecs.len(),
|
||||
msg_control: null_mut(),
|
||||
msg_controllen: 0,
|
||||
msg_flags: 0,
|
||||
};
|
||||
|
||||
if !in_fds.is_empty() {
|
||||
msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
|
||||
msg.msg_controllen = cmsg_capacity;
|
||||
}
|
||||
|
||||
// Safe because the msghdr was properly constructed from valid (or null) pointers of the
|
||||
// indicated length and we check the return value.
|
||||
let total_read = unsafe { recvmsg(fd, &mut msg, libc::MSG_WAITALL) };
|
||||
|
||||
if total_read == -1 {
|
||||
return Err(Error::last());
|
||||
}
|
||||
|
||||
if total_read == 0 && msg.msg_controllen < size_of::<cmsghdr>() {
|
||||
return Ok((0, 0));
|
||||
}
|
||||
|
||||
let mut cmsg_ptr = msg.msg_control as *mut cmsghdr;
|
||||
let mut in_fds_count = 0;
|
||||
while !cmsg_ptr.is_null() {
|
||||
// Safe because we checked that cmsg_ptr was non-null, and the loop is constructed such that
|
||||
// that only happens when there is at least sizeof(cmsghdr) space after the pointer to read.
|
||||
let cmsg = unsafe { (cmsg_ptr as *mut cmsghdr).read_unaligned() };
|
||||
|
||||
if cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS {
|
||||
let fd_count = (cmsg.cmsg_len - CMSG_LEN!(0)) / size_of::<RawFd>();
|
||||
unsafe {
|
||||
copy_nonoverlapping(
|
||||
CMSG_DATA(cmsg_ptr),
|
||||
in_fds[in_fds_count..(in_fds_count + fd_count)].as_mut_ptr(),
|
||||
fd_count,
|
||||
);
|
||||
}
|
||||
in_fds_count += fd_count;
|
||||
}
|
||||
|
||||
cmsg_ptr = get_next_cmsg(&msg, &cmsg, cmsg_ptr);
|
||||
}
|
||||
|
||||
Ok((total_read as usize, in_fds_count))
|
||||
}
|
||||
|
||||
/// Trait for file descriptors can send and receive socket control messages via `sendmsg` and
|
||||
/// `recvmsg`.
|
||||
pub trait ScmSocket {
|
||||
/// Gets the file descriptor of this socket.
|
||||
fn socket_fd(&self) -> RawFd;
|
||||
|
||||
/// Sends the given data and file descriptor over the socket.
|
||||
///
|
||||
/// On success, returns the number of bytes sent.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `buf` - A buffer of data to send on the `socket`.
|
||||
/// * `fd` - A file descriptors to be sent.
|
||||
fn send_with_fd<D: IntoIovec>(&self, buf: D, fd: RawFd) -> Result<usize> {
|
||||
self.send_with_fds(&[buf], &[fd])
|
||||
}
|
||||
|
||||
/// Sends the given data and file descriptors over the socket.
|
||||
///
|
||||
/// On success, returns the number of bytes sent.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `bufs` - A list of data buffer to send on the `socket`.
|
||||
/// * `fds` - A list of file descriptors to be sent.
|
||||
fn send_with_fds<D: IntoIovec>(&self, bufs: &[D], fds: &[RawFd]) -> Result<usize> {
|
||||
raw_sendmsg(self.socket_fd(), bufs, fds)
|
||||
}
|
||||
|
||||
/// Receives data and potentially a file descriptor from the socket.
|
||||
///
|
||||
/// On success, returns the number of bytes and an optional file descriptor.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `buf` - A buffer to receive data from the socket.
|
||||
fn recv_with_fd(&self, buf: &mut [u8]) -> Result<(usize, Option<File>)> {
|
||||
let mut fd = [0];
|
||||
let mut iovecs = [iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf.len(),
|
||||
}];
|
||||
|
||||
let (read_count, fd_count) = self.recv_with_fds(&mut iovecs[..], &mut fd)?;
|
||||
let file = if fd_count == 0 {
|
||||
None
|
||||
} else {
|
||||
// Safe because the first fd from recv_with_fds is owned by us and valid because this
|
||||
// branch was taken.
|
||||
Some(unsafe { File::from_raw_fd(fd[0]) })
|
||||
};
|
||||
Ok((read_count, file))
|
||||
}
|
||||
|
||||
/// Receives data and file descriptors from the socket.
|
||||
///
|
||||
/// On success, returns the number of bytes and file descriptors received as a tuple
|
||||
/// `(bytes count, files count)`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `iovecs` - A list of iovec to receive data from the socket.
|
||||
/// * `fds` - A slice of `RawFd`s to put the received file descriptors into. On success, the
|
||||
/// number of valid file descriptors is indicated by the second element of the
|
||||
/// returned tuple. The caller owns these file descriptors, but they will not be
|
||||
/// closed on drop like a `File`-like type would be. It is recommended that each valid
|
||||
/// file descriptor gets wrapped in a drop type that closes it after this returns.
|
||||
fn recv_with_fds(&self, iovecs: &mut [iovec], fds: &mut [RawFd]) -> Result<(usize, usize)> {
|
||||
raw_recvmsg(self.socket_fd(), iovecs, fds)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScmSocket for UnixDatagram {
|
||||
fn socket_fd(&self) -> RawFd {
|
||||
self.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl ScmSocket for UnixStream {
|
||||
fn socket_fd(&self) -> RawFd {
|
||||
self.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that can be converted into an `iovec` that can be referenced by a syscall for
|
||||
/// the lifetime of this object.
|
||||
///
|
||||
/// This trait is unsafe because interfaces that use this trait depend on the base pointer and size
|
||||
/// being accurate.
|
||||
pub unsafe trait IntoIovec {
|
||||
/// Gets the base pointer of this `iovec`.
|
||||
fn as_ptr(&self) -> *const c_void;
|
||||
|
||||
/// Gets the size in bytes of this `iovec`.
|
||||
fn size(&self) -> usize;
|
||||
}
|
||||
|
||||
// Safe because this slice can not have another mutable reference and it's pointer and size are
|
||||
// guaranteed to be valid.
|
||||
unsafe impl<'a> IntoIovec for &'a [u8] {
|
||||
// Clippy false positive: https://github.com/rust-lang/rust-clippy/issues/3480
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::useless_asref))]
|
||||
fn as_ptr(&self) -> *const c_void {
|
||||
self.as_ref().as_ptr() as *const c_void
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::io::Write;
|
||||
use std::mem::size_of;
|
||||
use std::os::raw::c_long;
|
||||
use std::os::unix::net::UnixDatagram;
|
||||
use std::slice::from_raw_parts;
|
||||
|
||||
use libc::cmsghdr;
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[test]
|
||||
fn buffer_len() {
|
||||
assert_eq!(CMSG_SPACE!(0 * size_of::<RawFd>()), size_of::<cmsghdr>());
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(1 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>()
|
||||
);
|
||||
if size_of::<RawFd>() == 4 {
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(2 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>()
|
||||
);
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(3 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>() * 2
|
||||
);
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(4 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>() * 2
|
||||
);
|
||||
} else if size_of::<RawFd>() == 8 {
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(2 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>() * 2
|
||||
);
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(3 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>() * 3
|
||||
);
|
||||
assert_eq!(
|
||||
CMSG_SPACE!(4 * size_of::<RawFd>()),
|
||||
size_of::<cmsghdr>() + size_of::<c_long>() * 4
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_recv_no_fd() {
|
||||
let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
|
||||
|
||||
let write_count = s1
|
||||
.send_with_fds(&[[1u8, 1, 2].as_ref(), [21u8, 34, 55].as_ref()], &[])
|
||||
.expect("failed to send data");
|
||||
|
||||
assert_eq!(write_count, 6);
|
||||
|
||||
let mut buf = [0u8; 6];
|
||||
let mut files = [0; 1];
|
||||
let mut iovecs = [iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf.len(),
|
||||
}];
|
||||
let (read_count, file_count) = s2
|
||||
.recv_with_fds(&mut iovecs[..], &mut files)
|
||||
.expect("failed to recv data");
|
||||
|
||||
assert_eq!(read_count, 6);
|
||||
assert_eq!(file_count, 0);
|
||||
assert_eq!(buf, [1, 1, 2, 21, 34, 55]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_recv_only_fd() {
|
||||
let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
|
||||
|
||||
let evt = EventFd::new(0).expect("failed to create eventfd");
|
||||
let write_count = s1
|
||||
.send_with_fd([].as_ref(), evt.as_raw_fd())
|
||||
.expect("failed to send fd");
|
||||
|
||||
assert_eq!(write_count, 0);
|
||||
|
||||
let (read_count, file_opt) = s2.recv_with_fd(&mut []).expect("failed to recv fd");
|
||||
|
||||
let mut file = file_opt.unwrap();
|
||||
|
||||
assert_eq!(read_count, 0);
|
||||
assert!(file.as_raw_fd() >= 0);
|
||||
assert_ne!(file.as_raw_fd(), s1.as_raw_fd());
|
||||
assert_ne!(file.as_raw_fd(), s2.as_raw_fd());
|
||||
assert_ne!(file.as_raw_fd(), evt.as_raw_fd());
|
||||
|
||||
file.write(unsafe { from_raw_parts(&1203u64 as *const u64 as *const u8, 8) })
|
||||
.expect("failed to write to sent fd");
|
||||
|
||||
assert_eq!(evt.read().expect("failed to read from eventfd"), 1203);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_recv_with_fd() {
|
||||
let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
|
||||
|
||||
let evt = EventFd::new(0).expect("failed to create eventfd");
|
||||
let write_count = s1
|
||||
.send_with_fds(&[[237].as_ref()], &[evt.as_raw_fd()])
|
||||
.expect("failed to send fd");
|
||||
|
||||
assert_eq!(write_count, 1);
|
||||
|
||||
let mut files = [0; 2];
|
||||
let mut buf = [0u8];
|
||||
let mut iovecs = [iovec {
|
||||
iov_base: buf.as_mut_ptr() as *mut c_void,
|
||||
iov_len: buf.len(),
|
||||
}];
|
||||
let (read_count, file_count) = s2
|
||||
.recv_with_fds(&mut iovecs[..], &mut files)
|
||||
.expect("failed to recv fd");
|
||||
|
||||
assert_eq!(read_count, 1);
|
||||
assert_eq!(buf[0], 237);
|
||||
assert_eq!(file_count, 1);
|
||||
assert!(files[0] >= 0);
|
||||
assert_ne!(files[0], s1.as_raw_fd());
|
||||
assert_ne!(files[0], s2.as_raw_fd());
|
||||
assert_ne!(files[0], evt.as_raw_fd());
|
||||
|
||||
let mut file = unsafe { File::from_raw_fd(files[0]) };
|
||||
|
||||
file.write(unsafe { from_raw_parts(&1203u64 as *const u64 as *const u8, 8) })
|
||||
.expect("failed to write to sent fd");
|
||||
|
||||
assert_eq!(evt.read().expect("failed to read from eventfd"), 1203);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
//
|
||||
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD file.
|
||||
|
||||
//! Trait to control vhost-vsock backend drivers.
|
||||
|
||||
use crate::backend::VhostBackend;
|
||||
use crate::Result;
|
||||
|
||||
/// Trait to control vhost-vsock backend drivers.
|
||||
pub trait VhostVsock: VhostBackend {
|
||||
/// Set the CID for the guest.
|
||||
/// This number is used for routing all data destined for running in the guest.
|
||||
/// Each guest on a hypervisor must have an unique CID.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `cid` - CID to assign to the guest
|
||||
fn set_guest_cid(&mut self, cid: u64) -> Result<()>;
|
||||
|
||||
/// Tell the VHOST driver to start performing data transfer.
|
||||
fn start(&mut self) -> Result<()>;
|
||||
|
||||
/// Tell the VHOST driver to stop performing data transfer.
|
||||
fn stop(&mut self) -> Result<()>;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user