diff --git a/Cargo.lock b/Cargo.lock index 80689e68..7cd826cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,65 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys", +] [[package]] name = "api" @@ -15,62 +74,103 @@ dependencies = [ name = "arch" version = "0.1.0" dependencies = [ + "kvm-bindings", + "kvm-ioctls", + "log", "vm-fdt", "vm-memory", ] [[package]] -name = "atty" -version = "0.2.14" +name = "bindgen" +version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "bitflags 2.8.0", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "1.2.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" -version = "0.1.10" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] [[package]] name = "clap" -version = "3.2.17" +version = "4.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ - "atty", - "bitflags", + "anstream", + "anstyle", "clap_lex", - "indexmap", "strsim", - "termcolor", - "textwrap", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "devices" @@ -82,6 +182,7 @@ dependencies = [ "libc", "linux-loader", "log", + "thiserror", "utils", "virtio-blk", "virtio-device", @@ -92,56 +193,57 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "event-manager" -version = "0.2.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377fa591135fbe23396a18e2655a6d5481bf7c5823cdfa3cc81b01a229cbe640" +checksum = "90b16fe5161a1160c9c7cece9f7504f2412ef5e2c0643d1e322eccf37692a42b" dependencies = [ "libc", "vmm-sys-util", ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "glob" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] -name = "hermit-abi" -version = "0.1.17" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" -dependencies = [ - "libc", -] +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "indexmap" -version = "1.9.1" +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ - "autocfg", - "hashbrown", + "either", ] [[package]] name = "kvm-bindings" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78c049190826fff959994b7c1d8a2930d0a348f1b8f3aa4f9bb34cd5d7f2952" +version = "0.11.0" +source = "git+https://github.com/rust-vmm/kvm.git#fac89f0a7b2f726577e859e5c869d5064a220460" dependencies = [ "vmm-sys-util", ] [[package]] name = "kvm-ioctls" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97422ba48d7ffb66fd4d18130f72ab66f9bbbf791fb7a87b9291cdcfec437593" +version = "0.20.0" +source = "git+https://github.com/rust-vmm/kvm.git#fac89f0a7b2f726577e859e5c869d5064a220460" dependencies = [ + "bitflags 2.8.0", "kvm-bindings", "libc", "vmm-sys-util", @@ -149,97 +251,163 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.91" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libloading" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] [[package]] name = "linux-loader" -version = "0.4.0" -source = "git+https://github.com/rust-vmm/linux-loader.git?rev=9a9f071#9a9f071219a8c74dcd703aec40ba0249e3a5993b" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870c3814345f050991f99869417779f6062542bcf4ed81db7a1b926ad1306638" dependencies = [ "vm-memory", ] [[package]] name = "log" -version = "0.4.6" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "cfg-if", + "memchr", + "minimal-lexical", ] [[package]] -name = "os_str_bytes" -version = "6.3.0" +name = "once_cell" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "prettyplease" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +dependencies = [ + "proc-macro2", + "syn", +] [[package]] name = "proc-macro2" -version = "1.0.34" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.10" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] -name = "strsim" -version = "0.10.0" +name = "regex" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] [[package]] -name = "syn" -version = "1.0.82" +name = "regex-automata" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", + "aho-corasick", + "memchr", + "regex-syntax", ] [[package]] -name = "termcolor" -version = "1.1.3" +name = "regex-syntax" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] -name = "textwrap" -version = "0.15.0" +name = "rustc-hash" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -247,21 +415,36 @@ dependencies = [ ] [[package]] -name = "unicode-xid" +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utils" version = "0.1.0" +[[package]] +name = "virtio-bindings" +version = "0.2.4" +source = "git+https://github.com/rust-vmm/vm-virtio.git#2c21f2536b0ab84589f011f3c3c395803bf9c6f1" +dependencies = [ + "bindgen", +] + [[package]] name = "virtio-blk" version = "0.1.0" -source = "git+https://github.com/rust-vmm/vm-virtio.git#d8ef45f57b46baa99e80e555deffd3fa1ab9affc" +source = "git+https://github.com/rust-vmm/vm-virtio.git#2c21f2536b0ab84589f011f3c3c395803bf9c6f1" dependencies = [ "log", + "virtio-bindings", "virtio-device", "virtio-queue", "vm-memory", @@ -271,28 +454,30 @@ dependencies = [ [[package]] name = "virtio-device" version = "0.1.0" -source = "git+https://github.com/rust-vmm/vm-virtio.git#d8ef45f57b46baa99e80e555deffd3fa1ab9affc" +source = "git+https://github.com/rust-vmm/vm-virtio.git#2c21f2536b0ab84589f011f3c3c395803bf9c6f1" dependencies = [ "log", + "virtio-bindings", "virtio-queue", "vm-memory", ] [[package]] name = "virtio-queue" -version = "0.2.0" -source = "git+https://github.com/rust-vmm/vm-virtio.git#d8ef45f57b46baa99e80e555deffd3fa1ab9affc" +version = "0.14.0" +source = "git+https://github.com/rust-vmm/vm-virtio.git#2c21f2536b0ab84589f011f3c3c395803bf9c6f1" dependencies = [ "log", + "virtio-bindings", "vm-memory", "vmm-sys-util", ] [[package]] name = "vm-allocator" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "565b6886b7dd1b3bf34ec9243d90a97db4f2a83c2416caa52fcc95fd255d45e4" +checksum = "5e4ce718bd4e8d74b1747363e27f715a6b1bd6971597cb21425dadbf4e712241" dependencies = [ "libc", "thiserror", @@ -306,25 +491,26 @@ checksum = "599adbdaddea4947ca23c085d2b8e47f3499ccda35438424526f3853748a8eb6" [[package]] name = "vm-fdt" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43fb5a6bd1a7d423ad72802801036719b7546cf847a103f8fe4575f5b0d45a6" +checksum = "7e21282841a059bb62627ce8441c491f09603622cd5a21c43bfedc85a2952f23" [[package]] name = "vm-memory" -version = "0.7.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339d4349c126fdcd87e034631d7274370cf19eb0e87b33166bcd956589fc72c5" +checksum = "f1720e7240cdc739f935456eb77f370d7e9b2a3909204da1e2b47bef1137a013" dependencies = [ "libc", + "thiserror", "winapi", ] [[package]] name = "vm-superio" -version = "0.5.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b5231d334edbc03b22704caa1a022e4c07491d6df736593f26094df8b04a51" +checksum = "3428ee25acbfc75ed14600f2043876e0889cbd57c39dd441191417377cdceda0" [[package]] name = "vm-vcpu" @@ -349,8 +535,10 @@ dependencies = [ "kvm-bindings", "kvm-ioctls", "libc", + "log", "thiserror", "vm-memory", + "vm-superio", "vmm-sys-util", ] @@ -380,16 +568,17 @@ name = "vmm-reference" version = "0.1.0" dependencies = [ "api", + "linux-loader", "vmm", ] [[package]] name = "vmm-sys-util" -version = "0.8.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cf11afbc4ebc0d5c7a7748a77d19e2042677fc15faa2f4ccccb27c18a60605" +checksum = "1d1435039746e20da4f8d507a72ee1b916f7b4b05af7a91c093d2c6561934ede" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", ] @@ -410,16 +599,80 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "winapi-util" -version = "0.1.5" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "winapi", + "windows-targets", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index aa0039fb..08b63e84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ license = "Apache-2.0 OR BSD-3-Clause" [dependencies] vmm = { path = "src/vmm" } api = { path = "src/api" } +linux-loader = "0.13.0" [workspace] members = ["src/vm-vcpu-ref"] @@ -21,5 +22,5 @@ lto = true panic = "abort" [patch.crates-io] -# TODO: Using this patch until a version > 4.0 gets published. -linux-loader = { git = "https://github.com/rust-vmm/linux-loader.git", rev = "9a9f071" } +# TODO: Update with https://github.com/rust-vmm/linux-loader.git hash of commit +# "add as_string() to the Cmdline crate" diff --git a/src/api/Cargo.toml b/src/api/Cargo.toml index 7bc3dc38..5f1a9161 100644 --- a/src/api/Cargo.toml +++ b/src/api/Cargo.toml @@ -5,9 +5,9 @@ authors = ["rust-vmm AWS maintainers "] edition = "2018" [dependencies] -clap = "3.2.17" +clap = "4.5.28" vmm = { path = "../vmm" } [dev-dependencies] -linux-loader = "0.4.0" +linux-loader = "0.13.0" diff --git a/src/api/src/lib.rs b/src/api/src/lib.rs index 9d23c992..b73a366a 100644 --- a/src/api/src/lib.rs +++ b/src/api/src/lib.rs @@ -5,7 +5,7 @@ #![deny(missing_docs)] use std::result; -use clap::{App, Arg}; +use clap::{Arg, Command}; use vmm::VMMConfig; /// Command line parser. @@ -18,38 +18,45 @@ impl Cli { /// /// * `cmdline_args` - command line arguments passed to the application. pub fn launch(cmdline_args: Vec<&str>) -> result::Result { - let mut app = App::new(cmdline_args[0].to_string()) + let mut app = Command::new("vmm-reference") .arg( - Arg::with_name("memory") + Arg::new("memory") .long("memory") - .takes_value(true) .help("Guest memory configuration.\n\tFormat: \"size_mib=\""), ) .arg( - Arg::with_name("vcpu") + Arg::new("vcpu") .long("vcpu") - .takes_value(true) .help("vCPU configuration.\n\tFormat: \"num=\""), ) .arg( - Arg::with_name("kernel") + Arg::new("kernel") .long("kernel") .required(true) - .takes_value(true) .help("Kernel configuration.\n\tFormat: \"path=[,cmdline=,kernel_load_addr=]\""), ) .arg( - Arg::with_name("net") + Arg::new("net") .long("net") - .takes_value(true) .help("Network device configuration. \n\tFormat: \"tap=\"") ) .arg( - Arg::with_name("block") + Arg::new("block") .long("block") .required(false) - .takes_value(true) .help("Block device configuration. \n\tFormat: \"path=\"") + ) + .arg( + Arg::new("dump-dtb") + .long("dump-dtb") + .required(false) + .help("If set, dump the DTB to a file") + ) + .arg( + Arg::new("dtb") + .long("dtb") + .required(false) + .help("If set, takes as DTB the one at the specified path") ); // Save the usage beforehand as a string, because `get_matches` consumes the `App`. @@ -58,17 +65,19 @@ impl Cli { let _ = app.write_long_help(&mut help_msg_buf); let help_msg = String::from_utf8_lossy(&help_msg_buf); - let matches = app.get_matches_from_safe(cmdline_args).map_err(|e| { + let matches = app.try_get_matches_from(cmdline_args).map_err(|e| { eprintln!("{}", help_msg); format!("Invalid command line arguments: {}", e) })?; VMMConfig::builder() - .memory_config(matches.value_of("memory")) - .kernel_config(matches.value_of("kernel")) - .vcpu_config(matches.value_of("vcpu")) - .net_config(matches.value_of("net")) - .block_config(matches.value_of("block")) + .memory_config(matches.get_one::("memory")) + .kernel_config(matches.get_one::("kernel")) + .vcpu_config(matches.get_one::("vcpu")) + .net_config(matches.get_one::("net")) + .block_config(matches.get_one::("block")) + .dump_dtb_config(matches.get_one::("dump-dtb")) + .dtb_config(matches.get_one::("dtb")) .build() .map_err(|e| format!("{:?}", e)) } @@ -211,7 +220,7 @@ mod tests { ]) .is_err()); - let mut foo_cmdline = Cmdline::new(4096); + let mut foo_cmdline = Cmdline::new(4096).unwrap(); foo_cmdline.insert_str("\"foo=bar bar=foo\"").unwrap(); // OK. @@ -236,6 +245,8 @@ mod tests { vcpu_config: VcpuConfig { num: 1 }, block_config: None, net_config: None, + dump_dtb: None, + dtb: None, } ); @@ -252,6 +263,8 @@ mod tests { vcpu_config: VcpuConfig { num: 1 }, block_config: None, net_config: None, + dump_dtb: None, + dtb: None, } ); } diff --git a/src/arch/Cargo.toml b/src/arch/Cargo.toml index f3a2eb9b..a9c51bf9 100644 --- a/src/arch/Cargo.toml +++ b/src/arch/Cargo.toml @@ -7,5 +7,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -vm-fdt = "0.2.0" -vm-memory = "0.7.0" +log = "0.4.21" +vm-fdt = "0.3.0" +vm-memory = "0.16.0" +kvm-ioctls = { git = "https://github.com/rust-vmm/kvm.git"} +kvm-bindings = { git = "https://github.com/rust-vmm/kvm.git", features = ["fam-wrappers"] } diff --git a/src/arch/src/lib.rs b/src/arch/src/lib.rs index 5362b2e8..198352a1 100644 --- a/src/arch/src/lib.rs +++ b/src/arch/src/lib.rs @@ -2,401 +2,829 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -pub use vm_fdt::{Error as FdtError, FdtWriter}; -use vm_memory::{guest_memory::Error as GuestMemoryError, Bytes, GuestAddress, GuestMemory}; // This is an arbitrary number to specify the node for the GIC. // If we had a more complex interrupt architecture, then we'd need an enum for // these. -const PHANDLE_GIC: u32 = 1; - -pub const AARCH64_FDT_MAX_SIZE: u64 = 0x200000; - -// This indicates the start of DRAM inside the physical address space. -pub const AARCH64_PHYS_MEM_START: u64 = 0x80000000; - -// This is the base address of MMIO devices. -pub const AARCH64_MMIO_BASE: u64 = 1 << 30; - -const AARCH64_AXI_BASE: u64 = 0x40000000; - -// These constants indicate the address space used by the ARM vGIC. -const AARCH64_GIC_DIST_SIZE: u64 = 0x10000; -const AARCH64_GIC_CPUI_SIZE: u64 = 0x20000; - -// These constants indicate the placement of the GIC registers in the physical -// address space. -pub const AARCH64_GIC_DIST_BASE: u64 = AARCH64_AXI_BASE - AARCH64_GIC_DIST_SIZE; -pub const AARCH64_GIC_CPUI_BASE: u64 = AARCH64_GIC_DIST_BASE - AARCH64_GIC_CPUI_SIZE; -pub const AARCH64_GIC_REDIST_SIZE: u64 = 0x20000; - -// These are specified by the Linux GIC bindings -const GIC_FDT_IRQ_NUM_CELLS: u32 = 3; -const GIC_FDT_IRQ_TYPE_SPI: u32 = 0; -const GIC_FDT_IRQ_TYPE_PPI: u32 = 1; -const GIC_FDT_IRQ_PPI_CPU_SHIFT: u32 = 8; -const GIC_FDT_IRQ_PPI_CPU_MASK: u32 = 0xff << GIC_FDT_IRQ_PPI_CPU_SHIFT; -const IRQ_TYPE_EDGE_RISING: u32 = 0x00000001; -const IRQ_TYPE_LEVEL_HIGH: u32 = 0x00000004; -const IRQ_TYPE_LEVEL_LOW: u32 = 0x00000008; -// PMU PPI interrupt, same as qemu -const AARCH64_PMU_IRQ: u32 = 7; - -#[derive(Debug)] -pub enum Error { - Fdt(FdtError), - Memory(GuestMemoryError), - MissingRequiredConfig(String), -} +#[cfg(target_arch = "aarch64")] +pub mod aarch64_consts { + // These constants indicate the placement of the GIC registers in the physical + // address space. + pub const AARCH64_GIC_DIST_BASE: u64 = AARCH64_AXI_BASE - AARCH64_GIC_DIST_SIZE; -impl From for Error { - fn from(inner: FdtError) -> Self { - Error::Fdt(inner) - } -} + pub const AARCH64_GIC_CPUI_BASE: u64 = AARCH64_GIC_DIST_BASE - AARCH64_GIC_CPUI_SIZE; -impl From for Error { - fn from(inner: GuestMemoryError) -> Self { - Error::Memory(inner) - } -} + pub const AARCH64_GIC_REDIST_SIZE: u64 = 0x20000; + + pub const AARCH64_FDT_MAX_SIZE: u64 = 0x200000; + + // This indicates the start of DRAM inside the physical address space. + pub const AARCH64_PHYS_MEM_START: u64 = 0x80000000; + + // This is the base address of MMIO devices. + pub const AARCH64_MMIO_BASE: u64 = 1 << 30; + + pub const AARCH64_AXI_BASE: u64 = 0x40000000; + + // These constants indicate the address space used by the ARM vGIC. + pub const AARCH64_GIC_DIST_SIZE: u64 = 0x10000; + + pub const AARCH64_GIC_CPUI_SIZE: u64 = 0x20000; + + // These are specified by the Linux GIC bindings + pub const GIC_FDT_IRQ_NUM_CELLS: u32 = 3; + + pub const GIC_FDT_IRQ_TYPE_SPI: u32 = 0; + + pub const GIC_FDT_IRQ_TYPE_PPI: u32 = 1; + + pub const GIC_FDT_IRQ_PPI_CPU_SHIFT: u32 = 8; + + pub const GIC_FDT_IRQ_PPI_CPU_MASK: u32 = 0xff << GIC_FDT_IRQ_PPI_CPU_SHIFT; -pub type Result = std::result::Result; + // PMU PPI interrupt, same as qemu + pub const AARCH64_PMU_IRQ: u32 = 7; -/// It contains info about the virtio device for fdt. -struct DeviceInfo { - addr: u64, - size: u64, - irq: u32, + pub const IRQ_TYPE_EDGE_RISING: u32 = 0x00000001; + + pub const IRQ_TYPE_LEVEL_HIGH: u32 = 0x00000004; + + pub const IRQ_TYPE_LEVEL_LOW: u32 = 0x00000008; + + pub const PHANDLE_GIC: u32 = 1; } -#[derive(Default)] -pub struct FdtBuilder { - cmdline: Option, - mem_size: Option, - num_vcpus: Option, - serial_console: Option<(u64, u64)>, - rtc: Option<(u64, u64)>, - virtio_devices: Vec, +// RISCV +#[cfg(target_arch = "riscv64")] +pub mod riscv64_consts { + pub const RISCV_PLIC: u64 = 0x0c00_0000; + + pub const RISCV_PLIC_SIZE: u64 = 0x600_000; + + pub const RISCV_IMSIC: u64 = 0x28000000; + + pub const RISCV_IMSIC_SIZE: u64 = 0x1000; + + pub const PHANDLE_CPU_INTC_BASE: u32 = 1; + + pub const PHANDLE_APLIC: u32 = 0xd; + + pub const PHANDLE_IMSIC: u32 = 0x9; + + pub const MAX_DEVICES: u32 = 1024; + + // This indicates the start of DRAM inside the physical address space. + pub const RISCV64_PHYS_MEM_START: u64 = 0x80000000; + + pub const RISCV64_FDT_MAX_SIZE: u64 = 0x200000; + + pub const RISCV64_MMIO_BASE: u64 = 1 << 30; + + pub const FDT_APLIC_INT_CELLS: u32 = 2; + + pub const FDT_APLIC_ADDR_CELLS: u32 = 0; + + pub const VIRT_IRQCHIP_NUM_SOURCES: u32 = 96; + + pub const IRQ_S_EXT: u32 = 0x9; + + pub const VIRT_IRQCHIP_NUM_MSIS: u32 = 255; + + pub const FDT_IMSIC_INT_CELLS: u32 = 0; + + pub const IRQ_TYPE_EDGE_RISING: u32 = 0x00000001; + + pub const IRQ_TYPE_LEVEL_HIGH: u32 = 0x00000004; + + pub const IRQ_TYPE_LEVEL_LOW: u32 = 0x00000008; } -impl FdtBuilder { - pub fn new() -> Self { - FdtBuilder::default() +#[cfg(target_arch = "riscv64")] +mod riscv_regs; + +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +pub mod fdt { + #[cfg(target_arch = "riscv64")] + use kvm_bindings::*; + #[cfg(target_arch = "riscv64")] + use kvm_ioctls::VcpuFd; + use log::debug; + #[cfg(target_arch = "riscv64")] + use std::mem::offset_of; + use std::path::PathBuf; + use std::{ + fs::File, + io::{self, Read, Write}, + }; + pub use vm_fdt::{Error as FdtError, FdtWriter}; + use vm_memory::{guest_memory::Error as GuestMemoryError, Bytes, GuestAddress, GuestMemory}; + + #[cfg(target_arch = "aarch64")] + use crate::aarch64_consts::*; + + #[cfg(target_arch = "riscv64")] + use crate::riscv64_consts::*; + #[cfg(target_arch = "riscv64")] + use crate::*; + #[cfg(target_arch = "riscv64")] + const ISA_INFO_ARR: [(&str, u32); 9] = [ + /* sorted alphabetically */ + ("ssaia", KVM_RISCV_ISA_EXT_ID_KVM_RISCV_ISA_EXT_SSAIA), + ("sstc", KVM_RISCV_ISA_EXT_ID_KVM_RISCV_ISA_EXT_SSTC), + ("svinval", KVM_RISCV_ISA_EXT_ID_KVM_RISCV_ISA_EXT_SVINVAL), + ("svnapot", KVM_RISCV_ISA_EXT_ID_KVM_RISCV_ISA_EXT_SVNAPOT), + ("svpbmt", KVM_RISCV_ISA_EXT_ID_KVM_RISCV_ISA_EXT_SVPBMT), + ("zbb", KVM_RISCV_ISA_EXT_ID_KVM_RISCV_ISA_EXT_ZBB), + ("zicbom", KVM_RISCV_ISA_EXT_ID_KVM_RISCV_ISA_EXT_ZICBOM), + ("zicboz", KVM_RISCV_ISA_EXT_ID_KVM_RISCV_ISA_EXT_ZICBOZ), + ( + "zihintpause", + KVM_RISCV_ISA_EXT_ID_KVM_RISCV_ISA_EXT_ZIHINTPAUSE, + ), + ]; + #[derive(Debug)] + pub enum Error { + Fdt(FdtError), + Memory(GuestMemoryError), + MissingRequiredConfig(String), + FdtCustomDtb(io::Error), } - pub fn with_cmdline(&mut self, cmdline: String) -> &mut Self { - self.cmdline = Some(cmdline); - self + impl From for Error { + fn from(inner: FdtError) -> Self { + Error::Fdt(inner) + } } - pub fn with_mem_size(&mut self, mem_size: u64) -> &mut Self { - self.mem_size = Some(mem_size); - self + impl From for Error { + fn from(inner: GuestMemoryError) -> Self { + Error::Memory(inner) + } } - pub fn with_num_vcpus(&mut self, num_vcpus: u32) -> &mut Self { - self.num_vcpus = Some(num_vcpus); - self + impl From for Error { + fn from(inner: io::Error) -> Self { + Error::FdtCustomDtb(inner) + } } - pub fn with_serial_console(&mut self, addr: u64, size: u64) -> &mut Self { - self.serial_console = Some((addr, size)); - self + pub type Result = std::result::Result; + + /// It contains info about the virtio device for fdt. + struct DeviceInfo { + addr: u64, + size: u64, + irq: u32, } - pub fn with_rtc(&mut self, addr: u64, size: u64) -> &mut Self { - self.rtc = Some((addr, size)); - self + #[derive(Default)] + pub struct FdtBuilder { + cmdline: Option, + mem_size: Option, + num_vcpus: Option, + serial_console: Option<(u64, u64)>, + rtc: Option<(u64, u64)>, + virtio_devices: Vec, + prebuilt: Option, + } + + impl FdtBuilder { + pub fn new() -> Self { + FdtBuilder::default() + } + + pub fn with_prebuilt_fdt(&mut self, prebuilt_fdt_path: PathBuf) -> Result<&mut Self> { + let mut fdt_file = File::options() + .read(true) + .create(false) + .write(false) + .append(false) + .open(prebuilt_fdt_path) + .map_err(Error::FdtCustomDtb)?; + + let mut prebuilt_fdt = Fdt::default(); + fdt_file + .read_to_end(&mut prebuilt_fdt.fdt_blob) + .map_err(Error::FdtCustomDtb)?; + self.prebuilt = Some(prebuilt_fdt); + Ok(self) + } + + pub fn has_prebuilt_fdt(&self) -> bool { + self.prebuilt.is_some() + } + + pub fn get_prebuilt_fdt(&self) -> Option { + self.prebuilt.clone() + } + + pub fn with_cmdline(&mut self, cmdline: String) -> &mut Self { + self.cmdline = Some(cmdline); + self + } + + pub fn with_mem_size(&mut self, mem_size: u64) -> &mut Self { + self.mem_size = Some(mem_size); + self + } + + pub fn with_num_vcpus(&mut self, num_vcpus: u32) -> &mut Self { + self.num_vcpus = Some(num_vcpus); + self + } + + pub fn with_serial_console(&mut self, addr: u64, size: u64) -> &mut Self { + self.serial_console = Some((addr, size)); + self + } + + pub fn with_rtc(&mut self, addr: u64, size: u64) -> &mut Self { + self.rtc = Some((addr, size)); + self + } + + pub fn add_virtio_device(&mut self, addr: u64, size: u64, irq: u32) -> &mut Self { + self.virtio_devices.push(DeviceInfo { addr, size, irq }); + self + } + + pub fn virtio_device_len(&self) -> usize { + self.virtio_devices.len() + } + + #[allow(clippy::needless_borrow)] + pub fn create_fdt( + &mut self, + #[cfg(target_arch = "riscv64")] vcpufd: &VcpuFd, + ) -> Result { + if let Some(fdt) = &mut self.prebuilt { + let ret: Fdt = fdt.clone(); + fdt.fdt_blob.clear(); + debug!("FdtBuilder: Returning prebuilt FDT"); + return Ok(ret); + } + + let mut fdt = FdtWriter::new()?; + + // The whole thing is put into one giant node with s + // ome top level properties + let root_node = fdt.begin_node("")?; + #[cfg(target_arch = "aarch64")] + fdt.property_u32("interrupt-parent", PHANDLE_GIC)?; + fdt.property_u32("#address-cells", 0x2)?; + fdt.property_u32("#size-cells", 0x2)?; + fdt.property_string("compatible", "riscv-virtio")?; + fdt.property_string("model", "linux,dummy-virt")?; + + let cmdline = self + .cmdline + .as_ref() + .ok_or_else(|| Error::MissingRequiredConfig("cmdline".to_owned()))?; + create_chosen_node(&mut fdt, cmdline)?; + + let mem_size = self + .mem_size + .ok_or_else(|| Error::MissingRequiredConfig("memory".to_owned()))?; + create_memory_node(&mut fdt, mem_size)?; + + let num_vcpus = self + .num_vcpus + .ok_or_else(|| Error::MissingRequiredConfig("vcpu".to_owned()))?; + + #[cfg(target_arch = "aarch64")] + { + create_cpu_nodes(&mut fdt, num_vcpus)?; + create_gic_node(&mut fdt, true, num_vcpus as u64)?; + } + #[cfg(target_arch = "riscv64")] + { + create_cpu_nodes(&mut fdt, num_vcpus, &vcpufd)?; + create_aplic_node(&mut fdt)?; + create_imsic_node(&mut fdt, num_vcpus)?; + } + + if let Some(serial_console) = self.serial_console { + create_serial_node(&mut fdt, serial_console.0, serial_console.1)?; + } + if let Some(rtc) = self.rtc { + create_rtc_node(&mut fdt, rtc.0, rtc.1)?; + } + + #[cfg(target_arch = "aarch64")] + { + create_timer_node(&mut fdt, num_vcpus)?; + create_psci_node(&mut fdt)?; + create_pmu_node(&mut fdt, num_vcpus)?; + } + + for info in &self.virtio_devices { + create_virtio_node(&mut fdt, info.addr, info.size, info.irq)?; + } + + fdt.end_node(root_node)?; + + Ok(Fdt { + fdt_blob: fdt.finish()?, + }) + } } - pub fn add_virtio_device(&mut self, addr: u64, size: u64, irq: u32) -> &mut Self { - self.virtio_devices.push(DeviceInfo { addr, size, irq }); - self + #[derive(Default, Clone)] + pub struct Fdt { + fdt_blob: Vec, } - pub fn virtio_device_len(&self) -> usize { - self.virtio_devices.len() + impl Fdt { + pub fn write_to_mem( + &self, + guest_mem: &T, + fdt_load_offset: u64, + ) -> Result<()> { + #[cfg(target_arch = "aarch64")] + let fdt_address = GuestAddress(AARCH64_PHYS_MEM_START + fdt_load_offset); + #[cfg(target_arch = "riscv64")] + let fdt_address = GuestAddress(RISCV64_PHYS_MEM_START + fdt_load_offset); + guest_mem.write_slice(self.fdt_blob.as_slice(), fdt_address)?; + Ok(()) + } + + pub fn write_to_file(&self, path: &str) -> std::io::Result<()> { + let mut file = File::options() + .read(false) + .create(true) + .truncate(true) + .write(true) + .append(false) + .open(path)?; + file.write_all(&self.fdt_blob)?; + Ok(()) + } } - pub fn create_fdt(&self) -> Result { - let mut fdt = FdtWriter::new()?; - - // The whole thing is put into one giant node with s - // ome top level properties - let root_node = fdt.begin_node("")?; - fdt.property_u32("interrupt-parent", PHANDLE_GIC)?; - fdt.property_string("compatible", "linux,dummy-virt")?; - fdt.property_u32("#address-cells", 0x2)?; - fdt.property_u32("#size-cells", 0x2)?; - - let cmdline = self - .cmdline - .as_ref() - .ok_or_else(|| Error::MissingRequiredConfig("cmdline".to_owned()))?; - create_chosen_node(&mut fdt, cmdline)?; - - let mem_size = self - .mem_size - .ok_or_else(|| Error::MissingRequiredConfig("memory".to_owned()))?; - create_memory_node(&mut fdt, mem_size)?; - let num_vcpus = self - .num_vcpus - .ok_or_else(|| Error::MissingRequiredConfig("vcpu".to_owned()))?; - create_cpu_nodes(&mut fdt, num_vcpus)?; - create_gic_node(&mut fdt, true, num_vcpus as u64)?; - if let Some(serial_console) = self.serial_console { - create_serial_node(&mut fdt, serial_console.0, serial_console.1)?; - } - if let Some(rtc) = self.rtc { - create_rtc_node(&mut fdt, rtc.0, rtc.1)?; - } - create_timer_node(&mut fdt, num_vcpus)?; - create_psci_node(&mut fdt)?; - create_pmu_node(&mut fdt, num_vcpus)?; - for info in &self.virtio_devices { - create_virtio_node(&mut fdt, info.addr, info.size, info.irq)?; - } - fdt.end_node(root_node)?; - - Ok(Fdt { - fdt_blob: fdt.finish()?, - }) + fn create_chosen_node(fdt: &mut FdtWriter, cmdline: &str) -> Result<()> { + let chosen_node = fdt.begin_node("chosen")?; + fdt.property_string("bootargs", cmdline)?; + fdt.end_node(chosen_node)?; + + Ok(()) } -} -pub struct Fdt { - fdt_blob: Vec, -} + fn create_memory_node(fdt: &mut FdtWriter, mem_size: u64) -> Result<()> { + #[cfg(target_arch = "aarch64")] + { + let mem_reg_prop = [AARCH64_PHYS_MEM_START, mem_size]; + let memory_str = format!("memory@{:x}", AARCH64_PHYS_MEM_START); + let memory_node = fdt.begin_node(&memory_str)?; + fdt.property_string("device_type", "memory")?; + fdt.property_array_u64("reg", &mem_reg_prop)?; + fdt.end_node(memory_node)?; + } + #[cfg(target_arch = "riscv64")] + { + let mem_reg_prop = [RISCV64_PHYS_MEM_START, mem_size]; + let memory_str = format!("memory@{:x}", RISCV64_PHYS_MEM_START); + let memory_node = fdt.begin_node(&memory_str)?; + fdt.property_string("device_type", "memory")?; + fdt.property_array_u64("reg", &mem_reg_prop)?; + fdt.end_node(memory_node)?; + } + Ok(()) + } -impl Fdt { - pub fn write_to_mem(&self, guest_mem: &T, fdt_load_offset: u64) -> Result<()> { - let fdt_address = GuestAddress(AARCH64_PHYS_MEM_START + fdt_load_offset); - guest_mem.write_slice(self.fdt_blob.as_slice(), fdt_address)?; + #[cfg(target_arch = "aarch64")] + fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> { + let cpus_node = fdt.begin_node("cpus")?; + fdt.property_u32("#address-cells", 0x1)?; + fdt.property_u32("#size-cells", 0x0)?; + + for cpu_id in 0..num_cpus { + let cpu_name = format!("cpu@{:x}", cpu_id); + let cpu_node = fdt.begin_node(&cpu_name)?; + fdt.property_string("device_type", "cpu")?; + fdt.property_u32("reg", cpu_id)?; + fdt.property_string("compatible", "arm,arm-v8")?; + fdt.property_string("enable-method", "psci")?; + fdt.end_node(cpu_node)?; + } + fdt.end_node(cpus_node)?; Ok(()) } -} -fn create_chosen_node(fdt: &mut FdtWriter, cmdline: &str) -> Result<()> { - let chosen_node = fdt.begin_node("chosen")?; - fdt.property_string("bootargs", cmdline)?; - fdt.end_node(chosen_node)?; + #[cfg(target_arch = "riscv64")] + fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32, vcpu_fd: &VcpuFd) -> Result<()> { + let isa_id: u64 = riscv_config_reg!(isa); + let mut data = [0_u8; 8]; + vcpu_fd.get_one_reg(isa_id, &mut data).expect("Error"); + let isa = u64::from_le_bytes(data); + let valid_isa_order = "IEMAFDQCLBJTPVNSUHKORWXYZG"; + let arr_sz = ISA_INFO_ARR.len(); + let cpus_node = fdt.begin_node("cpus")?; + fdt.property_u32("#address-cells", 0x1)?; + fdt.property_u32("#size-cells", 0x0)?; + let timer_id: u64 = riscv_timer_reg!(frequency); + let mut data = [0_u8; 8]; + vcpu_fd.get_one_reg(timer_id, &mut data).expect("Error"); + let frequency = u64::from_le_bytes(data); + fdt.property_u32("timebase-frequency", frequency as u32)?; + + for cpu_id in 0..num_cpus { + let mut cpu_isa = String::from("rv64"); + let mut cbom_blksz = 0; + let mut cboz_blksz = 0; + for c in valid_isa_order.chars() { + let index = c as u32 - 'A' as u32; + if (isa & (1 << index)) != 0 { + let ret = 'a' as u32 + index; + cpu_isa += &std::char::from_u32(ret).unwrap().to_string(); + } + } + + for info in ISA_INFO_ARR.iter().take(arr_sz) { + let reg_id = riscv_isa_ext_reg!(info.1 as u64); + let mut data = [0_u8; 8]; + if vcpu_fd.get_one_reg(reg_id, &mut data).is_err() { + continue; + } + let isa_ext_out = u64::from_le_bytes(data); + if isa_ext_out == 0 { + continue; + } + + if info.1 == KVM_RISCV_ISA_EXT_ID_KVM_RISCV_ISA_EXT_ZICBOM && cbom_blksz == 0 { + vcpu_fd.get_one_reg(reg_id, &mut data).expect("Error"); + cbom_blksz = u64::from_le_bytes(data); + } + + if info.1 == KVM_RISCV_ISA_EXT_ID_KVM_RISCV_ISA_EXT_ZICBOZ && cboz_blksz == 0 { + vcpu_fd.get_one_reg(reg_id, &mut data).expect("Error"); + cboz_blksz = u64::from_le_bytes(data); + } + cpu_isa += "_"; + cpu_isa += info.0; + } + + let reg_id = riscv_config_reg!(satp_mode); + let mut data = [0_u8; 8]; + vcpu_fd.get_one_reg(reg_id, &mut data).expect("Error"); + let satp_mode = u64::from_le_bytes(data); + + let cpu_name = format!("cpu@{:x}", cpu_id); + let cpu_node = fdt.begin_node(&cpu_name)?; + fdt.property_string("device_type", "cpu")?; + fdt.property_string("compatible", "riscv")?; + match satp_mode { + 10 => fdt.property_string("mmu-type", "riscv,sv57")?, + 9 => fdt.property_string("mmu-type", "riscv,sv48")?, + 8 => fdt.property_string("mmu-type", "riscv,sv39")?, + _ => fdt.property_string("mmu-type", "riscv,none")?, + } + fdt.property_string("riscv,isa", &cpu_isa)?; + if cbom_blksz != 0 { + fdt.property_u32("riscv,cbom-block-size", cbom_blksz as u32)?; + } + + if cbom_blksz != 0 { + fdt.property_u32("riscv,cboz-block-size", cboz_blksz as u32)?; + } + + fdt.property_u32("reg", cpu_id)?; + fdt.property_string("status", "okay")?; + let intc_node = fdt.begin_node("interrupt-controller")?; + fdt.property_u32("#interrupt-cells", 1)?; + fdt.property_string("compatible", "riscv,cpu-intc")?; + fdt.property_null("interrupt-controller")?; + fdt.property_phandle(PHANDLE_CPU_INTC_BASE + cpu_id)?; + fdt.end_node(intc_node)?; + fdt.end_node(cpu_node)?; + } + fdt.end_node(cpus_node)?; + Ok(()) + } - Ok(()) -} + #[cfg(target_arch = "aarch64")] + fn create_gic_node(fdt: &mut FdtWriter, is_gicv3: bool, num_cpus: u64) -> Result<()> { + let mut gic_reg_prop = [AARCH64_GIC_DIST_BASE, AARCH64_GIC_DIST_SIZE, 0, 0]; + + let intc_node = fdt.begin_node("intc")?; + if is_gicv3 { + fdt.property_string("compatible", "arm,gic-v3")?; + gic_reg_prop[2] = AARCH64_GIC_DIST_BASE - (AARCH64_GIC_REDIST_SIZE * num_cpus); + gic_reg_prop[3] = AARCH64_GIC_REDIST_SIZE * num_cpus; + } else { + fdt.property_string("compatible", "arm,cortex-a15-gic")?; + gic_reg_prop[2] = AARCH64_GIC_CPUI_BASE; + gic_reg_prop[3] = AARCH64_GIC_CPUI_SIZE; + } + fdt.property_u32("#interrupt-cells", GIC_FDT_IRQ_NUM_CELLS)?; + fdt.property_null("interrupt-controller")?; + fdt.property_array_u64("reg", &gic_reg_prop)?; + fdt.property_phandle(PHANDLE_GIC)?; + fdt.property_u32("#address-cells", 2)?; + fdt.property_u32("#size-cells", 2)?; + fdt.end_node(intc_node)?; -fn create_memory_node(fdt: &mut FdtWriter, mem_size: u64) -> Result<()> { - let mem_reg_prop = [AARCH64_PHYS_MEM_START, mem_size]; + Ok(()) + } - let memory_node = fdt.begin_node("memory")?; - fdt.property_string("device_type", "memory")?; - fdt.property_array_u64("reg", &mem_reg_prop)?; - fdt.end_node(memory_node)?; - Ok(()) -} + #[cfg(target_arch = "riscv64")] + fn create_aplic_node(fdt: &mut FdtWriter) -> Result<()> { + let intc_node = fdt.begin_node(&format!("aplic@{:x}", RISCV_PLIC))?; + fdt.property_phandle(PHANDLE_APLIC)?; + fdt.property_u32("riscv,num-sources", VIRT_IRQCHIP_NUM_SOURCES)?; + fdt.property_u32("msi-parent", PHANDLE_IMSIC)?; + fdt.property_u32("riscv,ndev", MAX_DEVICES - 1)?; + let plic_reg_prop = [RISCV_PLIC, RISCV_PLIC_SIZE]; + fdt.property_array_u64("reg", &plic_reg_prop)?; + fdt.property_null("interrupt-controller")?; + fdt.property_string("compatible", "riscv,aplic")?; + fdt.property_u32("#address-cells", FDT_APLIC_ADDR_CELLS)?; + fdt.property_u32("#interrupt-cells", FDT_APLIC_INT_CELLS)?; + fdt.end_node(intc_node)?; -fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> { - let cpus_node = fdt.begin_node("cpus")?; - fdt.property_u32("#address-cells", 0x1)?; - fdt.property_u32("#size-cells", 0x0)?; - - for cpu_id in 0..num_cpus { - let cpu_name = format!("cpu@{:x}", cpu_id); - let cpu_node = fdt.begin_node(&cpu_name)?; - fdt.property_string("device_type", "cpu")?; - fdt.property_string("compatible", "arm,arm-v8")?; - fdt.property_string("enable-method", "psci")?; - fdt.property_u32("reg", cpu_id)?; - fdt.end_node(cpu_node)?; + Ok(()) } - fdt.end_node(cpus_node)?; - Ok(()) -} -fn create_gic_node(fdt: &mut FdtWriter, is_gicv3: bool, num_cpus: u64) -> Result<()> { - let mut gic_reg_prop = [AARCH64_GIC_DIST_BASE, AARCH64_GIC_DIST_SIZE, 0, 0]; - - let intc_node = fdt.begin_node("intc")?; - if is_gicv3 { - fdt.property_string("compatible", "arm,gic-v3")?; - gic_reg_prop[2] = AARCH64_GIC_DIST_BASE - (AARCH64_GIC_REDIST_SIZE * num_cpus); - gic_reg_prop[3] = AARCH64_GIC_REDIST_SIZE * num_cpus; - } else { - fdt.property_string("compatible", "arm,cortex-a15-gic")?; - gic_reg_prop[2] = AARCH64_GIC_CPUI_BASE; - gic_reg_prop[3] = AARCH64_GIC_CPUI_SIZE; + #[cfg(target_arch = "riscv64")] + fn create_imsic_node(fdt: &mut FdtWriter, num_vcpus: u32) -> Result<()> { + let imsic_cells: Vec<_> = (0..num_vcpus) + .flat_map(|cpu| vec![PHANDLE_CPU_INTC_BASE + cpu, IRQ_S_EXT]) + .collect(); + + let imsic_regs = [RISCV_IMSIC, 0x8000]; + + let intc_node = fdt.begin_node(&format!("imsic@{:x}", RISCV_IMSIC))?; + fdt.property_array_u64("reg", &imsic_regs)?; + fdt.property_array_u32("interrupts-extended", &imsic_cells)?; + fdt.property_u32("#interrupt-cells", FDT_IMSIC_INT_CELLS)?; + fdt.property_null("msi-controller")?; + fdt.property_null("interrupt-controller")?; + fdt.property_string("compatible", "riscv,imsics")?; + fdt.property_phandle(PHANDLE_IMSIC)?; + fdt.property_u32("riscv,num-ids", VIRT_IRQCHIP_NUM_MSIS)?; + fdt.end_node(intc_node)?; + Ok(()) } - fdt.property_u32("#interrupt-cells", GIC_FDT_IRQ_NUM_CELLS)?; - fdt.property_null("interrupt-controller")?; - fdt.property_array_u64("reg", &gic_reg_prop)?; - fdt.property_phandle(PHANDLE_GIC)?; - fdt.property_u32("#address-cells", 2)?; - fdt.property_u32("#size-cells", 2)?; - fdt.end_node(intc_node)?; - - Ok(()) -} -fn create_psci_node(fdt: &mut FdtWriter) -> Result<()> { - let compatible = "arm,psci-0.2"; - let psci_node = fdt.begin_node("psci")?; - fdt.property_string("compatible", compatible)?; - // Two methods available: hvc and smc. - // As per documentation, PSCI calls between a guest and hypervisor may use the HVC conduit instead of SMC. - // So, since we are using kvm, we need to use hvc. - fdt.property_string("method", "hvc")?; - fdt.end_node(psci_node)?; - - Ok(()) -} + #[cfg(target_arch = "aarch64")] + fn create_psci_node(fdt: &mut FdtWriter) -> Result<()> { + let compatible = "arm,psci-0.2"; + let psci_node = fdt.begin_node("psci")?; + fdt.property_string("compatible", compatible)?; + // Two methods available: hvc and smc. + // As per documentation, PSCI calls between a guest and hypervisor may use the HVC conduit instead of SMC. + // So, since we are using kvm, we need to use hvc. + fdt.property_string("method", "hvc")?; + fdt.end_node(psci_node)?; -fn create_serial_node(fdt: &mut FdtWriter, addr: u64, size: u64) -> Result<()> { - let serial_node = fdt.begin_node(&format!("uart@{:x}", addr))?; - fdt.property_string("compatible", "ns16550a")?; - let serial_reg_prop = [addr, size]; - fdt.property_array_u64("reg", &serial_reg_prop)?; - - // fdt.property_u32("clock-frequency", AARCH64_SERIAL_SPEED)?; - const CLK_PHANDLE: u32 = 24; - fdt.property_u32("clocks", CLK_PHANDLE)?; - fdt.property_string("clock-names", "apb_pclk")?; - let irq = [GIC_FDT_IRQ_TYPE_SPI, 4, IRQ_TYPE_EDGE_RISING]; - fdt.property_array_u32("interrupts", &irq)?; - fdt.end_node(serial_node)?; - Ok(()) -} + Ok(()) + } -fn create_timer_node(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> { - // These are fixed interrupt numbers for the timer device. - let irqs = [13, 14, 11, 10]; - let compatible = "arm,armv8-timer"; - let cpu_mask: u32 = - (((1 << num_cpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) & GIC_FDT_IRQ_PPI_CPU_MASK; - - let mut timer_reg_cells = Vec::new(); - for &irq in &irqs { - timer_reg_cells.push(GIC_FDT_IRQ_TYPE_PPI); - timer_reg_cells.push(irq); - timer_reg_cells.push(cpu_mask | IRQ_TYPE_LEVEL_LOW); + fn create_serial_node(fdt: &mut FdtWriter, addr: u64, size: u64) -> Result<()> { + #[cfg(target_arch = "aarch64")] + { + let serial_node = fdt.begin_node(&format!("uart@{:x}", addr))?; + fdt.property_string("compatible", "ns16550a")?; + let serial_reg_prop = [addr, size]; + fdt.property_array_u64("reg", &serial_reg_prop)?; + + const CLK_PHANDLE: u32 = 24; + fdt.property_u32("clocks", CLK_PHANDLE)?; + fdt.property_string("clock-names", "apb_pclk")?; + let irq = [GIC_FDT_IRQ_TYPE_SPI, 4, IRQ_TYPE_EDGE_RISING]; + fdt.property_array_u32("interrupts", &irq)?; + fdt.end_node(serial_node)?; + } + #[cfg(target_arch = "riscv64")] + { + let serial_node = fdt.begin_node(&format!("serial@{:x}", addr))?; + fdt.property_array_u32("interrupts", &[4, IRQ_TYPE_LEVEL_HIGH])?; + fdt.property_u32("interrupt-parent", PHANDLE_APLIC)?; + let strs = vec!["".into(), "8@".into()]; + + fdt.property_string_list("clock-frequency", strs)?; + let serial_reg_prop = [addr, size]; + fdt.property_array_u64("reg", &serial_reg_prop)?; + fdt.property_string("compatible", "ns16550a")?; + fdt.end_node(serial_node)?; + } + Ok(()) } - let timer_node = fdt.begin_node("timer")?; - fdt.property_string("compatible", compatible)?; - fdt.property_array_u32("interrupts", &timer_reg_cells)?; - fdt.property_null("always-on")?; - fdt.end_node(timer_node)?; + #[cfg(target_arch = "aarch64")] + fn create_timer_node(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> { + // These are fixed interrupt numbers for the timer device. + let irqs = [13, 14, 11, 10]; + let compatible = "arm,armv8-timer"; + let cpu_mask: u32 = + (((1 << num_cpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) & GIC_FDT_IRQ_PPI_CPU_MASK; + + let mut timer_reg_cells = Vec::new(); + for &irq in &irqs { + timer_reg_cells.push(GIC_FDT_IRQ_TYPE_PPI); + timer_reg_cells.push(irq); + timer_reg_cells.push(cpu_mask | IRQ_TYPE_LEVEL_LOW); + } - Ok(()) -} + let timer_node = fdt.begin_node("timer")?; + fdt.property_string("compatible", compatible)?; + fdt.property_array_u32("interrupts", &timer_reg_cells)?; + fdt.property_null("always-on")?; + fdt.end_node(timer_node)?; -fn create_pmu_node(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> { - let compatible = "arm,armv8-pmuv3"; - let cpu_mask: u32 = - (((1 << num_cpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) & GIC_FDT_IRQ_PPI_CPU_MASK; - let irq = [ - GIC_FDT_IRQ_TYPE_PPI, - AARCH64_PMU_IRQ, - cpu_mask | IRQ_TYPE_LEVEL_HIGH, - ]; + Ok(()) + } - let pmu_node = fdt.begin_node("pmu")?; - fdt.property_string("compatible", compatible)?; - fdt.property_array_u32("interrupts", &irq)?; - fdt.end_node(pmu_node)?; - Ok(()) -} + #[cfg(target_arch = "aarch64")] + fn create_pmu_node(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> { + let compatible = "arm,armv8-pmuv3"; + let cpu_mask: u32 = + (((1 << num_cpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) & GIC_FDT_IRQ_PPI_CPU_MASK; + let irq = [ + GIC_FDT_IRQ_TYPE_PPI, + AARCH64_PMU_IRQ, + cpu_mask | IRQ_TYPE_LEVEL_HIGH, + ]; + + let pmu_node = fdt.begin_node("pmu")?; + fdt.property_string("compatible", compatible)?; + fdt.property_array_u32("interrupts", &irq)?; + fdt.end_node(pmu_node)?; + Ok(()) + } -fn create_rtc_node(fdt: &mut FdtWriter, rtc_addr: u64, size: u64) -> Result<()> { - // the kernel driver for pl030 really really wants a clock node - // associated with an AMBA device or it will fail to probe, so we - // need to make up a clock node to associate with the pl030 rtc - // node and an associated handle with a unique phandle value. - const CLK_PHANDLE: u32 = 24; - let clock_node = fdt.begin_node("apb-pclk")?; - fdt.property_u32("#clock-cells", 0)?; - fdt.property_string("compatible", "fixed-clock")?; - fdt.property_u32("clock-frequency", 24_000_000)?; - fdt.property_string("clock-output-names", "clk24mhz")?; - fdt.property_phandle(CLK_PHANDLE)?; - fdt.end_node(clock_node)?; - - let rtc_name = format!("rtc@{:x}", rtc_addr); - let reg = [rtc_addr, size]; - let irq = [GIC_FDT_IRQ_TYPE_SPI, 33, IRQ_TYPE_LEVEL_HIGH]; - - let rtc_node = fdt.begin_node(&rtc_name)?; - fdt.property_string_list( - "compatible", - vec![String::from("arm,pl031"), String::from("arm,primecell")], - )?; - // const PL030_AMBA_ID: u32 = 0x00041030; - // fdt.property_string("arm,pl031", PL030_AMBA_ID)?; - fdt.property_array_u64("reg", ®)?; - fdt.property_array_u32("interrupts", &irq)?; - fdt.property_u32("clocks", CLK_PHANDLE)?; - fdt.property_string("clock-names", "apb_pclk")?; - fdt.end_node(rtc_node)?; - Ok(()) -} + fn create_rtc_node(fdt: &mut FdtWriter, rtc_addr: u64, size: u64) -> Result<()> { + // the kernel driver for pl030 really really wants a clock node + // associated with an AMBA device or it will fail to probe, so we + // need to make up a clock node to associate with the pl030 rtc + // node and an associated handle with a unique phandle value. + #[cfg(target_arch = "aarch64")] + { + const CLK_PHANDLE: u32 = 24; + let clock_node = fdt.begin_node("apb-pclk")?; + fdt.property_u32("#clock-cells", 0)?; + fdt.property_string("compatible", "fixed-clock")?; + fdt.property_u32("clock-frequency", 24_000_000)?; + fdt.property_string("clock-output-names", "clk24mhz")?; + fdt.property_phandle(CLK_PHANDLE)?; + fdt.end_node(clock_node)?; + + let rtc_name = format!("rtc@{:x}", rtc_addr); + let reg = [rtc_addr, size]; + let irq = [GIC_FDT_IRQ_TYPE_SPI, 33, IRQ_TYPE_LEVEL_HIGH]; + + let rtc_node = fdt.begin_node(&rtc_name)?; + fdt.property_string_list( + "compatible", + vec![String::from("arm,pl031"), String::from("arm,primecell")], + )?; + // const PL030_AMBA_ID: u32 = 0x00041030; + // fdt.property_string("arm,pl031", PL030_AMBA_ID)?; + fdt.property_array_u64("reg", ®)?; + fdt.property_array_u32("interrupts", &irq)?; + fdt.property_u32("clocks", CLK_PHANDLE)?; + fdt.property_string("clock-names", "apb_pclk")?; + fdt.end_node(rtc_node)?; + } + #[cfg(target_arch = "riscv64")] + { + const CLK_PHANDLE: u32 = 24; + let clock_node = fdt.begin_node("apb-pclk")?; + fdt.property_u32("#clock-cells", 0)?; + fdt.property_string("compatible", "fixed-clock")?; + fdt.property_u32("clock-frequency", 24_000_000)?; + fdt.property_string("clock-output-names", "clk24mhz")?; + fdt.property_phandle(CLK_PHANDLE)?; + fdt.end_node(clock_node)?; + + let rtc_name = format!("rtc@{:x}", rtc_addr); + let reg = [rtc_addr, size]; + + let rtc_node = fdt.begin_node(&rtc_name)?; + fdt.property_string("compatible", "apb_pclk")?; + fdt.property_array_u64("reg", ®)?; + fdt.property_array_u32("interrupts", &[PHANDLE_CPU_INTC_BASE, IRQ_TYPE_LEVEL_HIGH])?; + fdt.property_u32("interrupt-parent", PHANDLE_APLIC)?; + fdt.property_u32("clocks", CLK_PHANDLE)?; + fdt.end_node(rtc_node)?; + } + Ok(()) + } -fn create_virtio_node(fdt: &mut FdtWriter, addr: u64, size: u64, irq: u32) -> Result<()> { - let virtio_mmio = fdt.begin_node(&format!("virtio_mmio@{:x}", addr))?; - fdt.property_string("compatible", "virtio,mmio")?; - fdt.property_array_u64("reg", &[addr, size])?; - fdt.property_array_u32( - "interrupts", - &[GIC_FDT_IRQ_TYPE_SPI, irq, IRQ_TYPE_EDGE_RISING], - )?; - fdt.property_array_u32("interrupt-parent", &[PHANDLE_GIC])?; - fdt.end_node(virtio_mmio)?; - Ok(()) -} + fn create_virtio_node(fdt: &mut FdtWriter, addr: u64, size: u64, irq: u32) -> Result<()> { + let virtio_mmio = fdt.begin_node(&format!("virtio_mmio@{:x}", addr))?; + fdt.property_string("compatible", "virtio,mmio")?; + fdt.property_array_u64("reg", &[addr, size])?; + #[cfg(target_arch = "aarch64")] + { + fdt.property_array_u32( + "interrupts", + &[GIC_FDT_IRQ_TYPE_SPI, irq, IRQ_TYPE_EDGE_RISING], + )?; + fdt.property_array_u32("interrupt-parent", &[PHANDLE_GIC])?; + } -#[cfg(test)] -mod tests { - use crate::FdtBuilder; + #[cfg(target_arch = "riscv64")] + { + fdt.property_array_u32("interrupts", &[irq, IRQ_TYPE_LEVEL_HIGH])?; + fdt.property_array_u32("interrupt-parent", &[PHANDLE_APLIC])?; + } - #[test] - fn test_adding_virtio() { - let mut fdt = FdtBuilder::new(); - let fdt = fdt.add_virtio_device(0x1000, 1000, 5); - assert_eq!(fdt.virtio_devices.len(), 1); - assert_eq!(fdt.virtio_device_len(), 1); + fdt.end_node(virtio_mmio)?; + Ok(()) } - #[test] - fn test_create_fdt() { - let fdt_ok = FdtBuilder::new() - .with_cmdline(String::from("reboot=t panic=1 pci=off")) - .with_num_vcpus(8) - .with_mem_size(4096) - .with_serial_console(0x40000000, 0x1000) - .with_rtc(0x40001000, 0x1000) - .add_virtio_device(0x1000, 1000, 5) - .create_fdt(); - assert!(fdt_ok.is_ok()); - - let fdt_no_cmdline = FdtBuilder::new() - .with_num_vcpus(8) - .with_mem_size(4096) - .with_serial_console(0x40000000, 0x1000) - .with_rtc(0x40001000, 0x1000) - .create_fdt(); - assert!(fdt_no_cmdline.is_err()); - - let fdt_no_num_vcpus = FdtBuilder::new() - .with_cmdline(String::from("reboot=t panic=1 pci=off")) - .with_mem_size(4096) - .with_serial_console(0x40000000, 0x1000) - .with_rtc(0x40001000, 0x1000) - .create_fdt(); - assert!(fdt_no_num_vcpus.is_err()); - - let fdt_no_mem_size = FdtBuilder::new() - .with_cmdline(String::from("reboot=t panic=1 pci=off")) - .with_num_vcpus(8) - .with_serial_console(0x40000000, 0x1000) - .with_rtc(0x40001000, 0x1000) - .create_fdt(); - assert!(fdt_no_mem_size.is_err()); + #[cfg(test)] + mod tests { + use crate::fdt::FdtBuilder; + #[cfg(target_arch = "riscv64")] + use kvm_ioctls::Kvm; + + #[test] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + fn test_adding_virtio() { + let mut fdt = FdtBuilder::new(); + let fdt = fdt.add_virtio_device(0x1000, 1000, 5); + assert_eq!(fdt.virtio_devices.len(), 1); + assert_eq!(fdt.virtio_device_len(), 1); + } + + #[test] + #[cfg(target_arch = "aarch64")] + fn test_create_fdt() { + let fdt_ok = FdtBuilder::new() + .with_cmdline(String::from("reboot=t panic=1 pci=off")) + .with_num_vcpus(8) + .with_mem_size(4096) + .with_serial_console(0x40000000, 0x1000) + .with_rtc(0x40001000, 0x1000) + .add_virtio_device(0x1000, 1000, 5) + .create_fdt(); + assert!(fdt_ok.is_ok()); + + let fdt_no_cmdline = FdtBuilder::new() + .with_num_vcpus(8) + .with_mem_size(4096) + .with_serial_console(0x40000000, 0x1000) + .with_rtc(0x40001000, 0x1000) + .create_fdt(); + assert!(fdt_no_cmdline.is_err()); + + let fdt_no_num_vcpus = FdtBuilder::new() + .with_cmdline(String::from("reboot=t panic=1 pci=off")) + .with_mem_size(4096) + .with_serial_console(0x40000000, 0x1000) + .with_rtc(0x40001000, 0x1000) + .create_fdt(); + assert!(fdt_no_num_vcpus.is_err()); + + let fdt_no_mem_size = FdtBuilder::new() + .with_cmdline(String::from("reboot=t panic=1 pci=off")) + .with_num_vcpus(8) + .with_serial_console(0x40000000, 0x1000) + .with_rtc(0x40001000, 0x1000) + .create_fdt(); + assert!(fdt_no_mem_size.is_err()); + } + + #[test] + #[cfg(target_arch = "riscv64")] + fn test_create_fdt() { + let kvm = Kvm::new().unwrap(); + let vm = kvm.create_vm().unwrap(); + let vcpu = vm.create_vcpu(0).unwrap(); + + let fdt_ok = FdtBuilder::new() + .with_cmdline(String::from("reboot=t panic=1 pci=off")) + .with_num_vcpus(8) + .with_mem_size(4096) + .with_serial_console(0x40000000, 0x1000) + .with_rtc(0x40001000, 0x1000) + .add_virtio_device(0x1000, 1000, 5) + .create_fdt(&vcpu); + assert!(fdt_ok.is_ok()); + + let fdt_no_cmdline = FdtBuilder::new() + .with_num_vcpus(8) + .with_mem_size(4096) + .with_serial_console(0x40000000, 0x1000) + .with_rtc(0x40001000, 0x1000) + .create_fdt(&vcpu); + assert!(fdt_no_cmdline.is_err()); + + let fdt_no_num_vcpus = FdtBuilder::new() + .with_cmdline(String::from("reboot=t panic=1 pci=off")) + .with_mem_size(4096) + .with_serial_console(0x40000000, 0x1000) + .with_rtc(0x40001000, 0x1000) + .create_fdt(&vcpu); + assert!(fdt_no_num_vcpus.is_err()); + + let fdt_no_mem_size = FdtBuilder::new() + .with_cmdline(String::from("reboot=t panic=1 pci=off")) + .with_num_vcpus(8) + .with_serial_console(0x40000000, 0x1000) + .with_rtc(0x40001000, 0x1000) + .create_fdt(&vcpu); + assert!(fdt_no_mem_size.is_err()); + } } } diff --git a/src/arch/src/riscv_regs.rs b/src/arch/src/riscv_regs.rs new file mode 100644 index 00000000..5aef40ab --- /dev/null +++ b/src/arch/src/riscv_regs.rs @@ -0,0 +1,74 @@ +#[cfg(target_arch = "riscv64")] +#[macro_export] +macro_rules! kvm_reg_riscv_core_reg { + ($name:ident) => { + offset_of!(kvm_bindings::user_regs_struct, $name) / std::mem::size_of::() + }; +} +#[macro_export] +macro_rules! riscv_core_reg { + ($name:ident) => { + kvm_reg_id!( + KVM_REG_RISCV_CORE as u64, + 0, + kvm_reg_riscv_core_reg!($name) as u64, + KVM_REG_SIZE_U64 + ) + }; +} + +// Registers configuration macros +#[macro_export] +macro_rules! kvm_reg_riscv_config_reg { + ($name:ident) => { + offset_of!(kvm_bindings::kvm_riscv_config, $name) / std::mem::size_of::() + }; +} +#[macro_export] +macro_rules! riscv_config_reg { + ($name:ident) => { + kvm_reg_id!( + KVM_REG_RISCV_CONFIG as u64, + 0, + kvm_reg_riscv_config_reg!($name) as u64, + KVM_REG_SIZE_U64 + ) + }; +} + +// Timer-related macros +#[macro_export] +macro_rules! kvm_reg_riscv_timer_reg { + ($name:ident) => { + offset_of!(kvm_riscv_timer, $name) / std::mem::size_of::() + }; +} +#[macro_export] +macro_rules! riscv_timer_reg { + ($name:ident) => { + kvm_reg_id!( + KVM_REG_RISCV_TIMER as u64, + 0, + kvm_reg_riscv_timer_reg!($name) as u64, + KVM_REG_SIZE_U64 + ) + }; +} + +#[macro_export] +macro_rules! riscv_isa_ext_reg { + ($id:expr) => { + kvm_reg_id!(KVM_REG_RISCV_ISA_EXT as u64, 0, $id, KVM_REG_SIZE_U64) + }; +} + +#[macro_export] +macro_rules! kvm_reg_id { + ($stype:expr, $subtype:expr, $idx:expr, $size:expr) => { + (KVM_REG_RISCV as u64) + | ($stype as u64) + | ($subtype as u64) + | ($idx as u64) + | ($size as u64) + }; +} diff --git a/src/devices/Cargo.toml b/src/devices/Cargo.toml index eb7701ee..a243ae8c 100644 --- a/src/devices/Cargo.toml +++ b/src/devices/Cargo.toml @@ -6,22 +6,23 @@ edition = "2018" license = "Apache-2.0 OR BSD-3-Clause" [dependencies] -event-manager = { version = "0.2.1", features = ["remote_endpoint"] } -kvm-ioctls = "0.11.0" +event-manager = { version = "0.4.0", features = ["remote_endpoint"] } +kvm-ioctls = { git = "https://github.com/rust-vmm/kvm.git", version = "0.20.0"} libc = "0.2.76" -linux-loader = "0.4.0" -log = "0.4.6" -vm-memory = "0.7.0" -vm-superio = "0.5.0" -vmm-sys-util = "0.8.0" +linux-loader = "0.13.0" +log = "*" +vm-memory = "0.16.0" +vm-superio = "0.8.0" +vmm-sys-util = "0.12.1" vm-device = "0.1.0" -virtio-blk = { git = "https://github.com/rust-vmm/vm-virtio.git", features = ["backend-stdio"] } -virtio-device = { git = "https://github.com/rust-vmm/vm-virtio.git"} -virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio.git"} +thiserror = "1.0" +virtio-blk = { git = "https://github.com/rust-vmm/vm-virtio.git", version = "0.1.0", features = ["backend-stdio"] } +virtio-device = { git = "https://github.com/rust-vmm/vm-virtio.git", version = "0.1.0"} +virtio-queue = { git = "https://github.com/rust-vmm/vm-virtio.git", version = "0.14.0"} utils = { path = "../utils" } [dev-dependencies] -vm-memory = { version = "0.7.0", features = ["backend-mmap"] } -kvm-bindings = "0.5.0" +vm-memory = { version = "0.16.0", features = ["backend-mmap"] } +kvm-bindings = { git = "https://github.com/rust-vmm/kvm.git", version = "0.11.0"} diff --git a/src/devices/src/intc/mod.rs b/src/devices/src/intc/mod.rs new file mode 100644 index 00000000..8cd4d5f1 --- /dev/null +++ b/src/devices/src/intc/mod.rs @@ -0,0 +1,30 @@ +use kvm_ioctls::VmFd; +use std::io; +use std::sync::Arc; +use vm_superio::Trigger; + +#[derive(Clone)] +pub struct APlicTrigger { + pub gsi: u32, + pub vm: Arc, +} + +impl APlicTrigger { + pub fn try_clone(&self) -> io::Result { + Ok(APlicTrigger { + gsi: self.gsi, + vm: self.vm.clone(), + }) + } +} + +impl Trigger for APlicTrigger { + type E = io::Error; + + fn trigger(&self) -> io::Result<()> { + self.vm.set_irq_line(self.gsi, true)?; + self.vm.set_irq_line(self.gsi, false)?; + + Ok(()) + } +} diff --git a/src/devices/src/legacy/mod.rs b/src/devices/src/legacy/mod.rs index d6ea0610..a364eb48 100644 --- a/src/devices/src/legacy/mod.rs +++ b/src/devices/src/legacy/mod.rs @@ -2,12 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause #[cfg(target_arch = "x86_64")] mod i8042; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] mod rtc; mod serial; #[cfg(target_arch = "x86_64")] pub use i8042::I8042Wrapper; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub use rtc::RtcWrapper; pub use serial::Error as SerialError; pub use serial::SerialWrapper; diff --git a/src/devices/src/legacy/serial.rs b/src/devices/src/legacy/serial.rs index a0f19f58..e49c0691 100644 --- a/src/devices/src/legacy/serial.rs +++ b/src/devices/src/legacy/serial.rs @@ -5,7 +5,7 @@ use std::convert::TryInto; use std::io::{self, stdin, Read, Write}; use event_manager::{EventOps, Events, MutEventSubscriber}; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use vm_device::{bus::MmioAddress, MutDeviceMmio}; #[cfg(target_arch = "x86_64")] use vm_device::{ @@ -106,7 +106,7 @@ impl, W: Write> MutDevicePio for SerialWrapper, W: Write> MutDeviceMmio for SerialWrapper { fn mmio_read(&mut self, _base: MmioAddress, offset: u64, data: &mut [u8]) { // TODO: this function can't return an Err, so we'll mark error conditions @@ -150,13 +150,13 @@ mod tests { // Check that passing invalid data does not result in a crash. #[cfg(target_arch = "x86_64")] serial_console.pio_read(PioAddress(0), valid_iir_offset, invalid_data); - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] serial_console.mmio_read(MmioAddress(0), valid_iir_offset, invalid_data); // The same scenario happens for writes. #[cfg(target_arch = "x86_64")] serial_console.pio_write(PioAddress(0), valid_iir_offset, invalid_data); - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] serial_console.mmio_write(MmioAddress(0), valid_iir_offset, invalid_data); } @@ -182,7 +182,7 @@ mod tests { let invalid_offset = PioAddressOffset::MAX; serial_console.pio_write(PioAddress(0), invalid_offset, &data); } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] { let invalid_offset = u64::MAX; serial_console.mmio_write(MmioAddress(0), invalid_offset, &data); @@ -203,7 +203,7 @@ mod tests { serial_console.pio_write(PioAddress(0), offset, &write_data); serial_console.pio_read(PioAddress(0), offset, read_data.as_mut()); } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] { serial_console.mmio_write(MmioAddress(0), offset, &write_data); serial_console.mmio_read(MmioAddress(0), offset, read_data.as_mut()); diff --git a/src/devices/src/lib.rs b/src/devices/src/lib.rs index 52789e1e..86303b0d 100644 --- a/src/devices/src/lib.rs +++ b/src/devices/src/lib.rs @@ -5,5 +5,6 @@ // we are striving to turn as much of the local code as possible into reusable building blocks // going forward. +pub mod intc; pub mod legacy; pub mod virtio; diff --git a/src/devices/src/virtio/block/device.rs b/src/devices/src/virtio/block/device.rs index a885a47e..84588f12 100644 --- a/src/devices/src/virtio/block/device.rs +++ b/src/devices/src/virtio/block/device.rs @@ -9,64 +9,82 @@ use std::sync::{Arc, Mutex}; use virtio_blk::stdio_executor::StdIoBackend; use virtio_device::{VirtioConfig, VirtioDeviceActions, VirtioDeviceType, VirtioMmioDevice}; -use virtio_queue::Queue; +use virtio_queue::{Queue, QueueT}; use vm_device::bus::MmioAddress; use vm_device::device_manager::MmioManager; use vm_device::{DeviceMmio, MutDeviceMmio}; -use vm_memory::GuestAddressSpace; +use vm_memory::{GuestAddressSpace, GuestMemoryMmap}; use crate::virtio::block::{BLOCK_DEVICE_ID, VIRTIO_BLK_F_RO}; -use crate::virtio::{CommonConfig, Env, SingleFdSignalQueue, QUEUE_MAX_SIZE}; +use crate::virtio::{CommonConfig, Env, IrqTrigger, SingleFdSignalQueue, QUEUE_MAX_SIZE}; use super::inorder_handler::InOrderQueueHandler; use super::queue_handler::QueueHandler; use super::{build_config_space, BlockArgs, Error, Result}; +use crate::intc::APlicTrigger; + // This Block device can only use the MMIO transport for now, but we plan to reuse large parts of // the functionality when we implement virtio PCI as well, for example by having a base generic // type, and then separate concrete instantiations for `MmioConfig` and `PciConfig`. -pub struct Block { +pub struct Block { cfg: CommonConfig, file_path: PathBuf, read_only: bool, // We'll prob need to remember this for state save/restore unless we pass the info from // the outside. _root_device: bool, + mem: Arc, + aplic_trigger: Option, } -impl Block -where - M: GuestAddressSpace + Clone + Send + 'static, -{ +impl Block { // Helper method that only creates a `Block` object. - fn create_block(env: &mut Env, args: &BlockArgs) -> Result { + fn create_block( + mem: Arc, + env: &mut Env, + args: &BlockArgs, + aplic_trigger: Option, + ) -> Result { let device_features = args.device_features(); // A block device has a single queue. - let queues = vec![Queue::new(env.mem.clone(), QUEUE_MAX_SIZE)]; + let queues = vec![Queue::new(QUEUE_MAX_SIZE).unwrap()]; let config_space = build_config_space(&args.file_path)?; let virtio_cfg = VirtioConfig::new(device_features, queues, config_space); let common_cfg = CommonConfig::new(virtio_cfg, env).map_err(Error::Virtio)?; Ok(Block { + mem, cfg: common_cfg, file_path: args.file_path.clone(), read_only: args.read_only, _root_device: args.root_device, + aplic_trigger, }) } // Create `Block` object, register it on the MMIO bus, and add any extra required info to // the kernel cmdline from the environment. - pub fn new(env: &mut Env, args: &BlockArgs) -> Result>> + pub fn new( + mem: Arc, + env: &mut Env, + args: &BlockArgs, + aplic_trigger: Option, + ) -> Result>> where // We're using this (more convoluted) bound so we can pass both references and smart // pointers such as mutex guards here. B: DerefMut, B::Target: MmioManager>, { - let block = Arc::new(Mutex::new(Self::create_block(env, args)?)); + let block = Arc::new(Mutex::new(Self::create_block( + mem, + env, + args, + aplic_trigger, + )?)); // Register the device on the MMIO bus. env.register_mmio_device(block.clone()) @@ -79,14 +97,14 @@ where } } -impl Borrow> for Block { - fn borrow(&self) -> &VirtioConfig { +impl Borrow> for Block { + fn borrow(&self) -> &VirtioConfig { &self.cfg.virtio } } -impl BorrowMut> for Block { - fn borrow_mut(&mut self) -> &mut VirtioConfig { +impl BorrowMut> for Block { + fn borrow_mut(&mut self) -> &mut VirtioConfig { &mut self.cfg.virtio } } @@ -116,9 +134,14 @@ impl VirtioDeviceActions for Bloc // TODO: Create the backend earlier (as part of `Block::new`)? let disk = StdIoBackend::new(file, features).map_err(Error::Backend)?; + let irq_trigger = if cfg!(target_arch = "riscv64") { + IrqTrigger::APlicTrigger(Some(self.aplic_trigger.clone().unwrap())) + } else { + IrqTrigger::IrqFd(self.cfg.irqfd.clone()) + }; let driver_notify = SingleFdSignalQueue { - irqfd: self.cfg.irqfd.clone(), + irq_trigger, interrupt_status: self.cfg.virtio.interrupt_status.clone(), }; @@ -131,6 +154,7 @@ impl VirtioDeviceActions for Bloc }; let handler = Arc::new(Mutex::new(QueueHandler { + mem: self.mem.clone(), inner, ioeventfd: ioevents.remove(0), })); @@ -144,7 +168,7 @@ impl VirtioDeviceActions for Bloc } } -impl VirtioMmioDevice for Block {} +impl VirtioMmioDevice for Block {} impl MutDeviceMmio for Block { fn mmio_read(&mut self, _base: MmioAddress, offset: u64, data: &mut [u8]) { @@ -177,13 +201,26 @@ mod tests { advertise_flush: true, }; - let block_mutex = Block::new(&mut env, &args).unwrap(); + let aplic_trigger = if cfg!(target_arch = "riscv64") { + Some(APlicTrigger { + gsi: 5, + vm: env.vm_fd.clone(), + }) + } else { + None + }; + + let block_mutex = Block::new(env.mem.clone(), &mut env, &args, aplic_trigger).unwrap(); let block = block_mutex.lock().unwrap(); assert_eq!(block.device_type(), BLOCK_DEVICE_ID); assert_eq!( - mock.kernel_cmdline.as_str(), + mock.kernel_cmdline + .as_cstring() + .unwrap() + .into_string() + .unwrap(), format!( "virtio_mmio.device=4K@0x{:x}:{} root=/dev/vda ro", mock.mmio_cfg.range.base().0, diff --git a/src/devices/src/virtio/block/inorder_handler.rs b/src/devices/src/virtio/block/inorder_handler.rs index eb8f04a6..3f364ff3 100644 --- a/src/devices/src/virtio/block/inorder_handler.rs +++ b/src/devices/src/virtio/block/inorder_handler.rs @@ -7,15 +7,21 @@ use std::result; use log::warn; use virtio_blk::request::Request; use virtio_blk::stdio_executor::{self, StdIoBackend}; -use virtio_queue::{DescriptorChain, Queue}; -use vm_memory::{self, GuestAddressSpace}; +use virtio_queue::{DescriptorChain, Queue, QueueOwnedT, QueueT}; +use vm_memory::{self, GuestAddressSpace, GuestMemoryMmap}; + +use std::sync::Arc; use crate::virtio::SignalUsedQueue; +use thiserror::Error; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum Error { + #[error("Guest memory error: {0}")] GuestMemory(vm_memory::GuestMemoryError), + #[error("Queue error: {0}")] Queue(virtio_queue::Error), + #[error("Process request error")] ProcessRequest(stdio_executor::ProcessReqError), } @@ -42,18 +48,20 @@ impl From for Error { // object), but the aim is to have a way of working with generic backends and turn this into // a more flexible building block. The name comes from processing and returning descriptor // chains back to the device in the same order they are received. -pub struct InOrderQueueHandler { +pub struct InOrderQueueHandler { pub driver_notify: S, - pub queue: Queue, + pub queue: Queue, pub disk: StdIoBackend, } -impl InOrderQueueHandler +impl InOrderQueueHandler where - M: GuestAddressSpace, S: SignalUsedQueue, { - fn process_chain(&mut self, mut chain: DescriptorChain) -> result::Result<(), Error> { + fn process_chain( + &mut self, + mut chain: DescriptorChain, + ) -> result::Result<(), Error> { let used_len = match Request::parse(&mut chain) { Ok(request) => self.disk.process_request(chain.memory(), &request)?, Err(e) => { @@ -62,26 +70,28 @@ where } }; - self.queue.add_used(chain.head_index(), used_len)?; + self.queue + .add_used(chain.memory(), chain.head_index(), used_len)?; - if self.queue.needs_notification()? { + if self.queue.needs_notification(chain.memory())? { self.driver_notify.signal_used_queue(0); } Ok(()) } - pub fn process_queue(&mut self) -> result::Result<(), Error> { + pub fn process_queue(&mut self, mem: Arc) -> result::Result<(), Error> { // To see why this is done in a loop, please look at the `Queue::enable_notification` // comments in `virtio_queue`. + loop { - self.queue.disable_notification()?; + self.queue.disable_notification(mem.as_ref())?; - while let Some(chain) = self.queue.iter()?.next() { - self.process_chain(chain)?; + while let Some(chain) = self.queue.iter(mem.as_ref())?.next() { + self.process_chain::<&GuestMemoryMmap>(chain)?; } - if !self.queue.enable_notification()? { + if !self.queue.enable_notification(mem.as_ref())? { break; } } diff --git a/src/devices/src/virtio/block/queue_handler.rs b/src/devices/src/virtio/block/queue_handler.rs index 13689227..efab7d2d 100644 --- a/src/devices/src/virtio/block/queue_handler.rs +++ b/src/devices/src/virtio/block/queue_handler.rs @@ -3,7 +3,8 @@ use event_manager::{EventOps, Events, MutEventSubscriber}; use log::error; -use vm_memory::GuestAddressSpace; +use std::sync::Arc; +use vm_memory::GuestMemoryMmap; use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::EventFd; @@ -16,12 +17,13 @@ const IOEVENT_DATA: u32 = 0; // signalling implementation based on `EventFd`s, and then also implements `MutEventSubscriber` // to interact with the event manager. `ioeventfd` is the `EventFd` connected to queue // notifications coming from the driver. -pub(crate) struct QueueHandler { - pub inner: InOrderQueueHandler, +pub(crate) struct QueueHandler { + pub mem: Arc, + pub inner: InOrderQueueHandler, pub ioeventfd: EventFd, } -impl MutEventSubscriber for QueueHandler { +impl MutEventSubscriber for QueueHandler { fn process(&mut self, events: Events, ops: &mut EventOps) { let mut error = true; @@ -33,7 +35,7 @@ impl MutEventSubscriber for QueueHandler { error!("unexpected events data {}", events.data()); } else if self.ioeventfd.read().is_err() { error!("ioeventfd read error") - } else if let Err(e) = self.inner.process_queue() { + } else if let Err(e) = self.inner.process_queue(self.mem.clone()) { error!("error processing block queue {:?}", e); } else { error = false; diff --git a/src/devices/src/virtio/mod.rs b/src/devices/src/virtio/mod.rs index d3cfc435..76501abf 100644 --- a/src/devices/src/virtio/mod.rs +++ b/src/devices/src/virtio/mod.rs @@ -8,10 +8,11 @@ pub mod net; use std::convert::TryFrom; use std::io; -use std::ops::DerefMut; +use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::{Arc, Mutex}; +use crate::intc::APlicTrigger; use event_manager::{ Error as EvmgrError, EventManager, MutEventSubscriber, RemoteEndpoint, Result as EvmgrResult, SubscriberId, @@ -19,10 +20,12 @@ use event_manager::{ use kvm_ioctls::{IoEventAddress, VmFd}; use linux_loader::cmdline::Cmdline; use virtio_device::VirtioConfig; +use virtio_queue::{Queue, QueueT}; use vm_device::bus::{self, MmioAddress, MmioRange}; use vm_device::device_manager::MmioManager; use vm_device::DeviceMmio; use vm_memory::{GuestAddress, GuestAddressSpace}; +use vm_superio::Trigger; use vmm_sys_util::errno; use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; @@ -65,6 +68,28 @@ pub enum Error { RegisterIrqfd(errno::Error), } +// Enum that abstracts the way in which IRQs are triggered +pub enum IrqTrigger { + IrqFd(Arc), + APlicTrigger(Option), +} + +impl IrqTrigger { + fn trigger(&self) { + match self { + IrqTrigger::IrqFd(irqfd) => { + irqfd + .write(1) + .expect("Failed write to eventfd when signaling queue"); + } + IrqTrigger::APlicTrigger(aplic_trigger) => aplic_trigger + .clone() + .unwrap() + .trigger() + .expect("Failed write to aplic trigger when signaling queue"), + } + } +} type Result = std::result::Result; pub type Subscriber = Arc>; @@ -117,7 +142,7 @@ pub struct Env<'a, M, B> { pub kernel_cmdline: &'a mut Cmdline, } -impl<'a, M, B> Env<'a, M, B> +impl Env<'_, M, B> where // We're using this (more convoluted) bound so we can pass both references and smart // pointers such as mutex guards here. @@ -154,23 +179,26 @@ where } // Holds configuration objects which are common to all current devices. -pub struct CommonConfig { - pub virtio: VirtioConfig, +pub struct CommonConfig { + pub mem: M, + pub virtio: VirtioConfig, pub mmio: MmioConfig, pub endpoint: RemoteEndpoint, pub vm_fd: Arc, pub irqfd: Arc, } -impl CommonConfig { - pub fn new(virtio_cfg: VirtioConfig, env: &Env) -> Result { +impl CommonConfig { + pub fn new(virtio_cfg: VirtioConfig, env: &Env) -> Result { let irqfd = Arc::new(EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?); + #[cfg(not(target_arch = "riscv64"))] env.vm_fd .register_irqfd(&irqfd, env.mmio_cfg.gsi) .map_err(Error::RegisterIrqfd)?; Ok(CommonConfig { + mem: env.mem.clone(), virtio: virtio_cfg, mmio: env.mmio_cfg, endpoint: env.event_mgr.remote_endpoint(), @@ -183,9 +211,15 @@ impl CommonConfig { // a `Vec` that contains `EventFd`s registered as ioeventfds, which are used to convey queue // notifications coming from the driver. pub fn prepare_activate(&self) -> Result> { - if !self.virtio.queues_valid() { - return Err(Error::QueuesNotValid); - } + let m = self.mem.memory(); + let guest_mem = <::T>::deref(&m); + + self.virtio + .queues + .iter() + .all(|q| ::is_valid(q, guest_mem)) + .then_some(()) + .ok_or(Error::QueuesNotValid)?; if self.virtio.device_activated { return Err(Error::AlreadyActivated); @@ -255,7 +289,7 @@ pub trait SignalUsedQueue { /// Uses a single irqfd as the basis of signalling any queue (useful for the MMIO transport, /// where a single interrupt is shared for everything). pub struct SingleFdSignalQueue { - pub irqfd: Arc, + pub irq_trigger: IrqTrigger, pub interrupt_status: Arc, } @@ -263,9 +297,7 @@ impl SignalUsedQueue for SingleFdSignalQueue { fn signal_used_queue(&self, _index: u16) { self.interrupt_status .fetch_or(VIRTIO_MMIO_INT_VRING, Ordering::SeqCst); - self.irqfd - .write(1) - .expect("Failed write to eventfd when signalling queue"); + self.irq_trigger.trigger(); } } @@ -284,7 +316,6 @@ pub(crate) mod tests { kvm_create_device, kvm_device_attr, kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3, KVM_DEV_ARM_VGIC_CTRL_INIT, KVM_DEV_ARM_VGIC_GRP_CTRL, }; - use virtio_queue::Queue; pub type MockMem = Arc; // Can be used in other modules to test functionality that requires a `CommonArgs` struct as @@ -324,7 +355,7 @@ pub(crate) mod tests { mmio_mgr: IoManager::new(), mmio_cfg, // `4096` seems large enough for testing. - kernel_cmdline: Cmdline::new(4096), + kernel_cmdline: Cmdline::new(4096).unwrap(), } } pub fn env(&mut self) -> Env { @@ -342,7 +373,7 @@ pub(crate) mod tests { // the IRQ fds and thus test the virtio functionality in arm as well. fn create_gic(vm_fd: &VmFd) { let mut create_device_attr = kvm_create_device { - type_: kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3 as u32, + type_: kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3, fd: 0, flags: 0, }; @@ -379,7 +410,11 @@ pub(crate) mod tests { assert_eq!(bus_range.size(), range.size()); assert_eq!( - mock.kernel_cmdline.as_str(), + mock.kernel_cmdline + .as_cstring() + .unwrap() + .into_string() + .unwrap(), format!( "virtio_mmio.device=4K@0x{:x}:{}", range.base().0, @@ -388,7 +423,13 @@ pub(crate) mod tests { ); mock.env().insert_cmdline_str("ending_string").unwrap(); - assert!(mock.kernel_cmdline.as_str().ends_with("ending_string")); + assert!(mock + .kernel_cmdline + .as_cstring() + .unwrap() + .into_string() + .unwrap() + .ends_with("ending_string")); } #[test] @@ -397,7 +438,7 @@ pub(crate) mod tests { let env = mock.env(); let device_features = 0; - let queues = vec![Queue::new(env.mem.clone(), 256)]; + let queues = vec![Queue::new(256).unwrap()]; let config_space = Vec::new(); let virtio_cfg = VirtioConfig::new(device_features, queues, config_space); @@ -407,8 +448,8 @@ pub(crate) mod tests { assert!(matches!(cfg.prepare_activate(), Err(Error::QueuesNotValid))); // Let's pretend the queue has been configured such that the `is_valid` check passes. - cfg.virtio.queues[0].state.ready = true; - cfg.virtio.queues[0].state.size = 256; + cfg.virtio.queues[0].set_ready(true); + cfg.virtio.queues[0].set_size(256); // This will fail because the "driver" didn't acknowledge `VIRTIO_F_VERSION_1`. assert!(matches!(cfg.prepare_activate(), Err(Error::BadFeatures(0)))); diff --git a/src/devices/src/virtio/net/device.rs b/src/devices/src/virtio/net/device.rs index ea78bc12..7fbe7358 100644 --- a/src/devices/src/virtio/net/device.rs +++ b/src/devices/src/virtio/net/device.rs @@ -6,15 +6,17 @@ use std::ops::DerefMut; use std::sync::{Arc, Mutex}; use virtio_device::{VirtioConfig, VirtioDeviceActions, VirtioDeviceType, VirtioMmioDevice}; -use virtio_queue::Queue; +use virtio_queue::{Queue, QueueT}; use vm_device::bus::MmioAddress; use vm_device::device_manager::MmioManager; use vm_device::{DeviceMmio, MutDeviceMmio}; -use vm_memory::GuestAddressSpace; +use vm_memory::{GuestAddressSpace, GuestMemoryMmap}; +use crate::intc::APlicTrigger; use crate::virtio::features::{VIRTIO_F_IN_ORDER, VIRTIO_F_RING_EVENT_IDX, VIRTIO_F_VERSION_1}; use crate::virtio::net::features::*; use crate::virtio::net::{Error, NetArgs, Result, NET_DEVICE_ID, VIRTIO_NET_HDR_SIZE}; +use crate::virtio::IrqTrigger; use crate::virtio::{CommonConfig, Env, SingleFdSignalQueue, QUEUE_MAX_SIZE}; use super::bindings; @@ -22,16 +24,26 @@ use super::queue_handler::QueueHandler; use super::simple_handler::SimpleHandler; use super::tap::Tap; -pub struct Net { +pub struct Net +where + M: GuestAddressSpace + Clone + Send + Sync + 'static, +{ + mem: Arc, cfg: CommonConfig, tap_name: String, + aplic_trigger: Option, } impl Net where - M: GuestAddressSpace + Clone + Send + 'static, + M: GuestAddressSpace + Clone + Send + Sync + 'static, { - pub fn new(env: &mut Env, args: &NetArgs) -> Result>> + pub fn new( + mem: Arc, + env: &mut Env, + args: &NetArgs, + aplic_trigger: Option, + ) -> Result>> where // We're using this (more convoluted) bound so we can pass both references and smart // pointers such as mutex guards here. @@ -52,8 +64,8 @@ where // An rx/tx queue pair. let queues = vec![ - Queue::new(env.mem.clone(), QUEUE_MAX_SIZE), - Queue::new(env.mem.clone(), QUEUE_MAX_SIZE), + Queue::new(QUEUE_MAX_SIZE).unwrap(), + Queue::new(QUEUE_MAX_SIZE).unwrap(), ]; // TODO: We'll need a minimal config space to support setting an explicit MAC addr @@ -64,8 +76,10 @@ where let common_cfg = CommonConfig::new(virtio_cfg, env).map_err(Error::Virtio)?; let net = Arc::new(Mutex::new(Net { + mem, cfg: common_cfg, tap_name: args.tap_name.clone(), + aplic_trigger, })); env.register_mmio_device(net.clone()) @@ -75,25 +89,27 @@ where } } -impl VirtioDeviceType for Net { +impl VirtioDeviceType for Net { fn device_type(&self) -> u32 { NET_DEVICE_ID } } -impl Borrow> for Net { - fn borrow(&self) -> &VirtioConfig { +impl Borrow> for Net { + fn borrow(&self) -> &VirtioConfig { &self.cfg.virtio } } -impl BorrowMut> for Net { - fn borrow_mut(&mut self) -> &mut VirtioConfig { +impl BorrowMut> + for Net +{ + fn borrow_mut(&mut self) -> &mut VirtioConfig { &mut self.cfg.virtio } } -impl VirtioDeviceActions for Net { +impl VirtioDeviceActions for Net { type E = Error; fn activate(&mut self) -> Result<()> { @@ -113,9 +129,14 @@ impl VirtioDeviceActions for Net< // should define this somewhere. tap.set_vnet_hdr_size(VIRTIO_NET_HDR_SIZE as i32) .map_err(Error::Tap)?; + let irq_trigger = if cfg!(target_arch = "riscv64") { + IrqTrigger::APlicTrigger(Some(self.aplic_trigger.clone().unwrap())) + } else { + IrqTrigger::IrqFd(self.cfg.irqfd.clone()) + }; let driver_notify = SingleFdSignalQueue { - irqfd: self.cfg.irqfd.clone(), + irq_trigger, interrupt_status: self.cfg.virtio.interrupt_status.clone(), }; @@ -123,7 +144,7 @@ impl VirtioDeviceActions for Net< let rxq = self.cfg.virtio.queues.remove(0); let txq = self.cfg.virtio.queues.remove(0); - let inner = SimpleHandler::new(driver_notify, rxq, txq, tap); + let inner = SimpleHandler::new(self.mem.clone(), driver_notify, rxq, txq, tap); let handler = Arc::new(Mutex::new(QueueHandler { inner, @@ -140,9 +161,9 @@ impl VirtioDeviceActions for Net< } } -impl VirtioMmioDevice for Net {} +impl VirtioMmioDevice for Net {} -impl MutDeviceMmio for Net { +impl MutDeviceMmio for Net { fn mmio_read(&mut self, _base: MmioAddress, offset: u64, data: &mut [u8]) { self.read(offset, data); } diff --git a/src/devices/src/virtio/net/queue_handler.rs b/src/devices/src/virtio/net/queue_handler.rs index 4759c2bf..90193dd8 100644 --- a/src/devices/src/virtio/net/queue_handler.rs +++ b/src/devices/src/virtio/net/queue_handler.rs @@ -1,9 +1,10 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +use std::os::fd::AsRawFd; + use event_manager::{EventOps, Events, MutEventSubscriber}; use log::error; -use vm_memory::GuestAddressSpace; use vmm_sys_util::epoll::EventSet; use vmm_sys_util::eventfd::EventFd; @@ -15,13 +16,13 @@ const TAPFD_DATA: u32 = 0; const RX_IOEVENT_DATA: u32 = 1; const TX_IOEVENT_DATA: u32 = 2; -pub struct QueueHandler { - pub inner: SimpleHandler, +pub struct QueueHandler { + pub inner: SimpleHandler, pub rx_ioevent: EventFd, pub tx_ioevent: EventFd, } -impl QueueHandler { +impl QueueHandler { // Helper method that receives an error message to be logged and the `ops` handle // which is used to unregister all events. fn handle_error>(&self, s: S, ops: &mut EventOps) { @@ -30,12 +31,12 @@ impl QueueHandler { .expect("Failed to remove rx ioevent"); ops.remove(Events::empty(&self.tx_ioevent)) .expect("Failed to remove tx ioevent"); - ops.remove(Events::empty(&self.inner.tap)) + ops.remove(Events::empty(&self.inner.tap.borrow().as_raw_fd())) .expect("Failed to remove tap event"); } } -impl MutEventSubscriber for QueueHandler { +impl MutEventSubscriber for QueueHandler { fn process(&mut self, events: Events, ops: &mut EventOps) { // TODO: We can also consider panicking on the errors that cannot be generated // or influenced. @@ -71,8 +72,10 @@ impl MutEventSubscriber for QueueHandler { } fn init(&mut self, ops: &mut EventOps) { + let raw_fd = &mut self.inner.tap.borrow_mut().as_raw_fd(); + ops.add(Events::with_data( - &self.inner.tap, + raw_fd, TAPFD_DATA, EventSet::IN | EventSet::EDGE_TRIGGERED, )) diff --git a/src/devices/src/virtio/net/simple_handler.rs b/src/devices/src/virtio/net/simple_handler.rs index e3df98ef..d7ed9b60 100644 --- a/src/devices/src/virtio/net/simple_handler.rs +++ b/src/devices/src/virtio/net/simple_handler.rs @@ -1,17 +1,20 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +use std::cell::RefCell; use std::cmp; use std::io::{self, Read, Write}; use std::result; use log::warn; -use virtio_queue::{DescriptorChain, Queue}; -use vm_memory::{Bytes, GuestAddressSpace}; +use thiserror::Error; +use virtio_queue::{DescriptorChain, Queue, QueueOwnedT, QueueT}; +use vm_memory::{Bytes, GuestMemoryMmap}; use crate::virtio::net::tap::Tap; use crate::virtio::net::{RXQ_INDEX, TXQ_INDEX}; use crate::virtio::SignalUsedQueue; +use std::sync::Arc; // According to the standard: "If the VIRTIO_NET_F_GUEST_TSO4, VIRTIO_NET_F_GUEST_TSO6 or // VIRTIO_NET_F_GUEST_UFO features are used, the maximum incoming packet will be to 65550 @@ -22,43 +25,54 @@ use crate::virtio::SignalUsedQueue; // We assume the TX frame will not exceed this size either. const MAX_BUFFER_SIZE: usize = 65562; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum Error { + #[error("Guest memory error: {0}")] GuestMemory(vm_memory::GuestMemoryError), - Queue(virtio_queue::Error), + #[error("Queue error")] + Queue, + #[error("Tap device error: {0}")] Tap(io::Error), } impl From for Error { - fn from(e: virtio_queue::Error) -> Self { - Error::Queue(e) + fn from(_e: virtio_queue::Error) -> Self { + Error::Queue } } // A simple handler implementation for a RX/TX queue pair, which does not make assumptions about // the way queue notification is implemented. The backend is not yet generic (we always assume a // `Tap` object), but we're looking at improving that going forward. -// TODO: Find a better name. -pub struct SimpleHandler { +pub struct SimpleHandler { + pub mem: Arc, pub driver_notify: S, - pub rxq: Queue, + pub rxq: Queue, pub rxbuf_current: usize, pub rxbuf: [u8; MAX_BUFFER_SIZE], - pub txq: Queue, - pub txbuf: [u8; MAX_BUFFER_SIZE], - pub tap: Tap, + pub txq: Queue, + pub txbuf: RefCell<[u8; MAX_BUFFER_SIZE]>, + pub tap: RefCell, } -impl SimpleHandler { - pub fn new(driver_notify: S, rxq: Queue, txq: Queue, tap: Tap) -> Self { +impl SimpleHandler { + pub fn new( + mem: Arc, + driver_notify: S, + rxq: Queue, + txq: Queue, + tap: Tap, + ) -> Self { SimpleHandler { + mem, driver_notify, rxq, rxbuf_current: 0, rxbuf: [0u8; MAX_BUFFER_SIZE], txq, - txbuf: [0u8; MAX_BUFFER_SIZE], - tap, + //inner: RefCell::new(SimpleHandlerInner { txbuf: [0u8; MAX_BUFFER_SIZE], tap }), + txbuf: RefCell::new([0u8; MAX_BUFFER_SIZE]), + tap: RefCell::new(tap), } } @@ -69,7 +83,7 @@ impl SimpleHandler { fn write_frame_to_guest(&mut self) -> result::Result { let num_bytes = self.rxbuf_current; - let mut chain = match self.rxq.iter()?.next() { + let mut chain = match self.rxq.iter(self.mem.as_ref())?.next() { Some(c) => c, _ => return Ok(false), }; @@ -98,7 +112,8 @@ impl SimpleHandler { warn!("rx frame too large"); } - self.rxq.add_used(chain.head_index(), count as u32)?; + self.rxq + .add_used(self.mem.as_ref(), chain.head_index(), count as u32)?; self.rxbuf_current = 0; @@ -108,7 +123,7 @@ impl SimpleHandler { pub fn process_tap(&mut self) -> result::Result<(), Error> { loop { if self.rxbuf_current == 0 { - match self.tap.read(&mut self.rxbuf) { + match self.tap.borrow_mut().read(&mut self.rxbuf) { Ok(n) => self.rxbuf_current = n, Err(_) => { // TODO: Do something (logs, metrics, etc.) in response to an error when @@ -119,12 +134,12 @@ impl SimpleHandler { } } - if !self.write_frame_to_guest()? && !self.rxq.enable_notification()? { + if !self.write_frame_to_guest()? && !self.rxq.enable_notification(self.mem.as_ref())? { break; } } - if self.rxq.needs_notification()? { + if self.rxq.needs_notification(self.mem.as_ref())? { self.driver_notify.signal_used_queue(RXQ_INDEX); } @@ -132,13 +147,13 @@ impl SimpleHandler { } fn send_frame_from_chain( - &mut self, - chain: &mut DescriptorChain, + &self, + chain: &mut DescriptorChain<&GuestMemoryMmap>, ) -> result::Result { let mut count = 0; while let Some(desc) = chain.next() { - let left = self.txbuf.len() - count; + let left = self.txbuf.borrow().len() - count; let len = desc.len() as usize; if len > left { @@ -148,39 +163,46 @@ impl SimpleHandler { chain .memory() - .read_slice(&mut self.txbuf[count..count + len], desc.addr()) + .read_slice( + &mut self.txbuf.borrow_mut()[count..count + len], + desc.addr(), + ) .map_err(Error::GuestMemory)?; count += len; } - self.tap.write(&self.txbuf[..count]).map_err(Error::Tap)?; + self.tap + .borrow_mut() + .write(&self.txbuf.borrow()[..count]) + .map_err(Error::Tap)?; Ok(count as u32) } pub fn process_txq(&mut self) -> result::Result<(), Error> { loop { - self.txq.disable_notification()?; + self.txq.disable_notification(self.mem.as_ref())?; - while let Some(mut chain) = self.txq.iter()?.next() { + while let Some(mut chain) = self.txq.iter(self.mem.as_ref())?.next() { self.send_frame_from_chain(&mut chain)?; - self.txq.add_used(chain.head_index(), 0)?; + self.txq + .add_used(self.mem.as_ref(), chain.head_index(), 0)?; - if self.txq.needs_notification()? { + if self.txq.needs_notification(self.mem.as_ref())? { self.driver_notify.signal_used_queue(TXQ_INDEX); } } - if !self.txq.enable_notification()? { + if !self.txq.enable_notification(self.mem.as_ref())? { return Ok(()); } } } pub fn process_rxq(&mut self) -> result::Result<(), Error> { - self.rxq.disable_notification()?; + self.rxq.disable_notification(self.mem.as_ref())?; self.process_tap() } } diff --git a/src/devices/src/virtio/net/tap.rs b/src/devices/src/virtio/net/tap.rs index 0db62d98..a760a789 100644 --- a/src/devices/src/virtio/net/tap.rs +++ b/src/devices/src/virtio/net/tap.rs @@ -14,10 +14,11 @@ use std::os::raw::{c_char, c_int, c_uint, c_ulong}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val}; -use vmm_sys_util::{ioctl_expr, ioctl_ioc_nr, ioctl_iow_nr}; +use vmm_sys_util::{ioctl_ioc_nr, ioctl_iow_nr}; use super::bindings::ifreq; +use std::ffi::CString; // As defined in the Linux UAPI: // https://elixir.bootlin.com/linux/v4.17/source/include/uapi/linux/if.h#L33 const IFACE_NAME_MAX_LEN: usize = 16; @@ -85,7 +86,7 @@ impl IfReqBuilder { } pub fn if_name(mut self, if_name: &[u8; IFACE_NAME_MAX_LEN]) -> Self { - // Since we don't call as_mut on the same union field more than once, this block is safe. + // SAFETY: Since we don't call as_mut on the same union field more than once, this block is safe. let ifrn_name = unsafe { self.0.ifr_ifrn.ifrn_name.as_mut() }; ifrn_name.copy_from_slice(if_name.as_ref()); @@ -93,7 +94,7 @@ impl IfReqBuilder { } pub(crate) fn flags(mut self, flags: i16) -> Self { - // Since we don't call as_mut on the same union field more than once, this block is safe. + // SAFETY: Since we don't call as_mut on the same union field more than once, this block is safe. let ifru_flags = unsafe { self.0.ifr_ifru.ifru_flags.as_mut() }; *ifru_flags = flags; @@ -101,7 +102,7 @@ impl IfReqBuilder { } pub(crate) fn execute(mut self, socket: &F, ioctl: u64) -> Result { - // ioctl is safe. Called with a valid socket fd, and we check the return. + // SAFETY: ioctl is safe. Called with a valid socket fd, and we check the return. let ret = unsafe { ioctl_with_mut_ref(socket, ioctl, &mut self.0) }; if ret < 0 { return Err(Error::IoctlError(IoError::last_os_error())); @@ -119,18 +120,21 @@ impl Tap { pub fn open_named(if_name: &str) -> Result { let terminated_if_name = build_terminated_if_name(if_name)?; + // SAFETY: Open calls are safe because we give a constant null-terminated + // string and verify the result. let fd = unsafe { // Open calls are safe because we give a constant null-terminated // string and verify the result. + let tun_str = CString::new("/dev/net/tun").unwrap(); libc::open( - b"/dev/net/tun\0".as_ptr() as *const c_char, + tun_str.as_ptr() as *const c_char, libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC, ) }; if fd < 0 { return Err(Error::OpenTun(IoError::last_os_error())); } - // We just checked that the fd is valid. + // SAFETY: We just checked that the fd is valid. let tuntap = unsafe { File::from_raw_fd(fd) }; let ifreq = IfReqBuilder::new() @@ -138,9 +142,9 @@ impl Tap { .flags((IFF_TAP | IFF_NO_PI | IFF_VNET_HDR) as i16) .execute(&tuntap, TUNSETIFF())?; - // Safe since only the name is accessed, and it's cloned out. Ok(Tap { tap_file: tuntap, + // SAFETY: since only the name is accessed, and it's cloned out. if_name: unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() }, }) } @@ -156,7 +160,7 @@ impl Tap { /// Set the offload flags for the tap interface. pub fn set_offload(&self, flags: c_uint) -> Result<()> { - // ioctl is safe. Called with a valid tap fd, and we check the return. + // SAFETY: ioctl is safe. Called with a valid tap fd, and we check the return. let ret = unsafe { ioctl_with_val(&self.tap_file, TUNSETOFFLOAD(), c_ulong::from(flags)) }; if ret < 0 { return Err(Error::IoctlError(IoError::last_os_error())); @@ -167,7 +171,7 @@ impl Tap { /// Set the size of the vnet hdr. pub fn set_vnet_hdr_size(&self, size: c_int) -> Result<()> { - // ioctl is safe. Called with a valid tap fd, and we check the return. + // SAFETY: ioctl is safe. Called with a valid tap fd, and we check the return. let ret = unsafe { ioctl_with_ref(&self.tap_file, TUNSETVNETHDRSZ(), &size) }; if ret < 0 { return Err(Error::IoctlError(IoError::last_os_error())); diff --git a/src/vm-vcpu-ref/Cargo.toml b/src/vm-vcpu-ref/Cargo.toml index 2080f4af..92379994 100644 --- a/src/vm-vcpu-ref/Cargo.toml +++ b/src/vm-vcpu-ref/Cargo.toml @@ -11,11 +11,13 @@ keywords = ["virt", "kvm", "vm"] [dependencies] thiserror = "1.0.30" -kvm-ioctls = { version = "0.11.0" } -kvm-bindings = { version = "0.5.0", features = ["fam-wrappers"] } -vm-memory = "0.7.0" +kvm-ioctls = { git = "https://github.com/rust-vmm/kvm.git", version = "0.20.0"} +kvm-bindings = { git = "https://github.com/rust-vmm/kvm.git", version = "0.11.0", features = ["fam-wrappers"] } +vm-memory = "0.16.0" libc = "0.2.76" +vm-superio = "0.8.0" +log = "0.4.21" [dev-dependencies] -vm-memory = { version = "0.7.0", features = ["backend-mmap"] } -vmm-sys-util = "0.8.0" +vm-memory = { version = "0.16.0", features = ["backend-mmap"] } +vmm-sys-util = "0.12.1" diff --git a/src/vm-vcpu-ref/src/aarch64/interrupts.rs b/src/vm-vcpu-ref/src/aarch64/interrupts.rs index d5e77a0e..0e4da236 100644 --- a/src/vm-vcpu-ref/src/aarch64/interrupts.rs +++ b/src/vm-vcpu-ref/src/aarch64/interrupts.rs @@ -392,7 +392,10 @@ mod tests { addr: &mut data as *const u32 as u64, ..Default::default() }; - gic.device_fd().get_device_attr(&mut nr_irqs_attr).unwrap(); + //SAFETY: we prepere the structure + unsafe { + gic.device_fd().get_device_attr(&mut nr_irqs_attr).unwrap(); + } assert_eq!(data, config.num_irqs); } diff --git a/src/vm-vcpu-ref/src/aarch64/regs/icc.rs b/src/vm-vcpu-ref/src/aarch64/regs/icc.rs index 837fe312..94ccc466 100644 --- a/src/vm-vcpu-ref/src/aarch64/regs/icc.rs +++ b/src/vm-vcpu-ref/src/aarch64/regs/icc.rs @@ -63,16 +63,12 @@ pub struct GicSysRegsState { impl SimpleReg { const fn gic_sys_reg(op0: u64, op1: u64, crn: u64, crm: u64, op2: u64) -> SimpleReg { - let offset = (((op0 as u64) << KVM_REG_ARM64_SYSREG_OP0_SHIFT) + let offset = ((op0 << 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); + | ((op1 << KVM_REG_ARM64_SYSREG_OP1_SHIFT) & KVM_REG_ARM64_SYSREG_OP1_MASK as u64) + | ((crn << KVM_REG_ARM64_SYSREG_CRN_SHIFT) & KVM_REG_ARM64_SYSREG_CRN_MASK as u64) + | ((crm << KVM_REG_ARM64_SYSREG_CRM_SHIFT) & KVM_REG_ARM64_SYSREG_CRM_MASK as u64) + | ((op2 << KVM_REG_ARM64_SYSREG_OP2_SHIFT) & KVM_REG_ARM64_SYSREG_OP2_MASK as u64); SimpleReg { offset, size: 8 } } diff --git a/src/vm-vcpu-ref/src/aarch64/regs/mod.rs b/src/vm-vcpu-ref/src/aarch64/regs/mod.rs index 81d92108..65f47a1e 100644 --- a/src/vm-vcpu-ref/src/aarch64/regs/mod.rs +++ b/src/vm-vcpu-ref/src/aarch64/regs/mod.rs @@ -142,9 +142,10 @@ where let mut data = Vec::with_capacity(reg.iter::().count()); for offset in reg.iter::() { let mut val = RegChunk::default(); - fd.get_device_attr(&mut kvm_device_attr( - group, offset, &mut val, mpidr, mpidr_mask, - ))?; + let mut dev_attr = kvm_device_attr(group, offset, &mut val, mpidr, mpidr_mask); + unsafe { + fd.get_device_attr(&mut dev_attr)?; + } data.push(val); } diff --git a/src/vm-vcpu-ref/src/lib.rs b/src/vm-vcpu-ref/src/lib.rs index 30967515..b419943a 100644 --- a/src/vm-vcpu-ref/src/lib.rs +++ b/src/vm-vcpu-ref/src/lib.rs @@ -3,7 +3,7 @@ #![deny(missing_docs)] //! The `vm-vcpu-ref` crate provides abstractions for setting up the `VM` and `vCPUs` for booting. //! The high level interface exported by this crate is uniform on both supported platforms -//! (x86_64 and aarch64). Differences only arise in configuration parameters as there are +//! (x86_64, aarch64, riscv64). Differences only arise in configuration parameters as there are //! features only supported on one platform (i.e. CPUID on x86_64), and in the saved/restored //! state as both platforms define registers and VM/vCPU specific features differently. @@ -12,3 +12,6 @@ pub mod x86_64; /// Helpers for setting up the `VM` for running on aarch64. pub mod aarch64; + +/// Helpers for setting up the `VM` for running on riscv64. +pub mod riscv; diff --git a/src/vm-vcpu-ref/src/riscv/interrupts.rs b/src/vm-vcpu-ref/src/riscv/interrupts.rs new file mode 100644 index 00000000..103527bd --- /dev/null +++ b/src/vm-vcpu-ref/src/riscv/interrupts.rs @@ -0,0 +1,243 @@ +use kvm_ioctls::{DeviceFd, VmFd}; +//use kvm_bindings::{kvm_create_device, kvm_device_type_KVM_DEV_TYPE_RISCV_AIA, KVM_DEV_RISCV_AIA_GRP_ADDR, KVM_DEV_RISCV_AIA_ADDR_APLIC, KVM_DEV_RISCV_AIA_CONFIG_GUEST_BITS, KVM_DEV_RISCV_AIA_CONFIG_SRCS, KVM_DEV_RISCV_AIA_CTRL_INIT, KVM_DEV_RISCV_AIA_GRP_CTRL, KVM_DEV_RISCV_AIA_CONFIG_GROUP_SHIFT, KVM_DEV_RISCV_AIA_CONFIG_GROUP_BITS, KVM_DEV_RISCV_AIA_CONFIG_IDS, KVM_DEV_RISCV_AIA_CONFIG_MODE, KVM_DEV_RISCV_AIA_GRP_CONFIG, KVM_DEV_RISCV_AIA_CONFIG_HART_BITS}; +use kvm_bindings::*; +use std::convert::TryInto; + +const IMSIC_MMIO_GROUP_MIN_SHIFT: u64 = 24; +const AIA_IRQ_NUM_SRCS: u64 = 96; +const AIA_MSI_NUM: u64 = 255; +const BITS_PER_LONG: usize = std::mem::size_of::() * 8; +const IMSIC_MMIO_PAGE_SZ: u64 = 1u64 << 12; + +/// Errors associated with operations related to the GIC. +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum Error { + /// Error calling into KVM ioctl. + #[error("Error calling into KVM ioctl: {0}")] + Kvm(kvm_ioctls::Error), + /// Error creating the APLIC device. + #[error("Error creating the APLIC device: {0}")] + CreateDevice(kvm_ioctls::Error), + /// Error setting an attribute for the APLIC device. + #[error("Error setting an attribute ({0}) for the APLIC device: {1}")] + SetAttr(&'static str, kvm_ioctls::Error), + /// Inconsisted vCPU count between APLIC and vCPU states. + #[error("Inconsisted vCPU count between the APLIC and vCPU state")] + InconsistentVcpuCount, +} + +impl From for Error { + fn from(inner: kvm_ioctls::Error) -> Self { + Error::Kvm(inner) + } +} + +/// Specialized result type for operations on the APLIC. +pub type Result = std::result::Result; + +/// High level wrapper for creating and managing the APLIC device. +#[derive(Debug)] +pub struct APlic { + device_fd: DeviceFd, + num_cpus: u8, +} + +#[derive(Debug, Clone, Default)] +/// Struct to config the APlic +pub struct APlicConfig { + /// Number of CPUs that this APLIC supports. This is not used when configuring + /// a APLIC. + pub num_cpus: u8, +} + +impl APlic { + /// Create a new Plic device + pub fn new(config: APlicConfig, vm_fd: &VmFd) -> Result { + let device_fd = APlic::create_device(vm_fd)?; + let mut aplic = APlic { + num_cpus: config.num_cpus, + device_fd, + }; + aplic.configure_device()?; + Ok(aplic) + } + + // Helper function that sets the required attributes for the device. + fn configure_device(&mut self) -> Result<()> { + let hart_count = self.num_cpus as u64; + let socket_count = 1; + let mut max_hart_per_socket = 0; + let imsic_base: u64 = 0x28000000; + let aplic_base: u64 = 0xc000000; + + let mut attr = kvm_bindings::kvm_device_attr::default(); + let mut default_aia_mode: u32 = 0; + attr.group = KVM_DEV_RISCV_AIA_GRP_CONFIG; + attr.attr = KVM_DEV_RISCV_AIA_CONFIG_MODE as u64; + attr.addr = &mut default_aia_mode as *const u32 as u64; + // SAFETY: This is safe because we set properly kvm_device_attr struct + assert!(unsafe { self.device_fd.get_device_attr(&mut attr).is_ok() }); + + let attr = kvm_bindings::kvm_device_attr { + group: KVM_DEV_RISCV_AIA_GRP_CONFIG, + attr: KVM_DEV_RISCV_AIA_CONFIG_SRCS as u64, + addr: &AIA_IRQ_NUM_SRCS as *const u64 as u64, + flags: 0, + }; + self.device_fd + .set_device_attr(&attr) + .map_err(|e| Error::SetAttr("Could not config srcs", e))?; + + let attr = kvm_bindings::kvm_device_attr { + group: KVM_DEV_RISCV_AIA_GRP_CONFIG, + attr: KVM_DEV_RISCV_AIA_CONFIG_IDS as u64, + addr: &AIA_MSI_NUM as *const u64 as u64, + flags: 0, + }; + self.device_fd + .set_device_attr(&attr) + .map_err(|e| Error::SetAttr("Could not config ids", e))?; + + if socket_count > 1 { + let socket_bits: u64 = find_last_bit(&[socket_count], BITS_PER_LONG) + 1; + let attr = kvm_bindings::kvm_device_attr { + group: KVM_DEV_RISCV_AIA_GRP_CONFIG, + attr: KVM_DEV_RISCV_AIA_CONFIG_GROUP_BITS as u64, + addr: &socket_bits as *const u64 as u64, + flags: 0, + }; + self.device_fd + .set_device_attr(&attr) + .map_err(|e| Error::SetAttr("Could not set group bits", e))?; + + let attr = kvm_bindings::kvm_device_attr { + group: KVM_DEV_RISCV_AIA_GRP_CONFIG, + attr: KVM_DEV_RISCV_AIA_CONFIG_GROUP_SHIFT as u64, + addr: &IMSIC_MMIO_GROUP_MIN_SHIFT as *const u64 as u64, + flags: 0, + }; + self.device_fd + .set_device_attr(&attr) + .map_err(|e| Error::SetAttr("Could not set group shift", e))?; + } + + let mut guest_bits: u64 = 0; + let guest_num = 0; + + if guest_num != 0 { + let last_bit = find_last_bit(&[guest_bits], BITS_PER_LONG) + 1; + guest_bits = last_bit; + } + + let attr = kvm_bindings::kvm_device_attr { + group: KVM_DEV_RISCV_AIA_GRP_CONFIG, + attr: KVM_DEV_RISCV_AIA_CONFIG_GUEST_BITS as u64, + addr: &guest_bits as *const u64 as u64, + flags: 0, + }; + self.device_fd + .set_device_attr(&attr) + .map_err(|e| Error::SetAttr("Could not set guest bits", e))?; + + let attr = kvm_bindings::kvm_device_attr { + group: KVM_DEV_RISCV_AIA_GRP_ADDR, + attr: KVM_DEV_RISCV_AIA_ADDR_APLIC as u64, + addr: &aplic_base as *const u64 as u64, + flags: 0, + }; + self.device_fd + .set_device_attr(&attr) + .map_err(|e| Error::SetAttr("Could not set APLIC base address", e))?; + + let imsic_hart_num_guests: u64 = 1u64 << guest_bits; + let imsic_hart_size: u64 = imsic_hart_num_guests * IMSIC_MMIO_PAGE_SZ; + let group_shift = IMSIC_MMIO_GROUP_MIN_SHIFT; + for socket in 0..socket_count { + let socket_imsic_base = imsic_base + socket * (1u64 << group_shift); + + if max_hart_per_socket < hart_count { + max_hart_per_socket = hart_count; + } + + for i in 0..hart_count { + let imsic_addr: u64 = socket_imsic_base + i * imsic_hart_size; + let attr = kvm_bindings::kvm_device_attr { + group: KVM_DEV_RISCV_AIA_GRP_ADDR, + attr: 1 + i, + addr: &imsic_addr as *const u64 as u64, + flags: 0, + }; + self.device_fd + .set_device_attr(&attr) + .map_err(|e| Error::SetAttr("Could not set IMSIC addr", e))?; + } + } + + let hart_bits = if max_hart_per_socket > 1 { + max_hart_per_socket -= 1; + find_last_bit(&[max_hart_per_socket], BITS_PER_LONG) + 1 + } else { + 0 + }; + + let attr = kvm_bindings::kvm_device_attr { + group: KVM_DEV_RISCV_AIA_GRP_CONFIG, + attr: u64::from(KVM_DEV_RISCV_AIA_CONFIG_HART_BITS), + addr: &hart_bits as *const u64 as u64, + flags: 0, + }; + self.device_fd + .set_device_attr(&attr) + .map_err(|e| Error::SetAttr("Could not set hart bits", e))?; + + let attr = kvm_bindings::kvm_device_attr { + group: KVM_DEV_RISCV_AIA_GRP_CTRL, + attr: u64::from(KVM_DEV_RISCV_AIA_CTRL_INIT), + addr: 0x0, + flags: 0, + }; + self.device_fd + .set_device_attr(&attr) + .map_err(|e| Error::SetAttr("Could not initialize AIA", e))?; + + Ok(()) + } + + // Create the device FD corresponding to the APLIC version specified as parameter. + fn create_device(vm_fd: &VmFd) -> Result { + let mut create_device_attr = kvm_create_device { + type_: kvm_device_type_KVM_DEV_TYPE_RISCV_AIA, + fd: 0, + flags: 0, + }; + vm_fd + .create_device(&mut create_device_attr) + .map_err(Error::CreateDevice) + } +} + +fn find_last_bit(addr: &[u64], size: usize) -> u64 { + let mut words = size / BITS_PER_LONG; + + if size & (BITS_PER_LONG - 1) != 0 { + let remaining_bits = size & (BITS_PER_LONG - 1); + let tmp = addr[words] & (!0usize >> (BITS_PER_LONG - remaining_bits)) as u64; + if tmp != 0 { + return (words * BITS_PER_LONG + BITS_PER_LONG - 1 - tmp.leading_zeros() as usize) + .try_into() + .unwrap(); + } + } + + while words > 0 { + words -= 1; + let tmp = addr[words]; + if tmp != 0 { + return (words * BITS_PER_LONG + BITS_PER_LONG - 1 - tmp.leading_zeros() as usize) + .try_into() + .unwrap(); + } + } + + size as u64 +} diff --git a/src/vm-vcpu-ref/src/riscv/mod.rs b/src/vm-vcpu-ref/src/riscv/mod.rs new file mode 100644 index 00000000..d40a1d68 --- /dev/null +++ b/src/vm-vcpu-ref/src/riscv/mod.rs @@ -0,0 +1,3 @@ +#![cfg(target_arch = "riscv64")] +/// Helpers for setting up the interrupt controller. +pub mod interrupts; diff --git a/src/vm-vcpu-ref/src/x86_64/cpuid.rs b/src/vm-vcpu-ref/src/x86_64/cpuid.rs index 5a615697..951c193f 100644 --- a/src/vm-vcpu-ref/src/x86_64/cpuid.rs +++ b/src/vm-vcpu-ref/src/x86_64/cpuid.rs @@ -48,7 +48,7 @@ pub fn filter_cpuid(kvm: &Kvm, vcpu_id: u8, cpu_count: u8, cpuid: &mut CpuId) { if kvm.check_extension(TscDeadlineTimer) { entry.ecx |= 1 << ECX_TSC_DEADLINE_TIMER_SHIFT; } - entry.ebx = ((vcpu_id as u32) << EBX_CPUID_SHIFT) as u32 + entry.ebx = ((vcpu_id as u32) << EBX_CPUID_SHIFT) | (EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT); if cpu_count > 1 { entry.ebx |= (cpu_count as u32) << EBX_CPU_COUNT_SHIFT; diff --git a/src/vm-vcpu-ref/src/x86_64/mptable.rs b/src/vm-vcpu-ref/src/x86_64/mptable.rs index d8ec2c06..98bcccad 100644 --- a/src/vm-vcpu-ref/src/x86_64/mptable.rs +++ b/src/vm-vcpu-ref/src/x86_64/mptable.rs @@ -5,7 +5,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the THIRD-PARTY file. -use std::io; use std::mem; use std::result; use std::slice; @@ -96,7 +95,7 @@ const MPC_SIGNATURE: [c_char; 4] = char_array!(c_char; 'P', 'C', 'M', 'P'); const MPC_SPEC: i8 = 4; const MPC_OEM: [c_char; 8] = char_array!(c_char; 'r', 'u', 's', 't', '-', 'v', 'm', 'm'); const MPC_PRODUCT_ID: [c_char; 12] = ['0' as c_char; 12]; -const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; 'I', 'S', 'A', ' ', ' ', ' '); +const BUS_TYPE_ISA: [u8; 6] = char_array!(u8; b'I', b'S', b'A', b' ', b' ', b' '); const IO_APIC_DEFAULT_PHYS_BASE: u32 = 0xfec0_0000; // source: linux/arch/x86/include/asm/apicdef.h const APIC_DEFAULT_PHYS_BASE: u32 = 0xfee0_0000; // source: linux/arch/x86/include/asm/apicdef.h const APIC_VERSION: u8 = 0x14; @@ -190,7 +189,8 @@ impl MpTable { let mut checksum: u8 = 0; let max_ioapic_id = self.cpu_num + 1; - mem.read_from(base_mp, &mut io::repeat(0), mp_size) + let zero_slice: Vec = (0..mp_size).map(|_| 0u8).collect(); + mem.write_slice(zero_slice.as_slice(), base_mp) .map_err(|_| Error::Clear)?; { @@ -405,23 +405,14 @@ mod tests { let mpc_offset = GuestAddress(u64::from(mpf_intel.0.physptr)); let mpc_table: MpcTable = mem.read_obj(mpc_offset).unwrap(); - struct Sum(u8); - impl io::Write for Sum { - fn write(&mut self, buf: &[u8]) -> io::Result { - for v in buf.iter() { - self.0 = self.0.wrapping_add(*v); - } - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - } - - let mut sum = Sum(0); - mem.write_to(mpc_offset, &mut sum, mpc_table.0.length as usize) + let mut mpc_table: Vec = (0..mpc_table.0.length).map(|_| 0u8).collect(); + mem.read_slice(mpc_table.as_mut_slice(), mpc_offset) .unwrap(); - assert_eq!(sum.0, 0); + let mut csum: u8 = 0; + mpc_table + .iter() + .for_each(|byte| csum = csum.wrapping_add(*byte)); + assert_eq!(csum, 0); } #[test] diff --git a/src/vm-vcpu/Cargo.toml b/src/vm-vcpu/Cargo.toml index a3f878fb..32040516 100644 --- a/src/vm-vcpu/Cargo.toml +++ b/src/vm-vcpu/Cargo.toml @@ -9,10 +9,10 @@ edition = "2018" [dependencies] thiserror = "1.0.30" libc = "0.2.76" -kvm-bindings = { version = "0.5.0", features = ["fam-wrappers"] } -kvm-ioctls = "0.11.0" -vm-memory = "0.7.0" -vmm-sys-util = ">=0.8.0" +kvm-bindings = { git = "https://github.com/rust-vmm/kvm.git", version = "0.11.0", features = ["fam-wrappers"] } +kvm-ioctls = { git = "https://github.com/rust-vmm/kvm.git", version = "0.20.0"} +vm-memory = "0.16.0" +vmm-sys-util = "0.12.1" vm-device = "0.1.0" utils = { path = "../utils" } @@ -20,4 +20,4 @@ vm-vcpu-ref = { path = "../vm-vcpu-ref" } arch = { path = "../arch" } [dev-dependencies] -vm-memory = { version = "0.7.0", features = ["backend-mmap"] } +vm-memory = { version = "0.16.0", features = ["backend-mmap"] } diff --git a/src/vm-vcpu/src/vcpu/mod.rs b/src/vm-vcpu/src/vcpu/mod.rs index 26c15ebc..5259681d 100644 --- a/src/vm-vcpu/src/vcpu/mod.rs +++ b/src/vm-vcpu/src/vcpu/mod.rs @@ -6,6 +6,8 @@ use libc::siginfo_t; use std::cell::RefCell; use std::ffi::c_void; use std::io::{self, stdin}; +#[cfg(target_arch = "riscv64")] +use std::mem::offset_of; use std::os::raw::c_int; use std::result; use std::sync::{Arc, Barrier, Condvar, Mutex}; @@ -20,10 +22,16 @@ use kvm_bindings::{ kvm_mp_state, kvm_one_reg, kvm_vcpu_init, KVM_REG_ARM64, KVM_REG_ARM_CORE, KVM_REG_SIZE_U64, KVM_SYSTEM_EVENT_CRASH, KVM_SYSTEM_EVENT_RESET, KVM_SYSTEM_EVENT_SHUTDOWN, }; +#[cfg(target_arch = "riscv64")] +use kvm_bindings::{ + kvm_mp_state, KVM_REG_RISCV, KVM_REG_RISCV_CORE, KVM_REG_SIZE_U64, KVM_SYSTEM_EVENT_CRASH, + KVM_SYSTEM_EVENT_RESET, KVM_SYSTEM_EVENT_SHUTDOWN, +}; + use kvm_ioctls::{Kvm, VcpuExit, VcpuFd, VmFd}; use vm_device::bus::{MmioAddress, PioAddress}; use vm_device::device_manager::{IoManager, MmioManager, PioManager}; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use vm_memory::GuestMemoryRegion; #[cfg(target_arch = "x86_64")] use vm_memory::{Address, Bytes}; @@ -51,7 +59,11 @@ use regs::*; use crate::vm::VmRunState; #[cfg(target_arch = "aarch64")] -use arch::{AARCH64_FDT_MAX_SIZE, AARCH64_PHYS_MEM_START}; +use arch::aarch64_consts::{AARCH64_FDT_MAX_SIZE, AARCH64_PHYS_MEM_START}; +#[cfg(target_arch = "riscv64")] +use arch::riscv64_consts::*; +#[cfg(target_arch = "riscv64")] +use arch::{kvm_reg_id, kvm_reg_riscv_core_reg, riscv_core_reg}; /// Initial stack for the boot CPU. #[cfg(target_arch = "x86_64")] @@ -256,7 +268,7 @@ impl VcpuConfigList { id: index, msrs: supported_msrs.clone(), }; - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] let vcpu_config = VcpuConfig { id: index }; configs.push(vcpu_config); @@ -268,7 +280,6 @@ impl VcpuConfigList { /// Structure holding the kvm state for an x86_64 VCPU. #[cfg(target_arch = "x86_64")] -#[derive(Clone)] pub struct VcpuState { pub cpuid: CpuId, pub msrs: Msrs, @@ -283,6 +294,24 @@ pub struct VcpuState { pub config: VcpuConfig, } +#[cfg(target_arch = "x86_64")] +impl Clone for VcpuState { + fn clone(&self) -> Self { + VcpuState { + cpuid: self.cpuid.clone(), + msrs: self.msrs.clone(), + debug_regs: self.debug_regs, + lapic: self.lapic, + mp_state: self.mp_state, + regs: self.regs, + sregs: self.sregs, + vcpu_events: self.vcpu_events, + xcrs: self.xcrs, + xsave: Default::default(), + config: self.config.clone(), + } + } +} #[cfg(target_arch = "aarch64")] #[derive(Clone)] pub struct VcpuState { @@ -294,6 +323,13 @@ pub struct VcpuState { pub config: VcpuConfig, } +#[cfg(target_arch = "riscv64")] +#[derive(Clone)] +pub struct VcpuState { + pub mp_state: kvm_mp_state, + pub config: VcpuConfig, +} + /// Represents the current run state of the VCPUs. #[derive(Default)] pub struct VcpuRunState { @@ -323,7 +359,7 @@ pub struct KvmVcpu { } impl KvmVcpu { - thread_local!(static TLS_VCPU_PTR: RefCell> = RefCell::new(None)); + thread_local!(static TLS_VCPU_PTR: RefCell> = const { RefCell::new(None) }); /// Create a new vCPU. // This is needed so we can initialize the vcpu the same way on x86_64 and aarch64, but @@ -339,8 +375,8 @@ impl KvmVcpu { ) -> Result { #[cfg(target_arch = "x86_64")] let vcpu; - #[cfg(target_arch = "aarch64")] - let mut vcpu; + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + let mut vcpu; //TODO check if there are missing configurations since we do just create_vcpu vcpu = KvmVcpu { vcpu_fd: vm_fd @@ -367,6 +403,9 @@ impl KvmVcpu { vcpu.configure_regs(memory)?; } + #[cfg(target_arch = "riscv64")] + vcpu.configure_regs(memory)?; + Ok(vcpu) } @@ -412,7 +451,7 @@ impl KvmVcpu { fn set_state(&mut self, state: VcpuState) -> Result<()> { for reg in state.regs { self.vcpu_fd - .set_one_reg(reg.id, reg.addr) + .set_one_reg(reg.id, &u128::to_le_bytes(reg.addr.into())) .map_err(Error::VcpuSetReg)?; } @@ -423,6 +462,15 @@ impl KvmVcpu { Ok(()) } + #[cfg(target_arch = "riscv64")] + fn set_state(&mut self, state: VcpuState) -> Result<()> { + self.vcpu_fd + .set_mp_state(state.mp_state) + .map_err(Error::VcpuSetMpState)?; + + Ok(()) + } + /// Create a vCPU from a previously saved state. pub fn from_state( vm_fd: &VmFd, @@ -440,7 +488,6 @@ impl KvmVcpu { run_barrier, run_state, }; - #[cfg(target_arch = "aarch64")] vcpu.init(vm_fd)?; @@ -458,7 +505,7 @@ impl KvmVcpu { data = (PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT | PSR_MODE_EL1h).into(); reg_id = arm64_core_reg!(pstate); self.vcpu_fd - .set_one_reg(reg_id, data) + .set_one_reg(reg_id, &(data as u128).to_le_bytes()) .map_err(Error::VcpuSetReg)?; // Other cpus are powered off initially @@ -466,17 +513,39 @@ impl KvmVcpu { /* X0 -- fdt address */ let mut fdt_offset: u64 = guest_mem.iter().map(|region| region.len()).sum(); fdt_offset = fdt_offset - AARCH64_FDT_MAX_SIZE - 0x10000; - data = (AARCH64_PHYS_MEM_START + fdt_offset) as u64; + data = AARCH64_PHYS_MEM_START + fdt_offset; // hack -- can't get this to do offsetof(regs[0]) but luckily it's at offset 0 reg_id = arm64_core_reg!(regs); self.vcpu_fd - .set_one_reg(reg_id, data) + .set_one_reg(reg_id, &(data as u128).to_le_bytes()) .map_err(Error::VcpuSetReg)?; } Ok(()) } + #[cfg(target_arch = "riscv64")] + fn configure_regs(&mut self, guest_mem: &M) -> Result<()> { + let mut data: u64; + if self.config.id == 0 { + let mut fdt_offset: u64 = guest_mem.iter().map(|region| region.len()).sum(); + fdt_offset = fdt_offset - RISCV64_FDT_MAX_SIZE - 0x10000; + + data = RISCV64_PHYS_MEM_START + fdt_offset; + let dt_reg = riscv_core_reg!(a1); + self.vcpu_fd + .set_one_reg(dt_reg, &(data as u128).to_le_bytes()) + .map_err(Error::VcpuSetReg)?; + + data = 0; + let hartid_reg = riscv_core_reg!(a0); + self.vcpu_fd + .set_one_reg(hartid_reg, &(data as u128).to_le_bytes()) + .map_err(Error::VcpuSetReg)?; + } + Ok(()) + } + #[cfg(target_arch = "aarch64")] fn init(&mut self, vm_fd: &VmFd) -> Result<()> { let mut kvi: kvm_vcpu_init = kvm_vcpu_init::default(); @@ -554,11 +623,11 @@ impl KvmVcpu { // Write segments to guest memory. gdt_table.write_to_mem(guest_memory).map_err(Error::Gdt)?; - sregs.gdt.base = BOOT_GDT_OFFSET as u64; + sregs.gdt.base = BOOT_GDT_OFFSET; sregs.gdt.limit = std::mem::size_of_val(&gdt_table) as u16 - 1; write_idt_value(0, guest_memory).map_err(Error::Gdt)?; - sregs.idt.base = BOOT_IDT_OFFSET as u64; + sregs.idt.base = BOOT_IDT_OFFSET; sregs.idt.limit = std::mem::size_of::() as u16 - 1; sregs.cs = code_seg; @@ -642,7 +711,7 @@ impl KvmVcpu { fn init_tls(&mut self) -> Result<()> { Self::TLS_VCPU_PTR.with(|vcpu| { if vcpu.borrow().is_none() { - *vcpu.borrow_mut() = Some(self as *const KvmVcpu); + *vcpu.borrow_mut() = Some(self as *mut KvmVcpu); Ok(()) } else { Err(Error::TlsInitialized) @@ -653,13 +722,13 @@ impl KvmVcpu { fn set_local_immediate_exit(value: u8) { Self::TLS_VCPU_PTR.with(|v| { - if let Some(vcpu) = *v.borrow() { - // The block below modifies a mmaped memory region (`kvm_run` struct) which is valid + if let Some(vcpu) = *v.borrow_mut() { + // SAFETY: The block below modifies a mmaped memory region (`kvm_run` struct) which is valid // as long as the `VMM` is still in scope. This function is called in response to // SIGRTMIN(), while the vCPU threads are still active. Their termination are // strictly bound to the lifespan of the `VMM` and it precedes the `VMM` dropping. unsafe { - let vcpu_ref = &*vcpu; + let vcpu_ref = &mut *vcpu; vcpu_ref.vcpu_fd.set_kvm_immediate_exit(value); }; } @@ -670,8 +739,8 @@ impl KvmVcpu { /// /// # Arguments /// - /// * `instruction_pointer`: Represents the start address of the vcpu. This can be None - /// when the IP is specified using the platform dependent registers. + /// * `instruction_pointer`: Represents the start address of the vcpu. + /// This can be None when the IP is specified using the platform dependent registers. #[allow(clippy::if_same_then_else)] pub fn run(&mut self, instruction_pointer: Option) -> Result<()> { if let Some(ip) = instruction_pointer { @@ -682,7 +751,16 @@ impl KvmVcpu { let data = ip.0; let reg_id = arm64_core_reg!(pc); self.vcpu_fd - .set_one_reg(reg_id, data) + .set_one_reg(reg_id, &(data as u128).to_le_bytes()) + .map_err(Error::VcpuSetReg)?; + } + #[cfg(target_arch = "riscv64")] + if self.config.id == 0 { + let data = ip.0; + let reg_id: u64 = riscv_core_reg!(pc); + /* Set program counter */ + self.vcpu_fd + .set_one_reg(reg_id, &(data as u128).to_le_bytes()) .map_err(Error::VcpuSetReg)?; } } @@ -759,7 +837,10 @@ impl KvmVcpu { .mmio_read(MmioAddress(addr), data) .is_err() { - debug!("Failed to read from mmio addr={} data={:#?}", addr, data); + debug!( + "Failed to read from mmio addr={:#016x?} data={:#?}", + addr, data + ); } } VcpuExit::MmioWrite(addr, data) => { @@ -770,10 +851,13 @@ impl KvmVcpu { .mmio_write(MmioAddress(addr), data) .is_err() { - debug!("Failed to write to mmio"); + debug!( + "Failed to write to mmio addr={:#016x?} data={:#?}", + addr, data + ); } } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] VcpuExit::SystemEvent(type_, flags) => match type_ { KVM_SYSTEM_EVENT_SHUTDOWN | KVM_SYSTEM_EVENT_RESET @@ -783,6 +867,14 @@ impl KvmVcpu { eprintln!("Failed to set canon mode. Stdin will not echo."); } self.run_state.set_and_notify(VmRunState::Exiting); + if type_ == KVM_SYSTEM_EVENT_SHUTDOWN { + println!("KVM session ended correctly"); + } else { + println!( + "Exit reason: {:#?}", + VcpuExit::SystemEvent(type_, flags) + ); + } break; } _ => { @@ -906,6 +998,19 @@ impl KvmVcpu { config: self.config.clone(), }) } + + #[cfg(target_arch = "riscv64")] + pub fn save_state(&mut self) -> Result { + let mp_state = self.vcpu_fd.get_mp_state().map_err(Error::VcpuGetMpState)?; + Ok(VcpuState { + mp_state, + config: self.config.clone(), + }) + } + + pub fn get_vcpu_fd(&self) -> &VcpuFd { + &self.vcpu_fd + } } impl Drop for KvmVcpu { diff --git a/src/vm-vcpu/src/vcpu/regs.rs b/src/vm-vcpu/src/vcpu/regs.rs index 226d70d2..6777e875 100644 --- a/src/vm-vcpu/src/vcpu/regs.rs +++ b/src/vm-vcpu/src/vcpu/regs.rs @@ -1,5 +1,6 @@ use kvm_bindings::*; use kvm_ioctls::VcpuFd; +use std::convert::TryInto; use super::Error; @@ -59,8 +60,15 @@ pub fn get_regs_and_mpidr(vcpu_fd: &VcpuFd) -> Result<(Vec, u64), E let mut mpidr = None; let mut regs = Vec::with_capacity(reg_id_list.as_slice().len()); for &id in reg_id_list.as_slice() { - let addr = vcpu_fd.get_one_reg(id).map_err(Error::VcpuGetReg)?; - regs.push(kvm_one_reg { id, addr }); + let mut addr = [0_u8; 16]; + vcpu_fd + .get_one_reg(id, &mut addr) + .map_err(Error::VcpuGetReg)?; + let new_addr = &addr[0..8]; + regs.push(kvm_one_reg { + id, + addr: u64::from_le_bytes(new_addr.try_into().unwrap()), + }); if id == MPIDR_EL1 { mpidr = Some(addr); @@ -72,5 +80,7 @@ pub fn get_regs_and_mpidr(vcpu_fd: &VcpuFd) -> Result<(Vec, u64), E } // unwrap() is safe because of the is_none() check above - Ok((regs, mpidr.unwrap())) + let new_mpidr = &mpidr.unwrap()[0..8]; + // unwrap safe because we take the first 8 bits + Ok((regs, u64::from_le_bytes(new_mpidr.try_into().unwrap()))) } diff --git a/src/vm-vcpu/src/vm.rs b/src/vm-vcpu/src/vm.rs index 05514fe6..bdd70c4f 100644 --- a/src/vm-vcpu/src/vm.rs +++ b/src/vm-vcpu/src/vm.rs @@ -1,13 +1,7 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // Copyright 2017 The Chromium OS Authors. All rights reserved. // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -#[cfg(target_arch = "x86_64")] -use std::convert::TryInto; -use std::io::{self, ErrorKind}; -use std::sync::{Arc, Barrier, Mutex}; -use std::thread::{self, JoinHandle}; - +use crate::vcpu::VcpuState; use kvm_bindings::kvm_userspace_memory_region; #[cfg(target_arch = "x86_64")] use kvm_bindings::{ @@ -16,21 +10,30 @@ use kvm_bindings::{ }; use kvm_ioctls::{Kvm, VmFd}; +#[cfg(target_arch = "x86_64")] +use std::convert::TryInto; +use std::io::{self, ErrorKind}; +use std::sync::{Arc, Barrier, Mutex}; +use std::thread::{self, JoinHandle}; use vm_device::device_manager::IoManager; use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryRegion}; use vmm_sys_util::errno::Error as Errno; use vmm_sys_util::eventfd::EventFd; use vmm_sys_util::signal::{Killable, SIGRTMIN}; -use crate::vcpu::{self, KvmVcpu, VcpuConfigList, VcpuRunState, VcpuState}; +use crate::vcpu::{self, KvmVcpu, VcpuConfigList, VcpuRunState}; #[cfg(target_arch = "aarch64")] use vm_vcpu_ref::aarch64::interrupts::{self, Gic, GicConfig, GicState}; +#[cfg(target_arch = "riscv64")] +use vm_vcpu_ref::riscv::interrupts::{self, APlic, APlicConfig}; #[cfg(target_arch = "x86_64")] use vm_vcpu_ref::x86_64::mptable::{self, MpTable}; #[cfg(target_arch = "aarch64")] pub const MAX_IRQ: u32 = interrupts::MIN_NR_IRQS; +#[cfg(target_arch = "riscv64")] +pub const MAX_IRQ: u32 = 64; #[cfg(target_arch = "x86_64")] pub const MAX_IRQ: u32 = mptable::IRQ_MAX as u32; @@ -73,6 +76,13 @@ pub struct VmState { pub gic_state: GicState, } +#[cfg(target_arch = "riscv64")] +#[derive(Clone)] +pub struct VmState { + pub config: VmConfig, + pub vcpus_state: Vec, +} + /// A KVM specific implementation of a Virtual Machine. /// /// Provides abstractions for working with a VM. Once a generic Vm trait will be available, @@ -89,8 +99,8 @@ pub struct KvmVm { vcpu_barrier: Arc, vcpu_run_state: Arc, - #[cfg(target_arch = "aarch64")] - gic: Option, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + irqchip: Option, } #[derive(Debug, thiserror::Error)] @@ -112,7 +122,7 @@ pub enum Error { #[cfg(target_arch = "x86_64")] SetupInterruptController(kvm_ioctls::Error), #[error("Failed to setup the interrupt controller: {0}")] - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] SetupInterruptController(interrupts::Error), /// Failed to create the vcpu. #[error("Failed to create the vcpu: {0}")] @@ -170,13 +180,36 @@ impl From for Error { } } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] impl From for Error { fn from(inner: interrupts::Error) -> Self { Error::SetupInterruptController(inner) } } +pub enum IrqChip { + #[cfg(target_arch = "aarch64")] + Gic(Gic), + #[cfg(target_arch = "riscv64")] + APlic(APlic), +} + +impl IrqChip { + #[allow(dead_code)] + #[cfg(target_arch = "aarch64")] + fn get_gic(&self) -> &Gic { + match self { + IrqChip::Gic(gic) => gic, + } + } + #[allow(dead_code)] + #[cfg(target_arch = "riscv64")] + fn get_aplic(&self) -> &APlic { + match self { + IrqChip::APlic(aplic) => aplic, + } + } +} /// Dedicated [`Result`](https://doc.rust-lang.org/std/result/) type. pub type Result = std::result::Result; @@ -222,8 +255,8 @@ impl KvmVm { exit_handler, vcpu_run_state, - #[cfg(target_arch = "aarch64")] - gic: None, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + irqchip: None, }; vm.configure_memory_regions(guest_memory, kvm)?; @@ -283,7 +316,9 @@ impl KvmVm { #[cfg(target_arch = "aarch64")] fn set_state(&mut self, state: VmState) -> Result<()> { let mpidrs = state.vcpus_state.iter().map(|state| state.mpidr).collect(); - self.gic().restore_state(&state.gic_state, mpidrs)?; + self.irqchip() + .get_gic() + .restore_state(&state.gic_state, mpidrs)?; Ok(()) } @@ -318,6 +353,11 @@ impl KvmVm { vm.setup_irq_controller()?; vm.set_state(state)?; } + #[cfg(target_arch = "riscv64")] + { + vm.create_vcpus_from_state::(bus, vcpus_state)?; + vm.setup_irq_controller()?; + } Ok(vm) } @@ -326,11 +366,16 @@ impl KvmVm { self.fd.clone() } - #[cfg(target_arch = "aarch64")] - fn gic(&self) -> &Gic { - // This method panics if the `gic` field, which - // is Option is not properly initialized. - self.gic.as_ref().expect("GIC is not set") + pub fn get_vcpus(&self) -> &Vec { + &self.vcpus + } + + #[allow(dead_code)] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + fn irqchip(&self) -> &IrqChip { + // This method panics if the `irqchip` field, which + // is Option is not properly initialized. + self.irqchip.as_ref().expect("Irqchip is not set") } /// Returns the max irq number independent of arch. @@ -349,13 +394,13 @@ impl KvmVm { let memory_region = kvm_userspace_memory_region { slot: index as u32, guest_phys_addr: region.start_addr().raw_value(), - memory_size: region.len() as u64, + memory_size: region.len(), // It's safe to unwrap because the guest address is valid. userspace_addr: guest_memory.get_host_address(region.start_addr()).unwrap() as u64, flags: 0, }; - // Safe because: + // SAFETY: because: // * userspace_addr is a valid address for a memory region, obtained by calling // get_host_address() on a valid region's start address; // * the memory regions do not overlap - there's either a single region spanning @@ -405,7 +450,19 @@ impl KvmVm { }, &self.vm_fd(), )?; - self.gic = Some(gic); + self.irqchip = Some(IrqChip::Gic(gic)); + Ok(()) + } + + #[cfg(target_arch = "riscv64")] + pub fn setup_irq_controller(&mut self) -> Result<()> { + let aplic = APlic::new( + APlicConfig { + num_cpus: self.config.num_vcpus, + }, + &self.vm_fd(), + )?; + self.irqchip = Some(IrqChip::APlic(aplic)); Ok(()) } @@ -434,7 +491,7 @@ impl KvmVm { }) .collect::>>() .map_err(Error::CreateVcpu)?; - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] self.setup_irq_controller()?; Ok(()) @@ -478,7 +535,7 @@ impl KvmVm { /// # Arguments /// /// * `vcpu_run_addr`: address in guest memory where the vcpu run starts. This can be None - /// when the IP is specified using the platform dependent registers. + /// when the IP is specified using the platform dependent registers. pub fn run(&mut self, vcpu_run_addr: Option) -> Result<()> { if self.vcpus.len() != self.config.num_vcpus as usize { return Err(Error::RunVcpus(io::Error::from(ErrorKind::InvalidInput))); @@ -530,7 +587,7 @@ impl KvmVm { .map_err(Error::SaveVcpuState)?; let mpidrs = vcpus_state.iter().map(|state| state.mpidr).collect(); - let gic_state = self.gic().save_state(mpidrs)?; + let gic_state = self.irqchip().get_gic().save_state(mpidrs)?; Ok(VmState { config: self.config.clone(), @@ -539,6 +596,20 @@ impl KvmVm { }) } + #[cfg(target_arch = "riscv64")] + pub fn save_state(&mut self) -> Result { + let vcpus_state = self + .vcpus + .iter_mut() + .map(|vcpu| vcpu.save_state()) + .collect::>>() + .map_err(Error::SaveVcpuState)?; + Ok(VmState { + config: self.config.clone(), + vcpus_state, + }) + } + /// Retrieve the state of a `paused` VM. /// /// Returns an error when the VM is not paused. @@ -659,7 +730,7 @@ mod tests { #[test] #[cfg(target_arch = "x86_64")] fn test_failed_setup_mptable() { - let num_vcpus = (MAX_SUPPORTED_CPUS + 1) as u8; + let num_vcpus = MAX_SUPPORTED_CPUS + 1; let kvm = Kvm::new().unwrap(); let guest_memory = default_memory(); let res = default_vm(&kvm, &guest_memory, num_vcpus); @@ -710,10 +781,12 @@ mod tests { fd: Arc::new(kvm.create_vm().unwrap()), exit_handler: WrappedExitHandler::default(), vcpu_run_state: Arc::new(VcpuRunState::default()), - #[cfg(target_arch = "aarch64")] - gic: None, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + irqchip: None, }; + #[cfg(target_arch = "riscv64")] + vm.fd.create_vcpu(0).unwrap(); // Setting up the irq_controller twice should return an error. vm.setup_irq_controller().unwrap(); let res = vm.setup_irq_controller(); @@ -786,7 +859,7 @@ mod tests { assert!(KvmVm::from_state(&kvm, vm_state, &guest_memory, exit_handler, io_manager).is_ok()); } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] #[test] fn test_vm_save_state() { let num_vcpus = 4; diff --git a/src/vmm/Cargo.toml b/src/vmm/Cargo.toml index a38c174e..8bb383bf 100644 --- a/src/vmm/Cargo.toml +++ b/src/vmm/Cargo.toml @@ -5,15 +5,15 @@ authors = ["rust-vmm AWS maintainers "] edition = "2018" [dependencies] -event-manager = "0.2.1" -kvm-bindings = { version = "0.5.0", features = ["fam-wrappers"] } -kvm-ioctls = "0.11.0" +event-manager = "0.4.0" +kvm-ioctls = { git = "https://github.com/rust-vmm/kvm.git"} +kvm-bindings = { git = "https://github.com/rust-vmm/kvm.git", features = ["fam-wrappers"] } libc = "0.2.91" -linux-loader = { version = "0.4.0", features = ["bzimage", "elf"] } +linux-loader = { version = "0.13.0", features = ["bzimage", "elf"] } vm-allocator = "0.1.0" -vm-memory = { version = "0.7.0", features = ["backend-mmap"] } -vm-superio = "0.5.0" -vmm-sys-util = "0.8.0" +vm-memory = { version = "0.16.0", features = ["backend-mmap"] } +vm-superio = "0.8.0" +vmm-sys-util = "0.12.1" vm-device = "0.1.0" devices = { path = "../devices" } diff --git a/src/vmm/src/config/builder.rs b/src/vmm/src/config/builder.rs index 30fb0a25..18a2311e 100644 --- a/src/vmm/src/config/builder.rs +++ b/src/vmm/src/config/builder.rs @@ -5,7 +5,8 @@ use std::convert::TryFrom; use super::{ - BlockConfig, ConversionError, KernelConfig, MemoryConfig, NetConfig, VMMConfig, VcpuConfig, + BlockConfig, ConversionError, DTBConfig, DumpDTBConfig, KernelConfig, MemoryConfig, NetConfig, + VMMConfig, VcpuConfig, }; /// Builder structure for VMMConfig @@ -40,29 +41,25 @@ impl Builder { /// # use vmm::VMMConfig; /// /// let vmmconfig = VMMConfig::builder() - /// .memory_config(Some("size_mib=1024")) - /// .vcpu_config(Some("num=1")) - /// .kernel_config(Some("path=/path/to/bzImage")) - /// .net_config(Some("tap=tap0")) - /// .block_config(Some("path=/dev/loop0")) + /// .memory_config(Some(&String::from("size_mib=1024"))) + /// .vcpu_config(Some(&String::from("num=1"))) + /// .kernel_config(Some(&String::from("path=/path/to/bzImage"))) + /// .net_config(Some(&String::from("tap=tap0"))) + /// .block_config(Some(&String::from("path=/dev/loop0"))) /// .build(); /// /// assert!(vmmconfig.is_ok()); /// ``` pub fn build(&self) -> Result { // Check if there are any errors - match &self.inner { - Ok(vc) => { - // Empty kernel image path. - if vc.kernel_config.path.to_str().unwrap().is_empty() { - return Err(ConversionError::ParseKernel( - "Kernel Image Path is Empty.".to_string(), - )); - } + if let Ok(vc) = &self.inner { + // Empty kernel image path. + if vc.kernel_config.path.to_str().unwrap().is_empty() { + return Err(ConversionError::ParseKernel( + "Kernel Image Path is Empty.".to_string(), + )); } - Err(_) => {} } - self.inner.clone() } @@ -168,6 +165,36 @@ impl Builder { } } + /// Configure Builder with dump DTB Configuration for the VMM. + pub fn dump_dtb_config(self, dump_dtb: Option) -> Self + where + DumpDTBConfig: TryFrom, + >::Error: Into, + { + match dump_dtb { + Some(dd) => self.and_then(|mut config| { + config.dump_dtb = Some(TryFrom::try_from(dd).map_err(Into::into)?); + Ok(config) + }), + None => self, + } + } + + /// Configure Builder with DTB Configuration for the VMM. + pub fn dtb_config(self, dtb: Option) -> Self + where + DTBConfig: TryFrom, + >::Error: Into, + { + match dtb { + Some(d) => self.and_then(|mut config| { + config.dtb = Some(TryFrom::try_from(d).map_err(Into::into)?); + Ok(config) + }), + None => self, + } + } + fn and_then(self, func: F) -> Self where F: FnOnce(VMMConfig) -> Result, @@ -195,8 +222,8 @@ mod tests { #[test] fn test_builder_memory_config_success() { let vmm_config = Builder::default() - .memory_config(Some("size_mib=1024")) - .kernel_config(Some("path=bzImage")) + .memory_config(Some(&String::from("size_mib=1024"))) + .kernel_config(Some(&String::from("path=bzImage"))) .build(); assert!(vmm_config.is_ok()); assert_eq!( @@ -208,8 +235,8 @@ mod tests { #[test] fn test_builder_memory_config_none_default() { let vmm_config = Builder::default() - .memory_config(None as Option<&str>) - .kernel_config(Some("path=bzImage")) + .memory_config(None as Option<&String>) + .kernel_config(Some(&String::from("path=bzImage"))) .build(); assert!(vmm_config.is_ok()); assert_eq!( @@ -221,8 +248,8 @@ mod tests { #[test] fn test_builder_vcpu_config_success() { let vmm_config = Builder::default() - .vcpu_config(Some("num=2")) - .kernel_config(Some("path=bzImage")) + .vcpu_config(Some(&String::from("num=2"))) + .kernel_config(Some(&String::from("path=bzImage"))) .build(); assert!(vmm_config.is_ok()); assert_eq!(vmm_config.unwrap().vcpu_config, VcpuConfig { num: 2 }); @@ -231,8 +258,8 @@ mod tests { #[test] fn test_builder_vcpu_config_none_default() { let vmm_config = Builder::default() - .memory_config(None as Option<&str>) - .kernel_config(Some("path=bzImage")) + .memory_config(None as Option<&String>) + .kernel_config(Some(&String::from("path=bzImage"))) .build(); assert!(vmm_config.is_ok()); assert_eq!(vmm_config.unwrap().vcpu_config, VcpuConfig { num: 1 }); @@ -241,7 +268,7 @@ mod tests { #[test] fn test_builder_kernel_config_success_default() { let vmm_config = Builder::default() - .kernel_config(Some("path=bzImage")) + .kernel_config(Some(&String::from("path=bzImage"))) .build(); assert!(vmm_config.is_ok()); assert_eq!( @@ -257,7 +284,7 @@ mod tests { #[test] fn test_builder_kernel_config_none_error() { let vmm_config = Builder::default() - .kernel_config(None as Option<&str>) + .kernel_config(None as Option<&String>) .build(); assert!(vmm_config.is_err()); @@ -266,8 +293,8 @@ mod tests { #[test] fn test_builder_net_config_none_default() { let vmm_config = Builder::default() - .net_config(None as Option<&str>) - .kernel_config(Some("path=bzImage")) + .net_config(None as Option<&String>) + .kernel_config(Some(&String::from("path=bzImage"))) .build(); assert!(vmm_config.is_ok()); assert!(vmm_config.unwrap().net_config.is_none()); @@ -276,8 +303,8 @@ mod tests { #[test] fn test_builder_net_config_success() { let vmm_config = Builder::default() - .net_config(Some("tap=tap0")) - .kernel_config(Some("path=bzImage")) + .net_config(Some(&String::from("tap=tap0"))) + .kernel_config(Some(&String::from("path=bzImage"))) .build(); assert!(vmm_config.is_ok()); assert_eq!( @@ -291,8 +318,8 @@ mod tests { #[test] fn test_builder_block_config_none_default() { let vmm_config = Builder::default() - .block_config(None as Option<&str>) - .kernel_config(Some("path=bzImage")) + .block_config(None as Option<&String>) + .kernel_config(Some(&String::from("path=bzImage"))) .build(); assert!(vmm_config.is_ok()); assert!(vmm_config.unwrap().block_config.is_none()); @@ -301,8 +328,8 @@ mod tests { #[test] fn test_builder_block_config_success() { let vmm_config = Builder::default() - .block_config(Some("path=/dev/loop0")) - .kernel_config(Some("path=bzImage")) + .block_config(Some(&String::from("path=/dev/loop0"))) + .kernel_config(Some(&String::from("path=bzImage"))) .build(); assert!(vmm_config.is_ok()); assert_eq!( @@ -316,11 +343,11 @@ mod tests { #[test] fn test_builder_vmm_config_success() { let vmm_config = Builder::default() - .memory_config(Some("size_mib=1024")) - .vcpu_config(Some("num=2")) - .net_config(Some("tap=tap0")) - .kernel_config(Some("path=bzImage")) - .block_config(Some("path=/dev/loop0")) + .memory_config(Some(&String::from("size_mib=1024"))) + .vcpu_config(Some(&String::from("num=2"))) + .net_config(Some(&String::from("tap=tap0"))) + .kernel_config(Some(&String::from("path=bzImage"))) + .block_config(Some(&String::from("path=/dev/loop0"))) .build(); assert!(vmm_config.is_ok()); assert_eq!( @@ -338,7 +365,9 @@ mod tests { }), block_config: Some(BlockConfig { path: PathBuf::from("/dev/loop0") - }) + }), + dump_dtb: None, + dtb: None, } ); } diff --git a/src/vmm/src/config/mod.rs b/src/vmm/src/config/mod.rs index 275fca06..c4248f95 100644 --- a/src/vmm/src/config/mod.rs +++ b/src/vmm/src/config/mod.rs @@ -85,10 +85,10 @@ impl Default for MemoryConfig { } } -impl TryFrom<&str> for MemoryConfig { +impl TryFrom<&String> for MemoryConfig { type Error = ConversionError; - fn try_from(mem_cfg_str: &str) -> result::Result { + fn try_from(mem_cfg_str: &String) -> result::Result { // Supported options: `size=` let mut arg_parser = CfgArgParser::new(mem_cfg_str); @@ -116,10 +116,10 @@ impl Default for VcpuConfig { } } -impl TryFrom<&str> for VcpuConfig { +impl TryFrom<&String> for VcpuConfig { type Error = ConversionError; - fn try_from(vcpu_cfg_str: &str) -> result::Result { + fn try_from(vcpu_cfg_str: &String) -> result::Result { // Supported options: `num=` let mut arg_parser = CfgArgParser::new(vcpu_cfg_str); let num = arg_parser @@ -147,7 +147,7 @@ pub struct KernelConfig { impl KernelConfig { /// Return the default kernel command line used by the Vmm. pub fn default_cmdline() -> Cmdline { - let mut cmdline = Cmdline::new(KERNEL_CMDLINE_CAPACITY); + let mut cmdline = Cmdline::new(KERNEL_CMDLINE_CAPACITY).unwrap(); // It's ok to use `unwrap` because the initial capacity of `cmdline` should be // sufficient to accommodate the default kernel cmdline. @@ -167,10 +167,10 @@ impl Default for KernelConfig { } } -impl TryFrom<&str> for KernelConfig { +impl TryFrom<&String> for KernelConfig { type Error = ConversionError; - fn try_from(kernel_cfg_str: &str) -> result::Result { + fn try_from(kernel_cfg_str: &String) -> result::Result { // Supported options: // `cmdline=<"string">,path=/path/to/kernel,kernel_load_addr=` // Required: path @@ -181,7 +181,7 @@ impl TryFrom<&str> for KernelConfig { .map_err(ConversionError::new_kernel)? .unwrap_or_else(|| DEFAULT_KERNEL_CMDLINE.to_string()); - let mut cmdline = Cmdline::new(KERNEL_CMDLINE_CAPACITY); + let mut cmdline = Cmdline::new(KERNEL_CMDLINE_CAPACITY).unwrap(); cmdline .insert_str(cmdline_str) .map_err(|_| ConversionError::new_kernel("Kernel cmdline capacity error"))?; @@ -213,10 +213,10 @@ pub struct NetConfig { pub tap_name: String, } -impl TryFrom<&str> for NetConfig { +impl TryFrom<&String> for NetConfig { type Error = ConversionError; - fn try_from(net_config_str: &str) -> Result { + fn try_from(net_config_str: &String) -> Result { // Supported options: `tap=String` let mut arg_parser = CfgArgParser::new(net_config_str); @@ -239,10 +239,10 @@ pub struct BlockConfig { pub path: PathBuf, } -impl TryFrom<&str> for BlockConfig { +impl TryFrom<&String> for BlockConfig { type Error = ConversionError; - fn try_from(block_cfg_str: &str) -> Result { + fn try_from(block_cfg_str: &String) -> Result { // Supported options: `path=PathBuf` let mut arg_parser = CfgArgParser::new(block_cfg_str); @@ -258,6 +258,58 @@ impl TryFrom<&str> for BlockConfig { } } +/// Dump DTB configuration +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DumpDTBConfig { + /// Path where to save dtb used. + pub path: PathBuf, +} + +impl TryFrom<&String> for DumpDTBConfig { + type Error = ConversionError; + + fn try_from(dump_dtb_cfg_str: &String) -> Result { + // Supported options: `path=PathBuf` + let mut arg_parser = CfgArgParser::new(dump_dtb_cfg_str); + + let path = arg_parser + .value_of("path") + .map_err(ConversionError::new_block)? + .ok_or_else(|| ConversionError::new_block("Missing required argument: path"))?; + + arg_parser + .all_consumed() + .map_err(ConversionError::new_block)?; + Ok(DumpDTBConfig { path }) + } +} + +/// DTB configuration +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DTBConfig { + /// Path to the FDT. + pub path: PathBuf, +} + +impl TryFrom<&String> for DTBConfig { + type Error = ConversionError; + + fn try_from(dtb_cfg_str: &String) -> Result { + // Supported options: `path=PathBuf` + let mut arg_parser = CfgArgParser::new(dtb_cfg_str); + + let path = arg_parser + .value_of("path") + .map_err(ConversionError::new_block)? + .ok_or_else(|| ConversionError::new_block("Missing required argument: path"))?; + + arg_parser + .all_consumed() + .map_err(ConversionError::new_block)?; + Ok(DTBConfig { path }) + } +} + /// VMM configuration. #[derive(Clone, Debug, Default, PartialEq)] pub struct VMMConfig { @@ -271,6 +323,10 @@ pub struct VMMConfig { pub net_config: Option, /// Block device configuration. pub block_config: Option, + /// Dump DTB configuration + pub dump_dtb: Option, + /// DTB configuration + pub dtb: Option, } #[cfg(test)] @@ -280,9 +336,8 @@ mod tests { #[test] fn test_kernel_config() { // Check that additional commas in the kernel string do not cause a panic. - let kernel_str = r#"path=/foo/bar,cmdline="foo=bar",kernel_load_addr=42,"#; - - let mut foo_cmdline = Cmdline::new(128); + let kernel_str = String::from(r#"path=/foo/bar,cmdline="foo=bar",kernel_load_addr=42,"#); + let mut foo_cmdline = Cmdline::new(128).unwrap(); foo_cmdline.insert_str("\"foo=bar\"").unwrap(); let expected_kernel_config = KernelConfig { @@ -290,131 +345,115 @@ mod tests { load_addr: 42, path: PathBuf::from("/foo/bar"), }; - assert_eq!( - KernelConfig::try_from(kernel_str).unwrap(), - expected_kernel_config - ); + assert_eq!(&KernelConfig::try_from(&kernel_str).unwrap(), &expected_kernel_config); // Check that an empty path returns a conversion error. - let kernel_str = r#"path=,cmdline="foo=bar",kernel_load_addr=42,"#; + let kernel_str = String::from(r#"path=,cmdline="foo=bar",kernel_load_addr=42,"#); assert_eq!( - KernelConfig::try_from(kernel_str).unwrap_err(), - ConversionError::ParseKernel("Missing required argument: path".to_string()) + &KernelConfig::try_from(&kernel_str).unwrap_err(), + &ConversionError::ParseKernel("Missing required argument: path".to_string()) ); - assert!(KernelConfig::try_from("path=/something,not=valid").is_err()); - assert!(KernelConfig::try_from("path=/something,kernel_load_addr=invalid").is_err()); + assert!(KernelConfig::try_from(&String::from("path=/something,not=valid")).is_err()); + assert!(KernelConfig::try_from(&String::from("path=/something,kernel_load_addr=invalid")).is_err()); } #[test] fn test_vcpu_config() { // Invalid vCPU numbers: 0, 256 (exceeds the u8 limit). - let vcpu_str = "num=0"; + let vcpu_str = String::from("num=0"); assert_eq!( - VcpuConfig::try_from(vcpu_str).unwrap_err(), - ConversionError::ParseVcpus( - "Param \'num\', parsing failed: number would be zero for non-zero type".to_string() + &VcpuConfig::try_from(&vcpu_str).unwrap_err(), + &ConversionError::ParseVcpus( + "Param 'num', parsing failed: number would be zero for non-zero type".to_string() ) ); - let vcpu_str = "num=256"; + let vcpu_str = String::from("num=256"); assert_eq!( - VcpuConfig::try_from(vcpu_str).unwrap_err(), - ConversionError::ParseVcpus( + &VcpuConfig::try_from(&vcpu_str).unwrap_err(), + &ConversionError::ParseVcpus( "Param 'num', parsing failed: number too large to fit in target type".to_string() ) ); // Missing vCPU number in config string, use default - let vcpu_str = "num="; - assert!(VcpuConfig::try_from(vcpu_str).is_ok()); + assert!(VcpuConfig::try_from(&String::from("num=")).is_ok()); // vCPU number parsing error - let vcpu_str = "num=abc"; - assert!(VcpuConfig::try_from(vcpu_str).is_err()); + assert!(VcpuConfig::try_from(&String::from("num=abc")).is_err()); // Extra argument - let vcpu_str = "num=1,foo=bar"; - assert!(VcpuConfig::try_from(vcpu_str).is_err()); + assert!(VcpuConfig::try_from(&String::from("num=1,foo=bar")).is_err()); } #[test] fn test_net_config() { - let net_str = "tap=vmtap"; - let net_cfg = NetConfig::try_from(net_str).unwrap(); + let net_str = String::from("tap=vmtap"); + let net_cfg = NetConfig::try_from(&net_str).unwrap(); let expected_cfg = NetConfig { tap_name: "vmtap".to_string(), }; - assert_eq!(net_cfg, expected_cfg); + assert_eq!(&net_cfg, &expected_cfg); - // Test case: empty string error. - assert!(NetConfig::try_from("").is_err()); + // Test case: empty string error + assert!(NetConfig::try_from(&String::from("")).is_err()); - // Test case: empty tap name error. - let net_str = "tap="; - assert!(NetConfig::try_from(net_str).is_err()); + // Test case: empty tap name error + assert!(NetConfig::try_from(&String::from("tap=")).is_err()); // Test case: invalid string. - let net_str = "blah=blah"; - assert!(NetConfig::try_from(net_str).is_err()); + assert!(NetConfig::try_from(&String::from("blah=blah")).is_err()); // Test case: unused parameters - let net_str = "tap=something,blah=blah"; - assert!(NetConfig::try_from(net_str).is_err()); + assert!(NetConfig::try_from(&String::from("tap=something,blah=blah")).is_err()); } #[test] fn test_block_config() { - let block_str = "path=/foo/bar"; - let block_cfg = BlockConfig::try_from(block_str).unwrap(); + let block_str = String::from("path=/foo/bar"); + let block_cfg = BlockConfig::try_from(&block_str).unwrap(); let expected_cfg = BlockConfig { path: PathBuf::from("/foo/bar"), }; - assert_eq!(block_cfg, expected_cfg); + assert_eq!(&block_cfg, &expected_cfg); - // Test case: empty string error. - assert!(BlockConfig::try_from("").is_err()); + // Test case: empty string error + assert!(BlockConfig::try_from(&String::from("")).is_err()); - // Test case: empty tap name error. - let block_str = "path="; - assert!(BlockConfig::try_from(block_str).is_err()); + // Test case: empty tap name error + assert!(BlockConfig::try_from(&String::from("path=")).is_err()); // Test case: invalid string. - let block_str = "blah=blah"; - assert!(BlockConfig::try_from(block_str).is_err()); + assert!(BlockConfig::try_from(&String::from("blah=blah")).is_err()); // Test case: unused parameters - let block_str = "path=/foo/bar,blah=blah"; - assert!(BlockConfig::try_from(block_str).is_err()); + assert!(BlockConfig::try_from(&String::from("path=/foo/bar,blah=blah")).is_err()); } #[test] fn test_memory_config() { let default = MemoryConfig { size_mib: 256 }; - let size_str = "size_mib=42"; - let memory_cfg = MemoryConfig::try_from(size_str).unwrap(); + let size_str = String::from("size_mib=42"); + let memory_cfg = MemoryConfig::try_from(&size_str).unwrap(); let expected_cfg = MemoryConfig { size_mib: 42 }; - assert_eq!(memory_cfg, expected_cfg); + assert_eq!(&memory_cfg, &expected_cfg); // Test case: empty string should use default - assert_eq!(MemoryConfig::try_from("").unwrap(), default); + assert_eq!(&MemoryConfig::try_from(&String::from("" )).unwrap(), &default); // Test case: empty size_mib, use default - let memory_str = "size_mib="; - assert!(MemoryConfig::try_from(memory_str).is_ok()); + assert!(MemoryConfig::try_from(&String::from("size_mib=")).is_ok()); // Test case: size_mib invalid input - let memory_str = "size_mib=ciao"; - assert!(MemoryConfig::try_from(memory_str).is_err()); + assert!(MemoryConfig::try_from(&String::from("size_mib=ciao")).is_err()); // Test case: invalid string. - let memory_str = "blah=blah"; assert_eq!( - MemoryConfig::try_from(memory_str).unwrap_err(), - ConversionError::ParseMemory("Unknown arguments found: \'blah\'".to_string()) + &MemoryConfig::try_from(&String::from("blah=blah")).unwrap_err(), + &ConversionError::ParseMemory("Unknown arguments found: 'blah'".to_string()) ); // Test case: unused parameters - let memory_str = "size_mib=12,blah=blah"; - assert!(MemoryConfig::try_from(memory_str).is_err()); + assert!(MemoryConfig::try_from(&String::from("size_mib=12,blah=blah")).is_err()); } } diff --git a/src/vmm/src/lib.rs b/src/vmm/src/lib.rs index 92c2cfe6..d4cd376e 100644 --- a/src/vmm/src/lib.rs +++ b/src/vmm/src/lib.rs @@ -4,7 +4,7 @@ #![deny(missing_docs)] use std::convert::TryFrom; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use std::convert::TryInto; use std::fs::File; use std::io::{self, stdin, stdout}; @@ -39,16 +39,16 @@ use vm_device::bus::{MmioAddress, MmioRange}; #[cfg(target_arch = "x86_64")] use vm_device::bus::{PioAddress, PioRange}; use vm_device::device_manager::IoManager; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use vm_device::device_manager::MmioManager; #[cfg(target_arch = "x86_64")] use vm_device::device_manager::PioManager; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use vm_memory::GuestMemoryRegion; use vm_memory::{GuestAddress, GuestMemory, GuestMemoryMmap}; #[cfg(target_arch = "x86_64")] use vm_superio::I8042Device; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use vm_superio::Rtc; use vm_superio::Serial; use vmm_sys_util::{epoll::EventSet, eventfd::EventFd, terminal::Terminal}; @@ -60,16 +60,22 @@ use devices::virtio::block::{self, BlockArgs}; use devices::virtio::net::{self, NetArgs}; use devices::virtio::{Env, MmioConfig}; +use devices::intc::APlicTrigger; #[cfg(target_arch = "x86_64")] use devices::legacy::I8042Wrapper; use devices::legacy::{EventFdTrigger, SerialWrapper}; use vm_vcpu::vm::{self, ExitHandler, KvmVm, VmConfig}; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +use arch::fdt::FdtBuilder; +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use devices::legacy::RtcWrapper; #[cfg(target_arch = "aarch64")] -use arch::{FdtBuilder, AARCH64_FDT_MAX_SIZE, AARCH64_MMIO_BASE, AARCH64_PHYS_MEM_START}; +use arch::aarch64_consts::{AARCH64_FDT_MAX_SIZE, AARCH64_MMIO_BASE, AARCH64_PHYS_MEM_START}; + +#[cfg(target_arch = "riscv64")] +use arch::riscv64_consts::{RISCV64_FDT_MAX_SIZE, RISCV64_MMIO_BASE, RISCV64_PHYS_MEM_START}; use vm_allocator::{AddressAllocator, AllocPolicy, RangeInclusive}; @@ -102,15 +108,21 @@ pub const DEFAULT_HIGH_RAM_START: u64 = 0x0010_0000; /// Default address for loading the kernel. #[cfg(target_arch = "x86_64")] pub const DEFAULT_KERNEL_LOAD_ADDR: u64 = DEFAULT_HIGH_RAM_START; -#[cfg(target_arch = "aarch64")] /// Default address for loading the kernel. +#[cfg(target_arch = "aarch64")] pub const DEFAULT_KERNEL_LOAD_ADDR: u64 = AARCH64_PHYS_MEM_START; +/// Default address for loading the kernel. +#[cfg(target_arch = "riscv64")] +pub const DEFAULT_KERNEL_LOAD_ADDR: u64 = RISCV64_PHYS_MEM_START; /// Default kernel command line. #[cfg(target_arch = "x86_64")] pub const DEFAULT_KERNEL_CMDLINE: &str = "panic=1 pci=off"; +/// Default kernel command line. #[cfg(target_arch = "aarch64")] +pub const DEFAULT_KERNEL_CMDLINE: &str = "reboot=t panic=1 pci=off"; /// Default kernel command line. +#[cfg(target_arch = "riscv64")] pub const DEFAULT_KERNEL_CMDLINE: &str = "reboot=t panic=1 pci=off"; /// Default address allocator alignment. It needs to be a power of 2. pub const DEFAULT_ADDRESSS_ALIGNEMNT: u64 = 4; @@ -172,9 +184,9 @@ pub enum Error { #[cfg(target_arch = "x86_64")] /// Cannot retrieve the supported MSRs. GetSupportedMsrs(vm_vcpu_ref::x86_64::msrs::Error), - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] /// Cannot setup the FDT for booting. - SetupFdt(arch::Error), + SetupFdt(arch::fdt::Error), /// IrqAllocator error IrqAllocator(irq_allocator::Error), } @@ -207,7 +219,7 @@ type Net = net::Net>; pub struct Vmm { vm: KvmVm, kernel_cfg: KernelConfig, - guest_memory: GuestMemoryMmap, + guest_memory: Arc, address_allocator: AddressAllocator, irq_allocator: IrqAllocator, // The `device_mgr` is an Arc so that it can be shared between @@ -223,10 +235,12 @@ pub struct Vmm { // TODO: fetch the vcpu number from the `vm` object. // TODO-continued: this is needed to make the arm POC work as we need to create the FDT // TODO-continued: after the other resources are created. - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] num_vcpus: u64, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fdt_builder: FdtBuilder, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + config: VMMConfig, } // The `VmmExitHandler` is used as the mechanism for exiting from the event manager loop. @@ -285,6 +299,8 @@ impl TryFrom for Vmm { type Error = Error; fn try_from(config: VMMConfig) -> Result { + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + let config_copy = config.clone(); let kvm = Kvm::new().map_err(Error::KvmIoctl)?; // Check that the KVM on the host is supported. @@ -312,14 +328,21 @@ impl TryFrom for Vmm { let mut event_manager = EventManager::>>::new() .map_err(Error::EventManager)?; event_manager.add_subscriber(wrapped_exit_handler.0.clone()); - #[cfg(target_arch = "aarch64")] - let fdt_builder = FdtBuilder::new(); + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + let mut fdt_builder = FdtBuilder::new(); + + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + if let Some(prebuilt_dtb) = &config.dtb { + fdt_builder + .with_prebuilt_fdt(prebuilt_dtb.path.clone()) + .map_err(Error::SetupFdt)?; + } let irq_allocator = IrqAllocator::new(SERIAL_IRQ, vm.max_irq())?; let mut vmm = Vmm { vm, - guest_memory, + guest_memory: Arc::new(guest_memory), address_allocator, irq_allocator, device_mgr, @@ -328,15 +351,17 @@ impl TryFrom for Vmm { exit_handler: wrapped_exit_handler, block_devices: Vec::new(), net_devices: Vec::new(), - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] num_vcpus: config.vcpu_config.num as u64, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fdt_builder, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + config: config_copy, }; vmm.add_serial_console()?; #[cfg(target_arch = "x86_64")] vmm.add_i8042_device()?; - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] vmm.add_rtc_device()?; // Adding the virtio devices. We'll come up with a cleaner abstraction for `Env`. @@ -358,9 +383,13 @@ impl Vmm { let load_result = self.load_kernel()?; #[cfg(target_arch = "x86_64")] let kernel_load_addr = self.compute_kernel_load_addr(&load_result)?; - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + /* + The RISC-V kernel expects to be placed at a PMD boundary (2MB aligned for rv64 + and 4MB aligned for rv32). + */ let kernel_load_addr = load_result.kernel_load; - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] self.setup_fdt()?; if stdin().lock().set_raw_mode().is_err() { @@ -407,7 +436,9 @@ impl Vmm { } #[cfg(target_arch = "aarch64")] - vec![(GuestAddress(AARCH64_PHYS_MEM_START), mem_size)] + return vec![(GuestAddress(AARCH64_PHYS_MEM_START), mem_size)]; + #[cfg(target_arch = "riscv64")] + return vec![(GuestAddress(RISCV64_PHYS_MEM_START), mem_size)]; } fn create_address_allocator(memory_config: &MemoryConfig) -> Result { @@ -416,6 +447,8 @@ impl Vmm { let start_addr = MMIO_GAP_START; #[cfg(target_arch = "aarch64")] let start_addr = AARCH64_MMIO_BASE; + #[cfg(target_arch = "riscv64")] + let start_addr = RISCV64_MMIO_BASE; let address_allocator = AddressAllocator::new(start_addr, mem_size)?; Ok(address_allocator) } @@ -428,14 +461,14 @@ impl Vmm { // Load the kernel into guest memory. let kernel_load = match Elf::load( - &self.guest_memory, + self.guest_memory.as_ref(), None, &mut kernel_image, Some(GuestAddress(self.kernel_cfg.load_addr)), ) { Ok(result) => result, Err(loader::Error::Elf(elf::Error::InvalidElfMagicNumber)) => BzImage::load( - &self.guest_memory, + self.guest_memory.as_ref(), None, &mut kernel_image, Some(GuestAddress(self.kernel_cfg.load_addr)), @@ -458,16 +491,32 @@ impl Vmm { // Add the kernel command line to the boot parameters. bootparams.hdr.cmd_line_ptr = CMDLINE_START as u32; - bootparams.hdr.cmdline_size = self.kernel_cfg.cmdline.as_str().len() as u32 + 1; + bootparams.hdr.cmdline_size = String::from( + self.kernel_cfg + .cmdline + .as_cstring() + .unwrap() + .to_str() + .unwrap(), + ) + .len() as u32 + + 1; // Load the kernel command line into guest memory. - let mut cmdline = Cmdline::new(4096); + let mut cmdline = Cmdline::new(4096).unwrap(); cmdline - .insert_str(self.kernel_cfg.cmdline.as_str()) + .insert_str(String::from( + self.kernel_cfg + .cmdline + .as_cstring() + .unwrap() + .to_str() + .unwrap(), + )) .map_err(Error::Cmdline)?; load_cmdline( - &self.guest_memory, + self.guest_memory.as_ref(), GuestAddress(CMDLINE_START), // Safe because we know the command line string doesn't contain any 0 bytes. &cmdline, @@ -484,11 +533,11 @@ impl Vmm { Ok(kernel_load) } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn load_kernel(&mut self) -> Result { let mut kernel_image = File::open(&self.kernel_cfg.path).map_err(Error::IO)?; linux_loader::loader::pe::PE::load( - &self.guest_memory, + self.guest_memory.as_ref(), Some(GuestAddress(self.kernel_cfg.load_addr)), &mut kernel_image, None, @@ -500,10 +549,21 @@ impl Vmm { fn add_serial_console(&mut self) -> Result<()> { // Create the serial console. let interrupt_evt = EventFdTrigger::new(libc::EFD_NONBLOCK).map_err(Error::IO)?; + #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] let serial = Arc::new(Mutex::new(SerialWrapper(Serial::new( interrupt_evt.try_clone().map_err(Error::IO)?, stdout(), )))); + #[cfg(target_arch = "riscv64")] + let serial = Arc::new(Mutex::new(SerialWrapper(Serial::new( + APlicTrigger { + gsi: SERIAL_IRQ, + vm: self.vm.vm_fd(), + } + .try_clone() + .map_err(Error::IO)?, + stdout(), + )))); // Register its interrupt fd with KVM. self.vm.register_irqfd(&interrupt_evt, SERIAL_IRQ)?; @@ -516,7 +576,13 @@ impl Vmm { #[cfg(target_arch = "aarch64")] self.kernel_cfg .cmdline - .insert_str(&format!("earlycon=uart,mmio,0x{:08x}", AARCH64_MMIO_BASE)) + .insert_str(format!("earlycon=uart,mmio,0x{:08x}", AARCH64_MMIO_BASE)) + .map_err(Error::Cmdline)?; + + #[cfg(target_arch = "riscv64")] + self.kernel_cfg + .cmdline + .insert_str(format!("earlycon=uart,mmio,0x{:08x}", RISCV64_MMIO_BASE)) .map_err(Error::Cmdline)?; // Put it on the bus. @@ -532,13 +598,20 @@ impl Vmm { .unwrap(); } + #[cfg(target_arch = "riscv64")] + let range = self.address_allocator.allocate( + 0x1000, + DEFAULT_ADDRESSS_ALIGNEMNT, + AllocPolicy::ExactMatch(RISCV64_MMIO_BASE), + )?; #[cfg(target_arch = "aarch64")] + let range = self.address_allocator.allocate( + 0x1000, + DEFAULT_ADDRESSS_ALIGNEMNT, + AllocPolicy::ExactMatch(AARCH64_MMIO_BASE), + )?; + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] { - let range = self.address_allocator.allocate( - 0x1000, - DEFAULT_ADDRESSS_ALIGNEMNT, - AllocPolicy::ExactMatch(AARCH64_MMIO_BASE), - )?; self.fdt_builder .with_serial_console(range.start(), range.len()); let range = mmio_from_range(&range); @@ -573,7 +646,7 @@ impl Vmm { Ok(()) } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn add_rtc_device(&mut self) -> Result<()> { let rtc = Arc::new(Mutex::new(RtcWrapper(Rtc::new()))); let range = self.address_allocator.allocate( @@ -596,7 +669,6 @@ impl Vmm { // can do it after figuring out how to better separate concerns and make the VMM agnostic of // the actual device types. fn add_block_device(&mut self, cfg: &BlockConfig) -> Result<()> { - let mem = Arc::new(self.guest_memory.clone()); let range = self.address_allocator.allocate( 0x1000, DEFAULT_ADDRESSS_ALIGNEMNT, @@ -612,7 +684,7 @@ impl Vmm { let mut guard = self.device_mgr.lock().unwrap(); let mut env = Env { - mem, + mem: self.guest_memory.clone(), vm_fd: self.vm.vm_fd(), event_mgr: &mut self.event_mgr, mmio_mgr: guard.deref_mut(), @@ -627,9 +699,19 @@ impl Vmm { advertise_flush: true, }; + let aplic_trigger = if cfg!(target_arch = "riscv64") { + Some(APlicTrigger { + gsi: irq, + vm: self.vm.vm_fd(), + }) + } else { + None + }; + let block = Block::new(self.guest_memory.clone(), &mut env, &args, aplic_trigger) + .map_err(Error::Block)?; + // We can also hold this somewhere if we need to keep the handle for later. - let block = Block::new(&mut env, &args).map_err(Error::Block)?; - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] self.fdt_builder .add_virtio_device(range.start(), range.len(), irq); self.block_devices.push(block); @@ -638,7 +720,6 @@ impl Vmm { } fn add_net_device(&mut self, cfg: &NetConfig) -> Result<()> { - let mem = Arc::new(self.guest_memory.clone()); let range = self.address_allocator.allocate( 0x1000, DEFAULT_ADDRESSS_ALIGNEMNT, @@ -654,7 +735,7 @@ impl Vmm { let mut guard = self.device_mgr.lock().unwrap(); let mut env = Env { - mem, + mem: self.guest_memory.clone(), vm_fd: self.vm.vm_fd(), event_mgr: &mut self.event_mgr, mmio_mgr: guard.deref_mut(), @@ -666,12 +747,22 @@ impl Vmm { tap_name: cfg.tap_name.clone(), }; + let aplic_trigger = if cfg!(target_arch = "riscv64") { + Some(APlicTrigger { + gsi: irq, + vm: self.vm.vm_fd(), + }) + } else { + None + }; + let net = Net::new(self.guest_memory.clone(), &mut env, &args, aplic_trigger) + .map_err(Error::Net)?; + // We can also hold this somewhere if we need to keep the handle for later. - let net = Net::new(&mut env, &args).map_err(Error::Net)?; - self.net_devices.push(net); - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] self.fdt_builder .add_virtio_device(range.start(), range.len(), irq); + self.net_devices.push(net); Ok(()) } @@ -704,7 +795,7 @@ impl Vmm { } fn check_kvm_capabilities(kvm: &Kvm) -> Result<()> { - let capabilities = vec![Irqchip, Ioeventfd, Irqfd, UserMemory]; + let capabilities = [Irqchip, Ioeventfd, Irqfd, UserMemory]; // Check that all desired capabilities are supported. if let Some(c) = capabilities @@ -717,21 +808,48 @@ impl Vmm { } } - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] // TODO: move this where it makes sense from a config point of view as we add all // needed stuff in FDT. fn setup_fdt(&mut self) -> Result<()> { let mem_size: u64 = self.guest_memory.iter().map(|region| region.len()).sum(); + #[cfg(target_arch = "aarch64")] let fdt_offset = mem_size - AARCH64_FDT_MAX_SIZE - 0x10000; - let cmdline = &self.kernel_cfg.cmdline; - let fdt = self - .fdt_builder - .with_cmdline(cmdline.as_str().to_string()) - .with_num_vcpus(self.num_vcpus.try_into().unwrap()) - .with_mem_size(mem_size) - .create_fdt() - .map_err(Error::SetupFdt)?; - fdt.write_to_mem(&self.guest_memory, fdt_offset) + #[cfg(target_arch = "riscv64")] + let fdt_offset = mem_size - RISCV64_FDT_MAX_SIZE - 0x10000; + + let fdt = if !self.fdt_builder.has_prebuilt_fdt() { + let cmdline = &self.kernel_cfg.cmdline; + #[cfg(target_arch = "aarch64")] + let fdt = self + .fdt_builder + .with_cmdline(String::from( + cmdline.as_cstring().unwrap().to_str().unwrap(), + )) + .with_num_vcpus(self.num_vcpus.try_into().unwrap()) + .with_mem_size(mem_size) + .create_fdt() + .map_err(Error::SetupFdt)?; + + #[cfg(target_arch = "riscv64")] + let fdt = self + .fdt_builder + .with_cmdline(String::from( + cmdline.as_cstring().unwrap().to_str().unwrap(), + )) + .with_num_vcpus(self.num_vcpus.try_into().unwrap()) + .with_mem_size(mem_size) + .create_fdt(self.vm.get_vcpus().first().unwrap().get_vcpu_fd()) + .map_err(Error::SetupFdt)?; + if let Some(dump_dtb_path) = &self.config.dump_dtb { + fdt.write_to_file(dump_dtb_path.path.to_str().unwrap()) + .expect("Failed to dump dtb"); + } + fdt + } else { + self.fdt_builder.get_prebuilt_fdt().unwrap() + }; + fdt.write_to_mem(self.guest_memory.as_ref(), fdt_offset) .map_err(Error::SetupFdt)?; Ok(()) } @@ -748,15 +866,13 @@ mod tests { use linux_loader::elf::Elf64_Ehdr; #[cfg(target_arch = "x86_64")] use linux_loader::loader::{self, bootparam::setup_header, elf::PvhBootCapability}; + use std::fs::write; use std::io::ErrorKind; #[cfg(target_arch = "x86_64")] use std::path::Path; use std::path::PathBuf; #[cfg(target_arch = "x86_64")] - use vm_memory::{ - bytes::{ByteValued, Bytes}, - Address, GuestAddress, GuestMemory, - }; + use vm_memory::{bytes::ByteValued, Address, GuestAddress, GuestMemory}; use vmm_sys_util::{tempdir::TempDir, tempfile::TempFile}; @@ -801,7 +917,7 @@ mod tests { fn default_vmm_config() -> VMMConfig { VMMConfig { kernel_config: KernelConfig { - #[cfg(target_arch = "x86_64")] + #[cfg(any(target_arch = "x86_64", target_arch = "riscv64"))] path: default_elf_path(), #[cfg(target_arch = "aarch64")] path: default_pe_path(), @@ -814,6 +930,8 @@ mod tests { vcpu_config: VcpuConfig { num: NUM_VCPUS }, block_config: None, net_config: None, + dump_dtb: None, + dtb: None, } } @@ -844,12 +962,12 @@ mod tests { device_mgr.clone(), ) .unwrap(); - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] let fdt_builder = FdtBuilder::new(); let irq_allocator = IrqAllocator::new(SERIAL_IRQ, vm.max_irq()).unwrap(); Vmm { vm, - guest_memory, + guest_memory: Arc::new(guest_memory), address_allocator, irq_allocator, device_mgr, @@ -858,21 +976,25 @@ mod tests { exit_handler, block_devices: Vec::new(), net_devices: Vec::new(), - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] num_vcpus: vmm_config.vcpu_config.num as u64, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fdt_builder, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + config: VMMConfig::default(), } } // Return the address where an ELF file should be loaded, as specified in its header. #[cfg(target_arch = "x86_64")] fn elf_load_addr(elf_path: &Path) -> GuestAddress { + use vm_memory::ReadVolatile; + let mut elf_file = File::open(elf_path).unwrap(); let mut ehdr = Elf64_Ehdr::default(); - ehdr.as_bytes() - .read_from(0, &mut elf_file, std::mem::size_of::()) - .unwrap(); + + elf_file.read_volatile(&mut ehdr.as_bytes()).unwrap(); + GuestAddress(ehdr.e_entry) } @@ -945,7 +1067,6 @@ mod tests { ); assert!(kernel_load_result.setup_header.is_some()); } - #[test] fn test_load_kernel_errors() { // Test case: kernel file does not exist. @@ -956,13 +1077,30 @@ mod tests { matches!(vmm.load_kernel().unwrap_err(), Error::IO(e) if e.kind() == ErrorKind::NotFound) ); - // Test case: kernel image is invalid. + // Test case: kernel image is invalid. This test has two flavors. In the first + // we try to load an empty file, in the second a file which has all zeros. let mut vmm_config = default_vmm_config(); let temp_file = TempFile::new().unwrap(); vmm_config.kernel_config.path = PathBuf::from(temp_file.as_path()); let mut vmm = mock_vmm(vmm_config); let err = vmm.load_kernel().unwrap_err(); + #[cfg(target_arch = "x86_64")] + assert!(matches!( + err, + Error::KernelLoad(loader::Error::Elf(loader::elf::Error::ReadElfHeader)) + )); + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + assert!(matches!( + err, + Error::KernelLoad(loader::Error::Pe(loader::pe::Error::ReadImageHeader)) + )); + + let temp_file_path = PathBuf::from(temp_file.as_path()); + let buffer: Vec = vec![0_u8; 1024]; + write(temp_file_path, buffer).unwrap(); + let err = vmm.load_kernel().unwrap_err(); + #[cfg(target_arch = "x86_64")] assert!(matches!( err, @@ -970,7 +1108,8 @@ mod tests { loader::bzimage::Error::InvalidBzImage )) )); - #[cfg(target_arch = "aarch64")] + + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] assert!(matches!( err, Error::KernelLoad(loader::Error::Pe( @@ -990,39 +1129,56 @@ mod tests { err, Error::KernelLoad(loader::Error::Elf(loader::elf::Error::ReadElfHeader)) )); - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] assert!(matches!( err, Error::KernelLoad(loader::Error::Pe(loader::pe::Error::ReadImageHeader)) )); } - #[test] - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn test_load_kernel() { // Test case: Loading the default & valid image is ok. let vmm_config = default_vmm_config(); let mut vmm = mock_vmm(vmm_config); assert!(vmm.load_kernel().is_ok()); } - #[test] fn test_cmdline_updates() { let mut vmm_config = default_vmm_config(); vmm_config.kernel_config.path = default_elf_path(); let mut vmm = mock_vmm(vmm_config); - assert_eq!(vmm.kernel_cfg.cmdline.as_str(), DEFAULT_KERNEL_CMDLINE); + assert_eq!( + vmm.kernel_cfg + .cmdline + .as_cstring() + .unwrap() + .to_str() + .unwrap(), + DEFAULT_KERNEL_CMDLINE + ); vmm.add_serial_console().unwrap(); #[cfg(target_arch = "x86_64")] - assert!(vmm.kernel_cfg.cmdline.as_str().contains("console=ttyS0")); - #[cfg(target_arch = "aarch64")] - assert!(vmm - .kernel_cfg - .cmdline - .as_str() - .contains("earlycon=uart,mmio")); + assert!(String::from( + vmm.kernel_cfg + .cmdline + .as_cstring() + .unwrap() + .to_str() + .unwrap() + ) + .contains("console=ttyS0")); + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + assert!(String::from( + vmm.kernel_cfg + .cmdline + .as_cstring() + .unwrap() + .to_str() + .unwrap(), + ) + .contains("earlycon=uart,mmio")); } - #[test] #[cfg(target_arch = "x86_64")] fn test_create_guest_memory() { @@ -1100,7 +1256,6 @@ mod tests { vmm_config.vcpu_config = VcpuConfig { num: 254 }; Vmm::try_from(vmm_config).unwrap(); } - #[test] // FIXME: We cannot run this on aarch64 because we do not have an image that just runs and // FIXME-continued: halts afterwards. Once we have this, we need to update `default_vmm_config` @@ -1110,6 +1265,7 @@ mod tests { let mut vmm = mock_vmm(vmm_config); let tempfile = TempFile::new().unwrap(); + let block_config = BlockConfig { path: tempfile.as_path().to_path_buf(), }; @@ -1118,7 +1274,14 @@ mod tests { assert_eq!(vmm.block_devices.len(), 1); #[cfg(target_arch = "aarch64")] assert_eq!(vmm.fdt_builder.virtio_device_len(), 1); - assert!(vmm.kernel_cfg.cmdline.as_str().contains("virtio")); + assert!(vmm + .kernel_cfg + .cmdline + .as_cstring() + .expect("Could not open cmdline") + .to_str() + .expect("CString is not valid UTF-8") + .contains("virtio")); let invalid_block_config = BlockConfig { // Let's create the tempfile directly here so that it gets out of scope immediately @@ -1151,10 +1314,16 @@ mod tests { assert_eq!(vmm.net_devices.len(), 1); #[cfg(target_arch = "aarch64")] assert_eq!(vmm.fdt_builder.virtio_device_len(), 1); - assert!(vmm.kernel_cfg.cmdline.as_str().contains("virtio")); + assert!(vmm + .kernel_cfg + .cmdline + .as_cstring() + .expect("Could not open cmdline") + .to_str() + .expect("CString is not valid UTF-8") + .contains("virtio")); } } - #[test] #[cfg(target_arch = "aarch64")] fn test_setup_fdt() { @@ -1172,14 +1341,18 @@ mod tests { let cmdline = &vmm.kernel_cfg.cmdline; let fdt = vmm .fdt_builder - .with_cmdline(cmdline.as_str().to_string()) + .with_cmdline(String::from( + cmdline.as_cstring().unwrap().to_str().unwrap(), + )) .with_num_vcpus(vmm.num_vcpus.try_into().unwrap()) .with_mem_size(mem_size) .with_serial_console(0x40000000, 0x1000) .with_rtc(0x40001000, 0x1000) .create_fdt() .unwrap(); - assert!(fdt.write_to_mem(&vmm.guest_memory, fdt_offset).is_err()); + assert!(fdt + .write_to_mem(vmm.guest_memory.as_ref(), fdt_offset) + .is_err()); } } #[test] @@ -1189,6 +1362,8 @@ mod tests { }; #[cfg(target_arch = "x86_64")] let start_addr = MMIO_GAP_START; + #[cfg(target_arch = "riscv64")] + let start_addr = RISCV64_MMIO_BASE; #[cfg(target_arch = "aarch64")] let start_addr = AARCH64_MMIO_BASE; let mut address_alloc = Vmm::create_address_allocator(&memory_config).unwrap(); diff --git a/src/vmm/tests/integration_tests.rs b/src/vmm/tests/integration_tests.rs index a6bdf2db..633a31a3 100644 --- a/src/vmm/tests/integration_tests.rs +++ b/src/vmm/tests/integration_tests.rs @@ -29,6 +29,8 @@ fn run_vmm(kernel_path: PathBuf) { vcpu_config: default_vcpu_config(), block_config: None, net_config: None, + dump_dtb: None, + dtb: None, }; let mut vmm = Vmm::try_from(vmm_config).unwrap(); @@ -65,7 +67,7 @@ fn test_dummy_vmm_bzimage() { } #[test] -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] fn test_dummy_vmm_pe() { let tags = r#" {