mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Compare commits
616 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f58b6f8417 | ||
|
|
71ae9cfe06 | ||
|
|
f0b7a09a49 | ||
|
|
bc84a1c79b | ||
|
|
9c5be6f660 | ||
|
|
8e9a5a0dbb | ||
|
|
d561b45552 | ||
|
|
9353856426 | ||
|
|
9af477e964 | ||
|
|
27515a6ec4 | ||
|
|
169a6bb83b | ||
|
|
19167e7647 | ||
|
|
2cdc3e3546 | ||
|
|
de9471fc72 | ||
|
|
b5450ca72c | ||
|
|
8008aad545 | ||
|
|
3bd47ffdc1 | ||
|
|
acfbee5b7a | ||
|
|
62c6efb2e5 | ||
|
|
aec0d309ee | ||
|
|
5fc12862e6 | ||
|
|
f4159ca016 | ||
|
|
6d63018d9f | ||
|
|
073e0f979b | ||
|
|
50a995b63d | ||
|
|
ccdea0274c | ||
|
|
688ead51c6 | ||
|
|
9792c9aafa | ||
|
|
48963e322a | ||
|
|
a59fbf0e37 | ||
|
|
a6b839b35c | ||
|
|
aa3d5cfbfe | ||
|
|
ddd0552c83 | ||
|
|
1fc8c9165a | ||
|
|
47f47ad53f | ||
|
|
69d1186ccb | ||
|
|
d0601a1473 | ||
|
|
b91b829a28 | ||
|
|
7928a697dc | ||
|
|
b5fb744828 | ||
|
|
29607f38ad | ||
|
|
9c3a73706f | ||
|
|
024675d1d8 | ||
|
|
2c913b8130 | ||
|
|
c397c9c95e | ||
|
|
54f814f34a | ||
|
|
ad8adcb955 | ||
|
|
f24094392e | ||
|
|
eee218f1f5 | ||
|
|
5ada3f59c8 | ||
|
|
89008a49cf | ||
|
|
295f4df7bf | ||
|
|
769194cac8 | ||
|
|
13a3ac6a77 | ||
|
|
b26777cb01 | ||
|
|
24c8cce012 | ||
|
|
c6854c5a97 | ||
|
|
b2e5dbaecb | ||
|
|
5bbf2dca80 | ||
|
|
1df952726a | ||
|
|
9ea087597f | ||
|
|
952f9bd3fe | ||
|
|
c29caf2a85 | ||
|
|
5fbeacabad | ||
|
|
f7502057d9 | ||
|
|
ff54909d2c | ||
|
|
6664e5a6e7 | ||
|
|
5209026f52 | ||
|
|
5db9b0ec99 | ||
|
|
c4fb0f36bd | ||
|
|
9cdcbb5121 | ||
|
|
a194dad98c | ||
|
|
b339aa6b88 | ||
|
|
276c9a10fd | ||
|
|
36360c7630 | ||
|
|
b959849149 | ||
|
|
50a89c6808 | ||
|
|
192d69d601 | ||
|
|
b8342ebeb4 | ||
|
|
58009af85d | ||
|
|
0655f25da4 | ||
|
|
2547f144cd | ||
|
|
ec7700544c | ||
|
|
0c60fa8268 | ||
|
|
76e15a4240 | ||
|
|
7659055eec | ||
|
|
5209bd6120 | ||
|
|
c2acea5156 | ||
|
|
fd9972ecc5 | ||
|
|
ca4ab99f42 | ||
|
|
977d824cf8 | ||
|
|
38fa630ce7 | ||
|
|
a59b361058 | ||
|
|
fa5b5fd602 | ||
|
|
7765398045 | ||
|
|
9282ad82b0 | ||
|
|
a09feecc36 | ||
|
|
060df69f07 | ||
|
|
f6c8e4b045 | ||
|
|
2824642e80 | ||
|
|
41cfdb50cd | ||
|
|
12e20effd7 | ||
|
|
9fc86a91e2 | ||
|
|
da8ce25abf | ||
|
|
23e3b022eb | ||
|
|
30033bdaea | ||
|
|
d578c408b7 | ||
|
|
55a3a38e14 | ||
|
|
6006068951 | ||
|
|
28ab6cea0e | ||
|
|
4ebeeb1310 | ||
|
|
77f6cd5ef4 | ||
|
|
f2c2a44ca6 | ||
|
|
8d418e1fca | ||
|
|
c90f77e399 | ||
|
|
a105089702 | ||
|
|
c366efc19e | ||
|
|
c9b68a3be6 | ||
|
|
72b736153e | ||
|
|
79a2afe44e | ||
|
|
7d299f1055 | ||
|
|
23f9ec50fb | ||
|
|
c99d3f70ac | ||
|
|
33d339c406 | ||
|
|
22649c4a87 | ||
|
|
ef363d79fb | ||
|
|
92f3f86669 | ||
|
|
f6d6495b93 | ||
|
|
68d90cbe65 | ||
|
|
d0b59ca3a0 | ||
|
|
981bb72a09 | ||
|
|
843a010163 | ||
|
|
c381b36b87 | ||
|
|
ff4d378f1b | ||
|
|
74a4aba257 | ||
|
|
7b376fa8e0 | ||
|
|
23afe89089 | ||
|
|
3b43551d98 | ||
|
|
c3df42d42f | ||
|
|
6b424b5f6b | ||
|
|
b2dd03b2dc | ||
|
|
4ebca7f4ea | ||
|
|
d036210ecd | ||
|
|
39d080e0c1 | ||
|
|
d83c9a74f4 | ||
|
|
d26866e018 | ||
|
|
ba7864e111 | ||
|
|
5881809126 | ||
|
|
21ba34542d | ||
|
|
ab456affb3 | ||
|
|
852bf0b2ce | ||
|
|
642a1a610f | ||
|
|
bdfe6a69ef | ||
|
|
7889fc9207 | ||
|
|
cb826aa2f1 | ||
|
|
df522cf12c | ||
|
|
a920ef48de | ||
|
|
9cd8aa1af9 | ||
|
|
f6b63af06d | ||
|
|
8ff1142bf4 | ||
|
|
5de4d03238 | ||
|
|
302e4c17d2 | ||
|
|
b49c7dfd65 | ||
|
|
985da4d3fe | ||
|
|
2ec7d1ae05 | ||
|
|
fc47dd6645 | ||
|
|
2e924f458e | ||
|
|
20fde6b924 | ||
|
|
72d054bf40 | ||
|
|
e0d79196c8 | ||
|
|
a286e960ed | ||
|
|
f70852c04b | ||
|
|
8f740f5c02 | ||
|
|
17c9e2cd4b | ||
|
|
b5653d5278 | ||
|
|
28abe63f46 | ||
|
|
49b49421d0 | ||
|
|
ef12216c19 | ||
|
|
e05ed9240a | ||
|
|
c91d25ea64 | ||
|
|
315a730128 | ||
|
|
230e8a23a0 | ||
|
|
f583aa9d30 | ||
|
|
76adea46d0 | ||
|
|
7f1f335950 | ||
|
|
2e6f93e1a9 | ||
|
|
539708e45e | ||
|
|
2e87d5ae02 | ||
|
|
14af74cb5b | ||
|
|
73612401a5 | ||
|
|
858f64b3dc | ||
|
|
225bedcb20 | ||
|
|
8367784cf3 | ||
|
|
ecc1da95dd | ||
|
|
184baff355 | ||
|
|
3ad94ddf7c | ||
|
|
fabd63072b | ||
|
|
05cdef17f4 | ||
|
|
6ccd32c904 | ||
|
|
50dae04c05 | ||
|
|
44ac11efc7 | ||
|
|
f452fe7497 | ||
|
|
a2e1e13918 | ||
|
|
a4134f6b25 | ||
|
|
d2de263774 | ||
|
|
9f2e7f455f | ||
|
|
faba6a3fb3 | ||
|
|
2aaf0540b6 | ||
|
|
d82e74e4bf | ||
|
|
f9a3476016 | ||
|
|
aaa364a9c5 | ||
|
|
d20e07b271 | ||
|
|
3074b178bd | ||
|
|
4751a5949d | ||
|
|
25d567b6c6 | ||
|
|
bb1ef5f1b8 | ||
|
|
d9e1eec47d | ||
|
|
a4c076bd09 | ||
|
|
8a27735826 | ||
|
|
e8c72b1b01 | ||
|
|
ee537d9bde | ||
|
|
873c79a931 | ||
|
|
7a51be48c6 | ||
|
|
7f9cdf1183 | ||
|
|
72e5ed3372 | ||
|
|
ca48f1c995 | ||
|
|
444905071b | ||
|
|
385d734847 | ||
|
|
16c2eebfd1 | ||
|
|
21dcac7721 | ||
|
|
3ee56112cb | ||
|
|
03db48306b | ||
|
|
dee42ebb29 | ||
|
|
7cc729c7d9 | ||
|
|
a8643dc523 | ||
|
|
1fc6d50f3e | ||
|
|
dbf4a252ad | ||
|
|
add9b13497 | ||
|
|
3d097eef44 | ||
|
|
8c85dd32fa | ||
|
|
aac86f4523 | ||
|
|
f3e889c204 | ||
|
|
3eb9e61a32 | ||
|
|
d42b50845b | ||
|
|
06f391e022 | ||
|
|
60d2469e7a | ||
|
|
c4f8e4b000 | ||
|
|
c32959261e | ||
|
|
ef3fad8388 | ||
|
|
2b42f6c835 | ||
|
|
7acb5c6437 | ||
|
|
63f356b75b | ||
|
|
fd0ef6cfb5 | ||
|
|
286a23fbd4 | ||
|
|
3a93487b82 | ||
|
|
23c46b162e | ||
|
|
f4af668d76 | ||
|
|
7fe5d276a3 | ||
|
|
e664fa386b | ||
|
|
9919dec1d9 | ||
|
|
9ce6c3b75c | ||
|
|
47f59409db | ||
|
|
b51f112629 | ||
|
|
d158fa8de2 | ||
|
|
c64de38140 | ||
|
|
9594ecec0d | ||
|
|
6c0c47a530 | ||
|
|
6d38612f6c | ||
|
|
a44d96c9cc | ||
|
|
93b7dcac12 | ||
|
|
dd3844a892 | ||
|
|
0c3ef986fa | ||
|
|
53c3b00fb0 | ||
|
|
03b824be0b | ||
|
|
558975aa51 | ||
|
|
d78b9bd287 | ||
|
|
365d39ca2c | ||
|
|
d79594aa3f | ||
|
|
27b5f8d7e3 | ||
|
|
2cc312b65c | ||
|
|
d416271457 | ||
|
|
9e6a023f7f | ||
|
|
90ae4676c8 | ||
|
|
89bc413add | ||
|
|
c352b59857 | ||
|
|
ab89b48143 | ||
|
|
ffaab46934 | ||
|
|
280d4fb245 | ||
|
|
15f0451c34 | ||
|
|
3fa6e17cc4 | ||
|
|
e2c81f9ed8 | ||
|
|
ead8453120 | ||
|
|
c4ebe83576 | ||
|
|
1908f488c3 | ||
|
|
593a958fe5 | ||
|
|
ba71390d6a | ||
|
|
f6892f2ede | ||
|
|
251434862f | ||
|
|
1ac4c42a84 | ||
|
|
7dc92d1dac | ||
|
|
7a4edecd29 | ||
|
|
d419e30df1 | ||
|
|
3faffcb087 | ||
|
|
b3a1f5f1be | ||
|
|
2bc5119730 | ||
|
|
5100bc94cc | ||
|
|
6bf9fe487b | ||
|
|
d1a23d9085 | ||
|
|
05f785fa88 | ||
|
|
a3d957ba4d | ||
|
|
5bd63efa45 | ||
|
|
66b001665f | ||
|
|
c6aea5af5d | ||
|
|
c8b655490e | ||
|
|
72741c557a | ||
|
|
b2608ca285 | ||
|
|
74fe032827 | ||
|
|
40a2e3b462 | ||
|
|
abc6672f21 | ||
|
|
053c9e4d73 | ||
|
|
9ea19852b5 | ||
|
|
9871ff454b | ||
|
|
e817b4f36c | ||
|
|
6f63cd00eb | ||
|
|
a6ad85dc40 | ||
|
|
0f4f30dbde | ||
|
|
fe5401223b | ||
|
|
12b7a498d9 | ||
|
|
1fc97e91a4 | ||
|
|
fc5d6c96be | ||
|
|
546778ebfb | ||
|
|
f0360aff83 | ||
|
|
ae96aeda65 | ||
|
|
b5b97f7b05 | ||
|
|
9fc2613b41 | ||
|
|
44b5a19209 | ||
|
|
df6b52924f | ||
|
|
1ab1341775 | ||
|
|
a29f7ffcc7 | ||
|
|
eda39d0469 | ||
|
|
de0c24810e | ||
|
|
fadeb98c67 | ||
|
|
1fc0b94fcd | ||
|
|
ef4c896bbf | ||
|
|
72bb255ff6 | ||
|
|
a406d90059 | ||
|
|
0dfffee6ba | ||
|
|
0fec326582 | ||
|
|
6a591ca81d | ||
|
|
a525f8365f | ||
|
|
f271378811 | ||
|
|
78b9edf848 | ||
|
|
d0aebfb922 | ||
|
|
f913dd6150 | ||
|
|
c622278030 | ||
|
|
d1d0421103 | ||
|
|
fb70baece0 | ||
|
|
0159ab93e1 | ||
|
|
609d704168 | ||
|
|
3ac9b6c404 | ||
|
|
b34703d29f | ||
|
|
cf6763dfdb | ||
|
|
11a69450ba | ||
|
|
c62e409827 | ||
|
|
041724a7cf | ||
|
|
8baa244ec1 | ||
|
|
b399287430 | ||
|
|
c0827e01b1 | ||
|
|
46e736973e | ||
|
|
096d99664d | ||
|
|
f7c42dc7d2 | ||
|
|
37f947d614 | ||
|
|
47d2421e39 | ||
|
|
04419065a3 | ||
|
|
ca60adda70 | ||
|
|
dfe2dadb3e | ||
|
|
523029449e | ||
|
|
7ac764518c | ||
|
|
aa98589bb4 | ||
|
|
ca5f1d6de8 | ||
|
|
270631922d | ||
|
|
465ef3f7ad | ||
|
|
50e0423304 | ||
|
|
906a1eb18d | ||
|
|
7b77f1ef90 | ||
|
|
0005d11e32 | ||
|
|
ec84abc5c7 | ||
|
|
2ea0d8eb91 | ||
|
|
6a25aaf6b9 | ||
|
|
1b6b45bf2f | ||
|
|
ecda97f759 | ||
|
|
5566992422 | ||
|
|
cc637f12f6 | ||
|
|
592babaadd | ||
|
|
138a8156c8 | ||
|
|
7a008e66a4 | ||
|
|
164d481683 | ||
|
|
52f60188ca | ||
|
|
8cb29af712 | ||
|
|
60768a84c5 | ||
|
|
3266eb7cce | ||
|
|
c9a28312a9 | ||
|
|
a278704ae0 | ||
|
|
093a581ee1 | ||
|
|
69394c9c35 | ||
|
|
0f5e5d9e6d | ||
|
|
4606f0f28a | ||
|
|
cb88ceeae8 | ||
|
|
7dcd3aff05 | ||
|
|
8c268e351c | ||
|
|
a79f058a90 | ||
|
|
26783fea89 | ||
|
|
cd1c2ed31e | ||
|
|
b947320feb | ||
|
|
d54544046c | ||
|
|
8c92d1dbdc | ||
|
|
cc67a05474 | ||
|
|
21db6f53c8 | ||
|
|
be1b6bc1e1 | ||
|
|
c22b788b47 | ||
|
|
dfd21cbfc5 | ||
|
|
6d4656c68f | ||
|
|
338521c700 | ||
|
|
2e1866a5db | ||
|
|
8bd3fdea8f | ||
|
|
f0d0d8ccaf | ||
|
|
0415146018 | ||
|
|
499fbd0367 | ||
|
|
e70674943f | ||
|
|
7e127df415 | ||
|
|
c0e8e5b53f | ||
|
|
1409b36cad | ||
|
|
cf067d94c3 | ||
|
|
c377c1398f | ||
|
|
b64a48adb8 | ||
|
|
bdbea19e23 | ||
|
|
8de3bd728c | ||
|
|
654846f433 | ||
|
|
a60b437f89 | ||
|
|
255dbd29ef | ||
|
|
2902a96ae6 | ||
|
|
89b3d7b49b | ||
|
|
f4e391922f | ||
|
|
3594685279 | ||
|
|
1d479e5e08 | ||
|
|
ae296d2229 | ||
|
|
555610181b | ||
|
|
28e12e9f3a | ||
|
|
700f63fad8 | ||
|
|
7ee63134d2 | ||
|
|
885ee9567b | ||
|
|
d2c7645731 | ||
|
|
ae375434cd | ||
|
|
0b0596ef30 | ||
|
|
e83d99ab10 | ||
|
|
e869d2f1bc | ||
|
|
5867a1af4a | ||
|
|
e9880ab0d1 | ||
|
|
02621c6150 | ||
|
|
cf86ca15c2 | ||
|
|
05c79119b9 | ||
|
|
c4dc25de09 | ||
|
|
b076602b87 | ||
|
|
2f2e10ea35 | ||
|
|
58474854f2 | ||
|
|
8b9c9bc97f | ||
|
|
f73a345107 | ||
|
|
74c08e5669 | ||
|
|
cc8b553e86 | ||
|
|
af3c6c34c3 | ||
|
|
d2741fdc84 | ||
|
|
9c6b03927a | ||
|
|
d986f74e27 | ||
|
|
fee873e325 | ||
|
|
7cbd47a71a | ||
|
|
d667ed0c70 | ||
|
|
0c967e1aa0 | ||
|
|
57f81d0375 | ||
|
|
1bae38ceb4 | ||
|
|
934f992536 | ||
|
|
76c04878fa | ||
|
|
d598341534 | ||
|
|
0eabc00b27 | ||
|
|
08ded4b449 | ||
|
|
9d882bc8fe | ||
|
|
1b9890b807 | ||
|
|
aec88e20d7 | ||
|
|
52ad78886c | ||
|
|
c97824ab76 | ||
|
|
e9738a4a49 | ||
|
|
9ab2a34b40 | ||
|
|
8bf0bac5a7 | ||
|
|
ef1e5fd953 | ||
|
|
a06a6e4696 | ||
|
|
10f380f95b | ||
|
|
299e83bf43 | ||
|
|
54d2298a93 | ||
|
|
74627d2de5 | ||
|
|
818dc72e5f | ||
|
|
a4be05ac40 | ||
|
|
029a6d8ab9 | ||
|
|
0f48b61265 | ||
|
|
789ee7b3e4 | ||
|
|
f639a8a0fd | ||
|
|
eabfd642e0 | ||
|
|
ab32375666 | ||
|
|
2a7b1d78ff | ||
|
|
573a5c63cf | ||
|
|
bb5b9584d2 | ||
|
|
2fac41f154 | ||
|
|
095ea049e2 | ||
|
|
ff0d2431d0 | ||
|
|
ed1fdd1f7d | ||
|
|
9ad14e6b3a | ||
|
|
30e1162d7c | ||
|
|
2333c5e233 | ||
|
|
1e3a6cb450 | ||
|
|
ac32b1e35f | ||
|
|
67025948dc | ||
|
|
a5d3604272 | ||
|
|
f48398083c | ||
|
|
00c0b9e170 | ||
|
|
162789a2d1 | ||
|
|
14163c5200 | ||
|
|
8824da27cb | ||
|
|
03f7d39ce5 | ||
|
|
c02a02edfc | ||
|
|
cc7a5aca6c | ||
|
|
b33969896b | ||
|
|
6aa5e21212 | ||
|
|
a32bd2fae1 | ||
|
|
e553c053af | ||
|
|
83f6aa47c9 | ||
|
|
a4ebb6399a | ||
|
|
0c25b4d9fb | ||
|
|
c2cc26fc82 | ||
|
|
03eebcede2 | ||
|
|
71c435ce91 | ||
|
|
6a9934d933 | ||
|
|
2d457ab974 | ||
|
|
6eeab85db0 | ||
|
|
c75f8b2f89 | ||
|
|
9dffc5da5c | ||
|
|
a8c2d70869 | ||
|
|
a070f44a51 | ||
|
|
dda1df0a58 | ||
|
|
2c2e7016c7 | ||
|
|
532b3063be | ||
|
|
91314e7b6c | ||
|
|
8851b48ce6 | ||
|
|
76c3230e08 | ||
|
|
664c3ceda6 | ||
|
|
0a4be7ddf5 | ||
|
|
8a11ef5ba6 | ||
|
|
1e5d35d13a | ||
|
|
df26b428b1 | ||
|
|
6d30fe05e4 | ||
|
|
af0c4c1659 | ||
|
|
668c563dac | ||
|
|
5a0d3277c8 | ||
|
|
4ef97d8ddb | ||
|
|
db5d42ad41 | ||
|
|
315cd5a054 | ||
|
|
77e9fcdebd | ||
|
|
3534b6932a | ||
|
|
34dd3b83af | ||
|
|
e105611eb6 | ||
|
|
f10872e706 | ||
|
|
4b32252028 | ||
|
|
6ebeaa9246 | ||
|
|
c54452c08a | ||
|
|
f762bc7573 | ||
|
|
7e130a65ba | ||
|
|
4913acc05e | ||
|
|
c85e396ce5 | ||
|
|
f4ec915c5d | ||
|
|
ae44e9c076 | ||
|
|
29b74804e1 | ||
|
|
1d3c3bc6ec | ||
|
|
2441798fe4 | ||
|
|
365b947023 | ||
|
|
cb2f11724a | ||
|
|
d03a48162f | ||
|
|
de88bef429 | ||
|
|
64351c1f3f | ||
|
|
2eaf1c70c0 | ||
|
|
ec56710c9b | ||
|
|
827810dbd5 | ||
|
|
c85c1f0d76 | ||
|
|
961c5f2cb2 | ||
|
|
7c40a78b66 | ||
|
|
3ea4a0797d | ||
|
|
48544e4e82 | ||
|
|
c6b47d39e0 | ||
|
|
970a5a410d | ||
|
|
47e65cd341 | ||
|
|
381d0b4372 | ||
|
|
39c9583b48 | ||
|
|
7ddcad1d8b | ||
|
|
dcf6d9d731 | ||
|
|
e7acbcc184 | ||
|
|
29ce3076c2 | ||
|
|
09d53aad11 | ||
|
|
bfde6977c8 | ||
|
|
f53990c7e7 | ||
|
|
89a6b63e6e | ||
|
|
b1285cf528 | ||
|
|
9dd188a8e8 | ||
|
|
ffafeda4b6 | ||
|
|
e3d45be6f7 | ||
|
|
5c3f4dbe6f | ||
|
|
22a2a99e5f | ||
|
|
223189c063 | ||
|
|
80be8ac0dc | ||
|
|
097ba3b191 | ||
|
|
7c346c3844 |
21
.github/workflows/build.yaml
vendored
21
.github/workflows/build.yaml
vendored
@@ -7,6 +7,7 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
@@ -18,6 +19,9 @@ jobs:
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@@ -25,17 +29,16 @@ jobs:
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
|
||||
- name: Debug Build (default features)
|
||||
run: cargo build --all --target=${{ matrix.target }}
|
||||
- name: Debug Check (default features)
|
||||
run: |
|
||||
git rev-list origin/master..$GITHUB_SHA | xargs -t -I % sh -c 'git checkout %; cargo check --all --target=${{ matrix.target }}'
|
||||
git checkout $GITHUB_SHA
|
||||
|
||||
- name: Build (pci,acpi,kvm)
|
||||
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "pci,acpi,kvm" -- -D warnings
|
||||
- name: Build (acpi,kvm)
|
||||
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "acpi,kvm" -- -D warnings
|
||||
|
||||
- name: Build (pci,kvm)
|
||||
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "pci,kvm" -- -D warnings
|
||||
- name: Build (kvm)
|
||||
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "kvm" -- -D warnings
|
||||
|
||||
- name: Build (mmio,kvm)
|
||||
run: cargo rustc --bin cloud-hypervisor --no-default-features --features "mmio,kvm" -- -D warnings
|
||||
|
||||
- name: Release Build (default features)
|
||||
run: cargo build --all --release --target=${{ matrix.target }}
|
||||
|
||||
6
.github/workflows/cross-build.yaml
vendored
6
.github/workflows/cross-build.yaml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- stable
|
||||
- 1.46.0
|
||||
target:
|
||||
- aarch64-unknown-linux-gnu
|
||||
- aarch64-unknown-linux-musl
|
||||
@@ -23,10 +23,10 @@ jobs:
|
||||
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"
|
||||
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 && mkdir -p target/debug/deps && cp ./tlibfdtdev/usr/lib/aarch64-linux-gnu/libfdt.a target/debug/deps/ && echo "libfdt copied into build tree"
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: build
|
||||
args: --target=${{ matrix.target }} --no-default-features --features "mmio,kvm"
|
||||
args: --target=${{ matrix.target }} --no-default-features --features "kvm"
|
||||
|
||||
36
.github/workflows/docker-image.yaml
vendored
Normal file
36
.github/workflows/docker-image.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Cloud-Hypervisor's Docker image update
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
paths: resources/Dockerfile
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: ./resources/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: cloudhypervisor/dev:latest
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
27
.github/workflows/fuzz-build.yaml
vendored
Normal file
27
.github/workflows/fuzz-build.yaml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Cloud Hypervisor Cargo Fuzz Build
|
||||
on: [pull_request, create]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Cargo Fuzz Build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- nightly
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
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 Cargo fuzz
|
||||
run: cargo install -f cargo-fuzz
|
||||
- name: Cargo Fuzz Build
|
||||
run: cargo fuzz build
|
||||
10
.github/workflows/quality-aarch64.yaml
vendored
10
.github/workflows/quality-aarch64.yaml
vendored
@@ -26,15 +26,9 @@ jobs:
|
||||
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: Formatting (rustfmt)
|
||||
run: cargo fmt -- --check
|
||||
- name: Clippy (mmio,kvm)
|
||||
- name: Clippy (kvm)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --no-default-features --features "mmio,kvm" -- -D warnings
|
||||
- name: Clippy (pci,kvm)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: true
|
||||
command: clippy
|
||||
args: --target=${{ matrix.target }} --no-default-features --features "pci,kvm" -- -D warnings
|
||||
args: --target=${{ matrix.target }} --no-default-features --features "kvm" -- -D warnings
|
||||
|
||||
41
.github/workflows/quality.yaml
vendored
41
.github/workflows/quality.yaml
vendored
@@ -18,22 +18,31 @@ jobs:
|
||||
- name: Install Rust toolchain (${{ matrix.rust }})
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
toolchain: ${{ matrix.rust }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Formatting (rustfmt)
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- name: Clippy (all features)
|
||||
run: cargo clippy --all-targets --all-features -- -D warnings
|
||||
|
||||
- name: Clippy (pci,acpi,kvm)
|
||||
run: cargo clippy --all-targets --no-default-features --features "pci,acpi,kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (pci,kvm)
|
||||
run: cargo clippy --all-targets --no-default-features --features "pci,kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (mmio,kvm)
|
||||
run: cargo clippy --all-targets --no-default-features --features "mmio,kvm" -- -D warnings
|
||||
- name: Clippy (all features,kvm)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "common,kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (all features,mshv)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "common,mshv" -- -D warnings
|
||||
|
||||
- name: Clippy (acpi,kvm)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (kvm)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "kvm" -- -D warnings
|
||||
|
||||
- name: Clippy (acpi,mshv)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,mshv" -- -D warnings
|
||||
|
||||
- name: Clippy (mshv)
|
||||
run: cargo clippy --all --all-targets --no-default-features --tests --features "mshv" -- -D warnings
|
||||
|
||||
- name: Clippy (integration tests)
|
||||
run: cargo clippy --all --all-targets --tests --features "integration_tests" -- -D warnings
|
||||
|
||||
@@ -88,5 +88,5 @@ Fixes #88
|
||||
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
|
||||
```
|
||||
|
||||
Then, after the correspoding PR is merged, Github will automatically close that issue when parsing the
|
||||
Then, after the corresponding PR is merged, Github will automatically close that issue when parsing the
|
||||
[commit message](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||
|
||||
708
Cargo.lock
generated
708
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
39
Cargo.toml
39
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cloud-hypervisor"
|
||||
version = "0.10.0"
|
||||
version = "0.13.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
default-run = "cloud-hypervisor"
|
||||
@@ -13,42 +13,49 @@ homepage = "https://github.com/cloud-hypervisor/cloud-hypervisor"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
api_client = { path = "api_client" }
|
||||
clap = { version = "2.33.3", features = ["wrap_help"] }
|
||||
epoll = ">=4.0.1"
|
||||
hypervisor = { path = "hypervisor" }
|
||||
libc = "0.2.77"
|
||||
log = { version = "0.4.11", features = ["std"] }
|
||||
libc = "0.2.86"
|
||||
log = { version = "0.4.14", features = ["std"] }
|
||||
option_parser = { path = "option_parser" }
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.22.0" }
|
||||
serde_json = "1.0.57"
|
||||
vhost_user_block = { path = "vhost_user_block"}
|
||||
vhost_user_net = { path = "vhost_user_net"}
|
||||
serde_json = "1.0.62"
|
||||
signal-hook = "0.3.4"
|
||||
thiserror = "1.0"
|
||||
vmm = { path = "vmm" }
|
||||
vmm-sys-util = "0.6.1"
|
||||
vmm-sys-util = "0.7.0"
|
||||
vm-memory = "0.5.0"
|
||||
wait-timeout = "0.2.0"
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "2.33.3", features = ["wrap_help"] }
|
||||
|
||||
[patch.crates-io]
|
||||
vm-memory = { git = "https://github.com/cloud-hypervisor/vm-memory", branch = "ch" }
|
||||
# List of patched crates
|
||||
[patch.'https://github.com/rust-vmm/vhost']
|
||||
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "ch", package = "vhost", features = ["vhost-user-master", "vhost-user-slave"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ssh2 = "0.8.2"
|
||||
ssh2 = "0.9.1"
|
||||
dirs = "3.0.1"
|
||||
credibility = "0.1.3"
|
||||
tempdir = "0.3.7"
|
||||
lazy_static= "1.4.0"
|
||||
tempfile = "3.1.0"
|
||||
serde_json = "1.0.57"
|
||||
tempfile = "3.2.0"
|
||||
serde_json = "1.0.62"
|
||||
net_util = { path = "net_util" }
|
||||
|
||||
[features]
|
||||
default = ["acpi", "pci", "cmos", "kvm"]
|
||||
default = ["acpi", "cmos", "io_uring", "kvm"]
|
||||
# Common features for all hypervisors
|
||||
common = ["acpi", "cmos", "fwdebug", "io_uring"]
|
||||
acpi = ["vmm/acpi"]
|
||||
pci = ["vmm/pci_support"]
|
||||
mmio = ["vmm/mmio_support"]
|
||||
cmos = ["vmm/cmos"]
|
||||
fwdebug = ["vmm/fwdebug"]
|
||||
kvm = ["vmm/kvm"]
|
||||
mshv = ["vmm/mshv"]
|
||||
io_uring = ["vmm/io_uring"]
|
||||
|
||||
# Integration tests require a special environment to run in
|
||||
@@ -57,6 +64,7 @@ integration_tests = []
|
||||
[workspace]
|
||||
members = [
|
||||
"acpi_tables",
|
||||
"api_client",
|
||||
"arch",
|
||||
"arch_gen",
|
||||
"block_util",
|
||||
@@ -69,7 +77,6 @@ members = [
|
||||
"qcow",
|
||||
"vhost_user_backend",
|
||||
"vhost_user_block",
|
||||
"vhost_user_fs",
|
||||
"vhost_user_net",
|
||||
"virtio-devices",
|
||||
"vmm",
|
||||
|
||||
75
Jenkinsfile
vendored
75
Jenkinsfile
vendored
@@ -22,13 +22,9 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Build') {
|
||||
failFast true
|
||||
parallel {
|
||||
parallel {
|
||||
stage ('Worker build') {
|
||||
agent { node { label 'bionic' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
agent { node { label 'groovy' } }
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
@@ -46,6 +42,9 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
@@ -54,9 +53,6 @@ pipeline{
|
||||
}
|
||||
stage ('AArch64 worker build') {
|
||||
agent { node { label 'bionic-arm64' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
@@ -69,6 +65,9 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration"
|
||||
}
|
||||
@@ -82,10 +81,7 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Worker build (musl)') {
|
||||
agent { node { label 'bionic' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
agent { node { label 'groovy' } }
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
@@ -98,6 +94,9 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Run integration tests for musl') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration --libc musl"
|
||||
}
|
||||
@@ -106,9 +105,6 @@ pipeline{
|
||||
}
|
||||
stage ('Worker build SGX') {
|
||||
agent { node { label 'bionic-sgx' } }
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
when { branch 'master' }
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
@@ -117,11 +113,17 @@ pipeline{
|
||||
}
|
||||
}
|
||||
stage ('Run SGX integration tests') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration-sgx"
|
||||
}
|
||||
}
|
||||
stage ('Run SGX integration tests for musl') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration-sgx --libc musl"
|
||||
}
|
||||
@@ -134,6 +136,47 @@ pipeline{
|
||||
}
|
||||
}
|
||||
}
|
||||
stage ('Worker build - Windows guest') {
|
||||
agent { node { label 'groovy-win' } }
|
||||
stages {
|
||||
stage ('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
}
|
||||
}
|
||||
stage ('Download assets') {
|
||||
steps {
|
||||
sh "mkdir ${env.HOME}/workloads"
|
||||
azureDownload(storageCredentialId: 'ch-image-store',
|
||||
containerName: 'private-images',
|
||||
includeFilesPattern: 'OVMF.fd',
|
||||
downloadType: 'container',
|
||||
downloadDirLoc: "${env.HOME}/workloads")
|
||||
azureDownload(storageCredentialId: 'ch-image-store',
|
||||
containerName: 'private-images',
|
||||
includeFilesPattern: 'windows-server-2019.raw',
|
||||
downloadType: 'container',
|
||||
downloadDirLoc: "${env.HOME}/workloads")
|
||||
}
|
||||
}
|
||||
stage ('Run Windows guest integration tests') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration-windows"
|
||||
}
|
||||
}
|
||||
stage ('Run Windows guest integration tests for musl') {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
steps {
|
||||
sh "scripts/dev_cli.sh tests --integration-windows --libc musl"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
87
README.md
87
README.md
@@ -1,28 +1,28 @@
|
||||
[](https://travis-ci.com/cloud-hypervisor/cloud-hypervisor)
|
||||
|
||||
- [1. What is Cloud Hypervisor?](#1-what-is-cloud-hypervisor)
|
||||
- [Objectives](#objectives)
|
||||
- [High Level](#high-level)
|
||||
- [Architectures](#architectures)
|
||||
- [Guest OS](#guest-os)
|
||||
- [Objectives](#objectives)
|
||||
- [High Level](#high-level)
|
||||
- [Architectures](#architectures)
|
||||
- [Guest OS](#guest-os)
|
||||
- [2. Getting Started](#2-getting-started)
|
||||
- [Clone and build](#clone-and-build)
|
||||
- [Containerized builds and tests](#containerized-builds-and-tests)
|
||||
- [Run](#run)
|
||||
- [Cloud image](#cloud-image)
|
||||
- [Custom kernel and disk image](#custom-kernel-and-disk-image)
|
||||
- [Building your kernel](#building-your-kernel)
|
||||
- [Disk image](#disk-image)
|
||||
- [Booting the guest VM](#booting-the-guest-vm)
|
||||
- [Clone and build](#clone-and-build)
|
||||
- [Containerized builds and tests](#containerized-builds-and-tests)
|
||||
- [Run](#run)
|
||||
- [Cloud image](#cloud-image)
|
||||
- [Custom kernel and disk image](#custom-kernel-and-disk-image)
|
||||
- [Building your kernel](#building-your-kernel)
|
||||
- [Disk image](#disk-image)
|
||||
- [Booting the guest VM](#booting-the-guest-vm)
|
||||
- [3. Status](#3-status)
|
||||
- [Hot Plug](#hot-plug)
|
||||
- [Device Model](#device-model)
|
||||
- [TODO](#todo)
|
||||
- [Hot Plug](#hot-plug)
|
||||
- [Device Model](#device-model)
|
||||
- [TODO](#todo)
|
||||
- [4. `rust-vmm` project dependency](#4-rust-vmm-project-dependency)
|
||||
- [Firecracker and crosvm](#firecracker-and-crosvm)
|
||||
- [Firecracker and crosvm](#firecracker-and-crosvm)
|
||||
- [5. Community](#5-community)
|
||||
- [Contribute](#contribute)
|
||||
- [Join us](#join-us)
|
||||
- [Contribute](#contribute)
|
||||
- [Join us](#join-us)
|
||||
|
||||
# 1. What is Cloud Hypervisor?
|
||||
|
||||
@@ -36,24 +36,24 @@ Cloud Hypervisor is implemented in [Rust](https://www.rust-lang.org/) and is bas
|
||||
|
||||
### High Level
|
||||
|
||||
* KVM based
|
||||
* Minimal emulation
|
||||
* Low latency
|
||||
* Low memory footprint
|
||||
* Low complexity
|
||||
* High performance
|
||||
* Small attack surface
|
||||
* 64-bit support only
|
||||
* CPU, memory, PCI hotplug
|
||||
* Machine to machine migration
|
||||
- KVM based
|
||||
- Minimal emulation
|
||||
- Low latency
|
||||
- Low memory footprint
|
||||
- Low complexity
|
||||
- High performance
|
||||
- Small attack surface
|
||||
- 64-bit support only
|
||||
- CPU, memory, PCI hotplug
|
||||
- Machine to machine migration
|
||||
|
||||
### Architectures
|
||||
|
||||
Cloud Hypervisor supports the `x86-64` and `AArch64` architecture. There are some small differences in functionality between the two architecture (see #1125).
|
||||
Cloud Hypervisor supports the `x86-64` and `AArch64` architectures. There are some small differences in functionality between the two architectures (see [#1125](https://github.com/cloud-hypervisor/cloud-hypervisor/issues/1125)).
|
||||
|
||||
### Guest OS
|
||||
|
||||
Cloud Hypervisor supports `64-bit Linux` with support for *modern* 64-bit Windows guests currently under development.
|
||||
Cloud Hypervisor supports `64-bit Linux` with support for _modern_ 64-bit Windows guests currently under development.
|
||||
|
||||
# 2. Getting Started
|
||||
|
||||
@@ -64,6 +64,23 @@ $ export CLOUDH=$HOME/cloud-hypervisor
|
||||
$ mkdir $CLOUDH
|
||||
```
|
||||
|
||||
## Install prerequisites
|
||||
|
||||
You need to install some prerequisite packages in order to build and test Cloud Hypervisor.
|
||||
Here, all the steps are based on Ubuntu, for other Linux distributions please replace the
|
||||
package manager and package name.
|
||||
|
||||
```shell
|
||||
# Install git
|
||||
$ sudo apt install git
|
||||
# Install rust tool chain
|
||||
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
# Install build-essential
|
||||
$ sudo apt install build-essential
|
||||
# If you want to build statically linked binary please add musl target
|
||||
$ rustup target add x86_64-unknown-linux-musl
|
||||
```
|
||||
|
||||
## Clone and build
|
||||
|
||||
First you need to clone and build the cloud-hypervisor repo:
|
||||
@@ -77,6 +94,8 @@ $ cargo build --release
|
||||
# We need to give the cloud-hypervisor binary the NET_ADMIN capabilities for it to set TAP interfaces up on the host.
|
||||
$ sudo setcap cap_net_admin+ep ./target/release/cloud-hypervisor
|
||||
|
||||
# If you want to build statically linked binary
|
||||
$ cargo build --release --target=x86_64-unknown-linux-musl --all
|
||||
$ popd
|
||||
```
|
||||
|
||||
@@ -117,7 +136,7 @@ You can run a guest VM by either using an existing cloud image or booting into y
|
||||
### Cloud image
|
||||
|
||||
Cloud Hypervisor supports booting disk images containing all needed
|
||||
components to run cloud workloads, a.k.a. cloud images. To do that we rely on
|
||||
components to run cloud workloads, a.k.a. cloud images. To do that we rely on
|
||||
the [Rust Hypervisor
|
||||
Firmware](https://github.com/cloud-hypervisor/rust-hypervisor-firmware) project to provide
|
||||
an ELF
|
||||
@@ -225,8 +244,9 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \
|
||||
Cloud Hypervisor is under active development. No API or feature stability is guaranteed.
|
||||
|
||||
As of 2020-07-02, the following cloud images are supported:
|
||||
* [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
|
||||
* [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (cloudimg)
|
||||
|
||||
- [Ubuntu Bionic](https://cloud-images.ubuntu.com/bionic/current/) (cloudimg)
|
||||
- [Ubuntu Focal](https://cloud-images.ubuntu.com/focal/current/) (cloudimg)
|
||||
|
||||
Direct kernel boot to userspace should work with a rootfs from most distributions.
|
||||
|
||||
@@ -297,4 +317,3 @@ etc, are all equal and welcome means of contribution. See the [CONTRIBUTING](CON
|
||||
|
||||
Get an [invite to our Slack channel](https://join.slack.com/t/cloud-hypervisor/shared_invite/enQtNjY3MTE3MDkwNDQ4LWQ1MTA1ZDVmODkwMWQ1MTRhYzk4ZGNlN2UwNTI3ZmFlODU0OTcwOWZjMTkwZDExYWE3YjFmNzgzY2FmNDAyMjI)
|
||||
and [join us on Slack](https://cloud-hypervisor.slack.com/).
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@ authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
vm-memory = "0.2.1"
|
||||
vm-memory = "0.5.0"
|
||||
|
||||
|
||||
5
api_client/Cargo.toml
Normal file
5
api_client/Cargo.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
[package]
|
||||
name = "api_client"
|
||||
version = "0.1.0"
|
||||
authors = ["The Cloud Hypervisor Authors"]
|
||||
edition = "2018"
|
||||
175
api_client/src/lib.rs
Normal file
175
api_client/src/lib.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Socket(std::io::Error),
|
||||
StatusCodeParsing(std::num::ParseIntError),
|
||||
MissingProtocol,
|
||||
ContentLengthParsing(std::num::ParseIntError),
|
||||
ServerResponse(StatusCode, Option<String>),
|
||||
}
|
||||
|
||||
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 or reading from 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum StatusCode {
|
||||
Continue,
|
||||
OK,
|
||||
NoContent,
|
||||
BadRequest,
|
||||
NotFound,
|
||||
InternalServerError,
|
||||
NotImplemented,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl StatusCode {
|
||||
fn from_raw(code: usize) -> StatusCode {
|
||||
match code {
|
||||
100 => StatusCode::Continue,
|
||||
200 => StatusCode::OK,
|
||||
204 => StatusCode::NoContent,
|
||||
400 => StatusCode::BadRequest,
|
||||
404 => StatusCode::NotFound,
|
||||
500 => StatusCode::InternalServerError,
|
||||
501 => StatusCode::NotImplemented,
|
||||
_ => StatusCode::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(code: &str) -> Result<StatusCode, Error> {
|
||||
Ok(StatusCode::from_raw(
|
||||
code.trim().parse().map_err(Error::StatusCodeParsing)?,
|
||||
))
|
||||
}
|
||||
|
||||
fn is_server_error(self) -> bool {
|
||||
!matches!(
|
||||
self,
|
||||
StatusCode::OK | StatusCode::Continue | StatusCode::NoContent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
|
||||
let header_str = format!("{}: ", header);
|
||||
if let Some(o) = res.find(&header_str) {
|
||||
Some(&res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_status_code(res: &str) -> Result<StatusCode, Error> {
|
||||
if let Some(o) = res.find("HTTP/1.1") {
|
||||
Ok(StatusCode::parse(
|
||||
&res[o + "HTTP/1.1 ".len()..res[o..].find('\r').unwrap()],
|
||||
)?)
|
||||
} else {
|
||||
Err(Error::MissingProtocol)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
|
||||
let mut res = String::new();
|
||||
let mut body_offset = None;
|
||||
let mut content_length: Option<usize> = None;
|
||||
loop {
|
||||
let mut bytes = vec![0; 256];
|
||||
let count = socket.read(&mut bytes).map_err(Error::Socket)?;
|
||||
res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
|
||||
|
||||
// End of headers
|
||||
if let Some(o) = res.find("\r\n\r\n") {
|
||||
body_offset = Some(o + "\r\n\r\n".len());
|
||||
|
||||
// With all headers available we can see if there is any body
|
||||
content_length = if let Some(length) = get_header(&res, "Content-Length") {
|
||||
Some(length.trim().parse().map_err(Error::ContentLengthParsing)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if content_length.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(body_offset) = body_offset {
|
||||
if let Some(content_length) = content_length {
|
||||
if res.len() >= content_length + body_offset {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let body_string = content_length.and(Some(String::from(&res[body_offset.unwrap()..])));
|
||||
let status_code = get_status_code(&res)?;
|
||||
|
||||
if status_code.is_server_error() {
|
||||
Err(Error::ServerResponse(status_code, body_string))
|
||||
} else {
|
||||
Ok(body_string)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simple_api_command<T: Read + Write>(
|
||||
socket: &mut T,
|
||||
method: &str,
|
||||
c: &str,
|
||||
request_body: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
socket
|
||||
.write_all(
|
||||
format!(
|
||||
"{} /api/v1/vm.{} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n",
|
||||
method, c
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.map_err(Error::Socket)?;
|
||||
|
||||
if let Some(request_body) = request_body {
|
||||
socket
|
||||
.write_all(format!("Content-Length: {}\r\n", request_body.len()).as_bytes())
|
||||
.map_err(Error::Socket)?;
|
||||
}
|
||||
|
||||
socket.write_all(b"\r\n").map_err(Error::Socket)?;
|
||||
|
||||
if let Some(request_body) = request_body {
|
||||
socket
|
||||
.write_all(request_body.as_bytes())
|
||||
.map_err(Error::Socket)?;
|
||||
}
|
||||
|
||||
socket.flush().map_err(Error::Socket)?;
|
||||
|
||||
if let Some(body) = parse_http_response(socket)? {
|
||||
println!("{}", body);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -11,9 +11,13 @@ acpi = ["acpi_tables"]
|
||||
anyhow = "1.0"
|
||||
byteorder = "1.3.4"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "0.2.77"
|
||||
log = "0.4.11"
|
||||
vm-memory = { version = "0.2.1", features = ["backend-mmap"] }
|
||||
libc = "0.2.86"
|
||||
log = "0.4.14"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-memory = { version = "0.5.0", features = ["backend-mmap"] }
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
arch_gen = { path = "../arch_gen" }
|
||||
|
||||
@@ -22,4 +26,4 @@ git = "https://github.com/rust-vmm/linux-loader"
|
||||
features = ["elf", "bzimage"]
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7.3"
|
||||
rand = "0.8.3"
|
||||
|
||||
@@ -97,9 +97,9 @@ pub fn create_fdt<T: DeviceInfoForFDT + Clone + Debug, S: ::std::hash::BuildHash
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
gic_device: &dyn GICDevice,
|
||||
initrd: &Option<InitramfsConfig>,
|
||||
pci_space_address: &Option<(u64, u64)>,
|
||||
pci_space_address: &(u64, u64),
|
||||
) -> Result<Vec<u8>> {
|
||||
// Alocate stuff necessary for the holding the blob.
|
||||
// Allocate stuff necessary for the holding the blob.
|
||||
let mut fdt = vec![0; FDT_MAX_SIZE];
|
||||
|
||||
allocate_fdt(&mut fdt)?;
|
||||
@@ -126,9 +126,7 @@ pub fn create_fdt<T: DeviceInfoForFDT + Clone + Debug, S: ::std::hash::BuildHash
|
||||
create_clock_node(&mut fdt)?;
|
||||
create_psci_node(&mut fdt)?;
|
||||
create_devices_node(&mut fdt, device_info)?;
|
||||
if let Some((pci_device_base, pci_device_size)) = pci_space_address {
|
||||
create_pci_nodes(&mut fdt, *pci_device_base, *pci_device_size)?;
|
||||
}
|
||||
create_pci_nodes(&mut fdt, pci_space_address.0, pci_space_address.1)?;
|
||||
|
||||
// End Header node.
|
||||
append_end_node(&mut fdt)?;
|
||||
@@ -402,7 +400,7 @@ fn create_gic_node(fdt: &mut Vec<u8>, gic_device: &dyn GICDevice) -> Result<()>
|
||||
|
||||
if gic_device.msi_compatible() {
|
||||
append_begin_node(fdt, "msic")?;
|
||||
append_property_string(fdt, "compatible", gic_device.msi_compatiblility())?;
|
||||
append_property_string(fdt, "compatible", gic_device.msi_compatibility())?;
|
||||
append_property_null(fdt, "msi-controller")?;
|
||||
append_property_u32(fdt, "phandle", MSI_PHANDLE)?;
|
||||
let msi_reg_prop = generate_prop64(gic_device.msi_properties());
|
||||
|
||||
182
arch/src/aarch64/gic/dist_regs.rs
Normal file
182
arch/src/aarch64/gic/dist_regs.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{Error, Result};
|
||||
use crate::layout::IRQ_BASE;
|
||||
use hypervisor::kvm::kvm_bindings::{
|
||||
kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_DIST_REGS, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
/*
|
||||
Distributor registers as detailed at page 456 from
|
||||
https://static.docs.arm.com/ihi0069/c/IHI0069C_gic_architecture_specification.pdf.
|
||||
Address offsets are relative to the Distributor base address defined
|
||||
by the system memory map. Unless otherwise stated in the register description,
|
||||
all GIC registers are 32-bits wide.
|
||||
*/
|
||||
const GICD_CTLR: u32 = 0x0;
|
||||
const GICD_STATUSR: u32 = 0x0010;
|
||||
const GICD_IGROUPR: u32 = 0x0080;
|
||||
const GICD_ISENABLER: u32 = 0x0100;
|
||||
const GICD_ICENABLER: u32 = 0x0180;
|
||||
const GICD_ISPENDR: u32 = 0x0200;
|
||||
const GICD_ICPENDR: u32 = 0x0280;
|
||||
const GICD_ISACTIVER: u32 = 0x0300;
|
||||
const GICD_ICACTIVER: u32 = 0x0380;
|
||||
const GICD_IPRIORITYR: u32 = 0x0400;
|
||||
const GICD_ICFGR: u32 = 0x0C00;
|
||||
const GICD_IROUTER: u32 = 0x6000;
|
||||
|
||||
/// This is how we represent the registers of the vgic's distributor.
|
||||
/// Some of the distributor register )(i.e GICD_STATUSR) are simple
|
||||
/// registers (i.e they are associated to a 32 bit value).
|
||||
/// However, there are other registers that have variable lengths since
|
||||
/// they dedicate some of the 32 bits to some specific interrupt. So, their length
|
||||
/// depends on the number of interrupts (i.e the ones that are represented as GICD_REG<n>)
|
||||
/// in the documentation mentioned above.
|
||||
struct DistReg {
|
||||
/// Offset from distributor address.
|
||||
base: u32,
|
||||
/// Bits per interrupt.
|
||||
/// Relevant for registers that DO share IRQs.
|
||||
bpi: u8,
|
||||
/// Length of the register.
|
||||
/// Relevant for registers that DO NOT share IRQs.
|
||||
length: u16,
|
||||
}
|
||||
|
||||
// All or at least the registers we are interested in are 32 bit, so
|
||||
// we use a constant for size(u32).
|
||||
const REG_SIZE: u8 = 4;
|
||||
|
||||
// Creates a vgic distributor register.
|
||||
macro_rules! VGIC_DIST_REG {
|
||||
($base:expr, $bpi:expr, $length:expr) => {
|
||||
DistReg {
|
||||
base: $base,
|
||||
bpi: $bpi,
|
||||
length: $length,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// List with relevant distributor registers that we will be restoring.
|
||||
// Order is taken from qemu.
|
||||
static VGIC_DIST_REGS: &'static [DistReg] = &[
|
||||
VGIC_DIST_REG!(GICD_STATUSR, 0, 4),
|
||||
VGIC_DIST_REG!(GICD_ICENABLER, 1, 0),
|
||||
VGIC_DIST_REG!(GICD_ISENABLER, 1, 0),
|
||||
VGIC_DIST_REG!(GICD_IGROUPR, 1, 0),
|
||||
VGIC_DIST_REG!(GICD_IROUTER, 64, 0),
|
||||
VGIC_DIST_REG!(GICD_ICFGR, 2, 0),
|
||||
VGIC_DIST_REG!(GICD_ICPENDR, 1, 0),
|
||||
VGIC_DIST_REG!(GICD_ISPENDR, 1, 0),
|
||||
VGIC_DIST_REG!(GICD_ICACTIVER, 1, 0),
|
||||
VGIC_DIST_REG!(GICD_ISACTIVER, 1, 0),
|
||||
VGIC_DIST_REG!(GICD_IPRIORITYR, 8, 0),
|
||||
];
|
||||
|
||||
fn dist_attr_access(
|
||||
gic: &Arc<dyn hypervisor::Device>,
|
||||
offset: u32,
|
||||
val: &u32,
|
||||
set: bool,
|
||||
) -> Result<()> {
|
||||
let mut gic_dist_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
|
||||
attr: offset as u64,
|
||||
addr: val as *const u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
if set {
|
||||
gic.set_device_attr(&gic_dist_attr)
|
||||
.map_err(Error::SetDeviceAttribute)?;
|
||||
} else {
|
||||
gic.get_device_attr(&mut gic_dist_attr)
|
||||
.map_err(Error::GetDeviceAttribute)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the distributor control register.
|
||||
pub fn read_ctlr(gic: &Arc<dyn hypervisor::Device>) -> Result<u32> {
|
||||
let val: u32 = 0;
|
||||
dist_attr_access(gic, GICD_CTLR, &val, false)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Set the distributor control register.
|
||||
pub fn write_ctlr(gic: &Arc<dyn hypervisor::Device>, val: u32) -> Result<()> {
|
||||
dist_attr_access(gic, GICD_CTLR, &val, true)
|
||||
}
|
||||
|
||||
fn get_interrupts_num(gic: &Arc<dyn hypervisor::Device>) -> Result<u32> {
|
||||
let num_irq = 0;
|
||||
|
||||
let mut nr_irqs_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
|
||||
attr: 0,
|
||||
addr: &num_irq as *const u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
gic.get_device_attr(&mut nr_irqs_attr)
|
||||
.map_err(Error::GetDeviceAttribute)?;
|
||||
Ok(num_irq)
|
||||
}
|
||||
|
||||
fn compute_reg_len(gic: &Arc<dyn hypervisor::Device>, reg: &DistReg, base: u32) -> Result<u32> {
|
||||
let mut end = base;
|
||||
let num_irq = get_interrupts_num(gic)?;
|
||||
if reg.length > 0 {
|
||||
// This is the single type register (i.e one that is not DIST_X<n>) and for which
|
||||
// the bpi is 0.
|
||||
// Look in the kernel for REGISTER_DESC_WITH_LENGTH.
|
||||
end = base + reg.length as u32;
|
||||
}
|
||||
if reg.bpi > 0 {
|
||||
// This is the type of register that takes into account the number of interrupts
|
||||
// that the model has. It is also the type of register where
|
||||
// a register relates to multiple interrupts.
|
||||
end = base + (reg.bpi as u32 * (num_irq - IRQ_BASE) / 8);
|
||||
if reg.bpi as u32 * (num_irq - IRQ_BASE) % 8 > 0 {
|
||||
end += REG_SIZE as u32;
|
||||
}
|
||||
}
|
||||
Ok(end)
|
||||
}
|
||||
|
||||
/// Set distributor registers of the GIC.
|
||||
pub fn set_dist_regs(gic: &Arc<dyn hypervisor::Device>, state: &[u32]) -> Result<()> {
|
||||
let mut idx = 0;
|
||||
|
||||
for dreg in VGIC_DIST_REGS {
|
||||
let mut base = dreg.base + REG_SIZE as u32 * dreg.bpi as u32;
|
||||
let end = compute_reg_len(gic, &dreg, base)?;
|
||||
|
||||
while base < end {
|
||||
let val = state[idx];
|
||||
dist_attr_access(gic, base, &val, true)?;
|
||||
idx += 1;
|
||||
base += REG_SIZE as u32;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Get distributor registers of the GIC.
|
||||
pub fn get_dist_regs(gic: &Arc<dyn hypervisor::Device>) -> Result<Vec<u32>> {
|
||||
let mut state = Vec::new();
|
||||
|
||||
for dreg in VGIC_DIST_REGS {
|
||||
let mut base = dreg.base + REG_SIZE as u32 * dreg.bpi as u32;
|
||||
let end = compute_reg_len(gic, &dreg, base)?;
|
||||
|
||||
while base < end {
|
||||
let val: u32 = 0;
|
||||
dist_attr_access(gic, base, &val, false)?;
|
||||
state.push(val);
|
||||
base += REG_SIZE as u32;
|
||||
}
|
||||
}
|
||||
Ok(state)
|
||||
}
|
||||
275
arch/src/aarch64/gic/gicv3.rs
Normal file
275
arch/src/aarch64/gic/gicv3.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod kvm {
|
||||
use crate::aarch64::gic::dist_regs::{get_dist_regs, read_ctlr, set_dist_regs, write_ctlr};
|
||||
use crate::aarch64::gic::icc_regs::{get_icc_regs, set_icc_regs};
|
||||
use crate::aarch64::gic::kvm::{save_pending_tables, KvmGICDevice};
|
||||
use crate::aarch64::gic::redist_regs::{get_redist_regs, set_redist_regs};
|
||||
use crate::aarch64::gic::GICDevice;
|
||||
use crate::layout;
|
||||
use anyhow::anyhow;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use std::any::Any;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::{boxed::Box, result};
|
||||
use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
|
||||
/// Errors thrown while saving/restoring the GICv3.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Error in saving RDIST pending tables into guest RAM.
|
||||
SavePendingTables(crate::aarch64::gic::Error),
|
||||
/// Error in saving GIC distributor registers.
|
||||
SaveDistributorRegisters(crate::aarch64::gic::Error),
|
||||
/// Error in restoring GIC distributor registers.
|
||||
RestoreDistributorRegisters(crate::aarch64::gic::Error),
|
||||
/// Error in saving GIC distributor control registers.
|
||||
SaveDistributorCtrlRegisters(crate::aarch64::gic::Error),
|
||||
/// Error in restoring GIC distributor control registers.
|
||||
RestoreDistributorCtrlRegisters(crate::aarch64::gic::Error),
|
||||
/// Error in saving GIC redistributor registers.
|
||||
SaveRedistributorRegisters(crate::aarch64::gic::Error),
|
||||
/// Error in restoring GIC redistributor registers.
|
||||
RestoreRedistributorRegisters(crate::aarch64::gic::Error),
|
||||
/// Error in saving GIC CPU interface registers.
|
||||
SaveICCRegisters(crate::aarch64::gic::Error),
|
||||
/// Error in restoring GIC CPU interface registers.
|
||||
RestoreICCRegisters(crate::aarch64::gic::Error),
|
||||
}
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub struct KvmGICv3 {
|
||||
/// The hypervisor agnostic device
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
|
||||
/// Vector holding values of GICR_TYPER for each vCPU
|
||||
gicr_typers: Vec<u64>,
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Gicv3State {
|
||||
dist: Vec<u32>,
|
||||
rdist: Vec<u32>,
|
||||
icc: Vec<u32>,
|
||||
// special register that enables interrupts and affinity routing
|
||||
gicd_ctlr: u32,
|
||||
}
|
||||
|
||||
impl KvmGICv3 {
|
||||
// Unfortunately bindgen omits defines that are based on other defines.
|
||||
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
|
||||
pub const SZ_64K: u64 = 0x0001_0000;
|
||||
const KVM_VGIC_V3_DIST_SIZE: u64 = KvmGICv3::SZ_64K;
|
||||
const KVM_VGIC_V3_REDIST_SIZE: u64 = (2 * KvmGICv3::SZ_64K);
|
||||
|
||||
// Device trees specific constants
|
||||
pub const ARCH_GIC_V3_MAINT_IRQ: u32 = 9;
|
||||
|
||||
/// Get the address of the GIC distributor.
|
||||
pub fn get_dist_addr() -> u64 {
|
||||
layout::MAPPED_IO_START - KvmGICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC distributor.
|
||||
pub fn get_dist_size() -> u64 {
|
||||
KvmGICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the address of the GIC redistributors.
|
||||
pub fn get_redists_addr(vcpu_count: u64) -> u64 {
|
||||
KvmGICv3::get_dist_addr() - KvmGICv3::get_redists_size(vcpu_count)
|
||||
}
|
||||
|
||||
/// Get the size of the GIC redistributors.
|
||||
pub fn get_redists_size(vcpu_count: u64) -> u64 {
|
||||
vcpu_count * KvmGICv3::KVM_VGIC_V3_REDIST_SIZE
|
||||
}
|
||||
|
||||
/// Save the state of GIC.
|
||||
fn state(&self, gicr_typers: &[u64]) -> Result<Gicv3State> {
|
||||
// Flush redistributors pending tables to guest RAM.
|
||||
save_pending_tables(&self.device()).map_err(Error::SavePendingTables)?;
|
||||
|
||||
let gicd_ctlr =
|
||||
read_ctlr(&self.device()).map_err(Error::SaveDistributorCtrlRegisters)?;
|
||||
|
||||
let dist_state =
|
||||
get_dist_regs(&self.device()).map_err(Error::SaveDistributorRegisters)?;
|
||||
|
||||
let rdist_state = get_redist_regs(&self.device(), &gicr_typers)
|
||||
.map_err(Error::SaveRedistributorRegisters)?;
|
||||
|
||||
let icc_state =
|
||||
get_icc_regs(&self.device(), &gicr_typers).map_err(Error::SaveICCRegisters)?;
|
||||
|
||||
Ok(Gicv3State {
|
||||
dist: dist_state,
|
||||
rdist: rdist_state,
|
||||
icc: icc_state,
|
||||
gicd_ctlr,
|
||||
})
|
||||
}
|
||||
|
||||
/// Restore the state of GIC.
|
||||
fn set_state(&mut self, gicr_typers: &[u64], state: &Gicv3State) -> Result<()> {
|
||||
write_ctlr(&self.device(), state.gicd_ctlr)
|
||||
.map_err(Error::RestoreDistributorCtrlRegisters)?;
|
||||
|
||||
set_dist_regs(&self.device(), &state.dist)
|
||||
.map_err(Error::RestoreDistributorRegisters)?;
|
||||
|
||||
set_redist_regs(&self.device(), gicr_typers, &state.rdist)
|
||||
.map_err(Error::RestoreRedistributorRegisters)?;
|
||||
|
||||
set_icc_regs(&self.device(), &gicr_typers, &state.icc)
|
||||
.map_err(Error::RestoreICCRegisters)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for KvmGICv3 {
|
||||
fn device(&self) -> &Arc<dyn hypervisor::Device> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
fn fdt_compatibility(&self) -> &str {
|
||||
"arm,gic-v3"
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
KvmGICv3::ARCH_GIC_V3_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn device_properties(&self) -> &[u64] {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
fn vcpu_count(&self) -> u64 {
|
||||
self.vcpu_count
|
||||
}
|
||||
|
||||
fn set_gicr_typers(&mut self, gicr_typers: Vec<u64>) {
|
||||
self.gicr_typers = gicr_typers;
|
||||
}
|
||||
|
||||
fn as_any_concrete_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl KvmGICDevice for KvmGICv3 {
|
||||
fn version() -> u32 {
|
||||
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3
|
||||
}
|
||||
|
||||
fn create_device(
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
vcpu_count: u64,
|
||||
) -> Box<dyn GICDevice> {
|
||||
Box::new(KvmGICv3 {
|
||||
device,
|
||||
gicr_typers: vec![0; vcpu_count.try_into().unwrap()],
|
||||
properties: [
|
||||
KvmGICv3::get_dist_addr(),
|
||||
KvmGICv3::get_dist_size(),
|
||||
KvmGICv3::get_redists_addr(vcpu_count),
|
||||
KvmGICv3::get_redists_size(vcpu_count),
|
||||
],
|
||||
vcpu_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(
|
||||
_vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &dyn GICDevice,
|
||||
) -> crate::aarch64::gic::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(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_DIST),
|
||||
&KvmGICv3::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(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_REDIST),
|
||||
&KvmGICv3::get_redists_addr(gic_device.vcpu_count()) as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub const GIC_V3_SNAPSHOT_ID: &str = "gic-v3";
|
||||
impl Snapshottable for KvmGICv3 {
|
||||
fn id(&self) -> String {
|
||||
GIC_V3_SNAPSHOT_ID.to_string()
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
let gicr_typers = self.gicr_typers.clone();
|
||||
let snapshot = serde_json::to_vec(&self.state(&gicr_typers).unwrap())
|
||||
.map_err(|e| MigratableError::Snapshot(e.into()))?;
|
||||
|
||||
let mut gic_v3_snapshot = Snapshot::new(self.id().as_str());
|
||||
gic_v3_snapshot.add_data_section(SnapshotDataSection {
|
||||
id: format!("{}-section", self.id()),
|
||||
snapshot,
|
||||
});
|
||||
|
||||
Ok(gic_v3_snapshot)
|
||||
}
|
||||
|
||||
fn restore(&mut self, snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
if let Some(gic_v3_section) = snapshot
|
||||
.snapshot_data
|
||||
.get(&format!("{}-section", self.id()))
|
||||
{
|
||||
let gic_v3_state = match serde_json::from_slice(&gic_v3_section.snapshot) {
|
||||
Ok(state) => state,
|
||||
Err(error) => {
|
||||
return Err(MigratableError::Restore(anyhow!(
|
||||
"Could not deserialize GICv3 {}",
|
||||
error
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let gicr_typers = self.gicr_typers.clone();
|
||||
return self.set_state(&gicr_typers, &gic_v3_state).map_err(|e| {
|
||||
MigratableError::Restore(anyhow!("Could not restore GICv3 state {:?}", e))
|
||||
});
|
||||
}
|
||||
|
||||
Err(MigratableError::Restore(anyhow!(
|
||||
"Could not find GICv3 snapshot section"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for KvmGICv3 {}
|
||||
impl Transportable for KvmGICv3 {}
|
||||
impl Migratable for KvmGICv3 {}
|
||||
}
|
||||
@@ -2,18 +2,23 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod kvm {
|
||||
use std::any::Any;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::{boxed::Box, result};
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
use crate::aarch64::gic::gicv3::kvm::KvmGICv3;
|
||||
use crate::aarch64::gic::kvm::KvmGICDevice;
|
||||
use crate::aarch64::gic::{Error, GICDevice};
|
||||
use crate::aarch64::gicv3::kvm::KvmGICv3;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
|
||||
pub struct KvmGICv3ITS {
|
||||
/// The hypervisor agnostic device
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
|
||||
/// Vector holding values of GICR_TYPER for each vCPU
|
||||
gicr_typers: Vec<u64>,
|
||||
|
||||
/// GIC device properties, to be used for setting up the fdt entry
|
||||
gic_properties: [u64; 4],
|
||||
|
||||
@@ -49,7 +54,7 @@ pub mod kvm {
|
||||
true
|
||||
}
|
||||
|
||||
fn msi_compatiblility(&self) -> &str {
|
||||
fn msi_compatibility(&self) -> &str {
|
||||
"arm,gic-v3-its"
|
||||
}
|
||||
|
||||
@@ -68,6 +73,14 @@ pub mod kvm {
|
||||
fn vcpu_count(&self) -> u64 {
|
||||
self.vcpu_count
|
||||
}
|
||||
|
||||
fn set_gicr_typers(&mut self, gicr_typers: Vec<u64>) {
|
||||
self.gicr_typers = gicr_typers;
|
||||
}
|
||||
|
||||
fn as_any_concrete_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl KvmGICDevice for KvmGICv3ITS {
|
||||
@@ -81,6 +94,7 @@ pub mod kvm {
|
||||
) -> Box<dyn GICDevice> {
|
||||
Box::new(KvmGICv3ITS {
|
||||
device,
|
||||
gicr_typers: vec![0; vcpu_count.try_into().unwrap()],
|
||||
gic_properties: [
|
||||
KvmGICv3::get_dist_addr(),
|
||||
KvmGICv3::get_dist_size(),
|
||||
196
arch/src/aarch64/gic/icc_regs.rs
Normal file
196
arch/src/aarch64/gic/icc_regs.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{Error, Result};
|
||||
use hypervisor::kvm::kvm_bindings::{
|
||||
kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, 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,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
const KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT: u32 = 32;
|
||||
const KVM_DEV_ARM_VGIC_V3_MPIDR_MASK: u64 = 0xffffffff << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT as u64;
|
||||
|
||||
const ICC_CTLR_EL1_PRIBITS_SHIFT: u32 = 8;
|
||||
const ICC_CTLR_EL1_PRIBITS_MASK: u32 = 7 << ICC_CTLR_EL1_PRIBITS_SHIFT;
|
||||
|
||||
macro_rules! arm64_vgic_sys_reg {
|
||||
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: expr) => {
|
||||
const $name: 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);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! SYS_ICC_AP0Rn_EL1 {
|
||||
($name: tt, $n: tt) => {
|
||||
arm64_vgic_sys_reg!($name, 3, 0, 12, 8, (4 | $n));
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! SYS_ICC_AP1Rn_EL1 {
|
||||
($name: tt, $n: tt) => {
|
||||
arm64_vgic_sys_reg!($name, 3, 0, 12, 9, $n);
|
||||
};
|
||||
}
|
||||
|
||||
arm64_vgic_sys_reg!(SYS_ICC_SRE_EL1, 3, 0, 12, 12, 5);
|
||||
arm64_vgic_sys_reg!(SYS_ICC_CTLR_EL1, 3, 0, 12, 12, 4);
|
||||
arm64_vgic_sys_reg!(SYS_ICC_IGRPEN0_EL1, 3, 0, 12, 12, 6);
|
||||
arm64_vgic_sys_reg!(SYS_ICC_IGRPEN1_EL1, 3, 0, 12, 12, 7);
|
||||
arm64_vgic_sys_reg!(SYS_ICC_PMR_EL1, 3, 0, 4, 6, 0);
|
||||
arm64_vgic_sys_reg!(SYS_ICC_BPR0_EL1, 3, 0, 12, 8, 3);
|
||||
arm64_vgic_sys_reg!(SYS_ICC_BPR1_EL1, 3, 0, 12, 12, 3);
|
||||
SYS_ICC_AP0Rn_EL1!(SYS_ICC_AP0R0_EL1, 0);
|
||||
SYS_ICC_AP0Rn_EL1!(SYS_ICC_AP0R1_EL1, 1);
|
||||
SYS_ICC_AP0Rn_EL1!(SYS_ICC_AP0R2_EL1, 2);
|
||||
SYS_ICC_AP0Rn_EL1!(SYS_ICC_AP0R3_EL1, 3);
|
||||
SYS_ICC_AP1Rn_EL1!(SYS_ICC_AP1R0_EL1, 0);
|
||||
SYS_ICC_AP1Rn_EL1!(SYS_ICC_AP1R1_EL1, 1);
|
||||
SYS_ICC_AP1Rn_EL1!(SYS_ICC_AP1R2_EL1, 2);
|
||||
SYS_ICC_AP1Rn_EL1!(SYS_ICC_AP1R3_EL1, 3);
|
||||
|
||||
static VGIC_ICC_REGS: &'static [u64] = &[
|
||||
SYS_ICC_SRE_EL1,
|
||||
SYS_ICC_CTLR_EL1,
|
||||
SYS_ICC_IGRPEN0_EL1,
|
||||
SYS_ICC_IGRPEN1_EL1,
|
||||
SYS_ICC_PMR_EL1,
|
||||
SYS_ICC_BPR0_EL1,
|
||||
SYS_ICC_BPR1_EL1,
|
||||
SYS_ICC_AP0R0_EL1,
|
||||
SYS_ICC_AP0R1_EL1,
|
||||
SYS_ICC_AP0R2_EL1,
|
||||
SYS_ICC_AP0R3_EL1,
|
||||
SYS_ICC_AP1R0_EL1,
|
||||
SYS_ICC_AP1R1_EL1,
|
||||
SYS_ICC_AP1R2_EL1,
|
||||
SYS_ICC_AP1R3_EL1,
|
||||
];
|
||||
|
||||
fn icc_attr_access(
|
||||
gic: &Arc<dyn hypervisor::Device>,
|
||||
offset: u64,
|
||||
typer: u64,
|
||||
val: &u32,
|
||||
set: bool,
|
||||
) -> Result<()> {
|
||||
let mut gic_icc_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS,
|
||||
attr: ((typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | offset), // this needs the mpidr
|
||||
addr: val as *const u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
if set {
|
||||
#[allow(clippy::unnecessary_mut_passed)]
|
||||
gic.set_device_attr(&mut gic_icc_attr)
|
||||
.map_err(Error::SetDeviceAttribute)?;
|
||||
} else {
|
||||
gic.get_device_attr(&mut gic_icc_attr)
|
||||
.map_err(Error::GetDeviceAttribute)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get ICC registers.
|
||||
pub fn get_icc_regs(gic: &Arc<dyn hypervisor::Device>, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
||||
let mut state: Vec<u32> = Vec::new();
|
||||
// We need this for the ICC_AP<m>R<n>_EL1 registers.
|
||||
let mut num_priority_bits = 0;
|
||||
|
||||
for ix in gicr_typer {
|
||||
let i = *ix;
|
||||
for icc_offset in VGIC_ICC_REGS {
|
||||
let val = 0;
|
||||
if *icc_offset == SYS_ICC_CTLR_EL1 {
|
||||
// calculate priority bits by reading the ctrl_el1 register.
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
// The priority bits are found in the ICC_CTLR_EL1 register (bits from 10:8).
|
||||
// See page 194 from https://static.docs.arm.com/ihi0069/c/IHI0069C_gic_
|
||||
// architecture_specification.pdf.
|
||||
// Citation:
|
||||
// "Priority bits. Read-only and writes are ignored. The number of priority bits
|
||||
// implemented, minus one."
|
||||
num_priority_bits =
|
||||
((val & ICC_CTLR_EL1_PRIBITS_MASK) >> ICC_CTLR_EL1_PRIBITS_SHIFT) + 1;
|
||||
state.push(val);
|
||||
}
|
||||
// As per ARMv8 documentation: https://static.docs.arm.com/ihi0069/c/IHI0069C_
|
||||
// gic_architecture_specification.pdf
|
||||
// page 178,
|
||||
// ICC_AP0R1_EL1 is only implemented in implementations that support 6 or more bits of
|
||||
// priority.
|
||||
// ICC_AP0R2_EL1 and ICC_AP0R3_EL1 are only implemented in implementations that support
|
||||
// 7 bits of priority.
|
||||
else if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
||||
if num_priority_bits >= 6 {
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
state.push(val);
|
||||
}
|
||||
} else if *icc_offset == SYS_ICC_AP0R2_EL1
|
||||
|| *icc_offset == SYS_ICC_AP0R3_EL1
|
||||
|| *icc_offset == SYS_ICC_AP1R2_EL1
|
||||
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
||||
{
|
||||
if num_priority_bits == 7 {
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
state.push(val);
|
||||
}
|
||||
} else {
|
||||
icc_attr_access(gic, *icc_offset, i, &val, false)?;
|
||||
state.push(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// Set ICC registers.
|
||||
pub fn set_icc_regs(
|
||||
gic: &Arc<dyn hypervisor::Device>,
|
||||
gicr_typer: &[u64],
|
||||
state: &[u32],
|
||||
) -> Result<()> {
|
||||
let mut num_priority_bits = 0;
|
||||
let mut idx = 0;
|
||||
for ix in gicr_typer {
|
||||
let i = *ix;
|
||||
for icc_offset in VGIC_ICC_REGS {
|
||||
if *icc_offset == SYS_ICC_CTLR_EL1 {
|
||||
let ctrl_el1 = state[idx];
|
||||
num_priority_bits =
|
||||
((ctrl_el1 & ICC_CTLR_EL1_PRIBITS_MASK) >> ICC_CTLR_EL1_PRIBITS_SHIFT) + 1;
|
||||
}
|
||||
if *icc_offset == SYS_ICC_AP0R1_EL1 || *icc_offset == SYS_ICC_AP1R1_EL1 {
|
||||
if num_priority_bits >= 6 {
|
||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
||||
idx += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if *icc_offset == SYS_ICC_AP0R2_EL1
|
||||
|| *icc_offset == SYS_ICC_AP0R3_EL1
|
||||
|| *icc_offset == SYS_ICC_AP1R2_EL1
|
||||
|| *icc_offset == SYS_ICC_AP1R3_EL1
|
||||
{
|
||||
if num_priority_bits == 7 {
|
||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
||||
idx += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
icc_attr_access(gic, *icc_offset, i, &state[idx], true)?;
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,6 +1,16 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod dist_regs;
|
||||
pub mod gicv3;
|
||||
pub mod gicv3_its;
|
||||
pub mod icc_regs;
|
||||
pub mod redist_regs;
|
||||
|
||||
pub use self::dist_regs::{get_dist_regs, read_ctlr, set_dist_regs, write_ctlr};
|
||||
pub use self::icc_regs::{get_icc_regs, set_icc_regs};
|
||||
pub use self::redist_regs::{get_redist_regs, set_redist_regs};
|
||||
use std::any::Any;
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -11,10 +21,12 @@ pub enum Error {
|
||||
CreateGIC(hypervisor::HypervisorVmError),
|
||||
/// Error while setting device attributes for the GIC.
|
||||
SetDeviceAttribute(hypervisor::HypervisorDeviceError),
|
||||
/// Error while getting device attributes for the GIC.
|
||||
GetDeviceAttribute(hypervisor::HypervisorDeviceError),
|
||||
}
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub trait GICDevice {
|
||||
pub trait GICDevice: Send {
|
||||
/// Returns the hypervisor agnostic Device of the GIC device
|
||||
fn device(&self) -> &Arc<dyn hypervisor::Device>;
|
||||
|
||||
@@ -36,7 +48,7 @@ pub trait GICDevice {
|
||||
}
|
||||
|
||||
/// Returns the MSI compatibility property of the device
|
||||
fn msi_compatiblility(&self) -> &str {
|
||||
fn msi_compatibility(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
@@ -44,14 +56,18 @@ pub trait GICDevice {
|
||||
fn msi_properties(&self) -> &[u64] {
|
||||
&[]
|
||||
}
|
||||
|
||||
/// Get the values of GICR_TYPER for each vCPU.
|
||||
fn set_gicr_typers(&mut self, gicr_typers: Vec<u64>);
|
||||
|
||||
/// Downcast the trait object to its concrete type.
|
||||
fn as_any_concrete_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
pub mod kvm {
|
||||
use super::GICDevice;
|
||||
use super::Result;
|
||||
use crate::aarch64::gicv2::kvm::KvmGICv2;
|
||||
use crate::aarch64::gicv3::kvm::KvmGICv3;
|
||||
use crate::aarch64::gicv3_its::kvm::KvmGICv3ITS;
|
||||
use crate::aarch64::gic::gicv3_its::kvm::KvmGICv3ITS;
|
||||
use crate::layout;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use std::boxed::Box;
|
||||
@@ -107,6 +123,27 @@ pub mod kvm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a GIC device attribute
|
||||
fn get_device_attribute(
|
||||
device: &Arc<dyn hypervisor::Device>,
|
||||
group: u32,
|
||||
attr: u64,
|
||||
addr: u64,
|
||||
flags: u32,
|
||||
) -> Result<()> {
|
||||
let mut attr = kvm_bindings::kvm_device_attr {
|
||||
group,
|
||||
attr,
|
||||
addr,
|
||||
flags,
|
||||
};
|
||||
device
|
||||
.get_device_attr(&mut attr)
|
||||
.map_err(super::Error::GetDeviceAttribute)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finalize the setup of a GIC device
|
||||
fn finalize_device(gic_device: &dyn GICDevice) -> Result<()> {
|
||||
/* We need to tell the kernel how many irqs to support with this vgic.
|
||||
@@ -137,6 +174,7 @@ pub mod kvm {
|
||||
}
|
||||
|
||||
/// Method to initialize the GIC device
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
fn new(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
|
||||
let vgic_fd = Self::init_device(vm)?;
|
||||
|
||||
@@ -150,25 +188,24 @@ pub mod kvm {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a GIC device.
|
||||
/// Create a GICv3-ITS 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: &Arc<dyn hypervisor::Vm>,
|
||||
vcpu_count: u64,
|
||||
its_required: bool,
|
||||
) -> Result<Box<dyn GICDevice>> {
|
||||
if its_required {
|
||||
KvmGICv3ITS::new(vm, vcpu_count)
|
||||
} else {
|
||||
KvmGICv3ITS::new(vm, vcpu_count).or_else(|_| {
|
||||
debug!("Failed to create GICv3-ITS, will try GICv3 instead.");
|
||||
KvmGICv3::new(vm, vcpu_count).or_else(|_| {
|
||||
debug!("Failed to create GICv3, will try GICv2 instead.");
|
||||
KvmGICv2::new(vm, vcpu_count)
|
||||
})
|
||||
})
|
||||
}
|
||||
pub fn create_gic(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
|
||||
debug!("creating a GICv3-ITS");
|
||||
KvmGICv3ITS::new(vm, vcpu_count)
|
||||
}
|
||||
|
||||
/// Function that saves RDIST pending tables into guest RAM.
|
||||
///
|
||||
/// The tables get flushed to guest RAM whenever the VM gets stopped.
|
||||
pub fn save_pending_tables(gic: &Arc<dyn hypervisor::Device>) -> Result<()> {
|
||||
let init_gic_attr = kvm_bindings::kvm_device_attr {
|
||||
group: kvm_bindings::KVM_DEV_ARM_VGIC_GRP_CTRL,
|
||||
attr: u64::from(kvm_bindings::KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES),
|
||||
addr: 0,
|
||||
flags: 0,
|
||||
};
|
||||
gic.set_device_attr(&init_gic_attr)
|
||||
.map_err(super::Error::SetDeviceAttribute)
|
||||
}
|
||||
}
|
||||
179
arch/src/aarch64/gic/redist_regs.rs
Normal file
179
arch/src/aarch64/gic/redist_regs.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{Error, Result};
|
||||
use hypervisor::kvm::kvm_bindings::{kvm_device_attr, KVM_DEV_ARM_VGIC_GRP_REDIST_REGS};
|
||||
use std::sync::Arc;
|
||||
|
||||
// Relevant redistributor registers that we want to save/restore.
|
||||
const GICR_CTLR: u32 = 0x0000;
|
||||
const GICR_STATUSR: u32 = 0x0010;
|
||||
const GICR_WAKER: u32 = 0x0014;
|
||||
const GICR_PROPBASER: u32 = 0x0070;
|
||||
const GICR_PENDBASER: u32 = 0x0078;
|
||||
|
||||
/* SGI and PPI Redistributor registers, offsets from RD_base */
|
||||
/*
|
||||
* Redistributor frame offsets from RD_base which is actually SZ_
|
||||
*/
|
||||
const GICR_SGI_OFFSET: u32 = 0x0001_0000;
|
||||
const GICR_IGROUPR0: u32 = GICR_SGI_OFFSET + 0x0080;
|
||||
const GICR_ICENABLER0: u32 = GICR_SGI_OFFSET + 0x0180;
|
||||
const GICR_ISENABLER0: u32 = GICR_SGI_OFFSET + 0x0100;
|
||||
const GICR_ISPENDR0: u32 = GICR_SGI_OFFSET + 0x0200;
|
||||
const GICR_ICPENDR0: u32 = GICR_SGI_OFFSET + 0x0280;
|
||||
const GICR_ISACTIVER0: u32 = GICR_SGI_OFFSET + 0x0300;
|
||||
const GICR_ICACTIVER0: u32 = GICR_SGI_OFFSET + 0x0380;
|
||||
const GICR_IPRIORITYR0: u32 = GICR_SGI_OFFSET + 0x0400;
|
||||
const GICR_ICFGR0: u32 = GICR_SGI_OFFSET + 0x0C00;
|
||||
|
||||
const KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT: u32 = 32;
|
||||
const KVM_DEV_ARM_VGIC_V3_MPIDR_MASK: u64 = 0xffffffff << KVM_DEV_ARM_VGIC_V3_MPIDR_SHIFT as u64;
|
||||
|
||||
/// This is how we represent the registers of a distributor.
|
||||
/// It is relrvant their offset from the base address of the
|
||||
/// distributor.
|
||||
/// Each register has a different number
|
||||
/// of bits_per_irq and is therefore variable length.
|
||||
/// First 32 interrupts (0-32) are private to each CPU (SGIs and PPIs).
|
||||
/// and so we save the first irq to identify between the type of the interrupt
|
||||
/// that the respective register deals with.
|
||||
struct RdistReg {
|
||||
/// Offset from distributor address.
|
||||
base: u32,
|
||||
/// Length of the register.
|
||||
length: u8,
|
||||
}
|
||||
|
||||
// All or at least the registers we are interested in are 32 bit, so
|
||||
// we use a constant for size(u32).
|
||||
const REG_SIZE: u8 = 4;
|
||||
|
||||
// Creates a vgic redistributor register.
|
||||
macro_rules! VGIC_RDIST_REG {
|
||||
($base:expr, $len:expr) => {
|
||||
RdistReg {
|
||||
base: $base,
|
||||
length: $len,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// List with relevant distributor registers that we will be restoring.
|
||||
static VGIC_RDIST_REGS: &'static [RdistReg] = &[
|
||||
VGIC_RDIST_REG!(GICR_CTLR, 4),
|
||||
VGIC_RDIST_REG!(GICR_STATUSR, 4),
|
||||
VGIC_RDIST_REG!(GICR_WAKER, 4),
|
||||
VGIC_RDIST_REG!(GICR_PROPBASER, 8),
|
||||
VGIC_RDIST_REG!(GICR_PENDBASER, 8),
|
||||
];
|
||||
|
||||
// List with relevant distributor registers that we will be restoring.
|
||||
static VGIC_SGI_REGS: &'static [RdistReg] = &[
|
||||
VGIC_RDIST_REG!(GICR_IGROUPR0, 4),
|
||||
VGIC_RDIST_REG!(GICR_ICENABLER0, 4),
|
||||
VGIC_RDIST_REG!(GICR_ISENABLER0, 4),
|
||||
VGIC_RDIST_REG!(GICR_ICFGR0, 8),
|
||||
VGIC_RDIST_REG!(GICR_ICPENDR0, 4),
|
||||
VGIC_RDIST_REG!(GICR_ISPENDR0, 4),
|
||||
VGIC_RDIST_REG!(GICR_ICACTIVER0, 4),
|
||||
VGIC_RDIST_REG!(GICR_ISACTIVER0, 4),
|
||||
VGIC_RDIST_REG!(GICR_IPRIORITYR0, 32),
|
||||
];
|
||||
|
||||
fn redist_attr_access(
|
||||
gic: &Arc<dyn hypervisor::Device>,
|
||||
offset: u32,
|
||||
typer: u64,
|
||||
val: &u32,
|
||||
set: bool,
|
||||
) -> Result<()> {
|
||||
let mut gic_dist_attr = kvm_device_attr {
|
||||
group: KVM_DEV_ARM_VGIC_GRP_REDIST_REGS,
|
||||
attr: (typer & KVM_DEV_ARM_VGIC_V3_MPIDR_MASK) | (offset as u64), // this needs the mpidr
|
||||
addr: val as *const u32 as u64,
|
||||
flags: 0,
|
||||
};
|
||||
if set {
|
||||
#[allow(clippy::unnecessary_mut_passed)]
|
||||
gic.set_device_attr(&mut gic_dist_attr)
|
||||
.map_err(Error::SetDeviceAttribute)?;
|
||||
} else {
|
||||
gic.get_device_attr(&mut gic_dist_attr)
|
||||
.map_err(Error::GetDeviceAttribute)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn access_redists_aux(
|
||||
gic: &Arc<dyn hypervisor::Device>,
|
||||
gicr_typer: &[u64],
|
||||
state: &mut Vec<u32>,
|
||||
reg_list: &'static [RdistReg],
|
||||
idx: &mut usize,
|
||||
set: bool,
|
||||
) -> Result<()> {
|
||||
for i in gicr_typer {
|
||||
for rdreg in reg_list {
|
||||
let mut base = rdreg.base;
|
||||
let end = base + rdreg.length as u32;
|
||||
|
||||
while base < end {
|
||||
let mut val = 0;
|
||||
if set {
|
||||
val = state[*idx];
|
||||
redist_attr_access(gic, base, *i, &val, true)?;
|
||||
*idx += 1;
|
||||
} else {
|
||||
redist_attr_access(gic, base, *i, &val, false)?;
|
||||
state.push(val);
|
||||
}
|
||||
base += REG_SIZE as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get redistributor registers.
|
||||
pub fn get_redist_regs(gic: &Arc<dyn hypervisor::Device>, gicr_typer: &[u64]) -> Result<Vec<u32>> {
|
||||
let mut state = Vec::new();
|
||||
let mut idx: usize = 0;
|
||||
access_redists_aux(
|
||||
gic,
|
||||
&gicr_typer,
|
||||
&mut state,
|
||||
VGIC_RDIST_REGS,
|
||||
&mut idx,
|
||||
false,
|
||||
)?;
|
||||
|
||||
access_redists_aux(gic, &gicr_typer, &mut state, VGIC_SGI_REGS, &mut idx, false)?;
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// Set redistributor registers.
|
||||
pub fn set_redist_regs(
|
||||
gic: &Arc<dyn hypervisor::Device>,
|
||||
gicr_typer: &[u64],
|
||||
state: &[u32],
|
||||
) -> Result<()> {
|
||||
let mut idx: usize = 0;
|
||||
let mut mut_state = state.to_owned();
|
||||
access_redists_aux(
|
||||
gic,
|
||||
gicr_typer,
|
||||
&mut mut_state,
|
||||
VGIC_RDIST_REGS,
|
||||
&mut idx,
|
||||
true,
|
||||
)?;
|
||||
access_redists_aux(
|
||||
gic,
|
||||
gicr_typer,
|
||||
&mut mut_state,
|
||||
VGIC_SGI_REGS,
|
||||
&mut idx,
|
||||
true,
|
||||
)
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod kvm {
|
||||
use crate::aarch64::gic::kvm::KvmGICDevice;
|
||||
use crate::aarch64::gic::{Error, GICDevice};
|
||||
use std::{boxed::Box, result};
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
use crate::layout;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Represent a GIC v2 device
|
||||
pub struct KvmGICv2 {
|
||||
/// The hypervisor agnostic device
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
|
||||
/// 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 KvmGICv2 {
|
||||
// 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 {
|
||||
layout::MAPPED_IO_START - KvmGICv2::KVM_VGIC_V2_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC_v2 distributor.
|
||||
const fn get_dist_size() -> u64 {
|
||||
KvmGICv2::KVM_VGIC_V2_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the address of the GIC_v2 CPU.
|
||||
const fn get_cpu_addr() -> u64 {
|
||||
KvmGICv2::get_dist_addr() - KvmGICv2::KVM_VGIC_V2_CPU_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC_v2 CPU.
|
||||
const fn get_cpu_size() -> u64 {
|
||||
KvmGICv2::KVM_VGIC_V2_CPU_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for KvmGICv2 {
|
||||
fn device(&self) -> &Arc<dyn hypervisor::Device> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
fn device_properties(&self) -> &[u64] {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
fn fdt_compatibility(&self) -> &str {
|
||||
"arm,gic-400"
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
KvmGICv2::ARCH_GIC_V2_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn vcpu_count(&self) -> u64 {
|
||||
self.vcpu_count
|
||||
}
|
||||
}
|
||||
|
||||
impl KvmGICDevice for KvmGICv2 {
|
||||
fn version() -> u32 {
|
||||
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V2
|
||||
}
|
||||
|
||||
fn create_device(
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
vcpu_count: u64,
|
||||
) -> Box<dyn GICDevice> {
|
||||
Box::new(KvmGICv2 {
|
||||
device,
|
||||
properties: [
|
||||
KvmGICv2::get_dist_addr(),
|
||||
KvmGICv2::get_dist_size(),
|
||||
KvmGICv2::get_cpu_addr(),
|
||||
KvmGICv2::get_cpu_size(),
|
||||
],
|
||||
vcpu_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(
|
||||
_vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &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(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_DIST),
|
||||
&KvmGICv2::get_dist_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
/* Setting up the CPU attribute. */
|
||||
Self::set_device_attribute(
|
||||
gic_device.device(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V2_ADDR_TYPE_CPU),
|
||||
&KvmGICv2::get_cpu_addr() as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod kvm {
|
||||
use crate::aarch64::gic::kvm::KvmGICDevice;
|
||||
use crate::aarch64::gic::{Error, GICDevice};
|
||||
use crate::layout;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use std::sync::Arc;
|
||||
use std::{boxed::Box, result};
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
pub struct KvmGICv3 {
|
||||
/// The hypervisor agnostic device
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
|
||||
/// 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 KvmGICv3 {
|
||||
// Unfortunately bindgen omits defines that are based on other defines.
|
||||
// See arch/arm64/include/uapi/asm/kvm.h file from the linux kernel.
|
||||
pub const SZ_64K: u64 = 0x0001_0000;
|
||||
const KVM_VGIC_V3_DIST_SIZE: u64 = KvmGICv3::SZ_64K;
|
||||
const KVM_VGIC_V3_REDIST_SIZE: u64 = (2 * KvmGICv3::SZ_64K);
|
||||
|
||||
// Device trees specific constants
|
||||
pub const ARCH_GIC_V3_MAINT_IRQ: u32 = 9;
|
||||
|
||||
/// Get the address of the GIC distributor.
|
||||
pub fn get_dist_addr() -> u64 {
|
||||
layout::MAPPED_IO_START - KvmGICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the size of the GIC distributor.
|
||||
pub fn get_dist_size() -> u64 {
|
||||
KvmGICv3::KVM_VGIC_V3_DIST_SIZE
|
||||
}
|
||||
|
||||
/// Get the address of the GIC redistributors.
|
||||
pub fn get_redists_addr(vcpu_count: u64) -> u64 {
|
||||
KvmGICv3::get_dist_addr() - KvmGICv3::get_redists_size(vcpu_count)
|
||||
}
|
||||
|
||||
/// Get the size of the GIC redistributors.
|
||||
pub fn get_redists_size(vcpu_count: u64) -> u64 {
|
||||
vcpu_count * KvmGICv3::KVM_VGIC_V3_REDIST_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl GICDevice for KvmGICv3 {
|
||||
fn device(&self) -> &Arc<dyn hypervisor::Device> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
fn fdt_compatibility(&self) -> &str {
|
||||
"arm,gic-v3"
|
||||
}
|
||||
|
||||
fn fdt_maint_irq(&self) -> u32 {
|
||||
KvmGICv3::ARCH_GIC_V3_MAINT_IRQ
|
||||
}
|
||||
|
||||
fn device_properties(&self) -> &[u64] {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
fn vcpu_count(&self) -> u64 {
|
||||
self.vcpu_count
|
||||
}
|
||||
}
|
||||
|
||||
impl KvmGICDevice for KvmGICv3 {
|
||||
fn version() -> u32 {
|
||||
kvm_bindings::kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3
|
||||
}
|
||||
|
||||
fn create_device(
|
||||
device: Arc<dyn hypervisor::Device>,
|
||||
vcpu_count: u64,
|
||||
) -> Box<dyn GICDevice> {
|
||||
Box::new(KvmGICv3 {
|
||||
device,
|
||||
properties: [
|
||||
KvmGICv3::get_dist_addr(),
|
||||
KvmGICv3::get_dist_size(),
|
||||
KvmGICv3::get_redists_addr(vcpu_count),
|
||||
KvmGICv3::get_redists_size(vcpu_count),
|
||||
],
|
||||
vcpu_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn init_device_attributes(
|
||||
_vm: &Arc<dyn hypervisor::Vm>,
|
||||
gic_device: &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(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_DIST),
|
||||
&KvmGICv3::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(),
|
||||
kvm_bindings::KVM_DEV_ARM_VGIC_GRP_ADDR,
|
||||
u64::from(kvm_bindings::KVM_VGIC_V3_ADDR_TYPE_REDIST),
|
||||
&KvmGICv3::get_redists_addr(gic_device.vcpu_count()) as *const u64 as u64,
|
||||
0,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,13 +37,13 @@
|
||||
// | |
|
||||
// 144 M +---------------------------------------------------------------|
|
||||
// | |
|
||||
// | Reserverd (now GIC is here) |
|
||||
// | Reserved (now GIC is here) |
|
||||
// | |
|
||||
// 0GB +---------------------------------------------------------------+
|
||||
//
|
||||
//
|
||||
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
|
||||
pub const MAPPED_IO_START: u64 = 0x0900_0000;
|
||||
@@ -61,7 +61,7 @@ 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;
|
||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
||||
|
||||
/// Start of RAM on 64 bit ARM.
|
||||
pub const RAM_64BIT_START: u64 = 0x8000_0000;
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
pub mod fdt;
|
||||
/// Module for the global interrupt controller configuration.
|
||||
pub mod gic;
|
||||
mod gicv2;
|
||||
mod gicv3;
|
||||
mod gicv3_its;
|
||||
/// Layout for this aarch64 system.
|
||||
pub mod layout;
|
||||
/// Logic for configuring aarch64 registers.
|
||||
@@ -17,7 +14,7 @@ pub mod regs;
|
||||
pub use self::fdt::DeviceInfoForFDT;
|
||||
use crate::DeviceType;
|
||||
use crate::RegionType;
|
||||
use hypervisor::kvm::kvm_bindings;
|
||||
use aarch64::gic::GICDevice;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Debug;
|
||||
@@ -42,11 +39,8 @@ pub enum Error {
|
||||
/// Error configuring the general purpose registers
|
||||
REGSConfiguration(regs::Error),
|
||||
|
||||
/// Error fetching prefered target
|
||||
VcpuArmPreferredTarget(hypervisor::HypervisorVmError),
|
||||
|
||||
/// Error doing Vcpu Init on Arm.
|
||||
VcpuArmInit(hypervisor::HypervisorCpuError),
|
||||
/// Error configuring the MPIDR register
|
||||
VcpuRegMPIDR(hypervisor::HypervisorCpuError),
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
@@ -67,23 +61,9 @@ pub struct EntryPoint {
|
||||
pub fn configure_vcpu(
|
||||
fd: &Arc<dyn hypervisor::Vcpu>,
|
||||
id: u8,
|
||||
vm: &Arc<dyn hypervisor::Vm>,
|
||||
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.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,
|
||||
@@ -94,7 +74,7 @@ pub fn configure_vcpu(
|
||||
.map_err(Error::REGSConfiguration)?;
|
||||
}
|
||||
|
||||
let mpidr = regs::read_mpidr(fd).map_err(Error::REGSConfiguration)?;
|
||||
let mpidr = fd.read_mpidr().map_err(Error::VcpuRegMPIDR)?;
|
||||
Ok(mpidr)
|
||||
}
|
||||
|
||||
@@ -145,13 +125,9 @@ pub fn configure_system<T: DeviceInfoForFDT + Clone + Debug, S: ::std::hash::Bui
|
||||
vcpu_mpidr: Vec<u64>,
|
||||
device_info: &HashMap<(DeviceType, String), T, S>,
|
||||
initrd: &Option<super::InitramfsConfig>,
|
||||
pci_space_address: &Option<(u64, u64)>,
|
||||
) -> super::Result<()> {
|
||||
// If pci_space_address is present, it means PCI devices are used ("pci" feature enabled).
|
||||
// Then GITv3-ITS is required for MSI messaging.
|
||||
// Otherwise ("mmio" feature enabled), any version of GIC is OK.
|
||||
let gic_device = gic::kvm::create_gic(vm, vcpu_count, pci_space_address.is_some())
|
||||
.map_err(Error::SetupGIC)?;
|
||||
pci_space_address: &(u64, u64),
|
||||
) -> super::Result<Box<dyn GICDevice>> {
|
||||
let gic_device = gic::kvm::create_gic(vm, vcpu_count).map_err(Error::SetupGIC)?;
|
||||
|
||||
fdt::create_fdt(
|
||||
guest_mem,
|
||||
@@ -164,7 +140,7 @@ pub fn configure_system<T: DeviceInfoForFDT + Clone + Debug, S: ::std::hash::Bui
|
||||
)
|
||||
.map_err(Error::SetupFDT)?;
|
||||
|
||||
Ok(())
|
||||
Ok(gic_device)
|
||||
}
|
||||
|
||||
/// Returns the memory address where the initramfs could be loaded.
|
||||
@@ -210,10 +186,10 @@ fn get_fdt_addr(mem: &GuestMemoryMmap) -> u64 {
|
||||
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
|
||||
// In recent kernel versions, the maximum 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.
|
||||
// But in Cloud-Hypervisor we simply use the maximum 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,
|
||||
|
||||
@@ -7,12 +7,9 @@
|
||||
|
||||
use super::get_fdt_addr;
|
||||
use hypervisor::kvm::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,
|
||||
kvm_regs, user_pt_regs, KVM_REG_ARM64, KVM_REG_ARM_CORE, KVM_REG_SIZE_U64,
|
||||
};
|
||||
use hypervisor::{arm64_core_reg_id, offset__of};
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
use vm_memory::GuestMemoryMmap;
|
||||
@@ -38,80 +35,6 @@ 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 { &(*std::ptr::null::<user_pt_regs>()).$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
|
||||
@@ -126,31 +49,33 @@ pub fn setup_regs(
|
||||
boot_ip: u64,
|
||||
mem: &GuestMemoryMmap,
|
||||
) -> Result<()> {
|
||||
let kreg_off = offset__of!(kvm_regs, regs);
|
||||
|
||||
// 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)?;
|
||||
let pstate = offset__of!(user_pt_regs, pstate) + kreg_off;
|
||||
vcpu.set_reg(
|
||||
arm64_core_reg_id!(KVM_REG_SIZE_U64, 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)
|
||||
let pc = offset__of!(user_pt_regs, pc) + kreg_off;
|
||||
vcpu.set_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, pc), boot_ip as u64)
|
||||
.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)?;
|
||||
let regs0 = offset__of!(user_pt_regs, regs) + kreg_off;
|
||||
vcpu.set_reg(
|
||||
arm64_core_reg_id!(KVM_REG_SIZE_U64, regs0),
|
||||
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: &Arc<dyn hypervisor::Vcpu>) -> Result<u64> {
|
||||
vcpu.get_one_reg(MPIDR_EL1).map_err(Error::GetSysRegister)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
clippy::cast_ptr_alignment
|
||||
)]
|
||||
|
||||
extern crate anyhow;
|
||||
extern crate byteorder;
|
||||
extern crate hypervisor;
|
||||
extern crate libc;
|
||||
@@ -23,7 +24,13 @@ extern crate log;
|
||||
extern crate acpi_tables;
|
||||
extern crate arch_gen;
|
||||
extern crate linux_loader;
|
||||
extern crate serde;
|
||||
extern crate vm_memory;
|
||||
extern crate vm_migration;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::fmt;
|
||||
use std::result;
|
||||
@@ -155,6 +162,6 @@ impl DeviceInfoForFDT for MMIODeviceInfo {
|
||||
self.irq
|
||||
}
|
||||
fn length(&self) -> u64 {
|
||||
4096 as u64
|
||||
4096
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ pub fn set_lint(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "kvm")]
|
||||
mod tests {
|
||||
|
||||
extern crate rand;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
/*
|
||||
|
||||
@@ -81,22 +81,22 @@ pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
|
||||
|
||||
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
|
||||
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
|
||||
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = 1024 << 20;
|
||||
pub const MEM_32BIT_RESERVED_SIZE: u64 = 1024 << 20;
|
||||
|
||||
// == Fixed constants within the "32-bit reserved" range ==
|
||||
|
||||
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
|
||||
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
|
||||
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = 640 << 20;
|
||||
pub const MEM_32BIT_DEVICES_SIZE: u64 = 640 << 20;
|
||||
|
||||
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
|
||||
pub const PCI_MMCONFIG_START: GuestAddress =
|
||||
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
|
||||
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
|
||||
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
|
||||
|
||||
// IOAPIC
|
||||
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
|
||||
pub const IOAPIC_SIZE: GuestUsize = 0x20;
|
||||
pub const IOAPIC_SIZE: u64 = 0x20;
|
||||
|
||||
// APIC
|
||||
pub const APIC_START: GuestAddress = GuestAddress(0xfee0_0000);
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
use std::sync::Arc;
|
||||
mod gdt;
|
||||
pub mod interrupts;
|
||||
pub mod layout;
|
||||
#[cfg(not(feature = "acpi"))]
|
||||
mod mptable;
|
||||
pub mod regs;
|
||||
use crate::InitramfsConfig;
|
||||
@@ -26,6 +24,7 @@ use vm_memory::{
|
||||
GuestMemoryMmap, GuestMemoryRegion, GuestUsize,
|
||||
};
|
||||
mod smbios;
|
||||
use std::arch::x86_64;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum BootProtocol {
|
||||
@@ -141,7 +140,7 @@ unsafe impl ByteValued for BootParamsWrapper {}
|
||||
pub enum Error {
|
||||
/// Invalid e820 setup params.
|
||||
E820Configuration,
|
||||
#[cfg(not(feature = "acpi"))]
|
||||
|
||||
/// Error writing MP table to memory.
|
||||
MpTableSetup(mptable::Error),
|
||||
|
||||
@@ -174,9 +173,6 @@ pub enum Error {
|
||||
|
||||
/// Missing SGX_LC CPU feature
|
||||
MissingSgxLaunchControlFeature,
|
||||
|
||||
// Error populating Cpuid
|
||||
PopulatingCpuid,
|
||||
}
|
||||
|
||||
impl From<Error> for super::Error {
|
||||
@@ -338,62 +334,11 @@ pub fn configure_vcpu(
|
||||
cpuid: CpuId,
|
||||
kvm_hyperv: bool,
|
||||
) -> super::Result<()> {
|
||||
// Per vCPU CPUID changes; common are handled via CpuManager::generate_common_cpuid()
|
||||
let mut cpuid = cpuid;
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, u32::from(id));
|
||||
CpuidPatch::set_cpuid_reg(&mut cpuid, 0x1f, None, CpuidReg::EDX, u32::from(id));
|
||||
|
||||
if kvm_hyperv {
|
||||
// Remove conflicting entries
|
||||
cpuid.retain(|c| c.function != 0x4000_0000);
|
||||
cpuid.retain(|c| c.function != 0x4000_0001);
|
||||
|
||||
// See "Hypervisor Top Level Functional Specification" for details
|
||||
// Compliance with "Hv#1" requires leaves up to 0x4000_000a
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x40000000,
|
||||
eax: 0x4000000a, // Maximum cpuid leaf
|
||||
ebx: 0x756e694c, // "Linu"
|
||||
ecx: 0x564b2078, // "x KV"
|
||||
edx: 0x7648204d, // "M Hv"
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x40000001,
|
||||
eax: 0x31237648, // "Hv#1"
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x40000002,
|
||||
eax: 0x3839, // "Build number"
|
||||
ebx: 0xa0000, // "Version"
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: 0x4000_0003,
|
||||
eax: 1 << 1 // AccessPartitionReferenceCounter
|
||||
| 1 << 2 // AccessSynicRegs
|
||||
| 1 << 3 // AccessSyntheticTimerRegs
|
||||
| 1 << 9, // AccessPartitionReferenceTsc
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
for i in 0x4000_0004..=0x4000_000a {
|
||||
cpuid
|
||||
.push(CpuIdEntry {
|
||||
function: i,
|
||||
..Default::default()
|
||||
})
|
||||
.map_err(|_| Error::PopulatingCpuid)?;
|
||||
}
|
||||
}
|
||||
|
||||
fd.set_cpuid2(&cpuid)
|
||||
.map_err(|e| Error::SetSupportedCpusFailed(e.into()))?;
|
||||
|
||||
@@ -487,11 +432,12 @@ pub fn configure_system(
|
||||
boot_prot: BootProtocol,
|
||||
sgx_epc_region: Option<SgxEpcRegion>,
|
||||
) -> super::Result<()> {
|
||||
smbios::setup_smbios(guest_mem).map_err(Error::SmbiosSetup)?;
|
||||
let size = smbios::setup_smbios(guest_mem).map_err(Error::SmbiosSetup)?;
|
||||
|
||||
// Note that this puts the mptable at the last 1k of Linux's 640k base RAM
|
||||
#[cfg(not(feature = "acpi"))]
|
||||
mptable::setup_mptable(guest_mem, _num_cpus).map_err(Error::MpTableSetup)?;
|
||||
// Place the MP table after the SMIOS table aligned to 16 bytes
|
||||
let offset = GuestAddress(layout::SMBIOS_START).unchecked_add(size);
|
||||
let offset = GuestAddress((offset.0 + 16) & !0xf);
|
||||
mptable::setup_mptable(offset, 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 {
|
||||
@@ -570,7 +516,7 @@ fn configure_pvh(
|
||||
let mut memmap: Vec<hvm_memmap_table_entry> = Vec::new();
|
||||
|
||||
// Create the memory map entries.
|
||||
add_memmap_entry(&mut memmap, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
|
||||
add_memmap_entry(&mut memmap, 0, layout::EBDA_START.raw_value(), E820_RAM);
|
||||
|
||||
let mem_end = guest_mem.last_addr();
|
||||
|
||||
@@ -580,21 +526,21 @@ fn configure_pvh(
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
);
|
||||
} else {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::HIGH_RAM_START.raw_value(),
|
||||
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
|
||||
E820_RAM,
|
||||
)?;
|
||||
);
|
||||
if mem_end > layout::RAM_64BIT_START {
|
||||
add_memmap_entry(
|
||||
&mut memmap,
|
||||
layout::RAM_64BIT_START.raw_value(),
|
||||
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
|
||||
E820_RAM,
|
||||
)?;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,7 +549,7 @@ fn configure_pvh(
|
||||
layout::PCI_MMCONFIG_START.0,
|
||||
layout::PCI_MMCONFIG_SIZE,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
);
|
||||
|
||||
if let Some(sgx_epc_region) = sgx_epc_region {
|
||||
add_memmap_entry(
|
||||
@@ -611,7 +557,7 @@ fn configure_pvh(
|
||||
sgx_epc_region.start().raw_value(),
|
||||
sgx_epc_region.size() as u64,
|
||||
E820_RESERVED,
|
||||
)?;
|
||||
);
|
||||
}
|
||||
|
||||
start_info.0.memmap_entries = memmap.len() as u32;
|
||||
@@ -656,12 +602,7 @@ fn configure_pvh(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_memmap_entry(
|
||||
memmap: &mut Vec<hvm_memmap_table_entry>,
|
||||
addr: u64,
|
||||
size: u64,
|
||||
mem_type: u32,
|
||||
) -> Result<(), Error> {
|
||||
fn add_memmap_entry(memmap: &mut Vec<hvm_memmap_table_entry>, addr: u64, size: u64, mem_type: u32) {
|
||||
// Add the table entry to the vector
|
||||
memmap.push(hvm_memmap_table_entry {
|
||||
addr,
|
||||
@@ -669,8 +610,6 @@ fn add_memmap_entry(
|
||||
type_: mem_type,
|
||||
reserved: 0,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_64bit_boot(
|
||||
@@ -808,7 +747,6 @@ pub fn initramfs_load_addr(
|
||||
}
|
||||
|
||||
pub fn get_host_cpu_phys_bits() -> u8 {
|
||||
use std::arch::x86_64;
|
||||
unsafe {
|
||||
let leaf = x86_64::__cpuid(0x8000_0000);
|
||||
|
||||
@@ -951,7 +889,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn regions_lt_4gb() {
|
||||
let regions = arch_memory_regions(1 << 29 as GuestUsize);
|
||||
let regions = arch_memory_regions(1 << 29);
|
||||
assert_eq!(3, regions.len());
|
||||
assert_eq!(GuestAddress(0), regions[0].0);
|
||||
assert_eq!(1usize << 29, regions[0].1);
|
||||
@@ -959,7 +897,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn regions_gt_4gb() {
|
||||
let regions = arch_memory_regions((1 << 32 as GuestUsize) + 0x8000);
|
||||
let regions = arch_memory_regions((1 << 32) + 0x8000);
|
||||
assert_eq!(4, regions.len());
|
||||
assert_eq!(GuestAddress(0), regions[0].0);
|
||||
assert_eq!(GuestAddress(1 << 32), regions[1].0);
|
||||
@@ -968,7 +906,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_system_configuration() {
|
||||
let no_vcpus = 4;
|
||||
let gm = GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
|
||||
let config_err = configure_system(
|
||||
&gm,
|
||||
GuestAddress(0),
|
||||
@@ -1147,8 +1085,8 @@ mod tests {
|
||||
},
|
||||
];
|
||||
|
||||
add_memmap_entry(&mut memmap, 0, 0x1000, E820_RAM).unwrap();
|
||||
add_memmap_entry(&mut memmap, 0x10000, 0xa000, E820_RESERVED).unwrap();
|
||||
add_memmap_entry(&mut memmap, 0, 0x1000, E820_RAM);
|
||||
add_memmap_entry(&mut memmap, 0x10000, 0xa000, E820_RESERVED);
|
||||
|
||||
assert_eq!(format!("{:?}", memmap), format!("{:?}", expected_memmap));
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@ use std::slice;
|
||||
use libc::c_char;
|
||||
|
||||
use arch_gen::x86::mpspec;
|
||||
use layout::{APIC_START, IOAPIC_START, MPTABLE_START};
|
||||
use vm_memory::{Address, ByteValued, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
use layout::{APIC_START, HIGH_RAM_START, IOAPIC_START};
|
||||
use vm_memory::{
|
||||
Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap,
|
||||
};
|
||||
|
||||
// This is a workaround to the Rust enforcement specifying that any implementation of a foreign
|
||||
// trait (in this case `ByteValued`) where:
|
||||
@@ -121,16 +123,21 @@ fn compute_mp_size(num_cpus: u8) -> usize {
|
||||
}
|
||||
|
||||
/// Performs setup of the MP table for the given `num_cpus`.
|
||||
pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
pub fn setup_mptable(offset: GuestAddress, mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
if num_cpus as u32 > MAX_SUPPORTED_CPUS {
|
||||
return Err(Error::TooManyCpus);
|
||||
}
|
||||
|
||||
// Used to keep track of the next base pointer into the MP table.
|
||||
let mut base_mp = MPTABLE_START;
|
||||
let mut base_mp = offset;
|
||||
|
||||
let mp_size = compute_mp_size(num_cpus);
|
||||
|
||||
if offset.unchecked_add(mp_size as u64) >= HIGH_RAM_START {
|
||||
warn!("Skipping mptable creation due to insufficient space");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut checksum: u8 = 0;
|
||||
let ioapicid: u8 = num_cpus + 1;
|
||||
|
||||
@@ -280,6 +287,7 @@ pub fn setup_mptable(mem: &GuestMemoryMmap, num_cpus: u8) -> Result<()> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use layout::MPTABLE_START;
|
||||
use vm_memory::{GuestAddress, GuestUsize};
|
||||
|
||||
fn table_entry_size(type_: u8) -> usize {
|
||||
@@ -299,7 +307,7 @@ mod tests {
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
|
||||
setup_mptable(&mem, num_cpus).unwrap();
|
||||
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -308,7 +316,7 @@ mod tests {
|
||||
let mem = GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus) - 1)])
|
||||
.unwrap();
|
||||
|
||||
assert!(setup_mptable(&mem, num_cpus).is_err());
|
||||
assert!(setup_mptable(MPTABLE_START, &mem, num_cpus).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -317,7 +325,7 @@ mod tests {
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
|
||||
setup_mptable(&mem, num_cpus).unwrap();
|
||||
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||
|
||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||
|
||||
@@ -333,7 +341,7 @@ mod tests {
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(num_cpus))]).unwrap();
|
||||
|
||||
setup_mptable(&mem, num_cpus).unwrap();
|
||||
setup_mptable(MPTABLE_START, &mem, num_cpus).unwrap();
|
||||
|
||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||
@@ -367,7 +375,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
for i in 0..MAX_SUPPORTED_CPUS as u8 {
|
||||
setup_mptable(&mem, i).unwrap();
|
||||
setup_mptable(MPTABLE_START, &mem, i).unwrap();
|
||||
|
||||
let mpf_intel: MpfIntelWrapper = mem.read_obj(MPTABLE_START).unwrap();
|
||||
let mpc_offset = GuestAddress(mpf_intel.0.physptr as GuestUsize);
|
||||
@@ -400,7 +408,7 @@ mod tests {
|
||||
let mem =
|
||||
GuestMemoryMmap::from_ranges(&[(MPTABLE_START, compute_mp_size(cpus as u8))]).unwrap();
|
||||
|
||||
let result = setup_mptable(&mem, cpus as u8);
|
||||
let result = setup_mptable(MPTABLE_START, &mem, cpus as u8);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
use std::sync::Arc;
|
||||
use std::{mem, result};
|
||||
|
||||
use super::gdt::{gdt_entry, segment_from_gdt};
|
||||
use super::BootProtocol;
|
||||
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||
use hypervisor::arch::x86::regs::*;
|
||||
use hypervisor::x86_64::{FpuState, SpecialRegisters, StandardRegisters};
|
||||
use layout::{
|
||||
BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PML5_START, PVH_INFO_START,
|
||||
@@ -134,14 +135,6 @@ pub fn setup_sregs(
|
||||
|
||||
const BOOT_GDT_MAX: usize = 4;
|
||||
|
||||
const EFER_LMA: u64 = 0x400;
|
||||
const EFER_LME: u64 = 0x100;
|
||||
|
||||
const X86_CR0_PE: u64 = 0x1;
|
||||
const X86_CR0_PG: u64 = 0x80000000;
|
||||
const X86_CR4_PAE: u64 = 0x20;
|
||||
const X86_CR4_LA57: u64 = 0x1000;
|
||||
|
||||
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
|
||||
let boot_gdt_addr = BOOT_GDT_START;
|
||||
for (index, entry) in table.iter().enumerate() {
|
||||
@@ -209,12 +202,12 @@ pub fn configure_segments_and_sregs(
|
||||
|
||||
match boot_prot {
|
||||
BootProtocol::PvhBoot => {
|
||||
sregs.cr0 = X86_CR0_PE;
|
||||
sregs.cr0 = CR0_PE;
|
||||
sregs.cr4 = 0;
|
||||
}
|
||||
BootProtocol::LinuxBoot => {
|
||||
/* 64-bit protected mode */
|
||||
sregs.cr0 |= X86_CR0_PE;
|
||||
sregs.cr0 |= CR0_PE;
|
||||
sregs.efer |= EFER_LME | EFER_LMA;
|
||||
}
|
||||
}
|
||||
@@ -230,7 +223,7 @@ pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) ->
|
||||
.map_err(Error::WritePML5Address)?;
|
||||
|
||||
sregs.cr3 = PML5_START.raw_value();
|
||||
sregs.cr4 |= X86_CR4_LA57;
|
||||
sregs.cr4 |= CR4_LA57;
|
||||
} else {
|
||||
sregs.cr3 = PML4_START.raw_value();
|
||||
}
|
||||
@@ -250,8 +243,8 @@ pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) ->
|
||||
.map_err(Error::WritePDEAddress)?;
|
||||
}
|
||||
|
||||
sregs.cr4 |= X86_CR4_PAE;
|
||||
sregs.cr0 |= X86_CR0_PG;
|
||||
sregs.cr4 |= CR4_PAE;
|
||||
sregs.cr0 |= CR0_PG;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -264,7 +257,7 @@ mod tests {
|
||||
use vm_memory::{GuestAddress, GuestMemoryMmap};
|
||||
|
||||
fn create_guest_mem() -> GuestMemoryMmap {
|
||||
GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap()
|
||||
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
|
||||
}
|
||||
|
||||
fn read_u64(gm: &GuestMemoryMmap, offset: GuestAddress) -> u64 {
|
||||
@@ -301,7 +294,7 @@ mod tests {
|
||||
assert_eq!(0, sregs.tr.base);
|
||||
assert_eq!(0xffffffff, sregs.tr.limit);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert_eq!(X86_CR0_PE, sregs.cr0);
|
||||
assert_eq!(CR0_PE, sregs.cr0);
|
||||
assert_eq!(EFER_LME | EFER_LMA, sregs.efer);
|
||||
|
||||
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::PvhBoot).unwrap();
|
||||
@@ -331,7 +324,7 @@ mod tests {
|
||||
assert_eq!(0x67, sregs.tr.limit);
|
||||
assert_eq!(0xb, sregs.tr.type_);
|
||||
assert_eq!(0, sregs.tr.avl);
|
||||
assert_eq!(X86_CR0_PE, sregs.cr0);
|
||||
assert_eq!(CR0_PE, sregs.cr0);
|
||||
assert_eq!(0, sregs.cr4);
|
||||
}
|
||||
|
||||
@@ -358,7 +351,7 @@ mod tests {
|
||||
} else {
|
||||
assert_eq!(PML4_START.raw_value(), sregs.cr3);
|
||||
}
|
||||
assert_eq!(X86_CR4_PAE, sregs.cr4);
|
||||
assert_eq!(X86_CR0_PG, sregs.cr0);
|
||||
assert_eq!(CR4_PAE, sregs.cr4);
|
||||
assert_eq!(CR0_PG, sregs.cr0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,11 +157,11 @@ fn write_string(
|
||||
for c in val.as_bytes().iter() {
|
||||
curptr = write_and_incr(mem, *c, curptr)?;
|
||||
}
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
||||
Ok(curptr)
|
||||
}
|
||||
|
||||
pub fn setup_smbios(mem: &GuestMemoryMmap) -> Result<()> {
|
||||
pub fn setup_smbios(mem: &GuestMemoryMmap) -> Result<u64> {
|
||||
let physptr = GuestAddress(SMBIOS_START)
|
||||
.checked_add(mem::size_of::<Smbios30Entrypoint>() as u64)
|
||||
.ok_or(Error::NotEnoughMemory)?;
|
||||
@@ -170,61 +170,69 @@ pub fn setup_smbios(mem: &GuestMemoryMmap) -> Result<()> {
|
||||
|
||||
{
|
||||
handle += 1;
|
||||
let mut smbios_biosinfo = SmbiosBiosInfo::default();
|
||||
smbios_biosinfo.typ = BIOS_INFORMATION;
|
||||
smbios_biosinfo.length = mem::size_of::<SmbiosBiosInfo>() as u8;
|
||||
smbios_biosinfo.handle = handle;
|
||||
smbios_biosinfo.vendor = 1; // First string written in this section
|
||||
smbios_biosinfo.version = 2; // Second string written in this section
|
||||
smbios_biosinfo.characteristics = PCI_SUPPORTED;
|
||||
smbios_biosinfo.characteristics_ext2 = IS_VIRTUAL_MACHINE;
|
||||
let smbios_biosinfo = SmbiosBiosInfo {
|
||||
typ: BIOS_INFORMATION,
|
||||
length: mem::size_of::<SmbiosBiosInfo>() as u8,
|
||||
handle,
|
||||
vendor: 1, // First string written in this section
|
||||
version: 2, // Second string written in this section
|
||||
characteristics: PCI_SUPPORTED,
|
||||
characteristics_ext2: IS_VIRTUAL_MACHINE,
|
||||
..Default::default()
|
||||
};
|
||||
curptr = write_and_incr(mem, smbios_biosinfo, curptr)?;
|
||||
curptr = write_string(mem, "cloud-hypervisor", curptr)?;
|
||||
curptr = write_string(mem, "0", curptr)?;
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
||||
}
|
||||
|
||||
{
|
||||
handle += 1;
|
||||
let mut smbios_sysinfo = SmbiosSysInfo::default();
|
||||
smbios_sysinfo.typ = SYSTEM_INFORMATION;
|
||||
smbios_sysinfo.length = mem::size_of::<SmbiosSysInfo>() as u8;
|
||||
smbios_sysinfo.handle = handle;
|
||||
smbios_sysinfo.manufacturer = 1; // First string written in this section
|
||||
smbios_sysinfo.product_name = 2; // Second string written in this section
|
||||
let smbios_sysinfo = SmbiosSysInfo {
|
||||
typ: SYSTEM_INFORMATION,
|
||||
length: mem::size_of::<SmbiosSysInfo>() as u8,
|
||||
handle,
|
||||
manufacturer: 1, // First string written in this section
|
||||
product_name: 2, // Second string written in this section
|
||||
..Default::default()
|
||||
};
|
||||
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
|
||||
curptr = write_string(mem, "Cloud Hypervisor", curptr)?;
|
||||
curptr = write_string(mem, "cloud-hypervisor", curptr)?;
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
||||
}
|
||||
|
||||
{
|
||||
handle += 1;
|
||||
let mut smbios_sysinfo = SmbiosSysInfo::default();
|
||||
smbios_sysinfo.typ = END_OF_TABLE;
|
||||
smbios_sysinfo.length = mem::size_of::<SmbiosSysInfo>() as u8;
|
||||
smbios_sysinfo.handle = handle;
|
||||
let smbios_sysinfo = SmbiosSysInfo {
|
||||
typ: END_OF_TABLE,
|
||||
length: mem::size_of::<SmbiosSysInfo>() as u8,
|
||||
handle,
|
||||
..Default::default()
|
||||
};
|
||||
curptr = write_and_incr(mem, smbios_sysinfo, curptr)?;
|
||||
curptr = write_and_incr(mem, 0 as u8, curptr)?;
|
||||
curptr = write_and_incr(mem, 0u8, curptr)?;
|
||||
}
|
||||
|
||||
{
|
||||
let mut smbios_ep = Smbios30Entrypoint::default();
|
||||
smbios_ep.signature = *SM3_MAGIC_IDENT;
|
||||
smbios_ep.length = mem::size_of::<Smbios30Entrypoint>() as u8;
|
||||
// SMBIOS rev 3.2.0
|
||||
smbios_ep.majorver = 0x03;
|
||||
smbios_ep.minorver = 0x02;
|
||||
smbios_ep.docrev = 0x00;
|
||||
smbios_ep.revision = 0x01; // SMBIOS 3.0
|
||||
smbios_ep.max_size = curptr.unchecked_offset_from(physptr) as u32;
|
||||
smbios_ep.physptr = physptr.0;
|
||||
let mut smbios_ep = Smbios30Entrypoint {
|
||||
signature: *SM3_MAGIC_IDENT,
|
||||
length: mem::size_of::<Smbios30Entrypoint>() as u8,
|
||||
// SMBIOS rev 3.2.0
|
||||
majorver: 0x03,
|
||||
minorver: 0x02,
|
||||
docrev: 0x00,
|
||||
revision: 0x01, // SMBIOS 3.0
|
||||
max_size: curptr.unchecked_offset_from(physptr) as u32,
|
||||
physptr: physptr.0,
|
||||
..Default::default()
|
||||
};
|
||||
smbios_ep.checksum = compute_checksum(&smbios_ep);
|
||||
mem.write_obj(smbios_ep, GuestAddress(SMBIOS_START))
|
||||
.map_err(|_| Error::WriteSmbiosEp)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(curptr.unchecked_offset_from(physptr))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -67,7 +67,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
concat!("Alignment of ", stringify!(mpf_intel))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).signature as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -77,7 +77,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).physptr as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).physptr as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -87,7 +87,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).length as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).length as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -97,7 +97,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).specification as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).specification as *const _ as usize },
|
||||
9usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -107,7 +107,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).checksum as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).checksum as *const _ as usize },
|
||||
10usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -117,7 +117,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).feature1 as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature1 as *const _ as usize },
|
||||
11usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -127,7 +127,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).feature2 as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature2 as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -137,7 +137,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).feature3 as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature3 as *const _ as usize },
|
||||
13usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -147,7 +147,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).feature4 as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature4 as *const _ as usize },
|
||||
14usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -157,7 +157,7 @@ fn bindgen_test_layout_mpf_intel() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpf_intel)).feature5 as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpf_intel>()).feature5 as *const _ as usize },
|
||||
15usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -200,7 +200,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
concat!("Alignment of ", stringify!(mpc_table))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).signature as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -210,7 +210,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).length as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).length as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -220,7 +220,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).spec as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).spec as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -230,7 +230,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).checksum as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).checksum as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -240,7 +240,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).oem as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oem as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -250,7 +250,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).productid as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).productid as *const _ as usize },
|
||||
16usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -260,7 +260,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).oemptr as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oemptr as *const _ as usize },
|
||||
28usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -270,7 +270,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).oemsize as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oemsize as *const _ as usize },
|
||||
32usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -280,7 +280,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).oemcount as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).oemcount as *const _ as usize },
|
||||
34usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -290,7 +290,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).lapic as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).lapic as *const _ as usize },
|
||||
36usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -300,7 +300,7 @@ fn bindgen_test_layout_mpc_table() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_table)).reserved as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_table>()).reserved as *const _ as usize },
|
||||
40usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -339,7 +339,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
concat!("Alignment of ", stringify!(mpc_cpu))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).type_ as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -349,7 +349,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).apicid as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).apicid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -359,7 +359,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).apicver as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).apicver as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -369,7 +369,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).cpuflag as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).cpuflag as *const _ as usize },
|
||||
3usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -379,7 +379,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).cpufeature as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).cpufeature as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -389,7 +389,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).featureflag as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).featureflag as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -399,7 +399,7 @@ fn bindgen_test_layout_mpc_cpu() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_cpu)).reserved as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_cpu>()).reserved as *const _ as usize },
|
||||
12usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -434,7 +434,7 @@ fn bindgen_test_layout_mpc_bus() {
|
||||
concat!("Alignment of ", stringify!(mpc_bus))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_bus)).type_ as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_bus>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -444,7 +444,7 @@ fn bindgen_test_layout_mpc_bus() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_bus)).busid as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_bus>()).busid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -454,7 +454,7 @@ fn bindgen_test_layout_mpc_bus() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_bus)).bustype as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_bus>()).bustype as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -491,7 +491,7 @@ fn bindgen_test_layout_mpc_ioapic() {
|
||||
concat!("Alignment of ", stringify!(mpc_ioapic))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_ioapic)).type_ as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -501,7 +501,7 @@ fn bindgen_test_layout_mpc_ioapic() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_ioapic)).apicid as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicid as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -511,7 +511,7 @@ fn bindgen_test_layout_mpc_ioapic() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_ioapic)).apicver as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicver as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -521,7 +521,7 @@ fn bindgen_test_layout_mpc_ioapic() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_ioapic)).flags as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).flags as *const _ as usize },
|
||||
3usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -531,7 +531,7 @@ fn bindgen_test_layout_mpc_ioapic() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_ioapic)).apicaddr as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicaddr as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -570,7 +570,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
concat!("Alignment of ", stringify!(mpc_intsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).type_ as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -580,7 +580,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).irqtype as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).irqtype as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -590,7 +590,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).irqflag as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).irqflag as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -600,7 +600,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).srcbus as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).srcbus as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -610,7 +610,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).srcbusirq as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).srcbusirq as *const _ as usize },
|
||||
5usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -620,7 +620,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).dstapic as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).dstapic as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -630,7 +630,7 @@ fn bindgen_test_layout_mpc_intsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_intsrc)).dstirq as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_intsrc>()).dstirq as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -674,7 +674,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
concat!("Alignment of ", stringify!(mpc_lintsrc))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).type_ as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).type_ as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -684,7 +684,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).irqtype as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).irqtype as *const _ as usize },
|
||||
1usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -694,7 +694,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).irqflag as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).irqflag as *const _ as usize },
|
||||
2usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -704,7 +704,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).srcbusid as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).srcbusid as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -714,7 +714,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).srcbusirq as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).srcbusirq as *const _ as usize },
|
||||
5usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -724,7 +724,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).destapic as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).destapic as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -734,7 +734,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_lintsrc)).destapiclint as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).destapiclint as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -771,7 +771,7 @@ fn bindgen_test_layout_mpc_oemtable() {
|
||||
concat!("Alignment of ", stringify!(mpc_oemtable))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_oemtable)).signature as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).signature as *const _ as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -781,7 +781,7 @@ fn bindgen_test_layout_mpc_oemtable() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_oemtable)).length as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).length as *const _ as usize },
|
||||
4usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -791,7 +791,7 @@ fn bindgen_test_layout_mpc_oemtable() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_oemtable)).rev as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).rev as *const _ as usize },
|
||||
6usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -801,7 +801,7 @@ fn bindgen_test_layout_mpc_oemtable() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_oemtable)).checksum as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).checksum as *const _ as usize },
|
||||
7usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
@@ -811,7 +811,7 @@ fn bindgen_test_layout_mpc_oemtable() {
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { &(*(0 as *const mpc_oemtable)).mpc as *const _ as usize },
|
||||
unsafe { &(*std::ptr::null::<mpc_oemtable>()).mpc as *const _ as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Alignment of field: ",
|
||||
|
||||
@@ -10,12 +10,17 @@ io_uring = []
|
||||
|
||||
[dependencies]
|
||||
io-uring = ">=0.4.0"
|
||||
libc = "0.2.77"
|
||||
log = "0.4.11"
|
||||
libc = "0.2.86"
|
||||
log = "0.4.14"
|
||||
qcow = { path = "../qcow" }
|
||||
serde = ">=1.0.27"
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
thiserror = "1.0"
|
||||
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
|
||||
vm-memory = { version = "0.2.1", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.5.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.2.0"
|
||||
|
||||
56
block_util/src/async_io.rs
Normal file
56
block_util/src/async_io.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use thiserror::Error;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DiskFileError {
|
||||
/// Failed getting disk file size.
|
||||
#[error("Failed getting disk file size: {0}")]
|
||||
Size(#[source] std::io::Error),
|
||||
/// Failed creating a new AsyncIo.
|
||||
#[error("Failed creating a new AsyncIo: {0}")]
|
||||
NewAsyncIo(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
pub type DiskFileResult<T> = std::result::Result<T, DiskFileError>;
|
||||
|
||||
pub trait DiskFile: Send + Sync {
|
||||
fn size(&mut self) -> DiskFileResult<u64>;
|
||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>>;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AsyncIoError {
|
||||
/// Failed vectored reading from file.
|
||||
#[error("Failed vectored reading from file: {0}")]
|
||||
ReadVectored(#[source] std::io::Error),
|
||||
/// Failed vectored writing to file.
|
||||
#[error("Failed vectored writing to file: {0}")]
|
||||
WriteVectored(#[source] std::io::Error),
|
||||
/// Failed synchronizing file.
|
||||
#[error("Failed synchronizing file: {0}")]
|
||||
Fsync(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
pub type AsyncIoResult<T> = std::result::Result<T, AsyncIoError>;
|
||||
|
||||
pub trait AsyncIo: Send + Sync {
|
||||
fn notifier(&self) -> &EventFd;
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()>;
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()>;
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()>;
|
||||
fn complete(&mut self) -> Vec<(u64, i32)>;
|
||||
}
|
||||
110
block_util/src/fixed_vhd_async.rs
Normal file
110
block_util/src/fixed_vhd_async.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::raw_async::RawFileAsync;
|
||||
use crate::vhd::VhdFooter;
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct FixedVhdDiskAsync {
|
||||
file: File,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl FixedVhdDiskAsync {
|
||||
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||
let footer = VhdFooter::new(&mut file)?;
|
||||
|
||||
Ok(FixedVhdDiskAsync {
|
||||
file,
|
||||
size: footer.current_size(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for FixedVhdDiskAsync {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.size)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(
|
||||
FixedVhdAsync::new(self.file.as_raw_fd(), ring_depth, self.size)
|
||||
.map_err(DiskFileError::NewAsyncIo)?,
|
||||
) as Box<dyn AsyncIo>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FixedVhdAsync {
|
||||
raw_file_async: RawFileAsync,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl FixedVhdAsync {
|
||||
pub fn new(fd: RawFd, ring_depth: u32, size: u64) -> std::io::Result<Self> {
|
||||
let raw_file_async = RawFileAsync::new(fd, ring_depth)?;
|
||||
|
||||
Ok(FixedVhdAsync {
|
||||
raw_file_async,
|
||||
size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for FixedVhdAsync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
self.raw_file_async.notifier()
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
if offset as u64 >= self.size {
|
||||
return Err(AsyncIoError::ReadVectored(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"Invalid offset {}, can't be larger than file size {}",
|
||||
offset, self.size
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
self.raw_file_async.read_vectored(offset, iovecs, user_data)
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
if offset as u64 >= self.size {
|
||||
return Err(AsyncIoError::WriteVectored(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"Invalid offset {}, can't be larger than file size {}",
|
||||
offset, self.size
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
self.raw_file_async
|
||||
.write_vectored(offset, iovecs, user_data)
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
self.raw_file_async.fsync(user_data)
|
||||
}
|
||||
|
||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||
self.raw_file_async.complete()
|
||||
}
|
||||
}
|
||||
107
block_util/src/fixed_vhd_sync.rs
Normal file
107
block_util/src/fixed_vhd_sync.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use crate::raw_sync::RawFileSync;
|
||||
use crate::vhd::VhdFooter;
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct FixedVhdDiskSync {
|
||||
file: File,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl FixedVhdDiskSync {
|
||||
pub fn new(mut file: File) -> std::io::Result<Self> {
|
||||
let footer = VhdFooter::new(&mut file)?;
|
||||
|
||||
Ok(FixedVhdDiskSync {
|
||||
file,
|
||||
size: footer.current_size(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for FixedVhdDiskSync {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self.size)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(
|
||||
FixedVhdSync::new(self.file.as_raw_fd(), self.size)
|
||||
.map_err(DiskFileError::NewAsyncIo)?,
|
||||
) as Box<dyn AsyncIo>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FixedVhdSync {
|
||||
raw_file_sync: RawFileSync,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl FixedVhdSync {
|
||||
pub fn new(fd: RawFd, size: u64) -> std::io::Result<Self> {
|
||||
Ok(FixedVhdSync {
|
||||
raw_file_sync: RawFileSync::new(fd),
|
||||
size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for FixedVhdSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
self.raw_file_sync.notifier()
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
if offset as u64 >= self.size {
|
||||
return Err(AsyncIoError::ReadVectored(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"Invalid offset {}, can't be larger than file size {}",
|
||||
offset, self.size
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
self.raw_file_sync.read_vectored(offset, iovecs, user_data)
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
if offset as u64 >= self.size {
|
||||
return Err(AsyncIoError::WriteVectored(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"Invalid offset {}, can't be larger than file size {}",
|
||||
offset, self.size
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
self.raw_file_sync.write_vectored(offset, iovecs, user_data)
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
self.raw_file_sync.fsync(user_data)
|
||||
}
|
||||
|
||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||
self.raw_file_sync.complete()
|
||||
}
|
||||
}
|
||||
@@ -13,24 +13,35 @@ extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod async_io;
|
||||
pub mod fixed_vhd_async;
|
||||
pub mod fixed_vhd_sync;
|
||||
pub mod qcow_sync;
|
||||
pub mod raw_async;
|
||||
pub mod raw_sync;
|
||||
pub mod vhd;
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoError, AsyncIoResult, DiskFileError, DiskFileResult};
|
||||
#[cfg(feature = "io_uring")]
|
||||
use io_uring::{opcode, squeue, IoUring, Probe};
|
||||
use io_uring::{opcode, IoUring, Probe};
|
||||
use serde::ser::{Serialize, SerializeStruct, Serializer};
|
||||
use std::cmp;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
#[cfg(feature = "io_uring")]
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::result;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use virtio_bindings::bindings::virtio_blk::*;
|
||||
use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
|
||||
use vm_virtio::DescriptorChain;
|
||||
#[cfg(feature = "io_uring")]
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
const SECTOR_SHIFT: u8 = 9;
|
||||
pub const SECTOR_SIZE: u64 = (0x01 as u64) << SECTOR_SHIFT;
|
||||
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -96,6 +107,9 @@ pub enum ExecuteError {
|
||||
Unsupported(u32),
|
||||
SubmitIoUring(io::Error),
|
||||
GetHostAddress(GuestMemoryError),
|
||||
AsyncRead(AsyncIoError),
|
||||
AsyncWrite(AsyncIoError),
|
||||
AsyncFlush(AsyncIoError),
|
||||
}
|
||||
|
||||
impl ExecuteError {
|
||||
@@ -109,6 +123,9 @@ impl ExecuteError {
|
||||
ExecuteError::Unsupported(_) => VIRTIO_BLK_S_UNSUPP,
|
||||
ExecuteError::SubmitIoUring(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::GetHostAddress(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncRead(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncWrite(_) => VIRTIO_BLK_S_IOERR,
|
||||
ExecuteError::AsyncFlush(_) => VIRTIO_BLK_S_IOERR,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,13 +283,11 @@ impl Request {
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
#[cfg(feature = "io_uring")]
|
||||
pub fn execute_io_uring(
|
||||
pub fn execute_async(
|
||||
&self,
|
||||
mem: &GuestMemoryMmap,
|
||||
io_uring: &mut IoUring,
|
||||
disk_nsectors: u64,
|
||||
disk_image_fd: RawFd,
|
||||
disk_image: &mut dyn AsyncIo,
|
||||
disk_id: &[u8],
|
||||
user_data: u64,
|
||||
) -> result::Result<bool, ExecuteError> {
|
||||
@@ -280,9 +295,6 @@ impl Request {
|
||||
let request_type = self.request_type;
|
||||
let offset = (sector << SECTOR_SHIFT) as libc::off_t;
|
||||
|
||||
let (submitter, sq, _) = io_uring.split();
|
||||
let mut avail_sq = sq.available();
|
||||
|
||||
let mut iovecs = Vec::new();
|
||||
for (data_addr, data_len) in &self.data_descriptors {
|
||||
let mut top: u64 = u64::from(*data_len) / SECTOR_SIZE;
|
||||
@@ -310,49 +322,19 @@ impl Request {
|
||||
// Queue operations expected to be submitted.
|
||||
match request_type {
|
||||
RequestType::In => {
|
||||
// Safe because we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Readv::new(
|
||||
opcode::types::Fd(disk_image_fd),
|
||||
iovecs.as_ptr(),
|
||||
iovecs.len() as u32,
|
||||
)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
disk_image
|
||||
.read_vectored(offset, iovecs, user_data)
|
||||
.map_err(ExecuteError::AsyncRead)?;
|
||||
}
|
||||
RequestType::Out => {
|
||||
// Safe because we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Writev::new(
|
||||
opcode::types::Fd(disk_image_fd),
|
||||
iovecs.as_ptr(),
|
||||
iovecs.len() as u32,
|
||||
)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
disk_image
|
||||
.write_vectored(offset, iovecs, user_data)
|
||||
.map_err(ExecuteError::AsyncWrite)?;
|
||||
}
|
||||
RequestType::Flush => {
|
||||
// Safe because we know the file descriptor is valid.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Fsync::new(opcode::types::Fd(disk_image_fd))
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
disk_image
|
||||
.fsync(Some(user_data))
|
||||
.map_err(ExecuteError::AsyncFlush)?;
|
||||
}
|
||||
RequestType::GetDeviceID => {
|
||||
let (data_addr, data_len) = if self.data_descriptors.len() == 1 {
|
||||
@@ -370,11 +352,6 @@ impl Request {
|
||||
RequestType::Unsupported(t) => return Err(ExecuteError::Unsupported(t)),
|
||||
}
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
avail_sq.sync();
|
||||
submitter.submit().map_err(ExecuteError::SubmitIoUring)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -565,3 +542,150 @@ pub fn block_io_uring_is_supported() -> bool {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "io_uring"))]
|
||||
pub fn block_io_uring_is_supported() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn disk_size(file: &mut dyn Seek, semaphore: &mut Arc<Mutex<()>>) -> DiskFileResult<u64> {
|
||||
// Take the semaphore to ensure other threads are not interacting with
|
||||
// the underlying file.
|
||||
let _lock = semaphore.lock().unwrap();
|
||||
|
||||
Ok(file.seek(SeekFrom::End(0)).map_err(DiskFileError::Size)? as u64)
|
||||
}
|
||||
|
||||
pub trait ReadSeekFile: Read + Seek {}
|
||||
impl<F: Read + Seek> ReadSeekFile for F {}
|
||||
|
||||
pub fn read_vectored_sync(
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
file: &mut dyn ReadSeekFile,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut Vec<(u64, i32)>,
|
||||
semaphore: &mut Arc<Mutex<()>>,
|
||||
) -> AsyncIoResult<()> {
|
||||
// Convert libc::iovec into IoSliceMut
|
||||
let mut slices = Vec::new();
|
||||
for iovec in iovecs.iter() {
|
||||
slices.push(IoSliceMut::new(unsafe { std::mem::transmute(*iovec) }));
|
||||
}
|
||||
|
||||
let result = {
|
||||
// Take the semaphore to ensure other threads are not interacting
|
||||
// with the underlying file.
|
||||
let _lock = semaphore.lock().unwrap();
|
||||
|
||||
// Move the cursor to the right offset
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::ReadVectored)?;
|
||||
|
||||
// Read vectored
|
||||
file.read_vectored(slices.as_mut_slice())
|
||||
.map_err(AsyncIoError::ReadVectored)?
|
||||
};
|
||||
|
||||
completion_list.push((user_data, result as i32));
|
||||
eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub trait WriteSeekFile: Write + Seek {}
|
||||
impl<F: Write + Seek> WriteSeekFile for F {}
|
||||
|
||||
pub fn write_vectored_sync(
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
file: &mut dyn WriteSeekFile,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut Vec<(u64, i32)>,
|
||||
semaphore: &mut Arc<Mutex<()>>,
|
||||
) -> AsyncIoResult<()> {
|
||||
// Convert libc::iovec into IoSlice
|
||||
let mut slices = Vec::new();
|
||||
for iovec in iovecs.iter() {
|
||||
slices.push(IoSlice::new(unsafe { std::mem::transmute(*iovec) }));
|
||||
}
|
||||
|
||||
let result = {
|
||||
// Take the semaphore to ensure other threads are not interacting
|
||||
// with the underlying file.
|
||||
let _lock = semaphore.lock().unwrap();
|
||||
|
||||
// Move the cursor to the right offset
|
||||
file.seek(SeekFrom::Start(offset as u64))
|
||||
.map_err(AsyncIoError::WriteVectored)?;
|
||||
|
||||
// Write vectored
|
||||
file.write_vectored(slices.as_slice())
|
||||
.map_err(AsyncIoError::WriteVectored)?
|
||||
};
|
||||
|
||||
completion_list.push((user_data, result as i32));
|
||||
eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fsync_sync(
|
||||
user_data: Option<u64>,
|
||||
file: &mut dyn Write,
|
||||
eventfd: &EventFd,
|
||||
completion_list: &mut Vec<(u64, i32)>,
|
||||
semaphore: &mut Arc<Mutex<()>>,
|
||||
) -> AsyncIoResult<()> {
|
||||
let result: i32 = {
|
||||
// Take the semaphore to ensure other threads are not interacting
|
||||
// with the underlying file.
|
||||
let _lock = semaphore.lock().unwrap();
|
||||
|
||||
// Flush
|
||||
file.flush().map_err(AsyncIoError::Fsync)?;
|
||||
|
||||
0
|
||||
};
|
||||
|
||||
if let Some(user_data) = user_data {
|
||||
completion_list.push((user_data, result));
|
||||
eventfd.write(1).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub enum ImageType {
|
||||
FixedVhd,
|
||||
Qcow2,
|
||||
Raw,
|
||||
}
|
||||
|
||||
const QCOW_MAGIC: u32 = 0x5146_49fb;
|
||||
|
||||
/// Determine image type through file parsing.
|
||||
pub fn detect_image_type(f: &mut File) -> std::io::Result<ImageType> {
|
||||
// We must create a buffer aligned on 512 bytes with a size being a
|
||||
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
|
||||
#[repr(align(512))]
|
||||
struct Sector {
|
||||
data: [u8; 512],
|
||||
}
|
||||
let mut s = Sector { data: [0; 512] };
|
||||
|
||||
f.read_exact(&mut s.data)?;
|
||||
|
||||
// Check 4 first bytes to get the header value and determine the image type
|
||||
let image_type = if u32::from_be_bytes(s.data[0..4].try_into().unwrap()) == QCOW_MAGIC {
|
||||
ImageType::Qcow2
|
||||
} else if vhd::is_fixed_vhd(f)? {
|
||||
ImageType::FixedVhd
|
||||
} else {
|
||||
ImageType::Raw
|
||||
};
|
||||
|
||||
Ok(image_type)
|
||||
}
|
||||
|
||||
111
block_util/src/qcow_sync.rs
Normal file
111
block_util/src/qcow_sync.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use crate::async_io::{AsyncIo, AsyncIoResult, DiskFile, DiskFileResult};
|
||||
use crate::{disk_size, fsync_sync, read_vectored_sync, write_vectored_sync};
|
||||
use qcow::{QcowFile, RawFile};
|
||||
use std::fs::File;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct QcowDiskSync {
|
||||
qcow_file: QcowFile,
|
||||
semaphore: Arc<Mutex<()>>,
|
||||
}
|
||||
|
||||
impl QcowDiskSync {
|
||||
pub fn new(file: File, direct_io: bool) -> Self {
|
||||
QcowDiskSync {
|
||||
qcow_file: QcowFile::from(RawFile::new(file, direct_io))
|
||||
.expect("Failed creating QcowFile"),
|
||||
semaphore: Arc::new(Mutex::new(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for QcowDiskSync {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
disk_size(&mut self.qcow_file, &mut self.semaphore)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(QcowSync::new(
|
||||
self.qcow_file.clone(),
|
||||
self.semaphore.clone(),
|
||||
)) as Box<dyn AsyncIo>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QcowSync {
|
||||
qcow_file: QcowFile,
|
||||
eventfd: EventFd,
|
||||
completion_list: Vec<(u64, i32)>,
|
||||
semaphore: Arc<Mutex<()>>,
|
||||
}
|
||||
|
||||
impl QcowSync {
|
||||
pub fn new(qcow_file: QcowFile, semaphore: Arc<Mutex<()>>) -> Self {
|
||||
QcowSync {
|
||||
qcow_file,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK)
|
||||
.expect("Failed creating EventFd for QcowSync"),
|
||||
completion_list: Vec::new(),
|
||||
semaphore,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for QcowSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
read_vectored_sync(
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
&mut self.qcow_file,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
&mut self.semaphore,
|
||||
)
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
write_vectored_sync(
|
||||
offset,
|
||||
iovecs,
|
||||
user_data,
|
||||
&mut self.qcow_file,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
&mut self.semaphore,
|
||||
)
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
fsync_sync(
|
||||
user_data,
|
||||
&mut self.qcow_file,
|
||||
&self.eventfd,
|
||||
&mut self.completion_list,
|
||||
&mut self.semaphore,
|
||||
)
|
||||
}
|
||||
|
||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||
self.completion_list.drain(..).collect()
|
||||
}
|
||||
}
|
||||
170
block_util/src/raw_async.rs
Normal file
170
block_util/src/raw_async.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use io_uring::{opcode, squeue, IoUring};
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct RawFileDisk {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl RawFileDisk {
|
||||
pub fn new(file: File) -> Self {
|
||||
RawFileDisk { file }
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for RawFileDisk {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self
|
||||
.file
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(DiskFileError::Size)? as u64)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(
|
||||
RawFileAsync::new(self.file.as_raw_fd(), ring_depth)
|
||||
.map_err(DiskFileError::NewAsyncIo)?,
|
||||
) as Box<dyn AsyncIo>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawFileAsync {
|
||||
fd: RawFd,
|
||||
io_uring: IoUring,
|
||||
eventfd: EventFd,
|
||||
}
|
||||
|
||||
impl RawFileAsync {
|
||||
pub fn new(fd: RawFd, ring_depth: u32) -> std::io::Result<Self> {
|
||||
let io_uring = IoUring::new(ring_depth)?;
|
||||
let eventfd = EventFd::new(libc::EFD_NONBLOCK)?;
|
||||
|
||||
// Register the io_uring eventfd that will notify when something in
|
||||
// the completion queue is ready.
|
||||
io_uring.submitter().register_eventfd(eventfd.as_raw_fd())?;
|
||||
|
||||
Ok(RawFileAsync {
|
||||
fd,
|
||||
io_uring,
|
||||
eventfd,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for RawFileAsync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let (submitter, sq, _) = self.io_uring.split();
|
||||
let mut avail_sq = sq.available();
|
||||
|
||||
// Safe because we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Readv::new(
|
||||
opcode::types::Fd(self.fd),
|
||||
iovecs.as_ptr(),
|
||||
iovecs.len() as u32,
|
||||
)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
avail_sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::ReadVectored)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let (submitter, sq, _) = self.io_uring.split();
|
||||
let mut avail_sq = sq.available();
|
||||
|
||||
// Safe because we know the file descriptor is valid and we
|
||||
// relied on vm-memory to provide the buffer address.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Writev::new(
|
||||
opcode::types::Fd(self.fd),
|
||||
iovecs.as_ptr(),
|
||||
iovecs.len() as u32,
|
||||
)
|
||||
.offset(offset)
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
avail_sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::WriteVectored)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
if let Some(user_data) = user_data {
|
||||
let (submitter, sq, _) = self.io_uring.split();
|
||||
let mut avail_sq = sq.available();
|
||||
|
||||
// Safe because we know the file descriptor is valid.
|
||||
let _ = unsafe {
|
||||
avail_sq.push(
|
||||
opcode::Fsync::new(opcode::types::Fd(self.fd))
|
||||
.build()
|
||||
.flags(squeue::Flags::ASYNC)
|
||||
.user_data(user_data),
|
||||
)
|
||||
};
|
||||
|
||||
// Update the submission queue and submit new operations to the
|
||||
// io_uring instance.
|
||||
avail_sq.sync();
|
||||
submitter.submit().map_err(AsyncIoError::Fsync)?;
|
||||
} else {
|
||||
unsafe { libc::fsync(self.fd) };
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||
let mut completion_list = Vec::new();
|
||||
|
||||
let cq = self.io_uring.completion();
|
||||
for cq_entry in cq.available() {
|
||||
completion_list.push((cq_entry.user_data(), cq_entry.result()));
|
||||
}
|
||||
|
||||
completion_list
|
||||
}
|
||||
}
|
||||
122
block_util/src/raw_sync.rs
Normal file
122
block_util/src/raw_sync.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
|
||||
use crate::async_io::{
|
||||
AsyncIo, AsyncIoError, AsyncIoResult, DiskFile, DiskFileError, DiskFileResult,
|
||||
};
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
pub struct RawFileDiskSync {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl RawFileDiskSync {
|
||||
pub fn new(file: File) -> Self {
|
||||
RawFileDiskSync { file }
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskFile for RawFileDiskSync {
|
||||
fn size(&mut self) -> DiskFileResult<u64> {
|
||||
Ok(self
|
||||
.file
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(DiskFileError::Size)? as u64)
|
||||
}
|
||||
|
||||
fn new_async_io(&self, _ring_depth: u32) -> DiskFileResult<Box<dyn AsyncIo>> {
|
||||
Ok(Box::new(RawFileSync::new(self.file.as_raw_fd())) as Box<dyn AsyncIo>)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawFileSync {
|
||||
fd: RawFd,
|
||||
eventfd: EventFd,
|
||||
completion_list: Vec<(u64, i32)>,
|
||||
}
|
||||
|
||||
impl RawFileSync {
|
||||
pub fn new(fd: RawFd) -> Self {
|
||||
RawFileSync {
|
||||
fd,
|
||||
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
|
||||
completion_list: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncIo for RawFileSync {
|
||||
fn notifier(&self) -> &EventFd {
|
||||
&self.eventfd
|
||||
}
|
||||
|
||||
fn read_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let result = unsafe {
|
||||
libc::preadv(
|
||||
self.fd as libc::c_int,
|
||||
iovecs.as_ptr() as *const libc::iovec,
|
||||
iovecs.len() as libc::c_int,
|
||||
offset,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::ReadVectored(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push((user_data, result as i32));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_vectored(
|
||||
&mut self,
|
||||
offset: libc::off_t,
|
||||
iovecs: Vec<libc::iovec>,
|
||||
user_data: u64,
|
||||
) -> AsyncIoResult<()> {
|
||||
let result = unsafe {
|
||||
libc::pwritev(
|
||||
self.fd as libc::c_int,
|
||||
iovecs.as_ptr() as *const libc::iovec,
|
||||
iovecs.len() as libc::c_int,
|
||||
offset,
|
||||
)
|
||||
};
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::WriteVectored(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
self.completion_list.push((user_data, result as i32));
|
||||
self.eventfd.write(1).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
|
||||
let result = unsafe { libc::fsync(self.fd as libc::c_int) };
|
||||
if result < 0 {
|
||||
return Err(AsyncIoError::Fsync(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
if let Some(user_data) = user_data {
|
||||
self.completion_list.push((user_data, result as i32));
|
||||
self.eventfd.write(1).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn complete(&mut self) -> Vec<(u64, i32)> {
|
||||
self.completion_list.drain(..).collect()
|
||||
}
|
||||
}
|
||||
221
block_util/src/vhd.rs
Normal file
221
block_util/src/vhd.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
// Copyright © 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct VhdFooter {
|
||||
cookie: u64,
|
||||
features: u32,
|
||||
file_format_version: u32,
|
||||
data_offset: u64,
|
||||
time_stamp: u32,
|
||||
creator_application: u32,
|
||||
creator_version: u32,
|
||||
creator_host_os: u32,
|
||||
original_size: u64,
|
||||
current_size: u64,
|
||||
disk_geometry: u32,
|
||||
disk_type: u32,
|
||||
checksum: u32,
|
||||
unique_id: u128,
|
||||
saved_state: u8,
|
||||
}
|
||||
|
||||
impl VhdFooter {
|
||||
pub fn new(file: &mut File) -> std::io::Result<VhdFooter> {
|
||||
// We must create a buffer aligned on 512 bytes with a size being a
|
||||
// multiple of 512 bytes as the file might be opened with O_DIRECT flag.
|
||||
#[repr(align(512))]
|
||||
struct Sector {
|
||||
data: [u8; 512],
|
||||
}
|
||||
let mut s = Sector { data: [0; 512] };
|
||||
|
||||
// Place the cursor 512 bytes before the end of the file, as this is
|
||||
// where the footer starts.
|
||||
file.seek(SeekFrom::End(-512))?;
|
||||
|
||||
// Fill in the VhdFooter structure
|
||||
file.read_exact(&mut s.data)?;
|
||||
|
||||
Ok(VhdFooter {
|
||||
cookie: u64::from_be_bytes(s.data[0..8].try_into().unwrap()),
|
||||
features: u32::from_be_bytes(s.data[8..12].try_into().unwrap()),
|
||||
file_format_version: u32::from_be_bytes(s.data[12..16].try_into().unwrap()),
|
||||
data_offset: u64::from_be_bytes(s.data[16..24].try_into().unwrap()),
|
||||
time_stamp: u32::from_be_bytes(s.data[24..28].try_into().unwrap()),
|
||||
creator_application: u32::from_be_bytes(s.data[28..32].try_into().unwrap()),
|
||||
creator_version: u32::from_be_bytes(s.data[32..36].try_into().unwrap()),
|
||||
creator_host_os: u32::from_be_bytes(s.data[36..40].try_into().unwrap()),
|
||||
original_size: u64::from_be_bytes(s.data[40..48].try_into().unwrap()),
|
||||
current_size: u64::from_be_bytes(s.data[48..56].try_into().unwrap()),
|
||||
disk_geometry: u32::from_be_bytes(s.data[56..60].try_into().unwrap()),
|
||||
disk_type: u32::from_be_bytes(s.data[60..64].try_into().unwrap()),
|
||||
checksum: u32::from_be_bytes(s.data[64..68].try_into().unwrap()),
|
||||
unique_id: u128::from_be_bytes(s.data[68..84].try_into().unwrap()),
|
||||
saved_state: u8::from_be_bytes(s.data[84..85].try_into().unwrap()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cookie(&self) -> u64 {
|
||||
self.cookie
|
||||
}
|
||||
pub fn features(&self) -> u32 {
|
||||
self.features
|
||||
}
|
||||
pub fn file_format_version(&self) -> u32 {
|
||||
self.file_format_version
|
||||
}
|
||||
pub fn data_offset(&self) -> u64 {
|
||||
self.data_offset
|
||||
}
|
||||
pub fn time_stamp(&self) -> u32 {
|
||||
self.time_stamp
|
||||
}
|
||||
pub fn creator_application(&self) -> u32 {
|
||||
self.creator_application
|
||||
}
|
||||
pub fn creator_version(&self) -> u32 {
|
||||
self.creator_version
|
||||
}
|
||||
pub fn creator_host_os(&self) -> u32 {
|
||||
self.creator_host_os
|
||||
}
|
||||
pub fn original_size(&self) -> u64 {
|
||||
self.original_size
|
||||
}
|
||||
pub fn current_size(&self) -> u64 {
|
||||
self.current_size
|
||||
}
|
||||
pub fn disk_geometry(&self) -> u32 {
|
||||
self.disk_geometry
|
||||
}
|
||||
pub fn disk_type(&self) -> u32 {
|
||||
self.disk_type
|
||||
}
|
||||
pub fn checksum(&self) -> u32 {
|
||||
self.checksum
|
||||
}
|
||||
pub fn unique_id(&self) -> u128 {
|
||||
self.unique_id
|
||||
}
|
||||
pub fn saved_state(&self) -> u8 {
|
||||
self.saved_state
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine image type through file parsing.
|
||||
pub fn is_fixed_vhd(f: &mut File) -> std::io::Result<bool> {
|
||||
let footer = VhdFooter::new(f)?;
|
||||
|
||||
// "conectix" => 0x636f6e6563746978
|
||||
Ok(footer.cookie() == 0x636f6e6563746978
|
||||
&& footer.file_format_version() == 0x0001_0000
|
||||
&& footer.data_offset() == 0xffff_ffff_ffff_ffff
|
||||
&& footer.disk_type() == 0x2)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{is_fixed_vhd, VhdFooter};
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
use tempfile::tempfile;
|
||||
|
||||
fn valid_fixed_vhd_footer() -> Vec<u8> {
|
||||
vec![
|
||||
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
||||
0x00, 0x00, 0x00, 0x02, // features
|
||||
0x00, 0x01, 0x00, 0x00, // file format version
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // data offset
|
||||
0x27, 0xa6, 0xa6, 0x5d, // time stamp
|
||||
0x71, 0x65, 0x6d, 0x75, // creator application
|
||||
0x00, 0x05, 0x00, 0x03, // creator version
|
||||
0x57, 0x69, 0x32, 0x6b, // creator host os
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // original size
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // current size
|
||||
0x11, 0xe0, 0x10, 0x3f, // disk geometry
|
||||
0x00, 0x00, 0x00, 0x02, // disk type
|
||||
0x00, 0x00, 0x00, 0x00, // checksum
|
||||
0x98, 0x7b, 0xb1, 0xcd, 0x84, 0x14, 0x41, 0xfc, 0xa4, 0xab, 0xd0, 0x69, 0x45, 0x2b,
|
||||
0xf2, 0x23, // unique id
|
||||
0x00, // saved state
|
||||
]
|
||||
}
|
||||
|
||||
fn valid_dynamic_vhd_footer() -> Vec<u8> {
|
||||
vec![
|
||||
0x63, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x78, // cookie
|
||||
0x00, 0x00, 0x00, 0x02, // features
|
||||
0x00, 0x01, 0x00, 0x00, // file format version
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // data offset
|
||||
0x27, 0xa6, 0xa6, 0x5d, // time stamp
|
||||
0x71, 0x65, 0x6d, 0x75, // creator application
|
||||
0x00, 0x05, 0x00, 0x03, // creator version
|
||||
0x57, 0x69, 0x32, 0x6b, // creator host os
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // original size
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, // current size
|
||||
0x11, 0xe0, 0x10, 0x3f, // disk geometry
|
||||
0x00, 0x00, 0x00, 0x03, // disk type
|
||||
0x00, 0x00, 0x00, 0x00, // checksum
|
||||
0x98, 0x7b, 0xb1, 0xcd, 0x84, 0x14, 0x41, 0xfc, 0xa4, 0xab, 0xd0, 0x69, 0x45, 0x2b,
|
||||
0xf2, 0x23, // unique id
|
||||
0x00, // saved state
|
||||
]
|
||||
}
|
||||
|
||||
fn with_file<F>(footer: &[u8], mut testfn: F)
|
||||
where
|
||||
F: FnMut(File),
|
||||
{
|
||||
let mut disk_file: File = tempfile().unwrap();
|
||||
disk_file.set_len(0x1000_0200).unwrap();
|
||||
disk_file.seek(SeekFrom::Start(0x1000_0000)).unwrap();
|
||||
disk_file.write_all(&footer).unwrap();
|
||||
|
||||
testfn(disk_file); // File closed when the function exits.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_vhd_footer() {
|
||||
with_file(&valid_fixed_vhd_footer(), |mut file: File| {
|
||||
let vhd_footer = VhdFooter::new(&mut file).expect("Failed to create VHD footer");
|
||||
assert_eq!(vhd_footer.cookie(), 0x636f_6e65_6374_6978);
|
||||
assert_eq!(vhd_footer.features(), 0x0000_0002);
|
||||
assert_eq!(vhd_footer.file_format_version(), 0x0001_0000);
|
||||
assert_eq!(vhd_footer.data_offset(), 0xffff_ffff_ffff_ffff);
|
||||
assert_eq!(vhd_footer.time_stamp(), 0x27a6_a65d);
|
||||
assert_eq!(vhd_footer.creator_application(), 0x7165_6d75);
|
||||
assert_eq!(vhd_footer.creator_version(), 0x0005_0003);
|
||||
assert_eq!(vhd_footer.creator_host_os(), 0x5769_326b);
|
||||
assert_eq!(vhd_footer.original_size(), 0x0000_0000_1000_0000);
|
||||
assert_eq!(vhd_footer.current_size(), 0x0000_0000_1000_0000);
|
||||
assert_eq!(vhd_footer.disk_geometry(), 0x11e0_103f);
|
||||
assert_eq!(vhd_footer.disk_type(), 0x0000_0002);
|
||||
assert_eq!(vhd_footer.checksum(), 0x0000_0000);
|
||||
assert_eq!(
|
||||
vhd_footer.unique_id(),
|
||||
0x987b_b1cd_8414_41fc_a4ab_d069_452b_f223
|
||||
);
|
||||
assert_eq!(vhd_footer.saved_state(), 0x00);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_fixed_vhd() {
|
||||
with_file(&valid_fixed_vhd_footer(), |mut file: File| {
|
||||
assert!(is_fixed_vhd(&mut file).unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_not_fixed_vhd() {
|
||||
with_file(&valid_dynamic_vhd_footer(), |mut file: File| {
|
||||
assert!(!(is_fixed_vhd(&mut file).unwrap()));
|
||||
});
|
||||
}
|
||||
}
|
||||
2
build.rs
2
build.rs
@@ -9,7 +9,7 @@ extern crate clap;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let mut version = crate_version!().to_string();
|
||||
let mut version = "v".to_owned() + crate_version!();
|
||||
|
||||
if let Ok(git_out) = Command::new("git").args(&["describe", "--dirty"]).output() {
|
||||
if git_out.status.success() {
|
||||
|
||||
@@ -8,19 +8,19 @@ anyhow = "1.0"
|
||||
bitflags = ">=1.2.1"
|
||||
byteorder = "1.3.4"
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.77"
|
||||
log = "0.4.11"
|
||||
libc = "0.2.86"
|
||||
log = "0.4.14"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-device = { path = "../vm-device" }
|
||||
acpi_tables = { path = "../acpi_tables", optional = true }
|
||||
vm-memory = "0.2.1"
|
||||
vm-memory = "0.5.0"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
tempfile = "3.2.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
//
|
||||
|
||||
use acpi_tables::{aml, aml::Aml};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::time::Instant;
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
use vm_memory::GuestAddress;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
use HotPlugNotificationFlags;
|
||||
use AcpiNotificationFlags;
|
||||
|
||||
pub const GED_DEVICE_ACPI_SIZE: usize = 0x1;
|
||||
|
||||
/// A device for handling ACPI shutdown and reboot
|
||||
pub struct AcpiShutdownDevice {
|
||||
@@ -36,7 +39,7 @@ impl BusDevice for AcpiShutdownDevice {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data[0] == 1 {
|
||||
debug!("ACPI Reboot signalled");
|
||||
if let Err(e) = self.reset_evt.write(1) {
|
||||
@@ -54,28 +57,35 @@ impl BusDevice for AcpiShutdownDevice {
|
||||
error!("Error triggering ACPI shutdown event: {}", e);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A device for handling ACPI GED event generation
|
||||
pub struct AcpiGEDDevice {
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
notification_type: HotPlugNotificationFlags,
|
||||
notification_type: AcpiNotificationFlags,
|
||||
ged_irq: u32,
|
||||
address: GuestAddress,
|
||||
}
|
||||
|
||||
impl AcpiGEDDevice {
|
||||
pub fn new(interrupt: Arc<Box<dyn InterruptSourceGroup>>, ged_irq: u32) -> AcpiGEDDevice {
|
||||
pub fn new(
|
||||
interrupt: Arc<Box<dyn InterruptSourceGroup>>,
|
||||
ged_irq: u32,
|
||||
address: GuestAddress,
|
||||
) -> AcpiGEDDevice {
|
||||
AcpiGEDDevice {
|
||||
interrupt,
|
||||
notification_type: HotPlugNotificationFlags::NO_DEVICES_CHANGED,
|
||||
notification_type: AcpiNotificationFlags::NO_DEVICES_CHANGED,
|
||||
ged_irq,
|
||||
address,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify(
|
||||
&mut self,
|
||||
notification_type: HotPlugNotificationFlags,
|
||||
notification_type: AcpiNotificationFlags,
|
||||
) -> Result<(), std::io::Error> {
|
||||
self.notification_type |= notification_type;
|
||||
self.interrupt.trigger(0)
|
||||
@@ -91,10 +101,8 @@ impl BusDevice for AcpiGEDDevice {
|
||||
// Spec has all fields as zero
|
||||
fn read(&mut self, _base: u64, _offset: u64, data: &mut [u8]) {
|
||||
data[0] = self.notification_type.bits();
|
||||
self.notification_type = HotPlugNotificationFlags::NO_DEVICES_CHANGED;
|
||||
self.notification_type = AcpiNotificationFlags::NO_DEVICES_CHANGED;
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
@@ -115,7 +123,12 @@ impl Aml for AcpiGEDDevice {
|
||||
self.ged_irq,
|
||||
)]),
|
||||
),
|
||||
&aml::OpRegion::new("GDST".into(), aml::OpRegionSpace::SystemIO, 0xb000, 0x1),
|
||||
&aml::OpRegion::new(
|
||||
"GDST".into(),
|
||||
aml::OpRegionSpace::SystemMemory,
|
||||
self.address.0 as usize,
|
||||
GED_DEVICE_ACPI_SIZE,
|
||||
),
|
||||
&aml::Field::new(
|
||||
"GDST".into(),
|
||||
aml::FieldAccessType::Byte,
|
||||
@@ -143,6 +156,14 @@ impl Aml for AcpiGEDDevice {
|
||||
&aml::Equal::new(&aml::Local(1), &4usize),
|
||||
vec![&aml::MethodCall::new("\\_SB_.PCI0.PCNT".into(), vec![])],
|
||||
),
|
||||
&aml::And::new(&aml::Local(1), &aml::Local(0), &8usize),
|
||||
&aml::If::new(
|
||||
&aml::Equal::new(&aml::Local(1), &8usize),
|
||||
vec![&aml::Notify::new(
|
||||
&aml::Path::new("\\_SB_.PWRB"),
|
||||
&0x80usize,
|
||||
)],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -183,6 +204,4 @@ impl BusDevice for AcpiPMTimerDevice {
|
||||
|
||||
data.copy_from_slice(&counter.to_le_bytes());
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, _data: &[u8]) {}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig,
|
||||
};
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
type Result<T> = result::Result<T, Error>;
|
||||
|
||||
@@ -62,23 +62,8 @@ impl InterruptController for Gic {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const GIC_SNAPSHOT_ID: &str = "gic";
|
||||
impl Snapshottable for Gic {
|
||||
fn id(&self) -> String {
|
||||
GIC_SNAPSHOT_ID.to_string()
|
||||
}
|
||||
|
||||
fn snapshot(&mut self) -> std::result::Result<Snapshot, MigratableError> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn restore(&mut self, _snapshot: Snapshot) -> std::result::Result<(), MigratableError> {
|
||||
unimplemented!();
|
||||
fn notifier(&self, irq: usize) -> Option<EventFd> {
|
||||
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pausable for Gic {}
|
||||
impl Transportable for Gic {}
|
||||
impl Migratable for Gic {}
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
|
||||
use std::io;
|
||||
use std::result;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Invalid destination mode.
|
||||
InvalidDestinationMode,
|
||||
/// Invalid trigger mode.
|
||||
InvalidTriggerMode,
|
||||
/// Invalid delivery mode.
|
||||
@@ -58,4 +57,5 @@ pub trait InterruptController: Send {
|
||||
fn enable(&self) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn end_of_interrupt(&mut self, vec: u8);
|
||||
fn notifier(&self, irq: usize) -> Option<EventFd>;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use super::interrupt_controller::{Error, InterruptController};
|
||||
use anyhow::anyhow;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use vm_device::interrupt::{
|
||||
InterruptIndex, InterruptManager, InterruptSourceConfig, InterruptSourceGroup,
|
||||
MsiIrqGroupConfig, MsiIrqSourceConfig,
|
||||
@@ -24,6 +24,7 @@ use vm_migration::{
|
||||
Migratable, MigratableError, Pausable, Snapshot, SnapshotDataSection, Snapshottable,
|
||||
Transportable,
|
||||
};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "GuestAddress")]
|
||||
@@ -67,10 +68,12 @@ fn trigger_mode(entry: RedirectionTableEntry) -> u8 {
|
||||
fn interrupt_mask(entry: RedirectionTableEntry) -> u8 {
|
||||
((entry >> 16) & 0x1u64) as u8
|
||||
}
|
||||
fn destination_field_physical(entry: RedirectionTableEntry) -> u8 {
|
||||
((entry >> 56) & 0xfu64) as u8
|
||||
}
|
||||
fn destination_field_logical(entry: RedirectionTableEntry) -> u8 {
|
||||
fn destination_field(entry: RedirectionTableEntry) -> u8 {
|
||||
// When the destination mode is physical, the destination field should only
|
||||
// be defined through bits 56-59, as defined in the IOAPIC specification.
|
||||
// But from the APIC specification, the APIC ID is always defined on 8 bits
|
||||
// no matter which destination mode is selected. That's why we always
|
||||
// retrieve the destination field based on bits 56-63.
|
||||
((entry >> 56) & 0xffu64) as u8
|
||||
}
|
||||
fn set_delivery_status(entry: &mut RedirectionTableEntry, val: u8) {
|
||||
@@ -100,12 +103,6 @@ const IOWIN_OFF: u8 = 0x10;
|
||||
const IOWIN_SCALE: u8 = 0x2;
|
||||
const REG_MAX_OFFSET: u8 = IOWIN_OFF + (NUM_IOAPIC_PINS as u8 * 2) - 1;
|
||||
|
||||
#[repr(u8)]
|
||||
enum DestinationMode {
|
||||
Physical = 0,
|
||||
Logical = 1,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
enum TriggerMode {
|
||||
Edge = 0,
|
||||
@@ -171,7 +168,7 @@ impl BusDevice for Ioapic {
|
||||
LittleEndian::write_u32(data, value);
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
assert!(data.len() == 4);
|
||||
|
||||
debug!("IOAPIC_W @ offset 0x{:x}", offset);
|
||||
@@ -185,6 +182,7 @@ impl BusDevice for Ioapic {
|
||||
error!("IOAPIC: failed writing at offset {}", offset);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,20 +194,18 @@ impl Ioapic {
|
||||
) -> Result<Ioapic> {
|
||||
let interrupt_source_group = interrupt_manager
|
||||
.create_group(MsiIrqGroupConfig {
|
||||
base: 0 as InterruptIndex,
|
||||
base: 0,
|
||||
count: NUM_IOAPIC_PINS as InterruptIndex,
|
||||
})
|
||||
.map_err(Error::CreateInterruptSourceGroup)?;
|
||||
|
||||
interrupt_source_group
|
||||
.enable()
|
||||
.map_err(Error::EnableInterrupt)?;
|
||||
|
||||
// The IOAPIC is created with entries already masked. The guest will be
|
||||
// in charge of unmasking them if/when necessary.
|
||||
Ok(Ioapic {
|
||||
id,
|
||||
id_reg: 0,
|
||||
reg_sel: 0,
|
||||
reg_entries: [0; NUM_IOAPIC_PINS],
|
||||
reg_entries: [0x10000; NUM_IOAPIC_PINS],
|
||||
used_entries: [false; NUM_IOAPIC_PINS],
|
||||
apic_address,
|
||||
interrupt_source_group,
|
||||
@@ -220,6 +216,16 @@ impl Ioapic {
|
||||
debug!("IOAPIC_W reg 0x{:x}, val 0x{:x}", self.reg_sel, val);
|
||||
|
||||
match self.reg_sel as u8 {
|
||||
IOAPIC_REG_VERSION => {
|
||||
if val == 0 {
|
||||
// Windows writes zero here (see #1791)
|
||||
} else {
|
||||
error!(
|
||||
"IOAPIC: invalid write to version register (0x{:x}): 0x{:x}",
|
||||
self.reg_sel, val
|
||||
);
|
||||
}
|
||||
}
|
||||
IOAPIC_REG_ID => self.id_reg = (val >> 24) & 0xf,
|
||||
IOWIN_OFF..=REG_MAX_OFFSET => {
|
||||
let (index, is_high_bits) = decode_irq_from_selector(self.reg_sel as u8);
|
||||
@@ -241,7 +247,10 @@ impl Ioapic {
|
||||
// Store the information this IRQ is now being used.
|
||||
self.used_entries[index] = true;
|
||||
}
|
||||
_ => error!("IOAPIC: invalid write to register offset"),
|
||||
_ => error!(
|
||||
"IOAPIC: invalid write to register offset 0x{:x}",
|
||||
self.reg_sel
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +269,10 @@ impl Ioapic {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("IOAPIC: invalid read from register offset");
|
||||
error!(
|
||||
"IOAPIC: invalid read from register offset 0x{:x}",
|
||||
self.reg_sel
|
||||
);
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -296,11 +308,7 @@ impl Ioapic {
|
||||
|
||||
// Validate Destination Mode value, and retrieve Destination ID
|
||||
let destination_mode = destination_mode(entry);
|
||||
let destination_id: u8 = match destination_mode {
|
||||
x if x == DestinationMode::Physical as u8 => destination_field_physical(entry),
|
||||
x if x == DestinationMode::Logical as u8 => destination_field_logical(entry),
|
||||
_ => return Err(Error::InvalidDestinationMode),
|
||||
};
|
||||
let destination_id = destination_field(entry);
|
||||
|
||||
// When this bit is set, the message is directed to the processor with
|
||||
// the lowest interrupt priority among processors that can receive the
|
||||
@@ -398,6 +406,10 @@ impl InterruptController for Ioapic {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self, irq: usize) -> Option<EventFd> {
|
||||
self.interrupt_source_group.notifier(irq as InterruptIndex)
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshottable for Ioapic {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use libc::{clock_gettime, gmtime_r, time_t, timespec, tm, CLOCK_REALTIME};
|
||||
use std::cmp::min;
|
||||
use std::mem;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use vm_device::BusDevice;
|
||||
|
||||
const INDEX_MASK: u8 = 0x7f;
|
||||
@@ -44,16 +45,17 @@ impl Cmos {
|
||||
}
|
||||
|
||||
impl BusDevice for Cmos {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() != 1 {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
match offset {
|
||||
INDEX_OFFSET => self.index = data[0] & INDEX_MASK,
|
||||
DATA_OFFSET => self.data[self.index as usize] = data[0],
|
||||
o => panic!("bad write offset on CMOS device: {}", o),
|
||||
}
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
fn read(&mut self, _base: u64, offset: u64, data: &mut [u8]) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
||||
//
|
||||
|
||||
use std::sync::{Arc, Barrier};
|
||||
use vm_device::BusDevice;
|
||||
|
||||
/// Provides firmware debug output via I/O port controls
|
||||
@@ -30,11 +31,13 @@ impl BusDevice for FwDebugDevice {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, _offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() == 1 {
|
||||
print!("{}", data[0] as char);
|
||||
} else {
|
||||
error!("Invalid write size on debug port: {}", data.len())
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use std::sync::{Arc, Barrier};
|
||||
use vm_device::BusDevice;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
/// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
|
||||
pub struct I8042Device {
|
||||
@@ -32,12 +32,14 @@ impl BusDevice for I8042Device {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() == 1 && data[0] == 0xfe && offset == 3 {
|
||||
debug!("i8042 reset signalled");
|
||||
if let Err(e) = self.reset_evt.write(1) {
|
||||
error!("Error triggering i8042 reset event: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
//! a real-time clock input.
|
||||
//!
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::time::Instant;
|
||||
use std::{io, result};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
@@ -20,7 +20,7 @@ use vm_device::BusDevice;
|
||||
// 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 RTCLR: u64 = 0x8; // Load Register.
|
||||
const RTCCR: u64 = 0xc; // Control Register.
|
||||
const RTCIMSC: u64 = 0x10; // Interrupt Mask Set or Clear Register.
|
||||
const RTCRIS: u64 = 0x14; // Raw Interrupt Status.
|
||||
@@ -339,7 +339,7 @@ impl BusDevice for RTC {
|
||||
let v;
|
||||
let mut read_ok = true;
|
||||
|
||||
if offset < AMBA_ID_HIGH && offset >= AMBA_ID_LOW {
|
||||
if (AMBA_ID_LOW..AMBA_ID_HIGH).contains(&offset) {
|
||||
let index = ((offset - AMBA_ID_LOW) >> 2) as usize;
|
||||
v = u32::from(PL031_ID[index]);
|
||||
} else {
|
||||
@@ -371,7 +371,7 @@ impl BusDevice for RTC {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() <= 4 {
|
||||
let v = read_le_u32(&data[..]);
|
||||
if let Err(e) = self.handle_write(offset, v) {
|
||||
@@ -384,6 +384,8 @@ impl BusDevice for RTC {
|
||||
data.len()
|
||||
);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,8 +476,8 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notifier(&self, _index: InterruptIndex) -> Option<&EventFd> {
|
||||
Some(&self.event_fd)
|
||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||
Some(self.event_fd.try_clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
use anyhow::anyhow;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::{io, result};
|
||||
use vm_device::interrupt::InterruptSourceGroup;
|
||||
use vm_device::BusDevice;
|
||||
@@ -275,12 +275,14 @@ impl BusDevice for Serial {
|
||||
};
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if data.len() != 1 {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Err(_e) = self.handle_write(offset as u8, data[0]) {}
|
||||
self.handle_write(offset as u8, data[0]).ok();
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,6 +356,9 @@ mod tests {
|
||||
) -> result::Result<(), std::io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn notifier(&self, _index: InterruptIndex) -> Option<EventFd> {
|
||||
Some(self.event_fd.try_clone().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl TestInterrupt {
|
||||
@@ -394,13 +399,13 @@ mod tests {
|
||||
Box::new(serial_out.clone()),
|
||||
);
|
||||
|
||||
serial.write(0, DATA as u64, &['x' as u8, 'y' as u8]);
|
||||
serial.write(0, DATA as u64, &['a' as u8]);
|
||||
serial.write(0, DATA as u64, &['b' as u8]);
|
||||
serial.write(0, DATA as u64, &['c' as u8]);
|
||||
serial.write(0, DATA as u64, &[b'x', b'y']);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, &[b'b']);
|
||||
serial.write(0, DATA as u64, &[b'c']);
|
||||
assert_eq!(
|
||||
serial_out.buf.lock().unwrap().as_slice(),
|
||||
&['a' as u8, 'b' as u8, 'c' as u8]
|
||||
&[b'a', b'b', b'c']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -411,16 +416,14 @@ mod tests {
|
||||
let mut serial = Serial::new_out(
|
||||
String::from(SERIAL_NAME),
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
Box::new(serial_out.clone()),
|
||||
Box::new(serial_out),
|
||||
);
|
||||
|
||||
// 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)
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
serial.write(0, IER as u64, &[IER_RECV_BIT]);
|
||||
serial
|
||||
.queue_input_bytes(&['a' as u8, 'b' as u8, 'c' as u8])
|
||||
.unwrap();
|
||||
serial.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
|
||||
@@ -433,11 +436,11 @@ mod tests {
|
||||
serial.read(0, LSR as u64, &mut data[..]);
|
||||
assert_ne!(data[0] & LSR_DATA_BIT, 0);
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'a' as u8);
|
||||
assert_eq!(data[0], b'a');
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'b' as u8);
|
||||
assert_eq!(data[0], b'b');
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'c' as u8);
|
||||
assert_eq!(data[0], b'c');
|
||||
|
||||
// check if reading from the largest u8 offset returns 0
|
||||
serial.read(0, 0xff, &mut data[..]);
|
||||
@@ -456,7 +459,7 @@ mod tests {
|
||||
// counter doesn't change (for 0 it blocks)
|
||||
assert!(intr_evt.write(1).is_ok());
|
||||
serial.write(0, IER as u64, &[IER_THR_BIT]);
|
||||
serial.write(0, DATA as u64, &['a' as u8]);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
|
||||
assert_eq!(intr_evt.read().unwrap(), 2);
|
||||
let mut data = [0u8];
|
||||
@@ -474,13 +477,13 @@ mod tests {
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
serial.write(0, LCR as u64, &[LCR_DLAB_BIT as u8]);
|
||||
serial.write(0, DLAB_LOW as u64, &[0x12 as u8]);
|
||||
serial.write(0, DLAB_HIGH as u64, &[0x34 as u8]);
|
||||
serial.write(0, LCR as u64, &[LCR_DLAB_BIT]);
|
||||
serial.write(0, DLAB_LOW as u64, &[0x12]);
|
||||
serial.write(0, DLAB_HIGH as u64, &[0x34]);
|
||||
|
||||
let mut data = [0u8];
|
||||
serial.read(0, LCR as u64, &mut data[..]);
|
||||
assert_eq!(data[0], LCR_DLAB_BIT as u8);
|
||||
assert_eq!(data[0], LCR_DLAB_BIT);
|
||||
serial.read(0, DLAB_LOW as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 0x12);
|
||||
serial.read(0, DLAB_HIGH as u64, &mut data[..]);
|
||||
@@ -495,22 +498,22 @@ mod tests {
|
||||
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
|
||||
);
|
||||
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
|
||||
serial.write(0, DATA as u64, &['a' as u8]);
|
||||
serial.write(0, DATA as u64, &['b' as u8]);
|
||||
serial.write(0, DATA as u64, &['c' as u8]);
|
||||
serial.write(0, MCR as u64, &[MCR_LOOP_BIT]);
|
||||
serial.write(0, DATA as u64, &[b'a']);
|
||||
serial.write(0, DATA as u64, &[b'b']);
|
||||
serial.write(0, DATA as u64, &[b'c']);
|
||||
|
||||
let mut data = [0u8];
|
||||
serial.read(0, MSR as u64, &mut data[..]);
|
||||
assert_eq!(data[0], DEFAULT_MODEM_STATUS as u8);
|
||||
assert_eq!(data[0], DEFAULT_MODEM_STATUS);
|
||||
serial.read(0, MCR as u64, &mut data[..]);
|
||||
assert_eq!(data[0], MCR_LOOP_BIT as u8);
|
||||
assert_eq!(data[0], MCR_LOOP_BIT);
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'a' as u8);
|
||||
assert_eq!(data[0], b'a');
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'b' as u8);
|
||||
assert_eq!(data[0], b'b');
|
||||
serial.read(0, DATA as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 'c' as u8);
|
||||
assert_eq!(data[0], b'c');
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -521,10 +524,10 @@ mod tests {
|
||||
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]);
|
||||
|
||||
let mut data = [0u8];
|
||||
serial.read(0, SCR as u64, &mut data[..]);
|
||||
assert_eq!(data[0], 0x12 as u8);
|
||||
assert_eq!(data[0], 0x12);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
#[cfg(feature = "acpi")]
|
||||
mod acpi;
|
||||
pub mod acpi;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod gic;
|
||||
pub mod interrupt_controller;
|
||||
@@ -38,10 +38,11 @@ pub mod legacy;
|
||||
pub use self::acpi::{AcpiGEDDevice, AcpiPMTimerDevice, AcpiShutdownDevice};
|
||||
|
||||
bitflags! {
|
||||
pub struct HotPlugNotificationFlags: u8 {
|
||||
pub struct AcpiNotificationFlags: u8 {
|
||||
const NO_DEVICES_CHANGED = 0;
|
||||
const CPU_DEVICES_CHANGED = 0b1;
|
||||
const MEMORY_DEVICES_CHANGED = 0b10;
|
||||
const PCI_DEVICES_CHANGED = 0b100;
|
||||
const POWER_BUTTON_CHANGED = 0b1000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* [Reboot a Virtual Machine](#reboot-a-virtual-machine)
|
||||
* [Shut a Virtual Machine Down](#shut-a-virtual-machine-down)
|
||||
+ [Command Line Interface](#command-line-interface)
|
||||
+ [REST API and CLI Architecural Relationship](#rest-api-and-cli-architectural-relationship)
|
||||
+ [REST API and CLI Architectural Relationship](#rest-api-and-cli-architectural-relationship)
|
||||
* [Internal API](#internal-api)
|
||||
+ [Goals and Design](#goals-and-design)
|
||||
* [End to End Example](#end-to-end-example)
|
||||
|
||||
@@ -16,20 +16,10 @@ sudo apt-get install libfdt-dev
|
||||
|
||||
## Build
|
||||
|
||||
For Virtio devices, you can choose MMIO or PCI as transport option.
|
||||
|
||||
### MMIO
|
||||
Using PCI devices requires GICv3-ITS for MSI messaging. GICv3-ITS is very common in modern servers.
|
||||
|
||||
```bash
|
||||
cargo build --no-default-features --features mmio,kvm
|
||||
```
|
||||
|
||||
### PCI
|
||||
|
||||
Using PCI devices requires GICv3-ITS for MSI messaging. GICv3-ITS is very common in modern servers, but your machine happen to be old ones with GICv2(M) (like Raspberry Pi 4) or GICv3 without ITS, MMIO can still work.
|
||||
|
||||
```bash
|
||||
cargo build --no-default-features --features pci,kvm
|
||||
cargo build --no-default-features --features kvm
|
||||
```
|
||||
|
||||
## Image
|
||||
@@ -51,7 +41,7 @@ To build the development container:
|
||||
./scripts/dev_cli.sh build-container
|
||||
```
|
||||
|
||||
To build Cloud-hypervisor in the container: (The default option for Virtio transport is MMIO.)
|
||||
To build Cloud-hypervisor in the container:
|
||||
|
||||
```bash
|
||||
./scripts/dev_cli.sh build
|
||||
@@ -62,7 +52,7 @@ To build Cloud-hypervisor in the container: (The default option for Virtio trans
|
||||
Assuming you have built Cloud-hypervisor with the development container, a VM can be started with command:
|
||||
|
||||
```bash
|
||||
sudo build/cargo_target/aarch64-unknown-linux-gnu/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 --serial file=serial.log --log-file log.log -vvv
|
||||
sudo build/cargo_target/aarch64-unknown-linux-gnu/debug/cloud-hypervisor --kernel kernel.bin --disk path=rootfs.ext4 --cmdline "keep_bootcon console=hvc0 reboot=k panic=1 root=/dev/vda rw" --cpus boot=4 --memory size=512M --serial file=serial.log --log-file log.log -vvv
|
||||
```
|
||||
|
||||
If the build was done out of the container, replace the binary path with `target/debug/cloud-hypervisor`.
|
||||
|
||||
@@ -81,16 +81,24 @@ apt update
|
||||
apt install fio iperf iperf3 socat
|
||||
```
|
||||
|
||||
### Remove snapd
|
||||
### Remove counterproductive packages
|
||||
|
||||
* snapd:
|
||||
|
||||
This prevents snapd from trying to mount squashfs filesystem when the kernel
|
||||
might not support it. This might be the case when the image is used with direct
|
||||
kernel boot. This step is specific to Ubuntu distributions.
|
||||
|
||||
* pollinate:
|
||||
|
||||
Remove this package which can fail and lead to the SSH daemon failing to start.
|
||||
See #2113 for details.
|
||||
|
||||
```bash
|
||||
apt remove --purge snapd
|
||||
apt remove --purge snapd pollinate
|
||||
```
|
||||
|
||||
|
||||
### Cleanup the image
|
||||
|
||||
Leave no trace in the image before unmounting its content.
|
||||
@@ -108,15 +116,18 @@ umount /mnt
|
||||
Renaming is important to identify this is a modified image.
|
||||
|
||||
```bash
|
||||
mv focal-server-cloudimg-amd64.raw focal-server-cloudimg-amd64-custom.raw
|
||||
mv focal-server-cloudimg-amd64.raw focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.raw
|
||||
```
|
||||
|
||||
The `-0` is the revision and is only necessary to change if multiple images are
|
||||
updated on the same day.
|
||||
|
||||
### Create QCOW2 from RAW
|
||||
|
||||
Last step is to create the QCOW2 image back from the modified image.
|
||||
|
||||
```bash
|
||||
qemu-img convert -p -f raw -O qcow2 focal-server-cloudimg-amd64-custom.raw focal-server-cloudimg-amd64-custom.qcow2
|
||||
qemu-img convert -p -f raw -O qcow2 focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.raw focal-server-cloudimg-amd64-custom-$(date "+%Y%m%d")-0.qcow2
|
||||
```
|
||||
|
||||
## Switch CI to use the new image
|
||||
@@ -134,3 +145,5 @@ Last step is about updating the integration tests to work with this new image.
|
||||
The key point is to identify where the Linux filesystem partition is located,
|
||||
as we might need to update the direct kernel boot command line, replacing
|
||||
`/dev/vda1` with the appropriate partition number.
|
||||
|
||||
Update all references to the previous image name to the new one.
|
||||
|
||||
@@ -75,12 +75,8 @@ feature is enabled by default.
|
||||
|
||||
## Virtio devices
|
||||
|
||||
For all virtio devices listed below, both `virtio-mmio` and `virtio-pci`
|
||||
transport layers are supported, `virtio-pci` being the default.
|
||||
|
||||
Both `virtio-mmio` and `virtio-pci` can be compiled out. `virtio-pci` is
|
||||
built-in by default, and enabled by default. If both transport layers were
|
||||
built at the same time, `virtio-pci` would be the default transport layer.
|
||||
For all virtio devices listed below, only `virtio-pci` transport layer is
|
||||
supported.
|
||||
|
||||
### virtio-block
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ As per adding CPUs to the guest, after a reboot the VM will be running with the
|
||||
|
||||
## Memory Hot Plug
|
||||
|
||||
Extra memory can be added from a runing Cloud Hypervisor instance. This is controlled by two mechanisms:
|
||||
Extra memory can be added from a running Cloud Hypervisor instance. This is controlled by two mechanisms:
|
||||
|
||||
1. Allocating some of the guest physical address space for hotplug memory.
|
||||
2. Making a HTTP API request to the VMM to ask for a new amount of RAM to be assigned to the VM. In the case of expanding the memory for the VM the new memory will be hotplugged into the running VM, if reducing the size of the memory then change will take effect after the next reboot.
|
||||
|
||||
@@ -8,7 +8,7 @@ the host kernel. The required Linux and KVM changes can be found in the
|
||||
[KVM SGX Tree](https://github.com/intel/kvm-sgx).
|
||||
|
||||
Utilizing SGX in the guest requires a kernel/OS with SGX support, e.g. a kernel
|
||||
buit using the [SGX Linux Development Tree](https://github.com/jsakkine-intel/linux-sgx.git)
|
||||
built using the [SGX Linux Development Tree](https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-sgx.git)
|
||||
or the [KVM SGX Tree](https://github.com/intel/kvm-sgx). Running KVM SGX as the
|
||||
guest kernel allows nested virtualization of SGX.
|
||||
|
||||
@@ -19,7 +19,7 @@ following [instructions](https://github.com/intel/linux-sgx).
|
||||
|
||||
## Cloud-Hypervisor support
|
||||
|
||||
Assuming the host exposes `/dev/sgx/virt_epc`, we can pass SGX enclaves through
|
||||
Assuming the host exposes `/dev/sgx_virt_epc`, we can pass SGX enclaves through
|
||||
the guest.
|
||||
|
||||
In order to use SGX enclaves within a Cloud-Hypervisor VM, we must define one
|
||||
@@ -42,12 +42,12 @@ Once booted, and assuming your guest kernel contains the patches from the
|
||||
have been correctly created under `/dev/sgx`:
|
||||
|
||||
```bash
|
||||
ls /dev/sgx
|
||||
enclave provision virt_epc
|
||||
ls /dev/sgx*
|
||||
/dev/sgx_enclave /dev/sgx_provision /dev/sgx_virt_epc
|
||||
```
|
||||
|
||||
From this point, it is possible to run any SGX application from the guest, as
|
||||
it will access `/dev/sgx/enclave` device to create dedicated SGX enclaves.
|
||||
it will access `/dev/sgx_enclave` device to create dedicated SGX enclaves.
|
||||
|
||||
Note: There is only one contiguous SGX EPC region, which contains all SGX EPC
|
||||
sections. This region is exposed through ACPI and marked as reserved through
|
||||
|
||||
43
docs/logging.md
Normal file
43
docs/logging.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Logging
|
||||
|
||||
The target audience of this document is both:
|
||||
|
||||
* Developers who want to understand what log level to use and when,
|
||||
* Users who want to debug issues with running their workloads in Cloud Hypervisor
|
||||
|
||||
## Control
|
||||
|
||||
The number of `-v` parameters passed to the `cloud-hypervisor` binary will determine the log level. Currenly the default is log messages up to `WARN:` (`warn!`) are included by default. The `--log-file` allows the log to be sent to a location other than `stderr`.
|
||||
|
||||
## Levels
|
||||
|
||||
### `error!()`
|
||||
|
||||
For immediate, unrecoverable errors where it does not make sense for the execution to continue as the behaviour of the VM is considerablely impacted.
|
||||
|
||||
Cloud Hypervisor should exit shortly after reporting this error (with a non-zero exit code). Generally this should be used during initial construction of the VM state before the virtual CPUs have begun running code.
|
||||
|
||||
A typical situation where this might occur is when the user is using command line options that conflict with each other or is trying to use a file that is not present on the filesystem.
|
||||
|
||||
Users should react to this error by checking their initial VM configuration.
|
||||
|
||||
### `warn!()`
|
||||
|
||||
A serious problem has occured but the execution of the VM can continue although some functionality might be impacted.
|
||||
|
||||
A typical example of where this level of message should be generated is during an API call request that cannot be fulfilled.
|
||||
|
||||
The user should investigate the meaning of this warning and take steps to ensure the correct functionality.
|
||||
|
||||
|
||||
### `info!()`
|
||||
|
||||
Use `-v` to enable.
|
||||
|
||||
This level is for the benefit of developers. It should be used for sporadic and infrequent messages. The same message should not "spam" the logs. The VM should be usable when this level of debugging is enabled and trying to use `stdin/stdout` and the logs are going to `stderr`.
|
||||
|
||||
### `debug!()`
|
||||
|
||||
Use `-vv` to enable.
|
||||
|
||||
For the most verbose of logging messages. It is acceptable to "spam" the log with repeated invocations of the same message. This level of logging would be combined with `--log-file`.
|
||||
32
docs/macvtap-bridge.md
Normal file
32
docs/macvtap-bridge.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Using MACVTAP to Bridge onto Host Network
|
||||
|
||||
Cloud Hypervisor supports using a MACVTAP device which is derived from a MACVLAN. Full details of configuring MACVLAN or MACVTAP is out of scope of this document. However the example below indicates how to bridge the guest directly onto the the network the host is on. Due to the lack of hairpin mode it not usually possible to reach the guest directly from the host.
|
||||
|
||||
```bash
|
||||
# The MAC address must be attached to the macvtap and be used inside the guest
|
||||
mac="c2:67:4f:53:29:cb"
|
||||
# Host network adapter to bridge the guest onto
|
||||
host_net="eno1"
|
||||
|
||||
# Create the macvtap0 as a new virtual MAC associated with the host network
|
||||
sudo ip link add link "$host_net" name macvtap0 type macvtap
|
||||
sudo ip link set macvtap0 address "$mac" up
|
||||
sudo ip link show macvtap0
|
||||
|
||||
# A new character device is created for this interface
|
||||
tapindex=$(< /sys/class/net/macvtap0/ifindex)
|
||||
tapdevice="/dev/tap$tapindex"
|
||||
|
||||
# Ensure that we can access this device
|
||||
sudo chown "$UID.$UID" "$tapdevice"
|
||||
|
||||
# Use --net fd=3 to point to fd 3 which the shell has opened to point to the /dev/tapN device
|
||||
target/debug/cloud-hypervisor \
|
||||
--kernel ~/src/linux/vmlinux \
|
||||
--disk path=~/workloads/focal.raw \
|
||||
--cpus boot=1 --memory size=512M \
|
||||
--cmdline "root=/dev/vda1 console=hvc0" \
|
||||
--net fd=3,mac=$mac 3<>$"$tapdevice"
|
||||
```
|
||||
|
||||
As the guest is now connected to the same L2 network as the host you can obtain an IP address based on your host network (potentially including via DHCP)
|
||||
@@ -15,17 +15,16 @@ struct MemoryConfig {
|
||||
mergeable: bool,
|
||||
shared: bool,
|
||||
hugepages: bool,
|
||||
hugepage_size: Option<u64>,
|
||||
hotplug_method: HotplugMethod,
|
||||
hotplug_size: Option<u64>,
|
||||
hotplugged_size: Option<u64>,
|
||||
balloon: bool,
|
||||
balloon_size: u64,
|
||||
zones: Option<Vec<MemoryZoneConfig>>,
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
--memory <memory> Memory parameters "size=<guest_memory_size>,mergeable=on|off,shared=on|off,hugepages=on|off,hotplug_method=acpi|virtio-mem,hotplug_size=<hotpluggable_memory_size>,hotplugged_size=<hotplugged_memory_size>,balloon=on|off"
|
||||
--memory <memory> Memory parameters "size=<guest_memory_size>,mergeable=on|off,shared=on|off,hugepages=on|off,hotplug_method=acpi|virtio-mem,hotplug_size=<hotpluggable_memory_size>,hotplugged_size=<hotplugged_memory_size>"
|
||||
```
|
||||
|
||||
### `size`
|
||||
@@ -78,23 +77,24 @@ _Example_
|
||||
--memory size=1G,shared=on
|
||||
```
|
||||
|
||||
### `hugepages`
|
||||
### `hugepages` and `hugepage_size`
|
||||
|
||||
Specifies if the memory must be `mmap(2)` with `MAP_HUGETLB` and `MAP_HUGE_2MB`
|
||||
flags. This performs a memory mapping relying on 2MiB pages instead of the
|
||||
default 4kiB pages.
|
||||
Specifies if the memory must be created and `mmap(2)` with `MAP_HUGETLB` and size
|
||||
flags. This performs a memory mapping relying on the specified huge page size. If no huge page size is supplied the system's default huge page size is used.
|
||||
|
||||
By using hugepages, one can improve the overall performance of the VM, assuming
|
||||
the guest will allocate hugepages as well. Another interesting use case is VFIO
|
||||
as it speeds up the VM's boot time since the amount of IOMMU mappings are
|
||||
reduced.
|
||||
|
||||
The user is responsible for ensuring there are sufficient huge pages of the specified size for the VMM to use. Failure to do so may result in strange VMM behaviour.
|
||||
|
||||
By default this option is turned off.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=1G,hugepages=on
|
||||
--memory size=1G,hugepages=on,hugepage_size=2M
|
||||
```
|
||||
|
||||
### `hotplug_method`
|
||||
@@ -142,20 +142,6 @@ _Example_
|
||||
--memory size=1G,hotplug_method=virtio-mem,hotplug_size=1G,hotplugged_size=512M
|
||||
```
|
||||
|
||||
### `balloon`
|
||||
|
||||
Specifies if the `virtio-balloon` device must be activated. This creates a
|
||||
dedicated virtio device for managing the balloon in the guest, which allows
|
||||
guest to access more or less memory depending on the balloon size.
|
||||
|
||||
By default this option is turned off.
|
||||
|
||||
_Example_
|
||||
|
||||
```
|
||||
--memory size=1G,balloon=on
|
||||
```
|
||||
|
||||
## Advanced Parameters
|
||||
|
||||
`MemoryZoneConfig` or what is known as `--memory-zone` from the CLI perspective
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# How to use networking
|
||||
|
||||
cloud-hypervisor can emulate one or more virtual network interfaces, represented at the hypervisor host by [tap devices](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). This guide briefly describes, in a manual and distribution neutral way, how to setup and use networking with cloud-hypevisor.
|
||||
cloud-hypervisor can emulate one or more virtual network interfaces, represented at the hypervisor host by [tap devices](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). This guide briefly describes, in a manual and distribution neutral way, how to setup and use networking with cloud-hypervisor.
|
||||
|
||||
## Multiple queue support for net devices ##
|
||||
|
||||
@@ -14,7 +14,7 @@ Note:
|
||||
|
||||
- Currently, it does not support to use ethtool to change the combined queue numbers in guest.
|
||||
- Multiple queue is enabled for vhost-user-net backend in cloud-hypervisor, however, multiple thread is not added to handle mq, thus, the performance for vhost-user-net backend is not supposed to be improved. The multiple thread will be added for backend later.
|
||||
- Performance test for vhost-user-net will be covered once vhost-user-net backend has mulitple thread supported.
|
||||
- Performance test for vhost-user-net will be covered once vhost-user-net backend has multiple thread supported.
|
||||
- Performance test for virtio-net is done by comparing 2 queue pairs with 1 queue pairs, that to run 2 iperf3 sessions in the same test environments, throughput is improved about 37%.
|
||||
|
||||
## Start cloud-hypervisor with net devices
|
||||
|
||||
45
docs/profiling.md
Normal file
45
docs/profiling.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Profiling
|
||||
|
||||
`perf` can be used to profile the `cloud-hypervisor` binary but it is necessary to make some modifications to the the build in order to produce a binary that gives useful results.
|
||||
|
||||
## Building a suitable binary
|
||||
|
||||
Modify the `Cargo.toml` file to add `debug = 1` to the `[profile.release]` block. It should look like this:
|
||||
|
||||
```
|
||||
[profile.release]
|
||||
lto = true
|
||||
debug = 1
|
||||
```
|
||||
|
||||
This adds the symbol information to the release binary but does not otherwise affect the performance.
|
||||
|
||||
The binary must also be built with frame pointers included so that the call graph can be captured by the profiler.
|
||||
|
||||
```
|
||||
$ cargo clean && RUSTFLAGS='-C force-frame-pointers=y' cargo build --release
|
||||
```
|
||||
|
||||
## Profiling
|
||||
|
||||
`perf` may then be used in the usual manner:
|
||||
|
||||
e.g.
|
||||
|
||||
```
|
||||
$ perf record -g target/release/cloud-hypervisor \
|
||||
--kernel ~/src/linux/vmlinux \
|
||||
--pmem file=~/workloads/focal.raw \
|
||||
--cpus boot=1 --memory size=1G \
|
||||
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
|
||||
--serial tty --console off \
|
||||
--api-socket=/tmp/api1
|
||||
```
|
||||
|
||||
For analysing the samples:
|
||||
|
||||
```
|
||||
$ perf report -g
|
||||
```
|
||||
|
||||
If profiling with a network device attached either the TAP device must be already created and configured or the profiling must be done as root so that the TAP device can be created.
|
||||
@@ -54,7 +54,7 @@ drwxr-xr-x 47 foo bar 4096 Jul 22 11:47 ../
|
||||
In this particular example, we can observe that 2 memory region files were
|
||||
created. That is explained by the size of the guest RAM, which is 4GiB in this
|
||||
case. Because it exceeds 3GiB (which is where we can find a ~1GiB memory hole),
|
||||
Cloud-Hypervisor needs 2 distincts memory regions to be created. Each memory
|
||||
Cloud-Hypervisor needs 2 distinct memory regions to be created. Each memory
|
||||
region's content is stored through a dedicated file, which explains why we end
|
||||
up with 2 different files, the first one containing the guest RAM range 0-3GiB
|
||||
and the second one containing the guest RAM range 3-4GiB.
|
||||
@@ -63,7 +63,7 @@ and the second one containing the guest RAM range 3-4GiB.
|
||||
and state. The configuration bits are used to create a similar virtual machine
|
||||
with the correct amount of CPUs, RAM, and other expected devices. The state
|
||||
bits are used to restore each component in the state it was left before the
|
||||
snapshot occured.
|
||||
snapshot occurred.
|
||||
|
||||
## Restore a Cloud-Hypervisor VM
|
||||
|
||||
|
||||
75
docs/uefi.md
Normal file
75
docs/uefi.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# UEFI Boot
|
||||
|
||||
Cloud Hypervisor supports UEFI boot through the utilization of the EDK II based UEFI firmware.
|
||||
|
||||
## Building UEFI Firmware
|
||||
|
||||
To avoid any unnecessary issues, it is recommended to use Ubuntu 18.04 and its default toolset. Any other compatible Linux distribution is otherwise suitable, however it is suggested to use a temporary Docker container with Ubuntu 18.04 for a quick build on an existing Linux machine.
|
||||
|
||||
The commands below will compile an OVMF firmware suitable for Cloud Hypervisor.
|
||||
|
||||
```shell
|
||||
sudo apt-get update
|
||||
sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
|
||||
|
||||
git clone https://github.com/cloud-hypervisor/edk2 -b ch
|
||||
cd edk2
|
||||
. edksetup.sh
|
||||
git submodule update --init
|
||||
|
||||
echo "ACTIVE_PLATFORM=OvmfPkg/OvmfCh.dsc" >> Conf/target.txt
|
||||
echo "TARGET_ARCH=X64" >> Conf/target.txt
|
||||
echo "TOOL_CHAIN_TAG=GCC5" >> Conf/target.txt
|
||||
|
||||
make -C ./BaseTools
|
||||
build
|
||||
```
|
||||
|
||||
After the successful build, the resulting firmware binaries are available under `Build/OvmfCh/DEBUG_GCC5/FV` underneath the edk2 checkout.
|
||||
|
||||
## Using OVMF Binaries
|
||||
|
||||
Any UEFI capable image can be booted using the Cloud Hypervisor specific firmware. Windows guests under Cloud Hypervisor only support UEFI boot, therefore OVMF is mandatory there.
|
||||
|
||||
To make Cloud Hypervisor use UEFI boot, pass the `OVMF.fd` file path as an argument to the `--kernel` option. The firmware file will be opened in read only mode.
|
||||
|
||||
The same firmware can be used with Cloud Hypervisor or with QEMU. This is particularly useful if using QEMU for the preparation phase.
|
||||
|
||||
## Building UEFI Firmware with Compatibility Support Module (CSM)
|
||||
|
||||
CSM is a module that allows to boot legacy operating systems using the OVMF firmware. OVMF can embed a CSM build of SeaBIOS. To build the SeaBIOS with CSM support, add `CONFIG_CSM=y` to `.config` before the build. The outcome `out/Csm16.bin` is to be moved into `OvmfPkg/Csm/Csm16/Csm16.bin` before OVMF is built. Then, the OVMF build will have to be passed the `-D CSM_ENABLE` option in order to generate a legacy aware UEFI firmware. At the current stage, all the necessary patches are included in the Cloud Hypervisor specific [SeaBIOS branch](https://github.com/cloud-hypervisor/seabios/tree/ch). Taking into account the previous instructions, the modified command sequence to compile an OVMF binary with CSM support is the following one:
|
||||
|
||||
```shell
|
||||
sudo apt-get update
|
||||
sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
|
||||
|
||||
git checkout https://github.com/cloud-hypervisor/seabios -b ch
|
||||
cd seabios
|
||||
make menuconfig
|
||||
# Enable `CONFIG_CSM` and `CONFIG_QEMU_HARDWARE`
|
||||
make CONFIG_CSM=y CONFIG_QEMU_HARDWARE=y
|
||||
cd ..
|
||||
|
||||
git clone https://github.com/cloud-hypervisor/edk2 -b ch
|
||||
cd edk2
|
||||
. edksetup.sh
|
||||
git submodule update --init
|
||||
cp ../seabios/out/Csm16.bin OvmfPkg/Csm/Csm16/
|
||||
|
||||
echo "ACTIVE_PLATFORM=OvmfPkg/OvmfCh.dsc" >> Conf/target.txt
|
||||
echo "TARGET_ARCH=X64" >> Conf/target.txt
|
||||
echo "TOOL_CHAIN_TAG=GCC5" >> Conf/target.txt
|
||||
|
||||
make -C ./BaseTools
|
||||
build
|
||||
|
||||
```
|
||||
|
||||
Please note, that the CSM support has currently only been tested with Linux guests. There are no plans to provide legacy support for other OSes (e.g. Windows).
|
||||
|
||||
# Links
|
||||
|
||||
- [OVMF wiki](https://github.com/tianocore/tianocore.github.io/wiki/OVMF)
|
||||
- [Cloud Hypervisor specific tree](https://github.com/cloud-hypervisor/edk2/tree/ch)
|
||||
- [Redhat OVMF Status Report](https://access.redhat.com/sites/default/files/attachments/ovmf-whtepaper-031815.pdf)
|
||||
- [SeaBIOS Build Overview](https://www.seabios.org/Build_overview#Build_as_a_UEFI_Compatibility_Support_Module_.28CSM.29)
|
||||
@@ -78,5 +78,3 @@ takes the device's sysfs path as an argument. In our example it is
|
||||
The guest kernel will then detect the card reader on its PCI bus and provided
|
||||
that support for this device is enabled, it will probe and enable it for the
|
||||
guest to use.
|
||||
|
||||
|
||||
|
||||
337
docs/windows.md
Normal file
337
docs/windows.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# Windows Support
|
||||
|
||||
Starting with the release version [0.10.0](https://github.com/cloud-hypervisor/cloud-hypervisor/releases/tag/v0.10.0), Cloud Hypervisor supports Windows guests.
|
||||
|
||||
__Requirements__
|
||||
|
||||
- Host with KVM enabled
|
||||
- [UEFI](uefi.md) capable Windows guest image with Virtio drivers integrated
|
||||
|
||||
Any modern Windows Server version is compatible. Cloud Hypervisor has been successfully tested with Windows Server 2019 and Windows Server Core 2004.
|
||||
|
||||
At the current stage, only UEFI capable Windows images are supported. This implies the presence of the OVMF firmware during the Windows installation and in any subsequent usage. BIOS boot is not supported.
|
||||
|
||||
The subsequent sections will tell, in detail, how to prepare an appropriate Windows image.
|
||||
|
||||
## Image Preparation
|
||||
|
||||
### Installation using the stock Windows ISO
|
||||
|
||||
__Prerequisites__
|
||||
|
||||
- QEMU, version >=5.0.0 is recommended.
|
||||
- Windows installation ISO. Obtained through MSDN, Visual Studio subscription, evaluation center, etc.
|
||||
- [VirtIO driver ISO](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/)
|
||||
- Suitable [OVMF](uefi.md) firmware
|
||||
- With the suggested image size of 30G, there should be enough free disk space to hold the installation ISO and any other necessary files
|
||||
|
||||
This step currently requires QEMU to install Windows onto the guest. QEMU is only used at the preparation stage, the resulting image is then fully functional with Cloud Hypervisor.
|
||||
|
||||
Preparing several command parts as these will be used in the follow up sections as well.
|
||||
|
||||
```shell
|
||||
IMG_FILE=windows-disk.qcow
|
||||
WIN_ISO_FILE=en_windows_server_version_2004_updated_may_2020_x64_dvd_1e7f1cfa.iso
|
||||
VIRTIO_ISO_FILE=virtio-win-0.1.185.iso
|
||||
OVMF_DIR=./FV
|
||||
```
|
||||
|
||||
Create an empty image file, `qcow` or `raw` is supported.
|
||||
```shell
|
||||
qemu-img create -f qcow2 $IMG_FILE 30G
|
||||
```
|
||||
|
||||
Begin the Windows installation process under QEMU
|
||||
```shell
|
||||
qemu-system-x86_64 \
|
||||
-machine q35,accel=kvm \
|
||||
-cpu host \
|
||||
-m 4G \
|
||||
-bios ./$OVMF_DIR/OVMF_CODE.fd \
|
||||
-cdrom ./$WIN_ISO_FILE \
|
||||
-drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom
|
||||
-drive if=none,id=root,file=./$IMG_FILE \
|
||||
-device virtio-blk-pci,drive=root,disable-legacy=on \
|
||||
-device virtio-net-pci,netdev=mynet0,disable-legacy=on \
|
||||
-netdev user,id=mynet0 \
|
||||
-vga std
|
||||
```
|
||||
|
||||
Before the installation can proceed, point the Windows installation program to the VirtIO disk and install the necessary storage controller drivers. After that, the attached hard drive will become visible and the actual installation can commence.
|
||||
|
||||
After the installation has completed, proceed further to the configuration section. QEMU will be needed at least once more to enable the Windows Special Administration Console (SAC) and to possibly install extra device drivers.
|
||||
|
||||
## Image Usage
|
||||
|
||||
The basic command to boot a Windows image. The configuration section should be checked before executing it for the first time.
|
||||
|
||||
```shell
|
||||
cloud-hypervisor \
|
||||
--kernel ./$OVMF_DIR/OVMF.fd \
|
||||
--disk path=./$IMG_FILE \
|
||||
--cpus boot=1,kvm_hyperv=on \
|
||||
--memory size=4G \
|
||||
--serial tty \
|
||||
--console off \
|
||||
--net tap=
|
||||
```
|
||||
|
||||
It is necessary to always:
|
||||
|
||||
- Carry the OVMF firmware in the `--kernel` option
|
||||
- Add `kvm_hyperv=on` to the `--cpus` option
|
||||
|
||||
In cases where the host processor supports address space > 39 bits, it might be necessary to limit the address space. It can be done by appending the option `max_phys_bits=X` to the `--cpus` parameter, where `X` is the number of bits to be supported. Windows was tested to support at least 39-bit address space.
|
||||
|
||||
To daemonize the Cloud Hypervisor process, `nohup` can be used. Some STDIO redirections might need to be done. In a simple case it is sufficient to just redirect all the output to `/dev/null`.
|
||||
|
||||
## Image Configuration
|
||||
|
||||
### Device Drivers
|
||||
|
||||
After the Windows installation has finished under QEMU, there might be still devices with no drivers installed. This might happen for example, when a device was not used during the installation. In particular it is important to ensure that the VirtIO network device is setup correctly because further steps for the configuration and the usage require network in most case.
|
||||
|
||||
Boot once more under QEMU and use the [Device Manager](https://support.microsoft.com/en-in/help/4028443/windows-10-update-drivers), to ensure all the device drivers, and especially the network card, are installed correctly. Also, as Cloud Hypervisor can introduce new devices, it is advisable to repeat the procedure while booted under Cloud Hypervisor, when the RDP access to the image is functional.
|
||||
|
||||
### Windows Special Administration Console (SAC) enablement
|
||||
|
||||
SAC provides a text based console access to the Windows guest. As Cloud Hypervisor doesn't implement a VGA adaptor, SAC is an important instrument for the Windows guest management.
|
||||
|
||||
Boot the Windows image under QEMU and execute the below commands to permanently enable SAC
|
||||
|
||||
```cmd
|
||||
bcdedit /emssettings emsport:1 emsbaudrate:115200
|
||||
bcdedit /ems on
|
||||
bcdedit /bootems on
|
||||
```
|
||||
|
||||
Once SAC is enabled, the image can be booted under Cloud Hypervisor. The SAC prompt will show up
|
||||
|
||||
<pre>
|
||||
Computer is booting, SAC started and initialized.
|
||||
|
||||
Use the "ch -?" command for information about using channels.
|
||||
Use the "?" command for general help.
|
||||
|
||||
|
||||
SAC>
|
||||
</pre>
|
||||
|
||||
To open a console on the guest, the command sequence below can be used
|
||||
<pre>
|
||||
SAC>cmd
|
||||
The Command Prompt session was successfully launched.
|
||||
SAC>
|
||||
EVENT: A new channel has been created. Use "ch -?" for channel help.
|
||||
Channel: Cmd0001
|
||||
SAC>ch -si 1
|
||||
</pre>
|
||||
|
||||
See also the [links](#Links) section for a more extended SAC documentation.
|
||||
|
||||
## Network
|
||||
|
||||
This section illustrates the Windows specific corner points for the VM network configuration. For the extended networking guide, including bridging for multiple VMs, follow [networking.md](networking.md).
|
||||
|
||||
### Basic Networking
|
||||
|
||||
As the simplest option, using `--net tap=` in the Cloud Hypervisor command line will create a `vmtapX` device on the host with the default IPv4 adress `192.168.249.1`. After SAC becomes available, the guest configuration can be set with
|
||||
|
||||
<pre>
|
||||
SAC>i 10 192.168.249.2 255.255.255.0 192.168.249.1
|
||||
</pre>
|
||||
|
||||
Where `10` is the device index as shown by the `i` command.
|
||||
|
||||
### Guest Internet Connectivity
|
||||
|
||||
Additional steps are necessary to provide the guest with internet access.
|
||||
|
||||
- On the guest, add the DNS server either by using `netsh` or by opening `Network and Connectivity Center` and editing the adapter properties.
|
||||
- On the host, configure the traffic forwarding. Replace the `NET_DEV` with the name of your network device.
|
||||
```shell
|
||||
NET_DEV=wlp3s0
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
iptables -t nat -A POSTROUTING -o $NET_DEV -j MASQUERADE
|
||||
```
|
||||
|
||||
### Remote Desktop Protocol (RDP) enablement
|
||||
|
||||
#### Using QEMU
|
||||
- Execute `SystemPropertiesRemote`
|
||||
- In the properties window, choose "Allow remote connections to this computer"
|
||||
- Click "Select Users" and add some user to the allow list
|
||||
#### Using powershell
|
||||
```powershell
|
||||
Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\" -Name "fDenyTSConnections" -Value 0
|
||||
Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
|
||||
Add-LocalGroupMember -Group "Remote Desktop Users" -Member someuser
|
||||
```
|
||||
|
||||
Administrators can always RDP, non administrator users have to be explicitly enabled.
|
||||
|
||||
Once the configuration is set, RDP clients can connect to `192.168.249.2`.
|
||||
|
||||
### SSH
|
||||
|
||||
#### Enable using powershell
|
||||
|
||||
```powershell
|
||||
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
|
||||
Start-Service sshd
|
||||
Set-Service -Name sshd -StartupType ‘Automatic’
|
||||
```
|
||||
|
||||
This allows for SSH login from a remote machine, for example through the `administrator` user: `ssh administrator@192.168.249.2`. For a more detailed OpenSSH guide, please follow the MSDN article from the [links](#links) section.
|
||||
|
||||
## Debugging
|
||||
|
||||
The Windows guest debugging process relies heavily on QEMU and [socat](http://www.dest-unreach.org/socat/). The procedure requires two Windows VMs:
|
||||
|
||||
- A debugger VM running under QEMU.
|
||||
- A debuggee, a Windows VM that has been created in the previous steps, running under Cloud Hypervisor or QEMU.
|
||||
|
||||
The connection between both guests happens over TCP, whereby on the guest side it is automatically translated to a COM port. Because the VMs are connected through TCP, the debugging infrastructure can be distributed over the network. The serial port, while slowly transferring data, is common enough to support a wide range of cases and tools.
|
||||
|
||||
In this excercise, [WinDbg](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/) is used. Any other debugger of choice with the ability to use serial connection can be used instead.
|
||||
|
||||
### Debugger and Debuggee
|
||||
|
||||
#### WinDbg VM
|
||||
|
||||
For simplicity, the debugger VM is supposed to be only running under QEMU. It will require VGA and doesn't neccessarily depend on UEFI. As an OS, it can carry any supported Windows OS where the debugger of choice can be installed. The simplest way is to follow the image preparation instructions from the previous chapter, but avoid using the OVMF firmware. It is also not required to use VirtIO drivers, whereby it might be useful in some case. Though, while creating the image file for the debugger VM, be sure to choose a sufficient disk size that counts in the need to save the corresponding debug symbols and sources.
|
||||
|
||||
To create the debugger Windows VM, the following command can be used:
|
||||
|
||||
```shell
|
||||
qemu-system-x86_64 \
|
||||
-machine q35,accel=kvm \
|
||||
-cpu host \
|
||||
-smp 1 \
|
||||
-m 4G \
|
||||
-cdrom ./$WIN_ISO_FILE \
|
||||
-drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom
|
||||
-drive if=none,id=root,file=./windbg-disk.qcow \
|
||||
-device virtio-blk-pci,drive=root,disable-legacy=on \
|
||||
-device virtio-net-pci,netdev=mynet0,disable-legacy=on \
|
||||
-netdev user,id=mynet0,net=192.168.178.0/24,host=192.168.178.1,dhcpstart=192.168.178.64,hostname=windbg-host \
|
||||
-vga std
|
||||
```
|
||||
|
||||
A non server Windows OS like Windows 10 can be used to carry the debugging tools in the debugger VM.
|
||||
|
||||
#### Debuggee VM
|
||||
|
||||
The debuggee VM is the one that we've learned to configure and run in the first section. There might be various reasons to debug. For example, there could be an issue in the Windows guest with an emulated device or an included driver. Or, we might want to develop a custom feature like a kernel driver to be available in the guest.
|
||||
|
||||
Note, that there are several ways to debug Windows, not all of them need to be enabled at the same time. For example, if developing a kernel module, the only useful options would be to configure for the serial debugging and enable the kernel debug. In that case, any crash or misbehavior in the boot loader or kernel would be ignored. The commands below must be run as administrator on the debuggee guest VM.
|
||||
|
||||
##### Turn On Serial Debugging
|
||||
|
||||
This will configure the debugging to be enabled and instruct to use the serial port for it.
|
||||
|
||||
```cmd
|
||||
bcdedit /dbgsettings serial debugport:1 baudrate:115200
|
||||
```
|
||||
|
||||
##### Turn On Kernel Debuging
|
||||
|
||||
```cmd
|
||||
bcdedit /debug on
|
||||
```
|
||||
|
||||
##### Turn On Boot Loader Debug
|
||||
|
||||
```cmd
|
||||
bcdedit /bootdebug on
|
||||
```
|
||||
|
||||
##### Turn on boot manager debug
|
||||
|
||||
```cmd
|
||||
bcdedit /set {bootmgr} bootdebug on
|
||||
```
|
||||
|
||||
##### Disable Recovery Screen On Boot Failure
|
||||
|
||||
There could be a situation, where a crash is debugged. In such cases, the guest could be left in an inconsistent state. The default Windows behavior would be to boot into the recovery screen, however in some cases it might be not desired. To make Windows ignore failures and always proceed to booting the OS, use the command below:
|
||||
|
||||
```cmd
|
||||
bcdedit /set {default} bootstatuspolicy ignoreallfailures
|
||||
```
|
||||
|
||||
### Debugging Process
|
||||
|
||||
#### Invoke the WinDbg VM
|
||||
|
||||
```shell
|
||||
qemu-system-x86_64 \
|
||||
-machine q35,accel=kvm \
|
||||
-cpu host \
|
||||
-smp 1 \
|
||||
-m 4G \
|
||||
-drive if=none,id=root,file=./windbg-disk.qcow \
|
||||
-device virtio-blk-pci,drive=root,disable-legacy=on \
|
||||
-serial tcp::4445,server,nowait \
|
||||
-device virtio-net-pci,netdev=mynet0,disable-legacy=on \
|
||||
-netdev user,id=mynet0,net=192.168.178.0/24,host=192.168.178.1,dhcpstart=192.168.178.64,hostname=windbg-host \
|
||||
-vga std
|
||||
```
|
||||
|
||||
Note, this VM has the networking enabled. It is needed, because symbols and sources might need to be fetched from a network location.
|
||||
|
||||
Also, notice the `-serial` parameter - that's what does the magic on exposing the serial port to the guest while connecting the debugger VM with a client VM through the network. SAC/EMS needs to be disabled in the debugger VM, as otherwise the COM device might be blocked.
|
||||
|
||||
Hereafter, WinDbg can be started using a command below:
|
||||
|
||||
```cmd
|
||||
set _NT_DEBUG_PORT=com1
|
||||
set _NT_DEBUG_BAUD_RATE=115200
|
||||
|
||||
windbg -v -d -k
|
||||
```
|
||||
|
||||
Once started, WinDbg will wait for an incoming connection which is going to be initialized by the debuggee VM started in the next section.
|
||||
|
||||
#### Invoke the Debuggee VM
|
||||
|
||||
##### Under QEMU
|
||||
|
||||
Essentially it would be the command like depicted in the guest preparation sections, with a few modifications:
|
||||
```shell
|
||||
qemu-system-x86_64 \
|
||||
-machine q35,accel=kvm \
|
||||
-cpu host \
|
||||
-m 4G \
|
||||
-bios ./$OVMF_DIR/OVMF_CODE.fd \
|
||||
-cdrom ./$WIN_ISO_FILE \
|
||||
-drive file=./$VIRTIO_ISO_FILE,index=0,media=cdrom
|
||||
-drive if=none,id=root,file=./$IMG_FILE \
|
||||
-device virtio-blk-pci,drive=root,disable-legacy=on \
|
||||
-device virtio-net-pci,netdev=mynet0,disable-legacy=on \
|
||||
-netdev user,id=mynet0 \
|
||||
-serial tcp:127.0.0.1:4445 \
|
||||
-vga std
|
||||
```
|
||||
|
||||
It is to see, that `-serial` parameter is used here, to establish the connection with the debugger VM.
|
||||
|
||||
To disable HPET, attach `--no-hpet`. To enable hypervisor reference timer, use `-cpu host,hv-time`. These and other options can be used to achieve better [Hyper-V compatibility](https://archive.fosdem.org/2019/schedule/event/vai_enlightening_kvm/attachments/slides/2860/export/events/attachments/vai_enlightening_kvm/slides/2860/vkuznets_fosdem2019_enlightening_kvm.pdf).
|
||||
|
||||
##### Cloud Hypervisor
|
||||
|
||||
The `socat` tool is used to establish the QEMU compatible behavior. Here as well, the Cloud Hypervisor command used to run the Windows guest is to be used. Put the command into a shell script:
|
||||
|
||||
`socat SYSTEM:"./ch-script",openpty,raw,echo=0 TCP:localhost:4445`
|
||||
|
||||
The reason to pack the command into the shell script is that the command might contain a comma. When using SYSTEM, the shell command can't contain `,` or `!!`.
|
||||
|
||||
## Links
|
||||
|
||||
- [Fedora VirtIO guide for Windows](https://docs.fedoraproject.org/en-US/quick-docs/creating-windows-virtual-machines-using-virtio-drivers/)
|
||||
- [VirtIO driver binaries](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/)
|
||||
- [VirtIO driver sources](https://github.com/virtio-win/kvm-guest-drivers-windows)
|
||||
- [Emergency Management Services](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc787940(v=ws.10))
|
||||
- [OpenSSH server/client configuration](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse)
|
||||
- [Windows guest debugging under KVM](https://www.linux-kvm.org/page/WindowsGuestDrivers/GuestDebugging)
|
||||
- ["ENLIGHTENING" KVM](https://archive.fosdem.org/2019/schedule/event/vai_enlightening_kvm/attachments/slides/2860/export/events/attachments/vai_enlightening_kvm/slides/2860/vkuznets_fosdem2019_enlightening_kvm.pdf)
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
[package]
|
||||
name = "cloud-hypervisor-fuzz"
|
||||
version = "0.0.0"
|
||||
@@ -10,20 +9,19 @@ edition = "2018"
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
block_util = { path = "../block_util" }
|
||||
libc = "0.2.72"
|
||||
libfuzzer-sys = "0.3"
|
||||
qcow = { path = "../qcow" }
|
||||
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.22.0" }
|
||||
virtio-devices = { path = "../virtio-devices" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vm-memory = "0.2.1"
|
||||
vm-memory = "0.5.0"
|
||||
|
||||
[dependencies.cloud-hypervisor]
|
||||
path = ".."
|
||||
|
||||
[patch.crates-io]
|
||||
vm-memory = { git = "https://github.com/cloud-hypervisor/vm-memory", branch = "ch" }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
#![no_main]
|
||||
|
||||
use block_util::{async_io::DiskFile, qcow_sync::QcowDiskSync};
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use seccomp::SeccompAction;
|
||||
use std::ffi;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Cursor, Read, Seek, SeekFrom};
|
||||
@@ -82,16 +84,17 @@ fuzz_target!(|bytes| {
|
||||
|
||||
let shm = memfd_create(&ffi::CString::new("fuzz").unwrap(), 0).unwrap();
|
||||
let disk_file: File = unsafe { File::from_raw_fd(shm) };
|
||||
let raw_img = qcow::RawFile::new(disk_file, false);
|
||||
let qcow_disk = Box::new(QcowDiskSync::new(disk_file, false)) as Box<dyn DiskFile>;
|
||||
|
||||
let mut block = Block::new(
|
||||
"tmp".to_owned(),
|
||||
raw_img,
|
||||
qcow_disk,
|
||||
PathBuf::from(""),
|
||||
false,
|
||||
false,
|
||||
2,
|
||||
256,
|
||||
SeccompAction::Allow,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
[package]
|
||||
name = "hypervisor"
|
||||
version = "0.1.0"
|
||||
authors = ["Muminul Islam <muislam@microsoft.com>"]
|
||||
authors = ["Microsoft Authors"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0 OR BSD-3-Clause"
|
||||
|
||||
[features]
|
||||
kvm = []
|
||||
mshv = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
epoll = ">=4.0.1"
|
||||
thiserror = "1.0"
|
||||
libc = "0.2.77"
|
||||
log = "0.4.11"
|
||||
libc = "0.2.86"
|
||||
log = "0.4.14"
|
||||
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
|
||||
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch", features = ["with-serde", "fam-wrappers"] }
|
||||
|
||||
mshv-bindings = {git = "https://github.com/cloud-hypervisor/mshv", branch = "master", features = ["with-serde", "fam-wrappers"] }
|
||||
mshv-ioctls = { git = "https://github.com/cloud-hypervisor/mshv", branch = "master" }
|
||||
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-memory = { version = "0.2.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.5.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vmm-sys-util = { version = ">=0.5.0", features = ["with-serde"] }
|
||||
|
||||
[dependencies.linux-loader]
|
||||
git = "https://github.com/rust-vmm/linux-loader"
|
||||
features = ["elf", "bzimage"]
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies.iced-x86]
|
||||
version = "1.10"
|
||||
default-features = false
|
||||
features = ["std", "decoder", "op_code_info", "instr_info", "fast_fmt"]
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.8.3"
|
||||
|
||||
148
hypervisor/src/arch/emulator/mod.rs
Normal file
148
hypervisor/src/arch/emulator/mod.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate thiserror;
|
||||
|
||||
use core::fmt::Debug;
|
||||
use std::fmt::{self, Display};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Copy, Error, Debug)]
|
||||
pub struct Exception<T: Debug> {
|
||||
vector: T,
|
||||
ip: u64,
|
||||
error: Option<u32>,
|
||||
payload: Option<u64>,
|
||||
}
|
||||
|
||||
impl<T: Debug> Display for Exception<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Exception {:?} at IP {:#x}", self.vector, self.ip)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PlatformError {
|
||||
#[error("Invalid address: {0}")]
|
||||
InvalidAddress(#[source] anyhow::Error),
|
||||
|
||||
#[error("Invalid register: {0}")]
|
||||
InvalidRegister(#[source] anyhow::Error),
|
||||
|
||||
#[error("Invalid state: {0}")]
|
||||
InvalidState(#[source] anyhow::Error),
|
||||
|
||||
#[error("Memory read failure: {0}")]
|
||||
MemoryReadFailure(#[source] anyhow::Error),
|
||||
|
||||
#[error("Memory write failure: {0}")]
|
||||
MemoryWriteFailure(#[source] anyhow::Error),
|
||||
|
||||
#[error("Get CPU state failure: {0}")]
|
||||
GetCpuStateFailure(#[source] anyhow::Error),
|
||||
|
||||
#[error("Set CPU state failure: {0}")]
|
||||
SetCpuStateFailure(#[source] anyhow::Error),
|
||||
|
||||
#[error("Unmapped virtual address: {0}")]
|
||||
UnmappedGVA(#[source] anyhow::Error),
|
||||
|
||||
#[error("Unsupported CPU Mode: {0}")]
|
||||
UnsupportedCpuMode(#[source] anyhow::Error),
|
||||
|
||||
#[error("Invalid instruction operand: {0}")]
|
||||
InvalidOperand(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum EmulationError<T: Debug> {
|
||||
#[error("Unsupported instruction: {0}")]
|
||||
UnsupportedInstruction(#[source] anyhow::Error),
|
||||
|
||||
#[error("Unsupported memory size: {0}")]
|
||||
UnsupportedMemorySize(#[source] anyhow::Error),
|
||||
|
||||
#[error("Invalid operand: {0}")]
|
||||
InvalidOperand(#[source] anyhow::Error),
|
||||
|
||||
#[error("Wrong number of operands: {0}")]
|
||||
WrongNumberOperands(#[source] anyhow::Error),
|
||||
|
||||
#[error("Instruction Exception: {0}")]
|
||||
InstructionException(Exception<T>),
|
||||
|
||||
#[error("Instruction fetching error: {0}")]
|
||||
InstructionFetchingError(#[source] anyhow::Error),
|
||||
|
||||
#[error("Platform emulation error: {0}")]
|
||||
PlatformEmulationError(PlatformError),
|
||||
|
||||
#[error(transparent)]
|
||||
EmulationError(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
/// The PlatformEmulator trait emulates a guest platform.
|
||||
/// It's mostly a guest resources (memory and CPU state) getter and setter.
|
||||
///
|
||||
/// A CpuState is an architecture specific type, representing a CPU state.
|
||||
/// The emulator and its instruction handlers modify a given CPU state and
|
||||
/// eventually ask the platform to commit it back through `set_cpu_state`.
|
||||
pub trait PlatformEmulator: Send + Sync {
|
||||
type CpuState: Clone;
|
||||
|
||||
/// Read guest memory into a u8 slice.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `gva` - Guest virtual address to read from.
|
||||
/// * `data` - Data slice to read into.
|
||||
///
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError>;
|
||||
|
||||
/// Write a u8 slice into guest memory.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `gva` - Guest virtual address to write into.
|
||||
/// * `data` - Data slice to be written.
|
||||
///
|
||||
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError>;
|
||||
|
||||
/// Get a CPU state from the guest.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cpu_id` - Logical CPU ID.
|
||||
///
|
||||
fn cpu_state(&self, cpu_id: usize) -> Result<Self::CpuState, PlatformError>;
|
||||
|
||||
/// Set a guest CPU state.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cpu_id` - Logical CPU ID.
|
||||
/// * `state` - State to set the CPU into.
|
||||
///
|
||||
fn set_cpu_state(&self, cpu_id: usize, state: Self::CpuState) -> Result<(), PlatformError>;
|
||||
|
||||
/// Translate a guest virtual address into a physical one
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `gva` - Guest virtual address to translate.
|
||||
///
|
||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError>;
|
||||
|
||||
/// Fetch instruction bytes from memory.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ip` - Instruction pointer virtual address to start fetching instructions from.
|
||||
///
|
||||
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError>;
|
||||
}
|
||||
|
||||
pub type EmulationResult<S, E> = std::result::Result<S, EmulationError<E>>;
|
||||
@@ -11,5 +11,8 @@
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
|
||||
pub mod emulator;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[macro_use]
|
||||
pub mod x86;
|
||||
|
||||
337
hypervisor/src/arch/x86/emulator/instructions/cmp.rs
Normal file
337
hypervisor/src/arch/x86/emulator/instructions/cmp.rs
Normal file
@@ -0,0 +1,337 @@
|
||||
//
|
||||
// Copyright © 2020 Microsoft
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
//
|
||||
// CMP-Compare Two Operands
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::regs::*;
|
||||
use crate::arch::x86::Exception;
|
||||
|
||||
// CMP affects OF, SF, ZF, AF, PF and CF
|
||||
const FLAGS_MASK: u64 = CF | PF | AF | ZF | SF | OF;
|
||||
|
||||
// TODO: Switch to inline asm when that's stable. Executing CMP (or any arthimetic instructions)
|
||||
// natively and extracting RFLAGS will be much faster and make the code simpler.
|
||||
fn calc_rflags_cpazso(op0: u64, op1: u64, op_size: usize) -> u64 {
|
||||
let op_bits = op_size * 8;
|
||||
let msb_shift = op_bits - 1;
|
||||
// CMP is the same as SUB.
|
||||
let result = op0.wrapping_sub(op1);
|
||||
|
||||
// Carry-out vector for SUB.
|
||||
let cout = (!op0 & op1) | ((!op0 ^ op1) & result);
|
||||
|
||||
let cf = ((cout >> msb_shift) & 0x1) << CF_SHIFT;
|
||||
|
||||
// PF only needs the least significant byte. XOR its higher 4 bits with its lower 4 bits then
|
||||
// use the value directly.
|
||||
let pf = ((0x9669 >> ((result ^ (result >> 4)) & 0xf)) & 0x1) << PF_SHIFT;
|
||||
|
||||
// AF cares about the lowest 4 bits (nibble). msb_shift is 3 in this case.
|
||||
let af = ((cout >> 3) & 0x1) << AF_SHIFT;
|
||||
|
||||
let zf = if result & (!0u64 >> (63 - msb_shift)) == 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
} << ZF_SHIFT;
|
||||
|
||||
let sf = ((result >> msb_shift) & 0x1) << SF_SHIFT;
|
||||
|
||||
// Overflow happens when two operands have the same sign but the result has a different sign.
|
||||
let of = ((((op0 ^ op1) & (op0 ^ result)) >> msb_shift) & 0x1) << OF_SHIFT;
|
||||
|
||||
cf | pf | af | zf | sf | of
|
||||
}
|
||||
|
||||
macro_rules! cmp_rm_r {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cmp_r_rm {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! cmp_rm_imm {
|
||||
($imm:ty, $bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let op1_value = get_op(&insn, 1, std::mem::size_of::<$imm>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
|
||||
|
||||
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Cmp_rm64_r64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_r64 {
|
||||
cmp_rm_r!(u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm32_r32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_r32 {
|
||||
cmp_rm_r!(u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm16_r16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_r16 {
|
||||
cmp_rm_r!(u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm8_r8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_r8 {
|
||||
cmp_rm_r!(u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_r64_rm64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r64_rm64 {
|
||||
cmp_r_rm!(u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_r32_rm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r32_rm32 {
|
||||
cmp_r_rm!(u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_r16_rm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r16_rm16 {
|
||||
cmp_r_rm!(u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_r8_rm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r8_rm8 {
|
||||
cmp_r_rm!(u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_AL_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AL_imm8 {
|
||||
cmp_rm_imm!(u8, u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_AX_imm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AX_imm16 {
|
||||
cmp_rm_imm!(u16, u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_EAX_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_EAX_imm32 {
|
||||
cmp_rm_imm!(u32, u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_RAX_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_RAX_imm32 {
|
||||
cmp_rm_imm!(u32, u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm8_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_imm8 {
|
||||
cmp_rm_imm!(u8, u8);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm16_imm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm16 {
|
||||
cmp_rm_imm!(u16, u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm32_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm32 {
|
||||
cmp_rm_imm!(u32, u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm64_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm32 {
|
||||
cmp_rm_imm!(u32, u64);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm16_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm8 {
|
||||
cmp_rm_imm!(u8, u16);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm32_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm8 {
|
||||
cmp_rm_imm!(u8, u32);
|
||||
}
|
||||
|
||||
pub struct Cmp_rm64_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm8 {
|
||||
cmp_rm_imm!(u8, u64);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_mut)]
|
||||
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
// cmp ah,al
|
||||
fn test_cmp_rm8_r8_1() {
|
||||
let rax: u64 = 0x0;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x38, 0xc4]; // cmp ah,al
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b1000100, rflags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp eax,100
|
||||
fn test_cmp_rm32_imm8_1() {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x83, 0xf8, 0x64]; // cmp eax,100
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b100, rflags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp eax,-1
|
||||
fn test_cmp_rm32_imm8_2() {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x83, 0xf8, 0xff]; // cmp eax,-1
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b101, rflags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// cmp rax,rbx
|
||||
fn test_cmp_rm64_r64() {
|
||||
let rax: u64 = 0xabcdef;
|
||||
let rbx: u64 = 0x1234;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0x39, 0xd8, 0x00, 0xc3]; // cmp rax,rbx + two bytes garbage
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax), (Register::RBX, rbx)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(0b100, rflags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmp_64() {
|
||||
let data = [
|
||||
(0xabcdef, 0x1234, 0b100),
|
||||
(0x0, 0x101, 0b1001_0101),
|
||||
(0x0, 0x8000_0000_0000_0000, 0b1000_1000_0101),
|
||||
(0x1234abcd, 0x1234abcd, 0b100_0100),
|
||||
(0x1234abcd, 0xdeadbeef, 0b1001_0101),
|
||||
(0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b1000_0000),
|
||||
(0xffff_ffff_ffff_ffff, 0x0, 0b1000_0100),
|
||||
];
|
||||
|
||||
for d in data.iter() {
|
||||
let rax = d.0;
|
||||
let rbx = d.1;
|
||||
let insn = [0x48, 0x39, 0xd8]; // cmp rax,rbx
|
||||
let mut vmm = MockVMM::new(
|
||||
0x1000,
|
||||
vec![(Register::RAX, rax), (Register::RBX, rbx)],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(d.2, rflags);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmp_32() {
|
||||
let data = [
|
||||
(0xabcdef, 0x1234, 0b100),
|
||||
(0x0, 0x101, 0b1001_0101),
|
||||
(0x0, 0x8000_0000_0000_0000, 0b100_0100), // Same as cmp 0,0 due to truncation
|
||||
(0x1234abcd, 0x1234abcd, 0b100_0100),
|
||||
(0x1234abcd, 0xdeadbeef, 0b1_0101),
|
||||
(0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b0), // Same as cmp 0xffffffff,0xdeadbeef
|
||||
(0xffff_ffff, 0x0, 0b1000_0100),
|
||||
];
|
||||
|
||||
for d in data.iter() {
|
||||
let rax = d.0;
|
||||
let rbx = d.1;
|
||||
let insn = [0x39, 0xd8]; // cmp eax,ebx
|
||||
let mut vmm = MockVMM::new(
|
||||
0x1000,
|
||||
vec![(Register::RAX, rax), (Register::RBX, rbx)],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
|
||||
|
||||
let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
|
||||
assert_eq!(d.2, rflags);
|
||||
}
|
||||
}
|
||||
}
|
||||
159
hypervisor/src/arch/x86/emulator/instructions/mod.rs
Normal file
159
hypervisor/src/arch/x86/emulator/instructions/mod.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator, PlatformError};
|
||||
use crate::arch::x86::emulator::CpuStateManager;
|
||||
use crate::arch::x86::Exception;
|
||||
use iced_x86::*;
|
||||
|
||||
pub mod cmp;
|
||||
pub mod mov;
|
||||
|
||||
fn get_op<T: CpuStateManager>(
|
||||
insn: &Instruction,
|
||||
op_index: u32,
|
||||
op_size: usize,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<u64, PlatformError> {
|
||||
if insn.op_count() < op_index + 1 {
|
||||
return Err(PlatformError::InvalidOperand(anyhow!(
|
||||
"Invalid operand {:?}",
|
||||
op_index
|
||||
)));
|
||||
}
|
||||
|
||||
match op_size {
|
||||
1 | 2 | 4 | 8 => {}
|
||||
_ => {
|
||||
return Err(PlatformError::InvalidOperand(anyhow!(
|
||||
"Invalid operand size {:?}",
|
||||
op_size
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
let value = match insn
|
||||
.try_op_kind(op_index)
|
||||
.map_err(|e| PlatformError::InvalidOperand(e.into()))?
|
||||
{
|
||||
OpKind::Register => state.read_reg(
|
||||
insn.try_op_register(op_index)
|
||||
.map_err(|e| PlatformError::InvalidOperand(e.into()))?,
|
||||
)?,
|
||||
OpKind::Memory => {
|
||||
let addr = memory_operand_address(insn, state, false)?;
|
||||
let mut memory: [u8; 8] = [0; 8];
|
||||
platform.read_memory(addr, &mut memory[0..op_size])?;
|
||||
<u64>::from_le_bytes(memory)
|
||||
}
|
||||
OpKind::Immediate8 => insn.immediate8() as u64,
|
||||
OpKind::Immediate8to16 => insn.immediate8to16() as u64,
|
||||
OpKind::Immediate8to32 => insn.immediate8to32() as u64,
|
||||
OpKind::Immediate8to64 => insn.immediate8to64() as u64,
|
||||
OpKind::Immediate16 => insn.immediate16() as u64,
|
||||
OpKind::Immediate32 => insn.immediate32() as u64,
|
||||
OpKind::Immediate32to64 => insn.immediate32to64() as u64,
|
||||
OpKind::Immediate64 => insn.immediate64() as u64,
|
||||
k => return Err(PlatformError::InvalidOperand(anyhow!("{:?}", k))),
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn set_op<T: CpuStateManager>(
|
||||
insn: &Instruction,
|
||||
op_index: u32,
|
||||
op_size: usize,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
value: u64,
|
||||
) -> Result<(), PlatformError> {
|
||||
if insn.op_count() < op_index + 1 {
|
||||
return Err(PlatformError::InvalidOperand(anyhow!(
|
||||
"Invalid operand {:?}",
|
||||
op_index
|
||||
)));
|
||||
}
|
||||
|
||||
match op_size {
|
||||
1 | 2 | 4 | 8 => {}
|
||||
_ => {
|
||||
return Err(PlatformError::InvalidOperand(anyhow!(
|
||||
"Invalid operand size {:?}",
|
||||
op_size
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
match insn
|
||||
.try_op_kind(op_index)
|
||||
.map_err(|e| PlatformError::InvalidOperand(e.into()))?
|
||||
{
|
||||
OpKind::Register => state.write_reg(
|
||||
insn.try_op_register(op_index)
|
||||
.map_err(|e| PlatformError::InvalidOperand(e.into()))?,
|
||||
value,
|
||||
)?,
|
||||
OpKind::Memory => {
|
||||
let addr = memory_operand_address(insn, state, true)?;
|
||||
platform.write_memory(addr, &value.to_le_bytes()[..op_size])?;
|
||||
}
|
||||
k => return Err(PlatformError::InvalidOperand(anyhow!("{:?}", k))),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Returns the linear a.k.a. virtual address for a memory operand.
|
||||
fn memory_operand_address<T: CpuStateManager>(
|
||||
insn: &Instruction,
|
||||
state: &T,
|
||||
write: bool,
|
||||
) -> Result<u64, PlatformError> {
|
||||
let mut address: u64 = 0;
|
||||
|
||||
if insn.memory_base() != iced_x86::Register::None {
|
||||
let base: u64 = state.read_reg(insn.memory_base())?;
|
||||
address += base;
|
||||
}
|
||||
|
||||
if insn.memory_index() != iced_x86::Register::None {
|
||||
let mut index: u64 = state.read_reg(insn.memory_index())?;
|
||||
index *= insn.memory_index_scale() as u64;
|
||||
|
||||
address += index;
|
||||
}
|
||||
|
||||
address += insn.memory_displacement() as u64;
|
||||
|
||||
// Translate to a linear address.
|
||||
state.linearize(insn.memory_segment(), address, write)
|
||||
}
|
||||
|
||||
pub trait InstructionHandler<T: CpuStateManager> {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>>;
|
||||
}
|
||||
|
||||
macro_rules! insn_format {
|
||||
($insn:ident) => {{
|
||||
let mut output = String::new();
|
||||
let mut formatter = FastFormatter::new();
|
||||
formatter
|
||||
.options_mut()
|
||||
.set_space_after_operand_separator(true);
|
||||
formatter.format(&$insn, &mut output);
|
||||
|
||||
output
|
||||
}};
|
||||
}
|
||||
622
hypervisor/src/arch/x86/emulator/instructions/mov.rs
Normal file
622
hypervisor/src/arch/x86/emulator/instructions/mov.rs
Normal file
@@ -0,0 +1,622 @@
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
//
|
||||
// MOV-Move
|
||||
// SDM Volume 1, Chapter 4.3
|
||||
// Copies the second operand (source operand) to the first operand (destination operand).
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::Exception;
|
||||
|
||||
macro_rules! mov_rm_r {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let src_reg_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
set_op(
|
||||
&insn,
|
||||
0,
|
||||
std::mem::size_of::<$bound>(),
|
||||
state,
|
||||
platform,
|
||||
src_reg_value,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! mov_rm_imm {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let imm = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
set_op(
|
||||
&insn,
|
||||
0,
|
||||
std::mem::size_of::<$bound>(),
|
||||
state,
|
||||
platform,
|
||||
imm,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! movzx {
|
||||
($src_op_size:ty, $dest_op_size:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let src_value = get_op(
|
||||
&insn,
|
||||
1,
|
||||
std::mem::size_of::<$src_op_size>(),
|
||||
state,
|
||||
platform,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
set_op(
|
||||
&insn,
|
||||
0,
|
||||
std::mem::size_of::<$dest_op_size>(),
|
||||
state,
|
||||
platform,
|
||||
src_value,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// MOV r/rm is a special case of MOVZX, where both operands have the same size.
|
||||
macro_rules! mov_r_rm {
|
||||
($op_size:ty) => {
|
||||
movzx!($op_size, $op_size);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! mov_r_imm {
|
||||
($bound:ty) => {
|
||||
fn emulate(
|
||||
&self,
|
||||
insn: &Instruction,
|
||||
state: &mut T,
|
||||
platform: &mut dyn PlatformEmulator<CpuState = T>,
|
||||
) -> Result<(), EmulationError<Exception>> {
|
||||
let imm = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
set_op(
|
||||
&insn,
|
||||
0,
|
||||
std::mem::size_of::<$bound>(),
|
||||
state,
|
||||
platform,
|
||||
imm,
|
||||
)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Mov_r8_rm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r8_rm8 {
|
||||
mov_r_rm!(u8);
|
||||
}
|
||||
|
||||
pub struct Mov_r8_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r8_imm8 {
|
||||
mov_r_imm!(u8);
|
||||
}
|
||||
|
||||
pub struct Mov_r16_rm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r16_rm16 {
|
||||
mov_r_rm!(u16);
|
||||
}
|
||||
|
||||
pub struct Mov_r16_imm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r16_imm16 {
|
||||
mov_r_imm!(u16);
|
||||
}
|
||||
|
||||
pub struct Mov_r32_rm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r32_rm32 {
|
||||
mov_r_rm!(u32);
|
||||
}
|
||||
|
||||
pub struct Mov_r32_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r32_imm32 {
|
||||
mov_r_imm!(u32);
|
||||
}
|
||||
|
||||
pub struct Mov_r64_rm64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r64_rm64 {
|
||||
mov_r_rm!(u64);
|
||||
}
|
||||
|
||||
pub struct Mov_r64_imm64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r64_imm64 {
|
||||
mov_r_imm!(u64);
|
||||
}
|
||||
|
||||
pub struct Mov_rm8_imm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm8_imm8 {
|
||||
mov_rm_imm!(u8);
|
||||
}
|
||||
|
||||
pub struct Mov_rm8_r8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm8_r8 {
|
||||
mov_rm_r!(u8);
|
||||
}
|
||||
|
||||
pub struct Mov_rm16_imm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm16_imm16 {
|
||||
mov_rm_imm!(u16);
|
||||
}
|
||||
|
||||
pub struct Mov_rm16_r16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm16_r16 {
|
||||
mov_rm_r!(u16);
|
||||
}
|
||||
|
||||
pub struct Mov_rm32_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm32_imm32 {
|
||||
mov_rm_imm!(u32);
|
||||
}
|
||||
|
||||
pub struct Mov_rm32_r32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm32_r32 {
|
||||
mov_rm_r!(u32);
|
||||
}
|
||||
|
||||
pub struct Mov_rm64_imm32;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm64_imm32 {
|
||||
mov_rm_imm!(u32);
|
||||
}
|
||||
|
||||
pub struct Mov_rm64_r64;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm64_r64 {
|
||||
mov_rm_r!(u64);
|
||||
}
|
||||
|
||||
// MOVZX
|
||||
pub struct Movzx_r16_rm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movzx_r16_rm8 {
|
||||
movzx!(u16, u8);
|
||||
}
|
||||
|
||||
pub struct Movzx_r32_rm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movzx_r32_rm8 {
|
||||
movzx!(u32, u8);
|
||||
}
|
||||
|
||||
pub struct Movzx_r64_rm8;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movzx_r64_rm8 {
|
||||
movzx!(u64, u8);
|
||||
}
|
||||
|
||||
pub struct Movzx_r32_rm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movzx_r32_rm16 {
|
||||
movzx!(u32, u16);
|
||||
}
|
||||
|
||||
pub struct Movzx_r64_rm16;
|
||||
impl<T: CpuStateManager> InstructionHandler<T> for Movzx_r64_rm16 {
|
||||
movzx!(u64, u16);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_mut)]
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
// mov rax,rbx
|
||||
fn test_mov_r64_r64() {
|
||||
let rbx: u64 = 0x8899aabbccddeeff;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0x89, 0xd8];
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RBX, rbx)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, rbx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax,0x1122334411223344
|
||||
fn test_mov_r64_imm64() {
|
||||
let imm64: u64 = 0x1122334411223344;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0xb8, 0x44, 0x33, 0x22, 0x11, 0x44, 0x33, 0x22, 0x11];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, imm64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, [rax+rax]
|
||||
fn test_mov_r64_m64() {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let mut rax: u64 = 0x100;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory: [u8; 8] = target_rax.to_le_bytes();
|
||||
let insn = [0x48, 0x8b, 0x04, 0x00];
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], Some((rax + rax, &memory)));
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
rax = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, target_rax);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov al,0x11
|
||||
fn test_mov_r8_imm8() {
|
||||
let imm8: u8 = 0x11;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0xb0, 0x11];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let al = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::AL)
|
||||
.unwrap();
|
||||
assert_eq!(al as u8, imm8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov eax,0x11
|
||||
fn test_mov_r32_imm8() {
|
||||
let imm8: u8 = 0x11;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0xb8, 0x11, 0x00, 0x00, 0x00];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let eax = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(eax as u8, imm8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax,0x11223344
|
||||
fn test_mov_r64_imm32() {
|
||||
let imm32: u32 = 0x11223344;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x48, 0xc7, 0xc0, 0x44, 0x33, 0x22, 0x11];
|
||||
let mut vmm = MockVMM::new(ip, vec![], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, imm32 as u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov byte ptr [rax],dh
|
||||
fn test_mov_m8_r8() {
|
||||
let rax: u64 = 0x100;
|
||||
let dh: u8 = 0x99;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x88, 0x30];
|
||||
let mut vmm = MockVMM::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax), (Register::DH, dh.into())],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let mut memory: [u8; 1] = [0; 1];
|
||||
vmm.read_memory(rax, &mut memory).unwrap();
|
||||
|
||||
assert_eq!(u8::from_le_bytes(memory), dh);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov dword ptr [rax],esi
|
||||
fn test_mov_m32_r32() {
|
||||
let rax: u64 = 0x100;
|
||||
let esi: u32 = 0x8899;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x89, 0x30];
|
||||
let mut vmm = MockVMM::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax), (Register::ESI, esi.into())],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let mut memory: [u8; 4] = [0; 4];
|
||||
vmm.read_memory(rax, &mut memory).unwrap();
|
||||
|
||||
assert_eq!(u32::from_le_bytes(memory), esi);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov dword ptr [rax+0x00000001],edi
|
||||
fn test_mov_m32imm32_r32() {
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x1;
|
||||
let edi: u32 = 0x8899;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x89, 0x3c, 0x05, 0x01, 0x00, 0x00, 0x00];
|
||||
let mut vmm = MockVMM::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax), (Register::EDI, edi.into())],
|
||||
None,
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let mut memory: [u8; 4] = [0; 4];
|
||||
vmm.read_memory(rax + displacement, &mut memory).unwrap();
|
||||
|
||||
assert_eq!(u32::from_le_bytes(memory), edi);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov eax,dword ptr [rax+10h]
|
||||
fn test_mov_r32_m32imm32() {
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let eax: u32 = 0xaabbccdd;
|
||||
let memory: [u8; 4] = eax.to_le_bytes();
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x8b, 0x40, 0x10];
|
||||
let mut vmm = MockVMM::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax)],
|
||||
Some((rax + displacement, &memory)),
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let new_eax = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(new_eax, eax as u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov al,byte ptr [rax+10h]
|
||||
fn test_mov_r8_m32imm32() {
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let al: u8 = 0xaa;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x8a, 0x40, 0x10];
|
||||
let memory: [u8; 1] = al.to_le_bytes();
|
||||
let mut vmm = MockVMM::new(
|
||||
ip,
|
||||
vec![(Register::RAX, rax)],
|
||||
Some((rax + displacement, &memory)),
|
||||
);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let new_al = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::AL)
|
||||
.unwrap();
|
||||
assert_eq!(new_al, al as u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, 0x100
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
fn test_mov_r64_imm64_and_r64_m64() {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory: [u8; 8] = target_rax.to_le_bytes();
|
||||
let insn = [
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x01, 0x00, 0x00, // mov rax, 0x100
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
];
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
let rbx: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, target_rax);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, 0x100
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
fn test_mov_r64_imm64_and_r64_m64_first_insn() {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory: [u8; 8] = target_rax.to_le_bytes();
|
||||
let insn = [
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x01, 0x00, 0x00, // mov rax, 0x100
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
// Only run the first instruction.
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
assert_eq!(ip + 7, vmm.cpu_state(cpu_id).unwrap().ip());
|
||||
|
||||
let new_rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, new_rax);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// mov rax, 0x100
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// mov rax, 0x200
|
||||
fn test_mov_r64_imm64_and_r64_m64_two_insns() {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let rax: u64 = 0x100;
|
||||
let displacement: u64 = 0x10;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory: [u8; 8] = target_rax.to_le_bytes();
|
||||
let insn = [
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x01, 0x00, 0x00, // mov rax, 0x100
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x02, 0x00, 0x00, // mov rax, 0x200
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
|
||||
// Run the 2 first instructions.
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
assert_eq!(ip + 7 + 4, vmm.cpu_state(cpu_id).unwrap().ip());
|
||||
|
||||
let rbx: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, target_rax);
|
||||
|
||||
// Check that rax is still at 0x100
|
||||
let new_rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, new_rax);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// movzx eax, bl
|
||||
fn test_movzx_r32_r8l() {
|
||||
let bx: u16 = 0x8899;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x0f, 0xb6, 0xc3];
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::BX, bx as u64)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let eax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(eax, (bx & 0xff) as u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// movzx eax, bh
|
||||
fn test_movzx_r32_r8h() {
|
||||
let bx: u16 = 0x8899;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x0f, 0xb6, 0xc7];
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::BX, bx as u64)], None);
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let eax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(eax, (bx >> 8) as u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// movzx eax, byte ptr [rbx]
|
||||
fn test_movzx_r32_m8() {
|
||||
let rbx: u64 = 0x100;
|
||||
let value: u8 = 0xaa;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let insn = [0x0f, 0xb7, 0x03];
|
||||
let memory: [u8; 1] = value.to_le_bytes();
|
||||
let mut vmm = MockVMM::new(ip, vec![(Register::RBX, rbx)], Some((rbx, &memory)));
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
|
||||
|
||||
let eax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::EAX)
|
||||
.unwrap();
|
||||
assert_eq!(eax, value as u64);
|
||||
}
|
||||
}
|
||||
861
hypervisor/src/arch/x86/emulator/mod.rs
Normal file
861
hypervisor/src/arch/x86/emulator/mod.rs
Normal file
@@ -0,0 +1,861 @@
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
extern crate iced_x86;
|
||||
|
||||
use crate::arch::emulator::{EmulationError, EmulationResult, PlatformEmulator, PlatformError};
|
||||
use crate::arch::x86::emulator::instructions::*;
|
||||
use crate::arch::x86::regs::*;
|
||||
use crate::arch::x86::*;
|
||||
use crate::arch::x86::{Exception, SegmentRegisterOps};
|
||||
use crate::x86_64::{SegmentRegister, SpecialRegisters, StandardRegisters};
|
||||
use anyhow::Context;
|
||||
use iced_x86::*;
|
||||
|
||||
#[macro_use]
|
||||
mod instructions;
|
||||
|
||||
/// x86 CPU modes
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CpuMode {
|
||||
/// Real mode
|
||||
Real,
|
||||
|
||||
/// Virtual 8086 mode
|
||||
Virtual8086,
|
||||
|
||||
/// 16-bit protected mode
|
||||
Protected16,
|
||||
|
||||
/// 32-bit protected mode
|
||||
Protected,
|
||||
|
||||
/// 64-bit mode, a.k.a. long mode
|
||||
Long,
|
||||
}
|
||||
|
||||
/// CpuStateManager manages an x86 CPU state.
|
||||
///
|
||||
/// Instruction emulation handlers get a mutable reference to
|
||||
/// a `CpuStateManager` implementation, representing the current state of the
|
||||
/// CPU they have to emulate an instruction stream against. Usually those
|
||||
/// handlers will modify the CPU state by modifying `CpuState` and it is up to
|
||||
/// the handler caller to commit those changes back by invoking a
|
||||
/// `PlatformEmulator` implementation `set_state()` method.
|
||||
///
|
||||
pub trait CpuStateManager: Clone {
|
||||
/// Reads a CPU register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reg` - A general purpose, control or debug register.
|
||||
fn read_reg(&self, reg: Register) -> Result<u64, PlatformError>;
|
||||
|
||||
/// Write to a CPU register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reg` - A general purpose, control or debug register.
|
||||
/// * `val` - The value to load.
|
||||
fn write_reg(&mut self, reg: Register, val: u64) -> Result<(), PlatformError>;
|
||||
|
||||
/// Reads a segment register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reg` - A segment register.
|
||||
fn read_segment(&self, reg: Register) -> Result<SegmentRegister, PlatformError>;
|
||||
|
||||
/// Write to a segment register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reg` - A segment register.
|
||||
/// * `segment_reg` - The segment register value to load.
|
||||
fn write_segment(
|
||||
&mut self,
|
||||
reg: Register,
|
||||
segment_reg: SegmentRegister,
|
||||
) -> Result<(), PlatformError>;
|
||||
|
||||
/// Get the CPU instruction pointer.
|
||||
fn ip(&self) -> u64;
|
||||
|
||||
/// Set the CPU instruction pointer.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ip` - The CPU instruction pointer.
|
||||
fn set_ip(&mut self, ip: u64);
|
||||
|
||||
/// Get the CPU Extended Feature Enable Register.
|
||||
fn efer(&self) -> u64;
|
||||
|
||||
/// Set the CPU Extended Feature Enable Register.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `efer` - The CPU EFER value.
|
||||
fn set_efer(&mut self, efer: u64);
|
||||
|
||||
/// Get the CPU flags.
|
||||
fn flags(&self) -> u64;
|
||||
|
||||
/// Set the CPU flags.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `flags` - The CPU flags
|
||||
fn set_flags(&mut self, flags: u64);
|
||||
|
||||
/// Get the CPU mode.
|
||||
fn mode(&self) -> Result<CpuMode, PlatformError>;
|
||||
|
||||
/// Translate a logical (segmented) address into a linear (virtual) one.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `segment` - Which segment to use for linearization
|
||||
/// * `logical_addr` - The logical address to be translated
|
||||
fn linearize(
|
||||
&self,
|
||||
segment: Register,
|
||||
logical_addr: u64,
|
||||
write: bool,
|
||||
) -> Result<u64, PlatformError> {
|
||||
let segment_register = self.read_segment(segment)?;
|
||||
let mode = self.mode()?;
|
||||
|
||||
match mode {
|
||||
CpuMode::Long => {
|
||||
// TODO Check that we got a canonical address.
|
||||
Ok(logical_addr
|
||||
.checked_add(segment_register.base)
|
||||
.ok_or_else(|| {
|
||||
PlatformError::InvalidAddress(anyhow!(
|
||||
"Logical address {:#x} can not be linearized with segment {:#x?}",
|
||||
logical_addr,
|
||||
segment_register
|
||||
))
|
||||
})?)
|
||||
}
|
||||
|
||||
CpuMode::Protected | CpuMode::Real => {
|
||||
let segment_type = segment_register.segment_type();
|
||||
|
||||
// Must not write to a read-only segment.
|
||||
if segment_type_ro(segment_type) && write {
|
||||
return Err(PlatformError::InvalidAddress(anyhow!(
|
||||
"Can not write to a read-only segment"
|
||||
)));
|
||||
}
|
||||
|
||||
let logical_addr = logical_addr & 0xffff_ffffu64;
|
||||
let mut segment_limit: u32 = if segment_register.granularity() != 0 {
|
||||
(segment_register.limit << 12) | 0xfff
|
||||
} else {
|
||||
segment_register.limit
|
||||
};
|
||||
|
||||
// Expand-down segment
|
||||
if segment_type_expand_down(segment_type) {
|
||||
if logical_addr >= segment_limit.into() {
|
||||
return Err(PlatformError::InvalidAddress(anyhow!(
|
||||
"{:#x} is off limits {:#x} (expand down)",
|
||||
logical_addr,
|
||||
segment_limit
|
||||
)));
|
||||
}
|
||||
|
||||
if segment_register.db() != 0 {
|
||||
segment_limit = 0xffffffff
|
||||
} else {
|
||||
segment_limit = 0xffff
|
||||
}
|
||||
}
|
||||
|
||||
if logical_addr > segment_limit.into() {
|
||||
return Err(PlatformError::InvalidAddress(anyhow!(
|
||||
"{:#x} is off limits {:#x}",
|
||||
logical_addr,
|
||||
segment_limit
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(logical_addr + segment_register.base)
|
||||
}
|
||||
|
||||
_ => Err(PlatformError::UnsupportedCpuMode(anyhow!("{:?}", mode))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const REGISTER_MASK_64: u64 = 0xffff_ffff_ffff_ffffu64;
|
||||
const REGISTER_MASK_32: u64 = 0xffff_ffffu64;
|
||||
const REGISTER_MASK_16: u64 = 0xffffu64;
|
||||
const REGISTER_MASK_8: u64 = 0xffu64;
|
||||
|
||||
macro_rules! set_reg {
|
||||
($reg:expr, $mask:expr, $value:expr) => {
|
||||
$reg = ($reg & $mask) | $value
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
/// A minimal, emulated CPU state.
|
||||
///
|
||||
/// Hypervisors needing x86 emulation can choose to either use their own
|
||||
/// CPU state structures and implement the CpuStateManager interface for it,
|
||||
/// or use `EmulatorCpuState`. The latter implies creating a new state
|
||||
/// `EmulatorCpuState` instance for each platform `cpu_state()` call, which
|
||||
/// might be less efficient.
|
||||
pub struct EmulatorCpuState {
|
||||
pub regs: StandardRegisters,
|
||||
pub sregs: SpecialRegisters,
|
||||
}
|
||||
|
||||
impl CpuStateManager for EmulatorCpuState {
|
||||
fn read_reg(&self, reg: Register) -> Result<u64, PlatformError> {
|
||||
let mut reg_value: u64 = match reg {
|
||||
Register::RAX | Register::EAX | Register::AX | Register::AL | Register::AH => {
|
||||
self.regs.rax
|
||||
}
|
||||
Register::RBX | Register::EBX | Register::BX | Register::BL | Register::BH => {
|
||||
self.regs.rbx
|
||||
}
|
||||
Register::RCX | Register::ECX | Register::CX | Register::CL | Register::CH => {
|
||||
self.regs.rcx
|
||||
}
|
||||
Register::RDX | Register::EDX | Register::DX | Register::DL | Register::DH => {
|
||||
self.regs.rdx
|
||||
}
|
||||
Register::RSP | Register::ESP | Register::SP => self.regs.rsp,
|
||||
Register::RBP | Register::EBP | Register::BP => self.regs.rbp,
|
||||
Register::RSI | Register::ESI | Register::SI | Register::SIL => self.regs.rsi,
|
||||
Register::RDI | Register::EDI | Register::DI | Register::DIL => self.regs.rdi,
|
||||
Register::R8 | Register::R8D | Register::R8W | Register::R8L => self.regs.r8,
|
||||
Register::R9 | Register::R9D | Register::R9W | Register::R9L => self.regs.r9,
|
||||
Register::R10 | Register::R10D | Register::R10W | Register::R10L => self.regs.r10,
|
||||
Register::R11 | Register::R11D | Register::R11W | Register::R11L => self.regs.r11,
|
||||
Register::R12 | Register::R12D | Register::R12W | Register::R12L => self.regs.r12,
|
||||
Register::R13 | Register::R13D | Register::R13W | Register::R13L => self.regs.r13,
|
||||
Register::R14 | Register::R14D | Register::R14W | Register::R14L => self.regs.r14,
|
||||
Register::R15 | Register::R15D | Register::R15W | Register::R15L => self.regs.r15,
|
||||
Register::CR0 => self.sregs.cr0,
|
||||
Register::CR2 => self.sregs.cr2,
|
||||
Register::CR3 => self.sregs.cr3,
|
||||
Register::CR4 => self.sregs.cr4,
|
||||
Register::CR8 => self.sregs.cr8,
|
||||
|
||||
r => {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"read_reg invalid GPR {:?}",
|
||||
r
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
reg_value = if reg.is_gpr64() || reg.is_cr() {
|
||||
reg_value
|
||||
} else if reg.is_gpr32() {
|
||||
reg_value & REGISTER_MASK_32
|
||||
} else if reg.is_gpr16() {
|
||||
reg_value & REGISTER_MASK_16
|
||||
} else if reg.is_gpr8() {
|
||||
if reg == Register::AH
|
||||
|| reg == Register::BH
|
||||
|| reg == Register::CH
|
||||
|| reg == Register::DH
|
||||
{
|
||||
(reg_value >> 8) & REGISTER_MASK_8
|
||||
} else {
|
||||
reg_value & REGISTER_MASK_8
|
||||
}
|
||||
} else {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"read_reg invalid GPR {:?}",
|
||||
reg
|
||||
)));
|
||||
};
|
||||
|
||||
debug!("Register read: {:#x} from {:?}", reg_value, reg);
|
||||
|
||||
Ok(reg_value)
|
||||
}
|
||||
|
||||
fn write_reg(&mut self, reg: Register, val: u64) -> Result<(), PlatformError> {
|
||||
debug!("Register write: {:#x} to {:?}", val, reg);
|
||||
|
||||
// SDM Vol 1 - 3.4.1.1
|
||||
//
|
||||
// 8-bit and 16-bit operands generate an 8-bit or 16-bit result.
|
||||
// The upper 56 bits or 48 bits (respectively) of the destination
|
||||
// general-purpose register are not modified by the operation.
|
||||
let (reg_value, mask): (u64, u64) = if reg.is_gpr64() || reg.is_cr() {
|
||||
(val, !REGISTER_MASK_64)
|
||||
} else if reg.is_gpr32() {
|
||||
(val & REGISTER_MASK_32, !REGISTER_MASK_64)
|
||||
} else if reg.is_gpr16() {
|
||||
(val & REGISTER_MASK_16, !REGISTER_MASK_16)
|
||||
} else if reg.is_gpr8() {
|
||||
if reg == Register::AH
|
||||
|| reg == Register::BH
|
||||
|| reg == Register::CH
|
||||
|| reg == Register::DH
|
||||
{
|
||||
((val & REGISTER_MASK_8) << 8, !(REGISTER_MASK_8 << 8))
|
||||
} else {
|
||||
(val & REGISTER_MASK_8, !REGISTER_MASK_8)
|
||||
}
|
||||
} else {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"write_reg invalid register {:?}",
|
||||
reg
|
||||
)));
|
||||
};
|
||||
|
||||
match reg {
|
||||
Register::RAX | Register::EAX | Register::AX | Register::AL | Register::AH => {
|
||||
set_reg!(self.regs.rax, mask, reg_value);
|
||||
}
|
||||
Register::RBX | Register::EBX | Register::BX | Register::BL | Register::BH => {
|
||||
set_reg!(self.regs.rbx, mask, reg_value);
|
||||
}
|
||||
Register::RCX | Register::ECX | Register::CX | Register::CL | Register::CH => {
|
||||
set_reg!(self.regs.rcx, mask, reg_value);
|
||||
}
|
||||
Register::RDX | Register::EDX | Register::DX | Register::DL | Register::DH => {
|
||||
set_reg!(self.regs.rdx, mask, reg_value);
|
||||
}
|
||||
Register::RSP | Register::ESP | Register::SP => {
|
||||
set_reg!(self.regs.rsp, mask, reg_value)
|
||||
}
|
||||
Register::RBP | Register::EBP | Register::BP => {
|
||||
set_reg!(self.regs.rbp, mask, reg_value)
|
||||
}
|
||||
Register::RSI | Register::ESI | Register::SI | Register::SIL => {
|
||||
set_reg!(self.regs.rsi, mask, reg_value)
|
||||
}
|
||||
Register::RDI | Register::EDI | Register::DI | Register::DIL => {
|
||||
set_reg!(self.regs.rdi, mask, reg_value)
|
||||
}
|
||||
Register::R8 | Register::R8D | Register::R8W | Register::R8L => {
|
||||
set_reg!(self.regs.r8, mask, reg_value)
|
||||
}
|
||||
Register::R9 | Register::R9D | Register::R9W | Register::R9L => {
|
||||
set_reg!(self.regs.r9, mask, reg_value)
|
||||
}
|
||||
Register::R10 | Register::R10D | Register::R10W | Register::R10L => {
|
||||
set_reg!(self.regs.r10, mask, reg_value)
|
||||
}
|
||||
Register::R11 | Register::R11D | Register::R11W | Register::R11L => {
|
||||
set_reg!(self.regs.r11, mask, reg_value)
|
||||
}
|
||||
Register::R12 | Register::R12D | Register::R12W | Register::R12L => {
|
||||
set_reg!(self.regs.r12, mask, reg_value)
|
||||
}
|
||||
Register::R13 | Register::R13D | Register::R13W | Register::R13L => {
|
||||
set_reg!(self.regs.r13, mask, reg_value)
|
||||
}
|
||||
Register::R14 | Register::R14D | Register::R14W | Register::R14L => {
|
||||
set_reg!(self.regs.r14, mask, reg_value)
|
||||
}
|
||||
Register::R15 | Register::R15D | Register::R15W | Register::R15L => {
|
||||
set_reg!(self.regs.r15, mask, reg_value)
|
||||
}
|
||||
Register::CR0 => set_reg!(self.sregs.cr0, mask, reg_value),
|
||||
Register::CR2 => set_reg!(self.sregs.cr2, mask, reg_value),
|
||||
Register::CR3 => set_reg!(self.sregs.cr3, mask, reg_value),
|
||||
Register::CR4 => set_reg!(self.sregs.cr4, mask, reg_value),
|
||||
Register::CR8 => set_reg!(self.sregs.cr8, mask, reg_value),
|
||||
_ => {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"write_reg invalid register {:?}",
|
||||
reg
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_segment(&self, reg: Register) -> Result<SegmentRegister, PlatformError> {
|
||||
if !reg.is_segment_register() {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"read_segment {:?} is not a segment register",
|
||||
reg
|
||||
)));
|
||||
}
|
||||
|
||||
match reg {
|
||||
Register::CS => Ok(self.sregs.cs),
|
||||
Register::DS => Ok(self.sregs.ds),
|
||||
Register::ES => Ok(self.sregs.es),
|
||||
Register::FS => Ok(self.sregs.fs),
|
||||
Register::GS => Ok(self.sregs.gs),
|
||||
Register::SS => Ok(self.sregs.ss),
|
||||
r => Err(PlatformError::InvalidRegister(anyhow!(
|
||||
"read_segment invalid register {:?}",
|
||||
r
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_segment(
|
||||
&mut self,
|
||||
reg: Register,
|
||||
segment_register: SegmentRegister,
|
||||
) -> Result<(), PlatformError> {
|
||||
if !reg.is_segment_register() {
|
||||
return Err(PlatformError::InvalidRegister(anyhow!("{:?}", reg)));
|
||||
}
|
||||
|
||||
match reg {
|
||||
Register::CS => self.sregs.cs = segment_register,
|
||||
Register::DS => self.sregs.ds = segment_register,
|
||||
Register::ES => self.sregs.es = segment_register,
|
||||
Register::FS => self.sregs.fs = segment_register,
|
||||
Register::GS => self.sregs.gs = segment_register,
|
||||
Register::SS => self.sregs.ss = segment_register,
|
||||
r => return Err(PlatformError::InvalidRegister(anyhow!("{:?}", r))),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ip(&self) -> u64 {
|
||||
self.regs.rip
|
||||
}
|
||||
|
||||
fn set_ip(&mut self, ip: u64) {
|
||||
self.regs.rip = ip;
|
||||
}
|
||||
|
||||
fn efer(&self) -> u64 {
|
||||
self.sregs.efer
|
||||
}
|
||||
|
||||
fn set_efer(&mut self, efer: u64) {
|
||||
self.sregs.efer = efer
|
||||
}
|
||||
|
||||
fn flags(&self) -> u64 {
|
||||
self.regs.rflags
|
||||
}
|
||||
|
||||
fn set_flags(&mut self, flags: u64) {
|
||||
self.regs.rflags = flags;
|
||||
}
|
||||
|
||||
fn mode(&self) -> Result<CpuMode, PlatformError> {
|
||||
let efer = self.efer();
|
||||
let cr0 = self.read_reg(Register::CR0)?;
|
||||
let mut mode = CpuMode::Real;
|
||||
|
||||
if (cr0 & CR0_PE) == CR0_PE {
|
||||
mode = CpuMode::Protected;
|
||||
}
|
||||
|
||||
if (efer & EFER_LMA) == EFER_LMA {
|
||||
if mode != CpuMode::Protected {
|
||||
return Err(PlatformError::InvalidState(anyhow!(
|
||||
"Protection must be enabled in long mode"
|
||||
)));
|
||||
}
|
||||
|
||||
mode = CpuMode::Long;
|
||||
}
|
||||
|
||||
Ok(mode)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Emulator<'a, T: CpuStateManager> {
|
||||
platform: &'a mut dyn PlatformEmulator<CpuState = T>,
|
||||
}
|
||||
|
||||
// Reduce repetition, see its invocation in get_handler().
|
||||
macro_rules! gen_handler_match {
|
||||
($value: ident, $( ($module:ident, $code:ident) ),* ) => {
|
||||
match $value {
|
||||
$(
|
||||
Code::$code => Some(Box::new($module::$code)),
|
||||
)*
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a, T: CpuStateManager> Emulator<'a, T> {
|
||||
pub fn new(platform: &mut dyn PlatformEmulator<CpuState = T>) -> Emulator<T> {
|
||||
Emulator { platform }
|
||||
}
|
||||
|
||||
fn get_handler(code: Code) -> Option<Box<dyn InstructionHandler<T>>> {
|
||||
let handler: Option<Box<dyn InstructionHandler<T>>> = gen_handler_match!(
|
||||
code,
|
||||
// CMP
|
||||
(cmp, Cmp_rm32_r32),
|
||||
(cmp, Cmp_rm8_r8),
|
||||
(cmp, Cmp_rm32_imm8),
|
||||
(cmp, Cmp_rm64_r64),
|
||||
// MOV
|
||||
(mov, Mov_r8_rm8),
|
||||
(mov, Mov_r8_imm8),
|
||||
(mov, Mov_r16_imm16),
|
||||
(mov, Mov_r16_rm16),
|
||||
(mov, Mov_r32_imm32),
|
||||
(mov, Mov_r32_rm32),
|
||||
(mov, Mov_r64_imm64),
|
||||
(mov, Mov_r64_rm64),
|
||||
(mov, Mov_rm8_imm8),
|
||||
(mov, Mov_rm8_r8),
|
||||
(mov, Mov_rm16_imm16),
|
||||
(mov, Mov_rm16_r16),
|
||||
(mov, Mov_rm32_imm32),
|
||||
(mov, Mov_rm32_r32),
|
||||
(mov, Mov_rm64_imm32),
|
||||
(mov, Mov_rm64_r64),
|
||||
// MOVZX
|
||||
(mov, Movzx_r16_rm8),
|
||||
(mov, Movzx_r32_rm8),
|
||||
(mov, Movzx_r64_rm8),
|
||||
(mov, Movzx_r32_rm16),
|
||||
(mov, Movzx_r64_rm16)
|
||||
);
|
||||
|
||||
handler
|
||||
}
|
||||
|
||||
fn emulate_insn_stream(
|
||||
&mut self,
|
||||
cpu_id: usize,
|
||||
insn_stream: &[u8],
|
||||
num_insn: Option<usize>,
|
||||
) -> EmulationResult<T, Exception> {
|
||||
let mut state = self
|
||||
.platform
|
||||
.cpu_state(cpu_id)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
let mut decoder = Decoder::new(64, insn_stream, DecoderOptions::NONE);
|
||||
let mut insn = Instruction::default();
|
||||
let mut num_insn_emulated: usize = 0;
|
||||
let mut fetched_insn_stream: [u8; 16] = [0; 16];
|
||||
let mut last_decoded_ip: u64 = state.ip();
|
||||
let mut stop_emulation: bool = false;
|
||||
|
||||
decoder.set_ip(state.ip());
|
||||
|
||||
while decoder.can_decode() && !stop_emulation {
|
||||
decoder.decode_out(&mut insn);
|
||||
|
||||
if decoder.last_error() == DecoderError::NoMoreBytes {
|
||||
// The decoder is missing some bytes to decode the current
|
||||
// instruction, for example because the instruction stream
|
||||
// crosses a page boundary.
|
||||
// We fetch 16 more bytes from the instruction segment,
|
||||
// decode and emulate the failing instruction and terminate
|
||||
// the emulation loop.
|
||||
debug!(
|
||||
"Fetching {} bytes from {:#x}",
|
||||
fetched_insn_stream.len(),
|
||||
last_decoded_ip
|
||||
);
|
||||
|
||||
// fetched_insn_stream is 16 bytes long, enough to contain
|
||||
// any complete x86 instruction.
|
||||
self.platform
|
||||
.fetch(last_decoded_ip, &mut fetched_insn_stream)
|
||||
.map_err(EmulationError::PlatformEmulationError)?;
|
||||
|
||||
debug!("Fetched {:x?}", fetched_insn_stream);
|
||||
|
||||
// Once we have the new stream, we must create a new decoder
|
||||
// and emulate one last instruction from the last decoded IP.
|
||||
decoder = Decoder::new(64, &fetched_insn_stream, DecoderOptions::NONE);
|
||||
decoder.decode_out(&mut insn);
|
||||
if decoder.last_error() != DecoderError::None {
|
||||
return Err(EmulationError::InstructionFetchingError(anyhow!(
|
||||
"{:#x?}",
|
||||
insn_format!(insn)
|
||||
)));
|
||||
}
|
||||
|
||||
stop_emulation = true;
|
||||
}
|
||||
|
||||
// Emulate the decoded instruction
|
||||
Emulator::get_handler(insn.code())
|
||||
.ok_or_else(|| {
|
||||
EmulationError::UnsupportedInstruction(anyhow!(
|
||||
"{:#x?} {:?} {:?}",
|
||||
insn_format!(insn),
|
||||
insn.mnemonic(),
|
||||
insn.code()
|
||||
))
|
||||
})?
|
||||
.emulate(&insn, &mut state, self.platform)
|
||||
.context(anyhow!("Failed to emulate {:#x?}", insn_format!(insn)))?;
|
||||
|
||||
last_decoded_ip = decoder.ip();
|
||||
num_insn_emulated += 1;
|
||||
|
||||
if let Some(num_insn) = num_insn {
|
||||
if num_insn_emulated >= num_insn {
|
||||
// Exit the decoding loop, do not decode the next instruction.
|
||||
stop_emulation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.set_ip(decoder.ip());
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// Emulate all instructions from the instructions stream.
|
||||
pub fn emulate(&mut self, cpu_id: usize, insn_stream: &[u8]) -> EmulationResult<T, Exception> {
|
||||
self.emulate_insn_stream(cpu_id, insn_stream, None)
|
||||
}
|
||||
|
||||
/// Only emulate the first instruction from the stream.
|
||||
///
|
||||
/// This is useful for cases where we get readahead instruction stream
|
||||
/// but implicitly must only emulate the first instruction, and then return
|
||||
/// to the guest.
|
||||
pub fn emulate_first_insn(
|
||||
&mut self,
|
||||
cpu_id: usize,
|
||||
insn_stream: &[u8],
|
||||
) -> EmulationResult<T, Exception> {
|
||||
self.emulate_insn_stream(cpu_id, insn_stream, Some(1))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock_vmm {
|
||||
#![allow(unused_mut)]
|
||||
|
||||
extern crate env_logger;
|
||||
|
||||
use super::*;
|
||||
use crate::arch::emulator::{EmulationError, PlatformEmulator};
|
||||
use crate::arch::x86::emulator::{Emulator, EmulatorCpuState as CpuState};
|
||||
use crate::arch::x86::gdt::{gdt_entry, segment_from_gdt};
|
||||
use crate::arch::x86::Exception;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MockVMM {
|
||||
memory: Vec<u8>,
|
||||
state: Arc<Mutex<CpuState>>,
|
||||
}
|
||||
|
||||
unsafe impl Sync for MockVMM {}
|
||||
|
||||
pub type MockResult = Result<(), EmulationError<Exception>>;
|
||||
|
||||
impl MockVMM {
|
||||
pub fn new(ip: u64, regs: Vec<(Register, u64)>, memory: Option<(u64, &[u8])>) -> MockVMM {
|
||||
let _ = env_logger::try_init();
|
||||
let cs_reg = segment_from_gdt(gdt_entry(0xc09b, 0, 0xffffffff), 1);
|
||||
let ds_reg = segment_from_gdt(gdt_entry(0xc093, 0, 0xffffffff), 2);
|
||||
let es_reg = segment_from_gdt(gdt_entry(0xc093, 0, 0xffffffff), 3);
|
||||
let mut initial_state = CpuState::default();
|
||||
initial_state.set_ip(ip);
|
||||
initial_state.write_segment(Register::CS, cs_reg).unwrap();
|
||||
initial_state.write_segment(Register::DS, ds_reg).unwrap();
|
||||
initial_state.write_segment(Register::ES, es_reg).unwrap();
|
||||
for (reg, value) in regs {
|
||||
initial_state.write_reg(reg, value).unwrap();
|
||||
}
|
||||
|
||||
let mut vmm = MockVMM {
|
||||
memory: vec![0; 8192],
|
||||
state: Arc::new(Mutex::new(initial_state)),
|
||||
};
|
||||
|
||||
if let Some(mem) = memory {
|
||||
vmm.write_memory(mem.0, &mem.1).unwrap();
|
||||
}
|
||||
|
||||
vmm
|
||||
}
|
||||
|
||||
pub fn emulate_insn(
|
||||
&mut self,
|
||||
cpu_id: usize,
|
||||
insn: &[u8],
|
||||
num_insn: Option<usize>,
|
||||
) -> MockResult {
|
||||
let ip = self.cpu_state(cpu_id).unwrap().ip();
|
||||
let mut emulator = Emulator::new(self);
|
||||
|
||||
let new_state = emulator.emulate_insn_stream(cpu_id, &insn, num_insn)?;
|
||||
if num_insn.is_none() {
|
||||
assert_eq!(ip + insn.len() as u64, new_state.ip());
|
||||
}
|
||||
|
||||
self.set_cpu_state(cpu_id, new_state).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn emulate_first_insn(&mut self, cpu_id: usize, insn: &[u8]) -> MockResult {
|
||||
self.emulate_insn(cpu_id, insn, Some(1))
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformEmulator for MockVMM {
|
||||
type CpuState = CpuState;
|
||||
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
debug!(
|
||||
"Memory read {} bytes from [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gva
|
||||
);
|
||||
data.copy_from_slice(&self.memory[gva as usize..gva as usize + data.len()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError> {
|
||||
debug!(
|
||||
"Memory write {} bytes at [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gva
|
||||
);
|
||||
self.memory[gva as usize..gva as usize + data.len()].copy_from_slice(data);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cpu_state(&self, _cpu_id: usize) -> Result<CpuState, PlatformError> {
|
||||
Ok(self.state.lock().unwrap().clone())
|
||||
}
|
||||
|
||||
fn set_cpu_state(
|
||||
&self,
|
||||
_cpu_id: usize,
|
||||
state: Self::CpuState,
|
||||
) -> Result<(), PlatformError> {
|
||||
*self.state.lock().unwrap() = state;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError> {
|
||||
Ok(gva)
|
||||
}
|
||||
|
||||
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
|
||||
let rip = self
|
||||
.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.linearize(Register::CS, ip, false)?;
|
||||
self.read_memory(rip, instruction_bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused_mut)]
|
||||
use super::*;
|
||||
use crate::arch::x86::emulator::mock_vmm::*;
|
||||
|
||||
#[test]
|
||||
// Emulate truncated instruction stream, which should cause a fetch.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// Test with a first instruction truncated.
|
||||
fn test_fetch_first_instruction() {
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
// Padding
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
|
||||
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||
];
|
||||
let insn = [
|
||||
// First instruction is truncated
|
||||
0x48, 0xc7, 0xc0, 0x00, // mov rax, 0x1000 -- Missing bytes: 0x00, 0x10, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
let rax: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RAX)
|
||||
.unwrap();
|
||||
assert_eq!(rax, ip);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate truncated instruction stream, which should cause a fetch.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// mov rbx, qword ptr [rax+10h]
|
||||
// Test with a 2nd instruction truncated.
|
||||
fn test_fetch_second_instruction() {
|
||||
let target_rax: u64 = 0x1234567812345678;
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
|
||||
// Padding
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
|
||||
// Data at IP + 0x10 (0x1234567812345678 in LE)
|
||||
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
|
||||
];
|
||||
let insn = [
|
||||
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
|
||||
0x48, 0x8b, // Truncated mov rbx, qword ptr [rax+10h] -- missing [0x58, 0x10]
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
|
||||
|
||||
let rbx: u64 = vmm
|
||||
.cpu_state(cpu_id)
|
||||
.unwrap()
|
||||
.read_reg(Register::RBX)
|
||||
.unwrap();
|
||||
assert_eq!(rbx, target_rax);
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Emulate truncated instruction stream, which should cause a fetch.
|
||||
//
|
||||
// mov rax, 0x1000
|
||||
// Test with a first instruction truncated and a bad fetched instruction.
|
||||
// Verify that the instruction emulation returns an error.
|
||||
fn test_fetch_bad_insn() {
|
||||
let ip: u64 = 0x1000;
|
||||
let cpu_id = 0;
|
||||
let memory = [
|
||||
// Code at IP
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff,
|
||||
];
|
||||
let insn = [
|
||||
// First instruction is truncated
|
||||
0x48, 0xc7, 0xc0, 0x00, // mov rax, 0x1000 -- Missing bytes: 0x00, 0x10, 0x00, 0x00,
|
||||
];
|
||||
|
||||
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
|
||||
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_err());
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
// found in the LICENSE-BSD-3-Clause file.
|
||||
|
||||
// For GDT details see arch/x86/include/asm/segment.h
|
||||
use hypervisor::x86_64::SegmentRegister;
|
||||
use crate::x86_64::SegmentRegister;
|
||||
|
||||
/// Constructor for a conventional segment GDT (or LDT) entry. Derived from the kernel's segment.h.
|
||||
pub fn gdt_entry(flags: u16, base: u32, limit: u32) -> u64 {
|
||||
@@ -11,6 +11,8 @@
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
|
||||
pub mod gdt;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_snake_case)]
|
||||
@@ -28,9 +30,115 @@
|
||||
)]
|
||||
pub mod msr_index;
|
||||
|
||||
pub mod emulator;
|
||||
|
||||
// MTRR constants
|
||||
pub const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
|
||||
pub const MTRR_MEM_TYPE_WB: u64 = 0x6;
|
||||
|
||||
// IOAPIC pins
|
||||
pub const NUM_IOAPIC_PINS: usize = 24;
|
||||
|
||||
// X86 Exceptions
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Exception {
|
||||
DE = 0, // Divide Error
|
||||
DB = 1, // Debug Exception
|
||||
BP = 3, // Breakpoint
|
||||
OF = 4, // Overflow
|
||||
BR = 5, // BOUND Range Exceeded
|
||||
UD = 6, // Invalid/Undefined Opcode
|
||||
NM = 7, // No Math Coprocessor
|
||||
DF = 8, // Double Fault
|
||||
TS = 10, // Invalid TSS
|
||||
NP = 11, // Segment Not Present
|
||||
SS = 12, // Stack Segment Fault
|
||||
GP = 13, // General Protection
|
||||
PF = 14, // Page Fault
|
||||
MF = 16, // Math Fault
|
||||
AC = 17, // Alignment Check
|
||||
MC = 18, // Machine Check
|
||||
XM = 19, // SIMD Floating-Point Exception
|
||||
VE = 20, // Virtualization Exception
|
||||
CP = 21, // Control Protection Exception
|
||||
}
|
||||
|
||||
pub mod regs;
|
||||
|
||||
// Abstracted segment register ops.
|
||||
// Each x86 hypervisor should implement those.
|
||||
pub trait SegmentRegisterOps {
|
||||
// Segment type
|
||||
fn segment_type(&self) -> u8;
|
||||
fn set_segment_type(&mut self, val: u8);
|
||||
|
||||
// Descriptor Privilege Level (DPL)
|
||||
fn dpl(&self) -> u8;
|
||||
fn set_dpl(&mut self, val: u8);
|
||||
|
||||
// Granularity
|
||||
fn granularity(&self) -> u8;
|
||||
fn set_granularity(&mut self, val: u8);
|
||||
|
||||
// Memory Presence
|
||||
fn present(&self) -> u8;
|
||||
fn set_present(&mut self, val: u8);
|
||||
|
||||
// Long mode
|
||||
fn long(&self) -> u8;
|
||||
fn set_long(&mut self, val: u8);
|
||||
|
||||
// Available for system use (AVL)
|
||||
fn avl(&self) -> u8;
|
||||
fn set_avl(&mut self, val: u8);
|
||||
|
||||
// Descriptor type (System or code/data)
|
||||
fn desc_type(&self) -> u8;
|
||||
fn set_desc_type(&mut self, val: u8);
|
||||
|
||||
// D/B
|
||||
fn db(&self) -> u8;
|
||||
fn set_db(&mut self, val: u8);
|
||||
}
|
||||
|
||||
// Code segment
|
||||
pub const CODE_SEGMENT_TYPE: u8 = 0x8;
|
||||
|
||||
// Read/Write or Read/Exec segment
|
||||
pub const RWRX_SEGMENT_TYPE: u8 = 0x2;
|
||||
|
||||
// Expand down segment
|
||||
pub const EXPAND_DOWN_SEGMENT_TYPE: u8 = 0x4;
|
||||
|
||||
pub fn segment_type_code(t: u8) -> bool {
|
||||
t & CODE_SEGMENT_TYPE != 0
|
||||
}
|
||||
|
||||
pub fn segment_type_ro(t: u8) -> bool {
|
||||
t & !RWRX_SEGMENT_TYPE == 0
|
||||
}
|
||||
|
||||
pub fn segment_type_expand_down(t: u8) -> bool {
|
||||
!segment_type_code(t) && (t & EXPAND_DOWN_SEGMENT_TYPE != 0)
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! msr {
|
||||
($msr:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! msr_data {
|
||||
($msr:expr, $data:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: $data,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
32
hypervisor/src/arch/x86/regs.rs
Normal file
32
hypervisor/src/arch/x86/regs.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright © 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
// EFER (technically not a register) bits
|
||||
pub const EFER_LMA: u64 = 0x400;
|
||||
pub const EFER_LME: u64 = 0x100;
|
||||
|
||||
// CR0 bits
|
||||
pub const CR0_PE: u64 = 0x1;
|
||||
pub const CR0_PG: u64 = 0x80000000;
|
||||
|
||||
// CR4 bits
|
||||
pub const CR4_PAE: u64 = 0x20;
|
||||
pub const CR4_LA57: u64 = 0x1000;
|
||||
|
||||
// RFlags bits
|
||||
pub const CF_SHIFT: usize = 0;
|
||||
pub const PF_SHIFT: usize = 2;
|
||||
pub const AF_SHIFT: usize = 4;
|
||||
pub const ZF_SHIFT: usize = 6;
|
||||
pub const SF_SHIFT: usize = 7;
|
||||
pub const OF_SHIFT: usize = 11;
|
||||
|
||||
pub const CF: u64 = 1 << CF_SHIFT;
|
||||
pub const PF: u64 = 1 << PF_SHIFT;
|
||||
pub const AF: u64 = 1 << AF_SHIFT;
|
||||
pub const ZF: u64 = 1 << ZF_SHIFT;
|
||||
pub const SF: u64 = 1 << SF_SHIFT;
|
||||
pub const OF: u64 = 1 << OF_SHIFT;
|
||||
@@ -10,13 +10,19 @@
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::aarch64::VcpuInit;
|
||||
use crate::{CpuState, MpState};
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::aarch64::{RegList, Register, StandardRegisters};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::{CpuId, LapicState};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::{
|
||||
CpuId, ExtendedControlRegisters, FpuState, LapicState, MsrEntries, SpecialRegisters,
|
||||
StandardRegisters, VcpuEvents, Xsave,
|
||||
ExtendedControlRegisters, FpuState, MsrEntries, SpecialRegisters, StandardRegisters, VcpuEvents,
|
||||
};
|
||||
use crate::CpuState;
|
||||
#[cfg(feature = "kvm")]
|
||||
use crate::MpState;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::Xsave;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -137,22 +143,61 @@ pub enum HypervisorCpuError {
|
||||
/// Setting one reg error
|
||||
///
|
||||
#[error("Failed to init vcpu: {0}")]
|
||||
SetOneReg(#[source] anyhow::Error),
|
||||
SetRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting one reg error
|
||||
///
|
||||
#[error("Failed to init vcpu: {0}")]
|
||||
GetOneReg(#[source] anyhow::Error),
|
||||
GetRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting guest clock paused error
|
||||
///
|
||||
#[error("Failed to notify guest its clock was paused: {0}")]
|
||||
NotifyGuestClockPaused(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting debug register error
|
||||
///
|
||||
#[error("Failed to set debug registers: {0}")]
|
||||
SetDebugRegs(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting debug register error
|
||||
///
|
||||
#[error("Failed to get debug registers: {0}")]
|
||||
GetDebugRegs(#[source] anyhow::Error),
|
||||
///
|
||||
/// Write to Guest Mem
|
||||
///
|
||||
#[error("Failed to write to Guest Mem at: {0}")]
|
||||
GuestMemWrite(#[source] anyhow::Error),
|
||||
/// Enabling HyperV SynIC error
|
||||
///
|
||||
#[error("Failed to enable HyperV SynIC")]
|
||||
EnableHyperVSynIC(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting AArch64 core register error
|
||||
///
|
||||
#[error("Failed to get core register: {0}")]
|
||||
GetCoreRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting AArch64 core register error
|
||||
///
|
||||
#[error("Failed to set core register: {0}")]
|
||||
SetCoreRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting AArch64 registers list error
|
||||
///
|
||||
#[error("Failed to retrieve list of registers: {0}")]
|
||||
GetRegList(#[source] anyhow::Error),
|
||||
///
|
||||
/// Getting AArch64 system register error
|
||||
///
|
||||
#[error("Failed to get system register: {0}")]
|
||||
GetSysRegister(#[source] anyhow::Error),
|
||||
///
|
||||
/// Setting AArch64 system register error
|
||||
///
|
||||
#[error("Failed to set system register: {0}")]
|
||||
SetSysRegister(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -167,6 +212,7 @@ pub enum VmExit<'a> {
|
||||
MmioWrite(u64 /* address */, &'a [u8]),
|
||||
Ignore,
|
||||
Reset,
|
||||
Shutdown,
|
||||
Hyperv,
|
||||
}
|
||||
|
||||
@@ -244,10 +290,12 @@ pub trait Vcpu: Send + Sync {
|
||||
/// Setup the model-specific registers (MSR) for this vCPU.
|
||||
///
|
||||
fn set_msrs(&self, msrs: &MsrEntries) -> Result<usize>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Returns the vcpu's current "multiprocessing state".
|
||||
///
|
||||
fn get_mp_state(&self) -> Result<MpState>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Sets the vcpu's current "multiprocessing state".
|
||||
///
|
||||
@@ -284,7 +332,7 @@ pub trait Vcpu: Send + Sync {
|
||||
/// of the vcpu.
|
||||
///
|
||||
fn set_vcpu_events(&self, events: &VcpuEvents) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
///
|
||||
/// Let the guest know that it has been paused, which prevents from
|
||||
/// potential soft lockups when being resumed.
|
||||
@@ -299,12 +347,43 @@ pub trait Vcpu: Send + Sync {
|
||||
/// Sets the value of one register for this vCPU.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn set_one_reg(&self, reg_id: u64, data: u64) -> Result<()>;
|
||||
fn set_reg(&self, reg_id: u64, data: u64) -> Result<()>;
|
||||
///
|
||||
/// Sets the value of one register for this vCPU.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn get_one_reg(&self, reg_id: u64) -> Result<u64>;
|
||||
fn get_reg(&self, reg_id: u64) -> Result<u64>;
|
||||
///
|
||||
/// Gets a list of the guest registers that are supported for the
|
||||
/// KVM_GET_ONE_REG/KVM_SET_ONE_REG calls.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn get_reg_list(&self, reg_list: &mut RegList) -> Result<()>;
|
||||
///
|
||||
/// Save the state of the core registers.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn core_registers(&self, state: &mut StandardRegisters) -> Result<()>;
|
||||
///
|
||||
/// Restore the state of the core registers.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn set_core_registers(&self, state: &StandardRegisters) -> Result<()>;
|
||||
///
|
||||
/// Save the state of the system registers.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn system_registers(&self, state: &mut Vec<Register>) -> Result<()>;
|
||||
///
|
||||
/// Restore the state of the system registers.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn set_system_registers(&self, state: &[Register]) -> Result<()>;
|
||||
///
|
||||
/// Read the MPIDR - Multiprocessor Affinity Register.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn read_mpidr(&self) -> Result<u64>;
|
||||
///
|
||||
/// Retrieve the vCPU state.
|
||||
/// This function is necessary to snapshot the VM
|
||||
@@ -315,7 +394,6 @@ pub trait Vcpu: Send + Sync {
|
||||
/// This function is required when restoring the VM
|
||||
///
|
||||
fn set_state(&self, state: &CpuState) -> Result<()>;
|
||||
|
||||
///
|
||||
/// Triggers the running of the current virtual CPU returning an exit reason.
|
||||
///
|
||||
|
||||
@@ -22,6 +22,11 @@ pub enum HypervisorDeviceError {
|
||||
///
|
||||
#[error("Failed to set device attribute: {0}")]
|
||||
SetDeviceAttribute(#[source] anyhow::Error),
|
||||
///
|
||||
/// Get device attribute error
|
||||
///
|
||||
#[error("Failed to get device attribute: {0}")]
|
||||
GetDeviceAttribute(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
///
|
||||
@@ -37,4 +42,6 @@ pub type Result<T> = std::result::Result<T, HypervisorDeviceError>;
|
||||
pub trait Device: Send + Sync + AsRawFd {
|
||||
/// Set device attribute.
|
||||
fn set_device_attr(&self, attr: &DeviceAttr) -> Result<()>;
|
||||
/// Get device attribute.
|
||||
fn get_device_attr(&self, attr: &mut DeviceAttr) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
//
|
||||
use crate::vm::Vm;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::{CpuId, MsrList};
|
||||
use crate::x86_64::CpuId;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::x86_64::MsrList;
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
use kvm_ioctls::Cap;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -20,6 +22,11 @@ use thiserror::Error;
|
||||
///
|
||||
///
|
||||
pub enum HypervisorError {
|
||||
///
|
||||
/// hypervisor creation error
|
||||
///
|
||||
#[error("Failed to create the hypervisor: {0}")]
|
||||
HypervisorCreate(#[source] anyhow::Error),
|
||||
///
|
||||
/// Vm creation failure
|
||||
///
|
||||
@@ -78,19 +85,22 @@ pub trait Hypervisor: Send + Sync {
|
||||
/// Return a hypervisor-agnostic Vm trait object
|
||||
///
|
||||
fn create_vm(&self) -> Result<Arc<dyn Vm>>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Returns the size of the memory mapping required to use the vcpu's structures
|
||||
///
|
||||
fn get_vcpu_mmap_size(&self) -> Result<usize>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Gets the recommended maximum number of VCPUs per VM.
|
||||
///
|
||||
fn get_max_vcpus(&self) -> Result<usize>;
|
||||
#[cfg(feature = "kvm")]
|
||||
///
|
||||
/// Gets the recommended number of VCPUs per VM.
|
||||
///
|
||||
fn get_nr_vcpus(&self) -> Result<usize>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
///
|
||||
/// Checks if a particular `Cap` is available.
|
||||
///
|
||||
@@ -100,6 +110,7 @@ pub trait Hypervisor: Send + Sync {
|
||||
/// Get the supported CpuID
|
||||
///
|
||||
fn get_cpuid(&self) -> Result<CpuId>;
|
||||
#[cfg(not(feature = "mshv"))]
|
||||
///
|
||||
/// Check particular extensions if any
|
||||
///
|
||||
|
||||
@@ -12,16 +12,134 @@
|
||||
/// Export generically-named wrappers of kvm-bindings for Unix-based platforms
|
||||
///
|
||||
use crate::kvm::{KvmError, KvmResult};
|
||||
pub use kvm_bindings::kvm_vcpu_init as VcpuInit;
|
||||
use kvm_bindings::{
|
||||
kvm_mp_state, kvm_one_reg, kvm_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_COPROC_MASK, KVM_REG_ARM_CORE, KVM_REG_SIZE_MASK,
|
||||
KVM_REG_SIZE_U32, KVM_REG_SIZE_U64,
|
||||
};
|
||||
pub use kvm_bindings::{
|
||||
kvm_one_reg as Register, kvm_regs as StandardRegisters, kvm_vcpu_init as VcpuInit, RegList,
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
pub use {kvm_ioctls::Cap, kvm_ioctls::Kvm};
|
||||
|
||||
// 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_export]
|
||||
macro_rules! offset__of {
|
||||
($str:ty, $field:ident) => {
|
||||
unsafe { &(*std::ptr::null::<$str>()).$field as *const _ as usize }
|
||||
};
|
||||
}
|
||||
|
||||
// Get the ID of a core register
|
||||
#[macro_export]
|
||||
macro_rules! arm64_core_reg_id {
|
||||
($size: tt, $offset: tt) => {
|
||||
// The core registers of an arm64 machine are represented
|
||||
// in kernel by the `kvm_regs` structure. This structure is a
|
||||
// mix of 32, 64 and 128 bit fields:
|
||||
// struct kvm_regs {
|
||||
// struct user_pt_regs regs;
|
||||
//
|
||||
// __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;
|
||||
// };
|
||||
// The id of a core register can be obtained like this:
|
||||
// offset = id & ~(KVM_REG_ARCH_MASK | KVM_REG_SIZE_MASK | KVM_REG_ARM_CORE). Thus,
|
||||
// id = KVM_REG_ARM64 | KVM_REG_SIZE_U64/KVM_REG_SIZE_U32/KVM_REG_SIZE_U128 | KVM_REG_ARM_CORE | offset
|
||||
KVM_REG_ARM64 as u64
|
||||
| u64::from(KVM_REG_ARM_CORE)
|
||||
| $size
|
||||
| (($offset / 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_export]
|
||||
macro_rules! arm64_sys_reg {
|
||||
($name: tt, $op0: tt, $op1: tt, $crn: tt, $crm: tt, $op2: tt) => {
|
||||
pub 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);
|
||||
|
||||
/// Specifies whether a particular register is a system register or not.
|
||||
/// The kernel splits the registers on aarch64 in core registers and system registers.
|
||||
/// So, below we get the system registers by checking that they are not core registers.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `regid` - The index of the register we are checking.
|
||||
pub fn is_system_register(regid: u64) -> bool {
|
||||
if (regid & KVM_REG_ARM_COPROC_MASK as u64) == KVM_REG_ARM_CORE as u64 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let size = regid & KVM_REG_SIZE_MASK;
|
||||
if size != KVM_REG_SIZE_U32 && size != KVM_REG_SIZE_U64 {
|
||||
panic!("Unexpected register size for system register {}", size);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn check_required_kvm_extensions(kvm: &Kvm) -> KvmResult<()> {
|
||||
if !kvm.check_extension(Cap::SignalMsi) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::SignalMsi));
|
||||
}
|
||||
if !kvm.check_extension(Cap::OneReg) {
|
||||
return Err(KvmError::CapabilityMissing(Cap::OneReg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct VcpuKvmState {}
|
||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||
pub struct VcpuKvmState {
|
||||
pub mp_state: kvm_mp_state,
|
||||
pub core_regs: kvm_regs,
|
||||
pub sys_regs: Vec<kvm_one_reg>,
|
||||
// We will be using the mpidr for passing it to the VmState.
|
||||
// The VmState will give this away for saving restoring the icc and redistributor
|
||||
// registers.
|
||||
pub mpidr: u64,
|
||||
}
|
||||
|
||||
@@ -9,15 +9,22 @@
|
||||
//
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub use crate::aarch64::{check_required_kvm_extensions, VcpuInit, VcpuKvmState as CpuState};
|
||||
pub use crate::aarch64::{
|
||||
check_required_kvm_extensions, is_system_register, VcpuInit, VcpuKvmState as CpuState,
|
||||
MPIDR_EL1,
|
||||
};
|
||||
use crate::cpu;
|
||||
use crate::device;
|
||||
use crate::hypervisor;
|
||||
use crate::vm;
|
||||
use crate::vm::{self, VmmOps};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use crate::{arm64_core_reg_id, offset__of};
|
||||
use kvm_ioctls::{NoDatamatch, VcpuFd, VmFd};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::result;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use vm_memory::Address;
|
||||
@@ -31,6 +38,9 @@ use x86_64::{
|
||||
check_required_kvm_extensions, FpuState, SpecialRegisters, StandardRegisters, KVM_TSS_ADDRESS,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use aarch64::{RegList, Register, StandardRegisters};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::{
|
||||
CpuId, CpuIdEntry, ExtendedControlRegisters, LapicState, MsrEntries, VcpuKvmState as CpuState,
|
||||
@@ -38,7 +48,9 @@ pub use x86_64::{
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use kvm_bindings::{kvm_enable_cap, MsrList, KVM_CAP_HYPERV_SYNIC, KVM_CAP_SPLIT_IRQCHIP};
|
||||
use kvm_bindings::{
|
||||
kvm_enable_cap, kvm_msr_entry, MsrList, KVM_CAP_HYPERV_SYNIC, KVM_CAP_SPLIT_IRQCHIP,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::NUM_IOAPIC_PINS;
|
||||
@@ -46,11 +58,19 @@ use crate::arch::x86::NUM_IOAPIC_PINS;
|
||||
// aarch64 dependencies
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod aarch64;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use kvm_bindings::{
|
||||
kvm_regs, user_fpsimd_state, user_pt_regs, KVM_NR_SPSR, KVM_REG_ARM64, KVM_REG_ARM_CORE,
|
||||
KVM_REG_SIZE_U128, KVM_REG_SIZE_U32, KVM_REG_SIZE_U64,
|
||||
};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use std::mem;
|
||||
|
||||
pub use kvm_bindings;
|
||||
pub use kvm_bindings::{
|
||||
kvm_create_device, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_irq_routing, kvm_irq_routing_entry,
|
||||
kvm_userspace_memory_region, KVM_IRQ_ROUTING_MSI, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID,
|
||||
kvm_userspace_memory_region, KVM_IRQ_ROUTING_MSI, KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY,
|
||||
KVM_MSI_VALID_DEVID,
|
||||
};
|
||||
pub use kvm_ioctls;
|
||||
pub use kvm_ioctls::{Cap, Kvm};
|
||||
@@ -155,15 +175,22 @@ impl vm::Vm for KvmVm {
|
||||
///
|
||||
/// Creates a VcpuFd object from a vcpu RawFd.
|
||||
///
|
||||
fn create_vcpu(&self, id: u8) -> vm::Result<Arc<dyn cpu::Vcpu>> {
|
||||
fn create_vcpu(
|
||||
&self,
|
||||
id: u8,
|
||||
vmmops: Option<Arc<Box<dyn VmmOps>>>,
|
||||
) -> vm::Result<Arc<dyn cpu::Vcpu>> {
|
||||
let vc = self
|
||||
.fd
|
||||
.create_vcpu(id)
|
||||
.create_vcpu(id as u64)
|
||||
.map_err(|e| vm::HypervisorVmError::CreateVcpu(e.into()))?;
|
||||
let vcpu = KvmVcpu {
|
||||
fd: vc,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
msrs: self.msrs.clone(),
|
||||
vmmops,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
hyperv_synic: AtomicBool::new(false),
|
||||
};
|
||||
Ok(Arc::new(vcpu))
|
||||
}
|
||||
@@ -231,13 +258,19 @@ impl vm::Vm for KvmVm {
|
||||
memory_size: u64,
|
||||
userspace_addr: u64,
|
||||
readonly: bool,
|
||||
log_dirty_pages: bool,
|
||||
) -> MemoryRegion {
|
||||
MemoryRegion {
|
||||
slot,
|
||||
guest_phys_addr,
|
||||
memory_size,
|
||||
userspace_addr,
|
||||
flags: if readonly { KVM_MEM_READONLY } else { 0 },
|
||||
flags: if readonly { KVM_MEM_READONLY } else { 0 }
|
||||
| if log_dirty_pages {
|
||||
KVM_MEM_LOG_DIRTY_PAGES
|
||||
} else {
|
||||
0
|
||||
},
|
||||
}
|
||||
}
|
||||
///
|
||||
@@ -281,8 +314,10 @@ impl vm::Vm for KvmVm {
|
||||
// Create split irqchip
|
||||
// Only the local APIC is emulated in kernel, both PICs and IOAPIC
|
||||
// are not.
|
||||
let mut cap: kvm_enable_cap = Default::default();
|
||||
cap.cap = KVM_CAP_SPLIT_IRQCHIP;
|
||||
let mut cap = kvm_enable_cap {
|
||||
cap: KVM_CAP_SPLIT_IRQCHIP,
|
||||
..Default::default()
|
||||
};
|
||||
cap.args[0] = NUM_IOAPIC_PINS as u64;
|
||||
self.fd
|
||||
.enable_cap(&cap)
|
||||
@@ -327,9 +362,18 @@ impl vm::Vm for KvmVm {
|
||||
///
|
||||
/// Set the VM state
|
||||
///
|
||||
fn set_state(&self, _state: &VmState) -> vm::Result<()> {
|
||||
fn set_state(&self, _state: VmState) -> vm::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Get dirty pages bitmap (one bit per page)
|
||||
///
|
||||
fn get_dirty_log(&self, slot: u32, memory_size: u64) -> vm::Result<Vec<u64>> {
|
||||
self.fd
|
||||
.get_dirty_log(slot, memory_size as usize)
|
||||
.map_err(|e| vm::HypervisorVmError::GetDirtyLog(e.into()))
|
||||
}
|
||||
}
|
||||
/// Wrapper over KVM system ioctls.
|
||||
pub struct KvmHypervisor {
|
||||
@@ -475,6 +519,9 @@ pub struct KvmVcpu {
|
||||
fd: VcpuFd,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
msrs: MsrEntries,
|
||||
vmmops: Option<Arc<Box<dyn vm::VmmOps>>>,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
hyperv_synic: AtomicBool,
|
||||
}
|
||||
/// Implementation of Vcpu trait for KVM
|
||||
/// Example:
|
||||
@@ -483,7 +530,7 @@ pub struct KvmVcpu {
|
||||
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
|
||||
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
/// let vcpu = vm.create_vcpu(0).unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
/// vcpu.get/set().unwrap()
|
||||
///
|
||||
impl cpu::Vcpu for KvmVcpu {
|
||||
@@ -555,8 +602,14 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
/// X86 specific call to enable HyperV SynIC
|
||||
///
|
||||
fn enable_hyperv_synic(&self) -> cpu::Result<()> {
|
||||
let mut cap: kvm_enable_cap = Default::default();
|
||||
cap.cap = KVM_CAP_HYPERV_SYNIC;
|
||||
// Update the information about Hyper-V SynIC being enabled and
|
||||
// emulated as it will influence later which MSRs should be saved.
|
||||
self.hyperv_synic.store(true, Ordering::Release);
|
||||
|
||||
let cap = kvm_enable_cap {
|
||||
cap: KVM_CAP_HYPERV_SYNIC,
|
||||
..Default::default()
|
||||
};
|
||||
self.fd
|
||||
.enable_cap(&cap)
|
||||
.map_err(|e| cpu::HypervisorCpuError::EnableHyperVSynIC(e.into()))
|
||||
@@ -666,9 +719,27 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
match self.fd.run() {
|
||||
Ok(run) => match run {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
VcpuExit::IoIn(addr, data) => Ok(cpu::VmExit::IoIn(addr, data)),
|
||||
VcpuExit::IoIn(addr, data) => {
|
||||
if let Some(vmmops) = &self.vmmops {
|
||||
return vmmops
|
||||
.pio_read(addr.into(), data)
|
||||
.map(|_| cpu::VmExit::Ignore)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()));
|
||||
}
|
||||
|
||||
Ok(cpu::VmExit::IoIn(addr, data))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
VcpuExit::IoOut(addr, data) => Ok(cpu::VmExit::IoOut(addr, data)),
|
||||
VcpuExit::IoOut(addr, data) => {
|
||||
if let Some(vmmops) = &self.vmmops {
|
||||
return vmmops
|
||||
.pio_write(addr.into(), data)
|
||||
.map(|_| cpu::VmExit::Ignore)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()));
|
||||
}
|
||||
|
||||
Ok(cpu::VmExit::IoOut(addr, data))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
VcpuExit::IoapicEoi(vector) => Ok(cpu::VmExit::IoapicEoi(vector)),
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -676,11 +747,13 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
VcpuExit::SystemEvent(event_type, flags) => {
|
||||
use kvm_bindings::KVM_SYSTEM_EVENT_SHUTDOWN;
|
||||
use kvm_bindings::{KVM_SYSTEM_EVENT_RESET, KVM_SYSTEM_EVENT_SHUTDOWN};
|
||||
// On Aarch64, when the VM is shutdown, run() returns
|
||||
// VcpuExit::SystemEvent with reason KVM_SYSTEM_EVENT_SHUTDOWN
|
||||
if event_type == KVM_SYSTEM_EVENT_SHUTDOWN {
|
||||
if event_type == KVM_SYSTEM_EVENT_RESET {
|
||||
Ok(cpu::VmExit::Reset)
|
||||
} else if event_type == KVM_SYSTEM_EVENT_SHUTDOWN {
|
||||
Ok(cpu::VmExit::Shutdown)
|
||||
} else {
|
||||
Err(cpu::HypervisorCpuError::RunVcpu(anyhow!(
|
||||
"Unexpected system event with type 0x{:x}, flags 0x{:x}",
|
||||
@@ -690,8 +763,26 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
}
|
||||
}
|
||||
|
||||
VcpuExit::MmioRead(addr, data) => Ok(cpu::VmExit::MmioRead(addr, data)),
|
||||
VcpuExit::MmioWrite(addr, data) => Ok(cpu::VmExit::MmioWrite(addr, data)),
|
||||
VcpuExit::MmioRead(addr, data) => {
|
||||
if let Some(vmmops) = &self.vmmops {
|
||||
return vmmops
|
||||
.mmio_read(addr, data)
|
||||
.map(|_| cpu::VmExit::Ignore)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()));
|
||||
}
|
||||
|
||||
Ok(cpu::VmExit::MmioRead(addr, data))
|
||||
}
|
||||
VcpuExit::MmioWrite(addr, data) => {
|
||||
if let Some(vmmops) = &self.vmmops {
|
||||
return vmmops
|
||||
.mmio_write(addr, data)
|
||||
.map(|_| cpu::VmExit::Ignore)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()));
|
||||
}
|
||||
|
||||
Ok(cpu::VmExit::MmioWrite(addr, data))
|
||||
}
|
||||
VcpuExit::Hyperv => Ok(cpu::VmExit::Hyperv),
|
||||
|
||||
r => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!(
|
||||
@@ -749,19 +840,263 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
/// Sets the value of one register for this vCPU.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn set_one_reg(&self, reg_id: u64, data: u64) -> cpu::Result<()> {
|
||||
fn set_reg(&self, reg_id: u64, data: u64) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_one_reg(reg_id, data)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetOneReg(e.into()))
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetRegister(e.into()))
|
||||
}
|
||||
///
|
||||
/// Gets the value of one register for this vCPU.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn get_one_reg(&self, reg_id: u64) -> cpu::Result<u64> {
|
||||
fn get_reg(&self, reg_id: u64) -> cpu::Result<u64> {
|
||||
self.fd
|
||||
.get_one_reg(reg_id)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetOneReg(e.into()))
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetRegister(e.into()))
|
||||
}
|
||||
///
|
||||
/// Gets a list of the guest registers that are supported for the
|
||||
/// KVM_GET_ONE_REG/KVM_SET_ONE_REG calls.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn get_reg_list(&self, reg_list: &mut RegList) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.get_reg_list(reg_list)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetRegList(e.into()))
|
||||
}
|
||||
///
|
||||
/// Save the state of the core registers.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn core_registers(&self, state: &mut StandardRegisters) -> cpu::Result<()> {
|
||||
let mut off = offset__of!(user_pt_regs, regs);
|
||||
// There are 31 user_pt_regs:
|
||||
// https://elixir.free-electrons.com/linux/v4.14.174/source/arch/arm64/include/uapi/asm/ptrace.h#L72
|
||||
// These actually are the general-purpose registers of the Armv8-a
|
||||
// architecture (i.e x0-x30 if used as a 64bit register or w0-30 when used as a 32bit register).
|
||||
for i in 0..31 {
|
||||
state.regs.regs[i] = self
|
||||
.fd
|
||||
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetCoreRegister(e.into()))?;
|
||||
off += std::mem::size_of::<u64>();
|
||||
}
|
||||
|
||||
// We are now entering the "Other register" section of the ARMv8-a architecture.
|
||||
// First one, stack pointer.
|
||||
let off = offset__of!(user_pt_regs, sp);
|
||||
state.regs.sp = self
|
||||
.fd
|
||||
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetCoreRegister(e.into()))?;
|
||||
|
||||
// Second one, the program counter.
|
||||
let off = offset__of!(user_pt_regs, pc);
|
||||
state.regs.pc = self
|
||||
.fd
|
||||
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetCoreRegister(e.into()))?;
|
||||
|
||||
// Next is the processor state.
|
||||
let off = offset__of!(user_pt_regs, pstate);
|
||||
state.regs.pstate = self
|
||||
.fd
|
||||
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetCoreRegister(e.into()))?;
|
||||
|
||||
// The stack pointer associated with EL1
|
||||
let off = offset__of!(kvm_regs, sp_el1);
|
||||
state.sp_el1 = self
|
||||
.fd
|
||||
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetCoreRegister(e.into()))?;
|
||||
|
||||
// Exception Link Register for EL1, when taking an exception to EL1, this register
|
||||
// holds the address to which to return afterwards.
|
||||
let off = offset__of!(kvm_regs, elr_el1);
|
||||
state.elr_el1 = self
|
||||
.fd
|
||||
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetCoreRegister(e.into()))?;
|
||||
|
||||
// Saved Program Status Registers, there are 5 of them used in the kernel.
|
||||
let mut off = offset__of!(kvm_regs, spsr);
|
||||
for i in 0..KVM_NR_SPSR as usize {
|
||||
state.spsr[i] = self
|
||||
.fd
|
||||
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off))
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetCoreRegister(e.into()))?;
|
||||
off += std::mem::size_of::<u64>();
|
||||
}
|
||||
|
||||
// Now moving on to floting point registers which are stored in the user_fpsimd_state in the kernel:
|
||||
// https://elixir.free-electrons.com/linux/v4.9.62/source/arch/arm64/include/uapi/asm/kvm.h#L53
|
||||
let mut off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, vregs);
|
||||
for i in 0..32 {
|
||||
state.fp_regs.vregs[i][0] = self
|
||||
.fd
|
||||
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U128, off))
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetCoreRegister(e.into()))?;
|
||||
off += mem::size_of::<u128>();
|
||||
}
|
||||
|
||||
// Floating-point Status Register
|
||||
let off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, fpsr);
|
||||
state.fp_regs.fpsr = self
|
||||
.fd
|
||||
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U32, off))
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetCoreRegister(e.into()))?
|
||||
as u32;
|
||||
|
||||
// Floating-point Control Register
|
||||
let off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, fpcr);
|
||||
state.fp_regs.fpcr = self
|
||||
.fd
|
||||
.get_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U32, off))
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetCoreRegister(e.into()))?
|
||||
as u32;
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
/// Restore the state of the core registers.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn set_core_registers(&self, state: &StandardRegisters) -> cpu::Result<()> {
|
||||
// The function follows the exact identical order from `state`. Look there
|
||||
// for some additional info on registers.
|
||||
let mut off = offset__of!(user_pt_regs, regs);
|
||||
for i in 0..31 {
|
||||
self.fd
|
||||
.set_one_reg(
|
||||
arm64_core_reg_id!(KVM_REG_SIZE_U64, off),
|
||||
state.regs.regs[i],
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
|
||||
off += std::mem::size_of::<u64>();
|
||||
}
|
||||
|
||||
let off = offset__of!(user_pt_regs, sp);
|
||||
self.fd
|
||||
.set_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), state.regs.sp)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
|
||||
|
||||
let off = offset__of!(user_pt_regs, pc);
|
||||
self.fd
|
||||
.set_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), state.regs.pc)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
|
||||
|
||||
let off = offset__of!(user_pt_regs, pstate);
|
||||
self.fd
|
||||
.set_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), state.regs.pstate)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
|
||||
|
||||
let off = offset__of!(kvm_regs, sp_el1);
|
||||
self.fd
|
||||
.set_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), state.sp_el1)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
|
||||
|
||||
let off = offset__of!(kvm_regs, elr_el1);
|
||||
self.fd
|
||||
.set_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), state.elr_el1)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
|
||||
|
||||
let mut off = offset__of!(kvm_regs, spsr);
|
||||
for i in 0..KVM_NR_SPSR as usize {
|
||||
self.fd
|
||||
.set_one_reg(arm64_core_reg_id!(KVM_REG_SIZE_U64, off), state.spsr[i])
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
|
||||
off += std::mem::size_of::<u64>();
|
||||
}
|
||||
|
||||
let mut off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, vregs);
|
||||
for i in 0..32 {
|
||||
self.fd
|
||||
.set_one_reg(
|
||||
arm64_core_reg_id!(KVM_REG_SIZE_U128, off),
|
||||
state.fp_regs.vregs[i][0],
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
|
||||
off += mem::size_of::<u128>();
|
||||
}
|
||||
|
||||
let off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, fpsr);
|
||||
self.fd
|
||||
.set_one_reg(
|
||||
arm64_core_reg_id!(KVM_REG_SIZE_U32, off),
|
||||
state.fp_regs.fpsr as u64,
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
|
||||
|
||||
let off = offset__of!(kvm_regs, fp_regs) + offset__of!(user_fpsimd_state, fpcr);
|
||||
self.fd
|
||||
.set_one_reg(
|
||||
arm64_core_reg_id!(KVM_REG_SIZE_U32, off),
|
||||
state.fp_regs.fpcr as u64,
|
||||
)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetCoreRegister(e.into()))?;
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
/// Save the state of the system registers.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn system_registers(&self, state: &mut Vec<Register>) -> cpu::Result<()> {
|
||||
// Call KVM_GET_REG_LIST to get all registers available to the guest. For ArmV8 there are
|
||||
// around 500 registers.
|
||||
let mut reg_list = RegList::new(512);
|
||||
self.fd
|
||||
.get_reg_list(&mut reg_list)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetRegList(e.into()))?;
|
||||
|
||||
// At this point reg_list should contain: core registers and system registers.
|
||||
// The register list contains the number of registers and their ids. We will be needing to
|
||||
// call KVM_GET_ONE_REG on each id in order to save all of them. We carve out from the list
|
||||
// the core registers which are represented in the kernel by kvm_regs structure and for which
|
||||
// we can calculate the id based on the offset in the structure.
|
||||
|
||||
reg_list.retain(|regid| *regid != 0);
|
||||
reg_list.as_slice().to_vec().sort_unstable();
|
||||
|
||||
reg_list.retain(|regid| is_system_register(*regid));
|
||||
|
||||
// Now, for the rest of the registers left in the previously fetched register list, we are
|
||||
// simply calling KVM_GET_ONE_REG.
|
||||
let indices = reg_list.as_slice();
|
||||
for (_pos, index) in indices.iter().enumerate() {
|
||||
if _pos > 230 {
|
||||
break;
|
||||
}
|
||||
state.push(kvm_bindings::kvm_one_reg {
|
||||
id: *index,
|
||||
addr: self
|
||||
.fd
|
||||
.get_one_reg(*index)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetSysRegister(e.into()))?,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
/// Restore the state of the system registers.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn set_system_registers(&self, state: &[Register]) -> cpu::Result<()> {
|
||||
for reg in state {
|
||||
self.fd
|
||||
.set_one_reg(reg.id, reg.addr)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetSysRegister(e.into()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
/// Read the MPIDR - Multiprocessor Affinity Register.
|
||||
///
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
fn read_mpidr(&self) -> cpu::Result<u64> {
|
||||
self.fd
|
||||
.get_one_reg(MPIDR_EL1)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetSysRegister(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
@@ -798,10 +1133,11 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
/// let hv: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
|
||||
/// let vm = hv.create_vm().expect("new VM fd creation failed");
|
||||
/// vm.enable_split_irq().unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0).unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
/// let state = vcpu.state().unwrap();
|
||||
/// ```
|
||||
fn state(&self) -> cpu::Result<CpuState> {
|
||||
let cpuid = self.get_cpuid2(kvm_bindings::KVM_MAX_CPUID_ENTRIES)?;
|
||||
let mp_state = self.get_mp_state()?;
|
||||
let regs = self.get_regs()?;
|
||||
let sregs = self.get_sregs()?;
|
||||
@@ -816,6 +1152,26 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
// by chunks. This is the only way to make sure we try to get as many
|
||||
// MSRs as possible, even if some MSRs are not supported.
|
||||
let mut msr_entries = self.msrs.clone();
|
||||
|
||||
// Save extra MSRs if the Hyper-V synthetic interrupt controller is
|
||||
// emulated.
|
||||
if self.hyperv_synic.load(Ordering::Acquire) {
|
||||
let hyperv_synic_msrs = vec![
|
||||
0x40000020, 0x40000021, 0x40000080, 0x40000081, 0x40000082, 0x40000083, 0x40000084,
|
||||
0x40000090, 0x40000091, 0x40000092, 0x40000093, 0x40000094, 0x40000095, 0x40000096,
|
||||
0x40000097, 0x40000098, 0x40000099, 0x4000009a, 0x4000009b, 0x4000009c, 0x4000009d,
|
||||
0x4000009f, 0x400000b0, 0x400000b1, 0x400000b2, 0x400000b3, 0x400000b4, 0x400000b5,
|
||||
0x400000b6, 0x400000b7,
|
||||
];
|
||||
for index in hyperv_synic_msrs {
|
||||
let msr = kvm_msr_entry {
|
||||
index,
|
||||
..Default::default()
|
||||
};
|
||||
msr_entries.push(msr).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let expected_num_msrs = msr_entries.as_fam_struct_ref().nmsrs as usize;
|
||||
let num_msrs = self.get_msrs(&mut msr_entries)?;
|
||||
let msrs = if num_msrs != expected_num_msrs {
|
||||
@@ -861,6 +1217,7 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
let vcpu_events = self.get_vcpu_events()?;
|
||||
|
||||
Ok(CpuState {
|
||||
cpuid,
|
||||
msrs,
|
||||
vcpu_events,
|
||||
regs,
|
||||
@@ -872,9 +1229,20 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
mp_state,
|
||||
})
|
||||
}
|
||||
///
|
||||
/// Get the current AArch64 CPU state
|
||||
///
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn state(&self) -> cpu::Result<CpuState> {
|
||||
unimplemented!();
|
||||
let mut state = CpuState {
|
||||
mp_state: self.get_mp_state()?,
|
||||
mpidr: self.read_mpidr()?,
|
||||
..Default::default()
|
||||
};
|
||||
self.core_registers(&mut state.core_regs)?;
|
||||
self.system_registers(&mut state.sys_regs)?;
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
@@ -912,11 +1280,12 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
/// let hv: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
|
||||
/// let vm = hv.create_vm().expect("new VM fd creation failed");
|
||||
/// vm.enable_split_irq().unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0).unwrap();
|
||||
/// let vcpu = vm.create_vcpu(0, None).unwrap();
|
||||
/// let state = vcpu.state().unwrap();
|
||||
/// vcpu.set_state(&state).unwrap();
|
||||
/// ```
|
||||
fn set_state(&self, state: &CpuState) -> cpu::Result<()> {
|
||||
self.set_cpuid2(&state.cpuid)?;
|
||||
self.set_mp_state(state.mp_state)?;
|
||||
self.set_regs(&state.regs)?;
|
||||
self.set_sregs(&state.sregs)?;
|
||||
@@ -958,10 +1327,15 @@ impl cpu::Vcpu for KvmVcpu {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
///
|
||||
/// Restore the previously saved AArch64 CPU state
|
||||
///
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn set_state(&self, state: &CpuState) -> cpu::Result<()> {
|
||||
warn!("CPU state was not restored");
|
||||
self.set_core_registers(&state.core_regs)?;
|
||||
self.set_system_registers(&state.sys_regs)?;
|
||||
self.set_mp_state(state.mp_state)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -980,6 +1354,14 @@ impl device::Device for KvmDevice {
|
||||
.set_device_attr(attr)
|
||||
.map_err(|e| device::HypervisorDeviceError::SetDeviceAttribute(e.into()))
|
||||
}
|
||||
///
|
||||
/// Get device attribute
|
||||
///
|
||||
fn get_device_attr(&self, attr: &mut DeviceAttr) -> device::Result<()> {
|
||||
self.fd
|
||||
.get_device_attr(attr)
|
||||
.map_err(|e| device::HypervisorDeviceError::GetDeviceAttribute(e.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for KvmDevice {
|
||||
|
||||
@@ -8,11 +8,10 @@
|
||||
//
|
||||
//
|
||||
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
use crate::arch::x86::{msr_index, MTRR_ENABLE, MTRR_MEM_TYPE_WB};
|
||||
use crate::arch::x86::{msr_index, SegmentRegisterOps, MTRR_ENABLE, MTRR_MEM_TYPE_WB};
|
||||
use crate::kvm::{Cap, Kvm, KvmError, KvmResult};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
///
|
||||
/// Export generically-named wrappers of kvm-bindings for Unix-based platforms
|
||||
@@ -28,27 +27,73 @@ pub use {
|
||||
kvm_bindings::KVM_CPUID_FLAG_SIGNIFCANT_INDEX as CPUID_FLAG_VALID_INDEX,
|
||||
};
|
||||
|
||||
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
|
||||
impl SegmentRegisterOps for SegmentRegister {
|
||||
fn segment_type(&self) -> u8 {
|
||||
self.type_
|
||||
}
|
||||
fn set_segment_type(&mut self, val: u8) {
|
||||
self.type_ = val;
|
||||
}
|
||||
|
||||
macro_rules! msr {
|
||||
($msr:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: 0x0,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! msr_data {
|
||||
($msr:expr, $data:expr) => {
|
||||
MsrEntry {
|
||||
index: $msr,
|
||||
data: $data,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
fn dpl(&self) -> u8 {
|
||||
self.dpl
|
||||
}
|
||||
|
||||
fn set_dpl(&mut self, val: u8) {
|
||||
self.dpl = val;
|
||||
}
|
||||
|
||||
fn present(&self) -> u8 {
|
||||
self.present
|
||||
}
|
||||
|
||||
fn set_present(&mut self, val: u8) {
|
||||
self.present = val;
|
||||
}
|
||||
|
||||
fn long(&self) -> u8 {
|
||||
self.l
|
||||
}
|
||||
|
||||
fn set_long(&mut self, val: u8) {
|
||||
self.l = val;
|
||||
}
|
||||
|
||||
fn avl(&self) -> u8 {
|
||||
self.avl
|
||||
}
|
||||
|
||||
fn set_avl(&mut self, val: u8) {
|
||||
self.avl = val;
|
||||
}
|
||||
|
||||
fn desc_type(&self) -> u8 {
|
||||
self.s
|
||||
}
|
||||
|
||||
fn set_desc_type(&mut self, val: u8) {
|
||||
self.s = val;
|
||||
}
|
||||
|
||||
fn granularity(&self) -> u8 {
|
||||
self.g
|
||||
}
|
||||
|
||||
fn set_granularity(&mut self, val: u8) {
|
||||
self.g = val;
|
||||
}
|
||||
|
||||
fn db(&self) -> u8 {
|
||||
self.db
|
||||
}
|
||||
|
||||
fn set_db(&mut self, val: u8) {
|
||||
self.db = val;
|
||||
}
|
||||
}
|
||||
|
||||
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
|
||||
|
||||
pub fn boot_msr_entries() -> MsrEntries {
|
||||
MsrEntries::from_entries(&[
|
||||
msr!(msr_index::MSR_IA32_SYSENTER_CS),
|
||||
@@ -85,6 +130,7 @@ pub fn check_required_kvm_extensions(kvm: &Kvm) -> KvmResult<()> {
|
||||
}
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct VcpuKvmState {
|
||||
pub cpuid: CpuId,
|
||||
pub msrs: MsrEntries,
|
||||
pub vcpu_events: VcpuEvents,
|
||||
pub regs: StandardRegisters,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate serde;
|
||||
@@ -27,18 +28,24 @@ extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate thiserror;
|
||||
|
||||
/// Architecture specific definitions
|
||||
#[macro_use]
|
||||
pub mod arch;
|
||||
|
||||
#[cfg(feature = "kvm")]
|
||||
/// KVM implementation module
|
||||
pub mod kvm;
|
||||
|
||||
/// Microsoft Hypervisor implementation module
|
||||
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
|
||||
pub mod mshv;
|
||||
|
||||
/// Hypevisor related module
|
||||
pub mod hypervisor;
|
||||
|
||||
/// Vm related module
|
||||
pub mod vm;
|
||||
|
||||
/// Architecture specific definitions
|
||||
pub mod arch;
|
||||
|
||||
/// CPU related module
|
||||
mod cpu;
|
||||
|
||||
@@ -48,7 +55,10 @@ mod device;
|
||||
pub use crate::hypervisor::{Hypervisor, HypervisorError};
|
||||
pub use cpu::{HypervisorCpuError, Vcpu, VmExit};
|
||||
pub use device::{Device, HypervisorDeviceError};
|
||||
#[cfg(feature = "kvm")]
|
||||
pub use kvm::*;
|
||||
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
|
||||
pub use mshv::*;
|
||||
pub use vm::{DataMatch, HypervisorVmError, Vm};
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -57,5 +67,8 @@ pub fn new() -> std::result::Result<Arc<dyn Hypervisor>, HypervisorError> {
|
||||
#[cfg(feature = "kvm")]
|
||||
let hv = kvm::KvmHypervisor::new()?;
|
||||
|
||||
#[cfg(feature = "mshv")]
|
||||
let hv = mshv::MshvHypervisor::new()?;
|
||||
|
||||
Ok(Arc::new(hv))
|
||||
}
|
||||
|
||||
946
hypervisor/src/mshv/mod.rs
Normal file
946
hypervisor/src/mshv/mod.rs
Normal file
@@ -0,0 +1,946 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
|
||||
use crate::arch::emulator::{PlatformEmulator, PlatformError};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::x86::emulator::{Emulator, EmulatorCpuState};
|
||||
use crate::cpu;
|
||||
use crate::cpu::Vcpu;
|
||||
use crate::hypervisor;
|
||||
use crate::vm::{self, VmmOps};
|
||||
pub use mshv_bindings::*;
|
||||
pub use mshv_ioctls::IoEventAddress;
|
||||
use mshv_ioctls::{set_registers_64, InterruptRequest, Mshv, NoDatamatch, VcpuFd, VmFd};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use vm::DataMatch;
|
||||
// x86_64 dependencies
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
||||
use crate::device;
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::VcpuMshvState as CpuState;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::sync::RwLock;
|
||||
|
||||
pub const PAGE_SHIFT: usize = 12;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct HvState {
|
||||
hypercall_page: u64,
|
||||
}
|
||||
|
||||
pub use HvState as VmState;
|
||||
|
||||
/// Wrapper over mshv system ioctls.
|
||||
pub struct MshvHypervisor {
|
||||
mshv: Mshv,
|
||||
}
|
||||
|
||||
impl MshvHypervisor {
|
||||
/// Create a hypervisor based on Mshv
|
||||
pub fn new() -> hypervisor::Result<MshvHypervisor> {
|
||||
let mshv_obj =
|
||||
Mshv::new().map_err(|e| hypervisor::HypervisorError::HypervisorCreate(e.into()))?;
|
||||
Ok(MshvHypervisor { mshv: mshv_obj })
|
||||
}
|
||||
}
|
||||
/// Implementation of Hypervisor trait for Mshv
|
||||
/// Example:
|
||||
/// #[cfg(feature = "mshv")]
|
||||
/// extern crate hypervisor
|
||||
/// let mshv = hypervisor::mshv::MshvHypervisor::new().unwrap();
|
||||
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(mshv);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
///
|
||||
impl hypervisor::Hypervisor for MshvHypervisor {
|
||||
/// Create a mshv vm object and return the object as Vm trait object
|
||||
/// Example
|
||||
/// # extern crate hypervisor;
|
||||
/// # use hypervisor::MshvHypervisor;
|
||||
/// use hypervisor::MshvVm;
|
||||
/// let hypervisor = MshvHypervisor::new().unwrap();
|
||||
/// let vm = hypervisor.create_vm().unwrap()
|
||||
///
|
||||
fn create_vm(&self) -> hypervisor::Result<Arc<dyn vm::Vm>> {
|
||||
let fd: VmFd;
|
||||
loop {
|
||||
match self.mshv.create_vm() {
|
||||
Ok(res) => fd = res,
|
||||
Err(e) => {
|
||||
if e.errno() == libc::EINTR {
|
||||
// If the error returned is EINTR, which means the
|
||||
// ioctl has been interrupted, we have to retry as
|
||||
// this can't be considered as a regular error.
|
||||
continue;
|
||||
} else {
|
||||
return Err(hypervisor::HypervisorError::VmCreate(e.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let msr_list = self.get_msr_list()?;
|
||||
let num_msrs = msr_list.as_fam_struct_ref().nmsrs as usize;
|
||||
let mut msrs = MsrEntries::new(num_msrs);
|
||||
let indices = msr_list.as_slice();
|
||||
let msr_entries = msrs.as_mut_slice();
|
||||
for (pos, index) in indices.iter().enumerate() {
|
||||
msr_entries[pos].index = *index;
|
||||
}
|
||||
let vm_fd = Arc::new(fd);
|
||||
|
||||
let gsi_routes = Arc::new(RwLock::new(HashMap::new()));
|
||||
|
||||
Ok(Arc::new(MshvVm {
|
||||
fd: vm_fd,
|
||||
msrs,
|
||||
gsi_routes,
|
||||
hv_state: hv_state_init(),
|
||||
vmmops: None,
|
||||
}))
|
||||
}
|
||||
///
|
||||
/// Get the supported CpuID
|
||||
///
|
||||
fn get_cpuid(&self) -> hypervisor::Result<CpuId> {
|
||||
Ok(CpuId::new(1))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Retrieve the list of MSRs supported by KVM.
|
||||
///
|
||||
fn get_msr_list(&self) -> hypervisor::Result<MsrList> {
|
||||
self.mshv
|
||||
.get_msr_index_list()
|
||||
.map_err(|e| hypervisor::HypervisorError::GetMsrList(e.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
// A software emulated TLB.
|
||||
// This is mostly used by the instruction emulator to cache gva to gpa translations
|
||||
// passed from the hypervisor.
|
||||
struct SoftTLB {
|
||||
addr_map: HashMap<u64, u64>,
|
||||
}
|
||||
|
||||
impl SoftTLB {
|
||||
fn new() -> SoftTLB {
|
||||
SoftTLB {
|
||||
addr_map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a gva -> gpa mapping into the TLB.
|
||||
fn add_mapping(&mut self, gva: u64, gpa: u64) {
|
||||
*self.addr_map.entry(gva).or_insert(gpa) = gpa;
|
||||
}
|
||||
|
||||
// Do the actual gva -> gpa translation
|
||||
fn translate(&self, gva: u64) -> Result<u64, PlatformError> {
|
||||
self.addr_map
|
||||
.get(&gva)
|
||||
.ok_or_else(|| PlatformError::UnmappedGVA(anyhow!("{:#?}", gva)))
|
||||
.map(|v| *v)
|
||||
|
||||
// TODO Check if we could fallback to e.g. an hypercall for doing
|
||||
// the translation for us.
|
||||
}
|
||||
}
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[allow(dead_code)]
|
||||
/// Vcpu struct for Microsoft Hypervisor
|
||||
pub struct MshvVcpu {
|
||||
fd: VcpuFd,
|
||||
vp_index: u8,
|
||||
cpuid: CpuId,
|
||||
msrs: MsrEntries,
|
||||
gsi_routes: Arc<RwLock<HashMap<u32, MshvIrqRoutingEntry>>>,
|
||||
hv_state: Arc<RwLock<HvState>>, // Mshv State
|
||||
vmmops: Option<Arc<Box<dyn vm::VmmOps>>>,
|
||||
}
|
||||
|
||||
/// Implementation of Vcpu trait for Microsoft Hypervisor
|
||||
/// Example:
|
||||
/// #[cfg(feature = "mshv")]
|
||||
/// extern crate hypervisor
|
||||
/// let mshv = hypervisor::mshv::MshvHypervisor::new().unwrap();
|
||||
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(mshv);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
/// let vcpu = vm.create_vcpu(0).unwrap();
|
||||
/// vcpu.get/set().unwrap()
|
||||
///
|
||||
impl cpu::Vcpu for MshvVcpu {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the vCPU general purpose registers.
|
||||
///
|
||||
fn get_regs(&self) -> cpu::Result<StandardRegisters> {
|
||||
self.fd
|
||||
.get_regs()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetStandardRegs(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the vCPU general purpose registers.
|
||||
///
|
||||
fn set_regs(&self, regs: &StandardRegisters) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_regs(regs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetStandardRegs(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the vCPU special registers.
|
||||
///
|
||||
fn get_sregs(&self) -> cpu::Result<SpecialRegisters> {
|
||||
self.fd
|
||||
.get_sregs()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetSpecialRegs(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the vCPU special registers.
|
||||
///
|
||||
fn set_sregs(&self, sregs: &SpecialRegisters) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_sregs(sregs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetSpecialRegs(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the floating point state (FPU) from the vCPU.
|
||||
///
|
||||
fn get_fpu(&self) -> cpu::Result<FpuState> {
|
||||
self.fd
|
||||
.get_fpu()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetFloatingPointRegs(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Set the floating point state (FPU) of a vCPU.
|
||||
///
|
||||
fn set_fpu(&self, fpu: &FpuState) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_fpu(fpu)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetFloatingPointRegs(e.into()))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the model-specific registers (MSR) for this vCPU.
|
||||
///
|
||||
fn get_msrs(&self, msrs: &mut MsrEntries) -> cpu::Result<usize> {
|
||||
self.fd
|
||||
.get_msrs(msrs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetMsrEntries(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Setup the model-specific registers (MSR) for this vCPU.
|
||||
/// Returns the number of MSR entries actually written.
|
||||
///
|
||||
fn set_msrs(&self, msrs: &MsrEntries) -> cpu::Result<usize> {
|
||||
self.fd
|
||||
.set_msrs(msrs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetMsrEntries(e.into()))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that returns the vcpu's current "xcrs".
|
||||
///
|
||||
fn get_xcrs(&self) -> cpu::Result<ExtendedControlRegisters> {
|
||||
self.fd
|
||||
.get_xcrs()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetXcsr(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that sets the vcpu's current "xcrs".
|
||||
///
|
||||
fn set_xcrs(&self, xcrs: &ExtendedControlRegisters) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_xcrs(&xcrs)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetXcsr(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns currently pending exceptions, interrupts, and NMIs as well as related
|
||||
/// states of the vcpu.
|
||||
///
|
||||
fn get_vcpu_events(&self) -> cpu::Result<VcpuEvents> {
|
||||
self.fd
|
||||
.get_vcpu_events()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetVcpuEvents(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets pending exceptions, interrupts, and NMIs as well as related states
|
||||
/// of the vcpu.
|
||||
///
|
||||
fn set_vcpu_events(&self, events: &VcpuEvents) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_vcpu_events(events)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetVcpuEvents(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call to enable HyperV SynIC
|
||||
///
|
||||
fn enable_hyperv_synic(&self) -> cpu::Result<()> {
|
||||
/* We always have SynIC enabled on MSHV */
|
||||
Ok(())
|
||||
}
|
||||
#[allow(non_upper_case_globals)]
|
||||
fn run(&self) -> std::result::Result<cpu::VmExit, cpu::HypervisorCpuError> {
|
||||
// Safe because this is just only done during initialization.
|
||||
// TODO don't zero it everytime we enter this function.
|
||||
let hv_message: hv_message = unsafe { std::mem::zeroed() };
|
||||
match self.fd.run(hv_message) {
|
||||
Ok(x) => match x.header.message_type {
|
||||
hv_message_type_HVMSG_X64_HALT => {
|
||||
debug!("HALT");
|
||||
Ok(cpu::VmExit::Reset)
|
||||
}
|
||||
hv_message_type_HVMSG_UNRECOVERABLE_EXCEPTION => {
|
||||
warn!("TRIPLE FAULT");
|
||||
Ok(cpu::VmExit::Shutdown)
|
||||
}
|
||||
hv_message_type_HVMSG_X64_IO_PORT_INTERCEPT => {
|
||||
let info = x.to_ioport_info().unwrap();
|
||||
let access_info = info.access_info;
|
||||
if unsafe { access_info.__bindgen_anon_1.string_op() } == 1 {
|
||||
panic!("String IN/OUT not supported");
|
||||
}
|
||||
if unsafe { access_info.__bindgen_anon_1.rep_prefix() } == 1 {
|
||||
panic!("Rep IN/OUT not supported");
|
||||
}
|
||||
let len = unsafe { access_info.__bindgen_anon_1.access_size() } as usize;
|
||||
let is_write = info.header.intercept_access_type == 1;
|
||||
let port = info.port_number;
|
||||
let mut data: [u8; 4] = [0; 4];
|
||||
let mut ret_rax = info.rax;
|
||||
|
||||
if is_write {
|
||||
let data = (info.rax as u32).to_le_bytes();
|
||||
if let Some(vmmops) = &self.vmmops {
|
||||
vmmops
|
||||
.pio_write(port.into(), &data[0..len])
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
}
|
||||
} else {
|
||||
if let Some(vmmops) = &self.vmmops {
|
||||
vmmops
|
||||
.pio_read(port.into(), &mut data[0..len])
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
}
|
||||
|
||||
let v = u32::from_le_bytes(data);
|
||||
/* Preserve high bits in EAX but clear out high bits in RAX */
|
||||
let mask = 0xffffffff >> (32 - len * 8);
|
||||
let eax = (info.rax as u32 & !mask) | (v & mask);
|
||||
ret_rax = eax as u64;
|
||||
}
|
||||
|
||||
let insn_len = info.header.instruction_length() as u64;
|
||||
|
||||
/* Advance RIP and update RAX */
|
||||
let arr_reg_name_value = [
|
||||
(
|
||||
hv_register_name::HV_X64_REGISTER_RIP,
|
||||
info.header.rip + insn_len,
|
||||
),
|
||||
(hv_register_name::HV_X64_REGISTER_RAX, ret_rax),
|
||||
];
|
||||
set_registers_64!(self.fd, arr_reg_name_value)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetRegister(e.into()))?;
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
hv_message_type_HVMSG_UNMAPPED_GPA => {
|
||||
let info = x.to_memory_info().unwrap();
|
||||
let insn_len = info.instruction_byte_count as usize;
|
||||
assert!(insn_len > 0 && insn_len <= 16);
|
||||
|
||||
let mut context = MshvEmulatorContext {
|
||||
vcpu: self,
|
||||
tlb: SoftTLB::new(),
|
||||
};
|
||||
|
||||
// Add the GVA <-> GPA mapping.
|
||||
context
|
||||
.tlb
|
||||
.add_mapping(info.guest_virtual_address, info.guest_physical_address);
|
||||
|
||||
// Create a new emulator.
|
||||
let mut emul = Emulator::new(&mut context);
|
||||
|
||||
// Emulate the trapped instruction, and only the first one.
|
||||
let new_state = emul
|
||||
.emulate_first_insn(self.vp_index as usize, &info.instruction_bytes)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
|
||||
// Set CPU state back.
|
||||
context
|
||||
.set_cpu_state(self.vp_index as usize, new_state)
|
||||
.map_err(|e| cpu::HypervisorCpuError::RunVcpu(e.into()))?;
|
||||
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
hv_message_type_HVMSG_X64_CPUID_INTERCEPT => {
|
||||
let info = x.to_cpuid_info().unwrap();
|
||||
debug!("cpuid eax: {:x}", info.rax);
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
hv_message_type_HVMSG_X64_MSR_INTERCEPT => {
|
||||
let info = x.to_msr_info().unwrap();
|
||||
if info.header.intercept_access_type == 0 {
|
||||
debug!("msr read: {:x}", info.msr_number);
|
||||
} else {
|
||||
debug!("msr write: {:x}", info.msr_number);
|
||||
}
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
hv_message_type_HVMSG_X64_EXCEPTION_INTERCEPT => {
|
||||
//TODO: Handler for VMCALL here.
|
||||
let info = x.to_exception_info().unwrap();
|
||||
debug!("Exception Info {:?}", info.exception_vector);
|
||||
Ok(cpu::VmExit::Ignore)
|
||||
}
|
||||
exit => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!(
|
||||
"Unhandled VCPU exit {:?}",
|
||||
exit
|
||||
))),
|
||||
},
|
||||
|
||||
Err(e) => match e.errno() {
|
||||
libc::EAGAIN | libc::EINTR => Ok(cpu::VmExit::Ignore),
|
||||
_ => Err(cpu::HypervisorCpuError::RunVcpu(anyhow!(
|
||||
"VCPU error {:?}",
|
||||
e
|
||||
))),
|
||||
},
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call to setup the CPUID registers.
|
||||
///
|
||||
fn set_cpuid2(&self, _cpuid: &CpuId) -> cpu::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call to retrieve the CPUID registers.
|
||||
///
|
||||
fn get_cpuid2(&self, _num_entries: usize) -> cpu::Result<CpuId> {
|
||||
Ok(self.cpuid.clone())
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Returns the state of the LAPIC (Local Advanced Programmable Interrupt Controller).
|
||||
///
|
||||
fn get_lapic(&self) -> cpu::Result<LapicState> {
|
||||
self.fd
|
||||
.get_lapic()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetlapicState(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the state of the LAPIC (Local Advanced Programmable Interrupt Controller).
|
||||
///
|
||||
fn set_lapic(&self, lapic: &LapicState) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_lapic(lapic)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetLapicState(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that returns the vcpu's current "xsave struct".
|
||||
///
|
||||
fn get_xsave(&self) -> cpu::Result<Xsave> {
|
||||
self.fd
|
||||
.get_xsave()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetXsaveState(e.into()))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// X86 specific call that sets the vcpu's current "xsave struct".
|
||||
///
|
||||
fn set_xsave(&self, xsave: &Xsave) -> cpu::Result<()> {
|
||||
self.fd
|
||||
.set_xsave(*xsave)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetXsaveState(e.into()))
|
||||
}
|
||||
///
|
||||
/// Set CPU state
|
||||
///
|
||||
fn set_state(&self, state: &CpuState) -> cpu::Result<()> {
|
||||
self.set_msrs(&state.msrs)?;
|
||||
self.set_vcpu_events(&state.vcpu_events)?;
|
||||
self.set_regs(&state.regs)?;
|
||||
self.set_sregs(&state.sregs)?;
|
||||
self.set_fpu(&state.fpu)?;
|
||||
self.set_xcrs(&state.xcrs)?;
|
||||
self.set_lapic(&state.lapic)?;
|
||||
self.set_xsave(&state.xsave)?;
|
||||
self.fd
|
||||
.set_debug_regs(&state.dbg)
|
||||
.map_err(|e| cpu::HypervisorCpuError::SetDebugRegs(e.into()))?;
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
/// Get CPU State
|
||||
///
|
||||
fn state(&self) -> cpu::Result<CpuState> {
|
||||
let regs = self.get_regs()?;
|
||||
let sregs = self.get_sregs()?;
|
||||
let xcrs = self.get_xcrs()?;
|
||||
let fpu = self.get_fpu()?;
|
||||
let vcpu_events = self.get_vcpu_events()?;
|
||||
let mut msrs = self.msrs.clone();
|
||||
self.get_msrs(&mut msrs)?;
|
||||
let lapic = self.get_lapic()?;
|
||||
let xsave = self.get_xsave()?;
|
||||
let dbg = self
|
||||
.fd
|
||||
.get_debug_regs()
|
||||
.map_err(|e| cpu::HypervisorCpuError::GetDebugRegs(e.into()))?;
|
||||
Ok(CpuState {
|
||||
msrs,
|
||||
vcpu_events,
|
||||
regs,
|
||||
sregs,
|
||||
fpu,
|
||||
xcrs,
|
||||
lapic,
|
||||
dbg,
|
||||
xsave,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct MshvEmulatorContext<'a> {
|
||||
vcpu: &'a MshvVcpu,
|
||||
tlb: SoftTLB,
|
||||
}
|
||||
|
||||
/// Platform emulation for Hyper-V
|
||||
impl<'a> PlatformEmulator for MshvEmulatorContext<'a> {
|
||||
type CpuState = EmulatorCpuState;
|
||||
|
||||
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
|
||||
let gpa = self.tlb.translate(gva)?;
|
||||
debug!(
|
||||
"mshv emulator: memory read {} bytes from [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gpa
|
||||
);
|
||||
|
||||
if let Some(vmmops) = &self.vcpu.vmmops {
|
||||
vmmops
|
||||
.mmio_read(gpa, data)
|
||||
.map_err(|e| PlatformError::MemoryReadFailure(e.into()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError> {
|
||||
let gpa = self.tlb.translate(gva)?;
|
||||
debug!(
|
||||
"mshv emulator: memory write {} bytes at [{:#x} -> {:#x}]",
|
||||
data.len(),
|
||||
gva,
|
||||
gpa
|
||||
);
|
||||
|
||||
if let Some(vmmops) = &self.vcpu.vmmops {
|
||||
vmmops
|
||||
.mmio_write(gpa, data)
|
||||
.map_err(|e| PlatformError::MemoryWriteFailure(e.into()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cpu_state(&self, cpu_id: usize) -> Result<Self::CpuState, PlatformError> {
|
||||
if cpu_id != self.vcpu.vp_index as usize {
|
||||
return Err(PlatformError::GetCpuStateFailure(anyhow!(
|
||||
"CPU id mismatch {:?} {:?}",
|
||||
cpu_id,
|
||||
self.vcpu.vp_index
|
||||
)));
|
||||
}
|
||||
|
||||
let regs = self
|
||||
.vcpu
|
||||
.get_regs()
|
||||
.map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?;
|
||||
let sregs = self
|
||||
.vcpu
|
||||
.get_sregs()
|
||||
.map_err(|e| PlatformError::GetCpuStateFailure(e.into()))?;
|
||||
|
||||
debug!("mshv emulator: Getting new CPU state");
|
||||
debug!("mshv emulator: {:#x?}", regs);
|
||||
|
||||
Ok(EmulatorCpuState { regs, sregs })
|
||||
}
|
||||
|
||||
fn set_cpu_state(&self, cpu_id: usize, state: Self::CpuState) -> Result<(), PlatformError> {
|
||||
if cpu_id != self.vcpu.vp_index as usize {
|
||||
return Err(PlatformError::SetCpuStateFailure(anyhow!(
|
||||
"CPU id mismatch {:?} {:?}",
|
||||
cpu_id,
|
||||
self.vcpu.vp_index
|
||||
)));
|
||||
}
|
||||
|
||||
debug!("mshv emulator: Setting new CPU state");
|
||||
debug!("mshv emulator: {:#x?}", state.regs);
|
||||
|
||||
self.vcpu
|
||||
.set_regs(&state.regs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))?;
|
||||
self.vcpu
|
||||
.set_sregs(&state.sregs)
|
||||
.map_err(|e| PlatformError::SetCpuStateFailure(e.into()))
|
||||
}
|
||||
|
||||
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError> {
|
||||
self.tlb.translate(gva)
|
||||
}
|
||||
|
||||
fn fetch(&self, _ip: u64, _instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
|
||||
Err(PlatformError::MemoryReadFailure(anyhow!("unimplemented")))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[allow(dead_code)]
|
||||
/// Wrapper over Mshv VM ioctls.
|
||||
pub struct MshvVm {
|
||||
fd: Arc<VmFd>,
|
||||
msrs: MsrEntries,
|
||||
// GSI routing information
|
||||
gsi_routes: Arc<RwLock<HashMap<u32, MshvIrqRoutingEntry>>>,
|
||||
// Hypervisor State
|
||||
hv_state: Arc<RwLock<HvState>>,
|
||||
vmmops: Option<Arc<Box<dyn vm::VmmOps>>>,
|
||||
}
|
||||
|
||||
fn hv_state_init() -> Arc<RwLock<HvState>> {
|
||||
Arc::new(RwLock::new(HvState { hypercall_page: 0 }))
|
||||
}
|
||||
|
||||
///
|
||||
/// Implementation of Vm trait for Mshv
|
||||
/// Example:
|
||||
/// #[cfg(feature = "mshv")]
|
||||
/// # extern crate hypervisor;
|
||||
/// # use hypervisor::MshvHypervisor;
|
||||
/// let mshv = MshvHypervisor::new().unwrap();
|
||||
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(mshv);
|
||||
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
|
||||
/// vm.set/get().unwrap()
|
||||
///
|
||||
impl vm::Vm for MshvVm {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the address of the three-page region in the VM's address space.
|
||||
///
|
||||
fn set_tss_address(&self, _offset: usize) -> vm::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
/// Creates an in-kernel interrupt controller.
|
||||
///
|
||||
fn create_irq_chip(&self) -> vm::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
/// Registers an event that will, when signaled, trigger the `gsi` IRQ.
|
||||
///
|
||||
fn register_irqfd(&self, fd: &EventFd, gsi: u32) -> vm::Result<()> {
|
||||
debug!("register_irqfd fd {} gsi {}", fd.as_raw_fd(), gsi);
|
||||
|
||||
let gsi_routes = self.gsi_routes.read().unwrap();
|
||||
|
||||
if let Some(e) = gsi_routes.get(&gsi) {
|
||||
let msi = e
|
||||
.get_msi_routing()
|
||||
.map_err(|e| vm::HypervisorVmError::RegisterIrqFd(e.into()))?;
|
||||
let request = msi.to_interrupt_request();
|
||||
self.fd
|
||||
.register_irqfd(&fd, gsi, &request)
|
||||
.map_err(|e| vm::HypervisorVmError::RegisterIrqFd(e.into()))?;
|
||||
} else {
|
||||
error!("No routing info found for GSI {}", gsi)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
/// Unregisters an event that will, when signaled, trigger the `gsi` IRQ.
|
||||
///
|
||||
fn unregister_irqfd(&self, fd: &EventFd, gsi: u32) -> vm::Result<()> {
|
||||
debug!("unregister_irqfd fd {} gsi {}", fd.as_raw_fd(), gsi);
|
||||
|
||||
self.fd
|
||||
.unregister_irqfd(&fd, gsi)
|
||||
.map_err(|e| vm::HypervisorVmError::UnregisterIrqFd(e.into()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
/// Creates a VcpuFd object from a vcpu RawFd.
|
||||
///
|
||||
fn create_vcpu(
|
||||
&self,
|
||||
id: u8,
|
||||
vmmops: Option<Arc<Box<dyn VmmOps>>>,
|
||||
) -> vm::Result<Arc<dyn cpu::Vcpu>> {
|
||||
let vcpu_fd = self
|
||||
.fd
|
||||
.create_vcpu(id)
|
||||
.map_err(|e| vm::HypervisorVmError::CreateVcpu(e.into()))?;
|
||||
let vcpu = MshvVcpu {
|
||||
fd: vcpu_fd,
|
||||
vp_index: id,
|
||||
cpuid: CpuId::new(1),
|
||||
msrs: self.msrs.clone(),
|
||||
gsi_routes: self.gsi_routes.clone(),
|
||||
hv_state: self.hv_state.clone(),
|
||||
vmmops,
|
||||
};
|
||||
Ok(Arc::new(vcpu))
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn enable_split_irq(&self) -> vm::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn register_ioevent(
|
||||
&self,
|
||||
fd: &EventFd,
|
||||
addr: &IoEventAddress,
|
||||
datamatch: Option<DataMatch>,
|
||||
) -> vm::Result<()> {
|
||||
debug!(
|
||||
"register_ioevent fd {} addr {:x?} datamatch {:?}",
|
||||
fd.as_raw_fd(),
|
||||
addr,
|
||||
datamatch
|
||||
);
|
||||
if let Some(dm) = datamatch {
|
||||
match dm {
|
||||
vm::DataMatch::DataMatch32(mshv_dm32) => self
|
||||
.fd
|
||||
.register_ioevent(fd, addr, mshv_dm32)
|
||||
.map_err(|e| vm::HypervisorVmError::RegisterIoEvent(e.into())),
|
||||
vm::DataMatch::DataMatch64(mshv_dm64) => self
|
||||
.fd
|
||||
.register_ioevent(fd, addr, mshv_dm64)
|
||||
.map_err(|e| vm::HypervisorVmError::RegisterIoEvent(e.into())),
|
||||
}
|
||||
} else {
|
||||
self.fd
|
||||
.register_ioevent(fd, addr, NoDatamatch)
|
||||
.map_err(|e| vm::HypervisorVmError::RegisterIoEvent(e.into()))
|
||||
}
|
||||
}
|
||||
/// Unregister an event from a certain address it has been previously registered to.
|
||||
fn unregister_ioevent(&self, fd: &EventFd, addr: &IoEventAddress) -> vm::Result<()> {
|
||||
debug!("unregister_ioevent fd {} addr {:x?}", fd.as_raw_fd(), addr);
|
||||
|
||||
self.fd
|
||||
.unregister_ioevent(fd, addr, NoDatamatch)
|
||||
.map_err(|e| vm::HypervisorVmError::UnregisterIoEvent(e.into()))
|
||||
}
|
||||
|
||||
/// Creates/modifies a guest physical memory slot.
|
||||
fn set_user_memory_region(&self, user_memory_region: MemoryRegion) -> vm::Result<()> {
|
||||
self.fd
|
||||
.map_user_memory(user_memory_region)
|
||||
.map_err(|e| vm::HypervisorVmError::SetUserMemory(e.into()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_user_memory_region(
|
||||
&self,
|
||||
_slot: u32,
|
||||
guest_phys_addr: u64,
|
||||
memory_size: u64,
|
||||
userspace_addr: u64,
|
||||
readonly: bool,
|
||||
_log_dirty_pages: bool,
|
||||
) -> MemoryRegion {
|
||||
let mut flags = HV_MAP_GPA_READABLE | HV_MAP_GPA_EXECUTABLE;
|
||||
if !readonly {
|
||||
flags |= HV_MAP_GPA_WRITABLE;
|
||||
}
|
||||
|
||||
mshv_user_mem_region {
|
||||
flags,
|
||||
guest_pfn: guest_phys_addr >> PAGE_SHIFT,
|
||||
size: memory_size,
|
||||
userspace_addr: userspace_addr as u64,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_passthrough_device(&self) -> vm::Result<Arc<dyn device::Device>> {
|
||||
Err(vm::HypervisorVmError::CreatePassthroughDevice(anyhow!(
|
||||
"No passthrough support"
|
||||
)))
|
||||
}
|
||||
|
||||
fn set_gsi_routing(&self, irq_routing: &[IrqRoutingEntry]) -> vm::Result<()> {
|
||||
let mut routes = self.gsi_routes.write().unwrap();
|
||||
|
||||
routes.drain();
|
||||
|
||||
for r in irq_routing {
|
||||
debug!("gsi routing {:x?}", r);
|
||||
routes.insert(r.gsi, *r);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
/// Get the Vm state. Return VM specific data
|
||||
///
|
||||
fn state(&self) -> vm::Result<VmState> {
|
||||
Ok(*self.hv_state.read().unwrap())
|
||||
}
|
||||
///
|
||||
/// Set the VM state
|
||||
///
|
||||
fn set_state(&self, state: VmState) -> vm::Result<()> {
|
||||
self.hv_state.write().unwrap().hypercall_page = state.hypercall_page;
|
||||
Ok(())
|
||||
}
|
||||
///
|
||||
/// Get dirty pages bitmap (one bit per page)
|
||||
///
|
||||
fn get_dirty_log(&self, _slot: u32, _memory_size: u64) -> vm::Result<Vec<u64>> {
|
||||
Err(vm::HypervisorVmError::GetDirtyLog(anyhow!(
|
||||
"get_dirty_log not implemented"
|
||||
)))
|
||||
}
|
||||
}
|
||||
pub use hv_cpuid_entry as CpuIdEntry;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct MshvIrqRoutingMsi {
|
||||
pub address_lo: u32,
|
||||
pub address_hi: u32,
|
||||
pub data: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum MshvIrqRouting {
|
||||
Msi(MshvIrqRoutingMsi),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MshvIrqRoutingEntryError {
|
||||
#[error("Invalid MSI address: {0}")]
|
||||
InvalidMsiAddress(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct MshvIrqRoutingEntry {
|
||||
pub gsi: u32,
|
||||
pub route: MshvIrqRouting,
|
||||
}
|
||||
pub type IrqRoutingEntry = MshvIrqRoutingEntry;
|
||||
|
||||
impl MshvIrqRoutingEntry {
|
||||
fn get_msi_routing(&self) -> Result<MshvIrqRoutingMsi, MshvIrqRoutingEntryError> {
|
||||
let MshvIrqRouting::Msi(msi) = self.route;
|
||||
if msi.address_hi != 0 {
|
||||
return Err(MshvIrqRoutingEntryError::InvalidMsiAddress(anyhow!(
|
||||
"MSI high address part is not zero"
|
||||
)));
|
||||
}
|
||||
Ok(msi)
|
||||
}
|
||||
}
|
||||
|
||||
impl MshvIrqRoutingMsi {
|
||||
///
|
||||
/// See Intel SDM vol3 10.11.1
|
||||
/// We assume APIC ID and Hyper-V Vcpu ID are the same value
|
||||
///
|
||||
|
||||
fn get_destination(&self) -> u64 {
|
||||
((self.address_lo >> 12) & 0xff).into()
|
||||
}
|
||||
|
||||
fn get_destination_mode(&self) -> bool {
|
||||
if (self.address_lo >> 2) & 0x1 == 0x1 {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn get_vector(&self) -> u8 {
|
||||
(self.data & 0xff) as u8
|
||||
}
|
||||
|
||||
///
|
||||
/// True means level triggered
|
||||
///
|
||||
fn get_trigger_mode(&self) -> bool {
|
||||
if (self.data >> 15) & 0x1 == 0x1 {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn get_delivery_mode(&self) -> u8 {
|
||||
((self.data & 0x700) >> 8) as u8
|
||||
}
|
||||
|
||||
///
|
||||
/// Translate from architectural defined delivery mode to Hyper-V type
|
||||
/// See Intel SDM vol3 10.11.2
|
||||
///
|
||||
fn get_interrupt_type(&self) -> Option<hv_interrupt_type> {
|
||||
match self.get_delivery_mode() {
|
||||
0 => Some(hv_interrupt_type_HV_X64_INTERRUPT_TYPE_FIXED),
|
||||
1 => Some(hv_interrupt_type_HV_X64_INTERRUPT_TYPE_LOWESTPRIORITY),
|
||||
2 => Some(hv_interrupt_type_HV_X64_INTERRUPT_TYPE_SMI),
|
||||
4 => Some(hv_interrupt_type_HV_X64_INTERRUPT_TYPE_NMI),
|
||||
5 => Some(hv_interrupt_type_HV_X64_INTERRUPT_TYPE_INIT),
|
||||
7 => Some(hv_interrupt_type_HV_X64_INTERRUPT_TYPE_EXTINT),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_interrupt_request(&self) -> InterruptRequest {
|
||||
InterruptRequest {
|
||||
interrupt_type: self.get_interrupt_type().unwrap(),
|
||||
apic_id: self.get_destination(),
|
||||
vector: self.get_vector() as u32,
|
||||
level_triggered: self.get_trigger_mode(),
|
||||
logical_destination_mode: self.get_destination_mode(),
|
||||
long_mode: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const CPUID_FLAG_VALID_INDEX: u32 = 0;
|
||||
122
hypervisor/src/mshv/x86_64/mod.rs
Normal file
122
hypervisor/src/mshv/x86_64/mod.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright © 2019 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
//
|
||||
// Copyright © 2020, Microsoft Corporation
|
||||
//
|
||||
// Copyright 2018-2019 CrowdStrike, Inc.
|
||||
//
|
||||
//
|
||||
|
||||
use crate::arch::x86::{msr_index, SegmentRegisterOps};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
///
|
||||
/// Export generically-named wrappers of mshv_bindings for Unix-based platforms
|
||||
///
|
||||
pub use {
|
||||
mshv_bindings::mshv_user_mem_region as MemoryRegion, mshv_bindings::msr_entry as MsrEntry,
|
||||
mshv_bindings::CpuId, mshv_bindings::DebugRegisters,
|
||||
mshv_bindings::FloatingPointUnit as FpuState, mshv_bindings::LapicState,
|
||||
mshv_bindings::MsrList, mshv_bindings::Msrs as MsrEntries, mshv_bindings::Msrs,
|
||||
mshv_bindings::SegmentRegister, mshv_bindings::SpecialRegisters,
|
||||
mshv_bindings::StandardRegisters, mshv_bindings::VcpuEvents, mshv_bindings::XSave as Xsave,
|
||||
mshv_bindings::Xcrs as ExtendedControlRegisters,
|
||||
};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct VcpuMshvState {
|
||||
pub msrs: MsrEntries,
|
||||
pub vcpu_events: VcpuEvents,
|
||||
pub regs: StandardRegisters,
|
||||
pub sregs: SpecialRegisters,
|
||||
pub fpu: FpuState,
|
||||
pub xcrs: ExtendedControlRegisters,
|
||||
pub lapic: LapicState,
|
||||
pub dbg: DebugRegisters,
|
||||
pub xsave: Xsave,
|
||||
}
|
||||
|
||||
pub struct CreateDevice {}
|
||||
pub struct DeviceAttr {}
|
||||
pub struct IrqRouting {}
|
||||
pub enum VcpuExit {}
|
||||
pub struct MpState {}
|
||||
|
||||
impl SegmentRegisterOps for SegmentRegister {
|
||||
fn segment_type(&self) -> u8 {
|
||||
self.type_
|
||||
}
|
||||
fn set_segment_type(&mut self, val: u8) {
|
||||
self.type_ = val;
|
||||
}
|
||||
|
||||
fn dpl(&self) -> u8 {
|
||||
self.dpl
|
||||
}
|
||||
|
||||
fn set_dpl(&mut self, val: u8) {
|
||||
self.dpl = val;
|
||||
}
|
||||
|
||||
fn present(&self) -> u8 {
|
||||
self.present
|
||||
}
|
||||
|
||||
fn set_present(&mut self, val: u8) {
|
||||
self.present = val;
|
||||
}
|
||||
|
||||
fn long(&self) -> u8 {
|
||||
self.l
|
||||
}
|
||||
|
||||
fn set_long(&mut self, val: u8) {
|
||||
self.l = val;
|
||||
}
|
||||
|
||||
fn avl(&self) -> u8 {
|
||||
self.avl
|
||||
}
|
||||
|
||||
fn set_avl(&mut self, val: u8) {
|
||||
self.avl = val;
|
||||
}
|
||||
|
||||
fn desc_type(&self) -> u8 {
|
||||
self.s
|
||||
}
|
||||
|
||||
fn set_desc_type(&mut self, val: u8) {
|
||||
self.s = val;
|
||||
}
|
||||
|
||||
fn granularity(&self) -> u8 {
|
||||
self.g
|
||||
}
|
||||
|
||||
fn set_granularity(&mut self, val: u8) {
|
||||
self.g = val;
|
||||
}
|
||||
|
||||
fn db(&self) -> u8 {
|
||||
self.db
|
||||
}
|
||||
|
||||
fn set_db(&mut self, val: u8) {
|
||||
self.db = val;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn boot_msr_entries() -> MsrEntries {
|
||||
MsrEntries::from_entries(&[
|
||||
msr!(msr_index::MSR_IA32_SYSENTER_CS),
|
||||
msr!(msr_index::MSR_IA32_SYSENTER_ESP),
|
||||
msr!(msr_index::MSR_IA32_SYSENTER_EIP),
|
||||
msr!(msr_index::MSR_STAR),
|
||||
msr!(msr_index::MSR_CSTAR),
|
||||
msr!(msr_index::MSR_LSTAR),
|
||||
msr!(msr_index::MSR_KERNEL_GS_BASE),
|
||||
msr!(msr_index::MSR_SYSCALL_MASK),
|
||||
msr!(msr_index::MSR_IA32_TSC),
|
||||
])
|
||||
}
|
||||
@@ -12,10 +12,16 @@
|
||||
use crate::aarch64::VcpuInit;
|
||||
use crate::cpu::Vcpu;
|
||||
use crate::device::Device;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
use crate::ClockData;
|
||||
#[cfg(feature = "kvm")]
|
||||
use crate::CreateDevice;
|
||||
#[cfg(feature = "mshv")]
|
||||
use crate::HvState as VmState;
|
||||
#[cfg(feature = "kvm")]
|
||||
use crate::KvmVmState as VmState;
|
||||
use crate::{CreateDevice, IoEventAddress, IrqRoutingEntry, MemoryRegion};
|
||||
use crate::{IoEventAddress, IrqRoutingEntry, MemoryRegion};
|
||||
#[cfg(feature = "kvm")]
|
||||
use kvm_ioctls::Cap;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
@@ -24,6 +30,7 @@ use vmm_sys_util::eventfd::EventFd;
|
||||
///
|
||||
/// I/O events data matches (32 or 64 bits).
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub enum DataMatch {
|
||||
DataMatch32(u32),
|
||||
DataMatch64(u64),
|
||||
@@ -117,6 +124,45 @@ pub enum HypervisorVmError {
|
||||
///
|
||||
#[error("Failed to create passthrough device: {0}")]
|
||||
CreatePassthroughDevice(#[source] anyhow::Error),
|
||||
/// Write to Guest memory
|
||||
///
|
||||
#[error("Failed to write to guest memory: {0}")]
|
||||
GuestMemWrite(#[source] anyhow::Error),
|
||||
///
|
||||
/// Read Guest memory
|
||||
///
|
||||
#[error("Failed to read guest memory: {0}")]
|
||||
GuestMemRead(#[source] anyhow::Error),
|
||||
///
|
||||
/// Read from MMIO Bus
|
||||
///
|
||||
#[error("Failed to read from MMIO Bus: {0}")]
|
||||
MmioBusRead(#[source] anyhow::Error),
|
||||
///
|
||||
/// Write to MMIO Bus
|
||||
///
|
||||
#[error("Failed to write to MMIO Bus: {0}")]
|
||||
MmioBusWrite(#[source] anyhow::Error),
|
||||
///
|
||||
/// Read from IO Bus
|
||||
///
|
||||
#[error("Failed to read from IO Bus: {0}")]
|
||||
IoBusRead(#[source] anyhow::Error),
|
||||
///
|
||||
/// Write to IO Bus
|
||||
///
|
||||
#[error("Failed to write to IO Bus: {0}")]
|
||||
IoBusWrite(#[source] anyhow::Error),
|
||||
///
|
||||
/// Get dirty log error
|
||||
///
|
||||
#[error("Failed to get dirty log: {0}")]
|
||||
GetDirtyLog(#[source] anyhow::Error),
|
||||
///
|
||||
/// Assert virtual interrupt error
|
||||
///
|
||||
#[error("Failed to assert virtual Interrupt: {0}")]
|
||||
AsserttVirtualInterrupt(#[source] anyhow::Error),
|
||||
}
|
||||
///
|
||||
/// Result type for returning from a function
|
||||
@@ -139,7 +185,7 @@ pub trait Vm: Send + Sync {
|
||||
/// Unregister an event that will, when signaled, trigger the `gsi` IRQ.
|
||||
fn unregister_irqfd(&self, fd: &EventFd, gsi: u32) -> Result<()>;
|
||||
/// Creates a new KVM vCPU file descriptor and maps the memory corresponding
|
||||
fn create_vcpu(&self, id: u8) -> Result<Arc<dyn Vcpu>>;
|
||||
fn create_vcpu(&self, id: u8, vmmops: Option<Arc<Box<dyn VmmOps>>>) -> Result<Arc<dyn Vcpu>>;
|
||||
/// Registers an event to be signaled whenever a certain address is written to.
|
||||
fn register_ioevent(
|
||||
&self,
|
||||
@@ -159,9 +205,11 @@ pub trait Vm: Send + Sync {
|
||||
memory_size: u64,
|
||||
userspace_addr: u64,
|
||||
readonly: bool,
|
||||
log_dirty_pages: bool,
|
||||
) -> MemoryRegion;
|
||||
/// Creates/modifies a guest physical memory slot.
|
||||
fn set_user_memory_region(&self, user_memory_region: MemoryRegion) -> Result<()>;
|
||||
#[cfg(feature = "kvm")]
|
||||
/// Creates an emulated device in the kernel.
|
||||
fn create_device(&self, device: &mut CreateDevice) -> Result<Arc<dyn Device>>;
|
||||
/// Returns the preferred CPU target type which can be emulated by KVM on underlying host.
|
||||
@@ -171,11 +219,12 @@ pub trait Vm: Send + Sync {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn enable_split_irq(&self) -> Result<()>;
|
||||
/// Retrieve guest clock.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
fn get_clock(&self) -> Result<ClockData>;
|
||||
/// Set guest clock.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
|
||||
fn set_clock(&self, data: &ClockData) -> Result<()>;
|
||||
#[cfg(feature = "kvm")]
|
||||
/// Checks if a particular `Cap` is available.
|
||||
fn check_extension(&self, c: Cap) -> bool;
|
||||
/// Create a device that is used for passthrough
|
||||
@@ -183,5 +232,18 @@ pub trait Vm: Send + Sync {
|
||||
/// Get the Vm state. Return VM specific data
|
||||
fn state(&self) -> Result<VmState>;
|
||||
/// Set the VM state
|
||||
fn set_state(&self, state: &VmState) -> Result<()>;
|
||||
fn set_state(&self, state: VmState) -> Result<()>;
|
||||
/// Get dirty pages bitmap
|
||||
fn get_dirty_log(&self, slot: u32, memory_size: u64) -> Result<Vec<u64>>;
|
||||
}
|
||||
|
||||
pub trait VmmOps: Send + Sync {
|
||||
fn guest_mem_write(&self, gpa: u64, buf: &[u8]) -> Result<usize>;
|
||||
fn guest_mem_read(&self, gpa: u64, buf: &mut [u8]) -> Result<usize>;
|
||||
fn mmio_read(&self, gpa: u64, data: &mut [u8]) -> Result<()>;
|
||||
fn mmio_write(&self, gpa: u64, data: &[u8]) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn pio_read(&self, port: u64, data: &mut [u8]) -> Result<()>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn pio_write(&self, port: u64, data: &[u8]) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -5,17 +5,17 @@ authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
epoll = ">=4.0.1"
|
||||
libc = "0.2.77"
|
||||
log = "0.4.11"
|
||||
libc = "0.2.86"
|
||||
log = "0.4.14"
|
||||
net_gen = { path = "../net_gen" }
|
||||
rand = "0.7.3"
|
||||
serde = "1.0.116"
|
||||
rand = "0.8.3"
|
||||
serde = "1.0.123"
|
||||
virtio-bindings = "0.1.0"
|
||||
vm-memory = { version = "0.2.1", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-memory = { version = "0.5.0", features = ["backend-mmap", "backend-atomic"] }
|
||||
vm-virtio = { path = "../vm-virtio" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.3.0"
|
||||
pnet = "0.26.0"
|
||||
serde_json = "1.0.57"
|
||||
pnet = "0.27.2"
|
||||
serde_json = "1.0.62"
|
||||
|
||||
@@ -65,9 +65,12 @@ impl MacAddr {
|
||||
|
||||
// An error can only occur if the slice length is different from MAC_ADDR_LEN.
|
||||
#[inline]
|
||||
pub fn from_bytes(src: &[u8]) -> Result<MacAddr, ()> {
|
||||
pub fn from_bytes(src: &[u8]) -> Result<MacAddr, io::Error> {
|
||||
if src.len() != MAC_ADDR_LEN {
|
||||
return Err(());
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("invalid length of slice: {} vs {}", src.len(), MAC_ADDR_LEN),
|
||||
));
|
||||
}
|
||||
Ok(MacAddr::from_bytes_unchecked(src))
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ pub fn open_tap(
|
||||
netmask: Option<Ipv4Addr>,
|
||||
host_mac: &mut Option<MacAddr>,
|
||||
num_rx_q: usize,
|
||||
flags: Option<i32>,
|
||||
) -> Result<Vec<Tap>> {
|
||||
let mut taps: Vec<Tap> = Vec::new();
|
||||
let mut ifname: String = String::new();
|
||||
@@ -82,7 +83,7 @@ pub fn open_tap(
|
||||
let tap: Tap;
|
||||
if i == 0 {
|
||||
tap = match if_name {
|
||||
Some(name) => Tap::open_named(name, num_rx_q).map_err(Error::TapOpen)?,
|
||||
Some(name) => Tap::open_named(name, num_rx_q, flags).map_err(Error::TapOpen)?,
|
||||
None => Tap::new(num_rx_q).map_err(Error::TapOpen)?,
|
||||
};
|
||||
if let Some(ip) = ip_addr {
|
||||
@@ -104,7 +105,7 @@ pub fn open_tap(
|
||||
|
||||
ifname = String::from_utf8(tap.get_if_name()).unwrap();
|
||||
} else {
|
||||
tap = Tap::open_named(ifname.as_str(), num_rx_q).map_err(Error::TapOpen)?;
|
||||
tap = Tap::open_named(ifname.as_str(), num_rx_q, flags).map_err(Error::TapOpen)?;
|
||||
tap.set_offload(flag).map_err(Error::TapSetOffload)?;
|
||||
|
||||
tap.set_vnet_hdr_size(vnet_hdr_size)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the THIRD-PARTY file.
|
||||
|
||||
use super::{create_sockaddr, create_socket, Error as NetUtilError, MacAddr};
|
||||
use super::{create_sockaddr, create_socket, vnet_hdr_len, Error as NetUtilError, MacAddr};
|
||||
use mac::MAC_ADDR_LEN;
|
||||
use net_gen;
|
||||
use std::fs::File;
|
||||
@@ -31,7 +31,7 @@ pub enum Error {
|
||||
NetUtil(NetUtilError),
|
||||
InvalidIfname,
|
||||
/// Error parsing MAC data
|
||||
MacParsing(()),
|
||||
MacParsing(IoError),
|
||||
}
|
||||
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
@@ -83,7 +83,7 @@ fn build_terminated_if_name(if_name: &str) -> Result<Vec<u8>> {
|
||||
}
|
||||
|
||||
impl Tap {
|
||||
pub fn open_named(if_name: &str, num_queue_pairs: usize) -> Result<Tap> {
|
||||
pub fn open_named(if_name: &str, num_queue_pairs: usize, flags: Option<i32>) -> Result<Tap> {
|
||||
let terminated_if_name = build_terminated_if_name(if_name)?;
|
||||
|
||||
let fd = unsafe {
|
||||
@@ -91,7 +91,7 @@ impl Tap {
|
||||
// string and verify the result.
|
||||
libc::open(
|
||||
b"/dev/net/tun\0".as_ptr() as *const c_char,
|
||||
libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC,
|
||||
flags.unwrap_or(libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC),
|
||||
)
|
||||
};
|
||||
if fd < 0 {
|
||||
@@ -150,7 +150,54 @@ impl Tap {
|
||||
|
||||
/// Create a new tap interface.
|
||||
pub fn new(num_queue_pairs: usize) -> Result<Tap> {
|
||||
Self::open_named("vmtap%d", num_queue_pairs)
|
||||
Self::open_named("vmtap%d", num_queue_pairs, None)
|
||||
}
|
||||
|
||||
pub fn from_tap_fd(fd: RawFd, num_queue_pairs: usize) -> Result<Tap> {
|
||||
// Ensure that the file is opened non-blocking, this is particularly
|
||||
// needed when opened via the shell for macvtap.
|
||||
let ret = unsafe {
|
||||
let mut flags = libc::fcntl(fd, libc::F_GETFL);
|
||||
flags |= libc::O_NONBLOCK;
|
||||
libc::fcntl(fd, libc::F_SETFL, flags)
|
||||
};
|
||||
if ret < 0 {
|
||||
return Err(Error::ConfigureTap(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
let tap_file = unsafe { File::from_raw_fd(fd) };
|
||||
let mut ifreq: net_gen::ifreq = Default::default();
|
||||
|
||||
// Get current config including name
|
||||
let ret = unsafe { ioctl_with_mut_ref(&tap_file, net_gen::TUNGETIFF(), &mut ifreq) };
|
||||
if ret < 0 {
|
||||
return Err(Error::IoctlError(IoError::last_os_error()));
|
||||
}
|
||||
let if_name = unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() }.to_vec();
|
||||
|
||||
// Try and update flags. Depending on how the tap was created (macvtap
|
||||
// or via open_named()) this might return -EEXIST so we just ignore that.
|
||||
unsafe {
|
||||
let ifru_flags = ifreq.ifr_ifru.ifru_flags.as_mut();
|
||||
*ifru_flags =
|
||||
(net_gen::IFF_TAP | net_gen::IFF_NO_PI | net_gen::IFF_VNET_HDR) as c_short;
|
||||
if num_queue_pairs > 1 {
|
||||
*ifru_flags |= net_gen::IFF_MULTI_QUEUE as c_short;
|
||||
}
|
||||
}
|
||||
let ret = unsafe { ioctl_with_mut_ref(&tap_file, net_gen::TUNSETIFF(), &mut ifreq) };
|
||||
if ret < 0 && IoError::last_os_error().raw_os_error().unwrap() != libc::EEXIST {
|
||||
return Err(Error::ConfigureTap(IoError::last_os_error()));
|
||||
}
|
||||
|
||||
let tap = Tap { if_name, tap_file };
|
||||
let offload_flags =
|
||||
net_gen::TUN_F_CSUM | net_gen::TUN_F_UFO | net_gen::TUN_F_TSO4 | net_gen::TUN_F_TSO6;
|
||||
let vnet_hdr_size = vnet_hdr_len() as i32;
|
||||
tap.set_offload(offload_flags)?;
|
||||
tap.set_vnet_hdr_size(vnet_hdr_size)?;
|
||||
|
||||
Ok(tap)
|
||||
}
|
||||
|
||||
/// Set the host-side IP address for the tap interface.
|
||||
@@ -535,10 +582,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tap_create() {
|
||||
let _tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
||||
|
||||
let t = Tap::new(1).unwrap();
|
||||
println!("created tap: {:?}", t);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tap_from_fd() {
|
||||
let _tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
||||
|
||||
let orig_tap = Tap::new(1).unwrap();
|
||||
let fd = orig_tap.as_raw_fd();
|
||||
let _new_tap = Tap::from_tap_fd(fd, 1).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tap_configure() {
|
||||
// This should be the first thing to be called inside the function, so everything else
|
||||
@@ -559,6 +617,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_set_options() {
|
||||
let _tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
||||
|
||||
// This line will fail to provide an initialized FD if the test is not run as root.
|
||||
let tap = Tap::new(1).unwrap();
|
||||
tap.set_vnet_hdr_size(16).unwrap();
|
||||
@@ -567,6 +627,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tap_enable() {
|
||||
let _tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
||||
|
||||
let tap = Tap::new(1).unwrap();
|
||||
let ret = tap.enable();
|
||||
assert!(ret.is_ok());
|
||||
@@ -574,6 +636,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tap_get_ifreq() {
|
||||
let _tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
||||
|
||||
let tap = Tap::new(1).unwrap();
|
||||
let ret = tap.get_ifreq();
|
||||
assert_eq!(
|
||||
@@ -584,6 +648,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_raw_fd() {
|
||||
let _tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
||||
|
||||
let tap = Tap::new(1).unwrap();
|
||||
assert_eq!(tap.as_raw_fd(), tap.tap_file.as_raw_fd());
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ impl FromStr for IntegerList {
|
||||
let items: Vec<&str> = range.split('-').collect();
|
||||
|
||||
if items.len() > 2 {
|
||||
return Err(IntegerListParseError::InvalidValue(range.to_string()));
|
||||
return Err(IntegerListParseError::InvalidValue((*range).to_string()));
|
||||
}
|
||||
|
||||
let start_range = items[0]
|
||||
@@ -203,7 +203,7 @@ impl FromStr for IntegerList {
|
||||
.parse::<u64>()
|
||||
.map_err(|_| IntegerListParseError::InvalidValue(items[1].to_owned()))?;
|
||||
if start_range >= end_range {
|
||||
return Err(IntegerListParseError::InvalidValue(range.to_string()));
|
||||
return Err(IntegerListParseError::InvalidValue((*range).to_string()));
|
||||
}
|
||||
|
||||
for i in start_range..end_range {
|
||||
@@ -233,7 +233,9 @@ impl FromStr for TupleTwoIntegers {
|
||||
let items: Vec<&str> = tuple.split('@').collect();
|
||||
|
||||
if items.len() != 2 {
|
||||
return Err(TupleTwoIntegersParseError::InvalidValue(tuple.to_string()));
|
||||
return Err(TupleTwoIntegersParseError::InvalidValue(
|
||||
(*tuple).to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let item1 = items[0]
|
||||
|
||||
@@ -10,14 +10,14 @@ byteorder = "1.3.4"
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
vfio-ioctls = { git = "https://github.com/cloud-hypervisor/vfio-ioctls", branch = "ch" }
|
||||
vmm-sys-util = ">=0.3.1"
|
||||
libc = "0.2.77"
|
||||
log = "0.4.11"
|
||||
libc = "0.2.86"
|
||||
log = "0.4.14"
|
||||
serde = {version = ">=1.0.27", features = ["rc"] }
|
||||
serde_derive = ">=1.0.27"
|
||||
serde_json = ">=1.0.9"
|
||||
vm-allocator = { path = "../vm-allocator" }
|
||||
vm-device = { path = "../vm-device" }
|
||||
vm-memory = "0.2.1"
|
||||
vm-memory = "0.5.0"
|
||||
vm-migration = { path = "../vm-migration" }
|
||||
|
||||
[dependencies.vfio-bindings]
|
||||
|
||||
@@ -10,7 +10,7 @@ use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use vm_device::{Bus, BusDevice};
|
||||
use vm_memory::{Address, GuestAddress, GuestUsize};
|
||||
|
||||
@@ -71,8 +71,14 @@ impl PciRoot {
|
||||
impl BusDevice for PciRoot {}
|
||||
|
||||
impl PciDevice for PciRoot {
|
||||
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
|
||||
fn write_config_register(
|
||||
&mut self,
|
||||
reg_idx: usize,
|
||||
offset: u64,
|
||||
data: &[u8],
|
||||
) -> Option<Arc<Barrier>> {
|
||||
self.config.write_config_register(reg_idx, offset, data);
|
||||
None
|
||||
}
|
||||
|
||||
fn read_config_register(&mut self, reg_idx: usize) -> u32 {
|
||||
@@ -225,14 +231,14 @@ impl PciConfigIo {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn config_space_write(&mut self, offset: u64, data: &[u8]) {
|
||||
pub fn config_space_write(&mut self, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if offset as usize + data.len() > 4 {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
let enabled = (self.config_address & 0x8000_0000) != 0;
|
||||
if !enabled {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
let (bus, device, _function, register) =
|
||||
@@ -240,7 +246,7 @@ impl PciConfigIo {
|
||||
|
||||
// Only support one bus.
|
||||
if bus != 0 {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
|
||||
let pci_bus = self.pci_bus.lock().unwrap();
|
||||
@@ -265,7 +271,9 @@ impl PciConfigIo {
|
||||
}
|
||||
|
||||
// Update the register value
|
||||
device.write_config_register(register, offset, data);
|
||||
device.write_config_register(register, offset, data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,13 +320,16 @@ impl BusDevice for PciConfigIo {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
// `offset` is relative to 0xcf8
|
||||
match offset {
|
||||
o @ 0..=3 => self.set_config_address(o, data),
|
||||
o @ 0..=3 => {
|
||||
self.set_config_address(o, data);
|
||||
None
|
||||
}
|
||||
o @ 4..=7 => self.config_space_write(o - 4, data),
|
||||
_ => (),
|
||||
};
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,11 +418,13 @@ impl BusDevice for PciConfigMmio {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
|
||||
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option<Arc<Barrier>> {
|
||||
if offset > u64::from(u32::max_value()) {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
self.config_space_write(offset as u32, offset % 4, data)
|
||||
self.config_space_write(offset as u32, offset % 4, data);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user