mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
600 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 |
53
.github/workflows/build.yaml
vendored
53
.github/workflows/build.yaml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Cloud Hypervisor Actions
|
name: Cloud Hypervisor Build
|
||||||
on: [pull_request, create]
|
on: [pull_request, create]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -12,6 +12,9 @@ jobs:
|
|||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly
|
||||||
|
target:
|
||||||
|
- x86_64-unknown-linux-gnu
|
||||||
|
- x86_64-unknown-linux-musl
|
||||||
steps:
|
steps:
|
||||||
- name: Code checkout
|
- name: Code checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -19,52 +22,8 @@ jobs:
|
|||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
|
target: ${{ matrix.target }}
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --release
|
run: cargo build --release --target=${{ matrix.target }}
|
||||||
|
|
||||||
release:
|
|
||||||
if: github.event_name == 'create' && github.event.ref_type == 'tag'
|
|
||||||
name: Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Code checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Install Rust toolchain (stable)
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --release
|
|
||||||
- name: Create Release
|
|
||||||
id: create_release
|
|
||||||
uses: actions/create-release@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
release_name: ${{ github.ref }}
|
|
||||||
draft: true
|
|
||||||
prerelease: true
|
|
||||||
- name: Upload cloud-hypervisor
|
|
||||||
id: upload-release-cloud-hypervisor
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: target/release/cloud-hypervisor
|
|
||||||
asset_name: cloud-hypervisor
|
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
- name: Upload ch-remote
|
|
||||||
id: upload-release-ch-remote
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
asset_path: target/release/ch-remote
|
|
||||||
asset_name: ch-remote
|
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
|
|||||||
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
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
**/Cargo.lock
|
**/Cargo.lock
|
||||||
|
|||||||
1397
Cargo.lock
generated
1397
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
26
Cargo.toml
26
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cloud-hypervisor"
|
name = "cloud-hypervisor"
|
||||||
version = "0.6.0"
|
version = "0.8.0"
|
||||||
authors = ["The Cloud Hypervisor Authors"]
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
default-run = "cloud-hypervisor"
|
default-run = "cloud-hypervisor"
|
||||||
@@ -8,31 +8,38 @@ build = "build.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arc-swap = ">=0.4.4"
|
arc-swap = ">=0.4.4"
|
||||||
clap = { version = "2.33.0", features=["wrap_help"] }
|
clap = { version = "2.33.1", features=["wrap_help"] }
|
||||||
epoll = ">=4.0.1"
|
epoll = ">=4.0.1"
|
||||||
futures = { version = "0.3.4", features = ["thread-pool"] }
|
futures = { version = "0.3.5", features = ["thread-pool"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
libc = "0.2.68"
|
libc = "0.2.71"
|
||||||
log = { version = "0.4.8", features = ["std"] }
|
log = { version = "0.4.8", features = ["std"] }
|
||||||
|
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.21.1" }
|
||||||
serde_json = ">=1.0.9"
|
serde_json = ">=1.0.9"
|
||||||
vhost_user_backend = { path = "vhost_user_backend"}
|
vhost_user_backend = { path = "vhost_user_backend"}
|
||||||
vhost_user_block = { path = "vhost_user_block"}
|
vhost_user_block = { path = "vhost_user_block"}
|
||||||
vhost_user_fs = { path = "vhost_user_fs"}
|
vhost_user_fs = { path = "vhost_user_fs"}
|
||||||
vhost_user_net = { path = "vhost_user_net"}
|
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" }
|
vmm = { path = "vmm" }
|
||||||
vm-device = { path = "vm-device" }
|
vm-device = { path = "vm-device" }
|
||||||
vm-memory = "0.1.0"
|
vm-memory = "0.2.1"
|
||||||
vmm-sys-util = "0.4.0"
|
vmm-sys-util = "0.6.1"
|
||||||
vm-virtio = { path = "vm-virtio" }
|
vm-virtio = { path = "vm-virtio" }
|
||||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
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]
|
[dev-dependencies]
|
||||||
ssh2 = "0.8.0"
|
ssh2 = "0.8.1"
|
||||||
dirs = "2.0.2"
|
dirs = "2.0.2"
|
||||||
credibility = "0.1.3"
|
credibility = "0.1.3"
|
||||||
tempdir= "0.3.7"
|
tempdir= "0.3.7"
|
||||||
lazy_static= "1.4.0"
|
lazy_static= "1.4.0"
|
||||||
|
tempfile = "3.1.0"
|
||||||
|
serde_json = ">=1.0.9"
|
||||||
|
net_util = { path = "net_util" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["acpi", "pci", "cmos"]
|
default = ["acpi", "pci", "cmos"]
|
||||||
@@ -40,6 +47,7 @@ acpi = ["vmm/acpi"]
|
|||||||
pci = ["vmm/pci_support"]
|
pci = ["vmm/pci_support"]
|
||||||
mmio = ["vmm/mmio_support"]
|
mmio = ["vmm/mmio_support"]
|
||||||
cmos = ["vmm/cmos"]
|
cmos = ["vmm/cmos"]
|
||||||
|
fwdebug = ["vmm/fwdebug"]
|
||||||
|
|
||||||
# Integration tests require a special environment to run in
|
# Integration tests require a special environment to run in
|
||||||
integration_tests = []
|
integration_tests = []
|
||||||
@@ -53,11 +61,11 @@ members = [
|
|||||||
"vmm",
|
"vmm",
|
||||||
"vm-virtio",
|
"vm-virtio",
|
||||||
"vm-device",
|
"vm-device",
|
||||||
|
"vm-migration",
|
||||||
"vhost_user_block",
|
"vhost_user_block",
|
||||||
"vhost_user_backend",
|
"vhost_user_backend",
|
||||||
"vhost_user_fs",
|
"vhost_user_fs",
|
||||||
"vhost_user_net",
|
"vhost_user_net",
|
||||||
"vfio",
|
|
||||||
"net_util",
|
"net_util",
|
||||||
"acpi_tables",
|
"acpi_tables",
|
||||||
"arch_gen",
|
"arch_gen",
|
||||||
|
|||||||
23
Jenkinsfile
vendored
23
Jenkinsfile
vendored
@@ -67,6 +67,29 @@ pipeline{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stage ('Worker build (musl)') {
|
||||||
|
agent { node { label 'bionic' } }
|
||||||
|
options {
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!
|
`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)
|
* [Clear Linux](https://download.clearlinux.org/current/) (cloudguest and kvm)
|
||||||
* [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
|
* [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.
|
Direct kernel boot to userspace should work with most rootfs.
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ authors = ["The Cloud Hypervisor Authors"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vm-memory = "0.1.0"
|
vm-memory = "0.2.1"
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ authors = ["The Chromium OS Authors"]
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
acpi = ["acpi_tables"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.3.4"
|
byteorder = "1.3.4"
|
||||||
kvm-bindings = "0.2.0"
|
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch" }
|
||||||
kvm-ioctls = "0.5.0"
|
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||||
libc = "0.2.68"
|
libc = "0.2.71"
|
||||||
vm-memory = { version = "0.1.0", features = ["backend-mmap"] }
|
vm-memory = { version = "0.2.1", features = ["backend-mmap"] }
|
||||||
|
|
||||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||||
arch_gen = { path = "../arch_gen" }
|
arch_gen = { path = "../arch_gen" }
|
||||||
|
|||||||
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.
|
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
/// Kernel command line start address.
|
//
|
||||||
pub const CMDLINE_START: usize = 0x0;
|
// Memory layout of Aarch64 guest:
|
||||||
/// Kernel command line start address maximum size.
|
//
|
||||||
pub const CMDLINE_MAX_SIZE: usize = 0x0;
|
// 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.
|
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// 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;
|
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.
|
/// Errors thrown while configuring aarch64 system.
|
||||||
pub fn arch_memory_regions(size: usize) -> Vec<(GuestAddress, usize, RegionType)> {
|
#[derive(Debug)]
|
||||||
vec![(GuestAddress(0), size, RegionType::Ram)]
|
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.
|
impl From<Error> for super::Error {
|
||||||
pub fn configure_system(
|
fn from(e: Error) -> super::Error {
|
||||||
_guest_mem: &GuestMemory,
|
super::Error::AArch64Setup(e)
|
||||||
_cmdline_addr: GuestAddress,
|
}
|
||||||
_cmdline_size: usize,
|
}
|
||||||
_num_cpus: u8,
|
|
||||||
_rsdp_addr: Option<GuestAddress>,
|
#[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<()> {
|
) -> 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stub function that needs to be implemented when aarch64 functionality is added.
|
/// Returns the memory address where the kernel could be loaded.
|
||||||
pub fn get_reserved_mem_addr() -> usize {
|
pub fn get_kernel_start() -> u64 {
|
||||||
0
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
// Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
|
||||||
// Copyright © 2020, Oracle and/or its affiliates.
|
// Copyright © 2020, Oracle and/or its affiliates.
|
||||||
//
|
//
|
||||||
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
//! Implements platform specific functionality.
|
||||||
|
//! Supported platforms: x86_64, aarch64.
|
||||||
#![allow(
|
#![allow(
|
||||||
clippy::unreadable_literal,
|
clippy::unreadable_literal,
|
||||||
clippy::redundant_static_lifetimes,
|
clippy::redundant_static_lifetimes,
|
||||||
@@ -13,22 +16,29 @@
|
|||||||
|
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate kvm_bindings;
|
extern crate kvm_bindings;
|
||||||
|
extern crate kvm_ioctls;
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
|
||||||
|
extern crate vm_memory;
|
||||||
|
|
||||||
#[cfg(feature = "acpi")]
|
#[cfg(feature = "acpi")]
|
||||||
extern crate acpi_tables;
|
extern crate acpi_tables;
|
||||||
extern crate arch_gen;
|
extern crate arch_gen;
|
||||||
extern crate kvm_ioctls;
|
|
||||||
extern crate linux_loader;
|
extern crate linux_loader;
|
||||||
extern crate vm_memory;
|
|
||||||
|
|
||||||
|
use kvm_ioctls::*;
|
||||||
|
use std::fmt;
|
||||||
use std::result;
|
use std::result;
|
||||||
|
|
||||||
|
/// Type for returning error code.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
/// X86_64 specific error triggered during system configuration.
|
/// X86_64 specific error triggered during system configuration.
|
||||||
X86_64Setup(x86_64::Error),
|
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.
|
/// The zero page extends past the end of guest_mem.
|
||||||
ZeroPagePastRamEnd,
|
ZeroPagePastRamEnd,
|
||||||
/// Error writing the zero page of guest memory.
|
/// Error writing the zero page of guest memory.
|
||||||
@@ -41,10 +51,21 @@ pub enum Error {
|
|||||||
StartInfoPastRamEnd,
|
StartInfoPastRamEnd,
|
||||||
/// Error writing hvm_start_info to guest memory.
|
/// Error writing hvm_start_info to guest memory.
|
||||||
StartInfoSetup,
|
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>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
/// Type for memory region types.
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum RegionType {
|
pub enum RegionType {
|
||||||
/// RAM type
|
/// RAM type
|
||||||
Ram,
|
Ram,
|
||||||
@@ -62,13 +83,15 @@ pub enum RegionType {
|
|||||||
Reserved,
|
Reserved,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Module for aarch64 related functionality.
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub mod aarch64;
|
pub mod aarch64;
|
||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub use aarch64::{
|
pub use aarch64::{
|
||||||
arch_memory_regions, configure_system, get_reserved_mem_addr, layout::CMDLINE_MAX_SIZE,
|
arch_memory_regions, check_required_kvm_extensions, configure_system, configure_vcpu,
|
||||||
layout::CMDLINE_START,
|
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")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
@@ -76,6 +99,66 @@ pub mod x86_64;
|
|||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub use x86_64::{
|
pub use x86_64::{
|
||||||
arch_memory_regions, configure_system, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START,
|
arch_memory_regions, check_required_kvm_extensions, configure_system, configure_vcpu,
|
||||||
BootProtocol, EntryPoint,
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ pub const BOOT_IDT_START: GuestAddress = GuestAddress(0x520);
|
|||||||
/// Address for the hvm_start_info struct used in PVH boot
|
/// Address for the hvm_start_info struct used in PVH boot
|
||||||
pub const PVH_INFO_START: GuestAddress = GuestAddress(0x6000);
|
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
|
/// Address of memory map table used in PVH boot. Can overlap
|
||||||
/// with the zero page address since they are mutually exclusive.
|
/// with the zero page address since they are mutually exclusive.
|
||||||
pub const MEMMAP_START: GuestAddress = GuestAddress(0x7000);
|
pub const MEMMAP_START: GuestAddress = GuestAddress(0x7000);
|
||||||
@@ -43,9 +47,10 @@ pub const BOOT_STACK_START: GuestAddress = GuestAddress(0x8000);
|
|||||||
pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0);
|
pub const BOOT_STACK_POINTER: GuestAddress = GuestAddress(0x8ff0);
|
||||||
|
|
||||||
// Initial pagetables.
|
// Initial pagetables.
|
||||||
pub const PML4_START: GuestAddress = GuestAddress(0x9000);
|
pub const PML5_START: GuestAddress = GuestAddress(0x9000);
|
||||||
pub const PDPTE_START: GuestAddress = GuestAddress(0xa000);
|
pub const PML4_START: GuestAddress = GuestAddress(0xa000);
|
||||||
pub const PDE_START: GuestAddress = GuestAddress(0xb000);
|
pub const PDPTE_START: GuestAddress = GuestAddress(0xb000);
|
||||||
|
pub const PDE_START: GuestAddress = GuestAddress(0xc000);
|
||||||
|
|
||||||
/// Kernel command line start address.
|
/// Kernel command line start address.
|
||||||
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
pub const CMDLINE_START: GuestAddress = GuestAddress(0x20000);
|
||||||
@@ -74,18 +79,18 @@ pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
|
|||||||
|
|
||||||
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
|
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
|
||||||
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
|
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 ==
|
// == Fixed constants within the "32-bit reserved" range ==
|
||||||
|
|
||||||
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
|
// 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_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)
|
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
|
||||||
pub const PCI_MMCONFIG_START: GuestAddress =
|
pub const PCI_MMCONFIG_START: GuestAddress =
|
||||||
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
|
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
|
// IOAPIC
|
||||||
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
||||||
|
|||||||
@@ -10,15 +10,21 @@
|
|||||||
mod gdt;
|
mod gdt;
|
||||||
pub mod interrupts;
|
pub mod interrupts;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
#[cfg(not(feature = "acpi"))]
|
||||||
mod mptable;
|
mod mptable;
|
||||||
pub mod regs;
|
pub mod regs;
|
||||||
|
use crate::InitramfsConfig;
|
||||||
use crate::RegionType;
|
use crate::RegionType;
|
||||||
|
use kvm_bindings::CpuId;
|
||||||
|
use kvm_ioctls::*;
|
||||||
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
use linux_loader::loader::bootparam::{boot_params, setup_header};
|
||||||
use linux_loader::loader::start_info::{hvm_memmap_table_entry, hvm_start_info};
|
use linux_loader::loader::elf::start_info::{
|
||||||
|
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
|
||||||
|
};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestUsize,
|
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||||
|
GuestMemoryMmap, GuestMemoryRegion, GuestUsize,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
@@ -45,6 +51,8 @@ pub struct EntryPoint {
|
|||||||
pub entry_addr: GuestAddress,
|
pub entry_addr: GuestAddress,
|
||||||
/// Specifies which boot protocol to use
|
/// Specifies which boot protocol to use
|
||||||
pub protocol: BootProtocol,
|
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_RAM: u32 = 1;
|
||||||
@@ -66,6 +74,11 @@ struct MemmapTableEntryWrapper(hvm_memmap_table_entry);
|
|||||||
|
|
||||||
unsafe impl ByteValued for MemmapTableEntryWrapper {}
|
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
|
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||||
// trait (in this case `DataInit`) where:
|
// trait (in this case `DataInit`) where:
|
||||||
// * the type that is implementing the trait is foreign or
|
// * the type that is implementing the trait is foreign or
|
||||||
@@ -81,8 +94,27 @@ unsafe impl ByteValued for BootParamsWrapper {}
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Invalid e820 setup params.
|
/// Invalid e820 setup params.
|
||||||
E820Configuration,
|
E820Configuration,
|
||||||
|
#[cfg(not(feature = "acpi"))]
|
||||||
/// Error writing MP table to memory.
|
/// Error writing MP table to memory.
|
||||||
MpTableSetup(mptable::Error),
|
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 {
|
impl From<Error> for super::Error {
|
||||||
@@ -91,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.
|
/// Returns a Vec of the valid memory addresses.
|
||||||
/// These should be used to configure the GuestMemory structure for the platform.
|
/// These should be used to configure the GuestMemory structure for the platform.
|
||||||
/// For x86_64 all addresses are valid from the start of the kernel except a
|
/// For x86_64 all addresses are valid from the start of the kernel except a
|
||||||
@@ -151,20 +290,36 @@ pub fn configure_system(
|
|||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline_addr: GuestAddress,
|
cmdline_addr: GuestAddress,
|
||||||
cmdline_size: usize,
|
cmdline_size: usize,
|
||||||
num_cpus: u8,
|
initramfs: &Option<InitramfsConfig>,
|
||||||
|
_num_cpus: u8,
|
||||||
setup_hdr: Option<setup_header>,
|
setup_hdr: Option<setup_header>,
|
||||||
rsdp_addr: Option<GuestAddress>,
|
rsdp_addr: Option<GuestAddress>,
|
||||||
boot_prot: BootProtocol,
|
boot_prot: BootProtocol,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
||||||
mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?;
|
#[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 {
|
match boot_prot {
|
||||||
BootProtocol::PvhBoot => {
|
BootProtocol::PvhBoot => {
|
||||||
configure_pvh(guest_mem, cmdline_addr, rsdp_addr)?;
|
configure_pvh(guest_mem, cmdline_addr, initramfs, rsdp_addr)?;
|
||||||
}
|
}
|
||||||
BootProtocol::LinuxBoot => {
|
BootProtocol::LinuxBoot => {
|
||||||
configure_64bit_boot(guest_mem, cmdline_addr, cmdline_size, setup_hdr, rsdp_addr)?;
|
configure_64bit_boot(
|
||||||
|
guest_mem,
|
||||||
|
cmdline_addr,
|
||||||
|
cmdline_size,
|
||||||
|
initramfs,
|
||||||
|
setup_hdr,
|
||||||
|
rsdp_addr,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +329,7 @@ pub fn configure_system(
|
|||||||
fn configure_pvh(
|
fn configure_pvh(
|
||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline_addr: GuestAddress,
|
cmdline_addr: GuestAddress,
|
||||||
|
initramfs: &Option<InitramfsConfig>,
|
||||||
rsdp_addr: Option<GuestAddress>,
|
rsdp_addr: Option<GuestAddress>,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578;
|
||||||
@@ -190,6 +346,24 @@ fn configure_pvh(
|
|||||||
start_info.0.rsdp_paddr = rsdp_addr.0;
|
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
|
// Vector to hold the memory maps which needs to be written to guest memory
|
||||||
// at MEMMAP_START after all of the mappings are recorded.
|
// at MEMMAP_START after all of the mappings are recorded.
|
||||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||||
@@ -293,6 +467,7 @@ fn configure_64bit_boot(
|
|||||||
guest_mem: &GuestMemoryMmap,
|
guest_mem: &GuestMemoryMmap,
|
||||||
cmdline_addr: GuestAddress,
|
cmdline_addr: GuestAddress,
|
||||||
cmdline_size: usize,
|
cmdline_size: usize,
|
||||||
|
initramfs: &Option<InitramfsConfig>,
|
||||||
setup_hdr: Option<setup_header>,
|
setup_hdr: Option<setup_header>,
|
||||||
rsdp_addr: Option<GuestAddress>,
|
rsdp_addr: Option<GuestAddress>,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
@@ -319,6 +494,11 @@ fn configure_64bit_boot(
|
|||||||
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
|
||||||
params.0.hdr.cmdline_size = cmdline_size as u32;
|
params.0.hdr.cmdline_size = cmdline_size as u32;
|
||||||
|
|
||||||
|
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)?;
|
add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||||
|
|
||||||
let mem_end = guest_mem.last_addr();
|
let mem_end = guest_mem.last_addr();
|
||||||
@@ -388,6 +568,66 @@ fn add_e820_entry(
|
|||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -417,9 +657,10 @@ mod tests {
|
|||||||
&gm,
|
&gm,
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
0,
|
0,
|
||||||
|
&None,
|
||||||
1,
|
1,
|
||||||
None,
|
None,
|
||||||
None,
|
Some(layout::RSDP_POINTER),
|
||||||
BootProtocol::LinuxBoot,
|
BootProtocol::LinuxBoot,
|
||||||
);
|
);
|
||||||
assert!(config_err.is_err());
|
assert!(config_err.is_err());
|
||||||
@@ -437,6 +678,7 @@ mod tests {
|
|||||||
&gm,
|
&gm,
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
0,
|
0,
|
||||||
|
&None,
|
||||||
no_vcpus,
|
no_vcpus,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@@ -448,6 +690,7 @@ mod tests {
|
|||||||
&gm,
|
&gm,
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
0,
|
0,
|
||||||
|
&None,
|
||||||
no_vcpus,
|
no_vcpus,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@@ -468,6 +711,7 @@ mod tests {
|
|||||||
&gm,
|
&gm,
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
0,
|
0,
|
||||||
|
&None,
|
||||||
no_vcpus,
|
no_vcpus,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@@ -479,6 +723,7 @@ mod tests {
|
|||||||
&gm,
|
&gm,
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
0,
|
0,
|
||||||
|
&None,
|
||||||
no_vcpus,
|
no_vcpus,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@@ -499,6 +744,7 @@ mod tests {
|
|||||||
&gm,
|
&gm,
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
0,
|
0,
|
||||||
|
&None,
|
||||||
no_vcpus,
|
no_vcpus,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@@ -510,6 +756,7 @@ mod tests {
|
|||||||
&gm,
|
&gm,
|
||||||
GuestAddress(0),
|
GuestAddress(0),
|
||||||
0,
|
0,
|
||||||
|
&None,
|
||||||
no_vcpus,
|
no_vcpus,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ use super::BootProtocol;
|
|||||||
use arch_gen::x86::msr_index;
|
use arch_gen::x86::msr_index;
|
||||||
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_regs, kvm_sregs, Msrs};
|
use kvm_bindings::{kvm_fpu, kvm_msr_entry, kvm_regs, kvm_sregs, Msrs};
|
||||||
use kvm_ioctls::VcpuFd;
|
use kvm_ioctls::VcpuFd;
|
||||||
use layout::{BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PVH_INFO_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};
|
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||||
|
|
||||||
// MTRR constants
|
// MTRR constants
|
||||||
@@ -45,6 +47,8 @@ pub enum Error {
|
|||||||
WritePDEAddress(GuestMemoryError),
|
WritePDEAddress(GuestMemoryError),
|
||||||
/// Writing PML4 to RAM failed.
|
/// Writing PML4 to RAM failed.
|
||||||
WritePML4Address(GuestMemoryError),
|
WritePML4Address(GuestMemoryError),
|
||||||
|
/// Writing PML5 to RAM failed.
|
||||||
|
WritePML5Address(GuestMemoryError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
@@ -70,7 +74,7 @@ pub fn setup_fpu(vcpu: &VcpuFd) -> Result<()> {
|
|||||||
///
|
///
|
||||||
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
|
||||||
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
pub fn setup_msrs(vcpu: &VcpuFd) -> Result<()> {
|
||||||
vcpu.set_msrs(&create_msr_entries())
|
vcpu.set_msrs(&boot_msr_entries())
|
||||||
.map_err(Error::SetModelSpecificRegisters)?;
|
.map_err(Error::SetModelSpecificRegisters)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -138,6 +142,7 @@ const EFER_LME: u64 = 0x100;
|
|||||||
const X86_CR0_PE: u64 = 0x1;
|
const X86_CR0_PE: u64 = 0x1;
|
||||||
const X86_CR0_PG: u64 = 0x80000000;
|
const X86_CR0_PG: u64 = 0x80000000;
|
||||||
const X86_CR4_PAE: u64 = 0x20;
|
const X86_CR4_PAE: u64 = 0x20;
|
||||||
|
const X86_CR4_LA57: u64 = 0x1000;
|
||||||
|
|
||||||
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||||
let boot_gdt_addr = BOOT_GDT_START;
|
let boot_gdt_addr = BOOT_GDT_START;
|
||||||
@@ -220,7 +225,18 @@ fn configure_segments_and_sregs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()> {
|
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)
|
// Entry covering VA [0..512GB)
|
||||||
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
|
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
|
||||||
@@ -229,6 +245,7 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
|
|||||||
// Entry covering VA [0..1GB)
|
// Entry covering VA [0..1GB)
|
||||||
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
|
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
|
||||||
.map_err(Error::WritePDPTEAddress)?;
|
.map_err(Error::WritePDPTEAddress)?;
|
||||||
|
|
||||||
// 512 2MB entries together covering VA [0..1GB). Note we are assuming
|
// 512 2MB entries together covering VA [0..1GB). Note we are assuming
|
||||||
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
|
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
|
||||||
for i in 0..512 {
|
for i in 0..512 {
|
||||||
@@ -236,74 +253,49 @@ fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut kvm_sregs) -> Result<()>
|
|||||||
.map_err(Error::WritePDEAddress)?;
|
.map_err(Error::WritePDEAddress)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
sregs.cr3 = PML4_START.raw_value();
|
|
||||||
sregs.cr4 |= X86_CR4_PAE;
|
sregs.cr4 |= X86_CR4_PAE;
|
||||||
sregs.cr0 |= X86_CR0_PG;
|
sregs.cr0 |= X86_CR0_PG;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_msr_entries() -> Msrs {
|
macro_rules! kvm_msr {
|
||||||
let mut entries = Vec::<kvm_msr_entry>::new();
|
($msr:expr) => {
|
||||||
|
kvm_msr_entry {
|
||||||
|
index: $msr,
|
||||||
|
data: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
entries.push(kvm_msr_entry {
|
macro_rules! kvm_msr_data {
|
||||||
index: msr_index::MSR_IA32_SYSENTER_CS,
|
($msr:expr, $data:expr) => {
|
||||||
data: 0x0,
|
kvm_msr_entry {
|
||||||
..Default::default()
|
index: $msr,
|
||||||
});
|
data: $data,
|
||||||
entries.push(kvm_msr_entry {
|
..Default::default()
|
||||||
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()
|
|
||||||
});
|
|
||||||
|
|
||||||
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)]
|
#[cfg(test)]
|
||||||
@@ -393,8 +385,11 @@ mod tests {
|
|||||||
let gm = create_guest_mem();
|
let gm = create_guest_mem();
|
||||||
setup_page_tables(&gm, &mut sregs).unwrap();
|
setup_page_tables(&gm, &mut sregs).unwrap();
|
||||||
|
|
||||||
assert_eq!(0xa003, read_u64(&gm, PML4_START));
|
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
|
||||||
assert_eq!(0xb003, read_u64(&gm, PDPTE_START));
|
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 {
|
for i in 0..512 {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(i << 21) + 0x83u64,
|
(i << 21) + 0x83u64,
|
||||||
@@ -402,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_CR4_PAE, sregs.cr4);
|
||||||
assert_eq!(X86_CR0_PG, sregs.cr0);
|
assert_eq!(X86_CR0_PG, sregs.cr0);
|
||||||
}
|
}
|
||||||
@@ -451,7 +450,7 @@ mod tests {
|
|||||||
// Official entries that were setup when we did setup_msrs. We need to assert that the
|
// 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
|
// tenth one (i.e the one with index msr_index::MSR_IA32_MISC_ENABLE has the data we
|
||||||
// expect.
|
// expect.
|
||||||
let entry_vec = create_msr_entries();
|
let entry_vec = boot_msr_entries();
|
||||||
assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]);
|
assert_eq!(entry_vec.as_slice()[9], msrs.as_slice()[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,19 @@ version = "0.1.0"
|
|||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
bitflags = ">=1.2.1"
|
bitflags = ">=1.2.1"
|
||||||
byteorder = "1.3.4"
|
byteorder = "1.3.4"
|
||||||
epoll = ">=4.0.1"
|
epoll = ">=4.0.1"
|
||||||
libc = "0.2.68"
|
libc = "0.2.71"
|
||||||
log = "0.4.8"
|
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-device = { path = "../vm-device" }
|
||||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||||
vm-memory = "0.1.0"
|
vm-memory = "0.2.1"
|
||||||
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = ">=0.3.1"
|
vmm-sys-util = ">=0.3.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
@@ -21,3 +26,4 @@ tempfile = "3.1.0"
|
|||||||
default = []
|
default = []
|
||||||
acpi = ["acpi_tables"]
|
acpi = ["acpi_tables"]
|
||||||
cmos = []
|
cmos = []
|
||||||
|
fwdebug = []
|
||||||
|
|||||||
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
|
// 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.
|
// 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 crate::BusDevice;
|
||||||
|
use anyhow::anyhow;
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use std::io;
|
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
@@ -19,28 +20,14 @@ use vm_device::interrupt::{
|
|||||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||||
};
|
};
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||||
|
Transportable,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub enum Error {
|
#[serde(remote = "GuestAddress")]
|
||||||
/// Invalid destination mode.
|
pub struct GuestAddressDef(pub u64);
|
||||||
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>;
|
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;
|
*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;
|
pub const NUM_IOAPIC_PINS: usize = 24;
|
||||||
const IOAPIC_VERSION_ID: u32 = 0x0017_0011;
|
const IOAPIC_VERSION_ID: u32 = 0x0017_0011;
|
||||||
|
|
||||||
@@ -166,13 +134,25 @@ fn decode_irq_from_selector(selector: u8) -> (usize, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Ioapic {
|
pub struct Ioapic {
|
||||||
id: u32,
|
id: String,
|
||||||
|
id_reg: u32,
|
||||||
reg_sel: u32,
|
reg_sel: u32,
|
||||||
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
reg_entries: [RedirectionTableEntry; NUM_IOAPIC_PINS],
|
||||||
|
used_entries: [bool; NUM_IOAPIC_PINS],
|
||||||
apic_address: GuestAddress,
|
apic_address: GuestAddress,
|
||||||
interrupt_source_group: Arc<Box<dyn InterruptSourceGroup>>,
|
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 {
|
impl BusDevice for Ioapic {
|
||||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||||
assert!(data.len() == 4);
|
assert!(data.len() == 4);
|
||||||
@@ -210,6 +190,7 @@ impl BusDevice for Ioapic {
|
|||||||
|
|
||||||
impl Ioapic {
|
impl Ioapic {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
id: String,
|
||||||
apic_address: GuestAddress,
|
apic_address: GuestAddress,
|
||||||
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
interrupt_manager: Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
) -> Result<Ioapic> {
|
) -> Result<Ioapic> {
|
||||||
@@ -225,43 +206,87 @@ impl Ioapic {
|
|||||||
.map_err(Error::EnableInterrupt)?;
|
.map_err(Error::EnableInterrupt)?;
|
||||||
|
|
||||||
Ok(Ioapic {
|
Ok(Ioapic {
|
||||||
id: 0,
|
id,
|
||||||
|
id_reg: 0,
|
||||||
reg_sel: 0,
|
reg_sel: 0,
|
||||||
reg_entries: [0; NUM_IOAPIC_PINS],
|
reg_entries: [0; NUM_IOAPIC_PINS],
|
||||||
|
used_entries: [false; NUM_IOAPIC_PINS],
|
||||||
apic_address,
|
apic_address,
|
||||||
interrupt_source_group,
|
interrupt_source_group,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ioapic must be informed about EOIs in order to deassert interrupts
|
fn ioapic_write(&mut self, val: u32) {
|
||||||
// already sent.
|
debug!("IOAPIC_W reg 0x{:x}, val 0x{:x}", self.reg_sel, val);
|
||||||
pub fn end_of_interrupt(&mut self, vec: u8) {
|
|
||||||
for i in 0..NUM_IOAPIC_PINS {
|
match self.reg_sel as u8 {
|
||||||
let entry = &mut self.reg_entries[i];
|
IOAPIC_REG_ID => self.id_reg = (val >> 24) & 0xf,
|
||||||
// Clear Remote IRR bit
|
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||||
if vector(*entry) == vec && trigger_mode(*entry) == 1 {
|
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||||
set_remote_irr(entry, 0);
|
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
|
fn state(&self) -> IoapicState {
|
||||||
// running guest.
|
IoapicState {
|
||||||
pub fn service_irq(&mut self, irq: usize) -> Result<()> {
|
id_reg: self.id_reg,
|
||||||
let entry = &mut self.reg_entries[irq];
|
reg_sel: self.reg_sel,
|
||||||
|
reg_entries: self.reg_entries,
|
||||||
self.interrupt_source_group
|
used_entries: self.used_entries,
|
||||||
.trigger(irq as InterruptIndex)
|
apic_address: self.apic_address,
|
||||||
.map_err(Error::TriggerInterrupt)?;
|
}
|
||||||
debug!("Interrupt successfully delivered");
|
}
|
||||||
|
|
||||||
// If trigger mode is level sensitive, set the Remote IRR bit.
|
fn set_state(&mut self, state: &IoapicState) -> Result<()> {
|
||||||
// It will be cleared when the EOI is received.
|
self.id_reg = state.id_reg;
|
||||||
if trigger_mode(*entry) == 1 {
|
self.reg_sel = state.reg_sel;
|
||||||
set_remote_irr(entry, 1);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -337,52 +362,84 @@ impl Ioapic {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn ioapic_write(&mut self, val: u32) {
|
impl InterruptController for Ioapic {
|
||||||
debug!("IOAPIC_W reg 0x{:x}, val 0x{:x}", self.reg_sel, val);
|
// The ioapic must be informed about EOIs in order to deassert interrupts
|
||||||
|
// already sent.
|
||||||
match self.reg_sel as u8 {
|
fn end_of_interrupt(&mut self, vec: u8) {
|
||||||
IOAPIC_REG_ID => self.id = (val >> 24) & 0xf,
|
for i in 0..NUM_IOAPIC_PINS {
|
||||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
let entry = &mut self.reg_entries[i];
|
||||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
// Clear Remote IRR bit
|
||||||
if is_high_bits {
|
if vector(*entry) == vec && trigger_mode(*entry) == 1 {
|
||||||
self.reg_entries[index] &= 0xffff_ffff;
|
set_remote_irr(entry, 0);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => error!("IOAPIC: invalid write to register offset"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ioapic_read(&self) -> u32 {
|
// This should be called anytime an interrupt needs to be injected into the
|
||||||
debug!("IOAPIC_R reg 0x{:x}", self.reg_sel);
|
// running guest.
|
||||||
|
fn service_irq(&mut self, irq: usize) -> Result<()> {
|
||||||
|
let entry = &mut self.reg_entries[irq];
|
||||||
|
|
||||||
match self.reg_sel as u8 {
|
self.interrupt_source_group
|
||||||
IOAPIC_REG_VERSION => IOAPIC_VERSION_ID,
|
.trigger(irq as InterruptIndex)
|
||||||
IOAPIC_REG_ID | IOAPIC_REG_ARBITRATION_ID => (self.id & 0xf) << 24,
|
.map_err(Error::TriggerInterrupt)?;
|
||||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
debug!("Interrupt successfully delivered");
|
||||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
|
||||||
if is_high_bits {
|
// If trigger mode is level sensitive, set the Remote IRR bit.
|
||||||
(self.reg_entries[index] >> 32) as u32
|
// It will be cleared when the EOI is received.
|
||||||
} else {
|
if trigger_mode(*entry) == 1 {
|
||||||
(self.reg_entries[index] & 0xffff_ffff) as u32
|
set_remote_irr(entry, 1);
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error!("IOAPIC: invalid read from register offset");
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 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")]
|
#[cfg(feature = "cmos")]
|
||||||
mod cmos;
|
mod cmos;
|
||||||
|
#[cfg(feature = "fwdebug")]
|
||||||
|
mod fwdebug;
|
||||||
mod i8042;
|
mod i8042;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
mod rtc_pl031;
|
||||||
mod serial;
|
mod serial;
|
||||||
|
|
||||||
#[cfg(feature = "cmos")]
|
#[cfg(feature = "cmos")]
|
||||||
pub use self::cmos::Cmos;
|
pub use self::cmos::Cmos;
|
||||||
|
#[cfg(feature = "fwdebug")]
|
||||||
|
pub use self::fwdebug::FwDebugDevice;
|
||||||
pub use self::i8042::I8042Device;
|
pub use self::i8042::I8042Device;
|
||||||
pub use self::serial::Serial;
|
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.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use crate::BusDevice;
|
use crate::BusDevice;
|
||||||
|
use anyhow::anyhow;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{io, result};
|
use std::{io, result};
|
||||||
use vm_device::interrupt::InterruptSourceGroup;
|
use vm_device::interrupt::InterruptSourceGroup;
|
||||||
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||||
|
Transportable,
|
||||||
|
};
|
||||||
use vmm_sys_util::errno::Result;
|
use vmm_sys_util::errno::Result;
|
||||||
|
|
||||||
const LOOP_SIZE: usize = 0x40;
|
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
|
/// This can optionally write the guest's output to a Write trait object. To send input to the
|
||||||
/// guest, use `queue_input_bytes`.
|
/// guest, use `queue_input_bytes`.
|
||||||
pub struct Serial {
|
pub struct Serial {
|
||||||
|
id: String,
|
||||||
interrupt_enable: u8,
|
interrupt_enable: u8,
|
||||||
interrupt_identification: u8,
|
interrupt_identification: u8,
|
||||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||||
@@ -68,12 +74,27 @@ pub struct Serial {
|
|||||||
out: Option<Box<dyn io::Write + Send>>,
|
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 {
|
impl Serial {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
id: String,
|
||||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||||
out: Option<Box<dyn io::Write + Send>>,
|
out: Option<Box<dyn io::Write + Send>>,
|
||||||
) -> Serial {
|
) -> Serial {
|
||||||
Serial {
|
Serial {
|
||||||
|
id,
|
||||||
interrupt_enable: 0,
|
interrupt_enable: 0,
|
||||||
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
interrupt_identification: DEFAULT_INTERRUPT_IDENTIFICATION,
|
||||||
interrupt,
|
interrupt,
|
||||||
@@ -90,15 +111,16 @@ impl Serial {
|
|||||||
|
|
||||||
/// Constructs a Serial port ready for output.
|
/// Constructs a Serial port ready for output.
|
||||||
pub fn new_out(
|
pub fn new_out(
|
||||||
|
id: String,
|
||||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||||
out: Box<dyn io::Write + Send>,
|
out: Box<dyn io::Write + Send>,
|
||||||
) -> Serial {
|
) -> Serial {
|
||||||
Self::new(interrupt, Some(out))
|
Self::new(id, interrupt, Some(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a Serial port with no connected output.
|
/// Constructs a Serial port with no connected output.
|
||||||
pub fn new_sink(interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
|
pub fn new_sink(id: String, interrupt: Arc<Box<dyn InterruptSourceGroup>>) -> Serial {
|
||||||
Self::new(interrupt, None)
|
Self::new(id, interrupt, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queues raw bytes for the guest to read and signals the interrupt if the line status would
|
/// Queues raw bytes for the guest to read and signals the interrupt if the line status would
|
||||||
@@ -194,6 +216,32 @@ impl Serial {
|
|||||||
}
|
}
|
||||||
Ok(())
|
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 {
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -244,6 +337,8 @@ mod tests {
|
|||||||
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
use vm_device::interrupt::{InterruptIndex, InterruptSourceConfig};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
const SERIAL_NAME: &str = "serial";
|
||||||
|
|
||||||
struct TestInterrupt {
|
struct TestInterrupt {
|
||||||
event_fd: EventFd,
|
event_fd: EventFd,
|
||||||
}
|
}
|
||||||
@@ -294,6 +389,7 @@ mod tests {
|
|||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let serial_out = SharedBuffer::new();
|
let serial_out = SharedBuffer::new();
|
||||||
let mut serial = Serial::new_out(
|
let mut serial = Serial::new_out(
|
||||||
|
String::from(SERIAL_NAME),
|
||||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||||
Box::new(serial_out.clone()),
|
Box::new(serial_out.clone()),
|
||||||
);
|
);
|
||||||
@@ -313,6 +409,7 @@ mod tests {
|
|||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let serial_out = SharedBuffer::new();
|
let serial_out = SharedBuffer::new();
|
||||||
let mut serial = Serial::new_out(
|
let mut serial = Serial::new_out(
|
||||||
|
String::from(SERIAL_NAME),
|
||||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||||
Box::new(serial_out.clone()),
|
Box::new(serial_out.clone()),
|
||||||
);
|
);
|
||||||
@@ -350,9 +447,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn serial_thr() {
|
fn serial_thr() {
|
||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
let mut serial = Serial::new_sink(
|
||||||
intr_evt.try_clone().unwrap(),
|
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
|
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
|
||||||
// counter doesn't change (for 0 it blocks)
|
// counter doesn't change (for 0 it blocks)
|
||||||
@@ -371,9 +469,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn serial_dlab() {
|
fn serial_dlab() {
|
||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
let mut serial = Serial::new_sink(
|
||||||
intr_evt.try_clone().unwrap(),
|
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, LCR as u64, &[LCR_DLAB_BIT as u8]);
|
||||||
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
|
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
|
||||||
@@ -391,9 +490,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn serial_modem() {
|
fn serial_modem() {
|
||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
let mut serial = Serial::new_sink(
|
||||||
intr_evt.try_clone().unwrap(),
|
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, MCR as u64, &[MCR_LOOP_BIT as u8]);
|
||||||
serial.write(0, DATA as u64, &['a' as u8]);
|
serial.write(0, DATA as u64, &['a' as u8]);
|
||||||
@@ -416,9 +516,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn serial_scratch() {
|
fn serial_scratch() {
|
||||||
let intr_evt = EventFd::new(0).unwrap();
|
let intr_evt = EventFd::new(0).unwrap();
|
||||||
let mut serial = Serial::new_sink(Arc::new(Box::new(TestInterrupt::new(
|
let mut serial = Serial::new_sink(
|
||||||
intr_evt.try_clone().unwrap(),
|
String::from(SERIAL_NAME),
|
||||||
))));
|
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||||
|
);
|
||||||
|
|
||||||
serial.write(0, SCR as u64, &[0x12 as u8]);
|
serial.write(0, SCR as u64, &[0x12 as u8]);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
//! Emulates virtual and hardware devices.
|
//! Emulates virtual and hardware devices.
|
||||||
|
extern crate anyhow;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
@@ -15,9 +16,14 @@ extern crate libc;
|
|||||||
extern crate log;
|
extern crate log;
|
||||||
#[cfg(feature = "acpi")]
|
#[cfg(feature = "acpi")]
|
||||||
extern crate acpi_tables;
|
extern crate acpi_tables;
|
||||||
|
extern crate serde;
|
||||||
extern crate vm_device;
|
extern crate vm_device;
|
||||||
extern crate vm_memory;
|
extern crate vm_memory;
|
||||||
|
extern crate vm_migration;
|
||||||
extern crate vmm_sys_util;
|
extern crate vmm_sys_util;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
@@ -25,6 +31,10 @@ use std::io;
|
|||||||
#[cfg(feature = "acpi")]
|
#[cfg(feature = "acpi")]
|
||||||
mod acpi;
|
mod acpi;
|
||||||
mod bus;
|
mod bus;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
pub mod gic;
|
||||||
|
pub mod interrupt_controller;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub mod ioapic;
|
pub mod ioapic;
|
||||||
pub mod legacy;
|
pub mod legacy;
|
||||||
|
|
||||||
|
|||||||
10
docs/api.md
10
docs/api.md
@@ -81,8 +81,8 @@ Shut the VMM down | `/vmm.shutdown` | N/A | N/A
|
|||||||
Action | Endpoint | Request Body | Response Body | Prerequisites
|
Action | Endpoint | Request Body | Response Body | Prerequisites
|
||||||
-----------------------------------|---------------------|---------------------------|-------------------|---------------------------
|
-----------------------------------|---------------------|---------------------------|-------------------|---------------------------
|
||||||
Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet
|
Create the VM | `/vm.create` | `/schemas/VmConfig` | N/A | The VM is not created yet
|
||||||
Delete the VM | `/vm.delete` | N/A | N/A | The VM is created but not booted
|
Delete the VM | `/vm.delete` | N/A | N/A | N/A
|
||||||
Boot the VM | `/vm.boot` | N/A | N/A | The VM is created
|
Boot the VM | `/vm.boot` | N/A | N/A | The VM is created but not booted
|
||||||
Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted
|
Shut the VM down | `/vm.shutdown` | N/A | N/A | The VM is booted
|
||||||
Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted
|
Reboot the VM | `/vm.reboot` | N/A | N/A | The VM is booted
|
||||||
Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted
|
Pause the VM | `/vm.pause` | N/A | N/A | The VM is booted
|
||||||
@@ -92,6 +92,11 @@ Remove memory from the VM | `/vm.resize` | `/schemas/VmResize`
|
|||||||
Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created
|
Dump the VM information | `/vm.info` | N/A | `/schemas/VmInfo` | The VM is created
|
||||||
Add VFIO PCI device to the VM | `/vm.add-device` | `/schemas/VmAddDevice` | N/A | The VM is booted
|
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
|
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
|
### REST API Examples
|
||||||
|
|
||||||
@@ -370,4 +375,3 @@ APIs work together, let's look at a complete VM creation flow, from the
|
|||||||
user. This is abstracted by the
|
user. This is abstracted by the
|
||||||
[micro_http](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
|
[micro_http](https://github.com/firecracker-microvm/firecracker/tree/master/src/micro_http)
|
||||||
crate.
|
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`.
|
||||||
@@ -60,20 +60,20 @@ Assuming you have `clear-kvm.img` and `custom-vmlinux.bin` on your system, here
|
|||||||
--disk path=clear-kvm.img \
|
--disk path=clear-kvm.img \
|
||||||
--kernel custom-vmlinux.bin \
|
--kernel custom-vmlinux.bin \
|
||||||
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
--cmdline "console=ttyS0 reboot=k panic=1 nomodules root=/dev/vda3" \
|
||||||
--fs tag=myfs,sock=/tmp/virtiofs,num_queues=1,queue_size=512
|
--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:
|
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
|
```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.
|
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
|
```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
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
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.
|
||||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
|||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2.68"
|
libc = "0.2.71"
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
serde = "1.0.105"
|
serde = "1.0.111"
|
||||||
vmm-sys-util = ">=0.3.1"
|
vmm-sys-util = ">=0.3.1"
|
||||||
|
|
||||||
net_gen = { path = "../net_gen" }
|
net_gen = { path = "../net_gen" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lazy_static = "1.3.0"
|
lazy_static = "1.3.0"
|
||||||
pnet = "0.25.0"
|
pnet = "0.26.0"
|
||||||
serde_json = "1.0.48"
|
serde_json = "1.0.55"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use rand::Rng;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use serde::de::{Deserialize, Deserializer, Error};
|
use serde::de::{Deserialize, Deserializer, Error};
|
||||||
use serde::ser::{Serialize, Serializer};
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|||||||
@@ -5,15 +5,14 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the THIRD-PARTY file.
|
// 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::fs::File;
|
||||||
use std::io::{Error as IoError, Read, Result as IoResult, Write};
|
use std::io::{Error as IoError, Read, Result as IoResult, Write};
|
||||||
use std::net;
|
use std::net;
|
||||||
use std::os::raw::*;
|
use std::os::raw::*;
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
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};
|
use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -31,6 +30,8 @@ pub enum Error {
|
|||||||
/// Failed to create a socket.
|
/// Failed to create a socket.
|
||||||
NetUtil(NetUtilError),
|
NetUtil(NetUtilError),
|
||||||
InvalidIfname,
|
InvalidIfname,
|
||||||
|
/// Error parsing MAC data
|
||||||
|
MacParsing(()),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||||
@@ -176,6 +177,72 @@ impl Tap {
|
|||||||
Ok(())
|
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.
|
/// Set the netmask for the subnet that the tap interface will exist on.
|
||||||
pub fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()> {
|
pub fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()> {
|
||||||
let sock = create_socket().map_err(Error::NetUtil)?;
|
let sock = create_socket().map_err(Error::NetUtil)?;
|
||||||
@@ -219,9 +286,26 @@ impl Tap {
|
|||||||
|
|
||||||
let mut ifreq = self.get_ifreq();
|
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.
|
// We only access one field of the ifru union, hence this is safe.
|
||||||
unsafe {
|
unsafe {
|
||||||
let ifru_flags = ifreq.ifr_ifru.ifru_flags.as_mut();
|
let ifru_flags = ifreq.ifr_ifru.ifru_flags.as_mut();
|
||||||
|
|
||||||
*ifru_flags =
|
*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;
|
||||||
}
|
}
|
||||||
@@ -443,7 +527,7 @@ mod tests {
|
|||||||
let interface = interfaces.into_iter().find(interface_name_matches).unwrap();
|
let interface = interfaces.into_iter().find(interface_name_matches).unwrap();
|
||||||
|
|
||||||
if let Ok(Ethernet(tx, rx)) = datalink::channel(&interface, Default::default()) {
|
if let Ok(Ethernet(tx, rx)) = datalink::channel(&interface, Default::default()) {
|
||||||
(interface.mac_address(), tx, rx)
|
(interface.mac.unwrap(), tx, rx)
|
||||||
} else {
|
} else {
|
||||||
panic!("datalink channel error or unhandled channel type");
|
panic!("datalink channel error or unhandled channel type");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,23 @@ authors = ["Samuel Ortiz <sameo@linux.intel.com>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
vm-allocator = { path = "../vm-allocator" }
|
anyhow = "1.0"
|
||||||
byteorder = "1.3.4"
|
byteorder = "1.3.4"
|
||||||
devices = { path = "../devices" }
|
devices = { path = "../devices" }
|
||||||
libc = "0.2.68"
|
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"
|
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-device = { path = "../vm-device" }
|
||||||
vm-memory = "0.1.0"
|
vm-memory = "0.2.1"
|
||||||
|
vm-migration = { path = "../vm-migration" }
|
||||||
|
|
||||||
|
[dependencies.vfio-bindings]
|
||||||
|
git = "https://github.com/rust-vmm/vfio-bindings"
|
||||||
|
features = ["fam-wrappers"]
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use crate::configuration::{
|
|||||||
use crate::device::{DeviceRelocation, Error as PciDeviceError, PciDevice};
|
use crate::device::{DeviceRelocation, Error as PciDeviceError, PciDevice};
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use devices::BusDevice;
|
use devices::BusDevice;
|
||||||
use std;
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
@@ -34,6 +33,8 @@ pub enum PciRootError {
|
|||||||
NoPciDeviceSlotAvailable,
|
NoPciDeviceSlotAvailable,
|
||||||
/// Invalid PCI device identifier provided.
|
/// Invalid PCI device identifier provided.
|
||||||
InvalidPciDeviceSlot(usize),
|
InvalidPciDeviceSlot(usize),
|
||||||
|
/// Valid PCI device identifier but already used.
|
||||||
|
AlreadyInUsePciDeviceSlot(usize),
|
||||||
}
|
}
|
||||||
pub type Result<T> = std::result::Result<T, PciRootError>;
|
pub type Result<T> = std::result::Result<T, PciRootError>;
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ impl PciRoot {
|
|||||||
config: PciConfiguration::new(
|
config: PciConfiguration::new(
|
||||||
VENDOR_ID_INTEL,
|
VENDOR_ID_INTEL,
|
||||||
DEVICE_ID_INTEL_VIRT_PCIE_HOST,
|
DEVICE_ID_INTEL_VIRT_PCIE_HOST,
|
||||||
|
0,
|
||||||
PciClassCode::BridgeDevice,
|
PciClassCode::BridgeDevice,
|
||||||
&PciBridgeSubclass::HostBridge,
|
&PciBridgeSubclass::HostBridge,
|
||||||
None,
|
None,
|
||||||
@@ -154,6 +156,19 @@ impl PciBus {
|
|||||||
Err(PciRootError::NoPciDeviceSlotAvailable)
|
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<()> {
|
pub fn put_device_id(&mut self, id: usize) -> Result<()> {
|
||||||
if id < NUM_DEVICE_IDS {
|
if id < NUM_DEVICE_IDS {
|
||||||
self.device_ids[id] = false;
|
self.device_ids[id] = false;
|
||||||
@@ -239,7 +254,10 @@ impl PciConfigIo {
|
|||||||
device.deref_mut(),
|
device.deref_mut(),
|
||||||
params.region_type,
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,7 +373,10 @@ impl PciConfigMmio {
|
|||||||
device.deref_mut(),
|
device.deref_mut(),
|
||||||
params.region_type,
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
186
pci/src/configuration.rs
Executable file → Normal file
186
pci/src/configuration.rs
Executable file → Normal file
@@ -2,12 +2,13 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use crate::device::BarReprogrammingParams;
|
use crate::device::BarReprogrammingParams;
|
||||||
use crate::{MsixConfig, PciInterruptPin};
|
use crate::{MsixConfig, PciInterruptPin};
|
||||||
|
use anyhow::anyhow;
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
use std::fmt::{self, Display};
|
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, 4096 bytes.
|
// The number of 32bit registers in the config space, 4096 bytes.
|
||||||
const NUM_CONFIGURATION_REGISTERS: usize = 1024;
|
const NUM_CONFIGURATION_REGISTERS: usize = 1024;
|
||||||
@@ -244,6 +245,53 @@ pub trait PciCapability {
|
|||||||
fn id(&self) -> PciCapabilityID;
|
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.
|
/// Contains the configuration space of a PCI node.
|
||||||
/// See the [specification](https://en.wikipedia.org/wiki/PCI_configuration_space).
|
/// See the [specification](https://en.wikipedia.org/wiki/PCI_configuration_space).
|
||||||
/// The configuration space is accessed with DWORD reads and writes from the guest.
|
/// 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
|
/// See pci_regs.h in kernel
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum PciBarRegionType {
|
pub enum PciBarRegionType {
|
||||||
Memory32BitRegion = 0,
|
Memory32BitRegion = 0,
|
||||||
IORegion = 0x01,
|
IORegion = 0x01,
|
||||||
@@ -297,6 +345,10 @@ pub enum Error {
|
|||||||
CapabilityEmpty,
|
CapabilityEmpty,
|
||||||
CapabilityLengthInvalid(usize),
|
CapabilityLengthInvalid(usize),
|
||||||
CapabilitySpaceFull(usize),
|
CapabilitySpaceFull(usize),
|
||||||
|
Decode32BarSize,
|
||||||
|
Decode64BarSize,
|
||||||
|
Encode32BarSize,
|
||||||
|
Encode64BarSize,
|
||||||
RomBarAddressInvalid(u64, u64),
|
RomBarAddressInvalid(u64, u64),
|
||||||
RomBarInUse(usize),
|
RomBarInUse(usize),
|
||||||
RomBarInvalid(usize),
|
RomBarInvalid(usize),
|
||||||
@@ -324,6 +376,10 @@ impl Display for Error {
|
|||||||
CapabilityEmpty => write!(f, "empty capabilities are invalid"),
|
CapabilityEmpty => write!(f, "empty capabilities are invalid"),
|
||||||
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
|
CapabilityLengthInvalid(l) => write!(f, "Invalid capability length {}", l),
|
||||||
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
|
CapabilitySpaceFull(s) => write!(f, "capability of size {} doesn't fit", s),
|
||||||
|
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),
|
RomBarAddressInvalid(a, s) => write!(f, "address {} size {} too big", a, s),
|
||||||
RomBarInUse(b) => write!(f, "rom bar {} already used", b),
|
RomBarInUse(b) => write!(f, "rom bar {} already used", b),
|
||||||
RomBarInvalid(b) => write!(f, "rom bar {} invalid, max {}", b, NUM_BAR_REGS - 1),
|
RomBarInvalid(b) => write!(f, "rom bar {} invalid, max {}", b, NUM_BAR_REGS - 1),
|
||||||
@@ -337,6 +393,7 @@ impl PciConfiguration {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
vendor_id: u16,
|
vendor_id: u16,
|
||||||
device_id: u16,
|
device_id: u16,
|
||||||
|
revision_id: u8,
|
||||||
class_code: PciClassCode,
|
class_code: PciClassCode,
|
||||||
subclass: &dyn PciSubclass,
|
subclass: &dyn PciSubclass,
|
||||||
programming_interface: Option<&dyn PciProgrammingInterface>,
|
programming_interface: Option<&dyn PciProgrammingInterface>,
|
||||||
@@ -359,7 +416,8 @@ impl PciConfiguration {
|
|||||||
};
|
};
|
||||||
registers[2] = u32::from(class_code.get_register_value()) << 24
|
registers[2] = u32::from(class_code.get_register_value()) << 24
|
||||||
| u32::from(subclass.get_register_value()) << 16
|
| 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)
|
writable_bits[3] = 0x0000_00ff; // Cacheline size (r/w)
|
||||||
match header_type {
|
match header_type {
|
||||||
PciHeaderType::Device => {
|
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.
|
/// Reads a 32bit register from `reg_idx` in the register map.
|
||||||
pub fn read_reg(&self, reg_idx: usize) -> u32 {
|
pub fn read_reg(&self, reg_idx: usize) -> u32 {
|
||||||
*(self.registers.get(reg_idx).unwrap_or(&0xffff_ffff))
|
*(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
|
// Handle very specific case where the BAR is being written with
|
||||||
// all 1's to retrieve the BAR size during next BAR reading.
|
// all 1's to retrieve the BAR size during next BAR reading.
|
||||||
if value == 0xffff_ffff {
|
if value == 0xffff_ffff {
|
||||||
mask = self.bar_size[reg_idx - 4];
|
mask &= self.bar_size[reg_idx - 4];
|
||||||
}
|
}
|
||||||
} else if reg_idx == ROM_BAR_REG {
|
} else if reg_idx == ROM_BAR_REG {
|
||||||
// Handle very specific case where the BAR is being written with
|
// Handle very specific case where the BAR is being written with
|
||||||
// all 1's on bits 31-11 to retrieve the BAR size during next BAR
|
// all 1's on bits 31-11 to retrieve the BAR size during next BAR
|
||||||
// reading.
|
// reading.
|
||||||
if value & ROM_BAR_ADDR_MASK == ROM_BAR_ADDR_MASK {
|
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()) {
|
if end_addr > u64::from(u32::max_value()) {
|
||||||
return Err(Error::BarAddressInvalid(config.addr, config.size));
|
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 => {
|
PciBarRegionType::Memory64BitRegion => {
|
||||||
if config.reg_idx + 1 >= NUM_BAR_REGS {
|
if config.reg_idx + 1 >= NUM_BAR_REGS {
|
||||||
@@ -508,10 +602,16 @@ impl PciConfiguration {
|
|||||||
return Err(Error::BarInUse64(config.reg_idx));
|
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.registers[bar_idx + 1] = (config.addr >> 32) as u32;
|
||||||
self.writable_bits[bar_idx + 1] = 0xffff_ffff;
|
self.writable_bits[bar_idx + 1] = 0xffff_ffff;
|
||||||
self.bar_addr[config.reg_idx + 1] = self.registers[bar_idx + 1];
|
self.bar_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;
|
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.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
|
||||||
self.writable_bits[bar_idx] = mask;
|
self.writable_bits[bar_idx] = mask;
|
||||||
self.bar_addr[config.reg_idx] = self.registers[bar_idx];
|
self.bar_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_used[config.reg_idx] = true;
|
||||||
self.bar_type[config.reg_idx] = Some(config.region_type);
|
self.bar_type[config.reg_idx] = Some(config.region_type);
|
||||||
Ok(config.reg_idx)
|
Ok(config.reg_idx)
|
||||||
@@ -559,7 +658,8 @@ impl PciConfiguration {
|
|||||||
self.registers[config.reg_idx] = (config.addr as u32) | active;
|
self.registers[config.reg_idx] = (config.addr as u32) | active;
|
||||||
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
|
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK;
|
||||||
self.rom_bar_addr = self.registers[config.reg_idx];
|
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;
|
self.rom_bar_used = true;
|
||||||
Ok(config.reg_idx)
|
Ok(config.reg_idx)
|
||||||
}
|
}
|
||||||
@@ -697,7 +797,11 @@ impl PciConfiguration {
|
|||||||
);
|
);
|
||||||
let old_base = u64::from(self.bar_addr[bar_idx] & mask);
|
let old_base = u64::from(self.bar_addr[bar_idx] & mask);
|
||||||
let new_base = u64::from(value & 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;
|
let region_type = bar_type;
|
||||||
|
|
||||||
self.bar_addr[bar_idx] = value;
|
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]);
|
| u64::from(self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1]);
|
||||||
let new_base = u64::from(value & mask) << 32
|
let new_base = u64::from(value & mask) << 32
|
||||||
| u64::from(self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]);
|
| u64::from(self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]);
|
||||||
let len = u64::from(self.bar_size[bar_idx]) << 32
|
let len =
|
||||||
| u64::from(self.bar_size[bar_idx - 1]);
|
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;
|
let region_type = PciBarRegionType::Memory64BitRegion;
|
||||||
|
|
||||||
self.bar_addr[bar_idx] = value;
|
self.bar_addr[bar_idx] = value;
|
||||||
@@ -760,7 +866,11 @@ impl PciConfiguration {
|
|||||||
);
|
);
|
||||||
let old_base = u64::from(self.rom_bar_addr & mask);
|
let old_base = u64::from(self.rom_bar_addr & mask);
|
||||||
let new_base = u64::from(value & 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;
|
let region_type = PciBarRegionType::Memory32BitRegion;
|
||||||
|
|
||||||
self.rom_bar_addr = value;
|
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 {
|
impl Default for PciBarConfiguration {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PciBarConfiguration {
|
PciBarConfiguration {
|
||||||
@@ -862,6 +1020,7 @@ mod tests {
|
|||||||
let mut cfg = PciConfiguration::new(
|
let mut cfg = PciConfiguration::new(
|
||||||
0x1234,
|
0x1234,
|
||||||
0x5678,
|
0x5678,
|
||||||
|
0x1,
|
||||||
PciClassCode::MultimediaController,
|
PciClassCode::MultimediaController,
|
||||||
&PciMultimediaSubclass::AudioController,
|
&PciMultimediaSubclass::AudioController,
|
||||||
None,
|
None,
|
||||||
@@ -917,6 +1076,7 @@ mod tests {
|
|||||||
let cfg = PciConfiguration::new(
|
let cfg = PciConfiguration::new(
|
||||||
0x1234,
|
0x1234,
|
||||||
0x5678,
|
0x5678,
|
||||||
|
0x1,
|
||||||
PciClassCode::MultimediaController,
|
PciClassCode::MultimediaController,
|
||||||
&PciMultimediaSubclass::AudioController,
|
&PciMultimediaSubclass::AudioController,
|
||||||
Some(&TestPI::Test),
|
Some(&TestPI::Test),
|
||||||
|
|||||||
0
pci/src/device.rs
Executable file → Normal file
0
pci/src/device.rs
Executable file → Normal file
@@ -6,6 +6,10 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate devices;
|
extern crate devices;
|
||||||
|
extern crate serde;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
extern crate serde_json;
|
||||||
extern crate vm_memory;
|
extern crate vm_memory;
|
||||||
|
|
||||||
mod bus;
|
mod bus;
|
||||||
@@ -13,6 +17,7 @@ mod configuration;
|
|||||||
mod device;
|
mod device;
|
||||||
mod msi;
|
mod msi;
|
||||||
mod msix;
|
mod msix;
|
||||||
|
mod vfio;
|
||||||
|
|
||||||
pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
|
pub use self::bus::{PciBus, PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
|
||||||
pub use self::configuration::{
|
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::msi::{msi_num_enabled_vectors, MsiCap, MsiConfig};
|
||||||
pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE};
|
pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE};
|
||||||
|
pub use self::vfio::{VfioPciDevice, VfioPciError};
|
||||||
|
|
||||||
/// PCI has four interrupt pins A->D.
|
/// PCI has four interrupt pins A->D.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
|||||||
111
pci/src/msix.rs
111
pci/src/msix.rs
@@ -6,14 +6,17 @@
|
|||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate vm_memory;
|
extern crate vm_memory;
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::{PciCapability, PciCapabilityID};
|
use crate::{PciCapability, PciCapabilityID};
|
||||||
|
use anyhow::anyhow;
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
use std::io;
|
||||||
|
use std::result;
|
||||||
|
use std::sync::Arc;
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig,
|
InterruptIndex, InterruptSourceConfig, InterruptSourceGroup, MsiIrqSourceConfig,
|
||||||
};
|
};
|
||||||
use vm_memory::ByteValued;
|
use vm_memory::ByteValued;
|
||||||
|
use vm_migration::{MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable};
|
||||||
|
|
||||||
const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048;
|
const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048;
|
||||||
const MSIX_TABLE_ENTRIES_MODULO: u64 = 16;
|
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;
|
const MSIX_ENABLE_MASK: u16 = (1 << MSIX_ENABLE_BIT) as u16;
|
||||||
pub const MSIX_TABLE_ENTRY_SIZE: usize = 16;
|
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 struct MsixTableEntry {
|
||||||
pub msg_addr_lo: u32,
|
pub msg_addr_lo: u32,
|
||||||
pub msg_addr_hi: 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 struct MsixConfig {
|
||||||
pub table_entries: Vec<MsixTableEntry>,
|
pub table_entries: Vec<MsixTableEntry>,
|
||||||
pub pba_entries: Vec<u64>,
|
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 {
|
pub fn masked(&self) -> bool {
|
||||||
self.masked
|
self.masked
|
||||||
}
|
}
|
||||||
@@ -365,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)]
|
#[allow(dead_code)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Copy, Default)]
|
||||||
|
|||||||
@@ -4,30 +4,33 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
extern crate devices;
|
extern crate devices;
|
||||||
extern crate pci;
|
|
||||||
extern crate vm_allocator;
|
extern crate vm_allocator;
|
||||||
|
|
||||||
use crate::vfio_device::VfioDevice;
|
use crate::{
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
|
||||||
use devices::BusDevice;
|
|
||||||
use kvm_bindings::kvm_userspace_memory_region;
|
|
||||||
use kvm_ioctls::*;
|
|
||||||
use pci::{
|
|
||||||
msi_num_enabled_vectors, BarReprogrammingParams, MsiConfig, MsixCap, MsixConfig,
|
msi_num_enabled_vectors, BarReprogrammingParams, MsiConfig, MsixCap, MsixConfig,
|
||||||
PciBarConfiguration, PciBarRegionType, PciCapabilityID, PciClassCode, PciConfiguration,
|
PciBarConfiguration, PciBarRegionType, PciCapabilityID, PciClassCode, PciConfiguration,
|
||||||
PciDevice, PciDeviceError, PciHeaderType, PciSubclass, MSIX_TABLE_ENTRY_SIZE,
|
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::any::Any;
|
||||||
|
use std::ops::Deref;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::ptr::null_mut;
|
use std::ptr::null_mut;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{fmt, io, result};
|
use std::{fmt, io, result};
|
||||||
use vfio_bindings::bindings::vfio::*;
|
use vfio_bindings::bindings::vfio::*;
|
||||||
|
use vfio_ioctls::{VfioDevice, VfioError};
|
||||||
use vm_allocator::SystemAllocator;
|
use vm_allocator::SystemAllocator;
|
||||||
use vm_device::interrupt::{
|
use vm_device::interrupt::{
|
||||||
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
|
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;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -41,6 +44,7 @@ pub enum VfioPciError {
|
|||||||
SetGsiRouting(kvm_ioctls::Error),
|
SetGsiRouting(kvm_ioctls::Error),
|
||||||
MsiNotConfigured,
|
MsiNotConfigured,
|
||||||
MsixNotConfigured,
|
MsixNotConfigured,
|
||||||
|
UpdateMemory(VfioError),
|
||||||
UpdateMsiEventFd,
|
UpdateMsiEventFd,
|
||||||
UpdateMsixEventFd,
|
UpdateMsixEventFd,
|
||||||
}
|
}
|
||||||
@@ -62,6 +66,7 @@ impl fmt::Display for VfioPciError {
|
|||||||
VfioPciError::SetGsiRouting(e) => write!(f, "failed to set GSI routes for KVM: {}", e),
|
VfioPciError::SetGsiRouting(e) => write!(f, "failed to set GSI routes for KVM: {}", e),
|
||||||
VfioPciError::MsiNotConfigured => write!(f, "MSI interrupt not yet configured"),
|
VfioPciError::MsiNotConfigured => write!(f, "MSI interrupt not yet configured"),
|
||||||
VfioPciError::MsixNotConfigured => write!(f, "MSI-X 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::UpdateMsiEventFd => write!(f, "failed to update MSI eventfd"),
|
||||||
VfioPciError::UpdateMsixEventFd => write!(f, "failed to update MSI-X eventfd"),
|
VfioPciError::UpdateMsixEventFd => write!(f, "failed to update MSI-X eventfd"),
|
||||||
}
|
}
|
||||||
@@ -282,6 +287,7 @@ pub struct VfioPciDevice {
|
|||||||
configuration: PciConfiguration,
|
configuration: PciConfiguration,
|
||||||
mmio_regions: Vec<MmioRegion>,
|
mmio_regions: Vec<MmioRegion>,
|
||||||
interrupt: Interrupt,
|
interrupt: Interrupt,
|
||||||
|
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VfioPciDevice {
|
impl VfioPciDevice {
|
||||||
@@ -290,11 +296,13 @@ impl VfioPciDevice {
|
|||||||
vm_fd: &Arc<VmFd>,
|
vm_fd: &Arc<VmFd>,
|
||||||
device: VfioDevice,
|
device: VfioDevice,
|
||||||
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
interrupt_manager: &Arc<dyn InterruptManager<GroupConfig = MsiIrqGroupConfig>>,
|
||||||
|
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let device = Arc::new(device);
|
let device = Arc::new(device);
|
||||||
device.reset();
|
device.reset();
|
||||||
|
|
||||||
let configuration = PciConfiguration::new(
|
let configuration = PciConfiguration::new(
|
||||||
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
PciClassCode::Other,
|
PciClassCode::Other,
|
||||||
@@ -318,6 +326,7 @@ impl VfioPciDevice {
|
|||||||
msi: None,
|
msi: None,
|
||||||
msix: None,
|
msix: None,
|
||||||
},
|
},
|
||||||
|
mem,
|
||||||
};
|
};
|
||||||
|
|
||||||
vfio_pci_device.parse_capabilities(interrupt_manager);
|
vfio_pci_device.parse_capabilities(interrupt_manager);
|
||||||
@@ -610,6 +619,12 @@ impl VfioPciDevice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl Drop for VfioPciDevice {
|
||||||
@@ -628,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");
|
error!("failed to remove all guest memory regions from iommu table");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -834,7 +853,11 @@ 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");
|
error!("failed to add all guest memory regions into iommu table");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ path = "src/qcow.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.3.4"
|
byteorder = "1.3.4"
|
||||||
libc = "0.2.68"
|
libc = "0.2.71"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
remain = "0.2.1"
|
remain = "0.2.1"
|
||||||
vmm-sys-util = ">=0.3.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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE-BSD-3-Clause file.
|
// found in the LICENSE-BSD-3-Clause file.
|
||||||
|
|
||||||
use std;
|
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
|||||||
214
release-notes.md
214
release-notes.md
@@ -1,10 +1,29 @@
|
|||||||
|
- [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)
|
- [v0.6.0](#v060)
|
||||||
- [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug)
|
- [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug)
|
||||||
- [Shared Filesystem Improvements](#shared-filesystem-improvements)
|
- [Shared Filesystem Improvements](#shared-filesystem-improvements)
|
||||||
- [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading)
|
- [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading)
|
||||||
- [Command Line Interface](#command-line-interface)
|
- [Command Line Interface](#command-line-interface)
|
||||||
- [PVH Boot](#pvh-boot)
|
- [PVH Boot](#pvh-boot)
|
||||||
- [Contributors](#contributors)
|
- [Contributors](#contributors-2)
|
||||||
- [v0.5.1](#v051)
|
- [v0.5.1](#v051)
|
||||||
- [v0.5.0](#v050)
|
- [v0.5.0](#v050)
|
||||||
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
|
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
|
||||||
@@ -12,7 +31,7 @@
|
|||||||
- [New Interrupt Management Framework](#new-interrupt-management-framework)
|
- [New Interrupt Management Framework](#new-interrupt-management-framework)
|
||||||
- [Development Tools](#development-tools)
|
- [Development Tools](#development-tools)
|
||||||
- [Kata Containers Integration](#kata-containers-integration)
|
- [Kata Containers Integration](#kata-containers-integration)
|
||||||
- [Contributors](#contributors)
|
- [Contributors](#contributors-3)
|
||||||
- [v0.4.0](#v040)
|
- [v0.4.0](#v040)
|
||||||
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
|
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
|
||||||
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
|
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
|
||||||
@@ -21,7 +40,7 @@
|
|||||||
- [Userspace IOAPIC by default](#userspace-ioapic-by-default)
|
- [Userspace IOAPIC by default](#userspace-ioapic-by-default)
|
||||||
- [PCI BAR reprogramming](#pci-bar-reprogramming)
|
- [PCI BAR reprogramming](#pci-bar-reprogramming)
|
||||||
- [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization)
|
- [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization)
|
||||||
- [Contributors](#contributors-1)
|
- [Contributors](#contributors-4)
|
||||||
- [v0.3.0](#v030)
|
- [v0.3.0](#v030)
|
||||||
- [Block device offloading](#block-device-offloading)
|
- [Block device offloading](#block-device-offloading)
|
||||||
- [Network device backend](#network-device-backend)
|
- [Network device backend](#network-device-backend)
|
||||||
@@ -48,6 +67,195 @@
|
|||||||
- [Unit testing](#unit-testing)
|
- [Unit testing](#unit-testing)
|
||||||
- [Integration tests parallelization](#integration-tests-parallelization)
|
- [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
|
# v0.6.0
|
||||||
|
|
||||||
This release has been tracked through the [0.6.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).
|
This release has been tracked through the [0.6.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/7).
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
FROM ubuntu:18.04 as dev
|
FROM ubuntu:18.04 as dev
|
||||||
|
|
||||||
ARG RUST_TOOLCHAIN="1.42.0"
|
ARG TARGETARCH="x86_64"
|
||||||
|
ARG RUST_TOOLCHAIN="1.43.0"
|
||||||
ARG CLH_SRC_DIR="/cloud-hypervisor"
|
ARG CLH_SRC_DIR="/cloud-hypervisor"
|
||||||
ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build"
|
ARG CLH_BUILD_DIR="$CLH_SRC_DIR/build"
|
||||||
ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry"
|
ARG CARGO_REGISTRY_DIR="$CLH_BUILD_DIR/cargo_registry"
|
||||||
@@ -11,9 +12,9 @@ ENV RUSTUP_HOME=$CARGO_HOME
|
|||||||
ENV PATH="$PATH:$CARGO_HOME/bin"
|
ENV PATH="$PATH:$CARGO_HOME/bin"
|
||||||
|
|
||||||
# Install all CI dependencies
|
# Install all CI dependencies
|
||||||
RUN apt-get update
|
RUN apt-get update \
|
||||||
RUN apt-get -yq upgrade
|
&& apt-get -yq upgrade \
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||||
build-essential \
|
build-essential \
|
||||||
bc \
|
bc \
|
||||||
docker.io \
|
docker.io \
|
||||||
@@ -21,6 +22,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
|||||||
wget \
|
wget \
|
||||||
sudo \
|
sudo \
|
||||||
mtools \
|
mtools \
|
||||||
|
musl-tools \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
flex \
|
flex \
|
||||||
@@ -34,13 +36,31 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
|||||||
libcap-ng-dev \
|
libcap-ng-dev \
|
||||||
socat \
|
socat \
|
||||||
dosfstools \
|
dosfstools \
|
||||||
|
cpio \
|
||||||
|
bsdtar \
|
||||||
|
libfdt-dev \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& 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
|
# Install the rust toolchain
|
||||||
RUN nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$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 target add $TARGETARCH-unknown-linux-musl --toolchain "$RUST_TOOLCHAIN" \
|
||||||
&& rustup component add clippy \
|
&& 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 \
|
&& cargo install cargo-audit \
|
||||||
&& rm -rf "$CARGO_HOME/registry" \
|
&& rm -rf "$CARGO_HOME/registry" \
|
||||||
&& ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \
|
&& ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \
|
||||||
|
|||||||
@@ -178,7 +178,14 @@ CONFIG_NET_NS=y
|
|||||||
CONFIG_SCHED_AUTOGROUP=y
|
CONFIG_SCHED_AUTOGROUP=y
|
||||||
# CONFIG_SYSFS_DEPRECATED is not set
|
# CONFIG_SYSFS_DEPRECATED is not set
|
||||||
CONFIG_RELAY=y
|
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_BOOT_CONFIG is not set
|
||||||
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
|
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
|
||||||
# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
|
# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
|
||||||
@@ -279,7 +286,7 @@ CONFIG_X86_64_SMP=y
|
|||||||
CONFIG_ARCH_SUPPORTS_UPROBES=y
|
CONFIG_ARCH_SUPPORTS_UPROBES=y
|
||||||
CONFIG_FIX_EARLYCON_MEM=y
|
CONFIG_FIX_EARLYCON_MEM=y
|
||||||
CONFIG_DYNAMIC_PHYSICAL_MASK=y
|
CONFIG_DYNAMIC_PHYSICAL_MASK=y
|
||||||
CONFIG_PGTABLE_LEVELS=4
|
CONFIG_PGTABLE_LEVELS=5
|
||||||
CONFIG_CC_HAS_SANE_STACKPROTECTOR=y
|
CONFIG_CC_HAS_SANE_STACKPROTECTOR=y
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -364,7 +371,7 @@ CONFIG_X86_VSYSCALL_EMULATION=y
|
|||||||
# CONFIG_MICROCODE is not set
|
# CONFIG_MICROCODE is not set
|
||||||
CONFIG_X86_MSR=y
|
CONFIG_X86_MSR=y
|
||||||
CONFIG_X86_CPUID=y
|
CONFIG_X86_CPUID=y
|
||||||
# CONFIG_X86_5LEVEL is not set
|
CONFIG_X86_5LEVEL=y
|
||||||
CONFIG_X86_DIRECT_GBPAGES=y
|
CONFIG_X86_DIRECT_GBPAGES=y
|
||||||
# CONFIG_X86_CPA_STATISTICS is not set
|
# CONFIG_X86_CPA_STATISTICS is not set
|
||||||
CONFIG_AMD_MEM_ENCRYPT=y
|
CONFIG_AMD_MEM_ENCRYPT=y
|
||||||
@@ -485,6 +492,7 @@ CONFIG_ACPI_HOTPLUG_CPU=y
|
|||||||
# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set
|
# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set
|
||||||
CONFIG_ACPI_THERMAL=y
|
CONFIG_ACPI_THERMAL=y
|
||||||
CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
|
CONFIG_ARCH_HAS_ACPI_TABLE_UPGRADE=y
|
||||||
|
CONFIG_ACPI_TABLE_UPGRADE=y
|
||||||
# CONFIG_ACPI_DEBUG is not set
|
# CONFIG_ACPI_DEBUG is not set
|
||||||
# CONFIG_ACPI_PCI_SLOT is not set
|
# CONFIG_ACPI_PCI_SLOT is not set
|
||||||
CONFIG_ACPI_CONTAINER=y
|
CONFIG_ACPI_CONTAINER=y
|
||||||
@@ -2418,7 +2426,22 @@ CONFIG_ZLIB_INFLATE=y
|
|||||||
CONFIG_ZLIB_DEFLATE=y
|
CONFIG_ZLIB_DEFLATE=y
|
||||||
CONFIG_LZO_COMPRESS=y
|
CONFIG_LZO_COMPRESS=y
|
||||||
CONFIG_LZO_DECOMPRESS=y
|
CONFIG_LZO_DECOMPRESS=y
|
||||||
# CONFIG_XZ_DEC is not set
|
CONFIG_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_INTERVAL_TREE=y
|
||||||
CONFIG_XARRAY_MULTI=y
|
CONFIG_XARRAY_MULTI=y
|
||||||
CONFIG_ASSOCIATIVE_ARRAY=y
|
CONFIG_ASSOCIATIVE_ARRAY=y
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
CLI_NAME="Cloud Hypervisor"
|
CLI_NAME="Cloud Hypervisor"
|
||||||
|
|
||||||
CTR_IMAGE_TAG="cloudhypervisor/dev"
|
CTR_IMAGE_TAG="cloudhypervisor/dev"
|
||||||
CTR_IMAGE_VERSION="v1"
|
CTR_IMAGE_VERSION="v3"
|
||||||
CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}"
|
CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}"
|
||||||
|
|
||||||
DOCKER_RUNTIME="docker"
|
DOCKER_RUNTIME="docker"
|
||||||
@@ -113,6 +113,13 @@ ensure_build_dir() {
|
|||||||
done
|
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.
|
# 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.
|
# 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
|
# This fixes that by recursively changing the ownership of /cloud-hypervisor to the
|
||||||
@@ -139,16 +146,18 @@ cmd_help() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Available commands:"
|
echo "Available commands:"
|
||||||
echo ""
|
echo ""
|
||||||
echo " build [--debug|--release] [-- [<cargo args>]]"
|
echo " build [--debug|--release] [--libc musl|gnu] [-- [<cargo args>]]"
|
||||||
echo " Build the Cloud Hypervisor binaries."
|
echo " Build the Cloud Hypervisor binaries."
|
||||||
echo " --debug Build the debug binaries. This is the default."
|
echo " --debug Build the debug binaries. This is the default."
|
||||||
echo " --release Build the release binaries."
|
echo " --release Build the release binaries."
|
||||||
|
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
|
||||||
echo ""
|
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 " Run the Cloud Hypervisor tests."
|
||||||
echo " --unit Run the unit tests."
|
echo " --unit Run the unit tests."
|
||||||
echo " --cargo Run the cargo tests."
|
echo " --cargo Run the cargo tests."
|
||||||
echo " --integration Run the integration 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 " --all Run all tests."
|
||||||
echo ""
|
echo ""
|
||||||
echo " build-container [--type]"
|
echo " build-container [--type]"
|
||||||
@@ -165,12 +174,19 @@ cmd_help() {
|
|||||||
|
|
||||||
cmd_build() {
|
cmd_build() {
|
||||||
build="debug"
|
build="debug"
|
||||||
|
libc="gnu"
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||||
"--debug") { build="debug"; } ;;
|
"--debug") { build="debug"; } ;;
|
||||||
"--release") { build="release"; } ;;
|
"--release") { build="release"; } ;;
|
||||||
|
"--libc")
|
||||||
|
shift
|
||||||
|
[[ "$1" =~ ^(musl|gnu)$ ]] || \
|
||||||
|
die "Invalid libc: $1. Valid options are \"musl\" and \"gnu\"."
|
||||||
|
libc="$1"
|
||||||
|
;;
|
||||||
"--") { shift; break; } ;;
|
"--") { shift; break; } ;;
|
||||||
*)
|
*)
|
||||||
die "Unknown build argument: $1. Please use --help for help."
|
die "Unknown build argument: $1. Please use --help for help."
|
||||||
@@ -179,8 +195,23 @@ cmd_build() {
|
|||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
|
target="$(uname -m)-unknown-linux-${libc}"
|
||||||
|
|
||||||
cargo_args=("$@")
|
cargo_args=("$@")
|
||||||
[ $build = "release" ] && cargo_args+=("--release")
|
[ $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 \
|
$DOCKER_RUNTIME run \
|
||||||
--user "$(id -u):$(id -g)" \
|
--user "$(id -u):$(id -g)" \
|
||||||
@@ -188,10 +219,11 @@ cmd_build() {
|
|||||||
--rm \
|
--rm \
|
||||||
--volume /dev:/dev \
|
--volume /dev:/dev \
|
||||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||||
|
--env RUSTFLAGS="$rustflags" \
|
||||||
"$CTR_IMAGE" \
|
"$CTR_IMAGE" \
|
||||||
cargo build \
|
cargo build \
|
||||||
--target-dir "$CTR_CLH_CARGO_TARGET" \
|
--target-dir "$CTR_CLH_CARGO_TARGET" \
|
||||||
"${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$build"
|
"${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$target/$build"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_clean() {
|
cmd_clean() {
|
||||||
@@ -212,15 +244,22 @@ cmd_tests() {
|
|||||||
unit=false
|
unit=false
|
||||||
cargo=false
|
cargo=false
|
||||||
integration=false
|
integration=false
|
||||||
|
libc="gnu"
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
"-h"|"--help") { cmd_help; exit 1; } ;;
|
"-h"|"--help") { cmd_help; exit 1; } ;;
|
||||||
"--unit") { unit=true; } ;;
|
"--unit") { unit=true; } ;;
|
||||||
"--cargo") { cargo=true; } ;;
|
"--cargo") { cargo=true; } ;;
|
||||||
"--integration") { integration=true; } ;;
|
"--integration") { integration=true; } ;;
|
||||||
"--all") { cargo=true; unit=true; integration=true; } ;;
|
"--libc")
|
||||||
"--") { shift; break; } ;;
|
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."
|
die "Unknown tests argument: $1. Please use --help for help."
|
||||||
;;
|
;;
|
||||||
@@ -228,8 +267,16 @@ cmd_tests() {
|
|||||||
shift
|
shift
|
||||||
done
|
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
|
if [ "$unit" = true ] ; then
|
||||||
say "Running unit tests..."
|
say "Running unit tests for $target..."
|
||||||
$DOCKER_RUNTIME run \
|
$DOCKER_RUNTIME run \
|
||||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||||
--rm \
|
--rm \
|
||||||
@@ -237,6 +284,9 @@ cmd_tests() {
|
|||||||
--device /dev/net/tun \
|
--device /dev/net/tun \
|
||||||
--cap-add net_admin \
|
--cap-add net_admin \
|
||||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||||
|
--env BUILD_TARGET="$target" \
|
||||||
|
--env CFLAGS="$cflags" \
|
||||||
|
--env TARGET_CC="$target_cc" \
|
||||||
"$CTR_IMAGE" \
|
"$CTR_IMAGE" \
|
||||||
./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $?
|
./scripts/run_unit_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||||
fi
|
fi
|
||||||
@@ -252,7 +302,7 @@ cmd_tests() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$integration" = true ] ; then
|
if [ "$integration" = true ] ; then
|
||||||
say "Running integration tests..."
|
say "Running integration tests for $target..."
|
||||||
$DOCKER_RUNTIME run \
|
$DOCKER_RUNTIME run \
|
||||||
--workdir "$CTR_CLH_ROOT_DIR" \
|
--workdir "$CTR_CLH_ROOT_DIR" \
|
||||||
--rm \
|
--rm \
|
||||||
@@ -265,6 +315,7 @@ cmd_tests() {
|
|||||||
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
|
||||||
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
|
||||||
--env USER="root" \
|
--env USER="root" \
|
||||||
|
--env CH_LIBC="${libc}" \
|
||||||
"$CTR_IMAGE" \
|
"$CTR_IMAGE" \
|
||||||
./scripts/run_integration_tests.sh "$@" || fix_dir_perms $? || exit $?
|
./scripts/run_integration_tests.sh "$@" || fix_dir_perms $? || exit $?
|
||||||
fi
|
fi
|
||||||
@@ -296,6 +347,7 @@ cmd_build-container() {
|
|||||||
--target $container_type \
|
--target $container_type \
|
||||||
-t $CTR_IMAGE \
|
-t $CTR_IMAGE \
|
||||||
-f $BUILD_DIR/Dockerfile \
|
-f $BUILD_DIR/Dockerfile \
|
||||||
|
--build-arg TARGETARCH="$(uname -m)" \
|
||||||
$BUILD_DIR
|
$BUILD_DIR
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,5 +377,13 @@ cmd=cmd_$1
|
|||||||
shift
|
shift
|
||||||
|
|
||||||
ensure_build_dir
|
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 "$@"
|
$cmd "$@"
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ set -x
|
|||||||
|
|
||||||
source $HOME/.cargo/env
|
source $HOME/.cargo/env
|
||||||
|
|
||||||
|
export BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
|
||||||
|
|
||||||
WORKLOADS_DIR="$HOME/workloads"
|
WORKLOADS_DIR="$HOME/workloads"
|
||||||
mkdir -p "$WORKLOADS_DIR"
|
mkdir -p "$WORKLOADS_DIR"
|
||||||
|
|
||||||
@@ -51,20 +53,46 @@ if [ ! -f "$BIONIC_OS_RAW_IMAGE" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
EOAN_OS_IMAGE_NAME="eoan-server-cloudimg-amd64.img"
|
FOCAL_OS_IMAGE_NAME="focal-server-cloudimg-amd64.img"
|
||||||
EOAN_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$EOAN_OS_IMAGE_NAME"
|
FOCAL_OS_IMAGE_URL="https://cloudhypervisorstorage.blob.core.windows.net/images/$FOCAL_OS_IMAGE_NAME"
|
||||||
EOAN_OS_IMAGE="$WORKLOADS_DIR/$EOAN_OS_IMAGE_NAME"
|
FOCAL_OS_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_IMAGE_NAME"
|
||||||
if [ ! -f "$EOAN_OS_IMAGE" ]; then
|
if [ ! -f "$FOCAL_OS_IMAGE" ]; then
|
||||||
pushd $WORKLOADS_DIR
|
pushd $WORKLOADS_DIR
|
||||||
time wget --quiet $EOAN_OS_IMAGE_URL || exit 1
|
time wget --quiet $FOCAL_OS_IMAGE_URL || exit 1
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
EOAN_OS_RAW_IMAGE_NAME="eoan-server-cloudimg-amd64-raw.img"
|
FOCAL_OS_RAW_IMAGE_NAME="focal-server-cloudimg-amd64-raw.img"
|
||||||
EOAN_OS_RAW_IMAGE="$WORKLOADS_DIR/$EOAN_OS_RAW_IMAGE_NAME"
|
FOCAL_OS_RAW_IMAGE="$WORKLOADS_DIR/$FOCAL_OS_RAW_IMAGE_NAME"
|
||||||
if [ ! -f "$EOAN_OS_RAW_IMAGE" ]; then
|
if [ ! -f "$FOCAL_OS_RAW_IMAGE" ]; then
|
||||||
pushd $WORKLOADS_DIR
|
pushd $WORKLOADS_DIR
|
||||||
time qemu-img convert -p -f qcow2 -O raw $EOAN_OS_IMAGE_NAME $EOAN_OS_RAW_IMAGE_NAME || exit 1
|
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
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -182,13 +210,28 @@ sudo ip tuntap add name vunet-tap0 mode tap
|
|||||||
# Create tap interface with multipe queues support for vhost_user_net test.
|
# Create tap interface with multipe queues support for vhost_user_net test.
|
||||||
sudo ip tuntap add name vunet-tap1 mode tap multi_queue
|
sudo ip tuntap add name vunet-tap1 mode tap multi_queue
|
||||||
|
|
||||||
cargo build --release
|
BUILD_TARGET="$(uname -m)-unknown-linux-${CH_LIBC}"
|
||||||
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor
|
CFLAGS=""
|
||||||
sudo setcap cap_net_admin+ep target/release/vhost_user_net
|
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.
|
# 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/release/ch-remote $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
|
# Enable KSM with some reasonable parameters so that it won't take too long
|
||||||
# for the memory to be merged between two processes.
|
# for the memory to be merged between two processes.
|
||||||
@@ -213,8 +256,16 @@ RES=$?
|
|||||||
|
|
||||||
if [ $RES -eq 0 ]; then
|
if [ $RES -eq 0 ]; then
|
||||||
# virtio-mmio based testing
|
# virtio-mmio based testing
|
||||||
cargo build --release --no-default-features --features "mmio"
|
cargo build --release --target $BUILD_TARGET --no-default-features --features "mmio"
|
||||||
sudo setcap cap_net_admin+ep target/release/cloud-hypervisor
|
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
|
newgrp kvm << EOF
|
||||||
export RUST_BACKTRACE=1
|
export RUST_BACKTRACE=1
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
source $HOME/.cargo/env
|
source $HOME/.cargo/env
|
||||||
|
|
||||||
cargo test --workspace --no-run
|
BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
|
||||||
pushd target/debug
|
|
||||||
|
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
|
ls | grep net_util | grep -v "\.d" | xargs -n 1 sudo setcap cap_net_admin,cap_net_raw+ep
|
||||||
popd
|
popd
|
||||||
|
|
||||||
sudo adduser $USER kvm
|
sudo adduser $USER kvm
|
||||||
newgrp kvm << EOF || exit 1
|
newgrp kvm << EOF || exit 1
|
||||||
export RUST_BACKTRACE=1
|
export RUST_BACKTRACE=1
|
||||||
cargo test --workspace "$@" || exit 1;
|
cargo test --target $BUILD_TARGET --workspace "$@" || exit 1;
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ cf7cfa783082fc4d6b4d1c0a53e4402648c14b82 clear-31311-cloudguest.img
|
|||||||
142a410546b592ff9536b46bb410faf8ac11edee clear-31311-cloudguest-raw.img
|
142a410546b592ff9536b46bb410faf8ac11edee clear-31311-cloudguest-raw.img
|
||||||
27f3b17962ace69b51f0ddc2012095e3109e6ed8 bionic-server-cloudimg-amd64.img
|
27f3b17962ace69b51f0ddc2012095e3109e6ed8 bionic-server-cloudimg-amd64.img
|
||||||
8db9cc58b01452ce2d06c313177e6e74d8582d93 bionic-server-cloudimg-amd64-raw.img
|
8db9cc58b01452ce2d06c313177e6e74d8582d93 bionic-server-cloudimg-amd64-raw.img
|
||||||
4a452cdcf781f95d31a1668ecb92a937c176709a eoan-server-cloudimg-amd64.img
|
d4a44acc6014d5f83dea1c625c43d677a95fa75f alpine-minirootfs-x86_64.tar.gz
|
||||||
91fdfb21df8920fd55915edf7669282cda2505f6 eoan-server-cloudimg-amd64-raw.img
|
3cab64475048383e70a38550734e98961cfe0fd6 focal-server-cloudimg-amd64-raw.img
|
||||||
|
c74a95665b189f93cb16a2c1770820c6a6a43fb0 focal-server-cloudimg-amd64.img
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ extern crate serde_json;
|
|||||||
extern crate vmm;
|
extern crate vmm;
|
||||||
|
|
||||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||||
|
use std::fmt;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use std::process;
|
use std::process;
|
||||||
@@ -19,10 +20,44 @@ enum Error {
|
|||||||
StatusCodeParsing(std::num::ParseIntError),
|
StatusCodeParsing(std::num::ParseIntError),
|
||||||
MissingProtocol,
|
MissingProtocol,
|
||||||
ContentLengthParsing(std::num::ParseIntError),
|
ContentLengthParsing(std::num::ParseIntError),
|
||||||
ServerResponse(StatusCode),
|
ServerResponse(StatusCode, Option<String>),
|
||||||
InvalidCPUCount(std::num::ParseIntError),
|
InvalidCPUCount(std::num::ParseIntError),
|
||||||
InvalidMemorySize(std::num::ParseIntError),
|
InvalidMemorySize(std::num::ParseIntError),
|
||||||
AddDeviceConfig(vmm::config::Error),
|
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)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@@ -57,10 +92,10 @@ impl StatusCode {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(self) -> Result<(), Error> {
|
fn is_server_error(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
StatusCode::OK | StatusCode::Continue | StatusCode::NoContent => Ok(()),
|
StatusCode::OK | StatusCode::Continue | StatusCode::NoContent => false,
|
||||||
_ => Err(Error::ServerResponse(self)),
|
_ => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,10 +152,14 @@ fn parse_http_response(socket: &mut UnixStream) -> Result<Option<String>, Error>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let body_string = content_length.and(Some(String::from(&res[body_offset.unwrap()..])));
|
||||||
|
let status_code = get_status_code(&res)?;
|
||||||
|
|
||||||
get_status_code(&res)?.check()?;
|
if status_code.is_server_error() {
|
||||||
|
Err(Error::ServerResponse(status_code, body_string))
|
||||||
Ok(content_length.and(Some(String::from(&res[body_offset.unwrap()..]))))
|
} else {
|
||||||
|
Ok(body_string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn simple_api_command(
|
fn simple_api_command(
|
||||||
@@ -213,6 +252,85 @@ fn remove_device_api_command(socket: &mut UnixStream, id: &str) -> Result<(), Er
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
fn do_command(matches: &ArgMatches) -> Result<(), Error> {
|
||||||
let mut socket =
|
let mut socket =
|
||||||
UnixStream::connect(matches.value_of("api-socket").unwrap()).map_err(Error::Socket)?;
|
UnixStream::connect(matches.value_of("api-socket").unwrap()).map_err(Error::Socket)?;
|
||||||
@@ -246,6 +364,62 @@ fn do_command(matches: &ArgMatches) -> Result<(), Error> {
|
|||||||
.value_of("id")
|
.value_of("id")
|
||||||
.unwrap(),
|
.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),
|
Some(c) => simple_api_command(&mut socket, "PUT", c, None),
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
}
|
}
|
||||||
@@ -261,7 +435,7 @@ fn main() {
|
|||||||
.long("api-socket")
|
.long("api-socket")
|
||||||
.help("HTTP API socket path (UNIX domain socket).")
|
.help("HTTP API socket path (UNIX domain socket).")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.min_values(1)
|
.number_of_values(1)
|
||||||
.required(true),
|
.required(true),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
@@ -273,6 +447,51 @@ fn main() {
|
|||||||
.help(vmm::config::DeviceConfig::SYNTAX),
|
.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(
|
||||||
SubCommand::with_name("remove-device")
|
SubCommand::with_name("remove-device")
|
||||||
.about("Remove VFIO device")
|
.about("Remove VFIO device")
|
||||||
@@ -300,12 +519,30 @@ fn main() {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subcommand(SubCommand::with_name("resume").about("Resume the VM"))
|
.subcommand(SubCommand::with_name("resume").about("Resume the VM"))
|
||||||
.subcommand(SubCommand::with_name("shutdown").about("Shutdown 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();
|
let matches = app.get_matches();
|
||||||
|
|
||||||
if let Err(e) = do_command(&matches) {
|
if let Err(e) = do_command(&matches) {
|
||||||
eprintln!("Error running command: {:?}", e);
|
eprintln!("Error running command: {}", e);
|
||||||
process::exit(1)
|
process::exit(1)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,7 @@ fn main() {
|
|||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("block-backend")
|
Arg::with_name("block-backend")
|
||||||
.long("block-backend")
|
.long("block-backend")
|
||||||
.help(
|
.help(vhost_user_block::SYNTAX)
|
||||||
"vhost-user-block backend parameters \
|
|
||||||
\"image=<image_path>,sock=<socket_path>,num_queues=<number_of_queues>,\
|
|
||||||
readonly=true|false,direct=true|false,poll_queue=true|false\"",
|
|
||||||
)
|
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.min_values(1),
|
.min_values(1),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,21 +10,23 @@ extern crate vhost_user_backend;
|
|||||||
extern crate vm_virtio;
|
extern crate vm_virtio;
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use epoll;
|
|
||||||
use futures::executor::{ThreadPool, ThreadPoolBuilder};
|
use futures::executor::{ThreadPool, ThreadPoolBuilder};
|
||||||
use libc::EFD_NONBLOCK;
|
use libc::EFD_NONBLOCK;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
use seccomp::SeccompAction;
|
||||||
use std::num::Wrapping;
|
use std::num::Wrapping;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use std::{convert, error, fmt, io, process};
|
use std::{convert, error, fmt, io, process};
|
||||||
|
|
||||||
use vhost_rs::vhost_user::message::*;
|
use vhost_rs::vhost_user::message::*;
|
||||||
use vhost_rs::vhost_user::SlaveFsCacheReq;
|
use vhost_rs::vhost_user::{Listener, SlaveFsCacheReq};
|
||||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
|
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
|
||||||
use vhost_user_fs::descriptor_utils::Error as VufDescriptorError;
|
use vhost_user_fs::descriptor_utils::Error as VufDescriptorError;
|
||||||
use vhost_user_fs::descriptor_utils::{Reader, Writer};
|
use vhost_user_fs::descriptor_utils::{Reader, Writer};
|
||||||
use vhost_user_fs::filesystem::FileSystem;
|
use vhost_user_fs::filesystem::FileSystem;
|
||||||
use vhost_user_fs::passthrough::{self, PassthroughFs};
|
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::server::Server;
|
||||||
use vhost_user_fs::Error as VhostUserFsError;
|
use vhost_user_fs::Error as VhostUserFsError;
|
||||||
use virtio_bindings::bindings::virtio_net::*;
|
use virtio_bindings::bindings::virtio_net::*;
|
||||||
@@ -83,7 +85,7 @@ impl convert::From<Error> for io::Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
|
struct VhostUserFsThread<F: FileSystem + Send + Sync + 'static> {
|
||||||
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||||
kill_evt: EventFd,
|
kill_evt: EventFd,
|
||||||
server: Arc<Server<F>>,
|
server: Arc<Server<F>>,
|
||||||
@@ -93,9 +95,9 @@ struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
|
|||||||
pool: ThreadPool,
|
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 {
|
fn clone(&self) -> Self {
|
||||||
VhostUserFsBackend {
|
VhostUserFsThread {
|
||||||
mem: self.mem.clone(),
|
mem: self.mem.clone(),
|
||||||
kill_evt: self.kill_evt.try_clone().unwrap(),
|
kill_evt: self.kill_evt.try_clone().unwrap(),
|
||||||
server: self.server.clone(),
|
server: self.server.clone(),
|
||||||
@@ -106,9 +108,9 @@ impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsBackend<F> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
|
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsThread<F> {
|
||||||
fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
|
fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
|
||||||
Ok(VhostUserFsBackend {
|
Ok(VhostUserFsThread {
|
||||||
mem: None,
|
mem: None,
|
||||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
||||||
server: Arc::new(Server::new(fs)),
|
server: Arc::new(Server::new(fs)),
|
||||||
@@ -160,8 +162,9 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
|
|||||||
let mut vring = vring_lock.write().unwrap();
|
let mut vring = vring_lock.write().unwrap();
|
||||||
|
|
||||||
if event_idx {
|
if event_idx {
|
||||||
if let Some(used_idx) = vring.mut_queue().add_used(&mem, head_index, 0) {
|
let queue = vring.mut_queue();
|
||||||
if vring.needs_notification(&mem, Wrapping(used_idx)) {
|
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();
|
vring.signal_used_queue().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,6 +179,17 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBackend<F> {
|
impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBackend<F> {
|
||||||
fn num_queues(&self) -> usize {
|
fn num_queues(&self) -> usize {
|
||||||
NUM_QUEUES
|
NUM_QUEUES
|
||||||
@@ -197,25 +211,27 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_event_idx(&mut self, enabled: bool) {
|
fn set_event_idx(&mut self, enabled: bool) {
|
||||||
self.event_idx = enabled;
|
self.thread.lock().unwrap().event_idx = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||||
self.mem = Some(GuestMemoryAtomic::new(mem));
|
self.thread.lock().unwrap().mem = Some(GuestMemoryAtomic::new(mem));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_event(
|
fn handle_event(
|
||||||
&mut self,
|
&self,
|
||||||
device_event: u16,
|
device_event: u16,
|
||||||
evset: epoll::Events,
|
evset: epoll::Events,
|
||||||
vrings: &[Arc<RwLock<Vring>>],
|
vrings: &[Arc<RwLock<Vring>>],
|
||||||
|
_thread_id: usize,
|
||||||
) -> VhostUserBackendResult<bool> {
|
) -> VhostUserBackendResult<bool> {
|
||||||
if evset != epoll::Events::EPOLLIN {
|
if evset != epoll::Events::EPOLLIN {
|
||||||
return Err(Error::HandleEventNotEpollIn.into());
|
return Err(Error::HandleEventNotEpollIn.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mem = match &self.mem {
|
let mut thread = self.thread.lock().unwrap();
|
||||||
|
let mem = match &thread.mem {
|
||||||
Some(m) => m.memory(),
|
Some(m) => m.memory(),
|
||||||
None => return Err(Error::NoMemoryConfigured.into()),
|
None => return Err(Error::NoMemoryConfigured.into()),
|
||||||
};
|
};
|
||||||
@@ -232,7 +248,7 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
|
|||||||
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.event_idx {
|
if thread.event_idx {
|
||||||
// vm-virtio's Queue implementation only checks avail_index
|
// vm-virtio's Queue implementation only checks avail_index
|
||||||
// once, so to properly support EVENT_IDX we need to keep
|
// once, so to properly support EVENT_IDX we need to keep
|
||||||
// calling process_queue() until it stops finding new
|
// calling process_queue() until it stops finding new
|
||||||
@@ -242,24 +258,27 @@ impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBack
|
|||||||
let mut vring = vring_lock.write().unwrap();
|
let mut vring = vring_lock.write().unwrap();
|
||||||
vring.mut_queue().update_avail_event(&mem);
|
vring.mut_queue().update_avail_event(&mem);
|
||||||
}
|
}
|
||||||
if !self.process_queue(vring_lock.clone())? {
|
if !thread.process_queue(vring_lock.clone())? {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Without EVENT_IDX, a single call is enough.
|
// Without EVENT_IDX, a single call is enough.
|
||||||
self.process_queue(vring_lock)?;
|
thread.process_queue(vring_lock)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
|
fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||||
Some((self.kill_evt.try_clone().unwrap(), Some(KILL_EVENT)))
|
Some((
|
||||||
|
self.thread.lock().unwrap().kill_evt.try_clone().unwrap(),
|
||||||
|
Some(KILL_EVENT),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) {
|
fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) {
|
||||||
self.vu_req = Some(vu_req);
|
self.thread.lock().unwrap().vu_req = Some(vu_req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,6 +297,13 @@ fn main() {
|
|||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("sock")
|
Arg::with_name("sock")
|
||||||
.long("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")
|
.help("vhost-user socket path")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.min_values(1),
|
.min_values(1),
|
||||||
@@ -294,42 +320,86 @@ fn main() {
|
|||||||
.long("disable-xattr")
|
.long("disable-xattr")
|
||||||
.help("Disable support for extended attributes"),
|
.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();
|
.get_matches();
|
||||||
|
|
||||||
// Retrieve arguments
|
// Retrieve arguments
|
||||||
let shared_dir = cmd_arguments
|
let shared_dir = cmd_arguments
|
||||||
.value_of("shared-dir")
|
.value_of("shared-dir")
|
||||||
.expect("Failed to retrieve shared directory path");
|
.expect("Failed to retrieve shared directory path");
|
||||||
let sock = cmd_arguments
|
let socket = match cmd_arguments.value_of("socket") {
|
||||||
.value_of("sock")
|
Some(path) => path,
|
||||||
.expect("Failed to retrieve vhost-user socket 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") {
|
let thread_pool_size: usize = match cmd_arguments.value_of("thread-pool-size") {
|
||||||
Some(size) => size.parse().expect("Invalid argument for thread-pool-size"),
|
Some(size) => size.parse().expect("Invalid argument for thread-pool-size"),
|
||||||
None => THREAD_POOL_SIZE,
|
None => THREAD_POOL_SIZE,
|
||||||
};
|
};
|
||||||
let xattr: bool = !cmd_arguments.is_present("disable-xattr");
|
let xattr: bool = !cmd_arguments.is_present("disable-xattr");
|
||||||
|
let create_sandbox: bool = !cmd_arguments.is_present("disable-sandbox");
|
||||||
// Convert into appropriate types
|
let seccomp_mode: SeccompAction = match cmd_arguments.value_of("seccomp").unwrap() {
|
||||||
let sock = String::from(sock);
|
"none" => SeccompAction::Allow, // i.e. no seccomp
|
||||||
|
"kill" => SeccompAction::Kill,
|
||||||
let fs_cfg = passthrough::Config {
|
"log" => SeccompAction::Log,
|
||||||
root_dir: shared_dir.to_string(),
|
"trap" => SeccompAction::Trap,
|
||||||
xattr,
|
_ => unreachable!(), // We told Arg possible_values
|
||||||
..Default::default()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 = PassthroughFs::new(fs_cfg).unwrap();
|
||||||
let fs_backend = Arc::new(RwLock::new(
|
let fs_backend = Arc::new(RwLock::new(
|
||||||
VhostUserFsBackend::new(fs, thread_pool_size).unwrap(),
|
VhostUserFsBackend::new(fs, thread_pool_size).unwrap(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut daemon = VhostUserDaemon::new(
|
let mut daemon =
|
||||||
String::from("vhost-user-fs-backend"),
|
VhostUserDaemon::new(String::from("vhost-user-fs-backend"), fs_backend.clone()).unwrap();
|
||||||
sock,
|
|
||||||
fs_backend.clone(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if let Err(e) = daemon.start() {
|
if let Err(e) = daemon.start(listener) {
|
||||||
error!("Failed to start daemon: {:?}", e);
|
error!("Failed to start daemon: {:?}", e);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
@@ -338,7 +408,15 @@ fn main() {
|
|||||||
error!("Waiting for daemon failed: {:?}", e);
|
error!("Waiting for daemon failed: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let kill_evt = &fs_backend.read().unwrap().kill_evt;
|
let kill_evt = fs_backend
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.thread
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.kill_evt
|
||||||
|
.try_clone()
|
||||||
|
.unwrap();
|
||||||
if let Err(e) = kill_evt.write(1) {
|
if let Err(e) = kill_evt.write(1) {
|
||||||
error!("Error shutting down worker thread: {:?}", e)
|
error!("Error shutting down worker thread: {:?}", e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,7 @@ fn main() {
|
|||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("net-backend")
|
Arg::with_name("net-backend")
|
||||||
.long("net-backend")
|
.long("net-backend")
|
||||||
.help(
|
.help(vhost_user_net::SYNTAX)
|
||||||
"vhost-user-net backend parameters \
|
|
||||||
\"ip=<ip_addr>,mask=<net_mask>,sock=<socket_path>,\
|
|
||||||
num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,tap=<if_name>\"",
|
|
||||||
)
|
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.min_values(1),
|
.min_values(1),
|
||||||
)
|
)
|
||||||
|
|||||||
584
src/main.rs
Executable file → Normal file
584
src/main.rs
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@@ -74,7 +74,7 @@ write_files:
|
|||||||
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
|
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 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"
|
bash -c "echo 1af4 1041 > /sys/bus/pci/drivers/vfio-pci/new_id"
|
||||||
# 512M ram requires 256 pages
|
# 1G ram requires 512 pages
|
||||||
echo 256 | sudo tee /proc/sys/vm/nr_hugepages
|
echo 512 | sudo tee /proc/sys/vm/nr_hugepages
|
||||||
sudo chmod a+rwX /dev/hugepages
|
sudo chmod a+rwX /dev/hugepages
|
||||||
/mnt/cloud-hypervisor --kernel /mnt/vmlinux --cmdline "console=hvc0 reboot=k panic=1 nomodules i8042.noaux i8042.nomux i8042.nopnp i8042.dumbkbd root=PARTUUID=6fb4d1a8-6c8c-4dd7-9f7c-1fe0b9f2574c VFIOTAG" --disk path=/mnt/clear-31311-cloudguest.img path=/mnt/cloudinit.img --cpus boot=1 --memory size=512M,file=/dev/hugepages --device path=/sys/bus/pci/devices/0000:00:05.0/ path=/sys/bus/pci/devices/0000:00:06.0/ --api-socket /tmp/ch_api.sock
|
/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
2040
tests/integration.rs
2040
tests/integration.rs
File diff suppressed because it is too large
Load Diff
@@ -1,22 +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.68"
|
|
||||||
log = "0.4.8"
|
|
||||||
pci = { path = "../pci" }
|
|
||||||
vm-allocator = { path = "../vm-allocator" }
|
|
||||||
vm-device = { path = "../vm-device" }
|
|
||||||
vm-memory = { version = "0.1.0", features = ["backend-mmap"] }
|
|
||||||
vmm-sys-util = ">=0.3.1"
|
|
||||||
|
|
||||||
[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,902 +0,0 @@
|
|||||||
// Copyright © 2019 Intel Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
|
||||||
//
|
|
||||||
use crate::vec_with_array_field;
|
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
|
||||||
use kvm_ioctls::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::ffi::CString;
|
|
||||||
use std::fmt;
|
|
||||||
use std::fs::{File, OpenOptions};
|
|
||||||
use std::io;
|
|
||||||
use std::mem;
|
|
||||||
use std::num;
|
|
||||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
|
||||||
use std::os::unix::prelude::FileExt;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::result;
|
|
||||||
use std::sync::Arc;
|
|
||||||
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, GuestAddressSpace, GuestMemory, GuestMemoryAtomic, 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,
|
|
||||||
VfioNoIommuGroup(String),
|
|
||||||
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::VfioNoIommuGroup(e) => write!(f, "Failed to find iommu_group for: {}", 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: GuestMemoryAtomic<GuestMemoryMmap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VfioDmaMapping {
|
|
||||||
pub fn new(container: Arc<VfioContainer>, memory: GuestMemoryAtomic<GuestMemoryMmap>) -> Self {
|
|
||||||
VfioDmaMapping { container, memory }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExternalDmaMapping for VfioDmaMapping {
|
|
||||||
fn map(&self, iova: u64, gpa: u64, size: u64) -> result::Result<(), io::Error> {
|
|
||||||
let user_addr = if let Some(addr) = get_host_address_range(
|
|
||||||
&self.memory.memory(),
|
|
||||||
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: GuestMemoryAtomic<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: GuestMemoryAtomic<GuestMemoryMmap>,
|
|
||||||
iommu_attached: bool,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let uuid_path: PathBuf = [sysfspath, Path::new("iommu_group")].iter().collect();
|
|
||||||
if !uuid_path.exists() {
|
|
||||||
return Err(VfioError::VfioNoIommuGroup(
|
|
||||||
uuid_path.to_str().unwrap().to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let group_path = uuid_path.read_link().map_err(VfioError::ReadLink)?;
|
|
||||||
let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
|
|
||||||
let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?;
|
|
||||||
let group_id = group_str.parse::<u32>().map_err(VfioError::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.memory().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.memory().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);
|
|
||||||
@@ -11,10 +11,10 @@ mmio_support = ["vm-virtio/mmio_support"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
epoll = ">=4.0.1"
|
epoll = ">=4.0.1"
|
||||||
libc = "0.2.68"
|
libc = "0.2.71"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
virtio-bindings = "0.1.0"
|
virtio-bindings = "0.1.0"
|
||||||
vm-memory = "0.1.0"
|
vm-memory = "0.2.1"
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
vmm-sys-util = ">=0.3.1"
|
vmm-sys-util = ">=0.3.1"
|
||||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use vhost_rs::vhost_user::message::{
|
|||||||
VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState,
|
VhostUserVirtioFeatures, VhostUserVringAddrFlags, VhostUserVringState,
|
||||||
};
|
};
|
||||||
use vhost_rs::vhost_user::{
|
use vhost_rs::vhost_user::{
|
||||||
Error as VhostUserError, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener,
|
Error as VhostUserError, Listener, Result as VhostUserResult, SlaveFsCacheReq, SlaveListener,
|
||||||
VhostUserSlaveReqHandler,
|
VhostUserSlaveReqHandler,
|
||||||
};
|
};
|
||||||
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||||
@@ -64,9 +64,12 @@ pub trait VhostUserBackend: Send + Sync + 'static {
|
|||||||
/// Depth of each queue.
|
/// Depth of each queue.
|
||||||
fn max_queue_size(&self) -> usize;
|
fn max_queue_size(&self) -> usize;
|
||||||
|
|
||||||
/// Virtio features.
|
/// Available virtio features.
|
||||||
fn features(&self) -> u64;
|
fn features(&self) -> u64;
|
||||||
|
|
||||||
|
/// Acked virtio features.
|
||||||
|
fn acked_features(&mut self, _features: u64) {}
|
||||||
|
|
||||||
/// Virtio protocol features.
|
/// Virtio protocol features.
|
||||||
fn protocol_features(&self) -> VhostUserProtocolFeatures;
|
fn protocol_features(&self) -> VhostUserProtocolFeatures;
|
||||||
|
|
||||||
@@ -81,10 +84,11 @@ pub trait VhostUserBackend: Send + Sync + 'static {
|
|||||||
/// virtqueues on its own, but does not know what to do with events
|
/// virtqueues on its own, but does not know what to do with events
|
||||||
/// happening on custom listeners.
|
/// happening on custom listeners.
|
||||||
fn handle_event(
|
fn handle_event(
|
||||||
&mut self,
|
&self,
|
||||||
device_event: u16,
|
device_event: u16,
|
||||||
evset: epoll::Events,
|
evset: epoll::Events,
|
||||||
vrings: &[Arc<RwLock<Vring>>],
|
vrings: &[Arc<RwLock<Vring>>],
|
||||||
|
thread_id: usize,
|
||||||
) -> result::Result<bool, io::Error>;
|
) -> result::Result<bool, io::Error>;
|
||||||
|
|
||||||
/// Get virtio device configuration.
|
/// Get virtio device configuration.
|
||||||
@@ -105,7 +109,7 @@ pub trait VhostUserBackend: Send + Sync + 'static {
|
|||||||
/// When this EventFd is written to the worker thread will exit. An optional id may
|
/// When this EventFd is written to the worker thread will exit. An optional id may
|
||||||
/// also be provided, if it not provided then the exit event will be first event id
|
/// also be provided, if it not provided then the exit event will be first event id
|
||||||
/// after the last queue
|
/// after the last queue
|
||||||
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
|
fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,13 +117,16 @@ pub trait VhostUserBackend: Send + Sync + 'static {
|
|||||||
/// A default implementation is provided as we cannot expect all backends
|
/// A default implementation is provided as we cannot expect all backends
|
||||||
/// to implement this function.
|
/// to implement this function.
|
||||||
fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {}
|
fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {}
|
||||||
|
|
||||||
|
fn queues_per_thread(&self) -> Vec<u64> {
|
||||||
|
vec![0xffff_ffff]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This structure is the public API the backend is allowed to interact with
|
/// This structure is the public API the backend is allowed to interact with
|
||||||
/// in order to run a fully functional vhost-user daemon.
|
/// in order to run a fully functional vhost-user daemon.
|
||||||
pub struct VhostUserDaemon<S: VhostUserBackend> {
|
pub struct VhostUserDaemon<S: VhostUserBackend> {
|
||||||
name: String,
|
name: String,
|
||||||
sock_path: String,
|
|
||||||
handler: Arc<Mutex<VhostUserHandler<S>>>,
|
handler: Arc<Mutex<VhostUserHandler<S>>>,
|
||||||
main_thread: Option<thread::JoinHandle<Result<()>>>,
|
main_thread: Option<thread::JoinHandle<Result<()>>>,
|
||||||
}
|
}
|
||||||
@@ -131,14 +138,13 @@ impl<S: VhostUserBackend> VhostUserDaemon<S> {
|
|||||||
/// listening onto registered event. Those events can be vring events or
|
/// listening onto registered event. Those events can be vring events or
|
||||||
/// custom events from the backend, but they get to be registered later
|
/// custom events from the backend, but they get to be registered later
|
||||||
/// during the sequence.
|
/// during the sequence.
|
||||||
pub fn new(name: String, sock_path: String, backend: Arc<RwLock<S>>) -> Result<Self> {
|
pub fn new(name: String, backend: Arc<RwLock<S>>) -> Result<Self> {
|
||||||
let handler = Arc::new(Mutex::new(
|
let handler = Arc::new(Mutex::new(
|
||||||
VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?,
|
VhostUserHandler::new(backend).map_err(Error::NewVhostUserHandler)?,
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok(VhostUserDaemon {
|
Ok(VhostUserDaemon {
|
||||||
name,
|
name,
|
||||||
sock_path,
|
|
||||||
handler,
|
handler,
|
||||||
main_thread: None,
|
main_thread: None,
|
||||||
})
|
})
|
||||||
@@ -148,10 +154,9 @@ impl<S: VhostUserBackend> VhostUserDaemon<S> {
|
|||||||
/// all requests coming through this socket. This runs in an infinite loop
|
/// all requests coming through this socket. This runs in an infinite loop
|
||||||
/// that should be terminating once the other end of the socket (the VMM)
|
/// that should be terminating once the other end of the socket (the VMM)
|
||||||
/// disconnects.
|
/// disconnects.
|
||||||
pub fn start(&mut self) -> Result<()> {
|
pub fn start(&mut self, listener: Listener) -> Result<()> {
|
||||||
let mut slave_listener =
|
let mut slave_listener = SlaveListener::new(listener, self.handler.clone())
|
||||||
SlaveListener::new(self.sock_path.as_str(), true, self.handler.clone())
|
.map_err(Error::CreateSlaveListener)?;
|
||||||
.map_err(Error::CreateSlaveListener)?;
|
|
||||||
let mut slave_handler = slave_listener
|
let mut slave_handler = slave_listener
|
||||||
.accept()
|
.accept()
|
||||||
.map_err(Error::CreateSlaveReqHandler)?
|
.map_err(Error::CreateSlaveReqHandler)?
|
||||||
@@ -187,8 +192,8 @@ impl<S: VhostUserBackend> VhostUserDaemon<S> {
|
|||||||
/// Retrieve the vring worker. This is necessary to perform further
|
/// Retrieve the vring worker. This is necessary to perform further
|
||||||
/// actions like registering and unregistering some extra event file
|
/// actions like registering and unregistering some extra event file
|
||||||
/// descriptors.
|
/// descriptors.
|
||||||
pub fn get_vring_worker(&self) -> Arc<VringWorker> {
|
pub fn get_vring_workers(&self) -> Vec<Arc<VringWorker>> {
|
||||||
self.handler.lock().unwrap().get_vring_worker()
|
self.handler.lock().unwrap().get_vring_workers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,8 +213,6 @@ pub struct Vring {
|
|||||||
call: Option<EventFd>,
|
call: Option<EventFd>,
|
||||||
err: Option<EventFd>,
|
err: Option<EventFd>,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
event_idx: bool,
|
|
||||||
signalled_used: Option<Wrapping<u16>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vring {
|
impl Vring {
|
||||||
@@ -220,8 +223,6 @@ impl Vring {
|
|||||||
call: None,
|
call: None,
|
||||||
err: None,
|
err: None,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
event_idx: false,
|
|
||||||
signalled_used: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,31 +230,6 @@ impl Vring {
|
|||||||
&mut self.queue
|
&mut self.queue
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_event_idx(&mut self, enabled: bool) {
|
|
||||||
/* Also reset the last signalled event */
|
|
||||||
self.signalled_used = None;
|
|
||||||
self.event_idx = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn needs_notification(&mut self, mem: &GuestMemoryMmap, used_idx: Wrapping<u16>) -> bool {
|
|
||||||
if !self.event_idx {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut notify = true;
|
|
||||||
|
|
||||||
if let Some(old_idx) = self.signalled_used {
|
|
||||||
if let Some(used_event) = self.mut_queue().get_used_event(&mem) {
|
|
||||||
if (used_idx - used_event - Wrapping(1u16)) >= (used_idx - old_idx) {
|
|
||||||
notify = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.signalled_used = Some(used_idx);
|
|
||||||
notify
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn signal_used_queue(&mut self) -> result::Result<(), io::Error> {
|
pub fn signal_used_queue(&mut self) -> result::Result<(), io::Error> {
|
||||||
if let Some(call) = self.call.as_ref() {
|
if let Some(call) = self.call.as_ref() {
|
||||||
call.write(1)
|
call.write(1)
|
||||||
@@ -283,6 +259,7 @@ struct VringEpollHandler<S: VhostUserBackend> {
|
|||||||
backend: Arc<RwLock<S>>,
|
backend: Arc<RwLock<S>>,
|
||||||
vrings: Vec<Arc<RwLock<Vring>>>,
|
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||||
exit_event_id: Option<u16>,
|
exit_event_id: Option<u16>,
|
||||||
|
thread_id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: VhostUserBackend> VringEpollHandler<S> {
|
impl<S: VhostUserBackend> VringEpollHandler<S> {
|
||||||
@@ -310,9 +287,9 @@ impl<S: VhostUserBackend> VringEpollHandler<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.backend
|
self.backend
|
||||||
.write()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.handle_event(device_event, evset, &self.vrings)
|
.handle_event(device_event, evset, &self.vrings, self.thread_id)
|
||||||
.map_err(VringEpollHandlerError::HandleEventBackendHandling)
|
.map_err(VringEpollHandlerError::HandleEventBackendHandling)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,7 +307,13 @@ enum VringWorkerError {
|
|||||||
type VringWorkerResult<T> = std::result::Result<T, VringWorkerError>;
|
type VringWorkerResult<T> = std::result::Result<T, VringWorkerError>;
|
||||||
|
|
||||||
pub struct VringWorker {
|
pub struct VringWorker {
|
||||||
epoll_fd: RawFd,
|
epoll_file: File,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRawFd for VringWorker {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.epoll_file.as_raw_fd()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VringWorker {
|
impl VringWorker {
|
||||||
@@ -339,7 +322,7 @@ impl VringWorker {
|
|||||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||||
|
|
||||||
'epoll: loop {
|
'epoll: loop {
|
||||||
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) {
|
let num_events = match epoll::wait(self.epoll_file.as_raw_fd(), -1, &mut events[..]) {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.kind() == io::ErrorKind::Interrupted {
|
if e.kind() == io::ErrorKind::Interrupted {
|
||||||
@@ -393,7 +376,7 @@ impl VringWorker {
|
|||||||
data: u64,
|
data: u64,
|
||||||
) -> result::Result<(), io::Error> {
|
) -> result::Result<(), io::Error> {
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
self.epoll_fd,
|
self.epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
fd,
|
fd,
|
||||||
epoll::Event::new(ev_type, data),
|
epoll::Event::new(ev_type, data),
|
||||||
@@ -410,7 +393,7 @@ impl VringWorker {
|
|||||||
data: u64,
|
data: u64,
|
||||||
) -> result::Result<(), io::Error> {
|
) -> result::Result<(), io::Error> {
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
self.epoll_fd,
|
self.epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_DEL,
|
epoll::ControlOptions::EPOLL_CTL_DEL,
|
||||||
fd,
|
fd,
|
||||||
epoll::Event::new(ev_type, data),
|
epoll::Event::new(ev_type, data),
|
||||||
@@ -453,22 +436,24 @@ type VhostUserHandlerResult<T> = std::result::Result<T, VhostUserHandlerError>;
|
|||||||
|
|
||||||
struct VhostUserHandler<S: VhostUserBackend> {
|
struct VhostUserHandler<S: VhostUserBackend> {
|
||||||
backend: Arc<RwLock<S>>,
|
backend: Arc<RwLock<S>>,
|
||||||
worker: Arc<VringWorker>,
|
workers: Vec<Arc<VringWorker>>,
|
||||||
owned: bool,
|
owned: bool,
|
||||||
features_acked: bool,
|
features_acked: bool,
|
||||||
acked_features: u64,
|
acked_features: u64,
|
||||||
acked_protocol_features: u64,
|
acked_protocol_features: u64,
|
||||||
num_queues: usize,
|
num_queues: usize,
|
||||||
max_queue_size: usize,
|
max_queue_size: usize,
|
||||||
|
queues_per_thread: Vec<u64>,
|
||||||
memory: Option<Memory>,
|
memory: Option<Memory>,
|
||||||
vrings: Vec<Arc<RwLock<Vring>>>,
|
vrings: Vec<Arc<RwLock<Vring>>>,
|
||||||
worker_thread: Option<thread::JoinHandle<VringWorkerResult<()>>>,
|
worker_threads: Vec<thread::JoinHandle<VringWorkerResult<()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: VhostUserBackend> VhostUserHandler<S> {
|
impl<S: VhostUserBackend> VhostUserHandler<S> {
|
||||||
fn new(backend: Arc<RwLock<S>>) -> VhostUserHandlerResult<Self> {
|
fn new(backend: Arc<RwLock<S>>) -> VhostUserHandlerResult<Self> {
|
||||||
let num_queues = backend.read().unwrap().num_queues();
|
let num_queues = backend.read().unwrap().num_queues();
|
||||||
let max_queue_size = backend.read().unwrap().max_queue_size();
|
let max_queue_size = backend.read().unwrap().max_queue_size();
|
||||||
|
let queues_per_thread = backend.read().unwrap().queues_per_thread();
|
||||||
|
|
||||||
let mut vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
|
let mut vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
|
||||||
for _ in 0..num_queues {
|
for _ in 0..num_queues {
|
||||||
@@ -476,14 +461,20 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
|
|||||||
vrings.push(vring);
|
vrings.push(vring);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the epoll file descriptor
|
let mut workers = Vec::new();
|
||||||
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?;
|
let mut worker_threads = Vec::new();
|
||||||
|
for (thread_id, queues_mask) in queues_per_thread.iter().enumerate() {
|
||||||
|
// Create the epoll file descriptor
|
||||||
|
let epoll_fd = epoll::create(true).map_err(VhostUserHandlerError::EpollCreateFd)?;
|
||||||
|
// Use 'File' to enforce closing on 'epoll_fd'
|
||||||
|
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
|
||||||
|
|
||||||
let vring_worker = Arc::new(VringWorker { epoll_fd });
|
let vring_worker = Arc::new(VringWorker { epoll_file });
|
||||||
let worker = vring_worker.clone();
|
let worker = vring_worker.clone();
|
||||||
|
|
||||||
let exit_event_id =
|
let exit_event_id = if let Some((exit_event_fd, exit_event_id)) =
|
||||||
if let Some((exit_event_fd, exit_event_id)) = backend.read().unwrap().exit_event() {
|
backend.read().unwrap().exit_event(thread_id)
|
||||||
|
{
|
||||||
let exit_event_id = exit_event_id.unwrap_or(num_queues as u16);
|
let exit_event_id = exit_event_id.unwrap_or(num_queues as u16);
|
||||||
worker
|
worker
|
||||||
.register_listener(
|
.register_listener(
|
||||||
@@ -497,36 +488,47 @@ impl<S: VhostUserBackend> VhostUserHandler<S> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let vring_handler = VringEpollHandler {
|
let mut thread_vrings: Vec<Arc<RwLock<Vring>>> = Vec::new();
|
||||||
backend: backend.clone(),
|
for (index, vring) in vrings.iter().enumerate() {
|
||||||
vrings: vrings.clone(),
|
if (queues_mask >> index) & 1u64 == 1u64 {
|
||||||
exit_event_id,
|
thread_vrings.push(vring.clone());
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let worker_thread = Some(
|
let vring_handler = VringEpollHandler {
|
||||||
thread::Builder::new()
|
backend: backend.clone(),
|
||||||
|
vrings: thread_vrings,
|
||||||
|
exit_event_id,
|
||||||
|
thread_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let worker_thread = thread::Builder::new()
|
||||||
.name("vring_worker".to_string())
|
.name("vring_worker".to_string())
|
||||||
.spawn(move || vring_worker.run(vring_handler))
|
.spawn(move || vring_worker.run(vring_handler))
|
||||||
.map_err(VhostUserHandlerError::SpawnVringWorker)?,
|
.map_err(VhostUserHandlerError::SpawnVringWorker)?;
|
||||||
);
|
|
||||||
|
workers.push(worker);
|
||||||
|
worker_threads.push(worker_thread);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(VhostUserHandler {
|
Ok(VhostUserHandler {
|
||||||
backend,
|
backend,
|
||||||
worker,
|
workers,
|
||||||
owned: false,
|
owned: false,
|
||||||
features_acked: false,
|
features_acked: false,
|
||||||
acked_features: 0,
|
acked_features: 0,
|
||||||
acked_protocol_features: 0,
|
acked_protocol_features: 0,
|
||||||
num_queues,
|
num_queues,
|
||||||
max_queue_size,
|
max_queue_size,
|
||||||
|
queues_per_thread,
|
||||||
memory: None,
|
memory: None,
|
||||||
vrings,
|
vrings,
|
||||||
worker_thread,
|
worker_threads,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_vring_worker(&self) -> Arc<VringWorker> {
|
fn get_vring_workers(&self) -> Vec<Arc<VringWorker>> {
|
||||||
self.worker.clone()
|
self.workers.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult<u64> {
|
fn vmm_va_to_gpa(&self, vmm_va: u64) -> VhostUserHandlerResult<u64> {
|
||||||
@@ -584,6 +586,11 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
|||||||
vring.write().unwrap().enabled = vring_enabled;
|
vring.write().unwrap().enabled = vring_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.backend
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.acked_features(self.acked_features);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,6 +709,7 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
|||||||
self.vrings[index as usize]
|
self.vrings[index as usize]
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.mut_queue()
|
||||||
.set_event_idx(event_idx);
|
.set_event_idx(event_idx);
|
||||||
self.backend.write().unwrap().set_event_idx(event_idx);
|
self.backend.write().unwrap().set_event_idx(event_idx);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -718,9 +726,20 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
|||||||
// VHOST_USER_GET_VRING_BASE.
|
// VHOST_USER_GET_VRING_BASE.
|
||||||
self.vrings[index as usize].write().unwrap().queue.ready = false;
|
self.vrings[index as usize].write().unwrap().queue.ready = false;
|
||||||
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||||
self.worker
|
for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
|
||||||
.unregister_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
|
let shifted_queues_mask = queues_mask >> index;
|
||||||
.map_err(VhostUserError::ReqHandlerError)?;
|
if shifted_queues_mask & 1u64 == 1u64 {
|
||||||
|
let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones();
|
||||||
|
self.workers[thread_index]
|
||||||
|
.unregister_listener(
|
||||||
|
fd.as_raw_fd(),
|
||||||
|
epoll::Events::EPOLLIN,
|
||||||
|
u64::from(evt_idx),
|
||||||
|
)
|
||||||
|
.map_err(VhostUserError::ReqHandlerError)?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_avail = self.vrings[index as usize]
|
let next_avail = self.vrings[index as usize]
|
||||||
@@ -752,9 +771,20 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
|||||||
// VHOST_USER_GET_VRING_BASE.
|
// VHOST_USER_GET_VRING_BASE.
|
||||||
self.vrings[index as usize].write().unwrap().queue.ready = true;
|
self.vrings[index as usize].write().unwrap().queue.ready = true;
|
||||||
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
if let Some(fd) = self.vrings[index as usize].read().unwrap().kick.as_ref() {
|
||||||
self.worker
|
for (thread_index, queues_mask) in self.queues_per_thread.iter().enumerate() {
|
||||||
.register_listener(fd.as_raw_fd(), epoll::Events::EPOLLIN, u64::from(index))
|
let shifted_queues_mask = queues_mask >> index;
|
||||||
.map_err(VhostUserError::ReqHandlerError)?;
|
if shifted_queues_mask & 1u64 == 1u64 {
|
||||||
|
let evt_idx = queues_mask.count_ones() - shifted_queues_mask.count_ones();
|
||||||
|
self.workers[thread_index]
|
||||||
|
.register_listener(
|
||||||
|
fd.as_raw_fd(),
|
||||||
|
epoll::Events::EPOLLIN,
|
||||||
|
u64::from(evt_idx),
|
||||||
|
)
|
||||||
|
.map_err(VhostUserError::ReqHandlerError)?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -837,7 +867,7 @@ impl<S: VhostUserBackend> VhostUserSlaveReqHandler for VhostUserHandler<S> {
|
|||||||
|
|
||||||
impl<S: VhostUserBackend> Drop for VhostUserHandler<S> {
|
impl<S: VhostUserBackend> Drop for VhostUserHandler<S> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(thread) = self.worker_thread.take() {
|
for thread in self.worker_threads.drain(..) {
|
||||||
if let Err(e) = thread.join() {
|
if let Err(e) = thread.join() {
|
||||||
error!("Error in vring worker: {:?}", e);
|
error!("Error in vring worker: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.1.0"
|
bitflags = "1.1.0"
|
||||||
epoll = ">=4.0.1"
|
epoll = ">=4.0.1"
|
||||||
libc = "0.2.68"
|
libc = "0.2.71"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
qcow = { path = "../qcow" }
|
qcow = { path = "../qcow" }
|
||||||
vhost_user_backend = { path = "../vhost_user_backend" }
|
vhost_user_backend = { path = "../vhost_user_backend" }
|
||||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||||
virtio-bindings = "0.1.0"
|
virtio-bindings = "0.1.0"
|
||||||
vm-memory = "0.1.0"
|
vm-memory = "0.2.1"
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
|
vmm = { path = "../vmm" }
|
||||||
vmm-sys-util = ">=0.3.1"
|
vmm-sys-util = ">=0.3.1"
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ extern crate vhost_rs;
|
|||||||
extern crate vhost_user_backend;
|
extern crate vhost_user_backend;
|
||||||
extern crate vm_virtio;
|
extern crate vm_virtio;
|
||||||
|
|
||||||
use epoll;
|
|
||||||
use libc::EFD_NONBLOCK;
|
use libc::EFD_NONBLOCK;
|
||||||
use log::*;
|
use log::*;
|
||||||
use qcow::{self, ImageType, QcowFile};
|
use qcow::{self, ImageType, QcowFile};
|
||||||
@@ -21,25 +20,29 @@ use std::fs::File;
|
|||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::{Seek, SeekFrom, Write};
|
use std::io::{Seek, SeekFrom, Write};
|
||||||
use std::mem;
|
|
||||||
use std::num::Wrapping;
|
use std::num::Wrapping;
|
||||||
|
use std::ops::DerefMut;
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::slice;
|
use std::result;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use std::{convert, error, fmt, io};
|
use std::{convert, error, fmt, io};
|
||||||
use vhost_rs::vhost_user::message::*;
|
use vhost_rs::vhost_user::message::*;
|
||||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
use vhost_rs::vhost_user::Listener;
|
||||||
|
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
|
||||||
use virtio_bindings::bindings::virtio_blk::*;
|
use virtio_bindings::bindings::virtio_blk::*;
|
||||||
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||||
use vm_memory::{Bytes, GuestMemoryError, GuestMemoryMmap};
|
use vm_memory::ByteValued;
|
||||||
|
use vm_memory::{Bytes, GuestMemoryMmap};
|
||||||
use vm_virtio::block::{build_disk_image_id, Request};
|
use vm_virtio::block::{build_disk_image_id, Request};
|
||||||
|
use vm_virtio::VirtioBlockConfig;
|
||||||
|
use vmm::config::{OptionParser, OptionParserError, Toggle};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
const QUEUE_SIZE: usize = 1024;
|
|
||||||
const SECTOR_SHIFT: u8 = 9;
|
const SECTOR_SHIFT: u8 = 9;
|
||||||
const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT;
|
const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT;
|
||||||
const BLK_SIZE: u32 = 512;
|
const BLK_SIZE: u32 = 512;
|
||||||
@@ -51,37 +54,30 @@ const POLL_QUEUE_US: u128 = 50;
|
|||||||
trait DiskFile: Read + Seek + Write + Send + Sync {}
|
trait DiskFile: Read + Seek + Write + Send + Sync {}
|
||||||
impl<D: Read + Seek + Write + Send + Sync> DiskFile for D {}
|
impl<D: Read + Seek + Write + Send + Sync> DiskFile for D {}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
type Result<T> = std::result::Result<T, Error>;
|
||||||
pub type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
enum Error {
|
||||||
/// Failed to detect image type.
|
|
||||||
DetectImageType,
|
|
||||||
/// Bad memory address.
|
|
||||||
GuestMemory(GuestMemoryError),
|
|
||||||
/// Can't open image file.
|
|
||||||
OpenImage,
|
|
||||||
/// Failed to parse direct parameter.
|
|
||||||
ParseDirectParam,
|
|
||||||
/// Failed to parse image parameter.
|
|
||||||
ParseImageParam,
|
|
||||||
/// Failed to parse sock parameter.
|
|
||||||
ParseSockParam,
|
|
||||||
/// Failed to parse readonly parameter.
|
|
||||||
ParseReadOnlyParam,
|
|
||||||
/// Failed parsing fs number of queues parameter.
|
|
||||||
ParseBlkNumQueuesParam(std::num::ParseIntError),
|
|
||||||
/// Failed to parse the poll_queue parameter.
|
|
||||||
ParsePollQueueParam,
|
|
||||||
/// Failed to handle event other than input event.
|
|
||||||
HandleEventNotEpollIn,
|
|
||||||
/// Failed to create kill eventfd
|
/// Failed to create kill eventfd
|
||||||
CreateKillEventFd(io::Error),
|
CreateKillEventFd(io::Error),
|
||||||
|
/// Failed to parse configuration string
|
||||||
|
FailedConfigParse(OptionParserError),
|
||||||
|
/// Failed to handle event other than input event.
|
||||||
|
HandleEventNotEpollIn,
|
||||||
/// Failed to handle unknown event.
|
/// Failed to handle unknown event.
|
||||||
HandleEventUnknownEvent,
|
HandleEventUnknownEvent,
|
||||||
|
/// No path provided
|
||||||
|
PathParameterMissing,
|
||||||
|
/// No socket provided
|
||||||
|
SocketParameterMissing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const SYNTAX: &str = "vhost-user-block backend parameters \
|
||||||
|
\"path=<image_path>,socket=<socket_path>,num_queues=<number_of_queues>,\
|
||||||
|
queue_size=<size_of_each_queue>,readonly=true|false,direct=true|false,\
|
||||||
|
poll_queue=true|false\"";
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "vhost_user_block_error: {:?}", self)
|
write!(f, "vhost_user_block_error: {:?}", self)
|
||||||
@@ -96,70 +92,35 @@ impl convert::From<Error> for io::Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VhostUserBlkBackend {
|
struct VhostUserBlkThread {
|
||||||
mem: Option<GuestMemoryMmap>,
|
mem: Option<GuestMemoryMmap>,
|
||||||
vring_worker: Option<Arc<VringWorker>>,
|
disk_image: Arc<Mutex<dyn DiskFile>>,
|
||||||
disk_image: Box<dyn DiskFile>,
|
|
||||||
disk_image_id: Vec<u8>,
|
disk_image_id: Vec<u8>,
|
||||||
disk_nsectors: u64,
|
disk_nsectors: u64,
|
||||||
config: virtio_blk_config,
|
|
||||||
rdonly: bool,
|
|
||||||
poll_queue: bool,
|
|
||||||
event_idx: bool,
|
event_idx: bool,
|
||||||
kill_evt: EventFd,
|
kill_evt: EventFd,
|
||||||
|
writeback: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VhostUserBlkBackend {
|
impl VhostUserBlkThread {
|
||||||
pub fn new(
|
fn new(
|
||||||
image_path: String,
|
disk_image: Arc<Mutex<dyn DiskFile>>,
|
||||||
num_queues: usize,
|
disk_image_id: Vec<u8>,
|
||||||
rdonly: bool,
|
disk_nsectors: u64,
|
||||||
direct: bool,
|
writeback: Arc<AtomicBool>,
|
||||||
poll_queue: bool,
|
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let mut options = OpenOptions::new();
|
Ok(VhostUserBlkThread {
|
||||||
options.read(true);
|
|
||||||
options.write(!rdonly);
|
|
||||||
if direct {
|
|
||||||
options.custom_flags(libc::O_DIRECT);
|
|
||||||
}
|
|
||||||
let image: File = options.open(&image_path).unwrap();
|
|
||||||
let mut raw_img: vm_virtio::RawFile = vm_virtio::RawFile::new(image, direct);
|
|
||||||
|
|
||||||
let image_id = build_disk_image_id(&PathBuf::from(&image_path));
|
|
||||||
let image_type = qcow::detect_image_type(&mut raw_img).unwrap();
|
|
||||||
let mut image = match image_type {
|
|
||||||
ImageType::Raw => Box::new(raw_img) as Box<dyn DiskFile>,
|
|
||||||
ImageType::Qcow2 => Box::new(QcowFile::from(raw_img).unwrap()) as Box<dyn DiskFile>,
|
|
||||||
};
|
|
||||||
|
|
||||||
let nsectors = (image.seek(SeekFrom::End(0)).unwrap() as u64) / SECTOR_SIZE;
|
|
||||||
let mut config = virtio_blk_config::default();
|
|
||||||
|
|
||||||
config.capacity = nsectors;
|
|
||||||
config.blk_size = BLK_SIZE;
|
|
||||||
config.size_max = 65535;
|
|
||||||
config.seg_max = 128 - 2;
|
|
||||||
config.min_io_size = 1;
|
|
||||||
config.opt_io_size = 1;
|
|
||||||
config.num_queues = num_queues as u16;
|
|
||||||
config.wce = 1;
|
|
||||||
|
|
||||||
Ok(VhostUserBlkBackend {
|
|
||||||
mem: None,
|
mem: None,
|
||||||
vring_worker: None,
|
disk_image,
|
||||||
disk_image: image,
|
disk_image_id,
|
||||||
disk_image_id: image_id,
|
disk_nsectors,
|
||||||
disk_nsectors: nsectors,
|
|
||||||
config,
|
|
||||||
rdonly,
|
|
||||||
poll_queue,
|
|
||||||
event_idx: false,
|
event_idx: false,
|
||||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
||||||
|
writeback,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_queue(&mut self, vring: &mut Vring) -> bool {
|
fn process_queue(&mut self, vring: &mut Vring) -> bool {
|
||||||
let mut used_any = false;
|
let mut used_any = false;
|
||||||
let mem = match self.mem.as_ref() {
|
let mem = match self.mem.as_ref() {
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
@@ -170,10 +131,11 @@ impl VhostUserBlkBackend {
|
|||||||
debug!("got an element in the queue");
|
debug!("got an element in the queue");
|
||||||
let len;
|
let len;
|
||||||
match Request::parse(&head, mem) {
|
match Request::parse(&head, mem) {
|
||||||
Ok(request) => {
|
Ok(mut request) => {
|
||||||
debug!("element is a valid request");
|
debug!("element is a valid request");
|
||||||
|
request.set_writeback(self.writeback.load(Ordering::SeqCst));
|
||||||
let status = match request.execute(
|
let status = match request.execute(
|
||||||
&mut self.disk_image,
|
&mut self.disk_image.lock().unwrap().deref_mut(),
|
||||||
self.disk_nsectors,
|
self.disk_nsectors,
|
||||||
mem,
|
mem,
|
||||||
&self.disk_image_id,
|
&self.disk_image_id,
|
||||||
@@ -196,8 +158,9 @@ impl VhostUserBlkBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.event_idx {
|
if self.event_idx {
|
||||||
if let Some(used_idx) = vring.mut_queue().add_used(mem, head.index, len) {
|
let queue = vring.mut_queue();
|
||||||
if vring.needs_notification(&mem, Wrapping(used_idx)) {
|
if let Some(used_idx) = queue.add_used(mem, head.index, len) {
|
||||||
|
if queue.needs_notification(&mem, Wrapping(used_idx)) {
|
||||||
debug!("signalling queue");
|
debug!("signalling queue");
|
||||||
vring.signal_used_queue().unwrap();
|
vring.signal_used_queue().unwrap();
|
||||||
} else {
|
} else {
|
||||||
@@ -215,9 +178,103 @@ impl VhostUserBlkBackend {
|
|||||||
|
|
||||||
used_any
|
used_any
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
|
struct VhostUserBlkBackend {
|
||||||
self.vring_worker = vring_worker;
|
threads: Vec<Mutex<VhostUserBlkThread>>,
|
||||||
|
config: VirtioBlockConfig,
|
||||||
|
rdonly: bool,
|
||||||
|
poll_queue: bool,
|
||||||
|
queues_per_thread: Vec<u64>,
|
||||||
|
queue_size: usize,
|
||||||
|
acked_features: u64,
|
||||||
|
writeback: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VhostUserBlkBackend {
|
||||||
|
fn new(
|
||||||
|
image_path: String,
|
||||||
|
num_queues: usize,
|
||||||
|
rdonly: bool,
|
||||||
|
direct: bool,
|
||||||
|
poll_queue: bool,
|
||||||
|
queue_size: usize,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let mut options = OpenOptions::new();
|
||||||
|
options.read(true);
|
||||||
|
options.write(!rdonly);
|
||||||
|
if direct {
|
||||||
|
options.custom_flags(libc::O_DIRECT);
|
||||||
|
}
|
||||||
|
let image: File = options.open(&image_path).unwrap();
|
||||||
|
let mut raw_img: vm_virtio::RawFile = vm_virtio::RawFile::new(image, direct);
|
||||||
|
|
||||||
|
let image_id = build_disk_image_id(&PathBuf::from(&image_path));
|
||||||
|
let image_type = qcow::detect_image_type(&mut raw_img).unwrap();
|
||||||
|
let image = match image_type {
|
||||||
|
ImageType::Raw => Arc::new(Mutex::new(raw_img)) as Arc<Mutex<dyn DiskFile>>,
|
||||||
|
ImageType::Qcow2 => {
|
||||||
|
Arc::new(Mutex::new(QcowFile::from(raw_img).unwrap())) as Arc<Mutex<dyn DiskFile>>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let nsectors = (image.lock().unwrap().seek(SeekFrom::End(0)).unwrap() as u64) / SECTOR_SIZE;
|
||||||
|
let mut config = VirtioBlockConfig::default();
|
||||||
|
|
||||||
|
config.capacity = nsectors;
|
||||||
|
config.blk_size = BLK_SIZE;
|
||||||
|
config.size_max = 65535;
|
||||||
|
config.seg_max = 128 - 2;
|
||||||
|
config.min_io_size = 1;
|
||||||
|
config.opt_io_size = 1;
|
||||||
|
config.num_queues = num_queues as u16;
|
||||||
|
config.writeback = 1;
|
||||||
|
|
||||||
|
let mut queues_per_thread = Vec::new();
|
||||||
|
let mut threads = Vec::new();
|
||||||
|
let writeback = Arc::new(AtomicBool::new(true));
|
||||||
|
for i in 0..num_queues {
|
||||||
|
let thread = Mutex::new(VhostUserBlkThread::new(
|
||||||
|
image.clone(),
|
||||||
|
image_id.clone(),
|
||||||
|
nsectors,
|
||||||
|
writeback.clone(),
|
||||||
|
)?);
|
||||||
|
threads.push(thread);
|
||||||
|
queues_per_thread.push(0b1 << i);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(VhostUserBlkBackend {
|
||||||
|
threads,
|
||||||
|
config,
|
||||||
|
rdonly,
|
||||||
|
poll_queue,
|
||||||
|
queues_per_thread,
|
||||||
|
queue_size,
|
||||||
|
acked_features: 0,
|
||||||
|
writeback,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_writeback(&mut self) {
|
||||||
|
// Use writeback from config if VIRTIO_BLK_F_CONFIG_WCE
|
||||||
|
let writeback =
|
||||||
|
if self.acked_features & 1 << VIRTIO_BLK_F_CONFIG_WCE == 1 << VIRTIO_BLK_F_CONFIG_WCE {
|
||||||
|
self.config.writeback == 1
|
||||||
|
} else {
|
||||||
|
// Else check if VIRTIO_BLK_F_FLUSH negotiated
|
||||||
|
self.acked_features & 1 << VIRTIO_BLK_F_FLUSH == 1 << VIRTIO_BLK_F_FLUSH
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Changing cache mode to {}",
|
||||||
|
if writeback {
|
||||||
|
"writeback"
|
||||||
|
} else {
|
||||||
|
"writethrough"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
self.writeback.store(writeback, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +284,7 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn max_queue_size(&self) -> usize {
|
fn max_queue_size(&self) -> usize {
|
||||||
QUEUE_SIZE
|
self.queue_size as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
fn features(&self) -> u64 {
|
fn features(&self) -> u64 {
|
||||||
@@ -235,6 +292,7 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
|||||||
| 1 << VIRTIO_BLK_F_CONFIG_WCE
|
| 1 << VIRTIO_BLK_F_CONFIG_WCE
|
||||||
| 1 << VIRTIO_RING_F_EVENT_IDX
|
| 1 << VIRTIO_RING_F_EVENT_IDX
|
||||||
| 1 << VIRTIO_F_VERSION_1
|
| 1 << VIRTIO_F_VERSION_1
|
||||||
|
| 1 << VIRTIO_BLK_F_FLUSH
|
||||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
|
||||||
|
|
||||||
if self.rdonly {
|
if self.rdonly {
|
||||||
@@ -243,24 +301,34 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
|||||||
avail_features
|
avail_features
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn acked_features(&mut self, features: u64) {
|
||||||
|
self.acked_features = features;
|
||||||
|
self.update_writeback();
|
||||||
|
}
|
||||||
|
|
||||||
fn protocol_features(&self) -> VhostUserProtocolFeatures {
|
fn protocol_features(&self) -> VhostUserProtocolFeatures {
|
||||||
VhostUserProtocolFeatures::CONFIG
|
VhostUserProtocolFeatures::CONFIG
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_event_idx(&mut self, enabled: bool) {
|
fn set_event_idx(&mut self, enabled: bool) {
|
||||||
self.event_idx = enabled;
|
for thread in self.threads.iter() {
|
||||||
|
thread.lock().unwrap().event_idx = enabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||||
self.mem = Some(mem);
|
for thread in self.threads.iter() {
|
||||||
|
thread.lock().unwrap().mem = Some(mem.clone());
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_event(
|
fn handle_event(
|
||||||
&mut self,
|
&self,
|
||||||
device_event: u16,
|
device_event: u16,
|
||||||
evset: epoll::Events,
|
evset: epoll::Events,
|
||||||
vrings: &[Arc<RwLock<Vring>>],
|
vrings: &[Arc<RwLock<Vring>>],
|
||||||
|
thread_id: usize,
|
||||||
) -> VhostUserBackendResult<bool> {
|
) -> VhostUserBackendResult<bool> {
|
||||||
if evset != epoll::Events::EPOLLIN {
|
if evset != epoll::Events::EPOLLIN {
|
||||||
return Err(Error::HandleEventNotEpollIn.into());
|
return Err(Error::HandleEventNotEpollIn.into());
|
||||||
@@ -268,16 +336,17 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
|||||||
|
|
||||||
debug!("event received: {:?}", device_event);
|
debug!("event received: {:?}", device_event);
|
||||||
|
|
||||||
|
let mut thread = self.threads[thread_id].lock().unwrap();
|
||||||
match device_event {
|
match device_event {
|
||||||
q if device_event < self.config.num_queues => {
|
0 => {
|
||||||
let mut vring = vrings[q as usize].write().unwrap();
|
let mut vring = vrings[0].write().unwrap();
|
||||||
|
|
||||||
if self.poll_queue {
|
if self.poll_queue {
|
||||||
// Actively poll the queue until POLL_QUEUE_US has passed
|
// Actively poll the queue until POLL_QUEUE_US has passed
|
||||||
// without seeing a new request.
|
// without seeing a new request.
|
||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
if self.process_queue(&mut vring) {
|
if thread.process_queue(&mut vring) {
|
||||||
now = Instant::now();
|
now = Instant::now();
|
||||||
} else if now.elapsed().as_micros() > POLL_QUEUE_US {
|
} else if now.elapsed().as_micros() > POLL_QUEUE_US {
|
||||||
break;
|
break;
|
||||||
@@ -285,7 +354,7 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.event_idx {
|
if thread.event_idx {
|
||||||
// vm-virtio's Queue implementation only checks avail_index
|
// vm-virtio's Queue implementation only checks avail_index
|
||||||
// once, so to properly support EVENT_IDX we need to keep
|
// once, so to properly support EVENT_IDX we need to keep
|
||||||
// calling process_queue() until it stops finding new
|
// calling process_queue() until it stops finding new
|
||||||
@@ -293,14 +362,14 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
|||||||
loop {
|
loop {
|
||||||
vring
|
vring
|
||||||
.mut_queue()
|
.mut_queue()
|
||||||
.update_avail_event(self.mem.as_ref().unwrap());
|
.update_avail_event(thread.mem.as_ref().unwrap());
|
||||||
if !self.process_queue(&mut vring) {
|
if !thread.process_queue(&mut vring) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Without EVENT_IDX, a single call is enough.
|
// Without EVENT_IDX, a single call is enough.
|
||||||
self.process_queue(&mut vring);
|
thread.process_queue(&mut vring);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
@@ -310,86 +379,98 @@ impl VhostUserBackend for VhostUserBlkBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
|
fn get_config(&self, _offset: u32, _size: u32) -> Vec<u8> {
|
||||||
// self.config is a statically allocated virtio_blk_config
|
self.config.as_slice().to_vec()
|
||||||
let buf = unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
&self.config as *const virtio_blk_config as *const _,
|
|
||||||
mem::size_of::<virtio_blk_config>(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
buf.to_vec()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
|
fn set_config(&mut self, offset: u32, data: &[u8]) -> result::Result<(), io::Error> {
|
||||||
Some((self.kill_evt.try_clone().unwrap(), None))
|
let config_slice = self.config.as_mut_slice();
|
||||||
|
let data_len = data.len() as u32;
|
||||||
|
let config_len = config_slice.len() as u32;
|
||||||
|
if offset + data_len > config_len {
|
||||||
|
error!("Failed to write config space");
|
||||||
|
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||||
|
}
|
||||||
|
let (_, right) = config_slice.split_at_mut(offset as usize);
|
||||||
|
right.copy_from_slice(&data[..]);
|
||||||
|
self.update_writeback();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_event(&self, thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||||
|
// The exit event is placed after the queue, which is event index 1.
|
||||||
|
Some((
|
||||||
|
self.threads[thread_index]
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.kill_evt
|
||||||
|
.try_clone()
|
||||||
|
.unwrap(),
|
||||||
|
Some(1),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queues_per_thread(&self) -> Vec<u64> {
|
||||||
|
self.queues_per_thread.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VhostUserBlkBackendConfig<'a> {
|
struct VhostUserBlkBackendConfig {
|
||||||
pub image: &'a str,
|
path: String,
|
||||||
pub sock: &'a str,
|
socket: String,
|
||||||
pub num_queues: usize,
|
num_queues: usize,
|
||||||
pub readonly: bool,
|
queue_size: usize,
|
||||||
pub direct: bool,
|
readonly: bool,
|
||||||
pub poll_queue: bool,
|
direct: bool,
|
||||||
|
poll_queue: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> VhostUserBlkBackendConfig<'a> {
|
impl VhostUserBlkBackendConfig {
|
||||||
pub fn parse(backend: &'a str) -> Result<Self> {
|
fn parse(backend: &str) -> Result<Self> {
|
||||||
let params_list: Vec<&str> = backend.split(',').collect();
|
let mut parser = OptionParser::new();
|
||||||
|
parser
|
||||||
|
.add("path")
|
||||||
|
.add("readonly")
|
||||||
|
.add("direct")
|
||||||
|
.add("num_queues")
|
||||||
|
.add("queue_size")
|
||||||
|
.add("socket")
|
||||||
|
.add("poll_queue");
|
||||||
|
parser.parse(backend).map_err(Error::FailedConfigParse)?;
|
||||||
|
|
||||||
let mut image: &str = "";
|
let path = parser.get("path").ok_or(Error::PathParameterMissing)?;
|
||||||
let mut sock: &str = "";
|
let readonly = parser
|
||||||
let mut num_queues_str: &str = "";
|
.convert::<Toggle>("readonly")
|
||||||
let mut readonly: bool = false;
|
.map_err(Error::FailedConfigParse)?
|
||||||
let mut direct: bool = false;
|
.unwrap_or(Toggle(false))
|
||||||
let mut poll_queue: bool = true;
|
.0;
|
||||||
|
let direct = parser
|
||||||
|
.convert::<Toggle>("direct")
|
||||||
|
.map_err(Error::FailedConfigParse)?
|
||||||
|
.unwrap_or(Toggle(false))
|
||||||
|
.0;
|
||||||
|
let num_queues = parser
|
||||||
|
.convert("num_queues")
|
||||||
|
.map_err(Error::FailedConfigParse)?
|
||||||
|
.unwrap_or(1);
|
||||||
|
let socket = parser.get("socket").ok_or(Error::SocketParameterMissing)?;
|
||||||
|
let poll_queue = parser
|
||||||
|
.convert::<Toggle>("poll_queue")
|
||||||
|
.map_err(Error::FailedConfigParse)?
|
||||||
|
.unwrap_or_else(|| Toggle(true))
|
||||||
|
.0;
|
||||||
|
let queue_size = parser
|
||||||
|
.convert("queue_size")
|
||||||
|
.map_err(Error::FailedConfigParse)?
|
||||||
|
.unwrap_or(1024);
|
||||||
|
|
||||||
for param in params_list.iter() {
|
|
||||||
if param.starts_with("image=") {
|
|
||||||
image = ¶m[6..];
|
|
||||||
} else if param.starts_with("sock=") {
|
|
||||||
sock = ¶m[5..];
|
|
||||||
} else if param.starts_with("num_queues=") {
|
|
||||||
num_queues_str = ¶m[11..];
|
|
||||||
} else if param.starts_with("readonly=") {
|
|
||||||
readonly = match param[9..].parse::<bool>() {
|
|
||||||
Ok(b) => b,
|
|
||||||
Err(_) => return Err(Error::ParseReadOnlyParam),
|
|
||||||
}
|
|
||||||
} else if param.starts_with("direct=") {
|
|
||||||
direct = match param[7..].parse::<bool>() {
|
|
||||||
Ok(b) => b,
|
|
||||||
Err(_) => return Err(Error::ParseDirectParam),
|
|
||||||
}
|
|
||||||
} else if param.starts_with("poll_queue=") {
|
|
||||||
poll_queue = match param[11..].parse::<bool>() {
|
|
||||||
Ok(b) => b,
|
|
||||||
Err(_) => return Err(Error::ParsePollQueueParam),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut num_queues: usize = 1;
|
|
||||||
if image.is_empty() {
|
|
||||||
return Err(Error::ParseImageParam);
|
|
||||||
}
|
|
||||||
if sock.is_empty() {
|
|
||||||
return Err(Error::ParseSockParam);
|
|
||||||
}
|
|
||||||
if !num_queues_str.is_empty() {
|
|
||||||
num_queues = num_queues_str
|
|
||||||
.parse()
|
|
||||||
.map_err(Error::ParseBlkNumQueuesParam)?;
|
|
||||||
}
|
|
||||||
Ok(VhostUserBlkBackendConfig {
|
Ok(VhostUserBlkBackendConfig {
|
||||||
image,
|
path,
|
||||||
sock,
|
socket,
|
||||||
num_queues,
|
num_queues,
|
||||||
readonly,
|
readonly,
|
||||||
direct,
|
direct,
|
||||||
poll_queue,
|
poll_queue,
|
||||||
|
queue_size,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,33 +486,26 @@ pub fn start_block_backend(backend_command: &str) {
|
|||||||
|
|
||||||
let blk_backend = Arc::new(RwLock::new(
|
let blk_backend = Arc::new(RwLock::new(
|
||||||
VhostUserBlkBackend::new(
|
VhostUserBlkBackend::new(
|
||||||
backend_config.image.to_string(),
|
backend_config.path,
|
||||||
backend_config.num_queues,
|
backend_config.num_queues,
|
||||||
backend_config.readonly,
|
backend_config.readonly,
|
||||||
backend_config.direct,
|
backend_config.direct,
|
||||||
backend_config.poll_queue,
|
backend_config.poll_queue,
|
||||||
|
backend_config.queue_size,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
));
|
));
|
||||||
|
|
||||||
debug!("blk_backend is created!\n");
|
debug!("blk_backend is created!\n");
|
||||||
|
|
||||||
|
let listener = Listener::new(&backend_config.socket, true).unwrap();
|
||||||
|
|
||||||
let name = "vhost-user-blk-backend";
|
let name = "vhost-user-blk-backend";
|
||||||
let mut blk_daemon = VhostUserDaemon::new(
|
let mut blk_daemon = VhostUserDaemon::new(name.to_string(), blk_backend.clone()).unwrap();
|
||||||
name.to_string(),
|
|
||||||
backend_config.sock.to_string(),
|
|
||||||
blk_backend.clone(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
debug!("blk_daemon is created!\n");
|
debug!("blk_daemon is created!\n");
|
||||||
|
|
||||||
let vring_worker = blk_daemon.get_vring_worker();
|
if let Err(e) = blk_daemon.start(listener) {
|
||||||
blk_backend
|
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.set_vring_worker(Some(vring_worker));
|
|
||||||
|
|
||||||
if let Err(e) = blk_daemon.start() {
|
|
||||||
error!(
|
error!(
|
||||||
"Failed to start daemon for vhost-user-block with error: {:?}\n",
|
"Failed to start daemon for vhost-user-block with error: {:?}\n",
|
||||||
e
|
e
|
||||||
@@ -443,8 +517,9 @@ pub fn start_block_backend(backend_command: &str) {
|
|||||||
error!("Error from the main thread: {:?}", e);
|
error!("Error from the main thread: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let kill_evt = &blk_backend.write().unwrap().kill_evt;
|
for thread in blk_backend.read().unwrap().threads.iter() {
|
||||||
if let Err(e) = kill_evt.write(1) {
|
if let Err(e) = thread.lock().unwrap().kill_evt.write(1) {
|
||||||
error!("Error shutting down worker thread: {:?}", e)
|
error!("Error shutting down worker thread: {:?}", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.1.0"
|
bitflags = "1.1.0"
|
||||||
libc = "0.2.68"
|
libc = "0.2.71"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
vm-memory = "0.1.0"
|
# Match the version in vmm
|
||||||
|
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.21.1" }
|
||||||
|
tempdir= "0.3.7"
|
||||||
|
vm-memory = "0.2.1"
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||||
|
|||||||
@@ -563,7 +563,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -583,15 +583,15 @@ mod tests {
|
|||||||
assert_eq!(reader.bytes_read(), 0);
|
assert_eq!(reader.bytes_read(), 0);
|
||||||
|
|
||||||
let mut buffer = [0 as u8; 64];
|
let mut buffer = [0 as u8; 64];
|
||||||
if let Err(_) = reader.read_exact(&mut buffer) {
|
if let Err(e) = reader.read_exact(&mut buffer) {
|
||||||
panic!("read_exact should not fail here");
|
panic!("read_exact should not fail here: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(reader.available_bytes(), 42);
|
assert_eq!(reader.available_bytes(), 42);
|
||||||
assert_eq!(reader.bytes_read(), 64);
|
assert_eq!(reader.bytes_read(), 64);
|
||||||
|
|
||||||
match reader.read(&mut buffer) {
|
match reader.read(&mut buffer) {
|
||||||
Err(_) => panic!("read should not fail here"),
|
Err(e) => panic!("read should not fail here: {:?}", e),
|
||||||
Ok(length) => assert_eq!(length, 42),
|
Ok(length) => assert_eq!(length, 42),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,7 +604,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -623,16 +623,16 @@ mod tests {
|
|||||||
assert_eq!(writer.available_bytes(), 106);
|
assert_eq!(writer.available_bytes(), 106);
|
||||||
assert_eq!(writer.bytes_written(), 0);
|
assert_eq!(writer.bytes_written(), 0);
|
||||||
|
|
||||||
let mut buffer = [0 as u8; 64];
|
let buffer = [0 as u8; 64];
|
||||||
if let Err(_) = writer.write_all(&mut buffer) {
|
if let Err(e) = writer.write_all(&buffer) {
|
||||||
panic!("write_all should not fail here");
|
panic!("write_all should not fail here: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(writer.available_bytes(), 42);
|
assert_eq!(writer.available_bytes(), 42);
|
||||||
assert_eq!(writer.bytes_written(), 64);
|
assert_eq!(writer.bytes_written(), 64);
|
||||||
|
|
||||||
match writer.write(&mut buffer) {
|
match writer.write(&buffer) {
|
||||||
Err(_) => panic!("write should not fail here"),
|
Err(e) => panic!("write should not fail here {:?}", e),
|
||||||
Ok(length) => assert_eq!(length, 42),
|
Ok(length) => assert_eq!(length, 42),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +645,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -670,7 +670,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -695,7 +695,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -743,9 +743,9 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let secret: Le32 = 0x12345678.into();
|
let secret: Le32 = 0x1234_5678.into();
|
||||||
|
|
||||||
// Create a descriptor chain with memory regions that are properly separated.
|
// Create a descriptor chain with memory regions that are properly separated.
|
||||||
let chain_writer = create_descriptor_chain(
|
let chain_writer = create_descriptor_chain(
|
||||||
@@ -757,8 +757,8 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.expect("create_descriptor_chain failed");
|
.expect("create_descriptor_chain failed");
|
||||||
let mut writer = Writer::new(&memory, chain_writer).expect("failed to create Writer");
|
let mut writer = Writer::new(&memory, chain_writer).expect("failed to create Writer");
|
||||||
if let Err(_) = writer.write_obj(secret) {
|
if let Err(e) = writer.write_obj(secret) {
|
||||||
panic!("write_obj should not fail here");
|
panic!("write_obj should not fail here: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now create new descriptor chain pointing to the same memory and try to read it.
|
// Now create new descriptor chain pointing to the same memory and try to read it.
|
||||||
@@ -772,7 +772,7 @@ mod tests {
|
|||||||
.expect("create_descriptor_chain failed");
|
.expect("create_descriptor_chain failed");
|
||||||
let mut reader = Reader::new(&memory, chain_reader).expect("failed to create Reader");
|
let mut reader = Reader::new(&memory, chain_reader).expect("failed to create Reader");
|
||||||
match reader.read_obj::<Le32>() {
|
match reader.read_obj::<Le32>() {
|
||||||
Err(_) => panic!("read_obj should not fail here"),
|
Err(e) => panic!("read_obj should not fail here: {:?}", e),
|
||||||
Ok(read_secret) => assert_eq!(read_secret, secret),
|
Ok(read_secret) => assert_eq!(read_secret, secret),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -782,7 +782,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -795,8 +795,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(1024);
|
let mut buf = vec![0; 1024];
|
||||||
buf.resize(1024, 0);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
reader
|
reader
|
||||||
@@ -812,7 +811,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -841,7 +840,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -870,7 +869,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -899,7 +898,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -928,7 +927,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -947,7 +946,7 @@ mod tests {
|
|||||||
.expect("create_descriptor_chain failed");
|
.expect("create_descriptor_chain failed");
|
||||||
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
let mut reader = Reader::new(&memory, chain).expect("failed to create Reader");
|
||||||
|
|
||||||
if let Ok(_) = reader.split_at(256) {
|
if reader.split_at(256).is_ok() {
|
||||||
panic!("successfully split Reader with out of bounds offset");
|
panic!("successfully split Reader with out of bounds offset");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -957,7 +956,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
@@ -981,7 +980,7 @@ mod tests {
|
|||||||
use DescriptorType::*;
|
use DescriptorType::*;
|
||||||
|
|
||||||
let memory_start_addr = GuestAddress(0x0);
|
let memory_start_addr = GuestAddress(0x0);
|
||||||
let memory = GuestMemoryMmap::from_ranges(&vec![(memory_start_addr, 0x10000)]).unwrap();
|
let memory = GuestMemoryMmap::from_ranges(&[(memory_start_addr, 0x10000)]).unwrap();
|
||||||
|
|
||||||
let chain = create_descriptor_chain(
|
let chain = create_descriptor_chain(
|
||||||
&memory,
|
&memory,
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ use std::io;
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use libc;
|
|
||||||
|
|
||||||
use crate::fuse;
|
use crate::fuse;
|
||||||
|
|
||||||
use super::fs_cache_req_handler::FsCacheReqHandler;
|
use super::fs_cache_req_handler::FsCacheReqHandler;
|
||||||
@@ -682,11 +680,11 @@ pub trait FileSystem {
|
|||||||
/// implementation did not return a `Handle` from `open` then the contents of `handle` are
|
/// implementation did not return a `Handle` from `open` then the contents of `handle` are
|
||||||
/// undefined.
|
/// undefined.
|
||||||
///
|
///
|
||||||
/// If the `FsOptions::HANDLE_KILLPRIV` feature is not enabled then then the file system is
|
|
||||||
/// expected to clear the setuid and setgid bits.
|
|
||||||
///
|
|
||||||
/// If `delayed_write` is true then it indicates that this is a write for buffered data.
|
/// If `delayed_write` is true then it indicates that this is a write for buffered data.
|
||||||
///
|
///
|
||||||
|
/// If `kill_priv` is true then it indicates that the file system is expected to clear the
|
||||||
|
/// setuid and setgid bits.
|
||||||
|
///
|
||||||
/// This method should return exactly the number of bytes requested by the kernel, except in the
|
/// This method should return exactly the number of bytes requested by the kernel, except in the
|
||||||
/// case of error. An exception to this rule is if the file was opened with the "direct I/O"
|
/// case of error. An exception to this rule is if the file was opened with the "direct I/O"
|
||||||
/// option (`libc::O_DIRECT`), in which case the kernel will forward the return code from this
|
/// option (`libc::O_DIRECT`), in which case the kernel will forward the return code from this
|
||||||
@@ -702,6 +700,7 @@ pub trait FileSystem {
|
|||||||
offset: u64,
|
offset: u64,
|
||||||
lock_owner: Option<u64>,
|
lock_owner: Option<u64>,
|
||||||
delayed_write: bool,
|
delayed_write: bool,
|
||||||
|
kill_priv: bool,
|
||||||
flags: u32,
|
flags: u32,
|
||||||
) -> io::Result<usize> {
|
) -> io::Result<usize> {
|
||||||
Err(io::Error::from_raw_os_error(libc::ENOSYS))
|
Err(io::Error::from_raw_os_error(libc::ENOSYS))
|
||||||
@@ -1096,6 +1095,22 @@ pub trait FileSystem {
|
|||||||
Err(io::Error::from_raw_os_error(libc::ENOSYS))
|
Err(io::Error::from_raw_os_error(libc::ENOSYS))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn copyfilerange(
|
||||||
|
&self,
|
||||||
|
ctx: Context,
|
||||||
|
inode_in: Self::Inode,
|
||||||
|
handle_in: Self::Handle,
|
||||||
|
offset_in: u64,
|
||||||
|
inode_out: Self::Inode,
|
||||||
|
handle_out: Self::Handle,
|
||||||
|
offset_out: u64,
|
||||||
|
len: u64,
|
||||||
|
flags: u64,
|
||||||
|
) -> io::Result<usize> {
|
||||||
|
Err(io::Error::from_raw_os_error(libc::ENOSYS))
|
||||||
|
}
|
||||||
|
|
||||||
/// TODO: support this
|
/// TODO: support this
|
||||||
fn getlk(&self) -> io::Result<()> {
|
fn getlk(&self) -> io::Result<()> {
|
||||||
Err(io::Error::from_raw_os_error(libc::ENOSYS))
|
Err(io::Error::from_raw_os_error(libc::ENOSYS))
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ impl FsCacheReqHandler for SlaveFsCacheReq {
|
|||||||
VhostUserFSSlaveMsgFlags::MAP_R
|
VhostUserFSSlaveMsgFlags::MAP_R
|
||||||
};
|
};
|
||||||
|
|
||||||
self.fs_slave_map(&msg, fd)
|
self.fs_slave_map(&msg, fd)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unmap(&mut self, requests: Vec<fuse::RemovemappingOne>) -> io::Result<()> {
|
fn unmap(&mut self, requests: Vec<fuse::RemovemappingOne>) -> io::Result<()> {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use libc;
|
|
||||||
use vm_memory::ByteValued;
|
use vm_memory::ByteValued;
|
||||||
|
|
||||||
/// Version number of this interface.
|
/// Version number of this interface.
|
||||||
@@ -55,6 +54,9 @@ const FOPEN_KEEP_CACHE: u32 = 2;
|
|||||||
/// The file is not seekable.
|
/// The file is not seekable.
|
||||||
const FOPEN_NONSEEKABLE: u32 = 4;
|
const FOPEN_NONSEEKABLE: u32 = 4;
|
||||||
|
|
||||||
|
/// Allow caching this directory.
|
||||||
|
const FOPEN_CACHE_DIR: u32 = 8;
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
/// Options controlling the behavior of files opened by the server in response
|
/// Options controlling the behavior of files opened by the server in response
|
||||||
/// to an open or create request.
|
/// to an open or create request.
|
||||||
@@ -62,6 +64,7 @@ bitflags! {
|
|||||||
const DIRECT_IO = FOPEN_DIRECT_IO;
|
const DIRECT_IO = FOPEN_DIRECT_IO;
|
||||||
const KEEP_CACHE = FOPEN_KEEP_CACHE;
|
const KEEP_CACHE = FOPEN_KEEP_CACHE;
|
||||||
const NONSEEKABLE = FOPEN_NONSEEKABLE;
|
const NONSEEKABLE = FOPEN_NONSEEKABLE;
|
||||||
|
const CACHE_DIR = FOPEN_CACHE_DIR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +133,21 @@ const HANDLE_KILLPRIV: u32 = 524_288;
|
|||||||
/// FileSystem supports posix acls.
|
/// FileSystem supports posix acls.
|
||||||
const POSIX_ACL: u32 = 1_048_576;
|
const POSIX_ACL: u32 = 1_048_576;
|
||||||
|
|
||||||
|
/// Reading the device after abort returns ECONNABORTED.
|
||||||
|
const ABORT_ERROR: u32 = 2_097_152;
|
||||||
|
|
||||||
|
/// Init_out.max_pages contains the max number of req pages.
|
||||||
|
const MAX_PAGES: u32 = 4_194_304;
|
||||||
|
|
||||||
|
/// Cache READLINK responses
|
||||||
|
const CACHE_SYMLINKS: u32 = 8_388_608;
|
||||||
|
|
||||||
|
/// Kernel supports zero-message opendir
|
||||||
|
const NO_OPENDIR_SUPPORT: u32 = 16_777_216;
|
||||||
|
|
||||||
|
/// Only invalidate cached pages on explicit request
|
||||||
|
const EXPLICIT_INVAL_DATA: u32 = 33_554_432;
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
/// A bitfield passed in as a parameter to and returned from the `init` method of the
|
/// A bitfield passed in as a parameter to and returned from the `init` method of the
|
||||||
/// `FileSystem` trait.
|
/// `FileSystem` trait.
|
||||||
@@ -296,6 +314,43 @@ bitflags! {
|
|||||||
///
|
///
|
||||||
/// This feature is disabled by default.
|
/// This feature is disabled by default.
|
||||||
const POSIX_ACL = POSIX_ACL;
|
const POSIX_ACL = POSIX_ACL;
|
||||||
|
|
||||||
|
/// Indicates that if the connection is gone because of sysfs abort, reading from the device
|
||||||
|
/// will return -ECONNABORTED.
|
||||||
|
///
|
||||||
|
/// This feature is not currently supported.
|
||||||
|
const ABORT_ERROR = ABORT_ERROR;
|
||||||
|
|
||||||
|
/// Indicates support for negotiating the maximum number of pages supported.
|
||||||
|
///
|
||||||
|
/// If this feature is enabled, we can tell the kernel the maximum number of pages that we
|
||||||
|
/// support to transfer in a single request.
|
||||||
|
///
|
||||||
|
/// This feature is enabled by default if supported by the kernel.
|
||||||
|
const MAX_PAGES = MAX_PAGES;
|
||||||
|
|
||||||
|
/// Indicates that the kernel supports caching READLINK responses.
|
||||||
|
///
|
||||||
|
/// This feature is not currently supported.
|
||||||
|
const CACHE_SYMLINKS = CACHE_SYMLINKS;
|
||||||
|
|
||||||
|
/// Indicates support for zero-message opens. If this flag is set in the `capable` parameter
|
||||||
|
/// of the `init` trait method, then the file system may return `ENOSYS` from the opendir() handler
|
||||||
|
/// to indicate success. Further attempts to open directories will be handled in the kernel. (If
|
||||||
|
/// this flag is not set, returning ENOSYS will be treated as an error and signaled to the
|
||||||
|
/// caller).
|
||||||
|
///
|
||||||
|
/// Setting (or not setting) the field in the `FsOptions` returned from the `init` method
|
||||||
|
/// has no effect.
|
||||||
|
const ZERO_MESSAGE_OPENDIR = NO_OPENDIR_SUPPORT;
|
||||||
|
|
||||||
|
/// Indicates support for explicit data invalidation. If this feature is enabled, the
|
||||||
|
/// server is fully responsible for data cache invalidation, and the kernel won't
|
||||||
|
/// invalidate files data cache on size change and only truncate that cache to new size
|
||||||
|
/// in case the size decreased.
|
||||||
|
///
|
||||||
|
/// This feature is not currently supported.
|
||||||
|
const EXPLICIT_INVAL_DATA = EXPLICIT_INVAL_DATA;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,6 +372,9 @@ pub const WRITE_CACHE: u32 = 1;
|
|||||||
/// `lock_owner` field is valid.
|
/// `lock_owner` field is valid.
|
||||||
pub const WRITE_LOCKOWNER: u32 = 2;
|
pub const WRITE_LOCKOWNER: u32 = 2;
|
||||||
|
|
||||||
|
/// Kill suid and sgid bits
|
||||||
|
pub const WRITE_KILL_PRIV: u32 = 4;
|
||||||
|
|
||||||
// Read flags.
|
// Read flags.
|
||||||
pub const READ_LOCKOWNER: u32 = 2;
|
pub const READ_LOCKOWNER: u32 = 2;
|
||||||
|
|
||||||
@@ -337,6 +395,9 @@ const IOCTL_32BIT: u32 = 8;
|
|||||||
/// Is a directory
|
/// Is a directory
|
||||||
const IOCTL_DIR: u32 = 16;
|
const IOCTL_DIR: u32 = 16;
|
||||||
|
|
||||||
|
/// x32 compat ioctl on 64bit machine (64bit time_t)
|
||||||
|
const IOCTL_COMPAT_X32: u32 = 32;
|
||||||
|
|
||||||
/// Maximum of in_iovecs + out_iovecs
|
/// Maximum of in_iovecs + out_iovecs
|
||||||
const IOCTL_MAX_IOV: u32 = 256;
|
const IOCTL_MAX_IOV: u32 = 256;
|
||||||
|
|
||||||
@@ -357,6 +418,9 @@ bitflags! {
|
|||||||
/// Is a directory
|
/// Is a directory
|
||||||
const IOCTL_DIR = IOCTL_DIR;
|
const IOCTL_DIR = IOCTL_DIR;
|
||||||
|
|
||||||
|
/// x32 compat ioctl on 64bit machine (64bit time_t)
|
||||||
|
const IOCTL_COMPAT_X32 = IOCTL_COMPAT_X32;
|
||||||
|
|
||||||
/// Maximum of in_iovecs + out_iovecs
|
/// Maximum of in_iovecs + out_iovecs
|
||||||
const IOCTL_MAX_IOV = IOCTL_MAX_IOV;
|
const IOCTL_MAX_IOV = IOCTL_MAX_IOV;
|
||||||
}
|
}
|
||||||
@@ -835,7 +899,9 @@ pub struct InitOut {
|
|||||||
pub congestion_threshold: u16,
|
pub congestion_threshold: u16,
|
||||||
pub max_write: u32,
|
pub max_write: u32,
|
||||||
pub time_gran: u32,
|
pub time_gran: u32,
|
||||||
pub unused: [u32; 9],
|
pub max_pages: u16,
|
||||||
|
pub padding: u16,
|
||||||
|
pub unused: [u32; 8],
|
||||||
}
|
}
|
||||||
unsafe impl ByteValued for InitOut {}
|
unsafe impl ByteValued for InitOut {}
|
||||||
|
|
||||||
@@ -1049,6 +1115,19 @@ pub struct LseekOut {
|
|||||||
}
|
}
|
||||||
unsafe impl ByteValued for LseekOut {}
|
unsafe impl ByteValued for LseekOut {}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
|
pub struct CopyfilerangeIn {
|
||||||
|
pub fh_in: u64,
|
||||||
|
pub off_in: u64,
|
||||||
|
pub nodeid_out: u64,
|
||||||
|
pub fh_out: u64,
|
||||||
|
pub off_out: u64,
|
||||||
|
pub len: u64,
|
||||||
|
pub flags: u64,
|
||||||
|
}
|
||||||
|
unsafe impl ByteValued for CopyfilerangeIn {}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct SetupmappingFlags: u64 {
|
pub struct SetupmappingFlags: u64 {
|
||||||
const WRITE = 0x1;
|
const WRITE = 0x1;
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ pub mod fs_cache_req_handler;
|
|||||||
pub mod fuse;
|
pub mod fuse;
|
||||||
pub mod multikey;
|
pub mod multikey;
|
||||||
pub mod passthrough;
|
pub mod passthrough;
|
||||||
|
pub mod sandbox;
|
||||||
|
pub mod seccomp;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
use std::ffi::FromBytesWithNulError;
|
use std::ffi::FromBytesWithNulError;
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
use super::fs_cache_req_handler::FsCacheReqHandler;
|
||||||
|
use crate::filesystem::{
|
||||||
|
Context, DirEntry, Entry, FileSystem, FsOptions, GetxattrReply, ListxattrReply, OpenOptions,
|
||||||
|
SetattrValid, ZeroCopyReader, ZeroCopyWriter,
|
||||||
|
};
|
||||||
|
use crate::fuse;
|
||||||
|
use crate::multikey::MultikeyBTreeMap;
|
||||||
use std::collections::btree_map;
|
use std::collections::btree_map;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
@@ -13,22 +20,12 @@ use std::str::FromStr;
|
|||||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use libc;
|
|
||||||
use vm_memory::ByteValued;
|
use vm_memory::ByteValued;
|
||||||
|
|
||||||
use super::fs_cache_req_handler::FsCacheReqHandler;
|
|
||||||
use crate::filesystem::{
|
|
||||||
Context, DirEntry, Entry, FileSystem, FsOptions, GetxattrReply, ListxattrReply, OpenOptions,
|
|
||||||
SetattrValid, ZeroCopyReader, ZeroCopyWriter,
|
|
||||||
};
|
|
||||||
use crate::fuse;
|
|
||||||
use crate::multikey::MultikeyBTreeMap;
|
|
||||||
|
|
||||||
const CURRENT_DIR_CSTR: &[u8] = b".\0";
|
const CURRENT_DIR_CSTR: &[u8] = b".\0";
|
||||||
const PARENT_DIR_CSTR: &[u8] = b"..\0";
|
const PARENT_DIR_CSTR: &[u8] = b"..\0";
|
||||||
const EMPTY_CSTR: &[u8] = b"\0";
|
const EMPTY_CSTR: &[u8] = b"\0";
|
||||||
const PROC_CSTR: &[u8] = b"/proc\0";
|
const PROC_CSTR: &[u8] = b"/proc/self/fd\0";
|
||||||
|
|
||||||
type Inode = u64;
|
type Inode = u64;
|
||||||
type Handle = u64;
|
type Handle = u64;
|
||||||
@@ -238,6 +235,13 @@ pub struct Config {
|
|||||||
///
|
///
|
||||||
/// The default value for this options is `false`.
|
/// The default value for this options is `false`.
|
||||||
pub xattr: bool,
|
pub xattr: bool,
|
||||||
|
|
||||||
|
/// Optional file descriptor for /proc/self/fd. Callers can obtain a file descriptor and pass it
|
||||||
|
/// here, so there's no need to open it in PassthroughFs::new(). This is specially useful for
|
||||||
|
/// sandboxing.
|
||||||
|
///
|
||||||
|
/// The default is `None`.
|
||||||
|
pub proc_sfd_rawfd: Option<RawFd>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
@@ -249,6 +253,7 @@ impl Default for Config {
|
|||||||
writeback: false,
|
writeback: false,
|
||||||
root_dir: String::from("/"),
|
root_dir: String::from("/"),
|
||||||
xattr: false,
|
xattr: false,
|
||||||
|
proc_sfd_rawfd: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,11 +276,11 @@ pub struct PassthroughFs {
|
|||||||
handles: RwLock<BTreeMap<Handle, Arc<HandleData>>>,
|
handles: RwLock<BTreeMap<Handle, Arc<HandleData>>>,
|
||||||
next_handle: AtomicU64,
|
next_handle: AtomicU64,
|
||||||
|
|
||||||
// File descriptor pointing to the `/proc` directory. This is used to convert an fd from
|
// File descriptor pointing to the `/proc/self/fd` directory. This is used to convert an fd from
|
||||||
// `inodes` into one that can go into `handles`. This is accomplished by reading the
|
// `inodes` into one that can go into `handles`. This is accomplished by reading the
|
||||||
// `self/fd/{}` symlink. We keep an open fd here in case the file system tree that we are meant
|
// `/proc/self/fd/{}` symlink. We keep an open fd here in case the file system tree that we are
|
||||||
// to be serving doesn't have access to `/proc`.
|
// meant to be serving doesn't have access to `/proc/self/fd`.
|
||||||
proc: File,
|
proc_self_fd: File,
|
||||||
|
|
||||||
// Whether writeback caching is enabled for this directory. This will only be true when
|
// Whether writeback caching is enabled for this directory. This will only be true when
|
||||||
// `cfg.writeback` is true and `init` was called with `FsOptions::WRITEBACK_CACHE`.
|
// `cfg.writeback` is true and `init` was called with `FsOptions::WRITEBACK_CACHE`.
|
||||||
@@ -286,23 +291,29 @@ pub struct PassthroughFs {
|
|||||||
|
|
||||||
impl PassthroughFs {
|
impl PassthroughFs {
|
||||||
pub fn new(cfg: Config) -> io::Result<PassthroughFs> {
|
pub fn new(cfg: Config) -> io::Result<PassthroughFs> {
|
||||||
// Safe because this is a constant value and a valid C string.
|
let fd = if let Some(fd) = cfg.proc_sfd_rawfd {
|
||||||
let proc_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(PROC_CSTR) };
|
fd
|
||||||
|
} else {
|
||||||
|
// Safe because this is a constant value and a valid C string.
|
||||||
|
let proc_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(PROC_CSTR) };
|
||||||
|
|
||||||
// Safe because this doesn't modify any memory and we check the return value.
|
// Safe because this doesn't modify any memory and we check the return value.
|
||||||
let fd = unsafe {
|
let fd = unsafe {
|
||||||
libc::openat(
|
libc::openat(
|
||||||
libc::AT_FDCWD,
|
libc::AT_FDCWD,
|
||||||
proc_cstr.as_ptr(),
|
proc_cstr.as_ptr(),
|
||||||
libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
|
libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
|
||||||
)
|
)
|
||||||
|
};
|
||||||
|
if fd < 0 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
fd
|
||||||
};
|
};
|
||||||
if fd < 0 {
|
|
||||||
return Err(io::Error::last_os_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safe because we just opened this fd.
|
// Safe because we just opened this fd or it was provided by our caller.
|
||||||
let proc = unsafe { File::from_raw_fd(fd) };
|
let proc_self_fd = unsafe { File::from_raw_fd(fd) };
|
||||||
|
|
||||||
Ok(PassthroughFs {
|
Ok(PassthroughFs {
|
||||||
inodes: RwLock::new(MultikeyBTreeMap::new()),
|
inodes: RwLock::new(MultikeyBTreeMap::new()),
|
||||||
@@ -311,7 +322,7 @@ impl PassthroughFs {
|
|||||||
handles: RwLock::new(BTreeMap::new()),
|
handles: RwLock::new(BTreeMap::new()),
|
||||||
next_handle: AtomicU64::new(0),
|
next_handle: AtomicU64::new(0),
|
||||||
|
|
||||||
proc,
|
proc_self_fd,
|
||||||
|
|
||||||
writeback: AtomicBool::new(false),
|
writeback: AtomicBool::new(false),
|
||||||
cfg,
|
cfg,
|
||||||
@@ -319,7 +330,7 @@ impl PassthroughFs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn keep_fds(&self) -> Vec<RawFd> {
|
pub fn keep_fds(&self) -> Vec<RawFd> {
|
||||||
vec![self.proc.as_raw_fd()]
|
vec![self.proc_self_fd.as_raw_fd()]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_inode(&self, inode: Inode, mut flags: i32) -> io::Result<File> {
|
fn open_inode(&self, inode: Inode, mut flags: i32) -> io::Result<File> {
|
||||||
@@ -331,7 +342,7 @@ impl PassthroughFs {
|
|||||||
.map(Arc::clone)
|
.map(Arc::clone)
|
||||||
.ok_or_else(ebadf)?;
|
.ok_or_else(ebadf)?;
|
||||||
|
|
||||||
let pathname = CString::new(format!("self/fd/{}", data.file.as_raw_fd()))
|
let pathname = CString::new(format!("{}", data.file.as_raw_fd()))
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||||
|
|
||||||
// When writeback caching is enabled, the kernel may send read requests even if the
|
// When writeback caching is enabled, the kernel may send read requests even if the
|
||||||
@@ -359,7 +370,7 @@ impl PassthroughFs {
|
|||||||
// to follow the `/proc/self/fd` symlink to get the file.
|
// to follow the `/proc/self/fd` symlink to get the file.
|
||||||
let fd = unsafe {
|
let fd = unsafe {
|
||||||
libc::openat(
|
libc::openat(
|
||||||
self.proc.as_raw_fd(),
|
self.proc_self_fd.as_raw_fd(),
|
||||||
pathname.as_ptr(),
|
pathname.as_ptr(),
|
||||||
(flags | libc::O_CLOEXEC) & (!libc::O_NOFOLLOW),
|
(flags | libc::O_CLOEXEC) & (!libc::O_NOFOLLOW),
|
||||||
)
|
)
|
||||||
@@ -557,7 +568,13 @@ impl PassthroughFs {
|
|||||||
OpenOptions::DIRECT_IO,
|
OpenOptions::DIRECT_IO,
|
||||||
flags & (libc::O_DIRECTORY as u32) == 0,
|
flags & (libc::O_DIRECTORY as u32) == 0,
|
||||||
),
|
),
|
||||||
CachePolicy::Always => opts |= OpenOptions::KEEP_CACHE,
|
CachePolicy::Always => {
|
||||||
|
if flags & (libc::O_DIRECTORY as u32) == 0 {
|
||||||
|
opts |= OpenOptions::KEEP_CACHE;
|
||||||
|
} else {
|
||||||
|
opts |= OpenOptions::CACHE_DIR;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -991,11 +1008,15 @@ impl FileSystem for PassthroughFs {
|
|||||||
offset: u64,
|
offset: u64,
|
||||||
_lock_owner: Option<u64>,
|
_lock_owner: Option<u64>,
|
||||||
_delayed_write: bool,
|
_delayed_write: bool,
|
||||||
|
kill_priv: bool,
|
||||||
_flags: u32,
|
_flags: u32,
|
||||||
) -> io::Result<usize> {
|
) -> io::Result<usize> {
|
||||||
// We need to change credentials during a write so that the kernel will remove setuid or
|
if kill_priv {
|
||||||
// setgid bits from the file if it was written to by someone other than the owner.
|
// We need to change credentials during a write so that the kernel will remove setuid
|
||||||
let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?;
|
// or setgid bits from the file if it was written to by someone other than the owner.
|
||||||
|
let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?;
|
||||||
|
}
|
||||||
|
|
||||||
let data = self
|
let data = self
|
||||||
.handles
|
.handles
|
||||||
.read()
|
.read()
|
||||||
@@ -1055,7 +1076,7 @@ impl FileSystem for PassthroughFs {
|
|||||||
let fd = hd.file.write().unwrap().as_raw_fd();
|
let fd = hd.file.write().unwrap().as_raw_fd();
|
||||||
Data::Handle(hd, fd)
|
Data::Handle(hd, fd)
|
||||||
} else {
|
} else {
|
||||||
let pathname = CString::new(format!("self/fd/{}", inode_data.file.as_raw_fd()))
|
let pathname = CString::new(format!("{}", inode_data.file.as_raw_fd()))
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||||
Data::ProcPath(pathname)
|
Data::ProcPath(pathname)
|
||||||
};
|
};
|
||||||
@@ -1066,7 +1087,7 @@ impl FileSystem for PassthroughFs {
|
|||||||
match data {
|
match data {
|
||||||
Data::Handle(_, fd) => libc::fchmod(fd, attr.st_mode),
|
Data::Handle(_, fd) => libc::fchmod(fd, attr.st_mode),
|
||||||
Data::ProcPath(ref p) => {
|
Data::ProcPath(ref p) => {
|
||||||
libc::fchmodat(self.proc.as_raw_fd(), p.as_ptr(), attr.st_mode, 0)
|
libc::fchmodat(self.proc_self_fd.as_raw_fd(), p.as_ptr(), attr.st_mode, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1152,7 +1173,7 @@ impl FileSystem for PassthroughFs {
|
|||||||
let res = match data {
|
let res = match data {
|
||||||
Data::Handle(_, fd) => unsafe { libc::futimens(fd, tvs.as_ptr()) },
|
Data::Handle(_, fd) => unsafe { libc::futimens(fd, tvs.as_ptr()) },
|
||||||
Data::ProcPath(ref p) => unsafe {
|
Data::ProcPath(ref p) => unsafe {
|
||||||
libc::utimensat(self.proc.as_raw_fd(), p.as_ptr(), tvs.as_ptr(), 0)
|
libc::utimensat(self.proc_self_fd.as_raw_fd(), p.as_ptr(), tvs.as_ptr(), 0)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if res < 0 {
|
if res < 0 {
|
||||||
@@ -1644,4 +1665,60 @@ impl FileSystem for PassthroughFs {
|
|||||||
Ok(res as u64)
|
Ok(res as u64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copyfilerange(
|
||||||
|
&self,
|
||||||
|
_ctx: Context,
|
||||||
|
inode_in: Inode,
|
||||||
|
handle_in: Handle,
|
||||||
|
offset_in: u64,
|
||||||
|
inode_out: Inode,
|
||||||
|
handle_out: Handle,
|
||||||
|
offset_out: u64,
|
||||||
|
len: u64,
|
||||||
|
flags: u64,
|
||||||
|
) -> io::Result<usize> {
|
||||||
|
let data_in = self
|
||||||
|
.handles
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(&handle_in)
|
||||||
|
.filter(|hd| hd.inode == inode_in)
|
||||||
|
.map(Arc::clone)
|
||||||
|
.ok_or_else(ebadf)?;
|
||||||
|
|
||||||
|
// Take just a read lock as we're not going to alter the file descriptor offset.
|
||||||
|
let fd_in = data_in.file.read().unwrap().as_raw_fd();
|
||||||
|
|
||||||
|
let data_out = self
|
||||||
|
.handles
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(&handle_out)
|
||||||
|
.filter(|hd| hd.inode == inode_out)
|
||||||
|
.map(Arc::clone)
|
||||||
|
.ok_or_else(ebadf)?;
|
||||||
|
|
||||||
|
// Take just a read lock as we're not going to alter the file descriptor offset.
|
||||||
|
let fd_out = data_out.file.read().unwrap().as_raw_fd();
|
||||||
|
|
||||||
|
// Safe because this will only modify `offset_in` and `offset_out` and we check
|
||||||
|
// the return value.
|
||||||
|
let res = unsafe {
|
||||||
|
libc::syscall(
|
||||||
|
libc::SYS_copy_file_range,
|
||||||
|
fd_in,
|
||||||
|
&mut (offset_in as i64) as &mut _ as *mut _,
|
||||||
|
fd_out,
|
||||||
|
&mut (offset_out as i64) as &mut _ as *mut _,
|
||||||
|
len,
|
||||||
|
flags,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if res < 0 {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
Ok(res as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
292
vhost_user_fs/src/sandbox.rs
Normal file
292
vhost_user_fs/src/sandbox.rs
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
// Copyright 2020 Red Hat, Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::os::unix::io::RawFd;
|
||||||
|
use std::{fmt, io};
|
||||||
|
|
||||||
|
use tempdir::TempDir;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Failed to bind mount `/proc/self/fd` into a temporary directory.
|
||||||
|
BindMountProcSelfFd(io::Error),
|
||||||
|
/// Failed to bind mount shared directory.
|
||||||
|
BindMountSharedDir(io::Error),
|
||||||
|
/// Failed to change to the old root directory.
|
||||||
|
ChdirOldRoot(io::Error),
|
||||||
|
/// Failed to change to the new root directory.
|
||||||
|
ChdirNewRoot(io::Error),
|
||||||
|
/// Failed to clean the properties of the mount point.
|
||||||
|
CleanMount(io::Error),
|
||||||
|
/// Failed to create a temporary directory.
|
||||||
|
CreateTempDir(io::Error),
|
||||||
|
/// Call to libc::fork returned an error.
|
||||||
|
Fork(io::Error),
|
||||||
|
/// Error bind-mounting a directory.
|
||||||
|
MountBind(io::Error),
|
||||||
|
/// Failed to mount old root.
|
||||||
|
MountOldRoot(io::Error),
|
||||||
|
/// Error mounting proc.
|
||||||
|
MountProc(io::Error),
|
||||||
|
/// Failed to mount new root.
|
||||||
|
MountNewRoot(io::Error),
|
||||||
|
/// Error mounting target directory.
|
||||||
|
MountTarget(io::Error),
|
||||||
|
/// Failed to open new root.
|
||||||
|
OpenNewRoot(io::Error),
|
||||||
|
/// Failed to open old root.
|
||||||
|
OpenOldRoot(io::Error),
|
||||||
|
/// Failed to open `/proc/self/fd`.
|
||||||
|
OpenProcSelfFd(io::Error),
|
||||||
|
/// Error switching root directory.
|
||||||
|
PivotRoot(io::Error),
|
||||||
|
/// Failed to remove temporary directory.
|
||||||
|
RmdirTempDir(io::Error),
|
||||||
|
/// Failed to lazily unmount old root.
|
||||||
|
UmountOldRoot(io::Error),
|
||||||
|
/// Failed to lazily unmount temporary directory.
|
||||||
|
UmountTempDir(io::Error),
|
||||||
|
/// Call to libc::unshare returned an error.
|
||||||
|
Unshare(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "vhost_user_fs_sandbox_error: {:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper for creating a sandbox for isolating the service.
|
||||||
|
pub struct Sandbox {
|
||||||
|
/// The directory that is going to be shared with the VM. The sandbox will be constructed on top
|
||||||
|
/// of this directory.
|
||||||
|
shared_dir: String,
|
||||||
|
/// A file descriptor for `/proc/self/fd` obtained from the sandboxed context.
|
||||||
|
proc_self_fd: Option<RawFd>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sandbox {
|
||||||
|
pub fn new(shared_dir: String) -> Self {
|
||||||
|
Sandbox {
|
||||||
|
shared_dir,
|
||||||
|
proc_self_fd: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make `self.shared_dir` our root directory, and get an isolated file descriptor for
|
||||||
|
// `/proc/self/fd`.
|
||||||
|
//
|
||||||
|
// This is based on virtiofsd's setup_namespaces() and setup_mounts(), and it's very similar to
|
||||||
|
// the strategy used in containers. Consists on a careful sequence of mounts and bind-mounts to
|
||||||
|
// ensure it's not possible to escape the sandbox through `self.shared_dir` nor the file
|
||||||
|
// descriptor obtained for `/proc/self/fd`.
|
||||||
|
//
|
||||||
|
// It's ugly, but it's the only way until Linux implements a proper containerization API.
|
||||||
|
fn setup_mounts(&mut self) -> Result<(), Error> {
|
||||||
|
// Ensure our mount changes don't affect the parent mount namespace.
|
||||||
|
let c_root_dir = CString::new("/").unwrap();
|
||||||
|
let ret = unsafe {
|
||||||
|
libc::mount(
|
||||||
|
std::ptr::null(),
|
||||||
|
c_root_dir.as_ptr(),
|
||||||
|
std::ptr::null(),
|
||||||
|
libc::MS_SLAVE | libc::MS_REC,
|
||||||
|
std::ptr::null(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(Error::CleanMount(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount `/proc` in this context.
|
||||||
|
let c_proc_dir = CString::new("/proc").unwrap();
|
||||||
|
let c_proc_fs = CString::new("proc").unwrap();
|
||||||
|
let ret = unsafe {
|
||||||
|
libc::mount(
|
||||||
|
c_proc_fs.as_ptr(),
|
||||||
|
c_proc_dir.as_ptr(),
|
||||||
|
c_proc_fs.as_ptr(),
|
||||||
|
libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_NOSUID | libc::MS_RELATIME,
|
||||||
|
std::ptr::null(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(Error::MountProc(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind-mount `/proc/self/fd` info a temporary directory, preventing access to ancestor
|
||||||
|
// directories.
|
||||||
|
let c_proc_self_fd = CString::new("/proc/self/fd").unwrap();
|
||||||
|
let tmp_dir = TempDir::new("vhostuserfs-")
|
||||||
|
.map_err(|_| Error::CreateTempDir(std::io::Error::last_os_error()))?;
|
||||||
|
let c_tmp_dir = CString::new(tmp_dir.into_path().to_str().unwrap()).unwrap();
|
||||||
|
let ret = unsafe {
|
||||||
|
libc::mount(
|
||||||
|
c_proc_self_fd.as_ptr(),
|
||||||
|
c_tmp_dir.as_ptr(),
|
||||||
|
std::ptr::null(),
|
||||||
|
libc::MS_BIND,
|
||||||
|
std::ptr::null(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret < 0 {
|
||||||
|
return Err(Error::BindMountProcSelfFd(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain a file descriptor for `/proc/self/fd` through the bind-mounted temporary directory.
|
||||||
|
let proc_self_fd = unsafe { libc::open(c_tmp_dir.as_ptr(), libc::O_PATH) };
|
||||||
|
if proc_self_fd < 0 {
|
||||||
|
return Err(Error::OpenProcSelfFd(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
self.proc_self_fd = Some(proc_self_fd);
|
||||||
|
|
||||||
|
// Now that we have a file descriptor for `/proc/self/fd`, we no longer need the bind-mount.
|
||||||
|
// Unmount it and remove the temporary directory.
|
||||||
|
let ret = unsafe { libc::umount2(c_tmp_dir.as_ptr(), libc::MNT_DETACH) };
|
||||||
|
if ret < 0 {
|
||||||
|
return Err(Error::UmountTempDir(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
let ret = unsafe { libc::rmdir(c_tmp_dir.as_ptr()) };
|
||||||
|
if ret < 0 {
|
||||||
|
return Err(Error::RmdirTempDir(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind-mount `self.shared_dir` on itself so we can use as new root on `pivot_root` syscall.
|
||||||
|
let c_shared_dir = CString::new(self.shared_dir.clone()).unwrap();
|
||||||
|
let ret = unsafe {
|
||||||
|
libc::mount(
|
||||||
|
c_shared_dir.as_ptr(),
|
||||||
|
c_shared_dir.as_ptr(),
|
||||||
|
std::ptr::null(),
|
||||||
|
libc::MS_BIND | libc::MS_REC,
|
||||||
|
std::ptr::null(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret < 0 {
|
||||||
|
return Err(Error::BindMountSharedDir(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a file descriptor to our old root so we can reference it after switching root.
|
||||||
|
let oldroot_fd = unsafe {
|
||||||
|
libc::open(
|
||||||
|
c_root_dir.as_ptr(),
|
||||||
|
libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if oldroot_fd < 0 {
|
||||||
|
return Err(Error::OpenOldRoot(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a file descriptor to the new root so we can reference it after switching root.
|
||||||
|
let newroot_fd = unsafe {
|
||||||
|
libc::open(
|
||||||
|
c_shared_dir.as_ptr(),
|
||||||
|
libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if newroot_fd < 0 {
|
||||||
|
return Err(Error::OpenNewRoot(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change to new root directory to prepare for `pivot_root` syscall.
|
||||||
|
let ret = unsafe { libc::fchdir(newroot_fd) };
|
||||||
|
if ret < 0 {
|
||||||
|
return Err(Error::ChdirNewRoot(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call to `pivot_root` using `.` as both new and old root.
|
||||||
|
let c_current_dir = CString::new(".").unwrap();
|
||||||
|
let ret = unsafe {
|
||||||
|
libc::syscall(
|
||||||
|
libc::SYS_pivot_root,
|
||||||
|
c_current_dir.as_ptr(),
|
||||||
|
c_current_dir.as_ptr(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret < 0 {
|
||||||
|
return Err(Error::PivotRoot(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change to old root directory to prepare for cleaning up and unmounting it.
|
||||||
|
let ret = unsafe { libc::fchdir(oldroot_fd) };
|
||||||
|
if ret < 0 {
|
||||||
|
return Err(Error::ChdirOldRoot(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up old root to avoid mount namespace propagation.
|
||||||
|
let c_empty = CString::new("").unwrap();
|
||||||
|
let ret = unsafe {
|
||||||
|
libc::mount(
|
||||||
|
c_empty.as_ptr(),
|
||||||
|
c_current_dir.as_ptr(),
|
||||||
|
c_empty.as_ptr(),
|
||||||
|
libc::MS_SLAVE | libc::MS_REC,
|
||||||
|
std::ptr::null(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(Error::CleanMount(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lazily unmount old root.
|
||||||
|
let ret = unsafe { libc::umount2(c_current_dir.as_ptr(), libc::MNT_DETACH) };
|
||||||
|
if ret < 0 {
|
||||||
|
return Err(Error::UmountOldRoot(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change to new root.
|
||||||
|
let ret = unsafe { libc::fchdir(newroot_fd) };
|
||||||
|
if ret < 0 {
|
||||||
|
return Err(Error::ChdirNewRoot(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We no longer need these file descriptors, so close them.
|
||||||
|
unsafe { libc::close(newroot_fd) };
|
||||||
|
unsafe { libc::close(oldroot_fd) };
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up sandbox, fork and jump into it.
|
||||||
|
///
|
||||||
|
/// On success, the returned value will be the PID of the child for the parent and `None` for
|
||||||
|
/// the child itself, with the latter running isolated in `self.shared_dir`.
|
||||||
|
pub fn enter(&mut self) -> Result<Option<i32>, Error> {
|
||||||
|
let uid = unsafe { libc::geteuid() };
|
||||||
|
|
||||||
|
let flags = if uid == 0 {
|
||||||
|
libc::CLONE_NEWPID | libc::CLONE_NEWNS | libc::CLONE_NEWNET
|
||||||
|
} else {
|
||||||
|
// If running as an unprivileged user, rely on user_namespaces(7) for isolation. The
|
||||||
|
// main limitation of this strategy is that only the current uid/gid are mapped into
|
||||||
|
// the new namespace, so most operations on permissions will fail.
|
||||||
|
libc::CLONE_NEWPID | libc::CLONE_NEWNS | libc::CLONE_NEWNET | libc::CLONE_NEWUSER
|
||||||
|
};
|
||||||
|
|
||||||
|
let ret = unsafe { libc::unshare(flags) };
|
||||||
|
if ret != 0 {
|
||||||
|
return Err(Error::Unshare(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let child = unsafe { libc::fork() };
|
||||||
|
match child {
|
||||||
|
0 => {
|
||||||
|
// This is the child. Request to receive SIGTERM on parent's death.
|
||||||
|
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) };
|
||||||
|
self.setup_mounts()?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
x if x > 0 => {
|
||||||
|
// This is the parent.
|
||||||
|
Ok(Some(child))
|
||||||
|
}
|
||||||
|
_ => Err(Error::Fork(std::io::Error::last_os_error())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_proc_self_fd(&self) -> Option<RawFd> {
|
||||||
|
self.proc_self_fd
|
||||||
|
}
|
||||||
|
}
|
||||||
141
vhost_user_fs/src/seccomp.rs
Normal file
141
vhost_user_fs/src/seccomp.rs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
// Copyright 2020 Red Hat, Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
use seccomp::{allow_syscall, BpfProgram, SeccompAction, SeccompFilter};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::{convert, fmt};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Cannot create seccomp filter
|
||||||
|
CreateSeccompFilter(seccomp::SeccompError),
|
||||||
|
|
||||||
|
/// Cannot apply seccomp filter
|
||||||
|
ApplySeccompFilter(seccomp::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl convert::From<seccomp::Error> for Error {
|
||||||
|
fn from(e: seccomp::Error) -> Self {
|
||||||
|
Error::ApplySeccompFilter(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "vhost_user_fs_seccomp_error: {:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vuf_filter(action: SeccompAction) -> Result<SeccompFilter, Error> {
|
||||||
|
Ok(SeccompFilter::new(
|
||||||
|
vec![
|
||||||
|
allow_syscall(libc::SYS_accept4),
|
||||||
|
allow_syscall(libc::SYS_brk),
|
||||||
|
allow_syscall(libc::SYS_capget), // For CAP_FSETID
|
||||||
|
allow_syscall(libc::SYS_capset),
|
||||||
|
allow_syscall(libc::SYS_clock_gettime),
|
||||||
|
allow_syscall(libc::SYS_clone),
|
||||||
|
allow_syscall(libc::SYS_close),
|
||||||
|
allow_syscall(libc::SYS_copy_file_range),
|
||||||
|
allow_syscall(libc::SYS_dup),
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
allow_syscall(libc::SYS_epoll_create),
|
||||||
|
allow_syscall(libc::SYS_epoll_create1),
|
||||||
|
allow_syscall(libc::SYS_epoll_ctl),
|
||||||
|
allow_syscall(libc::SYS_epoll_pwait),
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
allow_syscall(libc::SYS_epoll_wait),
|
||||||
|
allow_syscall(libc::SYS_eventfd2),
|
||||||
|
allow_syscall(libc::SYS_exit),
|
||||||
|
allow_syscall(libc::SYS_exit_group),
|
||||||
|
allow_syscall(libc::SYS_fallocate),
|
||||||
|
allow_syscall(libc::SYS_fchdir),
|
||||||
|
allow_syscall(libc::SYS_fchmodat),
|
||||||
|
allow_syscall(libc::SYS_fchownat),
|
||||||
|
allow_syscall(libc::SYS_fcntl),
|
||||||
|
allow_syscall(libc::SYS_fdatasync),
|
||||||
|
allow_syscall(libc::SYS_fgetxattr),
|
||||||
|
allow_syscall(libc::SYS_flistxattr),
|
||||||
|
allow_syscall(libc::SYS_flock),
|
||||||
|
allow_syscall(libc::SYS_fremovexattr),
|
||||||
|
allow_syscall(libc::SYS_fsetxattr),
|
||||||
|
allow_syscall(libc::SYS_fstat),
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
allow_syscall(libc::SYS_fstatfs),
|
||||||
|
allow_syscall(libc::SYS_fsync),
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
allow_syscall(libc::SYS_ftruncate),
|
||||||
|
allow_syscall(libc::SYS_futex),
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
allow_syscall(libc::SYS_getdents),
|
||||||
|
allow_syscall(libc::SYS_getdents64),
|
||||||
|
allow_syscall(libc::SYS_getegid),
|
||||||
|
allow_syscall(libc::SYS_geteuid),
|
||||||
|
allow_syscall(libc::SYS_getpid),
|
||||||
|
allow_syscall(libc::SYS_gettid),
|
||||||
|
allow_syscall(libc::SYS_gettimeofday),
|
||||||
|
allow_syscall(libc::SYS_getxattr),
|
||||||
|
allow_syscall(libc::SYS_linkat),
|
||||||
|
allow_syscall(libc::SYS_listxattr),
|
||||||
|
allow_syscall(libc::SYS_lseek),
|
||||||
|
allow_syscall(libc::SYS_madvise),
|
||||||
|
allow_syscall(libc::SYS_mkdirat),
|
||||||
|
allow_syscall(libc::SYS_mknodat),
|
||||||
|
allow_syscall(libc::SYS_mmap),
|
||||||
|
allow_syscall(libc::SYS_mprotect),
|
||||||
|
allow_syscall(libc::SYS_mremap),
|
||||||
|
allow_syscall(libc::SYS_munmap),
|
||||||
|
allow_syscall(libc::SYS_newfstatat),
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
allow_syscall(libc::SYS_open),
|
||||||
|
allow_syscall(libc::SYS_openat),
|
||||||
|
allow_syscall(libc::SYS_prctl), // TODO restrict to just PR_SET_NAME?
|
||||||
|
allow_syscall(libc::SYS_preadv),
|
||||||
|
allow_syscall(libc::SYS_pread64),
|
||||||
|
allow_syscall(libc::SYS_pwritev),
|
||||||
|
allow_syscall(libc::SYS_pwrite64),
|
||||||
|
allow_syscall(libc::SYS_read),
|
||||||
|
allow_syscall(libc::SYS_readlinkat),
|
||||||
|
allow_syscall(libc::SYS_recvmsg),
|
||||||
|
allow_syscall(libc::SYS_renameat),
|
||||||
|
allow_syscall(libc::SYS_renameat2),
|
||||||
|
allow_syscall(libc::SYS_removexattr),
|
||||||
|
allow_syscall(libc::SYS_rt_sigaction),
|
||||||
|
allow_syscall(libc::SYS_rt_sigprocmask),
|
||||||
|
allow_syscall(libc::SYS_rt_sigreturn),
|
||||||
|
allow_syscall(libc::SYS_sched_getaffinity), // used by thread_pool
|
||||||
|
allow_syscall(libc::SYS_sendmsg),
|
||||||
|
allow_syscall(libc::SYS_setresgid),
|
||||||
|
allow_syscall(libc::SYS_setresuid),
|
||||||
|
//allow_syscall(libc::SYS_setresgid32), Needed on some platforms,
|
||||||
|
//allow_syscall(libc::SYS_setresuid32), Needed on some platforms
|
||||||
|
allow_syscall(libc::SYS_set_robust_list),
|
||||||
|
allow_syscall(libc::SYS_setxattr),
|
||||||
|
allow_syscall(libc::SYS_sigaltstack),
|
||||||
|
allow_syscall(libc::SYS_statx),
|
||||||
|
allow_syscall(libc::SYS_symlinkat),
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
allow_syscall(libc::SYS_time), // Rarely needed, except on static builds
|
||||||
|
allow_syscall(libc::SYS_tgkill),
|
||||||
|
allow_syscall(libc::SYS_umask),
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
allow_syscall(libc::SYS_unlink),
|
||||||
|
allow_syscall(libc::SYS_unlinkat),
|
||||||
|
allow_syscall(libc::SYS_unshare),
|
||||||
|
allow_syscall(libc::SYS_utimensat),
|
||||||
|
allow_syscall(libc::SYS_write),
|
||||||
|
allow_syscall(libc::SYS_writev),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
action,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_seccomp(action: SeccompAction) -> Result<(), Error> {
|
||||||
|
let scfilter = vuf_filter(action)?;
|
||||||
|
let bpfprog: BpfProgram = scfilter.try_into()?;
|
||||||
|
SeccompFilter::apply(bpfprog.try_into().unwrap()).unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -2,14 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
use std::mem::size_of;
|
|
||||||
|
|
||||||
use libc;
|
|
||||||
use vm_memory::ByteValued;
|
|
||||||
|
|
||||||
use super::fs_cache_req_handler::FsCacheReqHandler;
|
use super::fs_cache_req_handler::FsCacheReqHandler;
|
||||||
use crate::descriptor_utils::{Reader, Writer};
|
use crate::descriptor_utils::{Reader, Writer};
|
||||||
use crate::filesystem::{
|
use crate::filesystem::{
|
||||||
@@ -18,8 +10,14 @@ use crate::filesystem::{
|
|||||||
};
|
};
|
||||||
use crate::fuse::*;
|
use crate::fuse::*;
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::mem::size_of;
|
||||||
|
use vm_memory::ByteValued;
|
||||||
|
|
||||||
const MAX_BUFFER_SIZE: u32 = (1 << 20);
|
const MAX_BUFFER_SIZE: u32 = 1 << 20;
|
||||||
const DIRENT_PADDING: [u8; 8] = [0; 8];
|
const DIRENT_PADDING: [u8; 8] = [0; 8];
|
||||||
|
|
||||||
struct ZCReader<'a>(Reader<'a>);
|
struct ZCReader<'a>(Reader<'a>);
|
||||||
@@ -124,6 +122,7 @@ impl<F: FileSystem + Sync> Server<F> {
|
|||||||
x if x == Opcode::Readdirplus as u32 => self.readdirplus(in_header, r, w),
|
x if x == Opcode::Readdirplus as u32 => self.readdirplus(in_header, r, w),
|
||||||
x if x == Opcode::Rename2 as u32 => self.rename2(in_header, r, w),
|
x if x == Opcode::Rename2 as u32 => self.rename2(in_header, r, w),
|
||||||
x if x == Opcode::Lseek as u32 => self.lseek(in_header, r, w),
|
x if x == Opcode::Lseek as u32 => self.lseek(in_header, r, w),
|
||||||
|
x if x == Opcode::CopyFileRange as u32 => self.copyfilerange(in_header, r, w),
|
||||||
x if x == Opcode::SetupMapping as u32 => self.setupmapping(in_header, r, w, vu_req),
|
x if x == Opcode::SetupMapping as u32 => self.setupmapping(in_header, r, w, vu_req),
|
||||||
x if x == Opcode::RemoveMapping as u32 => self.removemapping(in_header, r, w, vu_req),
|
x if x == Opcode::RemoveMapping as u32 => self.removemapping(in_header, r, w, vu_req),
|
||||||
_ => reply_error(
|
_ => reply_error(
|
||||||
@@ -634,6 +633,7 @@ impl<F: FileSystem + Sync> Server<F> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let delayed_write = write_flags & WRITE_CACHE != 0;
|
let delayed_write = write_flags & WRITE_CACHE != 0;
|
||||||
|
let kill_priv = write_flags & WRITE_KILL_PRIV != 0;
|
||||||
|
|
||||||
let data_reader = ZCReader(r);
|
let data_reader = ZCReader(r);
|
||||||
|
|
||||||
@@ -646,6 +646,7 @@ impl<F: FileSystem + Sync> Server<F> {
|
|||||||
offset,
|
offset,
|
||||||
owner,
|
owner,
|
||||||
delayed_write,
|
delayed_write,
|
||||||
|
kill_priv,
|
||||||
flags,
|
flags,
|
||||||
) {
|
) {
|
||||||
Ok(count) => {
|
Ok(count) => {
|
||||||
@@ -902,10 +903,14 @@ impl<F: FileSystem + Sync> Server<F> {
|
|||||||
| FsOptions::HANDLE_KILLPRIV
|
| FsOptions::HANDLE_KILLPRIV
|
||||||
| FsOptions::ASYNC_DIO
|
| FsOptions::ASYNC_DIO
|
||||||
| FsOptions::HAS_IOCTL_DIR
|
| FsOptions::HAS_IOCTL_DIR
|
||||||
| FsOptions::ATOMIC_O_TRUNC;
|
| FsOptions::ATOMIC_O_TRUNC
|
||||||
|
| FsOptions::MAX_PAGES;
|
||||||
|
|
||||||
let capable = FsOptions::from_bits_truncate(flags);
|
let capable = FsOptions::from_bits_truncate(flags);
|
||||||
|
|
||||||
|
let page_size: u32 = unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() };
|
||||||
|
let max_pages = ((MAX_BUFFER_SIZE - 1) / page_size) + 1;
|
||||||
|
|
||||||
match self.fs.init(capable) {
|
match self.fs.init(capable) {
|
||||||
Ok(want) => {
|
Ok(want) => {
|
||||||
let enabled = capable & (want | supported);
|
let enabled = capable & (want | supported);
|
||||||
@@ -919,6 +924,7 @@ impl<F: FileSystem + Sync> Server<F> {
|
|||||||
congestion_threshold: (::std::u16::MAX / 4) * 3,
|
congestion_threshold: (::std::u16::MAX / 4) * 3,
|
||||||
max_write: MAX_BUFFER_SIZE,
|
max_write: MAX_BUFFER_SIZE,
|
||||||
time_gran: 1, // nanoseconds
|
time_gran: 1, // nanoseconds
|
||||||
|
max_pages: max_pages.try_into().unwrap(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1261,6 +1267,41 @@ impl<F: FileSystem + Sync> Server<F> {
|
|||||||
Err(e) => reply_error(e, in_header.unique, w),
|
Err(e) => reply_error(e, in_header.unique, w),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copyfilerange(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
|
||||||
|
let CopyfilerangeIn {
|
||||||
|
fh_in,
|
||||||
|
off_in,
|
||||||
|
nodeid_out,
|
||||||
|
fh_out,
|
||||||
|
off_out,
|
||||||
|
len,
|
||||||
|
flags,
|
||||||
|
..
|
||||||
|
} = r.read_obj().map_err(Error::DecodeMessage)?;
|
||||||
|
|
||||||
|
match self.fs.copyfilerange(
|
||||||
|
Context::from(in_header),
|
||||||
|
in_header.nodeid.into(),
|
||||||
|
fh_in.into(),
|
||||||
|
off_in,
|
||||||
|
nodeid_out.into(),
|
||||||
|
fh_out.into(),
|
||||||
|
off_out,
|
||||||
|
len,
|
||||||
|
flags,
|
||||||
|
) {
|
||||||
|
Ok(count) => {
|
||||||
|
let out = WriteOut {
|
||||||
|
size: count as u32,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
reply_ok(Some(out), None, in_header.unique, w)
|
||||||
|
}
|
||||||
|
Err(e) => reply_error(e, in_header.unique, w),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reply_ok<T: ByteValued>(
|
fn reply_ok<T: ByteValued>(
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.1.0"
|
bitflags = "1.1.0"
|
||||||
epoll = ">=4.0.1"
|
epoll = ">=4.0.1"
|
||||||
libc = "0.2.68"
|
libc = "0.2.71"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
net_util = { path = "../net_util" }
|
net_util = { path = "../net_util" }
|
||||||
vhost_user_backend = { path = "../vhost_user_backend" }
|
vhost_user_backend = { path = "../vhost_user_backend" }
|
||||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-slave"] }
|
||||||
virtio-bindings = "0.1.0"
|
virtio-bindings = "0.1.0"
|
||||||
vm-memory = "0.1.0"
|
vm-memory = "0.2.1"
|
||||||
vm-virtio = { path = "../vm-virtio" }
|
vm-virtio = { path = "../vm-virtio" }
|
||||||
|
vmm = { path = "../vmm" }
|
||||||
vmm-sys-util = ">=0.3.1"
|
vmm-sys-util = ">=0.3.1"
|
||||||
|
|||||||
@@ -11,27 +11,27 @@ extern crate net_util;
|
|||||||
extern crate vhost_rs;
|
extern crate vhost_rs;
|
||||||
extern crate vhost_user_backend;
|
extern crate vhost_user_backend;
|
||||||
extern crate vm_virtio;
|
extern crate vm_virtio;
|
||||||
|
extern crate vmm;
|
||||||
|
|
||||||
use epoll;
|
use libc::{self, EFD_NONBLOCK};
|
||||||
use libc::{self, EAGAIN, EFD_NONBLOCK};
|
|
||||||
use log::*;
|
use log::*;
|
||||||
use net_util::Tap;
|
use net_util::{MacAddr, Tap};
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::Read;
|
|
||||||
use std::io::{self};
|
use std::io::{self};
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use vhost_rs::vhost_user::message::*;
|
use vhost_rs::vhost_user::message::*;
|
||||||
use vhost_rs::vhost_user::Error as VhostUserError;
|
use vhost_rs::vhost_user::{Error as VhostUserError, Listener};
|
||||||
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring, VringWorker};
|
||||||
use virtio_bindings::bindings::virtio_net::*;
|
use virtio_bindings::bindings::virtio_net::*;
|
||||||
use vm_memory::GuestMemoryMmap;
|
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||||
|
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
|
||||||
use vm_virtio::net_util::{open_tap, RxVirtio, TxVirtio};
|
use vm_virtio::net_util::{open_tap, RxVirtio, TxVirtio};
|
||||||
use vm_virtio::Queue;
|
use vm_virtio::NetQueuePair;
|
||||||
|
use vmm::config::{OptionParser, OptionParserError};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
pub type VhostUserResult<T> = std::result::Result<T, VhostUserError>;
|
pub type VhostUserResult<T> = std::result::Result<T, VhostUserError>;
|
||||||
@@ -52,8 +52,10 @@ pub enum Error {
|
|||||||
EpollCreateFd,
|
EpollCreateFd,
|
||||||
/// Failed to read Tap.
|
/// Failed to read Tap.
|
||||||
FailedReadTap,
|
FailedReadTap,
|
||||||
|
/// Failed to parse configuration string
|
||||||
|
FailedConfigParse(OptionParserError),
|
||||||
/// Failed to signal used queue.
|
/// Failed to signal used queue.
|
||||||
FailedSignalingUsedQueue,
|
FailedSignalingUsedQueue(io::Error),
|
||||||
/// Failed to handle event other than input event.
|
/// Failed to handle event other than input event.
|
||||||
HandleEventNotEpollIn,
|
HandleEventNotEpollIn,
|
||||||
/// Failed to handle unknown event.
|
/// Failed to handle unknown event.
|
||||||
@@ -64,20 +66,18 @@ pub enum Error {
|
|||||||
NoVringCallFdNotify,
|
NoVringCallFdNotify,
|
||||||
/// No memory configured.
|
/// No memory configured.
|
||||||
NoMemoryConfigured,
|
NoMemoryConfigured,
|
||||||
/// Failed to parse sock parameter.
|
|
||||||
ParseSockParam,
|
|
||||||
/// Failed to parse ip parameter.
|
|
||||||
ParseIpParam(std::net::AddrParseError),
|
|
||||||
/// Failed to parse mask parameter.
|
|
||||||
ParseMaskParam(std::net::AddrParseError),
|
|
||||||
/// Failed to parse queue number.
|
|
||||||
ParseQueueNumParam(std::num::ParseIntError),
|
|
||||||
/// Failed to parse queue size.
|
|
||||||
ParseQueueSizeParam(std::num::ParseIntError),
|
|
||||||
/// Open tap device failed.
|
/// Open tap device failed.
|
||||||
OpenTap(vm_virtio::net_util::Error),
|
OpenTap(vm_virtio::net_util::Error),
|
||||||
|
/// No socket provided
|
||||||
|
SocketParameterMissing,
|
||||||
|
/// Underlying QueuePair error
|
||||||
|
NetQueuePair(vm_virtio::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const SYNTAX: &str = "vhost-user-net backend parameters \
|
||||||
|
\"ip=<ip_addr>,mask=<net_mask>,socket=<socket_path>,\
|
||||||
|
num_queues=<number_of_queues>,queue_size=<size_of_each_queue>,tap=<if_name>\"";
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "vhost_user_net_error: {:?}", self)
|
write!(f, "vhost_user_net_error: {:?}", self)
|
||||||
@@ -92,163 +92,74 @@ impl std::convert::From<Error> for std::io::Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VhostUserNetBackend {
|
struct VhostUserNetThread {
|
||||||
mem: Option<GuestMemoryMmap>,
|
net: NetQueuePair,
|
||||||
vring_worker: Option<Arc<VringWorker>>,
|
vring_worker: Option<Arc<VringWorker>>,
|
||||||
kill_evt: EventFd,
|
kill_evt: EventFd,
|
||||||
taps: Vec<(Tap, usize)>,
|
|
||||||
rxs: Vec<RxVirtio>,
|
|
||||||
txs: Vec<TxVirtio>,
|
|
||||||
rx_tap_listenings: Vec<bool>,
|
|
||||||
num_queues: usize,
|
|
||||||
queue_size: u16,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VhostUserNetBackend {
|
impl VhostUserNetThread {
|
||||||
/// Create a new virtio network device with the given TAP interface.
|
/// Create a new virtio network device with the given TAP interface.
|
||||||
pub fn new_with_tap(taps: Vec<Tap>, num_queues: usize, queue_size: u16) -> Result<Self> {
|
fn new(tap: Tap) -> Result<Self> {
|
||||||
let mut taps_v: Vec<(Tap, usize)> = Vec::new();
|
Ok(VhostUserNetThread {
|
||||||
for (i, tap) in taps.iter().enumerate() {
|
|
||||||
taps_v.push((tap.clone(), num_queues + i));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut rxs: Vec<RxVirtio> = Vec::new();
|
|
||||||
let mut txs: Vec<TxVirtio> = Vec::new();
|
|
||||||
let mut rx_tap_listenings: Vec<bool> = Vec::new();
|
|
||||||
|
|
||||||
for _ in 0..taps.len() {
|
|
||||||
let rx = RxVirtio::new();
|
|
||||||
rxs.push(rx);
|
|
||||||
let tx = TxVirtio::new();
|
|
||||||
txs.push(tx);
|
|
||||||
rx_tap_listenings.push(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(VhostUserNetBackend {
|
|
||||||
mem: None,
|
|
||||||
vring_worker: None,
|
vring_worker: None,
|
||||||
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
|
||||||
taps: taps_v,
|
net: NetQueuePair {
|
||||||
rxs,
|
mem: None,
|
||||||
txs,
|
tap,
|
||||||
rx_tap_listenings,
|
rx: RxVirtio::new(),
|
||||||
num_queues,
|
tx: TxVirtio::new(),
|
||||||
queue_size,
|
rx_tap_listening: false,
|
||||||
|
epoll_fd: None,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new virtio network device with the given IP address and
|
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
|
||||||
/// netmask.
|
self.net.epoll_fd = Some(vring_worker.as_ref().unwrap().as_raw_fd());
|
||||||
pub fn new(
|
self.vring_worker = vring_worker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VhostUserNetBackend {
|
||||||
|
threads: Vec<Mutex<VhostUserNetThread>>,
|
||||||
|
num_queues: usize,
|
||||||
|
queue_size: u16,
|
||||||
|
queues_per_thread: Vec<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VhostUserNetBackend {
|
||||||
|
fn new(
|
||||||
ip_addr: Ipv4Addr,
|
ip_addr: Ipv4Addr,
|
||||||
|
host_mac: MacAddr,
|
||||||
netmask: Ipv4Addr,
|
netmask: Ipv4Addr,
|
||||||
num_queues: usize,
|
num_queues: usize,
|
||||||
queue_size: u16,
|
queue_size: u16,
|
||||||
ifname: Option<&str>,
|
ifname: Option<&str>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let taps = open_tap(ifname, Some(ip_addr), Some(netmask), num_queues / 2)
|
let mut taps = open_tap(
|
||||||
.map_err(Error::OpenTap)?;
|
ifname,
|
||||||
|
Some(ip_addr),
|
||||||
|
Some(netmask),
|
||||||
|
&mut Some(host_mac),
|
||||||
|
num_queues / 2,
|
||||||
|
)
|
||||||
|
.map_err(Error::OpenTap)?;
|
||||||
|
|
||||||
Self::new_with_tap(taps, num_queues, queue_size)
|
let mut queues_per_thread = Vec::new();
|
||||||
}
|
let mut threads = Vec::new();
|
||||||
|
for (i, tap) in taps.drain(..).enumerate() {
|
||||||
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
|
let thread = Mutex::new(VhostUserNetThread::new(tap)?);
|
||||||
// if a buffer was used, and false if the frame must be deferred until a buffer
|
threads.push(thread);
|
||||||
// is made available by the driver.
|
queues_per_thread.push(0b11 << (i * 2));
|
||||||
fn rx_single_frame(&mut self, mut queue: &mut Queue, index: usize) -> Result<bool> {
|
|
||||||
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
|
|
||||||
|
|
||||||
let next_desc = queue.iter(&mem).next();
|
|
||||||
|
|
||||||
if next_desc.is_none() {
|
|
||||||
// Queue has no available descriptors
|
|
||||||
if self.rx_tap_listenings[index] {
|
|
||||||
self.vring_worker
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.unregister_listener(
|
|
||||||
self.taps[index].0.as_raw_fd(),
|
|
||||||
epoll::Events::EPOLLIN,
|
|
||||||
u64::try_from(self.taps[index].1).unwrap(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
self.rx_tap_listenings[index] = false;
|
|
||||||
}
|
|
||||||
return Ok(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let write_complete = self.rxs[index].process_desc_chain(&mem, next_desc, &mut queue);
|
Ok(VhostUserNetBackend {
|
||||||
|
threads,
|
||||||
Ok(write_complete)
|
num_queues,
|
||||||
}
|
queue_size,
|
||||||
|
queues_per_thread,
|
||||||
fn process_rx(&mut self, vring: &mut Vring, index: usize) -> Result<()> {
|
})
|
||||||
// Read as many frames as possible.
|
|
||||||
loop {
|
|
||||||
match self.read_tap(index) {
|
|
||||||
Ok(count) => {
|
|
||||||
self.rxs[index].bytes_read = count;
|
|
||||||
if !self.rx_single_frame(&mut vring.mut_queue(), index)? {
|
|
||||||
self.rxs[index].deferred_frame = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
// The tap device is non-blocking, so any error aside from EAGAIN is
|
|
||||||
// unexpected.
|
|
||||||
match e.raw_os_error() {
|
|
||||||
Some(err) if err == EAGAIN => (),
|
|
||||||
_ => {
|
|
||||||
error!("Failed to read tap: {:?}", e);
|
|
||||||
return Err(Error::FailedReadTap);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.rxs[index].deferred_irqs {
|
|
||||||
self.rxs[index].deferred_irqs = false;
|
|
||||||
vring.signal_used_queue().unwrap();
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resume_rx(&mut self, vring: &mut Vring, index: usize) -> Result<()> {
|
|
||||||
if self.rxs[index].deferred_frame {
|
|
||||||
if self.rx_single_frame(&mut vring.mut_queue(), index)? {
|
|
||||||
self.rxs[index].deferred_frame = false;
|
|
||||||
// process_rx() was interrupted possibly before consuming all
|
|
||||||
// packets in the tap; try continuing now.
|
|
||||||
self.process_rx(vring, index)
|
|
||||||
} else if self.rxs[index].deferred_irqs {
|
|
||||||
self.rxs[index].deferred_irqs = false;
|
|
||||||
vring.signal_used_queue().unwrap();
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_tx(&mut self, mut queue: &mut Queue, index: usize) -> Result<()> {
|
|
||||||
let mem = self.mem.as_ref().ok_or(Error::NoMemoryConfigured)?;
|
|
||||||
|
|
||||||
self.txs[index].process_desc_chain(&mem, &mut self.taps[index].0, &mut queue);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_tap(&mut self, index: usize) -> io::Result<usize> {
|
|
||||||
self.taps[index].0.read(&mut self.rxs[index].frame_buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_vring_worker(&mut self, vring_worker: Option<Arc<VringWorker>>) {
|
|
||||||
self.vring_worker = vring_worker;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,6 +180,7 @@ impl VhostUserBackend for VhostUserNetBackend {
|
|||||||
| 1 << VIRTIO_NET_F_HOST_TSO4
|
| 1 << VIRTIO_NET_F_HOST_TSO4
|
||||||
| 1 << VIRTIO_NET_F_HOST_UFO
|
| 1 << VIRTIO_NET_F_HOST_UFO
|
||||||
| 1 << VIRTIO_F_VERSION_1
|
| 1 << VIRTIO_F_VERSION_1
|
||||||
|
| 1 << VIRTIO_RING_F_EVENT_IDX
|
||||||
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,59 +191,59 @@ impl VhostUserBackend for VhostUserNetBackend {
|
|||||||
fn set_event_idx(&mut self, _enabled: bool) {}
|
fn set_event_idx(&mut self, _enabled: bool) {}
|
||||||
|
|
||||||
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
|
||||||
self.mem = Some(mem);
|
for thread in self.threads.iter() {
|
||||||
|
thread.lock().unwrap().net.mem = Some(GuestMemoryAtomic::new(mem.clone()));
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_event(
|
fn handle_event(
|
||||||
&mut self,
|
&self,
|
||||||
device_event: u16,
|
device_event: u16,
|
||||||
evset: epoll::Events,
|
evset: epoll::Events,
|
||||||
vrings: &[Arc<RwLock<Vring>>],
|
vrings: &[Arc<RwLock<Vring>>],
|
||||||
|
thread_id: usize,
|
||||||
) -> VhostUserBackendResult<bool> {
|
) -> VhostUserBackendResult<bool> {
|
||||||
if evset != epoll::Events::EPOLLIN {
|
if evset != epoll::Events::EPOLLIN {
|
||||||
return Err(Error::HandleEventNotEpollIn.into());
|
return Err(Error::HandleEventNotEpollIn.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let tap_start_index = self.num_queues as u16;
|
let mut thread = self.threads[thread_id].lock().unwrap();
|
||||||
let tap_end_index = (self.num_queues + self.num_queues / 2 - 1) as u16;
|
|
||||||
|
|
||||||
match device_event {
|
match device_event {
|
||||||
x if ((x < self.num_queues as u16) && (x % 2 == 0)) => {
|
0 => {
|
||||||
let index = (x / 2) as usize;
|
let mut vring = vrings[0].write().unwrap();
|
||||||
let mut vring = vrings[x as usize].write().unwrap();
|
if thread
|
||||||
self.resume_rx(&mut vring, index)?;
|
.net
|
||||||
|
.resume_rx(&mut vring.mut_queue())
|
||||||
if !self.rx_tap_listenings[index] {
|
.map_err(Error::NetQueuePair)?
|
||||||
self.vring_worker.as_ref().unwrap().register_listener(
|
{
|
||||||
self.taps[index].0.as_raw_fd(),
|
vring
|
||||||
epoll::Events::EPOLLIN,
|
.signal_used_queue()
|
||||||
u64::try_from(self.taps[index].1).unwrap(),
|
.map_err(Error::FailedSignalingUsedQueue)?
|
||||||
)?;
|
|
||||||
self.rx_tap_listenings[index] = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x if ((x < self.num_queues as u16) && (x % 2 != 0)) => {
|
1 => {
|
||||||
let index = ((x - 1) / 2) as usize;
|
let mut vring = vrings[1].write().unwrap();
|
||||||
let mut vring = vrings[x as usize].write().unwrap();
|
if thread
|
||||||
self.process_tx(&mut vring.mut_queue(), index)?;
|
.net
|
||||||
}
|
.process_tx(&mut vring.mut_queue())
|
||||||
x if x >= tap_start_index && x <= tap_end_index => {
|
.map_err(Error::NetQueuePair)?
|
||||||
let index = x as usize - self.num_queues;
|
|
||||||
let mut vring = vrings[2 * index].write().unwrap();
|
|
||||||
if self.rxs[index].deferred_frame
|
|
||||||
// Process a deferred frame first if available. Don't read from tap again
|
|
||||||
// until we manage to receive this deferred frame.
|
|
||||||
{
|
{
|
||||||
if self.rx_single_frame(&mut vring.mut_queue(), index)? {
|
vring
|
||||||
self.rxs[index].deferred_frame = false;
|
.signal_used_queue()
|
||||||
self.process_rx(&mut vring, index)?;
|
.map_err(Error::FailedSignalingUsedQueue)?
|
||||||
} else if self.rxs[index].deferred_irqs {
|
}
|
||||||
self.rxs[index].deferred_irqs = false;
|
}
|
||||||
vring.signal_used_queue()?;
|
2 => {
|
||||||
}
|
let mut vring = vrings[0].write().unwrap();
|
||||||
} else {
|
if thread
|
||||||
self.process_rx(&mut vring, index)?;
|
.net
|
||||||
|
.process_rx_tap(&mut vring.mut_queue())
|
||||||
|
.map_err(Error::NetQueuePair)?
|
||||||
|
{
|
||||||
|
vring
|
||||||
|
.signal_used_queue()
|
||||||
|
.map_err(Error::FailedSignalingUsedQueue)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
_ => return Err(Error::HandleEventUnknownEvent.into()),
|
||||||
@@ -340,78 +252,78 @@ impl VhostUserBackend for VhostUserNetBackend {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_event(&self) -> Option<(EventFd, Option<u16>)> {
|
fn exit_event(&self, thread_index: usize) -> Option<(EventFd, Option<u16>)> {
|
||||||
let tap_end_index = (self.num_queues + self.num_queues / 2 - 1) as u16;
|
// The exit event is placed after the queues and the tap event, which
|
||||||
let kill_index = tap_end_index + 1;
|
// is event index 3.
|
||||||
Some((self.kill_evt.try_clone().unwrap(), Some(kill_index)))
|
Some((
|
||||||
|
self.threads[thread_index]
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.kill_evt
|
||||||
|
.try_clone()
|
||||||
|
.unwrap(),
|
||||||
|
Some(3),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queues_per_thread(&self) -> Vec<u64> {
|
||||||
|
self.queues_per_thread.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VhostUserNetBackendConfig<'a> {
|
pub struct VhostUserNetBackendConfig {
|
||||||
pub ip: Ipv4Addr,
|
pub ip: Ipv4Addr,
|
||||||
|
pub host_mac: MacAddr,
|
||||||
pub mask: Ipv4Addr,
|
pub mask: Ipv4Addr,
|
||||||
pub sock: &'a str,
|
pub socket: String,
|
||||||
pub num_queues: usize,
|
pub num_queues: usize,
|
||||||
pub queue_size: u16,
|
pub queue_size: u16,
|
||||||
pub tap: Option<&'a str>,
|
pub tap: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> VhostUserNetBackendConfig<'a> {
|
impl VhostUserNetBackendConfig {
|
||||||
pub fn parse(backend: &'a str) -> Result<Self> {
|
pub fn parse(backend: &str) -> Result<Self> {
|
||||||
let params_list: Vec<&str> = backend.split(',').collect();
|
let mut parser = OptionParser::new();
|
||||||
|
|
||||||
let mut ip_str: &str = "";
|
parser
|
||||||
let mut mask_str: &str = "";
|
.add("tap")
|
||||||
let mut sock: &str = "";
|
.add("ip")
|
||||||
let mut num_queues_str: &str = "";
|
.add("host_mac")
|
||||||
let mut queue_size_str: &str = "";
|
.add("mask")
|
||||||
let mut tap_str: &str = "";
|
.add("queue_size")
|
||||||
|
.add("num_queues")
|
||||||
|
.add("socket");
|
||||||
|
|
||||||
for param in params_list.iter() {
|
parser.parse(backend).map_err(Error::FailedConfigParse)?;
|
||||||
if param.starts_with("ip=") {
|
|
||||||
ip_str = ¶m[3..];
|
|
||||||
} else if param.starts_with("mask=") {
|
|
||||||
mask_str = ¶m[5..];
|
|
||||||
} else if param.starts_with("sock=") {
|
|
||||||
sock = ¶m[5..];
|
|
||||||
} else if param.starts_with("num_queues=") {
|
|
||||||
num_queues_str = ¶m[11..];
|
|
||||||
} else if param.starts_with("queue_size=") {
|
|
||||||
queue_size_str = ¶m[11..];
|
|
||||||
} else if param.starts_with("tap=") {
|
|
||||||
tap_str = ¶m[4..];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ip: Ipv4Addr = Ipv4Addr::new(192, 168, 100, 1);
|
let tap = parser.get("tap");
|
||||||
let mut mask: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);
|
let ip = parser
|
||||||
let mut num_queues: usize = 2;
|
.convert("ip")
|
||||||
let mut queue_size: u16 = 256;
|
.map_err(Error::FailedConfigParse)?
|
||||||
let mut tap: Option<&str> = None;
|
.unwrap_or_else(|| Ipv4Addr::new(192, 168, 100, 1));
|
||||||
|
let host_mac = parser
|
||||||
if sock.is_empty() {
|
.convert("host_mac")
|
||||||
return Err(Error::ParseSockParam);
|
.map_err(Error::FailedConfigParse)?
|
||||||
}
|
.unwrap_or_else(MacAddr::local_random);
|
||||||
if !ip_str.is_empty() {
|
let mask = parser
|
||||||
ip = ip_str.parse().map_err(Error::ParseIpParam)?;
|
.convert("mask")
|
||||||
}
|
.map_err(Error::FailedConfigParse)?
|
||||||
if !mask_str.is_empty() {
|
.unwrap_or_else(|| Ipv4Addr::new(255, 255, 255, 0));
|
||||||
mask = mask_str.parse().map_err(Error::ParseMaskParam)?;
|
let queue_size = parser
|
||||||
}
|
.convert("queue_size")
|
||||||
if !num_queues_str.is_empty() {
|
.map_err(Error::FailedConfigParse)?
|
||||||
num_queues = num_queues_str.parse().map_err(Error::ParseQueueNumParam)?;
|
.unwrap_or(256);
|
||||||
}
|
let num_queues = parser
|
||||||
if !queue_size_str.is_empty() {
|
.convert("num_queues")
|
||||||
queue_size = queue_size_str.parse().map_err(Error::ParseQueueSizeParam)?;
|
.map_err(Error::FailedConfigParse)?
|
||||||
}
|
.unwrap_or(2);
|
||||||
if !tap_str.is_empty() {
|
let socket = parser.get("socket").ok_or(Error::SocketParameterMissing)?;
|
||||||
tap = Some(tap_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(VhostUserNetBackendConfig {
|
Ok(VhostUserNetBackendConfig {
|
||||||
ip,
|
ip,
|
||||||
|
host_mac,
|
||||||
mask,
|
mask,
|
||||||
sock,
|
socket,
|
||||||
num_queues,
|
num_queues,
|
||||||
queue_size,
|
queue_size,
|
||||||
tap,
|
tap,
|
||||||
@@ -423,38 +335,50 @@ pub fn start_net_backend(backend_command: &str) {
|
|||||||
let backend_config = match VhostUserNetBackendConfig::parse(backend_command) {
|
let backend_config = match VhostUserNetBackendConfig::parse(backend_command) {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Failed parsing parameters {:?}", e);
|
eprintln!("Failed parsing parameters {:?}", e);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let tap = if let Some(tap) = backend_config.tap.as_ref() {
|
||||||
|
Some(tap.as_str())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let net_backend = Arc::new(RwLock::new(
|
let net_backend = Arc::new(RwLock::new(
|
||||||
VhostUserNetBackend::new(
|
VhostUserNetBackend::new(
|
||||||
backend_config.ip,
|
backend_config.ip,
|
||||||
|
backend_config.host_mac,
|
||||||
backend_config.mask,
|
backend_config.mask,
|
||||||
backend_config.num_queues,
|
backend_config.num_queues,
|
||||||
backend_config.queue_size,
|
backend_config.queue_size,
|
||||||
backend_config.tap,
|
tap,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut net_daemon = VhostUserDaemon::new(
|
let listener = Listener::new(&backend_config.socket, true).unwrap();
|
||||||
"vhost-user-net-backend".to_string(),
|
|
||||||
backend_config.sock.to_string(),
|
|
||||||
net_backend.clone(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let vring_worker = net_daemon.get_vring_worker();
|
let mut net_daemon =
|
||||||
|
VhostUserDaemon::new("vhost-user-net-backend".to_string(), net_backend.clone()).unwrap();
|
||||||
|
|
||||||
net_backend
|
let mut vring_workers = net_daemon.get_vring_workers();
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.set_vring_worker(Some(vring_worker));
|
|
||||||
|
|
||||||
if let Err(e) = net_daemon.start() {
|
if vring_workers.len() != net_backend.read().unwrap().threads.len() {
|
||||||
println!(
|
error!("Number of vring workers must be identical to the number of backend threads");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for thread in net_backend.read().unwrap().threads.iter() {
|
||||||
|
thread
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.set_vring_worker(Some(vring_workers.remove(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = net_daemon.start(listener) {
|
||||||
|
error!(
|
||||||
"failed to start daemon for vhost-user-net with error: {:?}",
|
"failed to start daemon for vhost-user-net with error: {:?}",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
@@ -465,8 +389,9 @@ pub fn start_net_backend(backend_command: &str) {
|
|||||||
error!("Error from the main thread: {:?}", e);
|
error!("Error from the main thread: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let kill_evt = &net_backend.write().unwrap().kill_evt;
|
for thread in net_backend.read().unwrap().threads.iter() {
|
||||||
if let Err(e) = kill_evt.write(1) {
|
if let Err(e) = thread.lock().unwrap().kill_evt.write(1) {
|
||||||
error!("Error shutting down worker thread: {:?}", e)
|
error!("Error shutting down worker thread: {:?}", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ authors = ["The Chromium OS Authors"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2.68"
|
libc = "0.2.71"
|
||||||
vm-memory = "0.1.0"
|
vm-memory = "0.2.1"
|
||||||
|
arch = { path = "../arch" }
|
||||||
|
|||||||
0
vm-allocator/src/address.rs
Executable file → Normal file
0
vm-allocator/src/address.rs
Executable file → Normal file
@@ -2,6 +2,9 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||||
|
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
use arch;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
use std::collections::btree_map::BTreeMap;
|
use std::collections::btree_map::BTreeMap;
|
||||||
use std::result;
|
use std::result;
|
||||||
|
|
||||||
@@ -13,12 +16,14 @@ pub enum Error {
|
|||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
/// GsiApic
|
/// GsiApic
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct GsiApic {
|
pub struct GsiApic {
|
||||||
base: u32,
|
base: u32,
|
||||||
irqs: u32,
|
irqs: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
impl GsiApic {
|
impl GsiApic {
|
||||||
/// New GSI APIC
|
/// New GSI APIC
|
||||||
pub fn new(base: u32, irqs: u32) -> Self {
|
pub fn new(base: u32, irqs: u32) -> Self {
|
||||||
@@ -28,12 +33,14 @@ impl GsiApic {
|
|||||||
|
|
||||||
/// GsiAllocator
|
/// GsiAllocator
|
||||||
pub struct GsiAllocator {
|
pub struct GsiAllocator {
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
apics: BTreeMap<u32, u32>,
|
apics: BTreeMap<u32, u32>,
|
||||||
next_irq: u32,
|
next_irq: u32,
|
||||||
next_gsi: u32,
|
next_gsi: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GsiAllocator {
|
impl GsiAllocator {
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
/// New GSI allocator
|
/// New GSI allocator
|
||||||
pub fn new(apics: Vec<GsiApic>) -> Self {
|
pub fn new(apics: Vec<GsiApic>) -> Self {
|
||||||
let mut allocator = GsiAllocator {
|
let mut allocator = GsiAllocator {
|
||||||
@@ -57,13 +64,23 @@ impl GsiAllocator {
|
|||||||
allocator
|
allocator
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate a GSI
|
#[cfg(target_arch = "aarch64")]
|
||||||
pub fn allocate_gsi(&mut self) -> Result<u32> {
|
/// New GSI allocator
|
||||||
self.next_gsi = self.next_gsi.checked_add(1).ok_or(Error::Overflow)?;
|
pub fn new() -> Self {
|
||||||
|
GsiAllocator {
|
||||||
Ok(self.next_gsi - 1)
|
next_irq: arch::IRQ_BASE,
|
||||||
|
next_gsi: arch::IRQ_BASE,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allocate a GSI
|
||||||
|
pub fn allocate_gsi(&mut self) -> Result<u32> {
|
||||||
|
let gsi = self.next_gsi;
|
||||||
|
self.next_gsi = self.next_gsi.checked_add(1).ok_or(Error::Overflow)?;
|
||||||
|
Ok(gsi)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
/// Allocate an IRQ
|
/// Allocate an IRQ
|
||||||
pub fn allocate_irq(&mut self) -> Result<u32> {
|
pub fn allocate_irq(&mut self) -> Result<u32> {
|
||||||
let mut irq: u32 = 0;
|
let mut irq: u32 = 0;
|
||||||
@@ -81,4 +98,12 @@ impl GsiAllocator {
|
|||||||
|
|
||||||
Ok(irq)
|
Ok(irq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
/// Allocate an IRQ
|
||||||
|
pub fn allocate_irq(&mut self) -> Result<u32> {
|
||||||
|
let irq = self.next_irq;
|
||||||
|
self.next_irq = self.next_irq.checked_add(1).ok_or(Error::Overflow)?;
|
||||||
|
Ok(irq)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,5 +18,7 @@ mod gsi;
|
|||||||
mod system;
|
mod system;
|
||||||
|
|
||||||
pub use crate::address::AddressAllocator;
|
pub use crate::address::AddressAllocator;
|
||||||
pub use crate::gsi::{GsiAllocator, GsiApic};
|
pub use crate::gsi::GsiAllocator;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
pub use crate::gsi::GsiApic;
|
||||||
pub use crate::system::SystemAllocator;
|
pub use crate::system::SystemAllocator;
|
||||||
|
|||||||
26
vm-allocator/src/system.rs
Executable file → Normal file
26
vm-allocator/src/system.rs
Executable file → Normal file
@@ -10,7 +10,9 @@
|
|||||||
use vm_memory::{GuestAddress, GuestUsize};
|
use vm_memory::{GuestAddress, GuestUsize};
|
||||||
|
|
||||||
use crate::address::AddressAllocator;
|
use crate::address::AddressAllocator;
|
||||||
use crate::gsi::{GsiAllocator, GsiApic};
|
use crate::gsi::GsiAllocator;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
use crate::gsi::GsiApic;
|
||||||
|
|
||||||
use libc::{sysconf, _SC_PAGESIZE};
|
use libc::{sysconf, _SC_PAGESIZE};
|
||||||
|
|
||||||
@@ -39,6 +41,7 @@ fn pagesize() -> usize {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
pub struct SystemAllocator {
|
pub struct SystemAllocator {
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
io_address_space: AddressAllocator,
|
io_address_space: AddressAllocator,
|
||||||
mmio_address_space: AddressAllocator,
|
mmio_address_space: AddressAllocator,
|
||||||
mmio_hole_address_space: AddressAllocator,
|
mmio_hole_address_space: AddressAllocator,
|
||||||
@@ -49,25 +52,32 @@ impl SystemAllocator {
|
|||||||
/// Creates a new `SystemAllocator` for managing addresses and irq numvers.
|
/// Creates a new `SystemAllocator` for managing addresses and irq numvers.
|
||||||
/// Can return `None` if `base` + `size` overflows a u64
|
/// Can return `None` if `base` + `size` overflows a u64
|
||||||
///
|
///
|
||||||
/// * `io_base` - The starting address of IO memory.
|
/// * `io_base` - (X86) The starting address of IO memory.
|
||||||
/// * `io_size` - The size of IO memory.
|
/// * `io_size` - (X86) The size of IO memory.
|
||||||
/// * `mmio_base` - The starting address of MMIO memory.
|
/// * `mmio_base` - The starting address of MMIO memory.
|
||||||
/// * `mmio_size` - The size of MMIO memory.
|
/// * `mmio_size` - The size of MMIO memory.
|
||||||
/// * `first_irq` - The first irq number to give out.
|
/// * `mmio_hole_base` - The starting address of MMIO memory in 32-bit address space.
|
||||||
|
/// * `mmio_hole_size` - The size of MMIO memory in 32-bit address space.
|
||||||
|
/// * `apics` - (X86) Vector of APIC's.
|
||||||
|
///
|
||||||
pub fn new(
|
pub fn new(
|
||||||
io_base: GuestAddress,
|
#[cfg(target_arch = "x86_64")] io_base: GuestAddress,
|
||||||
io_size: GuestUsize,
|
#[cfg(target_arch = "x86_64")] io_size: GuestUsize,
|
||||||
mmio_base: GuestAddress,
|
mmio_base: GuestAddress,
|
||||||
mmio_size: GuestUsize,
|
mmio_size: GuestUsize,
|
||||||
mmio_hole_base: GuestAddress,
|
mmio_hole_base: GuestAddress,
|
||||||
mmio_hole_size: GuestUsize,
|
mmio_hole_size: GuestUsize,
|
||||||
apics: Vec<GsiApic>,
|
#[cfg(target_arch = "x86_64")] apics: Vec<GsiApic>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
Some(SystemAllocator {
|
Some(SystemAllocator {
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
io_address_space: AddressAllocator::new(io_base, io_size)?,
|
io_address_space: AddressAllocator::new(io_base, io_size)?,
|
||||||
mmio_address_space: AddressAllocator::new(mmio_base, mmio_size)?,
|
mmio_address_space: AddressAllocator::new(mmio_base, mmio_size)?,
|
||||||
mmio_hole_address_space: AddressAllocator::new(mmio_hole_base, mmio_hole_size)?,
|
mmio_hole_address_space: AddressAllocator::new(mmio_hole_base, mmio_hole_size)?,
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
gsi_allocator: GsiAllocator::new(apics),
|
gsi_allocator: GsiAllocator::new(apics),
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
gsi_allocator: GsiAllocator::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +91,7 @@ impl SystemAllocator {
|
|||||||
self.gsi_allocator.allocate_gsi().ok()
|
self.gsi_allocator.allocate_gsi().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
/// Reserves a section of `size` bytes of IO address space.
|
/// Reserves a section of `size` bytes of IO address space.
|
||||||
pub fn allocate_io_addresses(
|
pub fn allocate_io_addresses(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -120,6 +131,7 @@ impl SystemAllocator {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
/// Free an IO address range.
|
/// Free an IO address range.
|
||||||
/// We can only free a range if it matches exactly an already allocated range.
|
/// We can only free a range if it matches exactly an already allocated range.
|
||||||
pub fn free_io_addresses(&mut self, address: GuestAddress, size: GuestUsize) {
|
pub fn free_io_addresses(&mut self, address: GuestAddress, size: GuestUsize) {
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ thiserror = "1.0"
|
|||||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||||
serde_derive = ">=1.0.27"
|
serde_derive = ">=1.0.27"
|
||||||
serde_json = ">=1.0.9"
|
serde_json = ">=1.0.9"
|
||||||
vm-memory = { version = "0.1.0", features = ["backend-mmap"] }
|
vm-memory = { version = "0.2.1", features = ["backend-mmap"] }
|
||||||
vmm-sys-util = ">=0.3.1"
|
vmm-sys-util = ">=0.3.1"
|
||||||
|
|
||||||
|
|||||||
@@ -1,116 +1,38 @@
|
|||||||
extern crate serde;
|
#[macro_use]
|
||||||
extern crate thiserror;
|
extern crate serde_derive;
|
||||||
extern crate vm_memory;
|
extern crate vm_memory;
|
||||||
|
|
||||||
pub mod interrupt;
|
pub mod interrupt;
|
||||||
|
|
||||||
use vm_memory::{
|
/// Type of Message Singaled Interrupt
|
||||||
Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion, GuestRegionMmap,
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
MemoryRegionAddress,
|
pub enum MsiIrqType {
|
||||||
};
|
/// PCI MSI IRQ numbers.
|
||||||
|
PciMsi,
|
||||||
use thiserror::Error;
|
/// PCI MSIx IRQ numbers.
|
||||||
|
PciMsix,
|
||||||
/// Trait meant for triggering the DMA mapping update related to an external
|
/// Generic MSI IRQ numbers.
|
||||||
/// device not managed fully through virtio. It is dedicated to virtio-iommu
|
GenericMsi,
|
||||||
/// in order to trigger the map update anytime the mapping is updated from the
|
|
||||||
/// guest.
|
|
||||||
pub trait ExternalDmaMapping: Send + Sync {
|
|
||||||
/// Map a memory range
|
|
||||||
fn map(&self, iova: u64, gpa: u64, size: u64) -> std::result::Result<(), std::io::Error>;
|
|
||||||
|
|
||||||
/// Unmap a memory range
|
|
||||||
fn unmap(&self, iova: u64, size: u64) -> std::result::Result<(), std::io::Error>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
/// Enumeration for device resources.
|
||||||
pub enum MigratableError {
|
#[allow(missing_docs)]
|
||||||
#[error("Failed to pause migratable component: {0}")]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
Pause(#[source] anyhow::Error),
|
pub enum Resource {
|
||||||
|
/// IO Port address range.
|
||||||
#[error("Failed to resume migratable component: {0}")]
|
PioAddressRange { base: u16, size: u16 },
|
||||||
Resume(#[source] anyhow::Error),
|
/// Memory Mapped IO address range.
|
||||||
}
|
MmioAddressRange { base: u64, size: u64 },
|
||||||
|
/// Legacy IRQ number.
|
||||||
/// A Pausable component can be paused and resumed.
|
LegacyIrq(u32),
|
||||||
pub trait Pausable {
|
/// Message Signaled Interrupt
|
||||||
/// Pause the component.
|
MsiIrq {
|
||||||
fn pause(&mut self) -> std::result::Result<(), MigratableError>;
|
ty: MsiIrqType,
|
||||||
|
base: u32,
|
||||||
/// Resume the component.
|
size: u32,
|
||||||
fn resume(&mut self) -> std::result::Result<(), MigratableError>;
|
},
|
||||||
}
|
/// Network Interface Card MAC address.
|
||||||
|
MacAddress(String),
|
||||||
/// A snapshotable component can be snapshoted.
|
/// KVM memslot index.
|
||||||
pub trait Snapshotable {}
|
KvmMemSlot(u32),
|
||||||
|
|
||||||
/// Trait to be implemented by any component (device, CPU, RAM, etc) that
|
|
||||||
/// can be migrated.
|
|
||||||
/// All migratable components are paused before being snapshotted, and then
|
|
||||||
/// eventually resumed. Thus any Migratable component must be both Pausable
|
|
||||||
/// and Snapshotable.
|
|
||||||
pub trait Migratable: Send + Pausable + Snapshotable {}
|
|
||||||
|
|
||||||
fn get_region_host_address_range(
|
|
||||||
region: &GuestRegionMmap,
|
|
||||||
addr: MemoryRegionAddress,
|
|
||||||
size: usize,
|
|
||||||
) -> Option<*mut u8> {
|
|
||||||
region.check_address(addr).and_then(|addr| {
|
|
||||||
region
|
|
||||||
.checked_offset(addr, size)
|
|
||||||
.map(|_| region.as_ptr().wrapping_offset(addr.raw_value() as isize))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert an absolute address into an address space (GuestMemory)
|
|
||||||
/// to a host pointer and verify that the provided size define a valid
|
|
||||||
/// range within a single memory region.
|
|
||||||
/// Return None if it is out of bounds or if addr+size overlaps a single region.
|
|
||||||
///
|
|
||||||
/// This is a temporary vm-memory wrapper.
|
|
||||||
pub fn get_host_address_range(
|
|
||||||
mem: &GuestMemoryMmap,
|
|
||||||
addr: GuestAddress,
|
|
||||||
size: usize,
|
|
||||||
) -> Option<*mut u8> {
|
|
||||||
mem.to_region_addr(addr)
|
|
||||||
.and_then(|(r, addr)| get_region_host_address_range(r, addr, size))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_get_host_address_range() {
|
|
||||||
let start_addr1 = GuestAddress(0x0);
|
|
||||||
let start_addr2 = GuestAddress(0x1000);
|
|
||||||
let guest_mem =
|
|
||||||
GuestMemoryMmap::from_ranges(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
|
|
||||||
|
|
||||||
assert!(get_host_address_range(&guest_mem, GuestAddress(0x600), 0x100).is_none());
|
|
||||||
|
|
||||||
// Overlapping range
|
|
||||||
assert!(get_host_address_range(&guest_mem, GuestAddress(0x1000), 0x500).is_none());
|
|
||||||
|
|
||||||
// Overlapping range
|
|
||||||
assert!(get_host_address_range(&guest_mem, GuestAddress(0x1200), 0x500).is_none());
|
|
||||||
|
|
||||||
let ptr = get_host_address_range(&guest_mem, GuestAddress(0x1000), 0x100).unwrap();
|
|
||||||
|
|
||||||
let ptr0 = get_host_address_range(&guest_mem, GuestAddress(0x1100), 0x100).unwrap();
|
|
||||||
|
|
||||||
let ptr1 = guest_mem.get_host_address(GuestAddress(0x1200)).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
ptr,
|
|
||||||
guest_mem
|
|
||||||
.find_region(GuestAddress(0x1100))
|
|
||||||
.unwrap()
|
|
||||||
.as_ptr()
|
|
||||||
);
|
|
||||||
assert_eq!(unsafe { ptr0.offset(0x100) }, ptr1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
vm-migration/Cargo.toml
Normal file
12
vm-migration/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "vm-migration"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Cloud Hypervisor Authors"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
|
thiserror = "1.0"
|
||||||
|
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||||
|
serde_derive = ">=1.0.27"
|
||||||
|
serde_json = ">=1.0.9"
|
||||||
162
vm-migration/src/lib.rs
Normal file
162
vm-migration/src/lib.rs
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
// Copyright © 2019 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
//
|
||||||
|
|
||||||
|
extern crate serde;
|
||||||
|
extern crate thiserror;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum MigratableError {
|
||||||
|
#[error("Failed to pause migratable component: {0}")]
|
||||||
|
Pause(#[source] anyhow::Error),
|
||||||
|
|
||||||
|
#[error("Failed to resume migratable component: {0}")]
|
||||||
|
Resume(#[source] anyhow::Error),
|
||||||
|
|
||||||
|
#[error("Failed to snapshot migratable component: {0}")]
|
||||||
|
Snapshot(#[source] anyhow::Error),
|
||||||
|
|
||||||
|
#[error("Failed to restore migratable component: {0}")]
|
||||||
|
Restore(#[source] anyhow::Error),
|
||||||
|
|
||||||
|
#[error("Failed to send migratable component snapshot: {0}")]
|
||||||
|
MigrateSend(#[source] anyhow::Error),
|
||||||
|
|
||||||
|
#[error("Failed to receive migratable component snapshot: {0}")]
|
||||||
|
MigrateReceive(#[source] anyhow::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Pausable component can be paused and resumed.
|
||||||
|
pub trait Pausable {
|
||||||
|
/// Pause the component.
|
||||||
|
fn pause(&mut self) -> std::result::Result<(), MigratableError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resume the component.
|
||||||
|
fn resume(&mut self) -> std::result::Result<(), MigratableError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Snapshottable component snapshot section.
|
||||||
|
/// Migratable component can split their migration snapshot into
|
||||||
|
/// separate sections.
|
||||||
|
/// Splitting a component migration data into different sections
|
||||||
|
/// allows for easier and forward compatible extensions.
|
||||||
|
#[derive(Clone, Default, Deserialize, Serialize)]
|
||||||
|
pub struct SnapshotDataSection {
|
||||||
|
/// The section id.
|
||||||
|
pub id: String,
|
||||||
|
|
||||||
|
/// The section serialized snapshot.
|
||||||
|
pub snapshot: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Snapshottable component's snapshot is a tree of snapshots, where leafs
|
||||||
|
/// contain the snapshot data. Nodes of this tree track all their children
|
||||||
|
/// through the snapshots field, which is basically their sub-components.
|
||||||
|
/// Leaves will typically have an empty snapshots map, while nodes usually
|
||||||
|
/// carry an empty snapshot_data.
|
||||||
|
///
|
||||||
|
/// For example, a device manager snapshot is the composition of all its
|
||||||
|
/// devices snapshots. The device manager Snapshot would have no snapshot_data
|
||||||
|
/// but one Snapshot child per tracked device. Then each device's Snapshot
|
||||||
|
/// would carry an empty snapshots map but a map of SnapshotDataSection, i.e.
|
||||||
|
/// the actual device snapshot data.
|
||||||
|
#[derive(Clone, Default, Deserialize, Serialize)]
|
||||||
|
pub struct Snapshot {
|
||||||
|
/// The Snapshottable component id.
|
||||||
|
pub id: String,
|
||||||
|
|
||||||
|
/// The Snapshottable component snapshots.
|
||||||
|
pub snapshots: std::collections::HashMap<String, Box<Snapshot>>,
|
||||||
|
|
||||||
|
/// The Snapshottable component's snapshot data.
|
||||||
|
/// A map of snapshot sections, indexed by the section ids.
|
||||||
|
pub snapshot_data: std::collections::HashMap<String, SnapshotDataSection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Snapshot {
|
||||||
|
/// Create an empty Snapshot.
|
||||||
|
pub fn new(id: &str) -> Self {
|
||||||
|
Snapshot {
|
||||||
|
id: id.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a sub-component's Snapshot to the Snapshot.
|
||||||
|
pub fn add_snapshot(&mut self, snapshot: Snapshot) {
|
||||||
|
self.snapshots
|
||||||
|
.insert(snapshot.id.clone(), Box::new(snapshot));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a SnapshotDatasection to the component snapshot data.
|
||||||
|
pub fn add_data_section(&mut self, section: SnapshotDataSection) {
|
||||||
|
self.snapshot_data.insert(section.id.clone(), section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A snapshottable component can be snapshotted.
|
||||||
|
pub trait Snapshottable: Pausable {
|
||||||
|
/// The snapshottable component id.
|
||||||
|
fn id(&self) -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take a component snapshot.
|
||||||
|
fn snapshot(&self) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
|
Ok(Snapshot::new(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore a component from its snapshot.
|
||||||
|
fn restore(&mut self, _snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A transportable component can be sent or receive to a specific URL.
|
||||||
|
///
|
||||||
|
/// This trait is meant to be used for component that have custom
|
||||||
|
/// transport handlers.
|
||||||
|
pub trait Transportable: Pausable + Snapshottable {
|
||||||
|
/// Send a component snapshot.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `snapshot` - The migratable component snapshot to send.
|
||||||
|
/// * `destination_url` - The destination URL to send the snapshot to. This
|
||||||
|
/// could be an HTTP endpoint, a TCP address or a local file.
|
||||||
|
fn send(
|
||||||
|
&self,
|
||||||
|
_snapshot: &Snapshot,
|
||||||
|
_destination_url: &str,
|
||||||
|
) -> std::result::Result<(), MigratableError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive a component snapshot.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `source_url` - The source URL to fetch the snapshot from. This could be an HTTP
|
||||||
|
/// endpoint, a TCP address or a local file.
|
||||||
|
fn recv(&self, _source_url: &str) -> std::result::Result<Snapshot, MigratableError> {
|
||||||
|
Ok(Snapshot::new(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait to be implemented by any component (device, CPU, RAM, etc) that
|
||||||
|
/// can be migrated.
|
||||||
|
/// All migratable components are paused before being snapshotted, and then
|
||||||
|
/// eventually resumed. Thus any Migratable component must be both Pausable
|
||||||
|
/// and Snapshottable.
|
||||||
|
/// Moreover a migratable component can be transported to a remote or local
|
||||||
|
/// destination and thus must be Transportable.
|
||||||
|
pub trait Migratable: Send + Pausable + Snapshottable + Transportable {}
|
||||||
@@ -10,19 +10,25 @@ pci_support = ["pci"]
|
|||||||
mmio_support = []
|
mmio_support = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
arc-swap = ">=0.4.4"
|
arc-swap = ">=0.4.4"
|
||||||
byteorder = "1.3.4"
|
byteorder = "1.3.4"
|
||||||
devices = { path = "../devices" }
|
devices = { path = "../devices" }
|
||||||
epoll = ">=4.0.1"
|
epoll = ">=4.0.1"
|
||||||
libc = "0.2.68"
|
libc = "0.2.71"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
net_gen = { path = "../net_gen" }
|
net_gen = { path = "../net_gen" }
|
||||||
net_util = { path = "../net_util" }
|
net_util = { path = "../net_util" }
|
||||||
pci = { path = "../pci", optional = true }
|
pci = { path = "../pci", optional = true }
|
||||||
|
serde = ">=1.0.27"
|
||||||
|
serde_derive = ">=1.0.27"
|
||||||
|
serde_json = ">=1.0.9"
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
virtio-bindings = { git = "https://github.com/rust-vmm/virtio-bindings", version = "0.1", features = ["virtio-v5_0_0"]}
|
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
|
||||||
|
vfio-ioctls = { git = "https://github.com/cloud-hypervisor/vfio-ioctls", branch = "ch" }
|
||||||
vm-allocator = { path = "../vm-allocator" }
|
vm-allocator = { path = "../vm-allocator" }
|
||||||
vm-device = { path = "../vm-device" }
|
vm-device = { path = "../vm-device" }
|
||||||
vm-memory = { version = "0.1.0", features = ["backend-mmap", "backend-atomic"] }
|
vm-memory = { version = "0.2.1", features = ["backend-mmap", "backend-atomic"] }
|
||||||
|
vm-migration = { path = "../vm-migration" }
|
||||||
vmm-sys-util = ">=0.3.1"
|
vmm-sys-util = ">=0.3.1"
|
||||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-master", "vhost-user-slave"] }
|
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "dragonball", package = "vhost", features = ["vhost-user-master", "vhost-user-slave"] }
|
||||||
|
|||||||
263
vm-virtio/src/block.rs
Executable file → Normal file
263
vm-virtio/src/block.rs
Executable file → Normal file
@@ -14,8 +14,9 @@ use super::{
|
|||||||
VirtioDeviceType, VirtioInterruptType,
|
VirtioDeviceType, VirtioInterruptType,
|
||||||
};
|
};
|
||||||
use crate::VirtioInterrupt;
|
use crate::VirtioInterrupt;
|
||||||
use epoll;
|
use anyhow::anyhow;
|
||||||
use libc::{c_void, EFD_NONBLOCK};
|
use libc::{c_void, EFD_NONBLOCK};
|
||||||
|
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
@@ -23,7 +24,7 @@ use std::fs::{File, Metadata};
|
|||||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::os::linux::fs::MetadataExt;
|
use std::os::linux::fs::MetadataExt;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
@@ -31,11 +32,15 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use virtio_bindings::bindings::virtio_blk::*;
|
use virtio_bindings::bindings::virtio_blk::*;
|
||||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemory, GuestMemoryAtomic,
|
||||||
GuestMemoryError, GuestMemoryMmap,
|
GuestMemoryError, GuestMemoryMmap,
|
||||||
};
|
};
|
||||||
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||||
|
Transportable,
|
||||||
|
};
|
||||||
use vmm_sys_util::{eventfd::EventFd, seek_hole::SeekHole, write_zeroes::PunchHole};
|
use vmm_sys_util::{eventfd::EventFd, seek_hole::SeekHole, write_zeroes::PunchHole};
|
||||||
|
|
||||||
const SECTOR_SHIFT: u8 = 9;
|
const SECTOR_SHIFT: u8 = 9;
|
||||||
@@ -344,7 +349,7 @@ impl Write for RawFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
self.file.flush()
|
self.file.sync_all()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,6 +478,7 @@ pub struct Request {
|
|||||||
data_addr: GuestAddress,
|
data_addr: GuestAddress,
|
||||||
data_len: u32,
|
data_len: u32,
|
||||||
pub status_addr: GuestAddress,
|
pub status_addr: GuestAddress,
|
||||||
|
writeback: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
@@ -491,6 +497,7 @@ impl Request {
|
|||||||
data_addr: GuestAddress(0),
|
data_addr: GuestAddress(0),
|
||||||
data_len: 0,
|
data_len: 0,
|
||||||
status_addr: GuestAddress(0),
|
status_addr: GuestAddress(0),
|
||||||
|
writeback: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let data_desc;
|
let data_desc;
|
||||||
@@ -570,13 +577,11 @@ impl Request {
|
|||||||
RequestType::Out => {
|
RequestType::Out => {
|
||||||
mem.write_all_to(self.data_addr, disk, self.data_len as usize)
|
mem.write_all_to(self.data_addr, disk, self.data_len as usize)
|
||||||
.map_err(ExecuteError::Write)?;
|
.map_err(ExecuteError::Write)?;
|
||||||
}
|
if !self.writeback {
|
||||||
RequestType::Flush => match disk.flush() {
|
disk.flush().map_err(ExecuteError::Flush)?;
|
||||||
Ok(_) => {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
}
|
||||||
Err(e) => return Err(ExecuteError::Flush(e)),
|
}
|
||||||
},
|
RequestType::Flush => disk.flush().map_err(ExecuteError::Flush)?,
|
||||||
RequestType::GetDeviceID => {
|
RequestType::GetDeviceID => {
|
||||||
if (self.data_len as usize) < disk_id.len() {
|
if (self.data_len as usize) < disk_id.len() {
|
||||||
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
return Err(ExecuteError::BadRequest(Error::InvalidOffset));
|
||||||
@@ -588,6 +593,10 @@ impl Request {
|
|||||||
};
|
};
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_writeback(&mut self, writeback: bool) {
|
||||||
|
self.writeback = writeback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BlockEpollHandler<T: DiskFile> {
|
struct BlockEpollHandler<T: DiskFile> {
|
||||||
@@ -599,6 +608,8 @@ struct BlockEpollHandler<T: DiskFile> {
|
|||||||
disk_image_id: Vec<u8>,
|
disk_image_id: Vec<u8>,
|
||||||
kill_evt: EventFd,
|
kill_evt: EventFd,
|
||||||
pause_evt: EventFd,
|
pause_evt: EventFd,
|
||||||
|
event_idx: bool,
|
||||||
|
writeback: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: DiskFile> BlockEpollHandler<T> {
|
impl<T: DiskFile> BlockEpollHandler<T> {
|
||||||
@@ -611,7 +622,9 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
|||||||
for avail_desc in queue.iter(&mem) {
|
for avail_desc in queue.iter(&mem) {
|
||||||
let len;
|
let len;
|
||||||
match Request::parse(&avail_desc, &mem) {
|
match Request::parse(&avail_desc, &mem) {
|
||||||
Ok(request) => {
|
Ok(mut request) => {
|
||||||
|
request.set_writeback(self.writeback.load(Ordering::SeqCst));
|
||||||
|
|
||||||
let mut disk_image_locked = self.disk_image.lock().unwrap();
|
let mut disk_image_locked = self.disk_image.lock().unwrap();
|
||||||
let mut disk_image = disk_image_locked.deref_mut();
|
let mut disk_image = disk_image_locked.deref_mut();
|
||||||
let status = match request.execute(
|
let status = match request.execute(
|
||||||
@@ -680,24 +693,26 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
|||||||
) -> result::Result<(), DeviceError> {
|
) -> result::Result<(), DeviceError> {
|
||||||
// Create the epoll file descriptor
|
// Create the epoll file descriptor
|
||||||
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||||
|
// Use 'File' to enforce closing on 'epoll_fd'
|
||||||
|
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
|
||||||
|
|
||||||
// Add events
|
// Add events
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
queue_evt.as_raw_fd(),
|
queue_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(QUEUE_AVAIL_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(QUEUE_AVAIL_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.kill_evt.as_raw_fd(),
|
self.kill_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.pause_evt.as_raw_fd(),
|
self.pause_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
|
||||||
@@ -708,7 +723,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
|||||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||||
|
|
||||||
'epoll: loop {
|
'epoll: loop {
|
||||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
let num_events = match epoll::wait(epoll_file.as_raw_fd(), -1, &mut events[..]) {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.kind() == io::ErrorKind::Interrupted {
|
if e.kind() == io::ErrorKind::Interrupted {
|
||||||
@@ -733,6 +748,28 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
|||||||
if let Err(e) = queue_evt.read() {
|
if let Err(e) = queue_evt.read() {
|
||||||
error!("Failed to get queue event: {:?}", e);
|
error!("Failed to get queue event: {:?}", e);
|
||||||
break 'epoll;
|
break 'epoll;
|
||||||
|
} else if self.event_idx {
|
||||||
|
// vm-virtio's Queue implementation only checks avail_index
|
||||||
|
// once, so to properly support EVENT_IDX we need to keep
|
||||||
|
// calling process_queue() until it stops finding new
|
||||||
|
// requests on the queue.
|
||||||
|
loop {
|
||||||
|
if self.process_queue() {
|
||||||
|
self.queue.update_avail_event(&self.mem.memory());
|
||||||
|
|
||||||
|
if self.queue.needs_notification(
|
||||||
|
&self.mem.memory(),
|
||||||
|
self.queue.next_used,
|
||||||
|
) {
|
||||||
|
if let Err(e) = self.signal_used_queue() {
|
||||||
|
error!("Failed to signal used queue: {:?}", e);
|
||||||
|
break 'epoll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if self.process_queue() {
|
} else if self.process_queue() {
|
||||||
if let Err(e) = self.signal_used_queue() {
|
if let Err(e) = self.signal_used_queue() {
|
||||||
error!("Failed to signal used queue: {:?}", e);
|
error!("Failed to signal used queue: {:?}", e);
|
||||||
@@ -766,7 +803,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
pub struct VirtioBlockGeometry {
|
pub struct VirtioBlockGeometry {
|
||||||
pub cylinders: u16,
|
pub cylinders: u16,
|
||||||
@@ -774,9 +811,31 @@ pub struct VirtioBlockGeometry {
|
|||||||
pub sectors: u8,
|
pub sectors: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We must explicitly implement Serialize since the structure is packed and
|
||||||
|
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||||
|
// Serialize from serde, it will borrow the values from the structure.
|
||||||
|
// That's why this implementation copies each field separately before it
|
||||||
|
// serializes the entire structure field by field.
|
||||||
|
impl Serialize for VirtioBlockGeometry {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let cylinders = self.cylinders;
|
||||||
|
let heads = self.heads;
|
||||||
|
let sectors = self.sectors;
|
||||||
|
|
||||||
|
let mut virtio_block_geometry = serializer.serialize_struct("VirtioBlockGeometry", 4)?;
|
||||||
|
virtio_block_geometry.serialize_field("cylinders", &cylinders)?;
|
||||||
|
virtio_block_geometry.serialize_field("heads", &heads)?;
|
||||||
|
virtio_block_geometry.serialize_field("sectors", §ors)?;
|
||||||
|
virtio_block_geometry.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl ByteValued for VirtioBlockGeometry {}
|
unsafe impl ByteValued for VirtioBlockGeometry {}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||||
#[repr(C, packed)]
|
#[repr(C, packed)]
|
||||||
pub struct VirtioBlockConfig {
|
pub struct VirtioBlockConfig {
|
||||||
pub capacity: u64,
|
pub capacity: u64,
|
||||||
@@ -788,7 +847,7 @@ pub struct VirtioBlockConfig {
|
|||||||
pub alignment_offset: u8,
|
pub alignment_offset: u8,
|
||||||
pub min_io_size: u16,
|
pub min_io_size: u16,
|
||||||
pub opt_io_size: u32,
|
pub opt_io_size: u32,
|
||||||
pub wce: u8,
|
pub writeback: u8,
|
||||||
unused: u8,
|
unused: u8,
|
||||||
pub num_queues: u16,
|
pub num_queues: u16,
|
||||||
pub max_discard_sectors: u32,
|
pub max_discard_sectors: u32,
|
||||||
@@ -800,10 +859,67 @@ pub struct VirtioBlockConfig {
|
|||||||
unused1: [u8; 3],
|
unused1: [u8; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We must explicitly implement Serialize since the structure is packed and
|
||||||
|
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||||
|
// Serialize from serde, it will borrow the values from the structure.
|
||||||
|
// That's why this implementation copies each field separately before it
|
||||||
|
// serializes the entire structure field by field.
|
||||||
|
impl Serialize for VirtioBlockConfig {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let capacity = self.capacity;
|
||||||
|
let size_max = self.size_max;
|
||||||
|
let seg_max = self.seg_max;
|
||||||
|
let geometry = self.geometry;
|
||||||
|
let blk_size = self.blk_size;
|
||||||
|
let physical_block_exp = self.physical_block_exp;
|
||||||
|
let alignment_offset = self.alignment_offset;
|
||||||
|
let min_io_size = self.min_io_size;
|
||||||
|
let opt_io_size = self.opt_io_size;
|
||||||
|
let writeback = self.writeback;
|
||||||
|
let unused = self.unused;
|
||||||
|
let num_queues = self.num_queues;
|
||||||
|
let max_discard_sectors = self.max_discard_sectors;
|
||||||
|
let max_discard_seg = self.max_discard_seg;
|
||||||
|
let discard_sector_alignment = self.discard_sector_alignment;
|
||||||
|
let max_write_zeroes_sectors = self.max_write_zeroes_sectors;
|
||||||
|
let max_write_zeroes_seg = self.max_write_zeroes_seg;
|
||||||
|
let write_zeroes_may_unmap = self.write_zeroes_may_unmap;
|
||||||
|
let unused1 = self.unused1;
|
||||||
|
|
||||||
|
let mut virtio_block_config = serializer.serialize_struct("VirtioBlockConfig", 60)?;
|
||||||
|
virtio_block_config.serialize_field("capacity", &capacity)?;
|
||||||
|
virtio_block_config.serialize_field("size_max", &size_max)?;
|
||||||
|
virtio_block_config.serialize_field("seg_max", &seg_max)?;
|
||||||
|
virtio_block_config.serialize_field("geometry", &geometry)?;
|
||||||
|
virtio_block_config.serialize_field("blk_size", &blk_size)?;
|
||||||
|
virtio_block_config.serialize_field("physical_block_exp", &physical_block_exp)?;
|
||||||
|
virtio_block_config.serialize_field("alignment_offset", &alignment_offset)?;
|
||||||
|
virtio_block_config.serialize_field("min_io_size", &min_io_size)?;
|
||||||
|
virtio_block_config.serialize_field("opt_io_size", &opt_io_size)?;
|
||||||
|
virtio_block_config.serialize_field("writeback", &writeback)?;
|
||||||
|
virtio_block_config.serialize_field("unused", &unused)?;
|
||||||
|
virtio_block_config.serialize_field("num_queues", &num_queues)?;
|
||||||
|
virtio_block_config.serialize_field("max_discard_sectors", &max_discard_sectors)?;
|
||||||
|
virtio_block_config.serialize_field("max_discard_seg", &max_discard_seg)?;
|
||||||
|
virtio_block_config
|
||||||
|
.serialize_field("discard_sector_alignment", &discard_sector_alignment)?;
|
||||||
|
virtio_block_config
|
||||||
|
.serialize_field("max_write_zeroes_sectors", &max_write_zeroes_sectors)?;
|
||||||
|
virtio_block_config.serialize_field("max_write_zeroes_seg", &max_write_zeroes_seg)?;
|
||||||
|
virtio_block_config.serialize_field("write_zeroes_may_unmap", &write_zeroes_may_unmap)?;
|
||||||
|
virtio_block_config.serialize_field("unused1", &unused1)?;
|
||||||
|
virtio_block_config.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl ByteValued for VirtioBlockConfig {}
|
unsafe impl ByteValued for VirtioBlockConfig {}
|
||||||
|
|
||||||
/// Virtio device for exposing block level read/write operations on a host file.
|
/// Virtio device for exposing block level read/write operations on a host file.
|
||||||
pub struct Block<T: DiskFile> {
|
pub struct Block<T: DiskFile> {
|
||||||
|
id: String,
|
||||||
kill_evt: Option<EventFd>,
|
kill_evt: Option<EventFd>,
|
||||||
disk_image: Arc<Mutex<T>>,
|
disk_image: Arc<Mutex<T>>,
|
||||||
disk_path: PathBuf,
|
disk_path: PathBuf,
|
||||||
@@ -817,6 +933,16 @@ pub struct Block<T: DiskFile> {
|
|||||||
pause_evt: Option<EventFd>,
|
pause_evt: Option<EventFd>,
|
||||||
paused: Arc<AtomicBool>,
|
paused: Arc<AtomicBool>,
|
||||||
queue_size: Vec<u16>,
|
queue_size: Vec<u16>,
|
||||||
|
writeback: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct BlockState {
|
||||||
|
pub disk_path: PathBuf,
|
||||||
|
pub disk_nsectors: u64,
|
||||||
|
pub avail_features: u64,
|
||||||
|
pub acked_features: u64,
|
||||||
|
pub config: VirtioBlockConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: DiskFile> Block<T> {
|
impl<T: DiskFile> Block<T> {
|
||||||
@@ -824,6 +950,7 @@ impl<T: DiskFile> Block<T> {
|
|||||||
///
|
///
|
||||||
/// The given file must be seekable and sizable.
|
/// The given file must be seekable and sizable.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
id: String,
|
||||||
mut disk_image: T,
|
mut disk_image: T,
|
||||||
disk_path: PathBuf,
|
disk_path: PathBuf,
|
||||||
is_disk_read_only: bool,
|
is_disk_read_only: bool,
|
||||||
@@ -840,7 +967,10 @@ impl<T: DiskFile> Block<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut avail_features = (1u64 << VIRTIO_F_VERSION_1) | (1u64 << VIRTIO_BLK_F_FLUSH);
|
let mut avail_features = (1u64 << VIRTIO_F_VERSION_1)
|
||||||
|
| (1u64 << VIRTIO_BLK_F_FLUSH)
|
||||||
|
| (1u64 << VIRTIO_RING_F_EVENT_IDX)
|
||||||
|
| (1u64 << VIRTIO_BLK_F_CONFIG_WCE);
|
||||||
|
|
||||||
if iommu {
|
if iommu {
|
||||||
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
avail_features |= 1u64 << VIRTIO_F_IOMMU_PLATFORM;
|
||||||
@@ -853,6 +983,7 @@ impl<T: DiskFile> Block<T> {
|
|||||||
let disk_nsectors = disk_size / SECTOR_SIZE;
|
let disk_nsectors = disk_size / SECTOR_SIZE;
|
||||||
let mut config = VirtioBlockConfig {
|
let mut config = VirtioBlockConfig {
|
||||||
capacity: disk_nsectors,
|
capacity: disk_nsectors,
|
||||||
|
writeback: 1,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -862,6 +993,7 @@ impl<T: DiskFile> Block<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(Block {
|
Ok(Block {
|
||||||
|
id,
|
||||||
kill_evt: None,
|
kill_evt: None,
|
||||||
disk_image: Arc::new(Mutex::new(disk_image)),
|
disk_image: Arc::new(Mutex::new(disk_image)),
|
||||||
disk_path,
|
disk_path,
|
||||||
@@ -875,8 +1007,50 @@ impl<T: DiskFile> Block<T> {
|
|||||||
pause_evt: None,
|
pause_evt: None,
|
||||||
paused: Arc::new(AtomicBool::new(false)),
|
paused: Arc::new(AtomicBool::new(false)),
|
||||||
queue_size: vec![queue_size; num_queues],
|
queue_size: vec![queue_size; num_queues],
|
||||||
|
writeback: Arc::new(AtomicBool::new(true)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> BlockState {
|
||||||
|
BlockState {
|
||||||
|
disk_path: self.disk_path.clone(),
|
||||||
|
disk_nsectors: self.disk_nsectors,
|
||||||
|
avail_features: self.avail_features,
|
||||||
|
acked_features: self.acked_features,
|
||||||
|
config: self.config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_state(&mut self, state: &BlockState) -> io::Result<()> {
|
||||||
|
self.disk_path = state.disk_path.clone();
|
||||||
|
self.disk_nsectors = state.disk_nsectors;
|
||||||
|
self.avail_features = state.avail_features;
|
||||||
|
self.acked_features = state.acked_features;
|
||||||
|
self.config = state.config;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_writeback(&mut self) {
|
||||||
|
// Use writeback from config if VIRTIO_BLK_F_CONFIG_WCE
|
||||||
|
let writeback =
|
||||||
|
if self.acked_features & 1 << VIRTIO_BLK_F_CONFIG_WCE == 1 << VIRTIO_BLK_F_CONFIG_WCE {
|
||||||
|
self.config.writeback == 1
|
||||||
|
} else {
|
||||||
|
// Else check if VIRTIO_BLK_F_FLUSH negotiated
|
||||||
|
self.acked_features & 1 << VIRTIO_BLK_F_FLUSH == 1 << VIRTIO_BLK_F_FLUSH
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Changing cache mode to {}",
|
||||||
|
if writeback {
|
||||||
|
"writeback"
|
||||||
|
} else {
|
||||||
|
"writethrough"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
self.writeback.store(writeback, Ordering::SeqCst);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: DiskFile> Drop for Block<T> {
|
impl<T: DiskFile> Drop for Block<T> {
|
||||||
@@ -938,6 +1112,7 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
|
|||||||
}
|
}
|
||||||
let (_, right) = config_slice.split_at_mut(offset as usize);
|
let (_, right) = config_slice.split_at_mut(offset as usize);
|
||||||
right.copy_from_slice(&data[..]);
|
right.copy_from_slice(&data[..]);
|
||||||
|
self.update_writeback();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(
|
||||||
@@ -997,6 +1172,10 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
|
|||||||
}
|
}
|
||||||
self.queue_evts = Some(tmp_queue_evts);
|
self.queue_evts = Some(tmp_queue_evts);
|
||||||
|
|
||||||
|
let event_idx = self.acked_features & 1u64 << VIRTIO_RING_F_EVENT_IDX
|
||||||
|
== 1u64 << VIRTIO_RING_F_EVENT_IDX;
|
||||||
|
self.update_writeback();
|
||||||
|
|
||||||
let mut epoll_threads = Vec::new();
|
let mut epoll_threads = Vec::new();
|
||||||
for _ in 0..self.queue_size.len() {
|
for _ in 0..self.queue_size.len() {
|
||||||
let mut handler = BlockEpollHandler {
|
let mut handler = BlockEpollHandler {
|
||||||
@@ -1008,8 +1187,12 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
|
|||||||
disk_image_id: disk_image_id.clone(),
|
disk_image_id: disk_image_id.clone(),
|
||||||
kill_evt: kill_evt.try_clone().unwrap(),
|
kill_evt: kill_evt.try_clone().unwrap(),
|
||||||
pause_evt: pause_evt.try_clone().unwrap(),
|
pause_evt: pause_evt.try_clone().unwrap(),
|
||||||
|
event_idx,
|
||||||
|
writeback: self.writeback.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handler.queue.set_event_idx(event_idx);
|
||||||
|
|
||||||
let queue_evt = queue_evts.remove(0);
|
let queue_evt = queue_evts.remove(0);
|
||||||
let paused = self.paused.clone();
|
let paused = self.paused.clone();
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
@@ -1051,5 +1234,45 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtio_pausable!(Block, T: 'static + DiskFile + Send);
|
virtio_pausable!(Block, T: 'static + DiskFile + Send);
|
||||||
impl<T: 'static + DiskFile + Send> Snapshotable for Block<T> {}
|
impl<T: 'static + DiskFile + Send> Snapshottable for Block<T> {
|
||||||
|
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 block_snapshot = Snapshot::new(self.id.as_str());
|
||||||
|
block_snapshot.add_data_section(SnapshotDataSection {
|
||||||
|
id: format!("{}-section", self.id),
|
||||||
|
snapshot,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(block_snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||||
|
if let Some(block_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||||
|
let block_state = match serde_json::from_slice(&block_section.snapshot) {
|
||||||
|
Ok(state) => state,
|
||||||
|
Err(error) => {
|
||||||
|
return Err(MigratableError::Restore(anyhow!(
|
||||||
|
"Could not deserialize BLOCK {}",
|
||||||
|
error
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return self.set_state(&block_state).map_err(|e| {
|
||||||
|
MigratableError::Restore(anyhow!("Could not restore BLOCK state {:?}", e))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(MigratableError::Restore(anyhow!(
|
||||||
|
"Could not find BLOCK snapshot section"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: 'static + DiskFile + Send> Transportable for Block<T> {}
|
||||||
impl<T: 'static + DiskFile + Send> Migratable for Block<T> {}
|
impl<T: 'static + DiskFile + Send> Migratable for Block<T> {}
|
||||||
|
|||||||
127
vm-virtio/src/console.rs
Executable file → Normal file
127
vm-virtio/src/console.rs
Executable file → Normal file
@@ -7,21 +7,25 @@ use super::{
|
|||||||
VirtioInterruptType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
VirtioInterruptType, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_VERSION_1,
|
||||||
};
|
};
|
||||||
use crate::VirtioInterrupt;
|
use crate::VirtioInterrupt;
|
||||||
use epoll;
|
use anyhow::anyhow;
|
||||||
use libc::EFD_NONBLOCK;
|
use libc::EFD_NONBLOCK;
|
||||||
use std;
|
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
|
||||||
use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
use vm_memory::{ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||||
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||||
|
Transportable,
|
||||||
|
};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
const QUEUE_SIZE: u16 = 256;
|
const QUEUE_SIZE: u16 = 256;
|
||||||
@@ -43,8 +47,8 @@ const PAUSE_EVENT: DeviceEventT = 5;
|
|||||||
//Console size feature bit
|
//Console size feature bit
|
||||||
const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
|
const VIRTIO_CONSOLE_F_SIZE: u64 = 0;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
#[derive(Copy, Clone, Debug, Default, Deserialize)]
|
||||||
#[repr(C)]
|
#[repr(C, packed)]
|
||||||
pub struct VirtioConsoleConfig {
|
pub struct VirtioConsoleConfig {
|
||||||
cols: u16,
|
cols: u16,
|
||||||
rows: u16,
|
rows: u16,
|
||||||
@@ -52,6 +56,30 @@ pub struct VirtioConsoleConfig {
|
|||||||
emerg_wr: u32,
|
emerg_wr: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We must explicitly implement Serialize since the structure is packed and
|
||||||
|
// it's unsafe to borrow from a packed structure. And by default, if we derive
|
||||||
|
// Serialize from serde, it will borrow the values from the structure.
|
||||||
|
// That's why this implementation copies each field separately before it
|
||||||
|
// serializes the entire structure field by field.
|
||||||
|
impl Serialize for VirtioConsoleConfig {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let cols = self.cols;
|
||||||
|
let rows = self.rows;
|
||||||
|
let max_nr_ports = self.max_nr_ports;
|
||||||
|
let emerg_wr = self.emerg_wr;
|
||||||
|
|
||||||
|
let mut virtio_console_config = serializer.serialize_struct("VirtioConsoleConfig", 12)?;
|
||||||
|
virtio_console_config.serialize_field("cols", &cols)?;
|
||||||
|
virtio_console_config.serialize_field("rows", &rows)?;
|
||||||
|
virtio_console_config.serialize_field("max_nr_ports", &max_nr_ports)?;
|
||||||
|
virtio_console_config.serialize_field("emerg_wr", &emerg_wr)?;
|
||||||
|
virtio_console_config.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Safe because it only has data and has no implicit padding.
|
// Safe because it only has data and has no implicit padding.
|
||||||
unsafe impl ByteValued for VirtioConsoleConfig {}
|
unsafe impl ByteValued for VirtioConsoleConfig {}
|
||||||
|
|
||||||
@@ -157,31 +185,33 @@ impl ConsoleEpollHandler {
|
|||||||
fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
|
fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
|
||||||
// Create the epoll file descriptor
|
// Create the epoll file descriptor
|
||||||
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||||
|
// Use 'File' to enforce closing on 'epoll_fd'
|
||||||
|
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
|
||||||
|
|
||||||
// Add events
|
// Add events
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.input_queue_evt.as_raw_fd(),
|
self.input_queue_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_QUEUE_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_QUEUE_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.output_queue_evt.as_raw_fd(),
|
self.output_queue_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(OUTPUT_QUEUE_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(OUTPUT_QUEUE_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.input_evt.as_raw_fd(),
|
self.input_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(INPUT_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.config_evt.as_raw_fd(),
|
self.config_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(CONFIG_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(CONFIG_EVENT)),
|
||||||
@@ -189,14 +219,14 @@ impl ConsoleEpollHandler {
|
|||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
|
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.kill_evt.as_raw_fd(),
|
self.kill_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.pause_evt.as_raw_fd(),
|
self.pause_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
|
||||||
@@ -207,7 +237,7 @@ impl ConsoleEpollHandler {
|
|||||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||||
|
|
||||||
'epoll: loop {
|
'epoll: loop {
|
||||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
let num_events = match epoll::wait(epoll_file.as_raw_fd(), -1, &mut events[..]) {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.kind() == io::ErrorKind::Interrupted {
|
if e.kind() == io::ErrorKind::Interrupted {
|
||||||
@@ -343,6 +373,7 @@ impl VirtioConsoleConfig {
|
|||||||
|
|
||||||
/// Virtio device for exposing console to the guest OS through virtio.
|
/// Virtio device for exposing console to the guest OS through virtio.
|
||||||
pub struct Console {
|
pub struct Console {
|
||||||
|
id: String,
|
||||||
kill_evt: Option<EventFd>,
|
kill_evt: Option<EventFd>,
|
||||||
pause_evt: Option<EventFd>,
|
pause_evt: Option<EventFd>,
|
||||||
avail_features: u64,
|
avail_features: u64,
|
||||||
@@ -356,9 +387,18 @@ pub struct Console {
|
|||||||
paused: Arc<AtomicBool>,
|
paused: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ConsoleState {
|
||||||
|
avail_features: u64,
|
||||||
|
acked_features: u64,
|
||||||
|
config: VirtioConsoleConfig,
|
||||||
|
in_buffer: VecDeque<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Console {
|
impl Console {
|
||||||
/// Create a new virtio console device that gets random data from /dev/urandom.
|
/// Create a new virtio console device that gets random data from /dev/urandom.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
id: String,
|
||||||
out: Box<dyn io::Write + Send + Sync + 'static>,
|
out: Box<dyn io::Write + Send + Sync + 'static>,
|
||||||
cols: u16,
|
cols: u16,
|
||||||
rows: u16,
|
rows: u16,
|
||||||
@@ -383,6 +423,7 @@ impl Console {
|
|||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Console {
|
Console {
|
||||||
|
id,
|
||||||
kill_evt: None,
|
kill_evt: None,
|
||||||
pause_evt: None,
|
pause_evt: None,
|
||||||
avail_features,
|
avail_features,
|
||||||
@@ -398,6 +439,24 @@ impl Console {
|
|||||||
console_input,
|
console_input,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> ConsoleState {
|
||||||
|
ConsoleState {
|
||||||
|
avail_features: self.avail_features,
|
||||||
|
acked_features: self.acked_features,
|
||||||
|
config: *(self.config.lock().unwrap()),
|
||||||
|
in_buffer: self.input.in_buffer.lock().unwrap().clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_state(&mut self, state: &ConsoleState) -> io::Result<()> {
|
||||||
|
self.avail_features = state.avail_features;
|
||||||
|
self.acked_features = state.acked_features;
|
||||||
|
*(self.config.lock().unwrap()) = state.config;
|
||||||
|
*(self.input.in_buffer.lock().unwrap()) = state.in_buffer.clone();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Console {
|
impl Drop for Console {
|
||||||
@@ -563,5 +622,45 @@ impl VirtioDevice for Console {
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtio_pausable!(Console);
|
virtio_pausable!(Console);
|
||||||
impl Snapshotable for Console {}
|
impl Snapshottable for Console {
|
||||||
|
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 console_snapshot = Snapshot::new(self.id.as_str());
|
||||||
|
console_snapshot.add_data_section(SnapshotDataSection {
|
||||||
|
id: format!("{}-section", self.id),
|
||||||
|
snapshot,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(console_snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||||
|
if let Some(console_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||||
|
let console_state = match serde_json::from_slice(&console_section.snapshot) {
|
||||||
|
Ok(state) => state,
|
||||||
|
Err(error) => {
|
||||||
|
return Err(MigratableError::Restore(anyhow!(
|
||||||
|
"Could not deserialize CONSOLE {}",
|
||||||
|
error
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return self.set_state(&console_state).map_err(|e| {
|
||||||
|
MigratableError::Restore(anyhow!("Could not restore CONSOLE state {:?}", e))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(MigratableError::Restore(anyhow!(
|
||||||
|
"Could not find CONSOLE snapshot section"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Transportable for Console {}
|
||||||
impl Migratable for Console {}
|
impl Migratable for Console {}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||||
|
|
||||||
use super::*;
|
use crate::{ActivateResult, Error, Queue};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap, GuestUsize};
|
use vm_memory::{GuestAddress, GuestMemoryAtomic, GuestMemoryMmap, GuestUsize};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
@@ -34,6 +34,15 @@ pub trait VirtioInterrupt: Send + Sync {
|
|||||||
pub type VirtioIommuRemapping =
|
pub type VirtioIommuRemapping =
|
||||||
Box<dyn Fn(u64) -> std::result::Result<u64, std::io::Error> + Send + Sync>;
|
Box<dyn Fn(u64) -> std::result::Result<u64, std::io::Error> + Send + Sync>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct UserspaceMapping {
|
||||||
|
pub host_addr: u64,
|
||||||
|
pub mem_slot: u32,
|
||||||
|
pub addr: GuestAddress,
|
||||||
|
pub len: GuestUsize,
|
||||||
|
pub mergeable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VirtioSharedMemory {
|
pub struct VirtioSharedMemory {
|
||||||
pub offset: u64,
|
pub offset: u64,
|
||||||
@@ -42,6 +51,8 @@ pub struct VirtioSharedMemory {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VirtioSharedMemoryList {
|
pub struct VirtioSharedMemoryList {
|
||||||
|
pub host_addr: u64,
|
||||||
|
pub mem_slot: u32,
|
||||||
pub addr: GuestAddress,
|
pub addr: GuestAddress,
|
||||||
pub len: GuestUsize,
|
pub len: GuestUsize,
|
||||||
pub region_list: Vec<VirtioSharedMemory>,
|
pub region_list: Vec<VirtioSharedMemory>,
|
||||||
@@ -97,6 +108,14 @@ pub trait VirtioDevice: Send {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates the list of shared memory regions required by the device.
|
||||||
|
fn set_shm_regions(
|
||||||
|
&mut self,
|
||||||
|
_shm_regions: VirtioSharedMemoryList,
|
||||||
|
) -> std::result::Result<(), Error> {
|
||||||
|
std::unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn iommu_translate(&self, addr: u64) -> u64 {
|
fn iommu_translate(&self, addr: u64) -> u64 {
|
||||||
addr
|
addr
|
||||||
}
|
}
|
||||||
@@ -106,6 +125,15 @@ pub trait VirtioDevice: Send {
|
|||||||
/// every device as part of shutting down the VM. Acting on the device
|
/// every device as part of shutting down the VM. Acting on the device
|
||||||
/// after a shutdown() can lead to unpredictable results.
|
/// after a shutdown() can lead to unpredictable results.
|
||||||
fn shutdown(&mut self) {}
|
fn shutdown(&mut self) {}
|
||||||
|
|
||||||
|
fn update_memory(&mut self, _mem: &GuestMemoryMmap) -> std::result::Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the list of userspace mappings associated with this device.
|
||||||
|
fn userspace_mappings(&self) -> Vec<UserspaceMapping> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait providing address translation the same way a physical DMA remapping
|
/// Trait providing address translation the same way a physical DMA remapping
|
||||||
|
|||||||
@@ -8,24 +8,29 @@ use super::{
|
|||||||
VirtioDeviceType, VIRTIO_F_VERSION_1,
|
VirtioDeviceType, VIRTIO_F_VERSION_1,
|
||||||
};
|
};
|
||||||
use crate::{DmaRemapping, VirtioInterrupt, VirtioInterruptType};
|
use crate::{DmaRemapping, VirtioInterrupt, VirtioInterruptType};
|
||||||
use epoll;
|
use anyhow::anyhow;
|
||||||
use libc::EFD_NONBLOCK;
|
use libc::EFD_NONBLOCK;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
use std::fs::File;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::ops::Bound::Included;
|
use std::ops::Bound::Included;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use vm_device::{ExternalDmaMapping, Migratable, MigratableError, Pausable, Snapshotable};
|
use vfio_ioctls::ExternalDmaMapping;
|
||||||
use vm_memory::{
|
use vm_memory::{
|
||||||
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic,
|
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic,
|
||||||
GuestMemoryError, GuestMemoryMmap,
|
GuestMemoryError, GuestMemoryMmap,
|
||||||
};
|
};
|
||||||
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||||
|
Transportable,
|
||||||
|
};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
/// Queues sizes
|
/// Queues sizes
|
||||||
@@ -646,31 +651,33 @@ impl IommuEpollHandler {
|
|||||||
fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
|
fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
|
||||||
// Create the epoll file descriptor
|
// Create the epoll file descriptor
|
||||||
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||||
|
// Use 'File' to enforce closing on 'epoll_fd'
|
||||||
|
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
|
||||||
|
|
||||||
// Add events
|
// Add events
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.queue_evts[0].as_raw_fd(),
|
self.queue_evts[0].as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(REQUEST_Q_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(REQUEST_Q_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.queue_evts[1].as_raw_fd(),
|
self.queue_evts[1].as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(EVENT_Q_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(EVENT_Q_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.kill_evt.as_raw_fd(),
|
self.kill_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.pause_evt.as_raw_fd(),
|
self.pause_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
|
||||||
@@ -681,7 +688,7 @@ impl IommuEpollHandler {
|
|||||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||||
|
|
||||||
'epoll: loop {
|
'epoll: loop {
|
||||||
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
|
let num_events = match epoll::wait(epoll_file.as_raw_fd(), -1, &mut events[..]) {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.kind() == io::ErrorKind::Interrupted {
|
if e.kind() == io::ErrorKind::Interrupted {
|
||||||
@@ -753,7 +760,7 @@ impl IommuEpollHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
struct Mapping {
|
struct Mapping {
|
||||||
gpa: u64,
|
gpa: u64,
|
||||||
size: u64,
|
size: u64,
|
||||||
@@ -792,6 +799,7 @@ impl DmaRemapping for IommuMapping {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Iommu {
|
pub struct Iommu {
|
||||||
|
id: String,
|
||||||
kill_evt: Option<EventFd>,
|
kill_evt: Option<EventFd>,
|
||||||
pause_evt: Option<EventFd>,
|
pause_evt: Option<EventFd>,
|
||||||
avail_features: u64,
|
avail_features: u64,
|
||||||
@@ -806,8 +814,16 @@ pub struct Iommu {
|
|||||||
paused: Arc<AtomicBool>,
|
paused: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct IommuState {
|
||||||
|
avail_features: u64,
|
||||||
|
acked_features: u64,
|
||||||
|
endpoints: BTreeMap<u32, u32>,
|
||||||
|
mappings: BTreeMap<u32, BTreeMap<u64, Mapping>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Iommu {
|
impl Iommu {
|
||||||
pub fn new() -> io::Result<(Self, Arc<IommuMapping>)> {
|
pub fn new(id: String) -> io::Result<(Self, Arc<IommuMapping>)> {
|
||||||
let config = VirtioIommuConfig {
|
let config = VirtioIommuConfig {
|
||||||
page_size_mask: VIRTIO_IOMMU_PAGE_SIZE_MASK,
|
page_size_mask: VIRTIO_IOMMU_PAGE_SIZE_MASK,
|
||||||
probe_size: PROBE_PROP_SIZE,
|
probe_size: PROBE_PROP_SIZE,
|
||||||
@@ -821,6 +837,7 @@ impl Iommu {
|
|||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Iommu {
|
Iommu {
|
||||||
|
id,
|
||||||
kill_evt: None,
|
kill_evt: None,
|
||||||
pause_evt: None,
|
pause_evt: None,
|
||||||
avail_features: 1u64 << VIRTIO_F_VERSION_1
|
avail_features: 1u64 << VIRTIO_F_VERSION_1
|
||||||
@@ -840,6 +857,24 @@ impl Iommu {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> IommuState {
|
||||||
|
IommuState {
|
||||||
|
avail_features: self.avail_features,
|
||||||
|
acked_features: self.acked_features,
|
||||||
|
endpoints: self.mapping.endpoints.read().unwrap().clone(),
|
||||||
|
mappings: self.mapping.mappings.read().unwrap().clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_state(&mut self, state: &IommuState) -> io::Result<()> {
|
||||||
|
self.avail_features = state.avail_features;
|
||||||
|
self.acked_features = state.acked_features;
|
||||||
|
*(self.mapping.endpoints.write().unwrap()) = state.endpoints.clone();
|
||||||
|
*(self.mapping.mappings.write().unwrap()) = state.mappings.clone();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// This function lets the caller specify a list of devices attached to the
|
// This function lets the caller specify a list of devices attached to the
|
||||||
// virtual IOMMU. This list is translated into a virtio-iommu configuration
|
// virtual IOMMU. This list is translated into a virtio-iommu configuration
|
||||||
// topology, so that it can be understood by the guest driver.
|
// topology, so that it can be understood by the guest driver.
|
||||||
@@ -1038,5 +1073,45 @@ impl VirtioDevice for Iommu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtio_pausable!(Iommu);
|
virtio_pausable!(Iommu);
|
||||||
impl Snapshotable for Iommu {}
|
impl Snapshottable for Iommu {
|
||||||
|
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 iommu_snapshot = Snapshot::new(self.id.as_str());
|
||||||
|
iommu_snapshot.add_data_section(SnapshotDataSection {
|
||||||
|
id: format!("{}-section", self.id),
|
||||||
|
snapshot,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(iommu_snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||||
|
if let Some(iommu_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||||
|
let iommu_state = match serde_json::from_slice(&iommu_section.snapshot) {
|
||||||
|
Ok(state) => state,
|
||||||
|
Err(error) => {
|
||||||
|
return Err(MigratableError::Restore(anyhow!(
|
||||||
|
"Could not deserialize IOMMU {}",
|
||||||
|
error
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return self.set_state(&iommu_state).map_err(|e| {
|
||||||
|
MigratableError::Restore(anyhow!("Could not restore IOMMU state {:?}", e))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(MigratableError::Restore(anyhow!(
|
||||||
|
"Could not find IOMMU snapshot section"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Transportable for Iommu {}
|
||||||
impl Migratable for Iommu {}
|
impl Migratable for Iommu {}
|
||||||
|
|||||||
20
vm-virtio/src/lib.rs
Executable file → Normal file
20
vm-virtio/src/lib.rs
Executable file → Normal file
@@ -16,6 +16,10 @@ extern crate epoll;
|
|||||||
extern crate log;
|
extern crate log;
|
||||||
#[cfg(feature = "pci_support")]
|
#[cfg(feature = "pci_support")]
|
||||||
extern crate pci;
|
extern crate pci;
|
||||||
|
extern crate serde;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
extern crate serde_json;
|
||||||
extern crate vhost_rs;
|
extern crate vhost_rs;
|
||||||
extern crate virtio_bindings;
|
extern crate virtio_bindings;
|
||||||
extern crate vm_device;
|
extern crate vm_device;
|
||||||
@@ -29,6 +33,7 @@ mod device;
|
|||||||
pub mod block;
|
pub mod block;
|
||||||
mod console;
|
mod console;
|
||||||
mod iommu;
|
mod iommu;
|
||||||
|
pub mod mem;
|
||||||
pub mod net;
|
pub mod net;
|
||||||
pub mod net_util;
|
pub mod net_util;
|
||||||
mod pmem;
|
mod pmem;
|
||||||
@@ -43,6 +48,7 @@ pub use self::block::*;
|
|||||||
pub use self::console::*;
|
pub use self::console::*;
|
||||||
pub use self::device::*;
|
pub use self::device::*;
|
||||||
pub use self::iommu::*;
|
pub use self::iommu::*;
|
||||||
|
pub use self::mem::*;
|
||||||
pub use self::net::*;
|
pub use self::net::*;
|
||||||
pub use self::net_util::*;
|
pub use self::net_util::*;
|
||||||
pub use self::pmem::*;
|
pub use self::pmem::*;
|
||||||
@@ -62,11 +68,11 @@ const VIRTIO_F_IOMMU_PLATFORM: u32 = 33;
|
|||||||
const VIRTIO_F_IN_ORDER: u32 = 35;
|
const VIRTIO_F_IN_ORDER: u32 = 35;
|
||||||
|
|
||||||
// Types taken from linux/virtio_ids.h
|
// Types taken from linux/virtio_ids.h
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
enum VirtioDeviceType {
|
pub enum VirtioDeviceType {
|
||||||
TYPE_NET = 1,
|
TYPE_NET = 1,
|
||||||
TYPE_BLOCK = 2,
|
TYPE_BLOCK = 2,
|
||||||
TYPE_CONSOLE = 3,
|
TYPE_CONSOLE = 3,
|
||||||
@@ -77,6 +83,7 @@ enum VirtioDeviceType {
|
|||||||
TYPE_INPUT = 18,
|
TYPE_INPUT = 18,
|
||||||
TYPE_VSOCK = 19,
|
TYPE_VSOCK = 19,
|
||||||
TYPE_IOMMU = 23,
|
TYPE_IOMMU = 23,
|
||||||
|
TYPE_MEM = 24,
|
||||||
TYPE_FS = 26,
|
TYPE_FS = 26,
|
||||||
TYPE_PMEM = 27,
|
TYPE_PMEM = 27,
|
||||||
TYPE_UNKNOWN = 0xFF,
|
TYPE_UNKNOWN = 0xFF,
|
||||||
@@ -95,6 +102,7 @@ impl From<u32> for VirtioDeviceType {
|
|||||||
18 => VirtioDeviceType::TYPE_INPUT,
|
18 => VirtioDeviceType::TYPE_INPUT,
|
||||||
19 => VirtioDeviceType::TYPE_VSOCK,
|
19 => VirtioDeviceType::TYPE_VSOCK,
|
||||||
23 => VirtioDeviceType::TYPE_IOMMU,
|
23 => VirtioDeviceType::TYPE_IOMMU,
|
||||||
|
24 => VirtioDeviceType::TYPE_MEM,
|
||||||
26 => VirtioDeviceType::TYPE_FS,
|
26 => VirtioDeviceType::TYPE_FS,
|
||||||
27 => VirtioDeviceType::TYPE_PMEM,
|
27 => VirtioDeviceType::TYPE_PMEM,
|
||||||
_ => VirtioDeviceType::TYPE_UNKNOWN,
|
_ => VirtioDeviceType::TYPE_UNKNOWN,
|
||||||
@@ -118,6 +126,7 @@ impl fmt::Display for VirtioDeviceType {
|
|||||||
VirtioDeviceType::TYPE_INPUT => "input",
|
VirtioDeviceType::TYPE_INPUT => "input",
|
||||||
VirtioDeviceType::TYPE_VSOCK => "vsock",
|
VirtioDeviceType::TYPE_VSOCK => "vsock",
|
||||||
VirtioDeviceType::TYPE_IOMMU => "iommu",
|
VirtioDeviceType::TYPE_IOMMU => "iommu",
|
||||||
|
VirtioDeviceType::TYPE_MEM => "mem",
|
||||||
VirtioDeviceType::TYPE_FS => "fs",
|
VirtioDeviceType::TYPE_FS => "fs",
|
||||||
VirtioDeviceType::TYPE_PMEM => "pmem",
|
VirtioDeviceType::TYPE_PMEM => "pmem",
|
||||||
VirtioDeviceType::TYPE_UNKNOWN => "UNKNOWN",
|
VirtioDeviceType::TYPE_UNKNOWN => "UNKNOWN",
|
||||||
@@ -171,8 +180,15 @@ pub enum Error {
|
|||||||
event: DeviceEventT,
|
event: DeviceEventT,
|
||||||
},
|
},
|
||||||
IoError(io::Error),
|
IoError(io::Error),
|
||||||
|
RegisterListener(io::Error),
|
||||||
|
UnregisterListener(io::Error),
|
||||||
EpollCreateFd(io::Error),
|
EpollCreateFd(io::Error),
|
||||||
EpollCtl(io::Error),
|
EpollCtl(io::Error),
|
||||||
EpollWait(io::Error),
|
EpollWait(io::Error),
|
||||||
FailedSignalingDriver(io::Error),
|
FailedSignalingDriver(io::Error),
|
||||||
|
VhostUserUpdateMemory(vhost_user::Error),
|
||||||
|
EventfdError(io::Error),
|
||||||
|
SetShmRegionsNotSupported,
|
||||||
|
EpollHander(String),
|
||||||
|
NoMemoryConfigured,
|
||||||
}
|
}
|
||||||
|
|||||||
983
vm-virtio/src/mem.rs
Normal file
983
vm-virtio/src/mem.rs
Normal file
@@ -0,0 +1,983 @@
|
|||||||
|
// Copyright (c) 2020 Ant Financial
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use super::Error as DeviceError;
|
||||||
|
use super::{
|
||||||
|
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
|
||||||
|
VirtioDeviceType, VIRTIO_F_VERSION_1,
|
||||||
|
};
|
||||||
|
use crate::{VirtioInterrupt, VirtioInterruptType};
|
||||||
|
use libc::EFD_NONBLOCK;
|
||||||
|
use std::cmp;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::mem::size_of;
|
||||||
|
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||||
|
use std::result;
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use vm_memory::{
|
||||||
|
Address, ByteValued, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic,
|
||||||
|
GuestMemoryError, GuestMemoryMmap, GuestMemoryRegion, GuestRegionMmap,
|
||||||
|
};
|
||||||
|
use vm_migration::{Migratable, MigratableError, Pausable, Snapshottable, Transportable};
|
||||||
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
const QUEUE_SIZE: u16 = 128;
|
||||||
|
const NUM_QUEUES: usize = 1;
|
||||||
|
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
|
||||||
|
|
||||||
|
// Use 2 MiB alignment so transparent hugepages can be used by KVM.
|
||||||
|
pub const VIRTIO_MEM_DEFAULT_BLOCK_SIZE: u64 = 512 * 4096;
|
||||||
|
const VIRTIO_MEM_USABLE_EXTENT: u64 = 256 * 1024 * 1024;
|
||||||
|
|
||||||
|
// Request processed successfully, applicable for
|
||||||
|
// - VIRTIO_MEM_REQ_PLUG
|
||||||
|
// - VIRTIO_MEM_REQ_UNPLUG
|
||||||
|
// - VIRTIO_MEM_REQ_UNPLUG_ALL
|
||||||
|
// - VIRTIO_MEM_REQ_STATE
|
||||||
|
const VIRTIO_MEM_RESP_ACK: u16 = 0;
|
||||||
|
|
||||||
|
// Request denied - e.g. trying to plug more than requested, applicable for
|
||||||
|
// - VIRTIO_MEM_REQ_PLUG
|
||||||
|
const VIRTIO_MEM_RESP_NACK: u16 = 1;
|
||||||
|
|
||||||
|
// Request cannot be processed right now, try again later, applicable for
|
||||||
|
// - VIRTIO_MEM_REQ_PLUG
|
||||||
|
// - VIRTIO_MEM_REQ_UNPLUG
|
||||||
|
// - VIRTIO_MEM_REQ_UNPLUG_ALL
|
||||||
|
// VIRTIO_MEM_RESP_BUSY: u16 = 2;
|
||||||
|
|
||||||
|
// Error in request (e.g. addresses/alignemnt), applicable for
|
||||||
|
// - VIRTIO_MEM_REQ_PLUG
|
||||||
|
// - VIRTIO_MEM_REQ_UNPLUG
|
||||||
|
// - VIRTIO_MEM_REQ_STATE
|
||||||
|
const VIRTIO_MEM_RESP_ERROR: u16 = 3;
|
||||||
|
|
||||||
|
// State of memory blocks is "plugged"
|
||||||
|
const VIRTIO_MEM_STATE_PLUGGED: u16 = 0;
|
||||||
|
// State of memory blocks is "unplugged"
|
||||||
|
const VIRTIO_MEM_STATE_UNPLUGGED: u16 = 1;
|
||||||
|
// State of memory blocks is "mixed"
|
||||||
|
const VIRTIO_MEM_STATE_MIXED: u16 = 2;
|
||||||
|
|
||||||
|
// request to plug memory blocks
|
||||||
|
const VIRTIO_MEM_REQ_PLUG: u16 = 0;
|
||||||
|
// request to unplug memory blocks
|
||||||
|
const VIRTIO_MEM_REQ_UNPLUG: u16 = 1;
|
||||||
|
// request to unplug all blocks and shrink the usable size
|
||||||
|
const VIRTIO_MEM_REQ_UNPLUG_ALL: u16 = 2;
|
||||||
|
// request information about the plugged state of memory blocks
|
||||||
|
const VIRTIO_MEM_REQ_STATE: u16 = 3;
|
||||||
|
|
||||||
|
// Get resize event.
|
||||||
|
const RESIZE_EVENT: DeviceEventT = 0;
|
||||||
|
// New descriptors are pending on the virtio queue.
|
||||||
|
const QUEUE_AVAIL_EVENT: DeviceEventT = 1;
|
||||||
|
// The device has been dropped.
|
||||||
|
const KILL_EVENT: DeviceEventT = 2;
|
||||||
|
// The device should be paused.
|
||||||
|
const PAUSE_EVENT: DeviceEventT = 3;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
// Guest gave us bad memory addresses.
|
||||||
|
GuestMemory(GuestMemoryError),
|
||||||
|
// Guest gave us a write only descriptor that protocol says to read from.
|
||||||
|
UnexpectedWriteOnlyDescriptor,
|
||||||
|
// Guest gave us a read only descriptor that protocol says to write to.
|
||||||
|
UnexpectedReadOnlyDescriptor,
|
||||||
|
// Guest gave us too few descriptors in a descriptor chain.
|
||||||
|
DescriptorChainTooShort,
|
||||||
|
// Guest gave us a buffer that was too short to use.
|
||||||
|
BufferLengthTooSmall,
|
||||||
|
// Guest sent us invalid request.
|
||||||
|
InvalidRequest,
|
||||||
|
// Failed to EventFd write.
|
||||||
|
EventFdWriteFail(std::io::Error),
|
||||||
|
// Failed to EventFd try_clone.
|
||||||
|
EventFdTryCloneFail(std::io::Error),
|
||||||
|
// Failed to MpscRecv.
|
||||||
|
MpscRecvFail(mpsc::RecvError),
|
||||||
|
// Resize invalid argument
|
||||||
|
ResizeInval(String),
|
||||||
|
// Fail to resize trigger
|
||||||
|
ResizeTriggerFail(DeviceError),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Got from qemu/include/standard-headers/linux/virtio_mem.h
|
||||||
|
// rust union doesn't support std::default::Default that
|
||||||
|
// need by mem.read_obj.
|
||||||
|
// Then move virtio_mem_req_plug, virtio_mem_req_unplug and
|
||||||
|
// virtio_mem_req_state to virtio_mem_req.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
struct VirtioMemReq {
|
||||||
|
req_type: u16,
|
||||||
|
padding: [u16; 3],
|
||||||
|
addr: u64,
|
||||||
|
nb_blocks: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe because it only has data and has no implicit padding.
|
||||||
|
unsafe impl ByteValued for VirtioMemReq {}
|
||||||
|
|
||||||
|
// Got from qemu/include/standard-headers/linux/virtio_mem.h
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
struct VirtioMemRespState {
|
||||||
|
state: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
struct VirtioMemResp {
|
||||||
|
resp_type: u16,
|
||||||
|
padding: [u16; 3],
|
||||||
|
|
||||||
|
state: VirtioMemRespState,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe because it only has data and has no implicit padding.
|
||||||
|
unsafe impl ByteValued for VirtioMemResp {}
|
||||||
|
|
||||||
|
// Got from qemu/include/standard-headers/linux/virtio_mem.h
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
struct VirtioMemConfig {
|
||||||
|
// Block size and alignment. Cannot change.
|
||||||
|
block_size: u32,
|
||||||
|
// Valid with VIRTIO_MEM_F_ACPI_PXM. Cannot change.
|
||||||
|
node_id: u16,
|
||||||
|
padding: u16,
|
||||||
|
// Start address of the memory region. Cannot change.
|
||||||
|
addr: u64,
|
||||||
|
// Region size (maximum). Cannot change.
|
||||||
|
region_size: u64,
|
||||||
|
// Currently usable region size. Can grow up to region_size. Can
|
||||||
|
// shrink due to VIRTIO_MEM_REQ_UNPLUG_ALL (in which case no config
|
||||||
|
// update will be sent).
|
||||||
|
usable_region_size: u64,
|
||||||
|
// Currently used size. Changes due to plug/unplug requests, but no
|
||||||
|
// config updates will be sent.
|
||||||
|
plugged_size: u64,
|
||||||
|
// Requested size. New plug requests cannot exceed it. Can change.
|
||||||
|
requested_size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe because it only has data and has no implicit padding.
|
||||||
|
unsafe impl ByteValued for VirtioMemConfig {}
|
||||||
|
|
||||||
|
struct Request {
|
||||||
|
req: VirtioMemReq,
|
||||||
|
status_addr: GuestAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request {
|
||||||
|
fn parse(
|
||||||
|
avail_desc: &DescriptorChain,
|
||||||
|
mem: &GuestMemoryMmap,
|
||||||
|
) -> result::Result<Request, Error> {
|
||||||
|
// The head contains the request type which MUST be readable.
|
||||||
|
if avail_desc.is_write_only() {
|
||||||
|
return Err(Error::UnexpectedWriteOnlyDescriptor);
|
||||||
|
}
|
||||||
|
if avail_desc.len as usize != size_of::<VirtioMemReq>() {
|
||||||
|
return Err(Error::InvalidRequest);
|
||||||
|
}
|
||||||
|
let req: VirtioMemReq = mem.read_obj(avail_desc.addr).map_err(Error::GuestMemory)?;
|
||||||
|
|
||||||
|
let status_desc = avail_desc
|
||||||
|
.next_descriptor()
|
||||||
|
.ok_or(Error::DescriptorChainTooShort)?;
|
||||||
|
|
||||||
|
// The status MUST always be writable
|
||||||
|
if !status_desc.is_write_only() {
|
||||||
|
return Err(Error::UnexpectedReadOnlyDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status_desc.len as usize) < size_of::<VirtioMemResp>() {
|
||||||
|
return Err(Error::BufferLengthTooSmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Request {
|
||||||
|
req,
|
||||||
|
status_addr: status_desc.addr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Resize {
|
||||||
|
size: Arc<AtomicU64>,
|
||||||
|
tx: mpsc::Sender<Result<(), Error>>,
|
||||||
|
rx: Option<mpsc::Receiver<Result<(), Error>>>,
|
||||||
|
evt: EventFd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resize {
|
||||||
|
pub fn new() -> io::Result<Self> {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
Ok(Resize {
|
||||||
|
size: Arc::new(AtomicU64::new(0)),
|
||||||
|
tx,
|
||||||
|
rx: Some(rx),
|
||||||
|
evt: EventFd::new(EFD_NONBLOCK)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_clone(&self) -> Result<Self, Error> {
|
||||||
|
Ok(Resize {
|
||||||
|
size: self.size.clone(),
|
||||||
|
tx: self.tx.clone(),
|
||||||
|
rx: None,
|
||||||
|
evt: self.evt.try_clone().map_err(Error::EventFdTryCloneFail)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn work(&self, size: u64) -> Result<(), Error> {
|
||||||
|
if let Some(rx) = &self.rx {
|
||||||
|
self.size.store(size, Ordering::SeqCst);
|
||||||
|
self.evt.write(1).map_err(Error::EventFdWriteFail)?;
|
||||||
|
rx.recv().map_err(Error::MpscRecvFail)?
|
||||||
|
} else {
|
||||||
|
panic!("work should not work with cloned resize")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_size(&self) -> u64 {
|
||||||
|
self.size.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send(&self, r: Result<(), Error>) -> Result<(), mpsc::SendError<Result<(), Error>>> {
|
||||||
|
self.tx.send(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MemEpollHandler {
|
||||||
|
host_addr: u64,
|
||||||
|
host_fd: Option<RawFd>,
|
||||||
|
mem_state: Vec<bool>,
|
||||||
|
config: Arc<Mutex<VirtioMemConfig>>,
|
||||||
|
resize: Resize,
|
||||||
|
queue: Queue,
|
||||||
|
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||||
|
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||||
|
queue_evt: EventFd,
|
||||||
|
kill_evt: EventFd,
|
||||||
|
pause_evt: EventFd,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StateChangeRequest<'a> {
|
||||||
|
config: VirtioMemConfig,
|
||||||
|
addr: u64,
|
||||||
|
size: u64,
|
||||||
|
nb_blocks: u16,
|
||||||
|
mem_state: &'a mut Vec<bool>,
|
||||||
|
host_addr: u64,
|
||||||
|
host_fd: Option<RawFd>,
|
||||||
|
plug: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemEpollHandler {
|
||||||
|
fn virtio_mem_valid_range(config: &VirtioMemConfig, addr: u64, size: u64) -> bool {
|
||||||
|
// address properly aligned?
|
||||||
|
if addr % config.block_size as u64 != 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reasonable size
|
||||||
|
if addr + size <= addr || size == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start address in usable range?
|
||||||
|
if addr < config.addr || addr >= config.addr + config.usable_region_size {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// end address in usable range?
|
||||||
|
if addr + size > config.addr + config.usable_region_size {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn virtio_mem_check_bitmap(
|
||||||
|
bit_index: usize,
|
||||||
|
nb_blocks: u16,
|
||||||
|
mem_state: &[bool],
|
||||||
|
plug: bool,
|
||||||
|
) -> bool {
|
||||||
|
for state in mem_state.iter().skip(bit_index).take(nb_blocks as usize) {
|
||||||
|
if *state != plug {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn virtio_mem_set_bitmap(
|
||||||
|
bit_index: usize,
|
||||||
|
nb_blocks: u16,
|
||||||
|
mem_state: &mut Vec<bool>,
|
||||||
|
plug: bool,
|
||||||
|
) {
|
||||||
|
for state in mem_state
|
||||||
|
.iter_mut()
|
||||||
|
.skip(bit_index)
|
||||||
|
.take(nb_blocks as usize)
|
||||||
|
{
|
||||||
|
*state = plug;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn virtio_mem_state_change_request(r: StateChangeRequest) -> u16 {
|
||||||
|
if r.plug && (r.config.plugged_size + r.size > r.config.requested_size) {
|
||||||
|
return VIRTIO_MEM_RESP_NACK;
|
||||||
|
}
|
||||||
|
if !MemEpollHandler::virtio_mem_valid_range(&r.config, r.addr, r.size) {
|
||||||
|
return VIRTIO_MEM_RESP_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = r.addr - r.config.addr;
|
||||||
|
|
||||||
|
let bit_index = (offset / r.config.block_size as u64) as usize;
|
||||||
|
if !MemEpollHandler::virtio_mem_check_bitmap(bit_index, r.nb_blocks, r.mem_state, !r.plug) {
|
||||||
|
return VIRTIO_MEM_RESP_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.plug {
|
||||||
|
if let Some(fd) = r.host_fd {
|
||||||
|
let res = unsafe {
|
||||||
|
libc::fallocate64(
|
||||||
|
fd,
|
||||||
|
libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE,
|
||||||
|
offset as libc::off64_t,
|
||||||
|
r.size as libc::off64_t,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if res != 0 {
|
||||||
|
error!("fallocate64 get error {}", io::Error::last_os_error());
|
||||||
|
return VIRTIO_MEM_RESP_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let res = unsafe {
|
||||||
|
libc::madvise(
|
||||||
|
(r.host_addr + offset) as *mut libc::c_void,
|
||||||
|
r.size as libc::size_t,
|
||||||
|
libc::MADV_DONTNEED,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if res != 0 {
|
||||||
|
error!("madvise get error {}", io::Error::last_os_error());
|
||||||
|
return VIRTIO_MEM_RESP_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MemEpollHandler::virtio_mem_set_bitmap(bit_index, r.nb_blocks, r.mem_state, r.plug);
|
||||||
|
|
||||||
|
VIRTIO_MEM_RESP_ACK
|
||||||
|
}
|
||||||
|
|
||||||
|
fn virtio_mem_unplug_all(
|
||||||
|
config: VirtioMemConfig,
|
||||||
|
mem_state: &mut Vec<bool>,
|
||||||
|
host_addr: u64,
|
||||||
|
host_fd: Option<RawFd>,
|
||||||
|
) -> u16 {
|
||||||
|
for x in 0..(config.region_size / config.block_size as u64) as usize {
|
||||||
|
if mem_state[x] {
|
||||||
|
let resp_type =
|
||||||
|
MemEpollHandler::virtio_mem_state_change_request(StateChangeRequest {
|
||||||
|
config,
|
||||||
|
addr: config.addr + x as u64 * config.block_size as u64,
|
||||||
|
size: config.block_size as u64,
|
||||||
|
nb_blocks: 1,
|
||||||
|
mem_state,
|
||||||
|
host_addr,
|
||||||
|
host_fd,
|
||||||
|
plug: false,
|
||||||
|
});
|
||||||
|
if resp_type != VIRTIO_MEM_RESP_ACK {
|
||||||
|
return resp_type;
|
||||||
|
}
|
||||||
|
mem_state[x] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VIRTIO_MEM_RESP_ACK
|
||||||
|
}
|
||||||
|
|
||||||
|
fn virtio_mem_state_request(
|
||||||
|
config: VirtioMemConfig,
|
||||||
|
addr: u64,
|
||||||
|
nb_blocks: u16,
|
||||||
|
mem_state: &mut Vec<bool>,
|
||||||
|
) -> (u16, u16) {
|
||||||
|
let size: u64 = nb_blocks as u64 * config.block_size as u64;
|
||||||
|
let resp_type = if MemEpollHandler::virtio_mem_valid_range(&config, addr, size) {
|
||||||
|
VIRTIO_MEM_RESP_ACK
|
||||||
|
} else {
|
||||||
|
VIRTIO_MEM_RESP_ERROR
|
||||||
|
};
|
||||||
|
|
||||||
|
let offset = addr - config.addr;
|
||||||
|
let bit_index = (offset / config.block_size as u64) as usize;
|
||||||
|
let resp_state =
|
||||||
|
if MemEpollHandler::virtio_mem_check_bitmap(bit_index, nb_blocks, mem_state, true) {
|
||||||
|
VIRTIO_MEM_STATE_PLUGGED
|
||||||
|
} else if MemEpollHandler::virtio_mem_check_bitmap(
|
||||||
|
bit_index, nb_blocks, mem_state, false,
|
||||||
|
) {
|
||||||
|
VIRTIO_MEM_STATE_UNPLUGGED
|
||||||
|
} else {
|
||||||
|
VIRTIO_MEM_STATE_MIXED
|
||||||
|
};
|
||||||
|
|
||||||
|
(resp_type, resp_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn virtio_mem_send_response(
|
||||||
|
mem: &GuestMemoryMmap,
|
||||||
|
resp_type: u16,
|
||||||
|
resp_state: u16,
|
||||||
|
status_addr: GuestAddress,
|
||||||
|
) -> u32 {
|
||||||
|
let mut resp = VirtioMemResp::default();
|
||||||
|
resp.resp_type = resp_type;
|
||||||
|
resp.state.state = resp_state;
|
||||||
|
match mem.write_obj(resp, status_addr) {
|
||||||
|
Ok(_) => size_of::<VirtioMemResp>() as u32,
|
||||||
|
Err(e) => {
|
||||||
|
error!("bad guest memory address: {}", e);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signal(&self, int_type: &VirtioInterruptType) -> result::Result<(), DeviceError> {
|
||||||
|
self.interrupt_cb
|
||||||
|
.trigger(int_type, Some(&self.queue))
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to signal used queue: {:?}", e);
|
||||||
|
DeviceError::FailedSignalingUsedQueue(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_queue(&mut self) -> bool {
|
||||||
|
let mut used_desc_heads = [(0, 0); QUEUE_SIZE as usize];
|
||||||
|
let mut used_count = 0;
|
||||||
|
let mem = self.mem.memory();
|
||||||
|
for avail_desc in self.queue.iter(&mem) {
|
||||||
|
let len = match Request::parse(&avail_desc, &mem) {
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed parse VirtioMemReq: {:?}", e);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
Ok(r) => {
|
||||||
|
let mut config = self.config.lock().unwrap();
|
||||||
|
match r.req.req_type {
|
||||||
|
VIRTIO_MEM_REQ_PLUG => {
|
||||||
|
let size: u64 = r.req.nb_blocks as u64 * config.block_size as u64;
|
||||||
|
let resp_type = MemEpollHandler::virtio_mem_state_change_request(
|
||||||
|
StateChangeRequest {
|
||||||
|
config: *config,
|
||||||
|
addr: r.req.addr,
|
||||||
|
size,
|
||||||
|
nb_blocks: r.req.nb_blocks,
|
||||||
|
mem_state: &mut self.mem_state,
|
||||||
|
host_addr: self.host_addr,
|
||||||
|
host_fd: self.host_fd,
|
||||||
|
plug: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if resp_type == VIRTIO_MEM_RESP_ACK {
|
||||||
|
config.plugged_size += size;
|
||||||
|
}
|
||||||
|
MemEpollHandler::virtio_mem_send_response(
|
||||||
|
&mem,
|
||||||
|
resp_type,
|
||||||
|
0u16,
|
||||||
|
r.status_addr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
VIRTIO_MEM_REQ_UNPLUG => {
|
||||||
|
let size: u64 = r.req.nb_blocks as u64 * config.block_size as u64;
|
||||||
|
let resp_type = MemEpollHandler::virtio_mem_state_change_request(
|
||||||
|
StateChangeRequest {
|
||||||
|
config: *config,
|
||||||
|
addr: r.req.addr,
|
||||||
|
size,
|
||||||
|
nb_blocks: r.req.nb_blocks,
|
||||||
|
mem_state: &mut self.mem_state,
|
||||||
|
host_addr: self.host_addr,
|
||||||
|
host_fd: self.host_fd,
|
||||||
|
plug: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if resp_type == VIRTIO_MEM_RESP_ACK {
|
||||||
|
config.plugged_size -= size;
|
||||||
|
}
|
||||||
|
MemEpollHandler::virtio_mem_send_response(
|
||||||
|
&mem,
|
||||||
|
resp_type,
|
||||||
|
0u16,
|
||||||
|
r.status_addr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
VIRTIO_MEM_REQ_UNPLUG_ALL => {
|
||||||
|
let resp_type = MemEpollHandler::virtio_mem_unplug_all(
|
||||||
|
*config,
|
||||||
|
&mut self.mem_state,
|
||||||
|
self.host_addr,
|
||||||
|
self.host_fd,
|
||||||
|
);
|
||||||
|
if resp_type == VIRTIO_MEM_RESP_ACK {
|
||||||
|
config.plugged_size = 0;
|
||||||
|
config.usable_region_size = cmp::min(
|
||||||
|
config.region_size,
|
||||||
|
config.requested_size + VIRTIO_MEM_USABLE_EXTENT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
MemEpollHandler::virtio_mem_send_response(
|
||||||
|
&mem,
|
||||||
|
resp_type,
|
||||||
|
0u16,
|
||||||
|
r.status_addr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
VIRTIO_MEM_REQ_STATE => {
|
||||||
|
let (resp_type, resp_state) = MemEpollHandler::virtio_mem_state_request(
|
||||||
|
*config,
|
||||||
|
r.req.addr,
|
||||||
|
r.req.nb_blocks,
|
||||||
|
&mut self.mem_state,
|
||||||
|
);
|
||||||
|
MemEpollHandler::virtio_mem_send_response(
|
||||||
|
&mem,
|
||||||
|
resp_type,
|
||||||
|
resp_state,
|
||||||
|
r.status_addr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("VirtioMemReq unknown request type {:?}", r.req.req_type);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
used_desc_heads[used_count] = (avail_desc.index, len);
|
||||||
|
used_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for &(desc_index, len) in &used_desc_heads[..used_count] {
|
||||||
|
self.queue.add_used(&mem, desc_index, len);
|
||||||
|
}
|
||||||
|
used_count > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self, paused: Arc<AtomicBool>) -> result::Result<(), DeviceError> {
|
||||||
|
// Create the epoll file descriptor
|
||||||
|
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||||
|
// Use 'File' to enforce closing on 'epoll_fd'
|
||||||
|
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
|
||||||
|
|
||||||
|
// Add events
|
||||||
|
epoll::ctl(
|
||||||
|
epoll_file.as_raw_fd(),
|
||||||
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
|
self.resize.evt.as_raw_fd(),
|
||||||
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RESIZE_EVENT)),
|
||||||
|
)
|
||||||
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
|
|
||||||
|
epoll::ctl(
|
||||||
|
epoll_file.as_raw_fd(),
|
||||||
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
|
self.queue_evt.as_raw_fd(),
|
||||||
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(QUEUE_AVAIL_EVENT)),
|
||||||
|
)
|
||||||
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
|
|
||||||
|
epoll::ctl(
|
||||||
|
epoll_file.as_raw_fd(),
|
||||||
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
|
self.kill_evt.as_raw_fd(),
|
||||||
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
||||||
|
)
|
||||||
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
|
|
||||||
|
epoll::ctl(
|
||||||
|
epoll_file.as_raw_fd(),
|
||||||
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
|
self.pause_evt.as_raw_fd(),
|
||||||
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
|
||||||
|
)
|
||||||
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
|
|
||||||
|
const EPOLL_EVENTS_LEN: usize = 100;
|
||||||
|
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
||||||
|
|
||||||
|
'epoll: loop {
|
||||||
|
let num_events = match epoll::wait(epoll_file.as_raw_fd(), -1, &mut events[..]) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() == io::ErrorKind::Interrupted {
|
||||||
|
// It's well defined from the epoll_wait() syscall
|
||||||
|
// documentation that the epoll loop can be interrupted
|
||||||
|
// before any of the requested events occurred or the
|
||||||
|
// timeout expired. In both those cases, epoll_wait()
|
||||||
|
// returns an error of type EINTR, but this should not
|
||||||
|
// be considered as a regular error. Instead it is more
|
||||||
|
// appropriate to retry, by calling into epoll_wait().
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Err(DeviceError::EpollWait(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for event in events.iter().take(num_events) {
|
||||||
|
let ev_type = event.data as u16;
|
||||||
|
|
||||||
|
match ev_type {
|
||||||
|
RESIZE_EVENT => {
|
||||||
|
if let Err(e) = self.resize.evt.read() {
|
||||||
|
return Err(DeviceError::EpollHander(format!(
|
||||||
|
"Failed to get resize event: {:?}",
|
||||||
|
e
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
let size = self.resize.get_size();
|
||||||
|
let mut config = self.config.lock().unwrap();
|
||||||
|
let mut signal_error = false;
|
||||||
|
let r = if config.requested_size == size {
|
||||||
|
Err(Error::ResizeInval(format!("Virtio-mem resize {} is same with current config.requested_size", size)))
|
||||||
|
} else if size > config.region_size {
|
||||||
|
let region_size = config.region_size;
|
||||||
|
Err(Error::ResizeInval(format!(
|
||||||
|
"Virtio-mem resize {} is bigger than config.region_size {}",
|
||||||
|
size, region_size
|
||||||
|
)))
|
||||||
|
} else if size % (config.block_size as u64) != 0 {
|
||||||
|
let block_size = config.block_size;
|
||||||
|
Err(Error::ResizeInval(format!(
|
||||||
|
"Virtio-mem resize {} is not aligned with config.block_size {}",
|
||||||
|
size, block_size
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
config.requested_size = size;
|
||||||
|
let tmp_size = cmp::min(
|
||||||
|
config.region_size,
|
||||||
|
config.requested_size + VIRTIO_MEM_USABLE_EXTENT,
|
||||||
|
);
|
||||||
|
config.usable_region_size =
|
||||||
|
cmp::max(config.usable_region_size, tmp_size);
|
||||||
|
if let Err(e) = self.signal(&VirtioInterruptType::Config) {
|
||||||
|
signal_error = true;
|
||||||
|
Err(Error::ResizeTriggerFail(e))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(e) = &r {
|
||||||
|
// This error will send back to resize caller.
|
||||||
|
error!("Handle resize event get error: {:?}", e);
|
||||||
|
}
|
||||||
|
if let Err(e) = self.resize.send(r) {
|
||||||
|
return Err(DeviceError::EpollHander(format!(
|
||||||
|
"Sending \"resize\" generated error: {:?}",
|
||||||
|
e
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if signal_error {
|
||||||
|
return Err(DeviceError::EpollHander(String::from(
|
||||||
|
"Signal get error",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QUEUE_AVAIL_EVENT => {
|
||||||
|
if let Err(e) = self.queue_evt.read() {
|
||||||
|
return Err(DeviceError::EpollHander(format!(
|
||||||
|
"Failed to get queue event: {:?}",
|
||||||
|
e
|
||||||
|
)));
|
||||||
|
} else if self.process_queue() {
|
||||||
|
if let Err(e) = self.signal(&VirtioInterruptType::Queue) {
|
||||||
|
return Err(DeviceError::EpollHander(format!(
|
||||||
|
"Failed to signal used queue: {:?}",
|
||||||
|
e
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KILL_EVENT => {
|
||||||
|
debug!("kill_evt received, stopping epoll loop");
|
||||||
|
break 'epoll;
|
||||||
|
}
|
||||||
|
PAUSE_EVENT => {
|
||||||
|
debug!("PAUSE_EVENT received, pausing virtio-pmem epoll loop");
|
||||||
|
// We loop here to handle spurious park() returns.
|
||||||
|
// Until we have not resumed, the paused boolean will
|
||||||
|
// be true.
|
||||||
|
while paused.load(Ordering::SeqCst) {
|
||||||
|
thread::park();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(DeviceError::EpollHander(String::from(
|
||||||
|
"Unknown event for virtio-mem",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Virtio device for exposing entropy to the guest OS through virtio.
|
||||||
|
pub struct Mem {
|
||||||
|
id: String,
|
||||||
|
resize: Resize,
|
||||||
|
kill_evt: Option<EventFd>,
|
||||||
|
pause_evt: Option<EventFd>,
|
||||||
|
avail_features: u64,
|
||||||
|
pub acked_features: u64,
|
||||||
|
host_addr: u64,
|
||||||
|
host_fd: Option<RawFd>,
|
||||||
|
config: Arc<Mutex<VirtioMemConfig>>,
|
||||||
|
queue_evts: Option<Vec<EventFd>>,
|
||||||
|
interrupt_cb: Option<Arc<dyn VirtioInterrupt>>,
|
||||||
|
epoll_threads: Option<Vec<thread::JoinHandle<result::Result<(), DeviceError>>>>,
|
||||||
|
paused: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mem {
|
||||||
|
// Create a new virtio-mem device.
|
||||||
|
pub fn new(id: String, region: &Arc<GuestRegionMmap>, resize: Resize) -> io::Result<Mem> {
|
||||||
|
let region_len = region.len();
|
||||||
|
|
||||||
|
if region_len != region_len / VIRTIO_MEM_DEFAULT_BLOCK_SIZE * VIRTIO_MEM_DEFAULT_BLOCK_SIZE
|
||||||
|
{
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
format!(
|
||||||
|
"Virtio-mem size is not aligned with {}",
|
||||||
|
VIRTIO_MEM_DEFAULT_BLOCK_SIZE
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixme: Not support VIRTIO_MEM_F_ACPI_PXM
|
||||||
|
let avail_features = 1u64 << VIRTIO_F_VERSION_1;
|
||||||
|
|
||||||
|
let mut config = VirtioMemConfig::default();
|
||||||
|
config.block_size = VIRTIO_MEM_DEFAULT_BLOCK_SIZE as u32;
|
||||||
|
config.addr = region.start_addr().raw_value();
|
||||||
|
config.region_size = region.len();
|
||||||
|
config.usable_region_size = cmp::min(
|
||||||
|
config.region_size,
|
||||||
|
config.requested_size + VIRTIO_MEM_USABLE_EXTENT,
|
||||||
|
);
|
||||||
|
|
||||||
|
let host_fd = if let Some(f_offset) = region.file_offset() {
|
||||||
|
Some(f_offset.file().as_raw_fd())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Mem {
|
||||||
|
id,
|
||||||
|
resize,
|
||||||
|
kill_evt: None,
|
||||||
|
pause_evt: None,
|
||||||
|
avail_features,
|
||||||
|
acked_features: 0u64,
|
||||||
|
host_addr: region.as_ptr() as u64,
|
||||||
|
host_fd,
|
||||||
|
config: Arc::new(Mutex::new(config)),
|
||||||
|
queue_evts: None,
|
||||||
|
interrupt_cb: None,
|
||||||
|
epoll_threads: None,
|
||||||
|
paused: Arc::new(AtomicBool::new(false)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Mem {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(kill_evt) = self.kill_evt.take() {
|
||||||
|
// Ignore the result because there is nothing we can do about it.
|
||||||
|
let _ = kill_evt.write(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtioDevice for Mem {
|
||||||
|
fn device_type(&self) -> u32 {
|
||||||
|
VirtioDeviceType::TYPE_MEM as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_max_sizes(&self) -> &[u16] {
|
||||||
|
QUEUE_SIZES
|
||||||
|
}
|
||||||
|
|
||||||
|
fn features(&self) -> u64 {
|
||||||
|
self.avail_features
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ack_features(&mut self, value: u64) {
|
||||||
|
let mut v = value;
|
||||||
|
// Check if the guest is ACK'ing a feature that we didn't claim to have.
|
||||||
|
let unrequested_features = v & !self.avail_features;
|
||||||
|
if unrequested_features != 0 {
|
||||||
|
warn!("Received acknowledge request for unknown feature.");
|
||||||
|
|
||||||
|
// Don't count these features as acked.
|
||||||
|
v &= !unrequested_features;
|
||||||
|
}
|
||||||
|
self.acked_features |= v;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_config(&self, offset: u64, mut data: &mut [u8]) {
|
||||||
|
let config = self.config.lock().unwrap();
|
||||||
|
let config_slice = config.as_slice();
|
||||||
|
let config_len = config_slice.len() as u64;
|
||||||
|
if offset >= config_len {
|
||||||
|
error!("Failed to read config space");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(end) = offset.checked_add(data.len() as u64) {
|
||||||
|
// This write can't fail, offset and end are checked against config_len.
|
||||||
|
data.write_all(&config_slice[offset as usize..cmp::min(end, config_len) as usize])
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_config(&mut self, _offset: u64, _data: &[u8]) {
|
||||||
|
warn!("virtio-mem device configuration is read-only");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate(
|
||||||
|
&mut self,
|
||||||
|
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
||||||
|
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||||
|
mut queues: Vec<Queue>,
|
||||||
|
mut queue_evts: Vec<EventFd>,
|
||||||
|
) -> ActivateResult {
|
||||||
|
if queues.len() != NUM_QUEUES || queue_evts.len() != NUM_QUEUES {
|
||||||
|
error!(
|
||||||
|
"Cannot perform activate. Expected {} queue(s), got {}",
|
||||||
|
NUM_QUEUES,
|
||||||
|
queues.len()
|
||||||
|
);
|
||||||
|
return Err(ActivateError::BadActivate);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (self_kill_evt, kill_evt) = EventFd::new(EFD_NONBLOCK)
|
||||||
|
.and_then(|e| Ok((e.try_clone()?, e)))
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("failed creating kill EventFd pair: {}", e);
|
||||||
|
ActivateError::BadActivate
|
||||||
|
})?;
|
||||||
|
self.kill_evt = Some(self_kill_evt);
|
||||||
|
|
||||||
|
let (self_pause_evt, pause_evt) = EventFd::new(EFD_NONBLOCK)
|
||||||
|
.and_then(|e| Ok((e.try_clone()?, e)))
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("failed creating pause EventFd pair: {}", e);
|
||||||
|
ActivateError::BadActivate
|
||||||
|
})?;
|
||||||
|
self.pause_evt = Some(self_pause_evt);
|
||||||
|
|
||||||
|
self.interrupt_cb = Some(interrupt_cb.clone());
|
||||||
|
|
||||||
|
let mut tmp_queue_evts: Vec<EventFd> = Vec::new();
|
||||||
|
for queue_evt in queue_evts.iter() {
|
||||||
|
// Save the queue EventFD as we need to return it on reset
|
||||||
|
// but clone it to pass into the thread.
|
||||||
|
tmp_queue_evts.push(queue_evt.try_clone().map_err(|e| {
|
||||||
|
error!("failed to clone queue EventFd: {}", e);
|
||||||
|
ActivateError::BadActivate
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
self.queue_evts = Some(tmp_queue_evts);
|
||||||
|
|
||||||
|
let config = self.config.lock().unwrap();
|
||||||
|
let mut handler = MemEpollHandler {
|
||||||
|
host_addr: self.host_addr,
|
||||||
|
host_fd: self.host_fd,
|
||||||
|
mem_state: vec![false; config.region_size as usize / config.block_size as usize],
|
||||||
|
config: self.config.clone(),
|
||||||
|
resize: self.resize.try_clone().map_err(|e| {
|
||||||
|
error!("failed to clone resize EventFd: {:?}", e);
|
||||||
|
ActivateError::BadActivate
|
||||||
|
})?,
|
||||||
|
queue: queues.remove(0),
|
||||||
|
mem,
|
||||||
|
interrupt_cb,
|
||||||
|
queue_evt: queue_evts.remove(0),
|
||||||
|
kill_evt,
|
||||||
|
pause_evt,
|
||||||
|
};
|
||||||
|
|
||||||
|
let paused = self.paused.clone();
|
||||||
|
let mut epoll_threads = Vec::new();
|
||||||
|
thread::Builder::new()
|
||||||
|
.name("virtio_mem".to_string())
|
||||||
|
.spawn(move || handler.run(paused))
|
||||||
|
.map(|thread| epoll_threads.push(thread))
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("failed to clone virtio-mem epoll thread: {}", e);
|
||||||
|
ActivateError::BadActivate
|
||||||
|
})?;
|
||||||
|
self.epoll_threads = Some(epoll_threads);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) -> Option<(Arc<dyn VirtioInterrupt>, Vec<EventFd>)> {
|
||||||
|
// We first must resume the virtio thread if it was paused.
|
||||||
|
if self.pause_evt.take().is_some() {
|
||||||
|
self.resume().ok()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(kill_evt) = self.kill_evt.take() {
|
||||||
|
// Ignore the result because there is nothing we can do about it.
|
||||||
|
let _ = kill_evt.write(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the interrupt and queue EventFDs
|
||||||
|
Some((
|
||||||
|
self.interrupt_cb.take().unwrap(),
|
||||||
|
self.queue_evts.take().unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtio_pausable!(Mem);
|
||||||
|
impl Snapshottable for Mem {
|
||||||
|
fn id(&self) -> String {
|
||||||
|
self.id.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Transportable for Mem {}
|
||||||
|
impl Migratable for Mem {}
|
||||||
@@ -15,23 +15,28 @@ use super::{
|
|||||||
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterruptType,
|
ActivateError, ActivateResult, Queue, VirtioDevice, VirtioDeviceType, VirtioInterruptType,
|
||||||
};
|
};
|
||||||
use crate::VirtioInterrupt;
|
use crate::VirtioInterrupt;
|
||||||
use epoll;
|
use anyhow::anyhow;
|
||||||
use libc::EAGAIN;
|
use libc::EAGAIN;
|
||||||
use libc::EFD_NONBLOCK;
|
use libc::EFD_NONBLOCK;
|
||||||
use net_util::{MacAddr, Tap};
|
use net_util::{MacAddr, Tap};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
use virtio_bindings::bindings::virtio_net::*;
|
use virtio_bindings::bindings::virtio_net::*;
|
||||||
use vm_device::{Migratable, MigratableError, Pausable, Snapshotable};
|
use virtio_bindings::bindings::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
|
||||||
use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||||
|
use vm_migration::{
|
||||||
|
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||||
|
Transportable,
|
||||||
|
};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -42,60 +47,53 @@ pub enum Error {
|
|||||||
|
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
struct NetEpollHandler {
|
pub struct NetQueuePair {
|
||||||
mem: GuestMemoryAtomic<GuestMemoryMmap>,
|
pub mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||||
tap: Tap,
|
pub tap: Tap,
|
||||||
rx: RxVirtio,
|
pub rx: RxVirtio,
|
||||||
tx: TxVirtio,
|
pub tx: TxVirtio,
|
||||||
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
pub epoll_fd: Option<RawFd>,
|
||||||
kill_evt: EventFd,
|
pub rx_tap_listening: bool,
|
||||||
pause_evt: EventFd,
|
|
||||||
epoll_fd: RawFd,
|
|
||||||
rx_tap_listening: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetEpollHandler {
|
impl NetQueuePair {
|
||||||
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
|
|
||||||
self.interrupt_cb
|
|
||||||
.trigger(&VirtioInterruptType::Queue, Some(queue))
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Failed to signal used queue: {:?}", e);
|
|
||||||
DeviceError::FailedSignalingUsedQueue(e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
|
// Copies a single frame from `self.rx.frame_buf` into the guest. Returns true
|
||||||
// if a buffer was used, and false if the frame must be deferred until a buffer
|
// if a buffer was used, and false if the frame must be deferred until a buffer
|
||||||
// is made available by the driver.
|
// is made available by the driver.
|
||||||
fn rx_single_frame(&mut self, mut queue: &mut Queue) -> bool {
|
fn rx_single_frame(&mut self, mut queue: &mut Queue) -> result::Result<bool, DeviceError> {
|
||||||
let mem = self.mem.memory();
|
let mem = self
|
||||||
|
.mem
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(DeviceError::NoMemoryConfigured)
|
||||||
|
.map(|m| m.memory())?;
|
||||||
let next_desc = queue.iter(&mem).next();
|
let next_desc = queue.iter(&mem).next();
|
||||||
|
|
||||||
if next_desc.is_none() {
|
if next_desc.is_none() {
|
||||||
// Queue has no available descriptors
|
// Queue has no available descriptors
|
||||||
if self.rx_tap_listening {
|
if self.rx_tap_listening {
|
||||||
unregister_listener(
|
unregister_listener(
|
||||||
self.epoll_fd,
|
self.epoll_fd.unwrap(),
|
||||||
self.tap.as_raw_fd(),
|
self.tap.as_raw_fd(),
|
||||||
epoll::Events::EPOLLIN,
|
epoll::Events::EPOLLIN,
|
||||||
u64::from(RX_TAP_EVENT),
|
u64::from(RX_TAP_EVENT),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.map_err(DeviceError::UnregisterListener)?;
|
||||||
self.rx_tap_listening = false;
|
self.rx_tap_listening = false;
|
||||||
|
info!("Listener unregistered");
|
||||||
}
|
}
|
||||||
return false;
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rx.process_desc_chain(&mem, next_desc, &mut queue)
|
Ok(self.rx.process_desc_chain(&mem, next_desc, &mut queue))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_rx(&mut self, queue: &mut Queue) -> result::Result<(), DeviceError> {
|
fn process_rx(&mut self, queue: &mut Queue) -> result::Result<bool, DeviceError> {
|
||||||
// Read as many frames as possible.
|
// Read as many frames as possible.
|
||||||
loop {
|
loop {
|
||||||
match self.read_tap() {
|
match self.read_tap() {
|
||||||
Ok(count) => {
|
Ok(count) => {
|
||||||
self.rx.bytes_read = count;
|
self.rx.bytes_read = count;
|
||||||
if !self.rx_single_frame(queue) {
|
if !self.rx_single_frame(queue)? {
|
||||||
self.rx.deferred_frame = true;
|
self.rx.deferred_frame = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -116,83 +114,152 @@ impl NetEpollHandler {
|
|||||||
}
|
}
|
||||||
if self.rx.deferred_irqs {
|
if self.rx.deferred_irqs {
|
||||||
self.rx.deferred_irqs = false;
|
self.rx.deferred_irqs = false;
|
||||||
self.signal_used_queue(queue)
|
let mem = self
|
||||||
|
.mem
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(DeviceError::NoMemoryConfigured)
|
||||||
|
.map(|m| m.memory())?;
|
||||||
|
Ok(queue.needs_notification(&mem, queue.next_used))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resume_rx(&mut self, queue: &mut Queue) -> result::Result<(), DeviceError> {
|
pub fn resume_rx(&mut self, queue: &mut Queue) -> result::Result<bool, DeviceError> {
|
||||||
|
if !self.rx_tap_listening {
|
||||||
|
register_listener(
|
||||||
|
self.epoll_fd.unwrap(),
|
||||||
|
self.tap.as_raw_fd(),
|
||||||
|
epoll::Events::EPOLLIN,
|
||||||
|
u64::from(RX_TAP_EVENT),
|
||||||
|
)
|
||||||
|
.map_err(DeviceError::RegisterListener)?;
|
||||||
|
self.rx_tap_listening = true;
|
||||||
|
info!("Listener registered");
|
||||||
|
}
|
||||||
if self.rx.deferred_frame {
|
if self.rx.deferred_frame {
|
||||||
if self.rx_single_frame(queue) {
|
if self.rx_single_frame(queue)? {
|
||||||
self.rx.deferred_frame = false;
|
self.rx.deferred_frame = false;
|
||||||
// process_rx() was interrupted possibly before consuming all
|
// process_rx() was interrupted possibly before consuming all
|
||||||
// packets in the tap; try continuing now.
|
// packets in the tap; try continuing now.
|
||||||
self.process_rx(queue)
|
self.process_rx(queue)
|
||||||
} else if self.rx.deferred_irqs {
|
} else if self.rx.deferred_irqs {
|
||||||
self.rx.deferred_irqs = false;
|
self.rx.deferred_irqs = false;
|
||||||
self.signal_used_queue(queue)
|
let mem = self
|
||||||
|
.mem
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(DeviceError::NoMemoryConfigured)
|
||||||
|
.map(|m| m.memory())?;
|
||||||
|
Ok(queue.needs_notification(&mem, queue.next_used))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_tx(&mut self, mut queue: &mut Queue) -> result::Result<(), DeviceError> {
|
pub fn process_tx(&mut self, mut queue: &mut Queue) -> result::Result<bool, DeviceError> {
|
||||||
let mem = self.mem.memory();
|
let mem = self
|
||||||
|
.mem
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(DeviceError::NoMemoryConfigured)
|
||||||
|
.map(|m| m.memory())?;
|
||||||
self.tx.process_desc_chain(&mem, &mut self.tap, &mut queue);
|
self.tx.process_desc_chain(&mem, &mut self.tap, &mut queue);
|
||||||
|
Ok(queue.needs_notification(&mem, queue.next_used))
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
pub fn process_rx_tap(&mut self, mut queue: &mut Queue) -> result::Result<bool, DeviceError> {
|
||||||
|
if self.rx.deferred_frame
|
||||||
|
// Process a deferred frame first if available. Don't read from tap again
|
||||||
|
// until we manage to receive this deferred frame.
|
||||||
|
{
|
||||||
|
if self.rx_single_frame(&mut queue)? {
|
||||||
|
self.rx.deferred_frame = false;
|
||||||
|
self.process_rx(&mut queue)
|
||||||
|
} else if self.rx.deferred_irqs {
|
||||||
|
self.rx.deferred_irqs = false;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.process_rx(&mut queue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_tap(&mut self) -> io::Result<usize> {
|
fn read_tap(&mut self) -> io::Result<usize> {
|
||||||
self.tap.read(&mut self.rx.frame_buf)
|
self.tap.read(&mut self.rx.frame_buf)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_rx_event(&mut self, mut queue: &mut Queue, queue_evt: &EventFd) {
|
struct NetEpollHandler {
|
||||||
|
net: NetQueuePair,
|
||||||
|
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||||
|
kill_evt: EventFd,
|
||||||
|
pause_evt: EventFd,
|
||||||
|
|
||||||
|
// Always generate interrupts until the driver has signalled to the device.
|
||||||
|
// This mitigates a problem with interrupts from tap events being "lost" upon
|
||||||
|
// a restore as the vCPU thread isn't ready to handle the interrupt. This causes
|
||||||
|
// issues when combined with VIRTIO_RING_F_EVENT_IDX interrupt suppression.
|
||||||
|
driver_awake: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetEpollHandler {
|
||||||
|
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
|
||||||
|
self.interrupt_cb
|
||||||
|
.trigger(&VirtioInterruptType::Queue, Some(queue))
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to signal used queue: {:?}", e);
|
||||||
|
DeviceError::FailedSignalingUsedQueue(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_rx_event(
|
||||||
|
&mut self,
|
||||||
|
mut queue: &mut Queue,
|
||||||
|
queue_evt: &EventFd,
|
||||||
|
) -> result::Result<(), DeviceError> {
|
||||||
if let Err(e) = queue_evt.read() {
|
if let Err(e) = queue_evt.read() {
|
||||||
error!("Failed to get rx queue event: {:?}", e);
|
error!("Failed to get rx queue event: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.resume_rx(&mut queue).unwrap();
|
if self.net.resume_rx(&mut queue)? || !self.driver_awake {
|
||||||
if !self.rx_tap_listening {
|
self.signal_used_queue(queue)?;
|
||||||
register_listener(
|
info!("Signalling RX queue");
|
||||||
self.epoll_fd,
|
} else {
|
||||||
self.tap.as_raw_fd(),
|
info!("Not signalling RX queue");
|
||||||
epoll::Events::EPOLLIN,
|
|
||||||
u64::from(RX_TAP_EVENT),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
self.rx_tap_listening = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_tx_event(&mut self, mut queue: &mut Queue, queue_evt: &EventFd) {
|
fn handle_tx_event(
|
||||||
|
&mut self,
|
||||||
|
mut queue: &mut Queue,
|
||||||
|
queue_evt: &EventFd,
|
||||||
|
) -> result::Result<(), DeviceError> {
|
||||||
if let Err(e) = queue_evt.read() {
|
if let Err(e) = queue_evt.read() {
|
||||||
error!("Failed to get tx queue event: {:?}", e);
|
error!("Failed to get tx queue event: {:?}", e);
|
||||||
}
|
}
|
||||||
|
if self.net.process_tx(&mut queue)? || !self.driver_awake {
|
||||||
self.process_tx(&mut queue).unwrap();
|
self.signal_used_queue(queue)?;
|
||||||
|
info!("Signalling TX queue");
|
||||||
|
} else {
|
||||||
|
info!("Not signalling TX queue");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_rx_tap_event(&mut self, mut queue: &mut Queue) {
|
fn handle_rx_tap_event(&mut self, queue: &mut Queue) -> result::Result<(), DeviceError> {
|
||||||
if self.rx.deferred_frame
|
if self.net.process_rx_tap(queue)? || !self.driver_awake {
|
||||||
// Process a deferred frame first if available. Don't read from tap again
|
self.signal_used_queue(queue)?;
|
||||||
// until we manage to receive this deferred frame.
|
info!("Signalling RX queue");
|
||||||
{
|
|
||||||
if self.rx_single_frame(&mut queue) {
|
|
||||||
self.rx.deferred_frame = false;
|
|
||||||
self.process_rx(&mut queue).unwrap();
|
|
||||||
} else if self.rx.deferred_irqs {
|
|
||||||
self.rx.deferred_irqs = false;
|
|
||||||
self.signal_used_queue(&queue).unwrap();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.process_rx(&mut queue).unwrap();
|
info!("Not signalling RX queue");
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
@@ -202,42 +269,62 @@ impl NetEpollHandler {
|
|||||||
queue_evts: Vec<EventFd>,
|
queue_evts: Vec<EventFd>,
|
||||||
) -> result::Result<(), DeviceError> {
|
) -> result::Result<(), DeviceError> {
|
||||||
// Create the epoll file descriptor
|
// Create the epoll file descriptor
|
||||||
self.epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
let epoll_fd = epoll::create(true).map_err(DeviceError::EpollCreateFd)?;
|
||||||
// Add events
|
// Use 'File' to enforce closing on 'epoll_fd'
|
||||||
|
let epoll_file = unsafe { File::from_raw_fd(epoll_fd) };
|
||||||
|
self.net.epoll_fd = Some(epoll_fd);
|
||||||
|
|
||||||
// Add events
|
// Add events
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
self.epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
queue_evts[0].as_raw_fd(),
|
queue_evts[0].as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_QUEUE_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_QUEUE_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
self.epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
queue_evts[1].as_raw_fd(),
|
queue_evts[1].as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(TX_QUEUE_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(TX_QUEUE_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
self.epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.kill_evt.as_raw_fd(),
|
self.kill_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(KILL_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
epoll::ctl(
|
epoll::ctl(
|
||||||
self.epoll_fd,
|
epoll_file.as_raw_fd(),
|
||||||
epoll::ControlOptions::EPOLL_CTL_ADD,
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
self.pause_evt.as_raw_fd(),
|
self.pause_evt.as_raw_fd(),
|
||||||
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(PAUSE_EVENT)),
|
||||||
)
|
)
|
||||||
.map_err(DeviceError::EpollCtl)?;
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
|
|
||||||
|
// If there are some already available descriptors on the RX queue,
|
||||||
|
// then we can start the thread while listening onto the TAP.
|
||||||
|
if queues[0]
|
||||||
|
.available_descriptors(&self.net.mem.as_ref().unwrap().memory())
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
epoll::ctl(
|
||||||
|
epoll_file.as_raw_fd(),
|
||||||
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
||||||
|
self.net.tap.as_raw_fd(),
|
||||||
|
epoll::Event::new(epoll::Events::EPOLLIN, u64::from(RX_TAP_EVENT)),
|
||||||
|
)
|
||||||
|
.map_err(DeviceError::EpollCtl)?;
|
||||||
|
self.net.rx_tap_listening = true;
|
||||||
|
error!("Listener registered at start");
|
||||||
|
}
|
||||||
|
|
||||||
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); NET_EVENTS_COUNT];
|
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); NET_EVENTS_COUNT];
|
||||||
|
|
||||||
'epoll: loop {
|
'epoll: loop {
|
||||||
let num_events = match epoll::wait(self.epoll_fd, -1, &mut events[..]) {
|
let num_events = match epoll::wait(epoll_file.as_raw_fd(), -1, &mut events[..]) {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.kind() == io::ErrorKind::Interrupted {
|
if e.kind() == io::ErrorKind::Interrupted {
|
||||||
@@ -259,13 +346,15 @@ impl NetEpollHandler {
|
|||||||
|
|
||||||
match ev_type {
|
match ev_type {
|
||||||
RX_QUEUE_EVENT => {
|
RX_QUEUE_EVENT => {
|
||||||
self.handle_rx_event(&mut queues[0], &queue_evts[0]);
|
self.driver_awake = true;
|
||||||
|
self.handle_rx_event(&mut queues[0], &queue_evts[0])?;
|
||||||
}
|
}
|
||||||
TX_QUEUE_EVENT => {
|
TX_QUEUE_EVENT => {
|
||||||
self.handle_tx_event(&mut queues[1], &queue_evts[1]);
|
self.driver_awake = true;
|
||||||
|
self.handle_tx_event(&mut queues[1], &queue_evts[1])?;
|
||||||
}
|
}
|
||||||
RX_TAP_EVENT => {
|
RX_TAP_EVENT => {
|
||||||
self.handle_rx_tap_event(&mut queues[0]);
|
self.handle_rx_tap_event(&mut queues[0])?;
|
||||||
}
|
}
|
||||||
KILL_EVENT => {
|
KILL_EVENT => {
|
||||||
debug!("KILL_EVENT received, stopping epoll loop");
|
debug!("KILL_EVENT received, stopping epoll loop");
|
||||||
@@ -275,6 +364,7 @@ impl NetEpollHandler {
|
|||||||
// Drain pause event
|
// Drain pause event
|
||||||
let _ = self.pause_evt.read();
|
let _ = self.pause_evt.read();
|
||||||
debug!("PAUSE_EVENT received, pausing virtio-net epoll loop");
|
debug!("PAUSE_EVENT received, pausing virtio-net epoll loop");
|
||||||
|
|
||||||
// We loop here to handle spurious park() returns.
|
// We loop here to handle spurious park() returns.
|
||||||
// Until we have not resumed, the paused boolean will
|
// Until we have not resumed, the paused boolean will
|
||||||
// be true.
|
// be true.
|
||||||
@@ -293,6 +383,7 @@ impl NetEpollHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Net {
|
pub struct Net {
|
||||||
|
id: String,
|
||||||
kill_evt: Option<EventFd>,
|
kill_evt: Option<EventFd>,
|
||||||
pause_evt: Option<EventFd>,
|
pause_evt: Option<EventFd>,
|
||||||
taps: Option<Vec<Tap>>,
|
taps: Option<Vec<Tap>>,
|
||||||
@@ -307,9 +398,18 @@ pub struct Net {
|
|||||||
queue_size: Vec<u16>,
|
queue_size: Vec<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct NetState {
|
||||||
|
pub avail_features: u64,
|
||||||
|
pub acked_features: u64,
|
||||||
|
pub config: VirtioNetConfig,
|
||||||
|
pub queue_size: Vec<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Net {
|
impl Net {
|
||||||
/// Create a new virtio network device with the given TAP interface.
|
/// Create a new virtio network device with the given TAP interface.
|
||||||
pub fn new_with_tap(
|
pub fn new_with_tap(
|
||||||
|
id: String,
|
||||||
taps: Vec<Tap>,
|
taps: Vec<Tap>,
|
||||||
guest_mac: Option<MacAddr>,
|
guest_mac: Option<MacAddr>,
|
||||||
iommu: bool,
|
iommu: bool,
|
||||||
@@ -322,6 +422,7 @@ impl Net {
|
|||||||
| 1 << VIRTIO_NET_F_GUEST_UFO
|
| 1 << VIRTIO_NET_F_GUEST_UFO
|
||||||
| 1 << VIRTIO_NET_F_HOST_TSO4
|
| 1 << VIRTIO_NET_F_HOST_TSO4
|
||||||
| 1 << VIRTIO_NET_F_HOST_UFO
|
| 1 << VIRTIO_NET_F_HOST_UFO
|
||||||
|
| 1 << VIRTIO_RING_F_EVENT_IDX
|
||||||
| 1 << VIRTIO_F_VERSION_1;
|
| 1 << VIRTIO_F_VERSION_1;
|
||||||
|
|
||||||
if iommu {
|
if iommu {
|
||||||
@@ -339,6 +440,7 @@ impl Net {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(Net {
|
Ok(Net {
|
||||||
|
id,
|
||||||
kill_evt: None,
|
kill_evt: None,
|
||||||
pause_evt: None,
|
pause_evt: None,
|
||||||
taps: Some(taps),
|
taps: Some(taps),
|
||||||
@@ -356,18 +458,40 @@ impl Net {
|
|||||||
|
|
||||||
/// Create a new virtio network device with the given IP address and
|
/// Create a new virtio network device with the given IP address and
|
||||||
/// netmask.
|
/// netmask.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
id: String,
|
||||||
if_name: Option<&str>,
|
if_name: Option<&str>,
|
||||||
ip_addr: Option<Ipv4Addr>,
|
ip_addr: Option<Ipv4Addr>,
|
||||||
netmask: Option<Ipv4Addr>,
|
netmask: Option<Ipv4Addr>,
|
||||||
guest_mac: Option<MacAddr>,
|
guest_mac: Option<MacAddr>,
|
||||||
|
host_mac: &mut Option<MacAddr>,
|
||||||
iommu: bool,
|
iommu: bool,
|
||||||
num_queues: usize,
|
num_queues: usize,
|
||||||
queue_size: u16,
|
queue_size: u16,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let taps = open_tap(if_name, ip_addr, netmask, num_queues / 2).map_err(Error::OpenTap)?;
|
let taps = open_tap(if_name, ip_addr, netmask, host_mac, num_queues / 2)
|
||||||
|
.map_err(Error::OpenTap)?;
|
||||||
|
|
||||||
Self::new_with_tap(taps, guest_mac, iommu, num_queues, queue_size)
|
Self::new_with_tap(id, taps, guest_mac, iommu, num_queues, queue_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> NetState {
|
||||||
|
NetState {
|
||||||
|
avail_features: self.avail_features,
|
||||||
|
acked_features: self.acked_features,
|
||||||
|
config: self.config,
|
||||||
|
queue_size: self.queue_size.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_state(&mut self, state: &NetState) -> Result<()> {
|
||||||
|
self.avail_features = state.avail_features;
|
||||||
|
self.acked_features = state.acked_features;
|
||||||
|
self.config = state.config;
|
||||||
|
self.queue_size = state.queue_size.clone();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,6 +627,8 @@ impl VirtioDevice for Net {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let event_idx = self.acked_features & 1 << VIRTIO_RING_F_EVENT_IDX != 0;
|
||||||
|
|
||||||
let mut epoll_threads = Vec::new();
|
let mut epoll_threads = Vec::new();
|
||||||
for _ in 0..taps.len() {
|
for _ in 0..taps.len() {
|
||||||
let rx = RxVirtio::new();
|
let rx = RxVirtio::new();
|
||||||
@@ -512,21 +638,26 @@ impl VirtioDevice for Net {
|
|||||||
let mut queue_pair = Vec::new();
|
let mut queue_pair = Vec::new();
|
||||||
queue_pair.push(queues.remove(0));
|
queue_pair.push(queues.remove(0));
|
||||||
queue_pair.push(queues.remove(0));
|
queue_pair.push(queues.remove(0));
|
||||||
|
queue_pair[0].set_event_idx(event_idx);
|
||||||
|
queue_pair[1].set_event_idx(event_idx);
|
||||||
|
|
||||||
let mut queue_evt_pair = Vec::new();
|
let mut queue_evt_pair = Vec::new();
|
||||||
queue_evt_pair.push(queue_evts.remove(0));
|
queue_evt_pair.push(queue_evts.remove(0));
|
||||||
queue_evt_pair.push(queue_evts.remove(0));
|
queue_evt_pair.push(queue_evts.remove(0));
|
||||||
|
|
||||||
let mut handler = NetEpollHandler {
|
let mut handler = NetEpollHandler {
|
||||||
mem: mem.clone(),
|
net: NetQueuePair {
|
||||||
tap: taps.remove(0),
|
mem: Some(mem.clone()),
|
||||||
rx,
|
tap: taps.remove(0),
|
||||||
tx,
|
rx,
|
||||||
|
tx,
|
||||||
|
epoll_fd: None,
|
||||||
|
rx_tap_listening,
|
||||||
|
},
|
||||||
interrupt_cb: interrupt_cb.clone(),
|
interrupt_cb: interrupt_cb.clone(),
|
||||||
kill_evt: kill_evt.try_clone().unwrap(),
|
kill_evt: kill_evt.try_clone().unwrap(),
|
||||||
pause_evt: pause_evt.try_clone().unwrap(),
|
pause_evt: pause_evt.try_clone().unwrap(),
|
||||||
epoll_fd: 0,
|
driver_awake: false,
|
||||||
rx_tap_listening,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let paused = self.paused.clone();
|
let paused = self.paused.clone();
|
||||||
@@ -567,5 +698,45 @@ impl VirtioDevice for Net {
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtio_ctrl_q_pausable!(Net);
|
virtio_ctrl_q_pausable!(Net);
|
||||||
impl Snapshotable for Net {}
|
impl Snapshottable for Net {
|
||||||
|
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 net_snapshot = Snapshot::new(self.id.as_str());
|
||||||
|
net_snapshot.add_data_section(SnapshotDataSection {
|
||||||
|
id: format!("{}-section", self.id),
|
||||||
|
snapshot,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(net_snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||||
|
if let Some(net_section) = snapshot.snapshot_data.get(&format!("{}-section", self.id)) {
|
||||||
|
let net_state = match serde_json::from_slice(&net_section.snapshot) {
|
||||||
|
Ok(state) => state,
|
||||||
|
Err(error) => {
|
||||||
|
return Err(MigratableError::Restore(anyhow!(
|
||||||
|
"Could not deserialize NET {}",
|
||||||
|
error
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return self.set_state(&net_state).map_err(|e| {
|
||||||
|
MigratableError::Restore(anyhow!("Could not restore NET state {:?}", e))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(MigratableError::Restore(anyhow!(
|
||||||
|
"Could not find NET snapshot section"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Transportable for Net {}
|
||||||
impl Migratable for Net {}
|
impl Migratable for Net {}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user