mirror of
https://github.com/google/alioth.git
synced 2024-11-24 04:09:36 +00:00
Initial release
Signed-off-by: Changyuan Lyu <changyuanl@google.com>
This commit is contained in:
commit
7f2e3bf287
48 changed files with 7080 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
746
Cargo.lock
generated
Normal file
746
Cargo.lock
generated
Normal file
|
@ -0,0 +1,746 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alioth"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitfield",
|
||||
"bitflags",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"rand",
|
||||
"thiserror",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alioth-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"alioth",
|
||||
"anyhow",
|
||||
"clap",
|
||||
"flexi_logger",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitfield"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flexi_logger"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47a8a297f2d1285b13abf253b27f2f4480eb6703a0424b5942f9dc872831045c"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"glob",
|
||||
"is-terminal",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"nu-ansi-term",
|
||||
"regex",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.49.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.51.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"alioth",
|
||||
"alioth-cli",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
opt-level = 3
|
||||
strip = true
|
202
LICENSE
Normal file
202
LICENSE
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
34
README.md
Normal file
34
README.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Alioth
|
||||
|
||||
Alioth is a toy virtual machine monitor based on KVM. Complementary to the
|
||||
official tutorial [Using the KVM API](https://lwn.net/Articles/658511/), it demonstrates
|
||||
detailed steps for building a type-2 hypervisor and booting a Linux guest kernel.
|
||||
|
||||
## Get started
|
||||
|
||||
* Build Alioth from source,
|
||||
|
||||
```sh
|
||||
cargo +nightly build --release --target x86_64-unknown-linux-gnu
|
||||
```
|
||||
|
||||
* Make an initramfs with [u-root](https://github.com/u-root/u-root?tab=readme-ov-file#examples),
|
||||
|
||||
* Boot a Linux kernel with 2 CPUs and 4 GiB memory:
|
||||
|
||||
```sh
|
||||
cargo +nightly run --release --target x86_64-unknown-linux-gnu -- \
|
||||
-l info \
|
||||
--log-to-file \
|
||||
run \
|
||||
--kernel /path/to/vmlinuz \
|
||||
--cmd-line "console=ttyS0" \
|
||||
--initramfs /path/to/initramfs \
|
||||
--mem-size 4G \
|
||||
--num-cpu=2
|
||||
```
|
||||
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Disclaimer: Alioth is not an officially supported Google product.
|
15
alioth-cli/Cargo.toml
Normal file
15
alioth-cli/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "alioth-cli"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
flexi_logger = "0.26"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
anyhow = "1"
|
||||
alioth = { path = "../alioth" }
|
||||
|
||||
[[bin]]
|
||||
path = "src/main.rs"
|
||||
name = "alioth"
|
140
alioth-cli/src/main.rs
Normal file
140
alioth-cli/src/main.rs
Normal file
|
@ -0,0 +1,140 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use alioth::hv::Kvm;
|
||||
use alioth::vm::{BoardConfig, ExecType, Machine, Payload};
|
||||
use anyhow::Result;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use flexi_logger::{FileSpec, Logger};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about)]
|
||||
struct Cli {
|
||||
#[arg(short, long)]
|
||||
/// Loglevel specification, see
|
||||
/// https://docs.rs/flexi_logger/0.25.5/flexi_logger/struct.LogSpecification.html.
|
||||
/// If not set, environment variable $RUST_LOG is used.
|
||||
pub log_spec: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
pub log_to_file: bool,
|
||||
|
||||
#[arg(long)]
|
||||
pub log_dir: Option<PathBuf>,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub cmd: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Command {
|
||||
Run(RunArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
struct RunArgs {
|
||||
#[arg(short, long)]
|
||||
kernel: Option<PathBuf>,
|
||||
|
||||
#[arg(short, long)]
|
||||
cmd_line: Option<String>,
|
||||
|
||||
#[arg(short, long)]
|
||||
initramfs: Option<PathBuf>,
|
||||
|
||||
#[arg(long, default_value_t = 1)]
|
||||
num_cpu: u32,
|
||||
|
||||
#[arg(long, default_value = "1G")]
|
||||
mem_size: String,
|
||||
}
|
||||
|
||||
fn parse_mem(s: &str) -> Result<usize> {
|
||||
if let Some((num, "")) = s.split_once(['g', 'G']) {
|
||||
let n = num.parse::<usize>()?;
|
||||
Ok(n << 30)
|
||||
} else if let Some((num, "")) = s.split_once(['m', 'M']) {
|
||||
let n = num.parse::<usize>()?;
|
||||
Ok(n << 20)
|
||||
} else if let Some((num, "")) = s.split_once(['k', 'K']) {
|
||||
let n = num.parse::<usize>()?;
|
||||
Ok(n << 10)
|
||||
} else {
|
||||
let n = s.parse::<usize>()?;
|
||||
Ok(n)
|
||||
}
|
||||
}
|
||||
|
||||
fn main_run(args: RunArgs) -> Result<()> {
|
||||
let hypervisor = Kvm::new()?;
|
||||
let payload = if let Some(kernel) = args.kernel {
|
||||
Some(Payload {
|
||||
exec_type: ExecType::Linux,
|
||||
executable: kernel,
|
||||
initramfs: args.initramfs,
|
||||
cmd_line: args.cmd_line,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let board_config = BoardConfig {
|
||||
mem_size: parse_mem(&args.mem_size)?,
|
||||
num_cpu: args.num_cpu,
|
||||
};
|
||||
let mut vm = Machine::new(hypervisor, board_config)?;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
vm.add_com1()?;
|
||||
if let Some(payload) = payload {
|
||||
vm.add_payload(payload);
|
||||
}
|
||||
vm.boot()?;
|
||||
for result in vm.wait() {
|
||||
result?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = Cli::parse();
|
||||
let logger = if let Some(ref spec) = cli.log_spec {
|
||||
Logger::try_with_str(spec)
|
||||
} else {
|
||||
Logger::try_with_env_or_str("warn")
|
||||
}?;
|
||||
let logger = if cli.log_to_file {
|
||||
logger.log_to_file(
|
||||
FileSpec::default()
|
||||
.suppress_timestamp()
|
||||
.o_directory(cli.log_dir),
|
||||
)
|
||||
} else {
|
||||
logger
|
||||
};
|
||||
let _handle = logger.start()?;
|
||||
log::debug!(
|
||||
"{} {} started...",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
);
|
||||
let Some(cmd) = cli.cmd else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match cmd {
|
||||
Command::Run(args) => main_run(args)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
14
alioth/Cargo.toml
Normal file
14
alioth/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "alioth"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1"
|
||||
zerocopy = { version = "0.7.32", features = ["derive", "alloc"] }
|
||||
bitflags = "2.4.0"
|
||||
bitfield = "0.14.0"
|
||||
log = "0.4"
|
||||
mio = { version = "0.8.8", features = ["os-poll", "os-ext", "net"] }
|
||||
rand = "0.8.5"
|
||||
libc = "0.2.150"
|
244
alioth/src/acpi.rs
Normal file
244
alioth/src/acpi.rs
Normal file
|
@ -0,0 +1,244 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod bindings;
|
||||
|
||||
use std::mem::{size_of, size_of_val};
|
||||
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::layout::{APIC_START, IOAPIC_START};
|
||||
use crate::{align_up, unsafe_impl_zerocopy};
|
||||
|
||||
use bindings::{
|
||||
AcpiGenericAddress, AcpiMadtIoApic, AcpiMadtLocalX2apic, AcpiSubtableHeader, AcpiTableFadt,
|
||||
AcpiTableHeader, AcpiTableMadt, AcpiTableMcfg, AcpiTableRsdp, AcpiTableXsdt,
|
||||
FADT_MAJOR_VERSION, FADT_MINOR_VERSION, MADT_IO_APIC, MADT_LOCAL_X2APIC, MADT_REVISION,
|
||||
RSDP_REVISION, SIG_FADT, SIG_MADT, SIG_RSDP, SIG_XSDT, XSDT_REVISION,
|
||||
};
|
||||
|
||||
unsafe_impl_zerocopy!(AcpiTableMcfg<1>, FromBytes, FromZeroes, AsBytes);
|
||||
unsafe_impl_zerocopy!(AcpiTableXsdt<2>, FromBytes, FromZeroes, AsBytes);
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const DSDT_DSDTTBL_HEADER: [u8; 104] = [
|
||||
0x44,0x53,0x44,0x54,0x67,0x00,0x00,0x00, /* 00000000 "DSDTg..." */
|
||||
0x02,0xFB,0x41,0x4C,0x49,0x4F,0x54,0x48, /* 00000008 "..ALIOTH" */
|
||||
0x41,0x4C,0x49,0x4F,0x54,0x48,0x56,0x4D, /* 00000010 "ALIOTHVM" */
|
||||
0x01,0x00,0x00,0x00,0x49,0x4E,0x54,0x4C, /* 00000018 "....INTL" */
|
||||
0x25,0x09,0x20,0x20, /* 00000020 "%. " */
|
||||
0x5B,0x82,0x37,0x2E,0x5F,0x53,0x42,0x5F, /* 00000024 "[.7._SB_" */
|
||||
0x43,0x4F,0x4D,0x31, /* 0000002C "COM1" */
|
||||
0x08,0x5F,0x48,0x49,0x44,0x0C,0x41,0xD0, /* 00000030 "._HID.A." */
|
||||
0x05,0x01, /* 00000038 ".." */
|
||||
0x08,0x5F,0x55,0x49,0x44,0x01, /* 0000003A "._UID." */
|
||||
0x08,0x5F,0x53,0x54,0x41,0x0A,0x0F, /* 00000040 "._STA.." */
|
||||
0x08,0x5F,0x43,0x52,0x53,0x11,0x10,0x0A, /* 00000047 "._CRS..." */
|
||||
0x0D, /* 0000004F "." */
|
||||
0x47,0x01,0xF8,0x03,0xF8,0x03,0x00,0x08, /* 00000050 "G......." */
|
||||
0x22,0x10,0x00, /* 00000058 "".." */
|
||||
0x79,0x00, /* 0000005B "y." */
|
||||
0x08,0x5F,0x53,0x35,0x5F, /* 0000005D "._S5_" */
|
||||
0x12,0x04,0x01,0x0A,0x05, /* 00000062 "....." */
|
||||
0x00,
|
||||
];
|
||||
|
||||
#[inline]
|
||||
fn gencsum<'a, T>(data: T) -> u8
|
||||
where
|
||||
T: IntoIterator<Item = &'a u8>,
|
||||
{
|
||||
(!wrapping_sum(data)).wrapping_add(1)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wrapping_sum<'a, T>(data: T) -> u8
|
||||
where
|
||||
T: IntoIterator<Item = &'a u8>,
|
||||
{
|
||||
data.into_iter().fold(0u8, |accu, e| accu.wrapping_add(*e))
|
||||
}
|
||||
|
||||
fn encode_addr64(addr: usize) -> [u32; 2] {
|
||||
[addr as u32, (addr >> 32) as u32]
|
||||
}
|
||||
|
||||
const OEM_ID: [u8; 6] = *b"ALIOTH";
|
||||
|
||||
fn default_header() -> AcpiTableHeader {
|
||||
AcpiTableHeader {
|
||||
checksum: 0,
|
||||
oem_id: OEM_ID,
|
||||
oem_table_id: *b"ALIOTHVM",
|
||||
oem_revision: 1,
|
||||
asl_compiler_id: *b"ALTH",
|
||||
asl_compiler_revision: 1,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#root-system-description-pointer-rsdp-structure
|
||||
pub fn create_rsdp(xsdt_addr: usize) -> AcpiTableRsdp {
|
||||
let mut rsdp = AcpiTableRsdp {
|
||||
signature: SIG_RSDP,
|
||||
oem_id: OEM_ID,
|
||||
revision: RSDP_REVISION,
|
||||
length: size_of::<AcpiTableRsdp>() as u32,
|
||||
xsdt_physical_address: encode_addr64(xsdt_addr),
|
||||
..Default::default()
|
||||
};
|
||||
rsdp.checksum = gencsum(&rsdp.as_bytes()[0..20]);
|
||||
rsdp.extended_checksum = gencsum(rsdp.as_bytes());
|
||||
rsdp
|
||||
}
|
||||
|
||||
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#extended-system-description-table-fields-xsdt
|
||||
pub fn create_xsdt(entries: [usize; 2]) -> AcpiTableXsdt<2> {
|
||||
let total_length = size_of::<AcpiTableHeader>() + size_of::<u64>() * 2;
|
||||
let entries = entries.map(encode_addr64);
|
||||
let mut xsdt = AcpiTableXsdt {
|
||||
header: AcpiTableHeader {
|
||||
signature: SIG_XSDT,
|
||||
length: total_length as u32,
|
||||
revision: XSDT_REVISION,
|
||||
..default_header()
|
||||
},
|
||||
entries,
|
||||
};
|
||||
xsdt.header.checksum = gencsum(xsdt.as_bytes());
|
||||
xsdt
|
||||
}
|
||||
|
||||
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#fadt-format
|
||||
pub fn create_fadt(dsdt_addr: usize) -> AcpiTableFadt {
|
||||
let mut fadt = AcpiTableFadt {
|
||||
header: AcpiTableHeader {
|
||||
signature: SIG_FADT,
|
||||
revision: FADT_MAJOR_VERSION,
|
||||
length: size_of::<AcpiTableFadt>() as u32,
|
||||
..default_header()
|
||||
},
|
||||
sleep_control: AcpiGenericAddress {
|
||||
space_id: 1,
|
||||
bit_width: 8,
|
||||
bit_offset: 0,
|
||||
access_width: 1,
|
||||
address: encode_addr64(0x600),
|
||||
},
|
||||
sleep_status: AcpiGenericAddress {
|
||||
space_id: 1,
|
||||
bit_width: 8,
|
||||
bit_offset: 0,
|
||||
access_width: 1,
|
||||
address: encode_addr64(0x601),
|
||||
},
|
||||
flags: (1 << 20),
|
||||
minor_revision: FADT_MINOR_VERSION,
|
||||
hypervisor_id: *b"ALIOTH ",
|
||||
xdsdt: encode_addr64(dsdt_addr),
|
||||
..Default::default()
|
||||
};
|
||||
fadt.header.checksum = gencsum(fadt.as_bytes());
|
||||
fadt
|
||||
}
|
||||
|
||||
// https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#multiple-apic-description-table-madt
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn create_madt(num_cpu: u32) -> (AcpiTableMadt, AcpiMadtIoApic, Vec<AcpiMadtLocalX2apic>) {
|
||||
let total_length = size_of::<AcpiTableMadt>()
|
||||
+ size_of::<AcpiMadtIoApic>()
|
||||
+ num_cpu as usize * size_of::<AcpiMadtLocalX2apic>();
|
||||
|
||||
let mut madt = AcpiTableMadt {
|
||||
header: AcpiTableHeader {
|
||||
signature: SIG_MADT,
|
||||
length: total_length as u32,
|
||||
revision: MADT_REVISION,
|
||||
..default_header()
|
||||
},
|
||||
address: APIC_START as u32,
|
||||
flags: 0,
|
||||
};
|
||||
|
||||
let io_apic = AcpiMadtIoApic {
|
||||
header: AcpiSubtableHeader {
|
||||
type_: MADT_IO_APIC,
|
||||
length: size_of::<AcpiMadtIoApic>() as u8,
|
||||
},
|
||||
id: 0,
|
||||
address: IOAPIC_START as u32,
|
||||
global_irq_base: 0,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut x2apics = vec![];
|
||||
let mut sums = vec![
|
||||
wrapping_sum(madt.as_bytes()),
|
||||
wrapping_sum(io_apic.as_bytes()),
|
||||
];
|
||||
|
||||
for i in 0..num_cpu {
|
||||
let x2apic = AcpiMadtLocalX2apic {
|
||||
header: AcpiSubtableHeader {
|
||||
type_: MADT_LOCAL_X2APIC,
|
||||
length: size_of::<AcpiMadtLocalX2apic>() as u8,
|
||||
},
|
||||
local_apic_id: i,
|
||||
uid: i,
|
||||
lapic_flags: 1,
|
||||
..Default::default()
|
||||
};
|
||||
sums.push(wrapping_sum(x2apic.as_bytes()));
|
||||
x2apics.push(x2apic);
|
||||
}
|
||||
|
||||
madt.header.checksum = gencsum(&sums);
|
||||
|
||||
(madt, io_apic, x2apics)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn create_acpi_tables(start: usize, num_cpu: u32) -> Vec<u8> {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
buf.extend(AcpiTableRsdp::default().as_bytes());
|
||||
|
||||
let dsdt_addr = start + size_of::<AcpiTableRsdp>();
|
||||
buf.extend(&DSDT_DSDTTBL_HEADER);
|
||||
|
||||
let fadt_addr = align_up!(dsdt_addr + size_of_val(&DSDT_DSDTTBL_HEADER), 4);
|
||||
let fadt = create_fadt(dsdt_addr);
|
||||
buf.extend(fadt.as_bytes());
|
||||
log::trace!("fadt: {:#x?}", fadt);
|
||||
|
||||
let madt_addr = fadt_addr + size_of_val(&fadt);
|
||||
let (madt, madt_ioapic, madt_apics) = create_madt(num_cpu);
|
||||
buf.extend(madt.as_bytes());
|
||||
buf.extend(madt_ioapic.as_bytes());
|
||||
for apic in madt_apics.iter() {
|
||||
buf.extend(apic.as_bytes());
|
||||
}
|
||||
log::trace!("madt: {:#x?} {:#x?} {:#x?}", madt, madt_ioapic, madt_apics);
|
||||
|
||||
let xsdt_addr = madt_addr + madt.header.length as usize;
|
||||
let xsdt = create_xsdt([fadt_addr, madt_addr]);
|
||||
log::trace!("xsdt: {:#x?}", xsdt);
|
||||
buf.extend(xsdt.as_bytes());
|
||||
|
||||
let rsdp = create_rsdp(xsdt_addr);
|
||||
buf[0..size_of_val(&rsdp)].copy_from_slice(rsdp.as_bytes());
|
||||
|
||||
buf
|
||||
}
|
223
alioth/src/acpi/bindings.rs
Normal file
223
alioth/src/acpi/bindings.rs
Normal file
|
@ -0,0 +1,223 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
|
||||
pub const SIG_RSDP: [u8; 8] = *b"RSD PTR ";
|
||||
pub const SIG_XSDT: [u8; 4] = *b"XSDT";
|
||||
pub const SIG_FADT: [u8; 4] = *b"FACP";
|
||||
pub const SIG_MADT: [u8; 4] = *b"APIC";
|
||||
pub const SIG_MCFG: [u8; 4] = *b"MCFG";
|
||||
#[allow(dead_code)]
|
||||
pub const SIG_DSDT: [u8; 4] = *b"DSDT";
|
||||
|
||||
pub const RSDP_REVISION: u8 = 2;
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Clone, Default, AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct AcpiTableRsdp {
|
||||
pub signature: [u8; 8],
|
||||
pub checksum: u8,
|
||||
pub oem_id: [u8; 6],
|
||||
pub revision: u8,
|
||||
pub rsdt_physical_address: u32,
|
||||
pub length: u32,
|
||||
pub xsdt_physical_address: [u32; 2],
|
||||
pub extended_checksum: u8,
|
||||
pub reserved: [u8; 3],
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Clone, Default, AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct AcpiTableHeader {
|
||||
pub signature: [u8; 4],
|
||||
pub length: u32,
|
||||
pub revision: u8,
|
||||
pub checksum: u8,
|
||||
pub oem_id: [u8; 6],
|
||||
pub oem_table_id: [u8; 8],
|
||||
pub oem_revision: u32,
|
||||
pub asl_compiler_id: [u8; 4],
|
||||
pub asl_compiler_revision: u32,
|
||||
}
|
||||
|
||||
pub const XSDT_REVISION: u8 = 1;
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AcpiTableXsdt<const N: usize> {
|
||||
pub header: AcpiTableHeader,
|
||||
pub entries: [[u32; 2]; N],
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Clone, AsBytes, Default, FromBytes, FromZeroes)]
|
||||
pub struct AcpiGenericAddress {
|
||||
pub space_id: u8,
|
||||
pub bit_width: u8,
|
||||
pub bit_offset: u8,
|
||||
pub access_width: u8,
|
||||
pub address: [u32; 2],
|
||||
}
|
||||
|
||||
pub const FADT_MAJOR_VERSION: u8 = 6;
|
||||
pub const FADT_MINOR_VERSION: u8 = 4;
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Clone, Default, AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct AcpiTableFadt {
|
||||
pub header: AcpiTableHeader,
|
||||
pub facs: u32,
|
||||
pub dsdt: u32,
|
||||
pub model: u8,
|
||||
pub preferred_profile: u8,
|
||||
pub sci_interrupt: u16,
|
||||
pub smi_command: u32,
|
||||
pub acpi_enable: u8,
|
||||
pub acpi_disable: u8,
|
||||
pub s4_bios_request: u8,
|
||||
pub pstate_control: u8,
|
||||
pub pm1a_event_block: u32,
|
||||
pub pm1b_event_block: u32,
|
||||
pub pm1a_control_block: u32,
|
||||
pub pm1b_control_block: u32,
|
||||
pub pm2_control_block: u32,
|
||||
pub pm_timer_block: u32,
|
||||
pub gpe0_block: u32,
|
||||
pub gpe1_block: u32,
|
||||
pub pm1_event_length: u8,
|
||||
pub pm1_control_length: u8,
|
||||
pub pm2_control_length: u8,
|
||||
pub pm_timer_length: u8,
|
||||
pub gpe0_block_length: u8,
|
||||
pub gpe1_block_length: u8,
|
||||
pub gpe1_base: u8,
|
||||
pub cst_control: u8,
|
||||
pub c2_latency: u16,
|
||||
pub c3_latency: u16,
|
||||
pub flush_size: u16,
|
||||
pub flush_stride: u16,
|
||||
pub duty_offset: u8,
|
||||
pub duty_width: u8,
|
||||
pub day_alarm: u8,
|
||||
pub month_alarm: u8,
|
||||
pub century: u8,
|
||||
pub boot_flags: u8,
|
||||
pub boot_flags_hi: u8,
|
||||
pub reserved: u8,
|
||||
pub flags: u32,
|
||||
pub reset_register: AcpiGenericAddress,
|
||||
pub reset_value: u8,
|
||||
pub arm_boot_flags: u8,
|
||||
pub arm_boot_flags_hi: u8,
|
||||
pub minor_revision: u8,
|
||||
pub xfacs: [u32; 2],
|
||||
pub xdsdt: [u32; 2],
|
||||
pub xpm1a_event_block: AcpiGenericAddress,
|
||||
pub xpm1b_event_block: AcpiGenericAddress,
|
||||
pub xpm1a_control_block: AcpiGenericAddress,
|
||||
pub xpm1b_control_block: AcpiGenericAddress,
|
||||
pub xpm2_control_block: AcpiGenericAddress,
|
||||
pub xpm_timer_block: AcpiGenericAddress,
|
||||
pub xgpe0_block: AcpiGenericAddress,
|
||||
pub xgpe1_block: AcpiGenericAddress,
|
||||
pub sleep_control: AcpiGenericAddress,
|
||||
pub sleep_status: AcpiGenericAddress,
|
||||
pub hypervisor_id: [u8; 8],
|
||||
}
|
||||
|
||||
pub const MADT_REVISION: u8 = 6;
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Clone, Default, AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct AcpiTableMadt {
|
||||
pub header: AcpiTableHeader,
|
||||
pub address: u32,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
pub const MADT_IO_APIC: u8 = 1;
|
||||
pub const MADT_LOCAL_X2APIC: u8 = 9;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, AsBytes, Default, FromBytes, FromZeroes)]
|
||||
pub struct AcpiSubtableHeader {
|
||||
pub type_: u8,
|
||||
pub length: u8,
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Clone, AsBytes, Default, FromBytes, FromZeroes)]
|
||||
pub struct AcpiMadtLocalX2apic {
|
||||
pub header: AcpiSubtableHeader,
|
||||
pub reserved: u16,
|
||||
pub local_apic_id: u32,
|
||||
pub lapic_flags: u32,
|
||||
pub uid: u32,
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Clone, AsBytes, Default, FromBytes, FromZeroes)]
|
||||
pub struct AcpiMadtIoApic {
|
||||
pub header: AcpiSubtableHeader,
|
||||
pub id: u8,
|
||||
pub reserved: u8,
|
||||
pub address: u32,
|
||||
pub global_irq_base: u32,
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Clone, AsBytes, Default, FromBytes, FromZeroes)]
|
||||
pub struct AcpiMcfgAllocation {
|
||||
pub address: [u32; 2],
|
||||
pub pci_segment: u16,
|
||||
pub start_bus_number: u8,
|
||||
pub end_bus_number: u8,
|
||||
pub reserved: u32,
|
||||
}
|
||||
|
||||
pub const MCFG_REVISION: u8 = 1;
|
||||
|
||||
#[repr(C, align(4))]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AcpiTableMcfg<const N: usize> {
|
||||
pub header: AcpiTableHeader,
|
||||
pub reserved: [u8; 8],
|
||||
pub allocations: [AcpiMcfgAllocation; N],
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::mem::size_of;
|
||||
|
||||
use super::{
|
||||
AcpiGenericAddress, AcpiMadtIoApic, AcpiMadtLocalX2apic, AcpiMcfgAllocation, AcpiTableFadt,
|
||||
AcpiTableHeader, AcpiTableMadt, AcpiTableMcfg, AcpiTableRsdp, AcpiTableXsdt,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_eq!(size_of::<AcpiTableRsdp>(), 36);
|
||||
assert_eq!(size_of::<AcpiTableHeader>(), 36);
|
||||
assert_eq!(size_of::<AcpiGenericAddress>(), 12);
|
||||
assert_eq!(size_of::<AcpiTableFadt>(), 276);
|
||||
assert_eq!(size_of::<AcpiTableMadt>(), 44);
|
||||
assert_eq!(size_of::<AcpiMadtIoApic>(), 12);
|
||||
assert_eq!(size_of::<AcpiMadtLocalX2apic>(), 16);
|
||||
assert_eq!(size_of::<AcpiMcfgAllocation>(), 16);
|
||||
assert_eq!(size_of::<AcpiTableMcfg<1>>(), 60);
|
||||
assert_eq!(size_of::<AcpiTableXsdt<0>>(), 36);
|
||||
assert_eq!(size_of::<AcpiTableXsdt<4>>(), 36 + 4 * 8);
|
||||
}
|
||||
}
|
18
alioth/src/action.rs
Normal file
18
alioth/src/action.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Action {
|
||||
Shutdown,
|
||||
}
|
18
alioth/src/arch.rs
Normal file
18
alioth/src/arch.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod x86_64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::*;
|
18
alioth/src/arch/x86_64.rs
Normal file
18
alioth/src/arch/x86_64.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod layout;
|
||||
pub mod msr;
|
||||
pub mod paging;
|
||||
pub mod reg;
|
53
alioth/src/arch/x86_64/layout.rs
Normal file
53
alioth/src/arch/x86_64/layout.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub const REAL_MODE_IVT_START: usize = 0x0;
|
||||
|
||||
pub const BIOS_DATA_AREA_START: usize = 0x400;
|
||||
pub const BIOS_DATA_END: usize = 0x500;
|
||||
|
||||
pub const BOOT_GDT_START: usize = 0x500;
|
||||
pub const BOOT_GDT_LIMIT: usize = 0x100;
|
||||
pub const BOOT_IDT_START: usize = 0x600;
|
||||
pub const BOOT_IDT_LIMIT: usize = 0xa00;
|
||||
|
||||
pub const LINUX_BOOT_PARAMS_START: usize = 0x1000; // size: 4KiB
|
||||
pub const HVM_START_INFO_START: usize = 0x1000; // size: 4KiB
|
||||
|
||||
pub const KERNEL_CMD_LINE_START: usize = 0x2000;
|
||||
pub const KERNEL_CMD_LINE_LIMIT: usize = 0x1000;
|
||||
|
||||
pub const BOOT_PAGING_START: usize = 0x3000;
|
||||
pub const BOOT_PAGING_LIMIT: usize = 0x4000;
|
||||
|
||||
pub const EBDA_START: usize = 0x8_0000;
|
||||
pub const EBDA_END: usize = 0xA_0000;
|
||||
|
||||
pub const KERNEL_IMAGE_START: usize = 0x100_0000; // 16 MiB
|
||||
|
||||
pub const RAM_32_END: usize = 0x8000_0000; // 2 GiB
|
||||
pub const RAM_32_SIZE: usize = RAM_32_END; // 2 GiB
|
||||
|
||||
pub const MMIO_32_START: usize = 0x8000_0000; // 2 GiB
|
||||
pub const MMIO_32_END: usize = 0xe000_0000; // 3.5 GiB
|
||||
|
||||
pub const PCIE_CONFIG_START: usize = 0xe000_0000; // 3.5 GiB
|
||||
pub const PCIE_CONFIG_END: usize = 0xf000_0000; // 3.75 GiB, size = 256 MiB
|
||||
|
||||
pub const IOAPIC_START: usize = 0xfec0_0000;
|
||||
pub const APIC_START: usize = 0xfee0_0000;
|
||||
|
||||
pub const MEM_64_START: usize = 0x1_0000_0000; // 4GiB
|
||||
|
||||
pub const PAGE_SIZE: usize = 0x1000; // 4KiB
|
40
alioth/src/arch/x86_64/msr.rs
Normal file
40
alioth/src/arch/x86_64/msr.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
// Intel Vol.4, Table 2-2.
|
||||
pub const IA32_EFER: u32 = 0xc000_0080;
|
||||
pub const IA32_STAR: u32 = 0xc000_0081;
|
||||
pub const IA32_LSTAR: u32 = 0xc000_0082;
|
||||
pub const IA32_CSTAR: u32 = 0xc000_0083;
|
||||
pub const IA32_FMASK: u32 = 0xc000_0084;
|
||||
pub const IA32_FS_BASE: u32 = 0xc000_0100;
|
||||
pub const IA32_GS_BASE: u32 = 0xc000_0101;
|
||||
pub const IA32_KERNEL_GS_BASE: u32 = 0xc000_0102;
|
||||
pub const IA32_TSC_AUX: u32 = 0xc000_0103;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Efer: u32 {
|
||||
/// SYSCALL enable
|
||||
const SCE = 1 << 0;
|
||||
/// IA-32e mode enable
|
||||
const LME = 1 << 8;
|
||||
/// IA-32e mode active
|
||||
const LMA = 1 << 10;
|
||||
/// Execute disable bit enable
|
||||
const NXE = 1 << 11;
|
||||
}
|
||||
}
|
39
alioth/src/arch/x86_64/paging.rs
Normal file
39
alioth/src/arch/x86_64/paging.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Entry: u32 {
|
||||
/// Present
|
||||
const P = 1 << 0;
|
||||
/// Read/write
|
||||
const RW = 1 << 1;
|
||||
/// User/supervisor
|
||||
const US = 1 << 2;
|
||||
/// Page-level write-through
|
||||
const PWT = 1 << 3;
|
||||
/// Page-level cache disable
|
||||
const PCD = 1 << 4;
|
||||
/// Accessed
|
||||
const A = 1 << 5;
|
||||
/// Dirty
|
||||
const D = 1 << 6;
|
||||
/// Page size
|
||||
const PS = 1 << 7;
|
||||
/// Global
|
||||
const G = 1 << 8;
|
||||
}
|
||||
}
|
170
alioth/src/arch/x86_64/reg.rs
Normal file
170
alioth/src/arch/x86_64/reg.rs
Normal file
|
@ -0,0 +1,170 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use bitfield::bitfield;
|
||||
use bitflags::bitflags;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Rflags: u32 {
|
||||
/// CarryCarry flag
|
||||
const CF = 1 << 0;
|
||||
/// CarryReserved
|
||||
const RESERVED_1 = 1 << 1;
|
||||
/// CarryParity flag
|
||||
const PF = 1 << 2;
|
||||
/// CarryAuxiliary Carry flag
|
||||
const AF = 1 << 4;
|
||||
/// CarryZero flag
|
||||
const ZF = 1 << 6;
|
||||
/// CarrySign flag
|
||||
const SF = 1 << 7;
|
||||
/// CarryTrap flag
|
||||
const TF = 1 << 8;
|
||||
/// CarryInterrupt enable flag
|
||||
const IF = 1 << 9;
|
||||
/// CarryDirection flag
|
||||
const DF = 1 << 10;
|
||||
/// CarryOverflow flag
|
||||
const OF = 1 << 11;
|
||||
/// CarryI/O privilege level
|
||||
const IOPL = 1 << 13;
|
||||
/// CarryNested task flag
|
||||
const NT = 1 << 14;
|
||||
/// CarryResume flag
|
||||
const RF = 1 << 16;
|
||||
/// CarryVirtual 8086 mode flag
|
||||
const VM = 1 << 17;
|
||||
/// CarryAlignment Check
|
||||
const AC = 1 << 18;
|
||||
/// CarryVirtual interrupt flag
|
||||
const VIF = 1 << 19;
|
||||
/// CarryVirtual interrupt pending
|
||||
const VIP = 1 << 20;
|
||||
/// CarryIdentification flag
|
||||
const ID = 1 << 21;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Cr0: u32 {
|
||||
/// CarryProtected Mode Enable
|
||||
const PE = 1 << 0;
|
||||
/// CarryMonitor co-processor
|
||||
const MP = 1 << 1;
|
||||
/// CarryEmulation
|
||||
const EM = 1 << 2;
|
||||
/// CarryTask switched
|
||||
const TS = 1 << 3;
|
||||
/// CarryExtension type
|
||||
const ET = 1 << 4;
|
||||
/// CarryNumeric error
|
||||
const NE = 1 << 5;
|
||||
/// CarryWrite protect
|
||||
const WP = 1 << 16;
|
||||
/// CarryAlignment mask
|
||||
const AM = 1 << 18;
|
||||
/// CarryNot-write through
|
||||
const NW = 1 << 29;
|
||||
/// CarryCache disable
|
||||
const CD = 1 << 30;
|
||||
/// CarryPaging
|
||||
const PG = 1 << 31;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Cr3: u64 {
|
||||
/// CarryPage-level write-through
|
||||
const PWT = 1 << 3;
|
||||
/// CarryPage-level Cache disable
|
||||
const PCD = 1 << 4;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Cr4: u32 {
|
||||
/// CarryVirtual 8086 Mode Extensions
|
||||
const VME = 1 << 0;
|
||||
/// CarryProtected-mode Virtual Interrupts
|
||||
const PVI = 1 << 1;
|
||||
/// CarryTime Stamp Disable
|
||||
const TSD = 1 << 2;
|
||||
/// CarryDebugging Extensions
|
||||
const DE = 1 << 3;
|
||||
/// CarryPage Size Extension
|
||||
const PSE = 1 << 4;
|
||||
/// CarryPhysical Address Extension
|
||||
const PAE = 1 << 5;
|
||||
/// CarryMachine Check Exception
|
||||
const MCE = 1 << 6;
|
||||
/// CarryPage Global Enabled
|
||||
const PGE = 1 << 7;
|
||||
/// CarryPerformance-Monitoring Counter enable
|
||||
const PCE = 1 << 8;
|
||||
/// CarryOperating system support for FXSAVE and FXRSTOR instructions
|
||||
const OSFXSR = 1 << 9;
|
||||
/// CarryOperating System Support for Unmasked SIMD Floating-Point Exceptions
|
||||
const OSXMMEXCPT = 1 << 10;
|
||||
/// CarryUser-Mode Instruction Prevention
|
||||
const UMIP = 1 << 11;
|
||||
/// Carry57-Bit Linear Addresses
|
||||
const LA57 = 1 << 12;
|
||||
/// CarryVirtual Machine Extensions Enable
|
||||
const VMXE = 1 << 13;
|
||||
/// CarrySafer Mode Extensions Enable
|
||||
const SMXE = 1 << 14;
|
||||
/// CarryFSGSBASE Enable
|
||||
const FSGSBASE = 1 << 16;
|
||||
/// CarryPCID Enable
|
||||
const PCIDE = 1 << 17;
|
||||
/// CarryXSAVE and Processor Extended States Enable
|
||||
const OSXSAVE = 1 << 18;
|
||||
/// CarryKey Locker Enable
|
||||
const KL = 1 << 19;
|
||||
/// CarrySupervisor Mode Execution Protection Enable
|
||||
const SMEP = 1 << 20;
|
||||
/// CarrySupervisor Mode Access Prevention Enable
|
||||
const SMAP = 1 << 21;
|
||||
/// CarryProtection Key Enable
|
||||
const PKE = 1 << 22;
|
||||
/// CarryControl-flow Enforcement Technology
|
||||
const CET = 1 << 23;
|
||||
/// CarryEnable Protection Keys for Supervisor-Mode Pages
|
||||
const PKS = 1 << 24;
|
||||
/// CarryUser Interrupts Enable
|
||||
const UINTR = 1 << 25;
|
||||
}
|
||||
}
|
||||
|
||||
bitfield! {
|
||||
/// Guest segment register access right.
|
||||
///
|
||||
/// See Intel Architecture Software Developer's Manual, Vol.3, Table 24-2.
|
||||
#[derive(Copy, Clone, Default, PartialEq, Eq, Hash)]
|
||||
pub struct SegAccess(u32);
|
||||
impl Debug;
|
||||
pub seg_type, _ : 3, 0;
|
||||
pub s_code_data, _ : 4;
|
||||
pub priv_level, _ : 6, 5;
|
||||
pub present, _ : 7;
|
||||
pub available, _ : 12;
|
||||
pub l_64bit, _ : 13;
|
||||
pub db_size_32, _: 14;
|
||||
pub granularity, _: 15;
|
||||
pub unusable, _: 16;
|
||||
}
|
15
alioth/src/device.rs
Normal file
15
alioth/src/device.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod serial;
|
490
alioth/src/device/serial.rs
Normal file
490
alioth/src/device/serial.rs
Normal file
|
@ -0,0 +1,490 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{self, ErrorKind};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use bitfield::bitfield;
|
||||
use bitflags::bitflags;
|
||||
use libc::{
|
||||
cfmakeraw, fcntl, tcgetattr, tcsetattr, termios, F_GETFL, F_SETFL, O_NONBLOCK, STDIN_FILENO,
|
||||
STDOUT_FILENO, TCSANOW,
|
||||
};
|
||||
use mio::unix::SourceFd;
|
||||
use mio::{Events, Interest, Poll, Token, Waker};
|
||||
|
||||
use crate::hv::IntxSender;
|
||||
use crate::mem::mmio::Mmio;
|
||||
use crate::{ffi, mem};
|
||||
|
||||
const TX_HOLDING_REGISTER: u16 = 0x0;
|
||||
const RX_BUFFER_REGISTER: u16 = 0x0;
|
||||
const DIVISOR_LATCH_LSB: u16 = 0x0;
|
||||
const DIVISOR_LATCH_MSB: u16 = 0x1;
|
||||
const INTERRUPT_ENABLE_REGISTER: u16 = 0x1;
|
||||
const FIFO_CONTROL_REGISTER: u16 = 0x2;
|
||||
const INTERRUPT_IDENTIFICATION_REGISTER: u16 = 0x2;
|
||||
const LINE_CONTROL_REGISTER: u16 = 0x3;
|
||||
const MODEM_CONTROL_REGISTER: u16 = 0x4;
|
||||
const LINE_STATUS_REGISTER: u16 = 0x5;
|
||||
const MODEM_STATUS_REGISTER: u16 = 0x6;
|
||||
const SCRATCH_REGISTER: u16 = 0x7;
|
||||
|
||||
// offset 0x1, Interrupt Enable Register (IER)
|
||||
bitflags! {
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct InterruptEnable: u8 {
|
||||
const MODEM_STATUS = 1 << 3;
|
||||
const RECEIVER_LINE_STATUS = 1 << 2;
|
||||
const TX_HOLDING_REGISTER_EMPTY = 1 << 1;
|
||||
const RECEIVED_DATA_AVAILABLE = 1 << 0;
|
||||
}
|
||||
}
|
||||
|
||||
// offset 0x2, write, FIFO Control Register (FCR)
|
||||
bitfield! {
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct FifoControl(u8);
|
||||
impl Debug;
|
||||
rx_trigger_size_bits, _: 7, 6;
|
||||
dma_mode, _: 3;
|
||||
tx_reset, _: 2;
|
||||
rx_reset, _: 1;
|
||||
fifo_enabled, _: 0;
|
||||
}
|
||||
|
||||
impl FifoControl {
|
||||
pub fn rx_trigger_size(&self) -> usize {
|
||||
match self.rx_trigger_size_bits() {
|
||||
0b00 => 1,
|
||||
0b01 => 4,
|
||||
0b10 => 8,
|
||||
0b11 => 14,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// offset 0x2, read, Interrupt Identification Register
|
||||
bitfield! {
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct InterruptIdentification(u8);
|
||||
impl Debug;
|
||||
fifo_enabled, _: 7, 6;
|
||||
interrupt_id, set_interrupt_id: 3,1;
|
||||
no_pending, set_no_pending: 0; // Interrupt Pending Bit
|
||||
}
|
||||
|
||||
impl InterruptIdentification {
|
||||
pub fn set_fifo_enabled(&mut self) {
|
||||
self.0 |= 0b11 << 6;
|
||||
}
|
||||
|
||||
pub fn clear_fifi_enabled(&mut self) {
|
||||
self.0 &= !(0b11 << 6);
|
||||
}
|
||||
|
||||
pub fn set_rx_data_available(&mut self) {
|
||||
self.0 = (self.0 & !0b1111) | 0b0100;
|
||||
}
|
||||
|
||||
pub fn set_tx_room_empty(&mut self) {
|
||||
self.0 = (self.0 & !0b1111) | 0b0010;
|
||||
}
|
||||
|
||||
pub fn clear_interrupt(&mut self) {
|
||||
self.0 = (self.0 & !0b1111) | 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for InterruptIdentification {
|
||||
fn default() -> Self {
|
||||
let mut val = InterruptIdentification(0);
|
||||
val.clear_interrupt();
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
// offset 0x3, Line Control Register (LCR)
|
||||
bitfield! {
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct LineControl(u8);
|
||||
impl Debug;
|
||||
divisor_latch_access, _: 7;
|
||||
break_, _: 6;
|
||||
stick_parity, _: 5;
|
||||
even_parity, _: 4;
|
||||
parity_enabled, _: 3;
|
||||
step_bits, _: 2;
|
||||
word_length, _: 1, 0;
|
||||
}
|
||||
|
||||
impl Default for LineControl {
|
||||
fn default() -> Self {
|
||||
LineControl(0b00000011) // 8 data bits as default
|
||||
}
|
||||
}
|
||||
|
||||
// offset 0x4, Modem Control Register
|
||||
bitfield! {
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct ModemControl(u8);
|
||||
impl Debug;
|
||||
loop_back, _: 4;
|
||||
out_2, _: 3;
|
||||
out_1, _: 2;
|
||||
request_to_send, _: 1;
|
||||
data_terminal_ready, _: 0; // Data Terminal Ready
|
||||
}
|
||||
|
||||
// offset 0x5, Line Status Register (LSR)
|
||||
bitflags! {
|
||||
#[derive(Debug)]
|
||||
pub struct LineStatus: u8 {
|
||||
const ERROR_IN_RX_FIFO = 1 << 7;
|
||||
const TX_EMPTY = 1 << 6;
|
||||
const TX_HOLDING_REGISTER_EMPTY = 1 << 5;
|
||||
const BREAK_INTERRUPT = 1 << 4;
|
||||
const FRAMING_ERROR = 1 << 3;
|
||||
const PARITY_ERROR = 1 << 2;
|
||||
const OVERRUN_ERROR = 1 << 1;
|
||||
const DATA_READY = 1 << 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LineStatus {
|
||||
fn default() -> Self {
|
||||
LineStatus::TX_EMPTY | LineStatus::TX_HOLDING_REGISTER_EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct SerialReg {
|
||||
interrupt_enable: InterruptEnable, // 0x1, Interrupt Enable Register (IER)
|
||||
#[allow(dead_code)]
|
||||
fifo_control: FifoControl, // 0x2, write, FIFO Control Register (FCR)
|
||||
interrupt_identification: InterruptIdentification, // 0x2, read, Interrupt Identification Register
|
||||
line_control: LineControl, // 0x3, Line Control Register (LCR)
|
||||
modem_control: ModemControl, // 0x4, Modem Control Register (MCR)
|
||||
line_status: LineStatus,
|
||||
modem_status: u8, // 0x6, Modem Status Register (MSR)
|
||||
scratch: u8, // 0x7, Scratch Register (SCR)
|
||||
divisor: u16,
|
||||
data: VecDeque<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Serial<I> {
|
||||
base_port: u16,
|
||||
irq_sender: Arc<I>,
|
||||
reg: Arc<Mutex<SerialReg>>,
|
||||
worker_thread: Option<JoinHandle<()>>,
|
||||
exit_waker: Waker,
|
||||
}
|
||||
|
||||
impl<I> Mmio for Serial<I>
|
||||
where
|
||||
I: IntxSender + Sync + Send + 'static,
|
||||
{
|
||||
fn size(&self) -> usize {
|
||||
8
|
||||
}
|
||||
|
||||
fn read(&self, offset: usize, _size: u8) -> Result<u64, mem::Error> {
|
||||
let mut reg = self.reg.lock()?;
|
||||
let ret = match offset as u16 {
|
||||
DIVISOR_LATCH_LSB if reg.line_control.divisor_latch_access() => reg.divisor as u8,
|
||||
DIVISOR_LATCH_MSB if reg.line_control.divisor_latch_access() => {
|
||||
(reg.divisor >> 8) as u8
|
||||
}
|
||||
RX_BUFFER_REGISTER => {
|
||||
if reg.data.len() <= 1 {
|
||||
reg.line_status &= !LineStatus::DATA_READY;
|
||||
}
|
||||
reg.data.pop_front().unwrap_or(0xff)
|
||||
}
|
||||
INTERRUPT_ENABLE_REGISTER => reg.interrupt_enable.bits(),
|
||||
INTERRUPT_IDENTIFICATION_REGISTER => {
|
||||
let ret = reg.interrupt_identification.0;
|
||||
reg.interrupt_identification.clear_interrupt();
|
||||
ret
|
||||
}
|
||||
LINE_CONTROL_REGISTER => reg.line_control.0,
|
||||
MODEM_CONTROL_REGISTER => reg.modem_control.0,
|
||||
LINE_STATUS_REGISTER => reg.line_status.bits(),
|
||||
MODEM_STATUS_REGISTER => reg.modem_status,
|
||||
SCRATCH_REGISTER => reg.scratch,
|
||||
_ => {
|
||||
log::error!(
|
||||
"Serial {:#x}: read unreachable port {:#x}",
|
||||
self.base_port,
|
||||
offset as u16 + self.base_port
|
||||
);
|
||||
0x0
|
||||
}
|
||||
};
|
||||
Ok(ret as u64)
|
||||
}
|
||||
|
||||
fn write(&self, offset: usize, _size: u8, val: u64) -> Result<(), mem::Error> {
|
||||
let byte = val as u8;
|
||||
let mut reg = self.reg.lock()?;
|
||||
match offset as u16 {
|
||||
DIVISOR_LATCH_LSB if reg.line_control.divisor_latch_access() => {
|
||||
reg.divisor = (reg.divisor & 0xff00) | byte as u16;
|
||||
}
|
||||
DIVISOR_LATCH_MSB if reg.line_control.divisor_latch_access() => {
|
||||
reg.divisor = (reg.divisor & 0x00ff) | (byte as u16) << 8;
|
||||
}
|
||||
TX_HOLDING_REGISTER => {
|
||||
if reg.modem_control.loop_back() {
|
||||
reg.data.push_back(byte);
|
||||
if reg
|
||||
.interrupt_enable
|
||||
.contains(InterruptEnable::RECEIVED_DATA_AVAILABLE)
|
||||
{
|
||||
reg.interrupt_identification.set_rx_data_available();
|
||||
self.send_irq();
|
||||
}
|
||||
reg.line_status |= LineStatus::DATA_READY;
|
||||
} else {
|
||||
if let Err(e) =
|
||||
ffi!(unsafe { libc::write(STDOUT_FILENO, &byte as *const u8 as _, 1) })
|
||||
{
|
||||
log::error!(
|
||||
"Serial {:#x}: cannot write byte {:#02x}: {:?}",
|
||||
self.base_port,
|
||||
byte,
|
||||
e
|
||||
)
|
||||
}
|
||||
if reg
|
||||
.interrupt_enable
|
||||
.contains(InterruptEnable::TX_HOLDING_REGISTER_EMPTY)
|
||||
{
|
||||
reg.interrupt_identification.set_tx_room_empty();
|
||||
self.send_irq()
|
||||
}
|
||||
}
|
||||
}
|
||||
INTERRUPT_ENABLE_REGISTER => {
|
||||
reg.interrupt_enable = InterruptEnable::from_bits_truncate(byte);
|
||||
}
|
||||
FIFO_CONTROL_REGISTER => {}
|
||||
LINE_CONTROL_REGISTER => {
|
||||
reg.line_control = LineControl(byte);
|
||||
}
|
||||
MODEM_CONTROL_REGISTER => {
|
||||
reg.modem_control = ModemControl(byte);
|
||||
}
|
||||
LINE_STATUS_REGISTER => {}
|
||||
MODEM_STATUS_REGISTER => {}
|
||||
SCRATCH_REGISTER => {
|
||||
reg.scratch = byte;
|
||||
}
|
||||
_ => log::error!(
|
||||
"Serial {:#x}: write unreachable offset {:#x}",
|
||||
self.base_port,
|
||||
offset as u16 + self.base_port
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct StdinBackup {
|
||||
termios: Option<termios>,
|
||||
flag: Option<i32>,
|
||||
}
|
||||
|
||||
impl StdinBackup {
|
||||
fn new() -> StdinBackup {
|
||||
let mut termios_backup = None;
|
||||
let mut t = MaybeUninit::uninit();
|
||||
match ffi!(unsafe { tcgetattr(STDIN_FILENO, t.as_mut_ptr()) }) {
|
||||
Ok(_) => termios_backup = Some(unsafe { t.assume_init() }),
|
||||
Err(e) => log::error!("tcgetattr() failed: {}", e),
|
||||
}
|
||||
let mut flag_backup = None;
|
||||
match ffi! { unsafe { fcntl(STDIN_FILENO, F_GETFL) } } {
|
||||
Ok(f) => flag_backup = Some(f),
|
||||
Err(e) => log::error!("fcntl(STDIN_FILENO, F_GETFL) failed: {}", e),
|
||||
}
|
||||
StdinBackup {
|
||||
termios: termios_backup,
|
||||
flag: flag_backup,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for StdinBackup {
|
||||
fn drop(&mut self) {
|
||||
if let Some(t) = self.termios.take() {
|
||||
if let Err(e) = ffi!(unsafe { tcsetattr(STDIN_FILENO, 1, &t) }) {
|
||||
log::error!("Restroing termios: {:?}", e);
|
||||
}
|
||||
}
|
||||
if let Some(f) = self.flag.take() {
|
||||
if let Err(e) = ffi!(unsafe { fcntl(STDIN_FILENO, F_SETFL, f) }) {
|
||||
log::error!("Restoring stdin flag to {:#x}: {:?}", f, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SeiralWorker<I> {
|
||||
pub base_port: u16,
|
||||
pub irq_sender: Arc<I>,
|
||||
pub reg: Arc<Mutex<SerialReg>>,
|
||||
pub poll: Poll,
|
||||
}
|
||||
|
||||
impl<I> SeiralWorker<I>
|
||||
where
|
||||
I: IntxSender,
|
||||
{
|
||||
fn setup_termios(&mut self) -> io::Result<()> {
|
||||
let mut raw_termios = MaybeUninit::uninit();
|
||||
ffi!(unsafe { tcgetattr(STDIN_FILENO, raw_termios.as_mut_ptr()) })?;
|
||||
unsafe { cfmakeraw(raw_termios.as_mut_ptr()) };
|
||||
ffi!(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, raw_termios.as_ptr()) })?;
|
||||
|
||||
let flag = ffi!(unsafe { fcntl(STDIN_FILENO, F_GETFL) })?;
|
||||
ffi!(unsafe { fcntl(STDIN_FILENO, F_SETFL, flag | O_NONBLOCK) })?;
|
||||
self.poll.registry().register(
|
||||
&mut SourceFd(&STDIN_FILENO),
|
||||
TOKEN_STDIN,
|
||||
Interest::READABLE,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_input(data: &mut VecDeque<u8>) -> io::Result<usize> {
|
||||
let mut total_size = 0;
|
||||
let mut buf = [0u8; 16];
|
||||
loop {
|
||||
match ffi!(unsafe { libc::read(STDIN_FILENO, buf.as_mut_ptr() as _, 16) }) {
|
||||
Ok(0) => break,
|
||||
Err(e) if e.kind() == ErrorKind::WouldBlock => break,
|
||||
Ok(len) => {
|
||||
data.extend(&buf[0..len as usize]);
|
||||
total_size += len as usize;
|
||||
}
|
||||
Err(e) => Err(e)?,
|
||||
}
|
||||
}
|
||||
Ok(total_size)
|
||||
}
|
||||
|
||||
fn send_irq(&self) {
|
||||
if let Err(e) = self.irq_sender.send() {
|
||||
log::error!("Serial {:#x}: sending interrupt: {:?}", self.base_port, e);
|
||||
}
|
||||
}
|
||||
|
||||
fn do_work_inner(&mut self) -> io::Result<()> {
|
||||
self.setup_termios()?;
|
||||
let mut events = Events::with_capacity(16);
|
||||
loop {
|
||||
self.poll.poll(&mut events, None)?;
|
||||
for event in events.iter() {
|
||||
if event.token() == TOKEN_SHUTDOWN {
|
||||
return Ok(());
|
||||
}
|
||||
let Ok(mut reg) = self.reg.lock() else {
|
||||
log::error!("Serial {:#x}: mutex poisoned", self.base_port);
|
||||
return Ok(());
|
||||
};
|
||||
if Self::read_input(&mut reg.data)? == 0 {
|
||||
continue;
|
||||
}
|
||||
if reg
|
||||
.interrupt_enable
|
||||
.contains(InterruptEnable::RECEIVED_DATA_AVAILABLE)
|
||||
{
|
||||
reg.interrupt_identification.set_rx_data_available();
|
||||
self.send_irq()
|
||||
}
|
||||
reg.line_status |= LineStatus::DATA_READY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_work(&mut self) {
|
||||
log::trace!("Serial {:#x}: start", self.base_port);
|
||||
let _backup = StdinBackup::new();
|
||||
if let Err(e) = self.do_work_inner() {
|
||||
log::error!("Serial {:#x}: {:?}", self.base_port, e)
|
||||
} else {
|
||||
log::trace!("Serial {:#x}: done", self.base_port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const TOKEN_SHUTDOWN: Token = Token(1);
|
||||
const TOKEN_STDIN: Token = Token(0);
|
||||
|
||||
impl<I> Serial<I>
|
||||
where
|
||||
I: IntxSender + Sync + Send + 'static,
|
||||
{
|
||||
pub fn new(base_port: u16, intx_sender: I) -> io::Result<Self> {
|
||||
let irq_sender = Arc::new(intx_sender);
|
||||
let reg = Arc::new(Mutex::new(SerialReg::default()));
|
||||
let poll = Poll::new()?;
|
||||
let waker = Waker::new(poll.registry(), TOKEN_SHUTDOWN)?;
|
||||
let mut worker = SeiralWorker {
|
||||
base_port,
|
||||
reg: reg.clone(),
|
||||
poll,
|
||||
irq_sender: irq_sender.clone(),
|
||||
};
|
||||
let worker_thread = std::thread::Builder::new()
|
||||
.name(format!("serial_{:#x}", base_port))
|
||||
.spawn(move || worker.do_work())?;
|
||||
let serial = Serial {
|
||||
reg,
|
||||
base_port,
|
||||
irq_sender,
|
||||
worker_thread: Some(worker_thread),
|
||||
exit_waker: waker,
|
||||
};
|
||||
Ok(serial)
|
||||
}
|
||||
|
||||
fn send_irq(&self) {
|
||||
if let Err(e) = self.irq_sender.send() {
|
||||
log::error!("Serial {:#x}: sending interrupt: {:?}", self.base_port, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Drop for Serial<I> {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = self.exit_waker.wake() {
|
||||
log::error!("Serial {:#x}: {:?}", self.base_port, e);
|
||||
return;
|
||||
}
|
||||
let Some(thread) = self.worker_thread.take() else {
|
||||
return;
|
||||
};
|
||||
if let Err(e) = thread.join() {
|
||||
log::error!("Serial {:#x}: {:?}", self.base_port, e);
|
||||
}
|
||||
}
|
||||
}
|
184
alioth/src/hv.rs
Normal file
184
alioth/src/hv.rs
Normal file
|
@ -0,0 +1,184 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod arch;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod kvm;
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use kvm::Kvm;
|
||||
|
||||
use std::backtrace::Backtrace;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, PoisonError};
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use arch::Reg;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use arch::{Cpuid, DtReg, DtRegVal, SReg, SegReg, SegRegVal};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("invalid memory map option for {hypervisor}: {option:?}")]
|
||||
MemMapOption {
|
||||
option: MemMapOption,
|
||||
hypervisor: &'static str,
|
||||
},
|
||||
#[error("lock poisoned")]
|
||||
RwLockPoisoned,
|
||||
#[error("IO error: {source}")]
|
||||
StdIo {
|
||||
#[from]
|
||||
source: std::io::Error,
|
||||
#[backtrace]
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[error("{msg}")]
|
||||
Unexpected { msg: String },
|
||||
#[error("lack capability: {cap}")]
|
||||
LackCap { cap: String },
|
||||
#[error("creating multipe memory")]
|
||||
CreatingMultipleMemory,
|
||||
}
|
||||
|
||||
impl<T> From<PoisonError<T>> for Error {
|
||||
fn from(_: PoisonError<T>) -> Self {
|
||||
Self::RwLockPoisoned
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MemMapOption {
|
||||
pub read: bool,
|
||||
pub write: bool,
|
||||
pub exec: bool,
|
||||
pub log_dirty: bool,
|
||||
}
|
||||
|
||||
impl Default for MemMapOption {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
read: true,
|
||||
write: true,
|
||||
exec: true,
|
||||
log_dirty: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Vcpu {
|
||||
fn get_reg(&self, reg: Reg) -> Result<u64, Error>;
|
||||
fn set_regs(&mut self, vals: &[(Reg, u64)]) -> Result<(), Error>;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_seg_reg(&self, reg: SegReg) -> Result<SegRegVal, Error>;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_dt_reg(&self, reg: DtReg) -> Result<DtRegVal, Error>;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_sreg(&self, reg: SReg) -> Result<u64, Error>;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn set_sregs(
|
||||
&mut self,
|
||||
sregs: &[(SReg, u64)],
|
||||
seg_regs: &[(SegReg, SegRegVal)],
|
||||
dt_regs: &[(DtReg, DtRegVal)],
|
||||
) -> Result<(), Error>;
|
||||
|
||||
fn run(&mut self, entry: VmEntry) -> Result<VmExit, Error>;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn set_cpuids(&mut self, cpuids: Vec<Cpuid>) -> Result<(), Error>;
|
||||
|
||||
fn dump(&self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub trait IntxSender: Debug + Send + Sync + 'static {
|
||||
fn send(&self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl<T> IntxSender for Arc<T>
|
||||
where
|
||||
T: IntxSender,
|
||||
{
|
||||
fn send(&self) -> Result<(), Error> {
|
||||
IntxSender::send(self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VmMemory: Debug + Send + Sync + 'static {
|
||||
fn mem_map(
|
||||
&self,
|
||||
slot: u32,
|
||||
gpa: usize,
|
||||
size: usize,
|
||||
hva: usize,
|
||||
option: MemMapOption,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
fn unmap(&self, slot: u32, gpa: usize, size: usize) -> Result<(), Error>;
|
||||
|
||||
fn max_mem_slots(&self) -> Result<u32, Error>;
|
||||
}
|
||||
|
||||
pub trait Vm {
|
||||
type Vcpu: Vcpu;
|
||||
type Memory: VmMemory;
|
||||
type IntxSender: IntxSender + Send + Sync;
|
||||
fn create_vcpu(&self, id: u32) -> Result<Self::Vcpu, Error>;
|
||||
fn create_intx_sender(&self, pin: u8) -> Result<Self::IntxSender, Error>;
|
||||
fn create_vm_memory(&mut self) -> Result<Self::Memory, Error>;
|
||||
fn stop_vcpu<T>(id: u32, handle: &JoinHandle<T>) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub trait Hypervisor {
|
||||
type Vm: Vm + Sync + Send + 'static;
|
||||
|
||||
fn create_vm(&self) -> Result<Self::Vm, Error>;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_supported_cpuids(&self) -> Result<Vec<Cpuid>, Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum VmExit {
|
||||
Io {
|
||||
port: u16,
|
||||
write: Option<u32>,
|
||||
size: u8,
|
||||
},
|
||||
Mmio {
|
||||
addr: usize,
|
||||
write: Option<u64>,
|
||||
size: u8,
|
||||
},
|
||||
Shutdown,
|
||||
Unknown(String),
|
||||
Interrupted,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum VmEntry {
|
||||
None,
|
||||
Shutdown,
|
||||
Io { data: u32 },
|
||||
Mmio { data: u64 },
|
||||
}
|
18
alioth/src/hv/arch.rs
Normal file
18
alioth/src/hv/arch.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::*;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod x86_64;
|
100
alioth/src/hv/arch/x86_64.rs
Normal file
100
alioth/src/hv/arch/x86_64.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::arch::reg::SegAccess;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Cpuid {
|
||||
pub func: u32,
|
||||
pub index: Option<u32>,
|
||||
pub eax: u32,
|
||||
pub ebx: u32,
|
||||
pub ecx: u32,
|
||||
pub edx: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Reg {
|
||||
Rax,
|
||||
Rbx,
|
||||
Rcx,
|
||||
Rdx,
|
||||
Rsi,
|
||||
Rdi,
|
||||
Rsp,
|
||||
Rbp,
|
||||
R8,
|
||||
R9,
|
||||
R10,
|
||||
R11,
|
||||
R12,
|
||||
R13,
|
||||
R14,
|
||||
R15,
|
||||
Rip,
|
||||
Rflags,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum SReg {
|
||||
Cr0,
|
||||
Cr2,
|
||||
Cr3,
|
||||
Cr4,
|
||||
Cr8,
|
||||
Efer,
|
||||
ApicBase,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum SegReg {
|
||||
Cs,
|
||||
Ds,
|
||||
Es,
|
||||
Fs,
|
||||
Gs,
|
||||
Ss,
|
||||
Tr,
|
||||
Ldtr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum DtReg {
|
||||
Gdtr,
|
||||
Idtr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
pub struct SegRegVal {
|
||||
pub selector: u16,
|
||||
pub base: u64,
|
||||
pub limit: u32,
|
||||
pub access: SegAccess,
|
||||
}
|
||||
|
||||
impl SegRegVal {
|
||||
pub fn to_desc(&self) -> u64 {
|
||||
((self.base & 0xff00_0000) << (56 - 24))
|
||||
| (((self.access.0 as u64) & 0x0000_f0ff) << 40)
|
||||
| (((self.limit as u64) & 0x000f_0000) << (48 - 16))
|
||||
| ((self.base & 0x00ff_ffff) << 16)
|
||||
| ((self.limit as u64) & 0x0000_ffff)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
pub struct DtRegVal {
|
||||
pub base: u64,
|
||||
pub limit: u16,
|
||||
}
|
133
alioth/src/hv/kvm.rs
Normal file
133
alioth/src/hv/kvm.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod bindings;
|
||||
mod ioctls;
|
||||
mod vcpu;
|
||||
mod vm;
|
||||
mod vmentry;
|
||||
mod vmexit;
|
||||
|
||||
use std::mem::{size_of, transmute};
|
||||
use std::os::fd::{FromRawFd, OwnedFd};
|
||||
use std::ptr::null_mut;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::ffi;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::hv::Cpuid;
|
||||
use crate::hv::{Error, Hypervisor};
|
||||
use bindings::{KvmCpuid2, KvmCpuid2Flag, KvmCpuidEntry2, KVM_API_VERSION, KVM_MAX_CPUID_ENTRIES};
|
||||
use ioctls::{kvm_create_irqchip, kvm_create_vm, kvm_get_api_version, kvm_get_vcpu_mmap_size};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use ioctls::{kvm_get_supported_cpuid, kvm_set_identity_map_addr, kvm_set_tss_addr};
|
||||
use libc::SIGRTMIN;
|
||||
use vm::KvmVm;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Kvm {
|
||||
fd: OwnedFd,
|
||||
}
|
||||
|
||||
extern "C" fn sigrtmin_handler(_: libc::c_int, _: *mut libc::siginfo_t, _: *mut libc::c_void) {}
|
||||
|
||||
impl Kvm {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let kvm_file = std::fs::File::open("/dev/kvm")?;
|
||||
let kvm_fd = OwnedFd::from(kvm_file);
|
||||
let version = unsafe { kvm_get_api_version(&kvm_fd) }?;
|
||||
if version != KVM_API_VERSION {
|
||||
return Err(Error::LackCap {
|
||||
cap: format!("current KVM API version {version}, need {KVM_API_VERSION}"),
|
||||
});
|
||||
}
|
||||
let mut action: libc::sigaction = unsafe { transmute([0u8; size_of::<libc::sigaction>()]) };
|
||||
action.sa_flags = libc::SA_SIGINFO;
|
||||
action.sa_sigaction = sigrtmin_handler as _;
|
||||
ffi!(unsafe { libc::sigfillset(&mut action.sa_mask) })?;
|
||||
ffi!(unsafe { libc::sigaction(SIGRTMIN(), &action, null_mut()) })?;
|
||||
Ok(Kvm { fd: kvm_fd })
|
||||
}
|
||||
}
|
||||
|
||||
impl Hypervisor for Kvm {
|
||||
type Vm = KvmVm;
|
||||
|
||||
fn create_vm(&self) -> Result<Self::Vm, Error> {
|
||||
let vcpu_mmap_size = unsafe { kvm_get_vcpu_mmap_size(&self.fd) }? as usize;
|
||||
let vm_fd = unsafe { kvm_create_vm(&self.fd, 0) }?;
|
||||
let fd = unsafe { OwnedFd::from_raw_fd(vm_fd) };
|
||||
unsafe { kvm_create_irqchip(&fd) }?;
|
||||
// TODO should be in parameters
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe { kvm_set_tss_addr(&fd, 0xf000_0000) }?;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe { kvm_set_identity_map_addr(&fd, &0xf000_3000) }?;
|
||||
Ok(KvmVm {
|
||||
fd: Arc::new(fd),
|
||||
vcpu_mmap_size,
|
||||
memory_created: false,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_supported_cpuids(&self) -> Result<Vec<Cpuid>, Error> {
|
||||
let mut kvm_cpuid2 = KvmCpuid2 {
|
||||
nent: KVM_MAX_CPUID_ENTRIES as u32,
|
||||
padding: 0,
|
||||
entries: [KvmCpuidEntry2::default(); KVM_MAX_CPUID_ENTRIES],
|
||||
};
|
||||
unsafe { kvm_get_supported_cpuid(&self.fd, &mut kvm_cpuid2) }?;
|
||||
let cpuids = kvm_cpuid2.entries[0..kvm_cpuid2.nent as usize]
|
||||
.iter()
|
||||
.map(|e| Cpuid {
|
||||
func: e.function,
|
||||
index: if e.flags.contains(KvmCpuid2Flag::SIGNIFCANT_INDEX) {
|
||||
Some(e.index)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
eax: e.eax,
|
||||
ebx: e.ebx,
|
||||
ecx: e.ecx,
|
||||
edx: e.edx,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(cpuids)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_get_supported_cpuid() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let mut kvm_cpuid_exist = false;
|
||||
let supported_cpuids = kvm.get_supported_cpuids().unwrap();
|
||||
for cpuid in &supported_cpuids {
|
||||
if cpuid.func == 0x4000_0000
|
||||
&& cpuid.ebx.to_le_bytes() == *b"KVMK"
|
||||
&& cpuid.ecx.to_le_bytes() == *b"VMKV"
|
||||
&& cpuid.edx.to_le_bytes() == *b"M\0\0\0"
|
||||
{
|
||||
kvm_cpuid_exist = true;
|
||||
}
|
||||
}
|
||||
assert!(kvm_cpuid_exist);
|
||||
}
|
||||
}
|
239
alioth/src/hv/kvm/bindings.rs
Normal file
239
alioth/src/hv/kvm/bindings.rs
Normal file
|
@ -0,0 +1,239 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
pub const KVMIO: u8 = 0xAE;
|
||||
pub const KVM_API_VERSION: i32 = 12;
|
||||
pub const KVM_MAX_CPUID_ENTRIES: usize = 256;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct KvmCpuid2Flag: u32 {
|
||||
const SIGNIFCANT_INDEX = 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct KvmCpuidEntry2 {
|
||||
pub function: u32,
|
||||
pub index: u32,
|
||||
pub flags: KvmCpuid2Flag,
|
||||
pub eax: u32,
|
||||
pub ebx: u32,
|
||||
pub ecx: u32,
|
||||
pub edx: u32,
|
||||
pub padding: [u32; 3],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KvmCpuid2<const N: usize> {
|
||||
pub nent: u32,
|
||||
pub padding: u32,
|
||||
pub entries: [KvmCpuidEntry2; N],
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct KvmMemFlag: u32 {
|
||||
const LOG_DIRTY_PAGES = 1;
|
||||
const READONLY = 2;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct KvmUserspaceMemoryRegion {
|
||||
pub slot: u32,
|
||||
pub flags: KvmMemFlag,
|
||||
pub guest_phys_addr: u64,
|
||||
pub memory_size: u64,
|
||||
pub userspace_addr: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct KvmRegs {
|
||||
pub rax: u64,
|
||||
pub rbx: u64,
|
||||
pub rcx: u64,
|
||||
pub rdx: u64,
|
||||
pub rsi: u64,
|
||||
pub rdi: u64,
|
||||
pub rsp: u64,
|
||||
pub rbp: u64,
|
||||
pub r8: u64,
|
||||
pub r9: u64,
|
||||
pub r10: u64,
|
||||
pub r11: u64,
|
||||
pub r12: u64,
|
||||
pub r13: u64,
|
||||
pub r14: u64,
|
||||
pub r15: u64,
|
||||
pub rip: u64,
|
||||
pub rflags: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct KvmSegment {
|
||||
pub base: u64,
|
||||
pub limit: u32,
|
||||
pub selector: u16,
|
||||
pub type_: u8,
|
||||
pub present: u8,
|
||||
pub dpl: u8,
|
||||
pub db: u8,
|
||||
pub s: u8,
|
||||
pub l: u8,
|
||||
pub g: u8,
|
||||
pub avl: u8,
|
||||
pub unusable: u8,
|
||||
pub padding: u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct KvmDtable {
|
||||
pub base: u64,
|
||||
pub limit: u16,
|
||||
pub padding: [u16; 3],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct KvmSregs2 {
|
||||
pub cs: KvmSegment,
|
||||
pub ds: KvmSegment,
|
||||
pub es: KvmSegment,
|
||||
pub fs: KvmSegment,
|
||||
pub gs: KvmSegment,
|
||||
pub ss: KvmSegment,
|
||||
pub tr: KvmSegment,
|
||||
pub ldt: KvmSegment,
|
||||
pub gdt: KvmDtable,
|
||||
pub idt: KvmDtable,
|
||||
pub cr0: u64,
|
||||
pub cr2: u64,
|
||||
pub cr3: u64,
|
||||
pub cr4: u64,
|
||||
pub cr8: u64,
|
||||
pub efer: u64,
|
||||
pub apic_base: u64,
|
||||
pub flags: u64,
|
||||
pub pdptrs: [u64; 4],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct KvmRun {
|
||||
pub request_interrupt_window: u8,
|
||||
pub immediate_exit: u8,
|
||||
pub padding1: [u8; 6],
|
||||
pub exit_reason: u32,
|
||||
pub ready_for_interrupt_injection: u8,
|
||||
pub if_flag: u8,
|
||||
pub flags: u16,
|
||||
pub cr8: u64,
|
||||
pub apic_base: u64,
|
||||
pub exit: KvmExit,
|
||||
pub kvm_valid_regs: u64,
|
||||
pub kvm_dirty_regs: u64,
|
||||
pub s: KvmSyncRegsBlock,
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union KvmExit {
|
||||
pub mmio: KvmExitMmio,
|
||||
pub io: KvmExitIo,
|
||||
pub padding: [u8; 256],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct KvmExitMmio {
|
||||
pub phys_addr: u64,
|
||||
pub data: [u8; 8],
|
||||
pub len: u32,
|
||||
pub is_write: u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct KvmExitIo {
|
||||
pub direction: u8,
|
||||
pub size: u8,
|
||||
pub port: u16,
|
||||
pub count: u32,
|
||||
pub data_offset: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union KvmSyncRegsBlock {
|
||||
pub padding: [u8; 2048],
|
||||
}
|
||||
|
||||
pub const KVM_EXIT_IO: u32 = 2;
|
||||
pub const KVM_EXIT_MMIO: u32 = 6;
|
||||
pub const KVM_EXIT_SHUTDOWN: u32 = 8;
|
||||
|
||||
pub const KVM_EXIT_IO_IN: u8 = 0;
|
||||
pub const KVM_EXIT_IO_OUT: u8 = 1;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct KvmIrqfd {
|
||||
pub fd: u32,
|
||||
pub gsi: u32,
|
||||
pub flags: u32,
|
||||
pub resamplefd: u32,
|
||||
pub pad: [u8; 16usize],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct KvmMsi {
|
||||
pub address_lo: u32,
|
||||
pub address_hi: u32,
|
||||
pub data: u32,
|
||||
pub flags: u32,
|
||||
pub devid: u32,
|
||||
pub pad: [u8; 12usize],
|
||||
}
|
||||
|
||||
pub const KVM_CAP_NR_MEMSLOTS: u64 = 10;
|
||||
pub const KVM_CAP_IRQFD: u64 = 32;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct KvmIoEventFdFlag: u32 {
|
||||
const DATA_MATCH = 1 << 0;
|
||||
const PIO = 1 << 1;
|
||||
const DEASSIGN = 1 << 2;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct KvmIoEventFd {
|
||||
pub datamatch: u64,
|
||||
pub addr: u64,
|
||||
pub len: u32,
|
||||
pub fd: i32,
|
||||
pub flags: KvmIoEventFdFlag,
|
||||
pub pad: [u32; 9],
|
||||
}
|
56
alioth/src/hv/kvm/ioctls.rs
Normal file
56
alioth/src/hv/kvm/ioctls.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::hv::kvm::bindings::{
|
||||
KvmCpuid2, KvmIrqfd, KvmRegs, KvmSregs2, KvmUserspaceMemoryRegion, KVMIO,
|
||||
};
|
||||
use crate::utils::ioctls::ioctl_io;
|
||||
use crate::{
|
||||
ioctl_none, ioctl_read, ioctl_write_buf, ioctl_write_ptr, ioctl_write_val, ioctl_writeread_buf,
|
||||
};
|
||||
|
||||
ioctl_none!(kvm_get_api_version, KVMIO, 0x00, 0);
|
||||
ioctl_write_val!(kvm_create_vm, ioctl_io(KVMIO, 0x01));
|
||||
ioctl_write_val!(kvm_check_extension, ioctl_io(KVMIO, 0x03));
|
||||
ioctl_none!(kvm_get_vcpu_mmap_size, KVMIO, 0x04, 0);
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
ioctl_writeread_buf!(kvm_get_supported_cpuid, KVMIO, 0x05, KvmCpuid2);
|
||||
|
||||
ioctl_write_val!(kvm_create_vcpu, ioctl_io(KVMIO, 0x41), u32);
|
||||
ioctl_write_ptr!(
|
||||
kvm_set_user_memory_region,
|
||||
KVMIO,
|
||||
0x46,
|
||||
KvmUserspaceMemoryRegion
|
||||
);
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
ioctl_write_val!(kvm_set_tss_addr, ioctl_io(KVMIO, 0x47));
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
ioctl_write_ptr!(kvm_set_identity_map_addr, KVMIO, 0x48, u64);
|
||||
|
||||
ioctl_none!(kvm_create_irqchip, KVMIO, 0x60, 0);
|
||||
|
||||
ioctl_write_ptr!(kvm_irqfd, KVMIO, 0x76, KvmIrqfd);
|
||||
|
||||
ioctl_none!(kvm_run, KVMIO, 0x80, 0);
|
||||
ioctl_read!(kvm_get_regs, KVMIO, 0x81, KvmRegs);
|
||||
ioctl_write_ptr!(kvm_set_regs, KVMIO, 0x82, KvmRegs);
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
ioctl_write_buf!(kvm_set_cpuid2, KVMIO, 0x90, KvmCpuid2);
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
ioctl_read!(kvm_get_sregs2, KVMIO, 0xcc, KvmSregs2);
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
ioctl_write_ptr!(kvm_set_sregs2, KVMIO, 0xcd, KvmSregs2);
|
481
alioth/src/hv/kvm/vcpu.rs
Normal file
481
alioth/src/hv/kvm/vcpu.rs
Normal file
|
@ -0,0 +1,481 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod x86_64;
|
||||
|
||||
use std::io::ErrorKind;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::os::fd::{OwnedFd, RawFd};
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use libc::{mmap, munmap, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE};
|
||||
|
||||
use crate::ffi;
|
||||
use crate::hv::arch::Reg;
|
||||
use crate::hv::kvm::bindings::{KvmRun, KVM_EXIT_IO, KVM_EXIT_MMIO};
|
||||
use crate::hv::kvm::ioctls::kvm_run;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::hv::{Cpuid, DtReg, DtRegVal, SReg, SegReg, SegRegVal};
|
||||
use crate::hv::{Error, Vcpu, VmEntry, VmExit};
|
||||
|
||||
use super::bindings::KVM_EXIT_SHUTDOWN;
|
||||
|
||||
pub(super) struct KvmRunBlock {
|
||||
addr: usize,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl KvmRunBlock {
|
||||
pub unsafe fn new(fd: RawFd, mmap_size: usize) -> Result<KvmRunBlock, Error> {
|
||||
let prot = PROT_READ | PROT_WRITE;
|
||||
let addr = ffi!(
|
||||
unsafe { mmap(null_mut(), mmap_size, prot, MAP_SHARED, fd, 0,) },
|
||||
MAP_FAILED
|
||||
)?;
|
||||
Ok(KvmRunBlock {
|
||||
addr: addr as usize,
|
||||
size: mmap_size,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) unsafe fn data_slice<T>(&self, offset: usize, count: usize) -> &[T] {
|
||||
std::slice::from_raw_parts((self.addr + offset) as *const T, count)
|
||||
}
|
||||
|
||||
pub(super) unsafe fn data_slice_mut<T>(&mut self, offset: usize, count: usize) -> &mut [T] {
|
||||
std::slice::from_raw_parts_mut((self.addr + offset) as *mut T, count)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for KvmRunBlock {
|
||||
type Target = KvmRun;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*(self.addr as *const Self::Target) }
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for KvmRunBlock {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { &mut *(self.addr as *mut Self::Target) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for KvmRunBlock {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = ffi!(unsafe { munmap(self.addr as _, self.size) }) {
|
||||
log::error!("unmap kvm_run: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KvmVcpu {
|
||||
pub(super) kvm_run: KvmRunBlock,
|
||||
pub(super) fd: OwnedFd,
|
||||
}
|
||||
|
||||
impl Vcpu for KvmVcpu {
|
||||
fn get_reg(&self, reg: Reg) -> Result<u64, Error> {
|
||||
self.kvm_get_reg(reg)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_dt_reg(&self, reg: DtReg) -> Result<DtRegVal, Error> {
|
||||
self.kvm_get_dt_reg(reg)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_seg_reg(&self, reg: SegReg) -> Result<SegRegVal, Error> {
|
||||
self.kvm_get_seg_reg(reg)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_sreg(&self, reg: SReg) -> Result<u64, Error> {
|
||||
self.kvm_get_sreg(reg)
|
||||
}
|
||||
|
||||
fn set_regs(&mut self, vals: &[(Reg, u64)]) -> Result<(), Error> {
|
||||
self.kvm_set_regs(vals)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn set_sregs(
|
||||
&mut self,
|
||||
sregs: &[(SReg, u64)],
|
||||
seg_regs: &[(SegReg, SegRegVal)],
|
||||
dt_regs: &[(DtReg, DtRegVal)],
|
||||
) -> Result<(), Error> {
|
||||
self.kvm_set_sregs(sregs, seg_regs, dt_regs)
|
||||
}
|
||||
|
||||
fn run(&mut self, entry: VmEntry) -> Result<VmExit, Error> {
|
||||
match entry {
|
||||
VmEntry::None => {}
|
||||
VmEntry::Io { data } => self.entry_io(data),
|
||||
VmEntry::Mmio { data } => self.entry_mmio(data),
|
||||
VmEntry::Shutdown => self.immediate_exit(),
|
||||
};
|
||||
let ret = unsafe { kvm_run(&self.fd) };
|
||||
match ret {
|
||||
Err(e) => match (e.kind(), entry) {
|
||||
(ErrorKind::WouldBlock, _) => Ok(VmExit::Interrupted),
|
||||
(ErrorKind::Interrupted, VmEntry::Shutdown) => Ok(VmExit::Shutdown),
|
||||
(ErrorKind::Interrupted, _) => Ok(VmExit::Interrupted),
|
||||
_ => Err(e.into()),
|
||||
},
|
||||
Ok(_) => match self.kvm_run.exit_reason {
|
||||
KVM_EXIT_IO => self.handle_io(),
|
||||
KVM_EXIT_MMIO => self.handle_mmio(),
|
||||
KVM_EXIT_SHUTDOWN => Ok(VmExit::Shutdown),
|
||||
reason => Ok(VmExit::Unknown(format!("unkown kvm exit: {:#x}", reason))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn set_cpuids(&mut self, cpuids: Vec<Cpuid>) -> Result<(), Error> {
|
||||
self.kvm_set_cpuids(cpuids)
|
||||
}
|
||||
|
||||
fn dump(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::assert_matches::assert_matches;
|
||||
use std::mem::size_of_val;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use libc::{mmap, MAP_ANONYMOUS, MAP_FAILED, MAP_SHARED, PROT_EXEC, PROT_READ, PROT_WRITE};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::msr::Efer;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::paging::Entry;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::reg::SegAccess;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::arch::reg::{Cr0, Cr4};
|
||||
use crate::ffi;
|
||||
use crate::hv::arch::Reg;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::hv::{DtReg, DtRegVal, SReg, SegReg, SegRegVal};
|
||||
use crate::hv::{Hypervisor, Kvm, MemMapOption, Vcpu, Vm, VmEntry, VmExit, VmMemory};
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_vcpu_regs() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let vm = kvm.create_vm().unwrap();
|
||||
let mut vcpu = vm.create_vcpu(0).unwrap();
|
||||
let regs = [
|
||||
(Reg::Rax, 0xa93f90f6ce9c8040),
|
||||
(Reg::Rbx, 0xacbfb3f1f6f9cc1a),
|
||||
(Reg::Rcx, 0x885e7996751c1cd5),
|
||||
(Reg::Rdx, 0xd0fdf85b84d0cc9c),
|
||||
(Reg::Rsi, 0x3cc1f46972391c30),
|
||||
(Reg::Rdi, 0xf67783992ddc4484),
|
||||
(Reg::Rsp, 0x6363e7f07d68f992),
|
||||
(Reg::Rbp, 0x7aeb086e85756325),
|
||||
(Reg::R8, 0x72a90eeeb1f73300),
|
||||
(Reg::R9, 0x8893ba64a98de27e),
|
||||
(Reg::R10, 0x543f074b89fd6531),
|
||||
(Reg::R11, 0x5330fea600e3a98c),
|
||||
(Reg::R12, 0x5d2af23af80a0c15),
|
||||
(Reg::R13, 0x596ad2d66a74a573),
|
||||
(Reg::R14, 0x9d97437934678adb),
|
||||
(Reg::R15, 0x7ae7b06eebe1f4fc),
|
||||
(Reg::Rip, 0xdb424549231b8d3e),
|
||||
(Reg::Rflags, 1 << 1),
|
||||
];
|
||||
vcpu.set_regs(®s).unwrap();
|
||||
for (reg, val) in regs {
|
||||
assert_eq!(vcpu.get_reg(reg).unwrap(), val);
|
||||
}
|
||||
|
||||
let sregs = [
|
||||
(SReg::Cr0, 1 << 0 | 1 << 5 | 1 << 31),
|
||||
(SReg::Cr2, 0xffff88ac93e00000),
|
||||
(SReg::Cr3, 0x1362d001),
|
||||
(SReg::Cr4, 1 << 5),
|
||||
(SReg::Cr8, 0x0),
|
||||
(SReg::Efer, 1 << 8 | 1 << 10),
|
||||
(SReg::ApicBase, 0xfee00900),
|
||||
];
|
||||
let seg_regs = [
|
||||
(
|
||||
SegReg::Cs,
|
||||
SegRegVal {
|
||||
selector: 0x10,
|
||||
base: 0,
|
||||
limit: 0xffff_ffff,
|
||||
access: SegAccess(0xa09b),
|
||||
},
|
||||
),
|
||||
(
|
||||
SegReg::Ds,
|
||||
SegRegVal {
|
||||
selector: 0x18,
|
||||
base: 0,
|
||||
limit: 0xffff_ffff,
|
||||
access: SegAccess(0xc093),
|
||||
},
|
||||
),
|
||||
(
|
||||
SegReg::Es,
|
||||
SegRegVal {
|
||||
selector: 0x18,
|
||||
base: 0,
|
||||
limit: 0xffff_ffff,
|
||||
access: SegAccess(0xc093),
|
||||
},
|
||||
),
|
||||
(
|
||||
SegReg::Fs,
|
||||
SegRegVal {
|
||||
selector: 0x18,
|
||||
base: 0,
|
||||
limit: 0xffff_ffff,
|
||||
access: SegAccess(0xc093),
|
||||
},
|
||||
),
|
||||
(
|
||||
SegReg::Gs,
|
||||
SegRegVal {
|
||||
selector: 0x18,
|
||||
base: 0,
|
||||
limit: 0xffff_ffff,
|
||||
access: SegAccess(0xc093),
|
||||
},
|
||||
),
|
||||
(
|
||||
SegReg::Ss,
|
||||
SegRegVal {
|
||||
selector: 0x18,
|
||||
base: 0,
|
||||
limit: 0xffff_ffff,
|
||||
access: SegAccess(0xc093),
|
||||
},
|
||||
),
|
||||
(
|
||||
SegReg::Tr,
|
||||
SegRegVal {
|
||||
selector: 0x20,
|
||||
base: 0,
|
||||
limit: 0xf_ffff,
|
||||
access: SegAccess(0x8b),
|
||||
},
|
||||
),
|
||||
(
|
||||
SegReg::Ldtr,
|
||||
SegRegVal {
|
||||
selector: 0x28,
|
||||
base: 0,
|
||||
limit: 0xf_ffff,
|
||||
access: SegAccess(0x82),
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
let dt_regs = [
|
||||
(
|
||||
DtReg::Gdtr,
|
||||
DtRegVal {
|
||||
base: 0xfffffe2a4aeeb000,
|
||||
limit: 0x7f,
|
||||
},
|
||||
),
|
||||
(
|
||||
DtReg::Idtr,
|
||||
DtRegVal {
|
||||
base: 0xfffffe0000000000,
|
||||
limit: 0xfff,
|
||||
},
|
||||
),
|
||||
];
|
||||
vcpu.set_sregs(&sregs, &seg_regs, &dt_regs).unwrap();
|
||||
|
||||
for (sreg, val) in sregs {
|
||||
assert_eq!(vcpu.get_sreg(sreg).unwrap(), val);
|
||||
}
|
||||
for (seg_reg, val) in seg_regs {
|
||||
assert_eq!(vcpu.get_seg_reg(seg_reg).unwrap(), val)
|
||||
}
|
||||
for (dt_reg, val) in dt_regs {
|
||||
assert_eq!(vcpu.get_dt_reg(dt_reg).unwrap(), val)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn test_kvm_run() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let mut vm = kvm.create_vm().unwrap();
|
||||
let memory = vm.create_vm_memory().unwrap();
|
||||
|
||||
let prot = PROT_WRITE | PROT_EXEC | PROT_READ;
|
||||
let flag = MAP_ANONYMOUS | MAP_SHARED;
|
||||
let user_mem = ffi!(
|
||||
unsafe { mmap(null_mut(), 0x4000, prot, flag, -1, 0,) },
|
||||
MAP_FAILED
|
||||
)
|
||||
.unwrap();
|
||||
let mmap_option = MemMapOption {
|
||||
read: true,
|
||||
write: true,
|
||||
exec: true,
|
||||
..Default::default()
|
||||
};
|
||||
memory
|
||||
.mem_map(0, 0, 0x4000, user_mem as usize, mmap_option)
|
||||
.unwrap();
|
||||
|
||||
// layout
|
||||
// 0x1000 - 0x1f00 code
|
||||
// 0x1f00 - 0x2000 GDT
|
||||
// 0x2000 - 0x3000 PML4
|
||||
// 0x3000 - 0x4000 PDPT
|
||||
|
||||
#[rustfmt::skip]
|
||||
const CODE: [u8; 29] = [
|
||||
// mov dx, 0x3f8
|
||||
0x66, 0xba, 0xf8, 0x03,
|
||||
// in al, dx
|
||||
0xec,
|
||||
// add eax, 0x1
|
||||
0x83, 0xc0, 0x01,
|
||||
// out dx, al
|
||||
0xee,
|
||||
// mov rax, [0x5000]
|
||||
0x48, 0x8b, 0x04, 0x25, 0x00, 0x50, 0x00,
|
||||
0x00,
|
||||
// add rax, 0x11
|
||||
0x48, 0x83, 0xc0, 0x11,
|
||||
// mov [0x5004], rax
|
||||
0x48, 0x89, 0x04, 0x25, 0x04, 0x50, 0x00,
|
||||
0x00,
|
||||
];
|
||||
unsafe { ((user_mem as usize + 0x1000) as *mut [u8; 29]).write(CODE) };
|
||||
|
||||
let pml4e = (Entry::P | Entry::RW).bits() as u64 | 0x3000;
|
||||
unsafe { ((user_mem as usize + 0x2000) as *mut u64).write(pml4e) }
|
||||
let ptpte = (Entry::P | Entry::RW | Entry::PS).bits() as u64;
|
||||
unsafe { ((user_mem as usize + 0x3000) as *mut u64).write(ptpte) }
|
||||
|
||||
let mut vcpu = vm.create_vcpu(0).unwrap();
|
||||
let cs = SegRegVal {
|
||||
selector: 0x10,
|
||||
base: 0,
|
||||
limit: 0xffff_ffff,
|
||||
access: SegAccess(0xa09b),
|
||||
};
|
||||
let ds = SegRegVal {
|
||||
selector: 0x18,
|
||||
base: 0,
|
||||
limit: 0xffff_ffff,
|
||||
access: SegAccess(0xc093),
|
||||
};
|
||||
let tr = SegRegVal {
|
||||
selector: 0x20,
|
||||
base: 0,
|
||||
limit: 0,
|
||||
access: SegAccess(0x8b),
|
||||
};
|
||||
let ldtr = SegRegVal {
|
||||
selector: 0x28,
|
||||
base: 0,
|
||||
limit: 0,
|
||||
access: SegAccess(0x82),
|
||||
};
|
||||
let gdt = [
|
||||
0,
|
||||
0,
|
||||
cs.to_desc(),
|
||||
ds.to_desc(),
|
||||
tr.to_desc(),
|
||||
ldtr.to_desc(),
|
||||
];
|
||||
assert!(size_of_val(&gdt) < 0x100);
|
||||
unsafe { ((user_mem as usize + 0x1f00) as *mut [u64; 6]).write(gdt) };
|
||||
let gdtr = DtRegVal {
|
||||
base: 0x1f00,
|
||||
limit: size_of_val(&gdt) as u16 - 1,
|
||||
};
|
||||
let idtr = DtRegVal { base: 0, limit: 0 };
|
||||
vcpu.set_sregs(
|
||||
&[
|
||||
(SReg::Efer, (Efer::LMA | Efer::LME).bits() as u64),
|
||||
(SReg::Cr0, (Cr0::NE | Cr0::PE | Cr0::PG).bits() as u64),
|
||||
(SReg::Cr3, 0x2000),
|
||||
(SReg::Cr4, Cr4::PAE.bits() as u64),
|
||||
],
|
||||
&[
|
||||
(SegReg::Cs, cs),
|
||||
(SegReg::Ds, ds),
|
||||
(SegReg::Es, ds),
|
||||
(SegReg::Fs, ds),
|
||||
(SegReg::Gs, ds),
|
||||
(SegReg::Ss, ds),
|
||||
(SegReg::Tr, tr),
|
||||
(SegReg::Ldtr, ldtr),
|
||||
],
|
||||
&[(DtReg::Gdtr, gdtr), (DtReg::Idtr, idtr)],
|
||||
)
|
||||
.unwrap();
|
||||
vcpu.set_regs(&[
|
||||
(Reg::Rip, 0x1000),
|
||||
(Reg::Rax, 0x2),
|
||||
(Reg::Rbx, 0x2),
|
||||
(Reg::Rdx, 0x3f8),
|
||||
(Reg::Rsi, 0x1000),
|
||||
(Reg::Rflags, 0x2),
|
||||
])
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
vcpu.run(VmEntry::None),
|
||||
Ok(VmExit::Io {
|
||||
port: 0x3f8,
|
||||
write: None,
|
||||
size: 1
|
||||
})
|
||||
);
|
||||
assert_matches!(
|
||||
vcpu.run(VmEntry::Io { data: 0x10 }),
|
||||
Ok(VmExit::Io {
|
||||
port: 0x3f8,
|
||||
write: Some(0x11),
|
||||
size: 1
|
||||
})
|
||||
);
|
||||
assert_matches!(
|
||||
vcpu.run(VmEntry::None),
|
||||
Ok(VmExit::Mmio {
|
||||
addr: 0x5000,
|
||||
write: None,
|
||||
size: 8
|
||||
})
|
||||
);
|
||||
assert_matches!(
|
||||
vcpu.run(VmEntry::Mmio { data: 0x0000_ffff }),
|
||||
Ok(VmExit::Mmio {
|
||||
addr: 0x5004,
|
||||
write: Some(0x0001_0010),
|
||||
size: 8
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
236
alioth/src/hv/kvm/vcpu/x86_64.rs
Normal file
236
alioth/src/hv/kvm/vcpu/x86_64.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::arch::reg::SegAccess;
|
||||
use crate::hv::arch::{Cpuid, DtReg, DtRegVal, Reg, SReg, SegReg, SegRegVal};
|
||||
use crate::hv::kvm::bindings::{
|
||||
KvmCpuid2, KvmCpuid2Flag, KvmCpuidEntry2, KvmRegs, KvmSregs2, KVM_MAX_CPUID_ENTRIES,
|
||||
};
|
||||
use crate::hv::kvm::ioctls::{
|
||||
kvm_get_regs, kvm_get_sregs2, kvm_set_cpuid2, kvm_set_regs, kvm_set_sregs2,
|
||||
};
|
||||
use crate::hv::kvm::vcpu::KvmVcpu;
|
||||
use crate::hv::{Error, Result};
|
||||
|
||||
impl KvmVcpu {
|
||||
fn get_kvm_sregs2(&self) -> Result<KvmSregs2> {
|
||||
let kvm_sregs2 = unsafe { kvm_get_sregs2(&self.fd) }?;
|
||||
Ok(kvm_sregs2)
|
||||
}
|
||||
|
||||
fn set_kvm_sregs2(&self, kvm_sregs2: &KvmSregs2) -> Result<()> {
|
||||
unsafe { kvm_set_sregs2(&self.fd, kvm_sregs2) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_kvm_regs(&self) -> Result<KvmRegs> {
|
||||
let kvm_regs = unsafe { kvm_get_regs(&self.fd) }?;
|
||||
Ok(kvm_regs)
|
||||
}
|
||||
|
||||
fn set_kvm_regs(&self, kvm_regs: &KvmRegs) -> Result<()> {
|
||||
unsafe { kvm_set_regs(&self.fd, kvm_regs) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn kvm_set_regs(&self, vals: &[(Reg, u64)]) -> Result<()> {
|
||||
let mut kvm_regs = self.get_kvm_regs()?;
|
||||
for (reg, val) in vals {
|
||||
match reg {
|
||||
Reg::Rax => kvm_regs.rax = *val,
|
||||
Reg::Rbx => kvm_regs.rbx = *val,
|
||||
Reg::Rcx => kvm_regs.rcx = *val,
|
||||
Reg::Rdx => kvm_regs.rdx = *val,
|
||||
Reg::Rsi => kvm_regs.rsi = *val,
|
||||
Reg::Rdi => kvm_regs.rdi = *val,
|
||||
Reg::Rsp => kvm_regs.rsp = *val,
|
||||
Reg::Rbp => kvm_regs.rbp = *val,
|
||||
Reg::R8 => kvm_regs.r8 = *val,
|
||||
Reg::R9 => kvm_regs.r9 = *val,
|
||||
Reg::R10 => kvm_regs.r10 = *val,
|
||||
Reg::R11 => kvm_regs.r11 = *val,
|
||||
Reg::R12 => kvm_regs.r12 = *val,
|
||||
Reg::R13 => kvm_regs.r13 = *val,
|
||||
Reg::R14 => kvm_regs.r14 = *val,
|
||||
Reg::R15 => kvm_regs.r15 = *val,
|
||||
Reg::Rip => kvm_regs.rip = *val,
|
||||
Reg::Rflags => kvm_regs.rflags = *val,
|
||||
}
|
||||
}
|
||||
self.set_kvm_regs(&kvm_regs)
|
||||
}
|
||||
|
||||
pub fn kvm_set_sregs(
|
||||
&mut self,
|
||||
sregs: &[(SReg, u64)],
|
||||
seg_regs: &[(SegReg, SegRegVal)],
|
||||
dt_regs: &[(DtReg, DtRegVal)],
|
||||
) -> Result<(), Error> {
|
||||
let mut kvm_sregs2 = self.get_kvm_sregs2()?;
|
||||
for (reg, val) in sregs {
|
||||
match reg {
|
||||
SReg::Cr0 => kvm_sregs2.cr0 = *val,
|
||||
SReg::Cr2 => kvm_sregs2.cr2 = *val,
|
||||
SReg::Cr3 => kvm_sregs2.cr3 = *val,
|
||||
SReg::Cr4 => kvm_sregs2.cr4 = *val,
|
||||
SReg::Cr8 => kvm_sregs2.cr8 = *val,
|
||||
SReg::Efer => kvm_sregs2.efer = *val,
|
||||
SReg::ApicBase => kvm_sregs2.apic_base = *val,
|
||||
}
|
||||
}
|
||||
for (reg, val) in dt_regs {
|
||||
let target = match reg {
|
||||
DtReg::Idtr => &mut kvm_sregs2.idt,
|
||||
DtReg::Gdtr => &mut kvm_sregs2.gdt,
|
||||
};
|
||||
target.limit = val.limit;
|
||||
target.base = val.base;
|
||||
}
|
||||
for (reg, val) in seg_regs {
|
||||
let target = match reg {
|
||||
SegReg::Cs => &mut kvm_sregs2.cs,
|
||||
SegReg::Ds => &mut kvm_sregs2.ds,
|
||||
SegReg::Es => &mut kvm_sregs2.es,
|
||||
SegReg::Fs => &mut kvm_sregs2.fs,
|
||||
SegReg::Gs => &mut kvm_sregs2.gs,
|
||||
SegReg::Ss => &mut kvm_sregs2.ss,
|
||||
SegReg::Tr => &mut kvm_sregs2.tr,
|
||||
SegReg::Ldtr => &mut kvm_sregs2.ldt,
|
||||
};
|
||||
target.selector = val.selector;
|
||||
target.base = val.base;
|
||||
target.limit = val.limit;
|
||||
target.type_ = val.access.seg_type() as u8;
|
||||
target.s = val.access.s_code_data() as u8;
|
||||
target.dpl = val.access.priv_level() as u8;
|
||||
target.present = val.access.present() as u8;
|
||||
target.avl = val.access.available() as u8;
|
||||
target.db = val.access.db_size_32() as u8;
|
||||
target.g = val.access.granularity() as u8;
|
||||
target.l = val.access.l_64bit() as u8;
|
||||
target.unusable = val.access.unusable() as u8;
|
||||
}
|
||||
self.set_kvm_sregs2(&kvm_sregs2)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn kvm_get_reg(&self, reg: Reg) -> Result<u64> {
|
||||
let kvm_regs = self.get_kvm_regs()?;
|
||||
let val = match reg {
|
||||
Reg::Rax => kvm_regs.rax,
|
||||
Reg::Rbx => kvm_regs.rbx,
|
||||
Reg::Rcx => kvm_regs.rcx,
|
||||
Reg::Rdx => kvm_regs.rdx,
|
||||
Reg::Rsi => kvm_regs.rsi,
|
||||
Reg::Rdi => kvm_regs.rdi,
|
||||
Reg::Rsp => kvm_regs.rsp,
|
||||
Reg::Rbp => kvm_regs.rbp,
|
||||
Reg::R8 => kvm_regs.r8,
|
||||
Reg::R9 => kvm_regs.r9,
|
||||
Reg::R10 => kvm_regs.r10,
|
||||
Reg::R11 => kvm_regs.r11,
|
||||
Reg::R12 => kvm_regs.r12,
|
||||
Reg::R13 => kvm_regs.r13,
|
||||
Reg::R14 => kvm_regs.r14,
|
||||
Reg::R15 => kvm_regs.r15,
|
||||
Reg::Rip => kvm_regs.rip,
|
||||
Reg::Rflags => kvm_regs.rflags,
|
||||
};
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
pub fn kvm_get_dt_reg(&self, reg: DtReg) -> Result<DtRegVal> {
|
||||
let kvm_sregs2 = self.get_kvm_sregs2()?;
|
||||
let target = match reg {
|
||||
DtReg::Idtr => &kvm_sregs2.idt,
|
||||
DtReg::Gdtr => &kvm_sregs2.gdt,
|
||||
};
|
||||
Ok(DtRegVal {
|
||||
limit: target.limit,
|
||||
base: target.base,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn kvm_get_seg_reg(&self, reg: SegReg) -> Result<SegRegVal> {
|
||||
let kvm_sregs2 = self.get_kvm_sregs2()?;
|
||||
let kvm_segment = match reg {
|
||||
SegReg::Cs => kvm_sregs2.cs,
|
||||
SegReg::Ds => kvm_sregs2.ds,
|
||||
SegReg::Es => kvm_sregs2.es,
|
||||
SegReg::Fs => kvm_sregs2.fs,
|
||||
SegReg::Gs => kvm_sregs2.gs,
|
||||
SegReg::Ss => kvm_sregs2.ss,
|
||||
SegReg::Tr => kvm_sregs2.tr,
|
||||
SegReg::Ldtr => kvm_sregs2.ldt,
|
||||
};
|
||||
let access = (kvm_segment.unusable as u32) << 16
|
||||
| (kvm_segment.g as u32) << 15
|
||||
| (kvm_segment.db as u32) << 14
|
||||
| (kvm_segment.l as u32) << 13
|
||||
| (kvm_segment.avl as u32) << 12
|
||||
| (kvm_segment.present as u32) << 7
|
||||
| (kvm_segment.dpl as u32) << 5
|
||||
| (kvm_segment.s as u32) << 4
|
||||
| (kvm_segment.type_ as u32);
|
||||
let val = SegRegVal {
|
||||
selector: kvm_segment.selector,
|
||||
base: kvm_segment.base,
|
||||
limit: kvm_segment.limit,
|
||||
access: SegAccess(access),
|
||||
};
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
pub fn kvm_get_sreg(&self, reg: SReg) -> Result<u64> {
|
||||
let kvm_sregs2 = self.get_kvm_sregs2()?;
|
||||
let val = match reg {
|
||||
SReg::Cr0 => kvm_sregs2.cr0,
|
||||
SReg::Cr2 => kvm_sregs2.cr2,
|
||||
SReg::Cr3 => kvm_sregs2.cr3,
|
||||
SReg::Cr4 => kvm_sregs2.cr4,
|
||||
SReg::Cr8 => kvm_sregs2.cr8,
|
||||
SReg::Efer => kvm_sregs2.efer,
|
||||
SReg::ApicBase => kvm_sregs2.apic_base,
|
||||
};
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
pub fn kvm_set_cpuids(&mut self, cpuids: Vec<Cpuid>) -> Result<(), Error> {
|
||||
if cpuids.len() > KVM_MAX_CPUID_ENTRIES {
|
||||
Err(Error::Unexpected {
|
||||
msg: format!("exeeds kvm cpuid entry limit: {}", KVM_MAX_CPUID_ENTRIES),
|
||||
})?
|
||||
}
|
||||
let mut kvm_cpuid2 = KvmCpuid2 {
|
||||
nent: cpuids.len() as u32,
|
||||
padding: 0,
|
||||
entries: [KvmCpuidEntry2::default(); KVM_MAX_CPUID_ENTRIES],
|
||||
};
|
||||
for (cpuid, entry) in std::iter::zip(cpuids, kvm_cpuid2.entries.iter_mut()) {
|
||||
entry.eax = cpuid.eax;
|
||||
entry.ebx = cpuid.ebx;
|
||||
entry.ecx = cpuid.ecx;
|
||||
entry.edx = cpuid.edx;
|
||||
entry.function = cpuid.func;
|
||||
if let Some(index) = cpuid.index {
|
||||
entry.index = index;
|
||||
entry.flags = KvmCpuid2Flag::SIGNIFCANT_INDEX;
|
||||
} else {
|
||||
entry.flags = KvmCpuid2Flag::empty();
|
||||
}
|
||||
}
|
||||
unsafe { kvm_set_cpuid2(&self.fd, &kvm_cpuid2) }?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
234
alioth/src/hv/kvm/vm.rs
Normal file
234
alioth/src/hv/kvm/vm.rs
Normal file
|
@ -0,0 +1,234 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
|
||||
use std::os::unix::thread::JoinHandleExt;
|
||||
use std::sync::Arc;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use libc::{eventfd, write, EFD_CLOEXEC, EFD_NONBLOCK, SIGRTMIN};
|
||||
|
||||
use crate::ffi;
|
||||
use crate::hv::{Error, IntxSender, MemMapOption, Vm, VmMemory};
|
||||
|
||||
use super::bindings::{
|
||||
KvmIrqfd, KvmMemFlag, KvmUserspaceMemoryRegion, KVM_CAP_IRQFD, KVM_CAP_NR_MEMSLOTS,
|
||||
};
|
||||
use super::ioctls::{kvm_check_extension, kvm_create_vcpu, kvm_irqfd, kvm_set_user_memory_region};
|
||||
use super::vcpu::{KvmRunBlock, KvmVcpu};
|
||||
|
||||
pub struct KvmVm {
|
||||
pub(super) fd: Arc<OwnedFd>,
|
||||
pub(super) vcpu_mmap_size: usize,
|
||||
pub(super) memory_created: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KvmMemory {
|
||||
pub(super) fd: Arc<OwnedFd>,
|
||||
}
|
||||
|
||||
impl VmMemory for KvmMemory {
|
||||
fn mem_map(
|
||||
&self,
|
||||
slot: u32,
|
||||
gpa: usize,
|
||||
size: usize,
|
||||
hva: usize,
|
||||
option: MemMapOption,
|
||||
) -> Result<(), Error> {
|
||||
let mut flags = KvmMemFlag::empty();
|
||||
if !option.read || !option.exec {
|
||||
return Err(Error::MemMapOption {
|
||||
option,
|
||||
hypervisor: "kvm",
|
||||
});
|
||||
}
|
||||
if !option.write {
|
||||
flags |= KvmMemFlag::READONLY;
|
||||
}
|
||||
if option.log_dirty {
|
||||
flags |= KvmMemFlag::LOG_DIRTY_PAGES;
|
||||
}
|
||||
let region = KvmUserspaceMemoryRegion {
|
||||
slot,
|
||||
guest_phys_addr: gpa as _,
|
||||
memory_size: size as _,
|
||||
userspace_addr: hva as _,
|
||||
flags,
|
||||
};
|
||||
unsafe { kvm_set_user_memory_region(&self.fd, ®ion) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmap(&self, slot: u32, gpa: usize, _size: usize) -> Result<(), Error> {
|
||||
let flags = KvmMemFlag::empty();
|
||||
let region = KvmUserspaceMemoryRegion {
|
||||
slot,
|
||||
guest_phys_addr: gpa as _,
|
||||
memory_size: 0,
|
||||
userspace_addr: 0,
|
||||
flags,
|
||||
};
|
||||
unsafe { kvm_set_user_memory_region(&self.fd, ®ion) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn max_mem_slots(&self) -> Result<u32, Error> {
|
||||
let ret = unsafe { kvm_check_extension(&self.fd, KVM_CAP_NR_MEMSLOTS) }?;
|
||||
Ok(ret as u32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KvmIntxSender {
|
||||
event_fd: OwnedFd,
|
||||
}
|
||||
|
||||
impl IntxSender for KvmIntxSender {
|
||||
fn send(&self) -> Result<(), Error> {
|
||||
ffi!(unsafe { write(self.event_fd.as_raw_fd(), &1u64 as *const _ as _, 8) })?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl KvmVm {
|
||||
fn check_extension(&self, id: u64) -> Result<bool, Error> {
|
||||
let ret = unsafe { kvm_check_extension(&self.fd, id) }?;
|
||||
Ok(ret == 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Vm for KvmVm {
|
||||
type Vcpu = KvmVcpu;
|
||||
type IntxSender = KvmIntxSender;
|
||||
type Memory = KvmMemory;
|
||||
|
||||
fn create_vcpu(&self, id: u32) -> Result<Self::Vcpu, Error> {
|
||||
let vcpu_fd = unsafe { kvm_create_vcpu(&self.fd, id) }?;
|
||||
let kvm_run = unsafe { KvmRunBlock::new(vcpu_fd, self.vcpu_mmap_size) }?;
|
||||
Ok(KvmVcpu {
|
||||
fd: unsafe { OwnedFd::from_raw_fd(vcpu_fd) },
|
||||
kvm_run,
|
||||
})
|
||||
}
|
||||
|
||||
fn stop_vcpu<T>(_id: u32, handle: &JoinHandle<T>) -> Result<(), Error> {
|
||||
ffi!(unsafe { libc::pthread_kill(handle.as_pthread_t(), SIGRTMIN()) })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_vm_memory(&mut self) -> Result<Self::Memory, Error> {
|
||||
if self.memory_created {
|
||||
Err(Error::CreatingMultipleMemory)
|
||||
} else {
|
||||
let kvm_memory = KvmMemory {
|
||||
fd: self.fd.clone(),
|
||||
};
|
||||
self.memory_created = true;
|
||||
Ok(kvm_memory)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_intx_sender(&self, pin: u8) -> Result<Self::IntxSender, Error> {
|
||||
if !self.check_extension(KVM_CAP_IRQFD)? {
|
||||
Err(Error::LackCap {
|
||||
cap: "KVM_CAP_IRQFD".to_string(),
|
||||
})?;
|
||||
}
|
||||
let event_fd = ffi!(unsafe { eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK) })?;
|
||||
let request = KvmIrqfd {
|
||||
fd: event_fd as u32,
|
||||
gsi: pin as u32,
|
||||
..Default::default()
|
||||
};
|
||||
unsafe { kvm_irqfd(&self.fd, &request) }?;
|
||||
Ok(KvmIntxSender {
|
||||
event_fd: unsafe { OwnedFd::from_raw_fd(event_fd) },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::assert_matches::assert_matches;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use libc::{mmap, MAP_ANONYMOUS, MAP_FAILED, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE};
|
||||
|
||||
use super::*;
|
||||
use crate::ffi;
|
||||
use crate::hv::{Hypervisor, Kvm, MemMapOption};
|
||||
|
||||
#[test]
|
||||
fn test_mem_map() {
|
||||
let kvm = Kvm::new().unwrap();
|
||||
let mut vm = kvm.create_vm().unwrap();
|
||||
let vm_memory = vm.create_vm_memory().unwrap();
|
||||
assert_matches!(vm_memory.max_mem_slots(), Ok(1..));
|
||||
let prot = PROT_WRITE | PROT_READ | PROT_EXEC;
|
||||
let flag = MAP_ANONYMOUS | MAP_PRIVATE;
|
||||
let user_mem = ffi!(
|
||||
unsafe { mmap(null_mut(), 0x1000, prot, flag, -1, 0,) },
|
||||
MAP_FAILED
|
||||
)
|
||||
.unwrap();
|
||||
let option_no_write = MemMapOption {
|
||||
read: false,
|
||||
write: true,
|
||||
exec: true,
|
||||
log_dirty: true,
|
||||
};
|
||||
assert_matches!(
|
||||
vm_memory.mem_map(0, 0x0, 0x1000, user_mem as usize, option_no_write),
|
||||
Err(Error::MemMapOption {
|
||||
option: MemMapOption {
|
||||
read: false,
|
||||
write: true,
|
||||
exec: true,
|
||||
log_dirty: true,
|
||||
},
|
||||
hypervisor: "kvm"
|
||||
})
|
||||
);
|
||||
let option_no_exec = MemMapOption {
|
||||
read: false,
|
||||
write: true,
|
||||
exec: true,
|
||||
log_dirty: true,
|
||||
};
|
||||
assert_matches!(
|
||||
vm_memory.mem_map(0, 0x0, 0x1000, user_mem as usize, option_no_exec),
|
||||
Err(Error::MemMapOption {
|
||||
option: MemMapOption {
|
||||
read: false,
|
||||
write: true,
|
||||
exec: true,
|
||||
log_dirty: true,
|
||||
},
|
||||
hypervisor: "kvm"
|
||||
})
|
||||
);
|
||||
let option = MemMapOption {
|
||||
read: true,
|
||||
write: false,
|
||||
exec: true,
|
||||
log_dirty: true,
|
||||
};
|
||||
vm_memory
|
||||
.mem_map(0, 0x0, 0x1000, user_mem as usize, option)
|
||||
.unwrap();
|
||||
vm_memory.mem_map(0, 0x0, 0, 0, option).unwrap();
|
||||
}
|
||||
}
|
51
alioth/src/hv/kvm/vmentry.rs
Normal file
51
alioth/src/hv/kvm/vmentry.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::hv::kvm::bindings::{KVM_EXIT_IO, KVM_EXIT_IO_IN, KVM_EXIT_MMIO};
|
||||
|
||||
use super::vcpu::KvmVcpu;
|
||||
|
||||
impl KvmVcpu {
|
||||
#[cfg(target_endian = "little")]
|
||||
pub(super) fn entry_mmio(&mut self, data: u64) {
|
||||
assert_eq!(self.kvm_run.exit_reason, KVM_EXIT_MMIO);
|
||||
let kvm_mmio = unsafe { &mut self.kvm_run.exit.mmio };
|
||||
assert_eq!(kvm_mmio.is_write, 0);
|
||||
kvm_mmio.data = data.to_ne_bytes();
|
||||
}
|
||||
|
||||
pub(super) fn immediate_exit(&mut self) {
|
||||
self.kvm_run.immediate_exit = 1;
|
||||
}
|
||||
|
||||
pub(super) fn entry_io(&mut self, data: u32) {
|
||||
assert_eq!(self.kvm_run.exit_reason, KVM_EXIT_IO);
|
||||
let kvm_io = unsafe { &self.kvm_run.exit.io };
|
||||
assert_eq!(kvm_io.direction, KVM_EXIT_IO_IN);
|
||||
let offset = kvm_io.data_offset as usize;
|
||||
let count = kvm_io.count as usize;
|
||||
match kvm_io.size {
|
||||
1 => unsafe {
|
||||
self.kvm_run.data_slice_mut(offset, count)[0] = data as u8;
|
||||
},
|
||||
2 => unsafe {
|
||||
self.kvm_run.data_slice_mut(offset, count)[0] = data as u16;
|
||||
},
|
||||
4 => unsafe {
|
||||
self.kvm_run.data_slice_mut(offset, count)[0] = data;
|
||||
},
|
||||
_ => unreachable!("kvm_io.size = {}", kvm_io.size),
|
||||
}
|
||||
}
|
||||
}
|
63
alioth/src/hv/kvm/vmexit.rs
Normal file
63
alioth/src/hv/kvm/vmexit.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::hv::kvm::bindings::{KVM_EXIT_IO_IN, KVM_EXIT_IO_OUT};
|
||||
use crate::hv::{Error, VmExit};
|
||||
|
||||
use super::vcpu::KvmVcpu;
|
||||
|
||||
impl KvmVcpu {
|
||||
#[cfg(target_endian = "little")]
|
||||
pub(super) fn handle_mmio(&mut self) -> Result<VmExit, Error> {
|
||||
let kvm_mmio = unsafe { &self.kvm_run.exit.mmio };
|
||||
let exit = VmExit::Mmio {
|
||||
addr: kvm_mmio.phys_addr as usize,
|
||||
write: if kvm_mmio.is_write > 0 {
|
||||
Some(u64::from_ne_bytes(kvm_mmio.data))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
size: kvm_mmio.len as u8,
|
||||
};
|
||||
Ok(exit)
|
||||
}
|
||||
|
||||
pub(super) fn handle_io(&mut self) -> Result<VmExit, Error> {
|
||||
let kvm_io = unsafe { &self.kvm_run.exit.io };
|
||||
let offset = kvm_io.data_offset as usize;
|
||||
let count = kvm_io.count as usize;
|
||||
assert_eq!(count, 1);
|
||||
let write = match (kvm_io.direction, kvm_io.size) {
|
||||
(KVM_EXIT_IO_IN, _) => None,
|
||||
(KVM_EXIT_IO_OUT, 1) => {
|
||||
Some(unsafe { self.kvm_run.data_slice::<u8>(offset, count) }[0] as u32)
|
||||
}
|
||||
(KVM_EXIT_IO_OUT, 2) => {
|
||||
Some(unsafe { self.kvm_run.data_slice::<u16>(offset, count) }[0] as u32)
|
||||
}
|
||||
(KVM_EXIT_IO_OUT, 4) => {
|
||||
Some(unsafe { self.kvm_run.data_slice::<u32>(offset, count) }[0])
|
||||
}
|
||||
_ => unreachable!(
|
||||
"kvm_io.direction = {}, kvm_io.size = {}",
|
||||
kvm_io.direction, kvm_io.size
|
||||
),
|
||||
};
|
||||
Ok(VmExit::Io {
|
||||
port: kvm_io.port,
|
||||
write,
|
||||
size: kvm_io.size,
|
||||
})
|
||||
}
|
||||
}
|
41
alioth/src/hv/test.rs
Normal file
41
alioth/src/hv/test.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::{Error, MemMapOption, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FakeVmMemory;
|
||||
|
||||
impl crate::hv::VmMemory for FakeVmMemory {
|
||||
fn mem_map(
|
||||
&self,
|
||||
_slot: u32,
|
||||
_gpa: usize,
|
||||
_size: usize,
|
||||
_hva: usize,
|
||||
_option: MemMapOption,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmap(&self, _slot: u32, _gpa: usize, _size: usize) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn max_mem_slots(&self) -> Result<u32> {
|
||||
Err(Error::LackCap {
|
||||
cap: "MaxMemSlots".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
27
alioth/src/lib.rs
Normal file
27
alioth/src/lib.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![feature(assert_matches)]
|
||||
#![feature(error_generic_member_access)]
|
||||
#![feature(pointer_is_aligned)]
|
||||
|
||||
pub mod acpi;
|
||||
pub mod action;
|
||||
pub mod arch;
|
||||
pub mod device;
|
||||
pub mod hv;
|
||||
pub mod loader;
|
||||
pub mod mem;
|
||||
pub(crate) mod utils;
|
||||
pub mod vm;
|
104
alioth/src/loader.rs
Normal file
104
alioth/src/loader.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::hv::arch::Reg;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use crate::hv::arch::{DtReg, DtRegVal, SReg, SegReg, SegRegVal};
|
||||
use crate::mem::{MemRegion, MemRegionType};
|
||||
|
||||
pub mod linux;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct InitState {
|
||||
pub regs: Vec<(Reg, u64)>,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub sregs: Vec<(SReg, u64)>,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub dt_regs: Vec<(DtReg, DtRegVal)>,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub seg_regs: Vec<(SegReg, SegRegVal)>,
|
||||
pub initramfs: Option<Range<usize>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("io: {0}")]
|
||||
Io(
|
||||
#[backtrace]
|
||||
#[from]
|
||||
std::io::Error,
|
||||
),
|
||||
|
||||
#[error("mem: {0}")]
|
||||
Mem(
|
||||
#[backtrace]
|
||||
#[from]
|
||||
crate::mem::Error,
|
||||
),
|
||||
|
||||
#[error("msssing magic number {magic:#x}, found {found:#x}")]
|
||||
MissingMagic { magic: u64, found: u64 },
|
||||
|
||||
#[error("cannot find entry point")]
|
||||
NoEntryPoint,
|
||||
|
||||
#[error("not a 64bit kernel")]
|
||||
Not64BitKernel,
|
||||
|
||||
#[error("not a relocatable kernel")]
|
||||
NotRelocatableKernel,
|
||||
|
||||
#[error("kernel command line too long, length: {0}, limit: {1}")]
|
||||
CmdLineTooLong(usize, usize),
|
||||
|
||||
#[error("cannot load initramfs at {addr:#x} - {max:#x}, initramfs max address: {addr_max:#x}")]
|
||||
InitramfsAddrLimit {
|
||||
addr: usize,
|
||||
max: usize,
|
||||
addr_max: usize,
|
||||
},
|
||||
|
||||
#[error("cannot find a memory region to load initramfs")]
|
||||
CannotLoadInitramfs,
|
||||
|
||||
#[error("{name} too old, minimum supported version {min:#x}, found version {found:#x}")]
|
||||
TooOld {
|
||||
name: &'static str,
|
||||
min: u64,
|
||||
found: u64,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn search_initramfs_address(
|
||||
mem_regions: &[(usize, MemRegion)],
|
||||
size: usize,
|
||||
addr_max: usize,
|
||||
) -> Result<usize, Error> {
|
||||
for (start, region) in mem_regions.iter().rev() {
|
||||
let region_max = region.size - 1 + start;
|
||||
let limit = std::cmp::min(region_max, addr_max);
|
||||
if limit < size - 1 {
|
||||
continue;
|
||||
}
|
||||
let load_addr = (limit - (size - 1)) & !0xfff;
|
||||
if region.type_ == MemRegionType::Ram && load_addr >= *start {
|
||||
return Ok(load_addr);
|
||||
}
|
||||
}
|
||||
Err(Error::CannotLoadInitramfs)
|
||||
}
|
21
alioth/src/loader/linux.rs
Normal file
21
alioth/src/loader/linux.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod bootparams;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod x86_64;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub use x86_64::load;
|
145
alioth/src/loader/linux/bootparams.rs
Normal file
145
alioth/src/loader/linux/bootparams.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use bitflags::bitflags;
|
||||
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
|
||||
pub const MAGIC_AA55: u16 = 0xaa55;
|
||||
pub const MAGIC_HDRS: u32 = 0x53726448; // "HdrS"
|
||||
pub const SETUP_HEADER_OFFSET: u64 = 0x01f1;
|
||||
|
||||
bitflags! {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
|
||||
pub struct LoadFlags: u8 {
|
||||
const LOADED_HIGH = (1<<0);
|
||||
const KASLR_FLAG = (1<<1);
|
||||
const QUIET_FLAG = (1<<5);
|
||||
const KEEP_SEGMENTS = (1<<6);
|
||||
const CAN_USE_HEAP = (1<<7);
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
|
||||
pub struct XLoadFlags: u16 {
|
||||
const XLF_KERNEL_64 = (1<<0);
|
||||
const XLF_CAN_BE_LOADED_ABOVE_4G = (1<<1);
|
||||
const XLF_EFI_HANDOVER_32 = (1<<2);
|
||||
const XLF_EFI_HANDOVER_64 = (1<<3);
|
||||
const XLF_EFI_KEXEC = (1<<4);
|
||||
const XLF_5LEVEL = (1<<5);
|
||||
const XLF_5LEVEL_ENABLED = (1<<6);
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct SetupHeader {
|
||||
pub setup_sects: u8,
|
||||
pub root_flags: u16,
|
||||
pub syssize: u32,
|
||||
pub ram_size: u16,
|
||||
pub vid_mode: u16,
|
||||
pub root_dev: u16,
|
||||
pub boot_flag: u16,
|
||||
pub jump: u16,
|
||||
pub header: u32,
|
||||
pub version: u16,
|
||||
pub realmode_swtch: u32,
|
||||
pub start_sys_seg: u16,
|
||||
pub kernel_version: u16,
|
||||
pub type_of_loader: u8,
|
||||
pub loadflags: u8,
|
||||
pub setup_move_size: u16,
|
||||
pub code32_start: u32,
|
||||
pub ramdisk_image: u32,
|
||||
pub ramdisk_size: u32,
|
||||
pub bootsect_kludge: u32,
|
||||
pub heap_end_ptr: u16,
|
||||
pub ext_loader_ver: u8,
|
||||
pub ext_loader_type: u8,
|
||||
pub cmd_line_ptr: u32,
|
||||
pub initrd_addr_max: u32,
|
||||
pub kernel_alignment: u32,
|
||||
pub relocatable_kernel: u8,
|
||||
pub min_alignment: u8,
|
||||
pub xloadflags: u16,
|
||||
pub cmdline_size: u32,
|
||||
pub hardware_subarch: u32,
|
||||
pub hardware_subarch_data: u64,
|
||||
pub payload_offset: u32,
|
||||
pub payload_length: u32,
|
||||
pub setup_data: u64,
|
||||
pub pref_address: u64,
|
||||
pub init_size: u32,
|
||||
pub handover_offset: u32,
|
||||
pub kernel_info_offset: u32,
|
||||
}
|
||||
|
||||
pub const E820_RAM: u32 = 1;
|
||||
pub const E820_RESERVED: u32 = 2;
|
||||
pub const E820_ACPI: u32 = 3;
|
||||
pub const E820_NVS: u32 = 4;
|
||||
pub const E820_UNUSABLE: u32 = 5;
|
||||
pub const E820_PMEM: u32 = 7;
|
||||
pub const E820_RESERVED_KERN: u32 = 128;
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct BootE820Entry {
|
||||
pub addr: u64,
|
||||
pub size: u64,
|
||||
pub type_: u32,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes)]
|
||||
pub struct BootParams {
|
||||
pub screen_info: [u8; 64],
|
||||
pub apm_bios_info: [u8; 20],
|
||||
pub _pad2: [u8; 4usize],
|
||||
pub tboot_addr: u64,
|
||||
pub ist_info: [u8; 16],
|
||||
pub acpi_rsdp_addr: u64,
|
||||
pub _pad3: [u8; 8usize],
|
||||
pub hd0_info: [u8; 16usize],
|
||||
pub hd1_info: [u8; 16usize],
|
||||
pub sys_desc_table: [u8; 16],
|
||||
pub olpc_ofw_header: [u8; 16],
|
||||
pub ext_ramdisk_image: u32,
|
||||
pub ext_ramdisk_size: u32,
|
||||
pub ext_cmd_line_ptr: u32,
|
||||
pub _pad4: [u8; 112usize],
|
||||
pub cc_blob_address: u32,
|
||||
pub edid_info: [u8; 128],
|
||||
pub efi_info: [u8; 32],
|
||||
pub alt_mem_k: u32,
|
||||
pub scratch: u32,
|
||||
pub e820_entries: u8,
|
||||
pub eddbuf_entries: u8,
|
||||
pub edd_mbr_sig_buf_entries: u8,
|
||||
pub kbd_status: u8,
|
||||
pub secure_boot: u8,
|
||||
pub _pad5: [u8; 2usize],
|
||||
pub sentinel: u8,
|
||||
pub _pad6: [u8; 1usize],
|
||||
pub hdr: SetupHeader,
|
||||
pub _pad7: [u8; 36usize],
|
||||
pub edd_mbr_sig_buffer: [u32; 16usize],
|
||||
pub e820_table: [BootE820Entry; 128usize],
|
||||
pub _pad8: [u8; 48usize],
|
||||
pub eddbuf: [u8; 492usize],
|
||||
pub _pad9: [u8; 276usize],
|
||||
}
|
240
alioth/src/loader/linux/x86_64.rs
Normal file
240
alioth/src/loader/linux/x86_64.rs
Normal file
|
@ -0,0 +1,240 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read, Seek, SeekFrom};
|
||||
use std::mem::{size_of, size_of_val};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::arch::msr::Efer;
|
||||
use crate::arch::paging::Entry;
|
||||
use crate::arch::reg::{Cr0, Cr4, Rflags, SegAccess};
|
||||
use crate::hv::arch::{DtReg, DtRegVal, Reg, SReg, SegReg, SegRegVal};
|
||||
use crate::mem::ram::RamBus;
|
||||
use zerocopy::{AsBytes, FromZeroes};
|
||||
|
||||
use crate::arch::layout::{
|
||||
BOOT_GDT_START, BOOT_PAGING_START, EBDA_START, KERNEL_CMD_LINE_LIMIT, KERNEL_CMD_LINE_START,
|
||||
KERNEL_IMAGE_START, LINUX_BOOT_PARAMS_START,
|
||||
};
|
||||
use crate::mem::{MemRegion, MemRegionType};
|
||||
|
||||
use crate::loader::linux::bootparams::{
|
||||
BootE820Entry, BootParams, XLoadFlags, E820_ACPI, E820_PMEM, E820_RAM, E820_RESERVED,
|
||||
MAGIC_AA55, MAGIC_HDRS, SETUP_HEADER_OFFSET,
|
||||
};
|
||||
use crate::loader::{search_initramfs_address, Error, InitState};
|
||||
|
||||
// loading bzImage and ramdisk above 4G in 64bit.
|
||||
const MINIMAL_VERSION: u16 = 0x020c;
|
||||
|
||||
pub fn load<P: AsRef<Path>>(
|
||||
memory: &RamBus,
|
||||
mem_regions: &[(usize, MemRegion)],
|
||||
kernel: P,
|
||||
cmd_line: Option<&str>,
|
||||
initramfs: Option<P>,
|
||||
) -> Result<InitState, Error> {
|
||||
let mut boot_params = BootParams::new_zeroed();
|
||||
let kernel_file = File::open(kernel)?;
|
||||
let kernel_meta = kernel_file.metadata()?;
|
||||
let mut kernel = BufReader::new(kernel_file);
|
||||
kernel.seek(SeekFrom::Start(SETUP_HEADER_OFFSET))?;
|
||||
kernel.read_exact(boot_params.hdr.as_bytes_mut())?;
|
||||
|
||||
// For backwards compatibility, if the setup_sects field contains 0,
|
||||
// the real value is 4.
|
||||
if boot_params.hdr.setup_sects == 0 {
|
||||
boot_params.hdr.setup_sects = 4;
|
||||
}
|
||||
|
||||
if boot_params.hdr.boot_flag != MAGIC_AA55 {
|
||||
return Err(Error::MissingMagic {
|
||||
magic: MAGIC_AA55 as u64,
|
||||
found: boot_params.hdr.boot_flag as u64,
|
||||
});
|
||||
}
|
||||
if boot_params.hdr.header != MAGIC_HDRS {
|
||||
return Err(Error::MissingMagic {
|
||||
magic: MAGIC_HDRS as u64,
|
||||
found: boot_params.hdr.header as u64,
|
||||
});
|
||||
}
|
||||
if boot_params.hdr.version < MINIMAL_VERSION {
|
||||
return Err(Error::TooOld {
|
||||
name: "bzimage",
|
||||
min: MINIMAL_VERSION as u64,
|
||||
found: boot_params.hdr.version as u64,
|
||||
});
|
||||
}
|
||||
if !XLoadFlags::from_bits_retain(boot_params.hdr.xloadflags).contains(XLoadFlags::XLF_KERNEL_64)
|
||||
{
|
||||
return Err(Error::Not64BitKernel);
|
||||
}
|
||||
if boot_params.hdr.relocatable_kernel == 0 {
|
||||
return Err(Error::NotRelocatableKernel);
|
||||
}
|
||||
|
||||
boot_params.hdr.type_of_loader = 0xff;
|
||||
|
||||
// load cmd line
|
||||
if let Some(cmd_line) = cmd_line {
|
||||
let cmd_line_limit =
|
||||
std::cmp::min(boot_params.hdr.cmdline_size as usize, KERNEL_CMD_LINE_LIMIT);
|
||||
if cmd_line.len() > cmd_line_limit {
|
||||
return Err(Error::CmdLineTooLong(cmd_line.len(), cmd_line_limit));
|
||||
}
|
||||
memory.write_range(KERNEL_CMD_LINE_START, cmd_line.len(), cmd_line.as_bytes())?;
|
||||
boot_params.hdr.cmd_line_ptr = KERNEL_CMD_LINE_START as u32;
|
||||
boot_params.ext_cmd_line_ptr = (KERNEL_CMD_LINE_START >> 32) as u32;
|
||||
}
|
||||
|
||||
// load kernel image
|
||||
let kernel_offset = (boot_params.hdr.setup_sects as u64 + 1) * 512;
|
||||
kernel.seek(SeekFrom::Start(kernel_offset))?;
|
||||
let kernel_size = (kernel_meta.len() - kernel_offset) as usize;
|
||||
memory.write_range(KERNEL_IMAGE_START, kernel_size, kernel)?;
|
||||
|
||||
// load initramfs
|
||||
let initramfs_range;
|
||||
if let Some(initramfs) = initramfs {
|
||||
let initramfs = File::open(initramfs)?;
|
||||
let initramfs_size = initramfs.metadata()?.len() as usize;
|
||||
let initramfs_gpa = search_initramfs_address(
|
||||
mem_regions,
|
||||
initramfs_size,
|
||||
boot_params.hdr.initrd_addr_max as usize,
|
||||
)?;
|
||||
let initramfs_end = initramfs_gpa + initramfs_size;
|
||||
memory.write_range(initramfs_gpa, initramfs_size, initramfs)?;
|
||||
boot_params.hdr.ramdisk_image = initramfs_gpa as u32;
|
||||
boot_params.ext_ramdisk_image = (initramfs_gpa >> 32) as u32;
|
||||
boot_params.hdr.ramdisk_size = initramfs_size as u32;
|
||||
boot_params.ext_ramdisk_size = (initramfs_size >> 32) as u32;
|
||||
log::info!(
|
||||
"initramfs loaded at {:#x} - {:#x}, ",
|
||||
initramfs_gpa,
|
||||
initramfs_end - 1,
|
||||
);
|
||||
initramfs_range = Some(initramfs_gpa..initramfs_end);
|
||||
} else {
|
||||
initramfs_range = None;
|
||||
}
|
||||
|
||||
// setup e820 table
|
||||
for (index, (addr, region)) in mem_regions.iter().enumerate() {
|
||||
let type_ = match region.type_ {
|
||||
MemRegionType::Ram => E820_RAM,
|
||||
MemRegionType::Reserved => E820_RESERVED,
|
||||
MemRegionType::Acpi => E820_ACPI,
|
||||
MemRegionType::Pmem => E820_PMEM,
|
||||
};
|
||||
boot_params.e820_table[index] = BootE820Entry {
|
||||
addr: *addr as u64,
|
||||
size: region.size as u64,
|
||||
type_,
|
||||
};
|
||||
}
|
||||
boot_params.e820_entries = mem_regions.len() as u8;
|
||||
|
||||
boot_params.acpi_rsdp_addr = EBDA_START as u64;
|
||||
|
||||
memory.write(LINUX_BOOT_PARAMS_START, &boot_params)?;
|
||||
|
||||
// set up identity paging
|
||||
let pml4_start = BOOT_PAGING_START;
|
||||
let pdpt_start = pml4_start + 0x1000;
|
||||
let pml4e = (Entry::P | Entry::RW).bits() as u64 | pdpt_start as u64;
|
||||
memory.write(pml4_start, &pml4e)?;
|
||||
let alignment = boot_params.hdr.kernel_alignment as usize;
|
||||
let runtime_start = (KERNEL_IMAGE_START + alignment - 1) & !(alignment - 1);
|
||||
let max_addr = std::cmp::max(
|
||||
runtime_start + boot_params.hdr.init_size as usize,
|
||||
std::cmp::max(
|
||||
LINUX_BOOT_PARAMS_START + size_of::<BootParams>(),
|
||||
KERNEL_CMD_LINE_START + KERNEL_CMD_LINE_LIMIT,
|
||||
),
|
||||
);
|
||||
let num_page = (max_addr as u64 + (1 << 30) - 1) >> 30;
|
||||
for i in 0..num_page {
|
||||
let pdpte = (i << 30) | (Entry::P | Entry::RW | Entry::PS).bits() as u64;
|
||||
memory.write(pdpt_start + i as usize * size_of::<u64>(), &pdpte)?;
|
||||
}
|
||||
|
||||
// set up gdt
|
||||
let boot_cs = SegRegVal {
|
||||
selector: 0x10,
|
||||
base: 0,
|
||||
limit: 0xfff_ffff,
|
||||
access: SegAccess(0xa09b),
|
||||
};
|
||||
let boot_ds = SegRegVal {
|
||||
selector: 0x18,
|
||||
base: 0,
|
||||
limit: 0xfff_ffff,
|
||||
access: SegAccess(0xc093),
|
||||
};
|
||||
let boot_tr = SegRegVal {
|
||||
selector: 0x20,
|
||||
base: 0,
|
||||
limit: 0,
|
||||
access: SegAccess(0x8b),
|
||||
};
|
||||
let boot_ldtr = SegRegVal {
|
||||
selector: 0x28,
|
||||
base: 0,
|
||||
limit: 0,
|
||||
access: SegAccess(0x82),
|
||||
};
|
||||
let gdt = [
|
||||
0,
|
||||
0,
|
||||
boot_cs.to_desc(),
|
||||
boot_ds.to_desc(),
|
||||
boot_tr.to_desc(),
|
||||
boot_ldtr.to_desc(),
|
||||
];
|
||||
let gdtr = DtRegVal {
|
||||
base: BOOT_GDT_START as u64,
|
||||
limit: size_of_val(&gdt) as u16 - 1,
|
||||
};
|
||||
let idtr = DtRegVal { base: 0, limit: 0 };
|
||||
memory.write(BOOT_GDT_START, &gdt)?;
|
||||
|
||||
Ok(InitState {
|
||||
regs: vec![
|
||||
(Reg::Rsi, LINUX_BOOT_PARAMS_START as u64),
|
||||
(Reg::Rip, KERNEL_IMAGE_START as u64 + 0x200),
|
||||
(Reg::Rflags, Rflags::RESERVED_1.bits() as u64),
|
||||
],
|
||||
sregs: vec![
|
||||
(SReg::Efer, (Efer::LMA | Efer::LME).bits() as u64),
|
||||
(SReg::Cr0, (Cr0::NE | Cr0::PE | Cr0::PG).bits() as u64),
|
||||
(SReg::Cr3, pml4_start as u64),
|
||||
(SReg::Cr4, Cr4::PAE.bits() as u64),
|
||||
],
|
||||
seg_regs: vec![
|
||||
(SegReg::Cs, boot_cs),
|
||||
(SegReg::Ds, boot_ds),
|
||||
(SegReg::Es, boot_ds),
|
||||
(SegReg::Fs, boot_ds),
|
||||
(SegReg::Gs, boot_ds),
|
||||
(SegReg::Ss, boot_ds),
|
||||
(SegReg::Tr, boot_tr),
|
||||
(SegReg::Ldtr, boot_ldtr),
|
||||
],
|
||||
dt_regs: vec![(DtReg::Gdtr, gdtr), (DtReg::Idtr, idtr)],
|
||||
initramfs: initramfs_range,
|
||||
})
|
||||
}
|
347
alioth/src/mem.rs
Normal file
347
alioth/src/mem.rs
Normal file
|
@ -0,0 +1,347 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod addressable;
|
||||
pub mod io;
|
||||
pub mod mmio;
|
||||
pub mod ram;
|
||||
|
||||
use std::backtrace::Backtrace;
|
||||
use std::sync::{Arc, Mutex, PoisonError};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::action::Action;
|
||||
use crate::align_up;
|
||||
use crate::hv::{self, VmEntry, VmMemory};
|
||||
use ram::UserMem;
|
||||
|
||||
use addressable::{Addressable, SlotBackend};
|
||||
use io::IoBus;
|
||||
use mmio::{Mmio, MmioBus};
|
||||
use ram::RamBus;
|
||||
|
||||
use self::io::IoDev;
|
||||
|
||||
use crate::arch::layout::{
|
||||
MEM_64_START, MMIO_32_START, PCIE_CONFIG_END, PCIE_CONFIG_START, RAM_32_END,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("[{new_addr:#x}, {new_end:#x}) overlaps with [{curr_addr:#x}, {curr_end:#x})")]
|
||||
Overlap {
|
||||
new_addr: usize,
|
||||
new_end: usize,
|
||||
curr_addr: usize,
|
||||
curr_end: usize,
|
||||
},
|
||||
#[error("(addr={addr:#x}, size={size:#x}) is out of range")]
|
||||
OutOfRange { addr: usize, size: usize },
|
||||
#[error("io: {source:#x?}")]
|
||||
Io {
|
||||
#[from]
|
||||
source: std::io::Error,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
#[error("mmap: {0}")]
|
||||
Mmap(#[source] std::io::Error),
|
||||
#[error("offset {offset:#x} exceeds limit {limit:#x}")]
|
||||
ExceedLimit { offset: usize, limit: usize },
|
||||
#[error("{0:#x} is not mapped")]
|
||||
NotMapped(usize),
|
||||
#[error("zero memory size")]
|
||||
ZeroMemorySize,
|
||||
#[error("lock poisoned")]
|
||||
LockPoisoned,
|
||||
#[error("cannot allocate")]
|
||||
CanotAllocate { backtrace: Backtrace },
|
||||
#[error("cannot register MMIO notifier: {0}")]
|
||||
Notifier(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error("{0}")]
|
||||
Hv(
|
||||
#[from]
|
||||
#[backtrace]
|
||||
hv::Error,
|
||||
),
|
||||
#[error("cannot handle action: {0:x?}")]
|
||||
Action(Action),
|
||||
#[error("not aligned")]
|
||||
NotAligned,
|
||||
#[error("not backed by continuous host memory")]
|
||||
NotContinuous,
|
||||
}
|
||||
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
impl<T> From<PoisonError<T>> for Error {
|
||||
fn from(_: PoisonError<T>) -> Self {
|
||||
Error::LockPoisoned
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Allocator {
|
||||
ram32: Addressable<MemRegion>,
|
||||
dev32: Addressable<MemRegion>,
|
||||
mem64: Addressable<MemRegion>,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
io: Addressable<MemRegion>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Memory {
|
||||
ram_bus: Arc<RamBus>,
|
||||
mmio_bus: MmioBus,
|
||||
io_bus: IoBus,
|
||||
// TODO do we need a global lock?
|
||||
allocator: Mutex<Allocator>,
|
||||
}
|
||||
|
||||
pub enum AddrOpt {
|
||||
Any,
|
||||
Fixed(usize),
|
||||
Below4G,
|
||||
Above4G,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DevMem {
|
||||
UserMem(UserMem),
|
||||
Mmio(Arc<dyn Mmio + Send + Sync>),
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
pub fn new<M>(vm_memory: M) -> Self
|
||||
where
|
||||
M: VmMemory,
|
||||
{
|
||||
Memory {
|
||||
ram_bus: Arc::new(RamBus::new(vm_memory)),
|
||||
mmio_bus: MmioBus::new(),
|
||||
allocator: Mutex::new(Allocator::default()),
|
||||
io_bus: IoBus::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ram_bus(&self) -> &Arc<RamBus> {
|
||||
&self.ram_bus
|
||||
}
|
||||
|
||||
pub fn to_mem_regions(&self) -> Result<Vec<(usize, MemRegion)>, Error> {
|
||||
let mut regions = Vec::new();
|
||||
let allocator = self.allocator.lock()?;
|
||||
for (addr, region) in allocator.ram32.iter() {
|
||||
regions.push((addr, *region));
|
||||
}
|
||||
for (addr, region) in allocator.dev32.iter() {
|
||||
regions.push((addr, *region));
|
||||
}
|
||||
for (addr, region) in allocator.mem64.iter() {
|
||||
regions.push((addr, *region));
|
||||
}
|
||||
Ok(regions)
|
||||
}
|
||||
|
||||
fn alloc_sub(
|
||||
size: usize,
|
||||
segment: &mut Addressable<MemRegion>,
|
||||
segment_start: usize,
|
||||
segment_end: usize,
|
||||
regions: &[(usize, MemRegionType)],
|
||||
) -> Result<usize, Error> {
|
||||
// let rounded_size = usize::next_power_of_two(usize::max(size, PAGE_SIZE));
|
||||
let rounded_size = size;
|
||||
let start = if let Some((start, region)) = segment.last() {
|
||||
start + region.size
|
||||
} else {
|
||||
segment_start
|
||||
};
|
||||
let start = std::cmp::max(start, segment_start);
|
||||
let aligned_start = align_up!(start, rounded_size);
|
||||
log::info!(
|
||||
"aligned start = {:#x}, segment_end = {:#x}",
|
||||
aligned_start,
|
||||
segment_end
|
||||
);
|
||||
if aligned_start + rounded_size <= segment_end {
|
||||
let mut addr = aligned_start;
|
||||
for (size, type_) in regions.iter() {
|
||||
let region = MemRegion {
|
||||
size: *size,
|
||||
type_: *type_,
|
||||
};
|
||||
segment.add(addr, region)?;
|
||||
addr += size;
|
||||
}
|
||||
Ok(aligned_start)
|
||||
} else {
|
||||
Err(Error::CanotAllocate {
|
||||
backtrace: Backtrace::force_capture(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_ram(
|
||||
&self,
|
||||
gpa: AddrOpt,
|
||||
user_mem: UserMem,
|
||||
regions: &[(usize, MemRegionType)],
|
||||
) -> Result<usize, Error> {
|
||||
let addr = self.alloc(gpa, user_mem.size(), false, regions)?;
|
||||
self.ram_bus.add(addr, user_mem)?;
|
||||
Ok(addr)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn add_io_dev(&self, port: Option<u16>, dev: IoDev) -> Result<u16, Error> {
|
||||
let mut allocator = self.allocator.lock()?;
|
||||
let port = match port {
|
||||
Some(port) => {
|
||||
allocator.io.add(
|
||||
port as usize,
|
||||
MemRegion {
|
||||
size: dev.size(),
|
||||
type_: MemRegionType::Reserved,
|
||||
},
|
||||
)?;
|
||||
port
|
||||
}
|
||||
None => {
|
||||
let port = Self::alloc_sub(
|
||||
dev.size(),
|
||||
&mut allocator.io,
|
||||
0x1000,
|
||||
0xffff,
|
||||
&[(dev.size(), MemRegionType::Reserved)],
|
||||
)?;
|
||||
port as u16
|
||||
}
|
||||
};
|
||||
self.io_bus.add(port, dev)?;
|
||||
Ok(port)
|
||||
}
|
||||
|
||||
fn alloc(
|
||||
&self,
|
||||
gpa: AddrOpt,
|
||||
size: usize,
|
||||
is_dev: bool,
|
||||
regions: &[(usize, MemRegionType)],
|
||||
) -> Result<usize, Error> {
|
||||
let mut allocator = self.allocator.lock()?;
|
||||
let addr_start = match gpa {
|
||||
AddrOpt::Fixed(gpa) => {
|
||||
let below_4g = gpa + size <= u32::MAX as usize;
|
||||
let mut region_gpa = gpa;
|
||||
for (size, type_) in regions.iter() {
|
||||
let region = MemRegion {
|
||||
size: *size,
|
||||
type_: *type_,
|
||||
};
|
||||
if below_4g {
|
||||
if is_dev {
|
||||
allocator.dev32.add(region_gpa, region)?;
|
||||
} else {
|
||||
allocator.ram32.add(region_gpa, region)?;
|
||||
}
|
||||
} else {
|
||||
allocator.mem64.add(region_gpa, region)?;
|
||||
}
|
||||
region_gpa += size;
|
||||
}
|
||||
Ok(gpa)
|
||||
}
|
||||
AddrOpt::Above4G | AddrOpt::Any => Self::alloc_sub(
|
||||
size,
|
||||
&mut allocator.mem64,
|
||||
MEM_64_START,
|
||||
usize::MAX,
|
||||
regions,
|
||||
),
|
||||
AddrOpt::Below4G => {
|
||||
if is_dev {
|
||||
Self::alloc_sub(
|
||||
size,
|
||||
&mut allocator.dev32,
|
||||
MMIO_32_START,
|
||||
PCIE_CONFIG_END,
|
||||
regions,
|
||||
)
|
||||
} else {
|
||||
Self::alloc_sub(size, &mut allocator.ram32, 0, RAM_32_END, regions)
|
||||
}
|
||||
}
|
||||
}?;
|
||||
Ok(addr_start)
|
||||
}
|
||||
|
||||
fn handle_action(&self, action: Action) -> Result<VmEntry> {
|
||||
match action {
|
||||
Action::Shutdown => Ok(VmEntry::Shutdown),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_mmio(&self, gpa: usize, write: Option<u64>, size: u8) -> Result<VmEntry> {
|
||||
if let Some(val) = write {
|
||||
match self.mmio_bus.write(gpa, size, val) {
|
||||
Ok(()) => Ok(VmEntry::None),
|
||||
Err(Error::Action(action)) => self.handle_action(action),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
let data = self.mmio_bus.read(gpa, size)?;
|
||||
Ok(VmEntry::Mmio { data })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_io(&self, port: u16, write: Option<u32>, size: u8) -> Result<VmEntry> {
|
||||
if port == 0x600 || port == 0x601 {
|
||||
log::warn!("port = {:#x}, val = {:#x?}, size = {}", port, write, size);
|
||||
if write == Some(0x34) {
|
||||
return Ok(VmEntry::Shutdown);
|
||||
}
|
||||
}
|
||||
if let Some(val) = write {
|
||||
match self.io_bus.write(port, size, val) {
|
||||
Ok(()) => Ok(VmEntry::None),
|
||||
Err(Error::Action(action)) => self.handle_action(action),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
let data = self.io_bus.read(port, size)?;
|
||||
Ok(VmEntry::Io { data })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MemRegionType {
|
||||
Ram,
|
||||
Reserved,
|
||||
Acpi,
|
||||
Pmem,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MemRegion {
|
||||
pub size: usize,
|
||||
pub type_: MemRegionType,
|
||||
}
|
||||
|
||||
impl SlotBackend for MemRegion {
|
||||
fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
}
|
277
alioth/src/mem/addressable.rs
Normal file
277
alioth/src/mem/addressable.rs
Normal file
|
@ -0,0 +1,277 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::ops::RangeBounds;
|
||||
|
||||
use crate::mem::{Error, Result};
|
||||
|
||||
pub trait SlotBackend {
|
||||
fn size(&self) -> usize;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Slot<B>
|
||||
where
|
||||
B: SlotBackend,
|
||||
{
|
||||
addr: usize,
|
||||
backend: B,
|
||||
}
|
||||
|
||||
impl<B> Slot<B>
|
||||
where
|
||||
B: SlotBackend,
|
||||
{
|
||||
fn new(addr: usize, backend: B) -> Result<Self> {
|
||||
debug_assert_ne!(backend.size(), 0);
|
||||
match (backend.size() - 1).checked_add(addr) {
|
||||
None => Err(Error::OutOfRange {
|
||||
addr,
|
||||
size: backend.size(),
|
||||
}),
|
||||
Some(_) => Ok(Self { addr, backend }),
|
||||
}
|
||||
}
|
||||
|
||||
fn addr_end(&self) -> usize {
|
||||
self.addr.wrapping_add(self.backend.size())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iter<'a, B>
|
||||
where
|
||||
B: SlotBackend,
|
||||
{
|
||||
iter: std::slice::Iter<'a, Slot<B>>,
|
||||
}
|
||||
|
||||
impl<'a, B> Iterator for Iter<'a, B>
|
||||
where
|
||||
B: SlotBackend,
|
||||
{
|
||||
type Item = (usize, &'a B);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().map(|slot| (slot.addr, &slot.backend))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B> DoubleEndedIterator for Iter<'a, B>
|
||||
where
|
||||
B: SlotBackend,
|
||||
{
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next_back().map(|slot| (slot.addr, &slot.backend))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Addressable<B>
|
||||
where
|
||||
B: SlotBackend,
|
||||
{
|
||||
slots: Vec<Slot<B>>,
|
||||
}
|
||||
|
||||
impl<B> Default for Addressable<B>
|
||||
where
|
||||
B: SlotBackend,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Addressable { slots: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Addressable<B>
|
||||
where
|
||||
B: SlotBackend,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter<'_, B> {
|
||||
Iter {
|
||||
iter: self.slots.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drain(
|
||||
&mut self,
|
||||
range: impl RangeBounds<usize>,
|
||||
) -> impl Iterator<Item = (usize, B)> + '_ {
|
||||
self.slots.drain(range).map(|s| (s.addr, s.backend))
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.slots.is_empty()
|
||||
}
|
||||
|
||||
pub fn last(&self) -> Option<(usize, &B)> {
|
||||
self.slots.last().map(|slot| (slot.addr, &slot.backend))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Addressable<B>
|
||||
where
|
||||
B: SlotBackend,
|
||||
{
|
||||
pub fn add(&mut self, addr: usize, backend: B) -> Result<&mut B> {
|
||||
assert_ne!(backend.size(), 0);
|
||||
let slot = Slot::new(addr, backend)?;
|
||||
let result = match self.slots.binary_search_by_key(&addr, |s| s.addr) {
|
||||
Ok(index) => Err(&self.slots[index]),
|
||||
Err(index) => {
|
||||
if index < self.slots.len() && self.slots[index].addr < slot.addr_end() {
|
||||
Err(&self.slots[index])
|
||||
} else if index > 0 && slot.addr < self.slots[index - 1].addr_end() {
|
||||
Err(&self.slots[index - 1])
|
||||
} else {
|
||||
Ok(index)
|
||||
}
|
||||
}
|
||||
};
|
||||
match result {
|
||||
Err(curr_slot) => Err(Error::Overlap {
|
||||
new_addr: slot.addr,
|
||||
new_end: slot.addr_end(),
|
||||
curr_addr: curr_slot.addr,
|
||||
curr_end: curr_slot.addr_end(),
|
||||
}),
|
||||
Ok(index) => {
|
||||
self.slots.insert(index, slot);
|
||||
// TODO add some compiler hint to eliminate bound check?
|
||||
Ok(&mut self.slots[index].backend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, addr: usize) -> Result<B> {
|
||||
match self.slots.binary_search_by_key(&addr, |s| s.addr) {
|
||||
Ok(index) => Ok(self.slots.remove(index).backend),
|
||||
Err(_) => Err(Error::NotMapped(addr)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search(&self, addr: usize) -> Option<(usize, &B)> {
|
||||
match self.slots.binary_search_by_key(&addr, |s| s.addr) {
|
||||
Ok(index) => Some((self.slots[index].addr, &self.slots[index].backend)),
|
||||
Err(0) => None,
|
||||
Err(index) => {
|
||||
let candidate = &self.slots[index - 1];
|
||||
if addr < candidate.addr_end() {
|
||||
Some((candidate.addr, &candidate.backend))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Backend {
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl SlotBackend for Backend {
|
||||
fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overflow() {
|
||||
assert_matches!(
|
||||
Slot::new(usize::MAX, Backend { size: 0x10 }),
|
||||
Err(Error::OutOfRange {
|
||||
size: 0x10,
|
||||
addr: usize::MAX,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addressable() {
|
||||
let mut memory = Addressable::<Backend>::new();
|
||||
assert_matches!(memory.add(0x1000, Backend { size: 0x1000 }), Ok(_));
|
||||
assert_matches!(memory.add(0x5000, Backend { size: 0x1000 }), Ok(_));
|
||||
assert_matches!(memory.add(0x2000, Backend { size: 0x2000 }), Ok(_));
|
||||
assert_eq!(memory.slots.len(), 3);
|
||||
assert!(!memory.is_empty());
|
||||
assert_eq!(memory.last(), Some((0x5000, &memory.slots[2].backend)));
|
||||
// assert_matches!(memory.last_mut(), Some((0x5000, _)));
|
||||
assert_matches!(
|
||||
memory.add(0x1000, Backend { size: 0x2000 }),
|
||||
Err(Error::Overlap {
|
||||
new_addr: 0x1000,
|
||||
new_end: 0x3000,
|
||||
curr_addr: 0x1000,
|
||||
curr_end: 0x2000
|
||||
})
|
||||
);
|
||||
assert_matches!(
|
||||
memory.add(0x0, Backend { size: 0x2000 }),
|
||||
Err(Error::Overlap {
|
||||
new_addr: 0x0,
|
||||
new_end: 0x2000,
|
||||
curr_addr: 0x1000,
|
||||
curr_end: 0x2000
|
||||
})
|
||||
);
|
||||
assert_matches!(
|
||||
memory.add(0x3000, Backend { size: 0x1000 }),
|
||||
Err(Error::Overlap {
|
||||
new_addr: 0x3000,
|
||||
new_end: 0x4000,
|
||||
curr_addr: 0x2000,
|
||||
curr_end: 0x4000
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
memory.search(0x1000),
|
||||
Some((memory.slots[0].addr, &memory.slots[0].backend))
|
||||
);
|
||||
assert_eq!(memory.search(0x0), None);
|
||||
assert_eq!(
|
||||
memory.search(0x1500),
|
||||
Some((memory.slots[0].addr, &memory.slots[0].backend))
|
||||
);
|
||||
assert_eq!(memory.search(0x4000), None);
|
||||
|
||||
let mut iter = memory.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some((memory.slots[0].addr, &memory.slots[0].backend))
|
||||
);
|
||||
assert_eq!(
|
||||
iter.next_back(),
|
||||
Some((memory.slots[2].addr, &memory.slots[2].backend))
|
||||
);
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some((memory.slots[1].addr, &memory.slots[1].backend))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
assert_matches!(memory.remove(0x1000), Ok(Backend { size: 0x1000 }));
|
||||
assert_matches!(memory.remove(0x2001), Err(Error::NotMapped(0x2001)));
|
||||
}
|
||||
}
|
56
alioth/src/mem/io.rs
Normal file
56
alioth/src/mem/io.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use super::mmio::{Mmio, MmioRange};
|
||||
use super::Result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IoBus {
|
||||
inner: RwLock<MmioRange>,
|
||||
}
|
||||
|
||||
pub type IoDev = Arc<dyn Mmio + Send + Sync + 'static>;
|
||||
|
||||
impl Default for IoBus {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl IoBus {
|
||||
pub fn new() -> IoBus {
|
||||
Self {
|
||||
inner: RwLock::new(MmioRange::with_size(u16::MAX as usize)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add(&self, port: u16, dev: IoDev) -> Result<()> {
|
||||
let mut inner = self.inner.write()?;
|
||||
let dev = inner.add(port as usize, dev)?;
|
||||
dev.mapped(port as usize)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read(&self, port: u16, size: u8) -> Result<u32> {
|
||||
let inner = self.inner.read()?;
|
||||
inner.read(port as usize, size).map(|v| v as u32)
|
||||
}
|
||||
|
||||
pub fn write(&self, port: u16, size: u8, val: u32) -> Result<()> {
|
||||
let inner = self.inner.read()?;
|
||||
inner.write(port as usize, size, val as u64)
|
||||
}
|
||||
}
|
174
alioth/src/mem/mmio.rs
Normal file
174
alioth/src/mem/mmio.rs
Normal file
|
@ -0,0 +1,174 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::type_name;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use super::addressable::{Addressable, SlotBackend};
|
||||
use super::{Error, Result};
|
||||
|
||||
pub trait Mmio: Debug {
|
||||
fn read(&self, offset: usize, size: u8) -> Result<u64>;
|
||||
fn write(&self, offset: usize, size: u8, val: u64) -> Result<()>;
|
||||
fn mapped(&self, addr: usize) -> Result<()> {
|
||||
log::trace!("{:#x} -> {}", addr, type_name::<Self>());
|
||||
Ok(())
|
||||
}
|
||||
fn unmapped(&self) -> Result<()> {
|
||||
log::trace!("{} unmapped", type_name::<Self>());
|
||||
Ok(())
|
||||
}
|
||||
fn size(&self) -> usize;
|
||||
}
|
||||
|
||||
pub type MmioRegion = Arc<dyn Mmio + Send + Sync + 'static>;
|
||||
|
||||
impl SlotBackend for MmioRegion {
|
||||
fn size(&self) -> usize {
|
||||
Mmio::size(self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MmioRange {
|
||||
limit: usize,
|
||||
inner: Addressable<MmioRegion>,
|
||||
}
|
||||
|
||||
impl MmioRange {
|
||||
pub fn with_size(size: usize) -> Self {
|
||||
assert_ne!(size, 0);
|
||||
MmioRange {
|
||||
limit: size - 1,
|
||||
inner: Addressable::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
MmioRange {
|
||||
limit: usize::MAX,
|
||||
inner: Addressable::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
|
||||
pub fn add(&mut self, offset: usize, dev: MmioRegion) -> Result<&mut MmioRegion> {
|
||||
let in_range = (dev.size() - 1)
|
||||
.checked_add(offset)
|
||||
.map(|max| max <= self.limit);
|
||||
match in_range {
|
||||
Some(true) => self.inner.add(offset, dev),
|
||||
Some(false) | None => Err(Error::OutOfRange {
|
||||
addr: offset,
|
||||
size: dev.size(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, addr: usize) -> Result<MmioRegion> {
|
||||
self.inner.remove(addr)
|
||||
}
|
||||
|
||||
pub fn read(&self, addr: usize, size: u8) -> Result<u64> {
|
||||
match self.inner.search(addr) {
|
||||
Some((start, dev)) => dev.read(addr - start, size),
|
||||
None => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, addr: usize, size: u8, val: u64) -> Result<()> {
|
||||
match self.inner.search(addr) {
|
||||
Some((start, dev)) => dev.write(addr - start, size, val),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mmio for MmioRange {
|
||||
fn size(&self) -> usize {
|
||||
// Overflow happens when limit = usize::MAX, which is only possible when
|
||||
// it was constructed through MmioRange::new(). MmioRange::new() is private
|
||||
// and only MmioBus uses it.
|
||||
self.limit.wrapping_add(1)
|
||||
}
|
||||
|
||||
fn read(&self, offset: usize, size: u8) -> Result<u64> {
|
||||
self.read(offset, size)
|
||||
}
|
||||
|
||||
fn write(&self, offset: usize, size: u8, val: u64) -> Result<()> {
|
||||
self.write(offset, size, val)
|
||||
}
|
||||
|
||||
fn mapped(&self, addr: usize) -> Result<()> {
|
||||
for (offset, range) in self.inner.iter() {
|
||||
range.mapped(addr + offset)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmapped(&self) -> Result<()> {
|
||||
for (_, range) in self.inner.iter() {
|
||||
range.unmapped()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MmioBus {
|
||||
inner: RwLock<MmioRange>,
|
||||
}
|
||||
|
||||
impl Default for MmioBus {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MmioBus {
|
||||
pub fn new() -> MmioBus {
|
||||
Self {
|
||||
inner: RwLock::new(MmioRange::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add(&self, addr: usize, dev: MmioRegion) -> Result<()> {
|
||||
let mut inner = self.inner.write()?;
|
||||
let dev = inner.add(addr, dev)?;
|
||||
dev.mapped(addr)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn remove(&self, addr: usize) -> Result<MmioRegion> {
|
||||
let mut inner = self.inner.write()?;
|
||||
let dev = inner.remove(addr)?;
|
||||
dev.unmapped()?;
|
||||
Ok(dev)
|
||||
}
|
||||
|
||||
pub fn read(&self, addr: usize, size: u8) -> Result<u64> {
|
||||
let inner = self.inner.read()?;
|
||||
inner.read(addr, size)
|
||||
}
|
||||
|
||||
pub fn write(&self, addr: usize, size: u8, val: u64) -> Result<()> {
|
||||
let inner = self.inner.read()?;
|
||||
inner.write(addr, size, val)
|
||||
}
|
||||
}
|
670
alioth/src/mem/ram.rs
Normal file
670
alioth/src/mem/ram.rs
Normal file
|
@ -0,0 +1,670 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::type_name;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::io::{IoSlice, IoSliceMut, Read, Write};
|
||||
use std::mem::size_of;
|
||||
use std::ops::Deref;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::ptr::{null_mut, NonNull};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::{Arc, RwLock, RwLockReadGuard};
|
||||
|
||||
use libc::{
|
||||
c_void, mmap, msync, munmap, MAP_ANONYMOUS, MAP_FAILED, MAP_PRIVATE, MS_ASYNC, PROT_EXEC,
|
||||
PROT_READ, PROT_WRITE,
|
||||
};
|
||||
use zerocopy::{AsBytes, FromBytes};
|
||||
|
||||
use crate::ffi;
|
||||
use crate::hv::{MemMapOption, VmMemory};
|
||||
|
||||
use super::addressable::{Addressable, SlotBackend};
|
||||
use super::{Error, Result};
|
||||
|
||||
const UNASSIGNED_SLOT_ID: u32 = u32::MAX;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UserMemInner {
|
||||
addr: NonNull<c_void>,
|
||||
len: usize,
|
||||
map_callback: RwLock<Vec<Box<dyn MmapCallback + Sync + Send + 'static>>>,
|
||||
slot_id: AtomicU32,
|
||||
}
|
||||
|
||||
unsafe impl Send for UserMemInner {}
|
||||
unsafe impl Sync for UserMemInner {}
|
||||
|
||||
impl Drop for UserMemInner {
|
||||
fn drop(&mut self) {
|
||||
let ret = unsafe { munmap(self.addr.as_ptr(), self.len) };
|
||||
if ret != 0 {
|
||||
log::error!("munmap({:p}, {:x}) = {:x}", self.addr, self.len, ret);
|
||||
} else {
|
||||
log::info!("munmap({:p}, {:x}) = {:x}, done", self.addr, self.len, ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MmapCallback: Debug {
|
||||
fn mapped(&self, addr: usize) -> Result<(), Error> {
|
||||
log::trace!("{:#x} -> {}", addr, type_name::<Self>());
|
||||
Ok(())
|
||||
}
|
||||
fn unmapped(&self) -> Result<(), Error> {
|
||||
log::trace!("{} unmapped", type_name::<Self>());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserMem {
|
||||
addr: usize,
|
||||
size: usize,
|
||||
inner: Arc<UserMemInner>,
|
||||
}
|
||||
|
||||
impl SlotBackend for UserMem {
|
||||
fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl UserMem {
|
||||
pub fn addr(&self) -> usize {
|
||||
self.addr
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn sync(&self) -> Result<()> {
|
||||
ffi!(unsafe { msync(self.addr as *mut _, self.size, MS_ASYNC) })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_map_callback(
|
||||
&self,
|
||||
f: Box<dyn MmapCallback + Sync + Send + 'static>,
|
||||
) -> Result<(), Error> {
|
||||
let mut callbacks = self.inner.map_callback.write()?;
|
||||
callbacks.push(f);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mapped_to_guest(&self, gpa: usize) -> Result<(), Error> {
|
||||
let callbacks = self.inner.map_callback.read()?;
|
||||
for callback in callbacks.iter() {
|
||||
callback.mapped(gpa)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmapped_from_guest(&self) -> Result<(), Error> {
|
||||
let callbacks = self.inner.map_callback.read()?;
|
||||
for callback in callbacks.iter() {
|
||||
callback.unmapped()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_raw(addr: *mut c_void, len: usize) -> Self {
|
||||
let addr = NonNull::new(addr).expect("address from mmap() should not be null");
|
||||
UserMem {
|
||||
addr: addr.as_ptr() as usize,
|
||||
size: len,
|
||||
inner: Arc::new(UserMemInner {
|
||||
addr,
|
||||
len,
|
||||
map_callback: RwLock::new(Vec::new()),
|
||||
slot_id: AtomicU32::new(UNASSIGNED_SLOT_ID),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_file(file: File) -> Result<Self, Error> {
|
||||
let mut prot = PROT_READ | PROT_EXEC;
|
||||
let meta = file.metadata().map_err(Error::Mmap)?;
|
||||
let is_readonly = meta.permissions().readonly();
|
||||
if !is_readonly {
|
||||
prot |= PROT_WRITE;
|
||||
}
|
||||
let size = meta.len() as usize;
|
||||
let addr = unsafe { mmap(null_mut(), size, prot, MAP_PRIVATE, file.as_raw_fd(), 0) };
|
||||
match addr {
|
||||
MAP_FAILED => Err(Error::Mmap(std::io::Error::last_os_error())),
|
||||
addr => Ok(Self::new_raw(addr, size)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_anon(size: usize) -> Result<Self, Error> {
|
||||
let prot = PROT_WRITE | PROT_READ | PROT_EXEC;
|
||||
let addr = unsafe { mmap(null_mut(), size, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0) };
|
||||
match addr {
|
||||
MAP_FAILED => Err(Error::Mmap(std::io::Error::last_os_error())),
|
||||
addr => Ok(Self::new_raw(addr, size)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Given offset and len, return the host virtual address and len;
|
||||
/// len might be truncated.
|
||||
fn get_valid_range(&self, offset: usize, len: usize) -> Result<(usize, usize)> {
|
||||
let end = offset.wrapping_add(len).wrapping_sub(1);
|
||||
if offset >= self.size || end < offset {
|
||||
return Err(Error::OutOfRange {
|
||||
addr: offset,
|
||||
size: len,
|
||||
});
|
||||
}
|
||||
let valid_len = std::cmp::min(self.size - offset, len);
|
||||
Ok((self.addr + offset, valid_len))
|
||||
}
|
||||
|
||||
pub fn read<T>(&self, offset: usize) -> Result<T, Error>
|
||||
where
|
||||
T: FromBytes,
|
||||
{
|
||||
let s = self.get_partial_slice(offset, size_of::<T>())?;
|
||||
match FromBytes::read_from(s) {
|
||||
None => Err(Error::OutOfRange {
|
||||
addr: offset,
|
||||
size: size_of::<T>(),
|
||||
}),
|
||||
Some(v) => Ok(v),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write<T>(&self, offset: usize, val: &T) -> Result<(), Error>
|
||||
where
|
||||
T: AsBytes,
|
||||
{
|
||||
let s = self.get_partial_slice_mut(offset, size_of::<T>())?;
|
||||
match AsBytes::write_to(val, s) {
|
||||
None => Err(Error::OutOfRange {
|
||||
addr: offset,
|
||||
size: size_of::<T>(),
|
||||
}),
|
||||
Some(()) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Given offset and len, return a slice, len might be truncated.
|
||||
fn get_partial_slice(&self, offset: usize, len: usize) -> Result<&[u8], Error> {
|
||||
let (addr, len) = self.get_valid_range(offset, len)?;
|
||||
Ok(unsafe { std::slice::from_raw_parts(addr as *const u8, len) })
|
||||
}
|
||||
|
||||
/// Given offset and len, return a mutable slice, len might be truncated.
|
||||
fn get_partial_slice_mut(&self, offset: usize, len: usize) -> Result<&mut [u8], Error> {
|
||||
let (addr, len) = self.get_valid_range(offset, len)?;
|
||||
Ok(unsafe { std::slice::from_raw_parts_mut(addr as *mut u8, len) })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RamBus {
|
||||
inner: RwLock<Addressable<UserMem>>,
|
||||
vm_memory: Box<dyn VmMemory>,
|
||||
next_slot_id: AtomicU32,
|
||||
max_mem_slots: u32,
|
||||
}
|
||||
|
||||
pub struct RamLayoutGuard<'a> {
|
||||
inner: RwLockReadGuard<'a, Addressable<UserMem>>,
|
||||
}
|
||||
|
||||
impl Deref for RamLayoutGuard<'_> {
|
||||
type Target = Addressable<UserMem>;
|
||||
|
||||
fn deref(&self) -> &Addressable<UserMem> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
struct Iter<'a> {
|
||||
inner: &'a Addressable<UserMem>,
|
||||
gpa: usize,
|
||||
remain: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = Result<&'a [u8]>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.remain == 0 {
|
||||
return None;
|
||||
}
|
||||
let r = self.inner.get_partial_slice(self.gpa, self.remain);
|
||||
if let Ok(s) = r {
|
||||
self.gpa += s.len();
|
||||
self.remain -= s.len();
|
||||
}
|
||||
Some(r)
|
||||
}
|
||||
}
|
||||
|
||||
struct IterMut<'a> {
|
||||
inner: &'a Addressable<UserMem>,
|
||||
gpa: usize,
|
||||
remain: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for IterMut<'a> {
|
||||
type Item = Result<&'a mut [u8]>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.remain == 0 {
|
||||
return None;
|
||||
}
|
||||
let r = self.inner.get_partial_slice_mut(self.gpa, self.remain);
|
||||
if let Ok(ref s) = r {
|
||||
self.gpa += s.len();
|
||||
self.remain -= s.len();
|
||||
}
|
||||
Some(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl Addressable<UserMem> {
|
||||
fn slice_iter(&self, gpa: usize, len: usize) -> Iter {
|
||||
Iter {
|
||||
inner: self,
|
||||
gpa,
|
||||
remain: len,
|
||||
}
|
||||
}
|
||||
|
||||
fn slice_iter_mut(&self, gpa: usize, len: usize) -> IterMut {
|
||||
IterMut {
|
||||
inner: self,
|
||||
gpa,
|
||||
remain: len,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_partial_slice(&self, gpa: usize, len: usize) -> Result<&[u8]> {
|
||||
let Some((start, user_mem)) = self.search(gpa) else {
|
||||
return Err(Error::NotMapped(gpa));
|
||||
};
|
||||
user_mem.get_partial_slice(gpa - start, len)
|
||||
}
|
||||
|
||||
fn get_partial_slice_mut(&self, gpa: usize, len: usize) -> Result<&mut [u8]> {
|
||||
let Some((start, user_mem)) = self.search(gpa) else {
|
||||
return Err(Error::NotMapped(gpa));
|
||||
};
|
||||
user_mem.get_partial_slice_mut(gpa - start, len)
|
||||
}
|
||||
|
||||
pub fn get_slice<T>(&self, gpa: usize, len: usize) -> Result<&[UnsafeCell<T>], Error> {
|
||||
let total_len = len * size_of::<T>();
|
||||
let host_ref = self.get_partial_slice(gpa, total_len)?;
|
||||
let ptr = host_ref.as_ptr() as *const UnsafeCell<T>;
|
||||
if host_ref.len() != total_len {
|
||||
Err(Error::NotContinuous)
|
||||
} else if !ptr.is_aligned() {
|
||||
Err(Error::NotAligned)
|
||||
} else {
|
||||
Ok(unsafe { &*core::ptr::slice_from_raw_parts(ptr, len) })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ref<T>(&self, gpa: usize) -> Result<&UnsafeCell<T>, Error> {
|
||||
let host_ref = self.get_partial_slice(gpa, size_of::<T>())?;
|
||||
let ptr = host_ref.as_ptr() as *const UnsafeCell<T>;
|
||||
if host_ref.len() != size_of::<T>() {
|
||||
Err(Error::NotContinuous)
|
||||
} else if !ptr.is_aligned() {
|
||||
Err(Error::NotAligned)
|
||||
} else {
|
||||
Ok(unsafe { &*ptr })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read<T>(&self, gpa: usize) -> Result<T, Error>
|
||||
where
|
||||
T: FromBytes + AsBytes,
|
||||
{
|
||||
let mut val = T::new_zeroed();
|
||||
let buf = val.as_bytes_mut();
|
||||
let host_ref = self.get_partial_slice(gpa, size_of::<T>())?;
|
||||
if host_ref.len() == buf.len() {
|
||||
buf.copy_from_slice(host_ref);
|
||||
Ok(val)
|
||||
} else {
|
||||
let mut cur = 0;
|
||||
for r in self.slice_iter(gpa, size_of::<T>()) {
|
||||
let s = r?;
|
||||
let s_len = s.len();
|
||||
buf[cur..(cur + s_len)].copy_from_slice(s);
|
||||
cur += s_len;
|
||||
}
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write<T>(&self, gpa: usize, val: &T) -> Result<(), Error>
|
||||
where
|
||||
T: AsBytes,
|
||||
{
|
||||
let buf = val.as_bytes();
|
||||
let host_ref = self.get_partial_slice_mut(gpa, size_of::<T>())?;
|
||||
if host_ref.len() == buf.len() {
|
||||
host_ref.copy_from_slice(buf);
|
||||
Ok(())
|
||||
} else {
|
||||
let mut cur = 0;
|
||||
for r in self.slice_iter_mut(gpa, size_of::<T>()) {
|
||||
let s = r?;
|
||||
let s_len = s.len();
|
||||
s.copy_from_slice(&buf[cur..(cur + s_len)]);
|
||||
cur += s_len;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn translate_iov<'a>(&'a self, iov: &[(usize, usize)]) -> Result<Vec<IoSlice<'a>>> {
|
||||
let mut slices = vec![];
|
||||
for (gpa, len) in iov {
|
||||
for r in self.slice_iter(*gpa, *len) {
|
||||
slices.push(IoSlice::new(r?));
|
||||
}
|
||||
}
|
||||
Ok(slices)
|
||||
}
|
||||
|
||||
pub fn translate_iov_mut<'a>(&'a self, iov: &[(usize, usize)]) -> Result<Vec<IoSliceMut<'a>>> {
|
||||
let mut slices = vec![];
|
||||
for (gpa, len) in iov {
|
||||
for r in self.slice_iter_mut(*gpa, *len) {
|
||||
slices.push(IoSliceMut::new(r?));
|
||||
}
|
||||
}
|
||||
Ok(slices)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RamBus {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = self.clear() {
|
||||
log::info!("dropping RamBus: {:x?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RamBus {
|
||||
pub fn lock_layout(&self) -> Result<RamLayoutGuard<'_>, Error> {
|
||||
let guard = RamLayoutGuard {
|
||||
inner: self.inner.read()?,
|
||||
};
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
pub fn new<M: VmMemory>(vm_memory: M) -> Self {
|
||||
let max_mem_slots = match vm_memory.max_mem_slots() {
|
||||
Ok(val) => val,
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"quering hypervisor for maximum supported memory slots, got error {e:?}"
|
||||
);
|
||||
log::error!(
|
||||
"assuming the maximum assuported memory slots is {:#x}",
|
||||
u16::MAX
|
||||
);
|
||||
u16::MAX as u32
|
||||
}
|
||||
};
|
||||
Self {
|
||||
inner: RwLock::new(Addressable::default()),
|
||||
vm_memory: Box::new(vm_memory),
|
||||
next_slot_id: AtomicU32::new(0),
|
||||
max_mem_slots,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_to_vm(&self, user_mem: &UserMem, addr: usize) -> Result<(), Error> {
|
||||
let mem_options = MemMapOption {
|
||||
read: true,
|
||||
write: true,
|
||||
exec: true,
|
||||
log_dirty: false,
|
||||
};
|
||||
let slot_id = user_mem.inner.slot_id.load(Ordering::Acquire);
|
||||
self.vm_memory
|
||||
.mem_map(slot_id, addr, user_mem.size(), user_mem.addr(), mem_options)?;
|
||||
log::trace!(
|
||||
"user memory {} mapped: {:#x} -> {:#x}",
|
||||
slot_id,
|
||||
user_mem.addr,
|
||||
addr
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmap_from_vm(&self, user_mem: &UserMem, addr: usize) -> Result<(), Error> {
|
||||
let slot_id = user_mem.inner.slot_id.load(Ordering::Acquire);
|
||||
self.vm_memory.unmap(slot_id, addr, user_mem.size())?;
|
||||
log::trace!(
|
||||
"user memory {} unmapped: {:#x} -> {:#x}",
|
||||
slot_id,
|
||||
user_mem.addr,
|
||||
addr
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn add(&self, gpa: usize, user_mem: UserMem) -> Result<(), Error> {
|
||||
let mut inner = self.inner.write()?;
|
||||
let mem = inner.add(gpa, user_mem)?;
|
||||
let mut slot_id = mem.inner.slot_id.load(Ordering::Acquire);
|
||||
if slot_id == UNASSIGNED_SLOT_ID {
|
||||
slot_id = self.next_slot_id.fetch_add(1, Ordering::AcqRel) % self.max_mem_slots;
|
||||
mem.inner.slot_id.store(slot_id, Ordering::Release);
|
||||
}
|
||||
self.map_to_vm(mem, gpa)?;
|
||||
mem.mapped_to_guest(gpa)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn clear(&self) -> Result<()> {
|
||||
let mut innter = self.inner.write()?;
|
||||
for (gpa, user_mem) in innter.drain(..) {
|
||||
self.unmap_from_vm(&user_mem, gpa)?;
|
||||
user_mem.unmapped_from_guest()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn remove(&self, gpa: usize) -> Result<UserMem, Error> {
|
||||
let mut inner = self.inner.write()?;
|
||||
let mem = inner.remove(gpa)?;
|
||||
self.unmap_from_vm(&mem, gpa)?;
|
||||
mem.unmapped_from_guest()?;
|
||||
Ok(mem)
|
||||
}
|
||||
|
||||
pub fn read<T>(&self, gpa: usize) -> Result<T, Error>
|
||||
where
|
||||
T: FromBytes + AsBytes,
|
||||
{
|
||||
let inner = self.inner.read()?;
|
||||
inner.read(gpa)
|
||||
}
|
||||
|
||||
pub fn write<T>(&self, gpa: usize, val: &T) -> Result<(), Error>
|
||||
where
|
||||
T: AsBytes,
|
||||
{
|
||||
let inner = self.inner.read()?;
|
||||
inner.write(gpa, val)
|
||||
}
|
||||
|
||||
pub fn read_range(&self, gpa: usize, len: usize, dst: &mut impl Write) -> Result<()> {
|
||||
let inner = self.inner.read()?;
|
||||
for r in inner.slice_iter(gpa, len) {
|
||||
dst.write_all(r?)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_range(&self, gpa: usize, len: usize, mut src: impl Read) -> Result<()> {
|
||||
let inner = self.inner.read()?;
|
||||
for r in inner.slice_iter_mut(gpa, len) {
|
||||
src.read_exact(r?)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_vectored<T, F>(&self, bufs: &[(usize, usize)], callback: F) -> Result<T, Error>
|
||||
where
|
||||
F: FnOnce(&[IoSlice<'_>]) -> T,
|
||||
{
|
||||
let inner = self.inner.read()?;
|
||||
let mut iov = vec![];
|
||||
for (gpa, len) in bufs {
|
||||
for r in inner.slice_iter(*gpa, *len) {
|
||||
iov.push(IoSlice::new(r?));
|
||||
}
|
||||
}
|
||||
Ok(callback(&iov))
|
||||
}
|
||||
|
||||
pub fn write_vectored<T, F>(&self, bufs: &[(usize, usize)], callback: F) -> Result<T, Error>
|
||||
where
|
||||
F: FnOnce(&mut [IoSliceMut<'_>]) -> T,
|
||||
{
|
||||
let inner = self.inner.read()?;
|
||||
let mut iov = vec![];
|
||||
for (gpa, len) in bufs {
|
||||
for r in inner.slice_iter_mut(*gpa, *len) {
|
||||
iov.push(IoSliceMut::new(r?));
|
||||
}
|
||||
}
|
||||
Ok(callback(&mut iov))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::assert_matches::assert_matches;
|
||||
use std::io::{Read, Write};
|
||||
use std::mem::size_of;
|
||||
use std::ptr::null_mut;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use libc::{mmap, munmap, MAP_ANONYMOUS, MAP_FAILED, MAP_PRIVATE, PROT_READ, PROT_WRITE};
|
||||
use zerocopy::{AsBytes, FromBytes, FromZeroes};
|
||||
|
||||
use crate::hv::test::FakeVmMemory;
|
||||
|
||||
use super::{MmapCallback, RamBus, Result, UserMem};
|
||||
|
||||
#[derive(Debug, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
struct MyStruct {
|
||||
data: [u32; 8],
|
||||
}
|
||||
|
||||
const PAGE_SIZE: usize = 1 << 12;
|
||||
|
||||
#[test]
|
||||
fn test_ram_bus_read() {
|
||||
let bus = RamBus::new(FakeVmMemory);
|
||||
let prot = PROT_READ | PROT_WRITE;
|
||||
let size = 3 * PAGE_SIZE;
|
||||
let addr = unsafe { mmap(null_mut(), size, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0) };
|
||||
assert_ne!(addr, MAP_FAILED);
|
||||
let munmap_ret = unsafe { munmap(addr.add(PAGE_SIZE), PAGE_SIZE) };
|
||||
assert_ne!(munmap_ret, -1);
|
||||
let mem1 = UserMem::new_raw(addr, PAGE_SIZE);
|
||||
let mem2_addr = unsafe { addr.add(2 * PAGE_SIZE) };
|
||||
let mem2 = UserMem::new_raw(mem2_addr, PAGE_SIZE);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Callback {
|
||||
mapped: Arc<AtomicBool>,
|
||||
}
|
||||
impl MmapCallback for Callback {
|
||||
fn mapped(&self, _addr: usize) -> Result<()> {
|
||||
self.mapped.store(true, Ordering::Release);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unmapped(&self) -> Result<()> {
|
||||
self.mapped.store(false, Ordering::Release);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
let mem1_mapped = Arc::new(AtomicBool::new(false));
|
||||
let mem1_callback = Callback {
|
||||
mapped: mem1_mapped.clone(),
|
||||
};
|
||||
mem1.add_map_callback(Box::new(mem1_callback)).unwrap();
|
||||
bus.add(0x0, mem1).unwrap();
|
||||
assert!(mem1_mapped.load(Ordering::Acquire));
|
||||
bus.add(PAGE_SIZE, mem2).unwrap();
|
||||
|
||||
let data = MyStruct {
|
||||
data: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||
};
|
||||
let data_size = size_of::<MyStruct>();
|
||||
for gpa in (PAGE_SIZE - data_size)..=PAGE_SIZE {
|
||||
bus.write(gpa, &data).unwrap();
|
||||
let r: MyStruct = bus.read(gpa).unwrap();
|
||||
assert_eq!(r, data)
|
||||
}
|
||||
let memory_end = PAGE_SIZE * 2;
|
||||
for gpa in (memory_end - data_size - 10)..=(memory_end - data_size) {
|
||||
bus.write(gpa, &data).unwrap();
|
||||
let r: MyStruct = bus.read(gpa).unwrap();
|
||||
assert_eq!(r, data)
|
||||
}
|
||||
for gpa in (memory_end - data_size + 1)..memory_end {
|
||||
assert_matches!(bus.write(gpa, &data), Err(_));
|
||||
assert_matches!(bus.read::<MyStruct>(gpa), Err(_));
|
||||
}
|
||||
|
||||
let data: Vec<u8> = (0..64).collect();
|
||||
for gpa in (PAGE_SIZE - 64)..=PAGE_SIZE {
|
||||
bus.write_range(gpa, 64, &*data).unwrap();
|
||||
let mut buf = Vec::new();
|
||||
bus.read_range(gpa, 64, &mut buf).unwrap();
|
||||
assert_eq!(data, buf)
|
||||
}
|
||||
|
||||
let guest_iov = [(0, 16), (PAGE_SIZE - 16, 32), (2 * PAGE_SIZE - 16, 16)];
|
||||
let write_ret = bus.write_vectored(&guest_iov, |iov| {
|
||||
assert_eq!(iov.len(), 4);
|
||||
(&*data).read_vectored(iov)
|
||||
});
|
||||
assert_matches!(write_ret, Ok(Ok(64)));
|
||||
let mut buf_read = Vec::new();
|
||||
let read_ret = bus.read_vectored(&guest_iov, |iov| {
|
||||
assert_eq!(iov.len(), 4);
|
||||
buf_read.write_vectored(iov)
|
||||
});
|
||||
assert_matches!(read_ret, Ok(Ok(64)));
|
||||
|
||||
let locked_bus = bus.lock_layout().unwrap();
|
||||
let bufs = locked_bus.translate_iov(&guest_iov).unwrap();
|
||||
println!("{:?}", bufs);
|
||||
drop(locked_bus);
|
||||
bus.remove(0x0).unwrap();
|
||||
assert!(!mem1_mapped.load(Ordering::Acquire));
|
||||
}
|
||||
}
|
78
alioth/src/utils.rs
Normal file
78
alioth/src/utils.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod ioctls;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! align_up {
|
||||
($num:expr, $align:expr) => {{
|
||||
debug_assert_eq!(($align as u64).count_ones(), 1);
|
||||
let mask = $align - 1;
|
||||
($num.wrapping_add(mask)) & !mask
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ffi {
|
||||
($f:expr) => {{
|
||||
let ret = $f;
|
||||
if ret <= -1 {
|
||||
Err(::std::io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}};
|
||||
($f:expr, $failure:ident) => {{
|
||||
let ret = $f;
|
||||
if ret == $failure {
|
||||
Err(::std::io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! unsafe_impl_zerocopy {
|
||||
($ty:ty, $($name:ident), +) => {
|
||||
$(
|
||||
unsafe impl ::zerocopy::$name for $ty {
|
||||
fn only_derive_is_allowed_to_implement_this_trait()
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_align_up() {
|
||||
assert_eq!(align_up!(0u64, 4), 0);
|
||||
assert_eq!(align_up!(1u64, 4), 4);
|
||||
assert_eq!(align_up!(3u64, 4), 4);
|
||||
|
||||
assert_eq!(align_up!(u64::MAX, 1), u64::MAX);
|
||||
assert_eq!(align_up!(u64::MAX, 4), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_align_up_panic() {
|
||||
let _ = align_up!(1u64, 3);
|
||||
}
|
||||
}
|
184
alioth/src/utils/ioctls.rs
Normal file
184
alioth/src/utils/ioctls.rs
Normal file
|
@ -0,0 +1,184 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
use libc::c_ulong;
|
||||
|
||||
const IOC_NONN: c_ulong = 0;
|
||||
const IOC_WRITE: c_ulong = 1;
|
||||
const IOC_READ: c_ulong = 2;
|
||||
|
||||
const IOC_NRSHIFT: usize = 0;
|
||||
const IOC_TYPESHIFT: usize = 8;
|
||||
const IOC_SIZESHIFT: usize = 16;
|
||||
const IOC_DIRSHIFT: usize = 30;
|
||||
|
||||
const fn ioctl_ioc(dir: c_ulong, type_: u8, nr: u8, size: c_ulong) -> c_ulong {
|
||||
(dir << IOC_DIRSHIFT)
|
||||
| (size << IOC_SIZESHIFT)
|
||||
| ((type_ as c_ulong) << IOC_TYPESHIFT)
|
||||
| ((nr as c_ulong) << IOC_NRSHIFT)
|
||||
}
|
||||
|
||||
pub const fn ioctl_io(type_: u8, nr: u8) -> c_ulong {
|
||||
ioctl_ioc(IOC_NONN, type_, nr, 0)
|
||||
}
|
||||
|
||||
pub const fn ioctl_ior<T>(type_: u8, nr: u8) -> c_ulong {
|
||||
ioctl_ioc(IOC_READ, type_, nr, size_of::<T>() as c_ulong)
|
||||
}
|
||||
|
||||
pub const fn ioctl_iow<T>(type_: u8, nr: u8) -> c_ulong {
|
||||
ioctl_ioc(IOC_WRITE, type_, nr, size_of::<T>() as c_ulong)
|
||||
}
|
||||
|
||||
pub const fn ioctl_iowr<T>(type_: u8, nr: u8) -> c_ulong {
|
||||
ioctl_ioc(IOC_WRITE | IOC_READ, type_, nr, size_of::<T>() as c_ulong)
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_none {
|
||||
($name:ident, $type_:expr, $nr:expr, $val:expr) => {
|
||||
pub unsafe fn $name<F: ::std::os::fd::AsRawFd>(fd: &F) -> ::std::io::Result<libc::c_int> {
|
||||
$crate::ffi!(::libc::ioctl(
|
||||
fd.as_raw_fd(),
|
||||
$crate::utils::ioctls::ioctl_io($type_, $nr),
|
||||
$val as ::libc::c_ulong,
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_write_val {
|
||||
($name:ident, $code:expr) => {
|
||||
pub unsafe fn $name<F: ::std::os::fd::AsRawFd>(
|
||||
fd: &F,
|
||||
val: ::libc::c_ulong,
|
||||
) -> ::std::io::Result<libc::c_int> {
|
||||
$crate::ffi!(::libc::ioctl(fd.as_raw_fd(), $code, val))
|
||||
}
|
||||
};
|
||||
($name:ident, $code:expr, $ty:ty) => {
|
||||
pub unsafe fn $name<F: ::std::os::fd::AsRawFd>(
|
||||
fd: &F,
|
||||
val: $ty,
|
||||
) -> ::std::io::Result<libc::c_int> {
|
||||
$crate::ffi!(::libc::ioctl(fd.as_raw_fd(), $code, val))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_write_ptr {
|
||||
($name:ident, $code:expr, $ty:ty) => {
|
||||
pub unsafe fn $name<F: ::std::os::fd::AsRawFd>(
|
||||
fd: &F,
|
||||
val: &$ty,
|
||||
) -> ::std::io::Result<libc::c_int> {
|
||||
$crate::ffi!(::libc::ioctl(fd.as_raw_fd(), $code, val as *const $ty))
|
||||
}
|
||||
};
|
||||
|
||||
($name:ident, $type_:expr, $nr:expr, $ty:ty) => {
|
||||
pub unsafe fn $name<F: ::std::os::fd::AsRawFd>(
|
||||
fd: &F,
|
||||
val: &$ty,
|
||||
) -> ::std::io::Result<libc::c_int> {
|
||||
$crate::ffi!(::libc::ioctl(
|
||||
fd.as_raw_fd(),
|
||||
$crate::utils::ioctls::ioctl_iow::<$ty>($type_, $nr),
|
||||
val as *const $ty,
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_write_buf {
|
||||
($name:ident, $type_:expr, $nr:expr, $ty:ident) => {
|
||||
pub unsafe fn $name<F: ::std::os::fd::AsRawFd, const N: usize>(
|
||||
fd: &F,
|
||||
val: &$ty<N>,
|
||||
) -> ::std::io::Result<libc::c_int> {
|
||||
$crate::ffi!(::libc::ioctl(
|
||||
fd.as_raw_fd(),
|
||||
$crate::utils::ioctls::ioctl_iow::<$ty<0>>($type_, $nr),
|
||||
val as *const $ty<N>,
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_writeread {
|
||||
($name:ident, $type_:expr, $nr:expr, $ty:ty) => {
|
||||
pub unsafe fn $name<F: ::std::os::fd::AsRawFd>(
|
||||
fd: &F,
|
||||
val: &mut $ty,
|
||||
) -> ::std::io::Result<libc::c_int> {
|
||||
$crate::ffi!(::libc::ioctl(
|
||||
fd.as_raw_fd(),
|
||||
$crate::utils::ioctls::ioctl_iowr::<$ty>($type_, $nr),
|
||||
val as *mut $ty,
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_writeread_buf {
|
||||
($name:ident, $type_:expr, $nr:expr, $ty:ident) => {
|
||||
pub unsafe fn $name<F: ::std::os::fd::AsRawFd, const N: usize>(
|
||||
fd: &F,
|
||||
val: &mut $ty<N>,
|
||||
) -> ::std::io::Result<libc::c_int> {
|
||||
$crate::ffi!(::libc::ioctl(
|
||||
fd.as_raw_fd(),
|
||||
$crate::utils::ioctls::ioctl_iowr::<$ty<0>>($type_, $nr),
|
||||
val as *mut $ty<N>,
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ioctl_read {
|
||||
($name:ident, $type_:expr, $nr:expr, $ty:ty) => {
|
||||
pub unsafe fn $name<F: ::std::os::fd::AsRawFd>(fd: &F) -> ::std::io::Result<$ty> {
|
||||
let mut val = ::core::mem::MaybeUninit::<$ty>::uninit();
|
||||
$crate::ffi!(::libc::ioctl(
|
||||
fd.as_raw_fd(),
|
||||
$crate::utils::ioctls::ioctl_ior::<$ty>($type_, $nr),
|
||||
val.as_mut_ptr(),
|
||||
))?;
|
||||
::std::io::Result::Ok(val.assume_init())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::utils::ioctls::{ioctl_io, ioctl_ior, ioctl_iow, ioctl_iowr};
|
||||
|
||||
#[test]
|
||||
fn test_codes() {
|
||||
const KVMIO: u8 = 0xAE;
|
||||
assert_eq!(ioctl_io(KVMIO, 0x01), 0xae01);
|
||||
assert_eq!(ioctl_ior::<[u8; 320]>(KVMIO, 0xcc), 0x8140aecc);
|
||||
assert_eq!(ioctl_iow::<[u8; 320]>(KVMIO, 0xcd), 0x4140aecd);
|
||||
assert_eq!(ioctl_iowr::<[u8; 8]>(KVMIO, 0x05), 0xc008ae05);
|
||||
}
|
||||
}
|
270
alioth/src/vm.rs
Normal file
270
alioth/src/vm.rs
Normal file
|
@ -0,0 +1,270 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod x86_64;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::{Arc, Barrier, PoisonError};
|
||||
use std::thread::{self, JoinHandle};
|
||||
|
||||
use mio::{Events, Poll, Token, Waker};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::device::serial::Serial;
|
||||
use crate::hv::{self, Vcpu, Vm, VmEntry, VmExit};
|
||||
use crate::loader::{self, linux, InitState};
|
||||
use crate::mem;
|
||||
use crate::mem::Memory;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use x86_64::ArchBoard;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("hypervisor: {0}")]
|
||||
Hv(
|
||||
#[from]
|
||||
#[backtrace]
|
||||
hv::Error,
|
||||
),
|
||||
|
||||
#[error("memory: {0}")]
|
||||
Memory(
|
||||
#[from]
|
||||
#[backtrace]
|
||||
mem::Error,
|
||||
),
|
||||
|
||||
#[error("host io: {0}")]
|
||||
HostIo(
|
||||
#[from]
|
||||
#[backtrace]
|
||||
std::io::Error,
|
||||
),
|
||||
|
||||
#[error("loader: {0}")]
|
||||
Loader(
|
||||
#[from]
|
||||
#[backtrace]
|
||||
loader::Error,
|
||||
),
|
||||
|
||||
#[error("rwlock poisoned")]
|
||||
RwLockPoisoned,
|
||||
|
||||
#[error("ACPI bytes exceed EBDA area")]
|
||||
AcpiTooLong,
|
||||
|
||||
#[error("cannot handle {0:#x?}")]
|
||||
VmExit(String),
|
||||
}
|
||||
|
||||
type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
impl<T> From<PoisonError<T>> for Error {
|
||||
fn from(_: PoisonError<T>) -> Self {
|
||||
Error::RwLockPoisoned
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Payload {
|
||||
pub executable: PathBuf,
|
||||
pub exec_type: ExecType,
|
||||
pub initramfs: Option<PathBuf>,
|
||||
pub cmd_line: Option<String>,
|
||||
}
|
||||
|
||||
pub struct BoardConfig {
|
||||
pub mem_size: usize,
|
||||
pub num_cpu: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExecType {
|
||||
Linux,
|
||||
}
|
||||
|
||||
pub struct Machine<H>
|
||||
where
|
||||
H: crate::hv::Hypervisor,
|
||||
{
|
||||
vcpu_threads: Vec<JoinHandle<Result<(), Error>>>,
|
||||
board: Arc<Board<H::Vm>>,
|
||||
// board_config: BoardConfig,
|
||||
payload: Option<Payload>,
|
||||
poll: Poll,
|
||||
}
|
||||
|
||||
const STATE_CREATED: u8 = 0;
|
||||
const STATE_RUNNING: u8 = 1;
|
||||
const STATE_SHUTDOWN: u8 = 2;
|
||||
|
||||
struct Board<V>
|
||||
where
|
||||
V: crate::hv::Vm,
|
||||
{
|
||||
vm: V,
|
||||
memory: Memory,
|
||||
arch: ArchBoard,
|
||||
config: BoardConfig,
|
||||
waker: Waker,
|
||||
state: AtomicU8,
|
||||
}
|
||||
|
||||
impl<V> Board<V>
|
||||
where
|
||||
V: crate::hv::Vm,
|
||||
{
|
||||
fn vcpu_loop(&self, vcpu: &mut <V as Vm>::Vcpu, id: u32) -> Result<(), Error> {
|
||||
let mut vm_entry = VmEntry::None;
|
||||
loop {
|
||||
// TODO is there any race here?
|
||||
if self.state.load(Ordering::Acquire) == STATE_SHUTDOWN {
|
||||
vm_entry = VmEntry::Shutdown;
|
||||
}
|
||||
let vm_exit = vcpu.run(vm_entry)?;
|
||||
vm_entry = match vm_exit {
|
||||
VmExit::Io { port, write, size } => self.memory.handle_io(port, write, size)?,
|
||||
VmExit::Mmio { addr, write, size } => self.memory.handle_mmio(addr, write, size)?,
|
||||
VmExit::Shutdown => {
|
||||
log::info!("vcpu {id} requested shutdown");
|
||||
break Ok(());
|
||||
}
|
||||
VmExit::Interrupted => VmEntry::None,
|
||||
VmExit::Unknown(msg) => break Err(Error::VmExit(msg)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn run_vcpu_inner(
|
||||
&self,
|
||||
id: u32,
|
||||
init_state: &InitState,
|
||||
barrier: &Barrier,
|
||||
) -> Result<(), Error> {
|
||||
let mut vcpu = self.init_vcpu(id, init_state)?;
|
||||
barrier.wait();
|
||||
self.vcpu_loop(&mut vcpu, id)
|
||||
}
|
||||
|
||||
fn run_vcpu(&self, id: u32, init_state: &InitState, barrier: &Barrier) -> Result<(), Error> {
|
||||
let ret = self.run_vcpu_inner(id, init_state, barrier);
|
||||
self.state.store(STATE_SHUTDOWN, Ordering::Release);
|
||||
self.waker.wake()?;
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> Machine<H>
|
||||
where
|
||||
H: crate::hv::Hypervisor + 'static,
|
||||
{
|
||||
pub fn new(hv: H, config: BoardConfig) -> Result<Self, Error> {
|
||||
let mut vm = hv.create_vm()?;
|
||||
let vm_mmemory = vm.create_vm_memory()?;
|
||||
let memory = Memory::new(vm_mmemory);
|
||||
let arch = ArchBoard::new(&hv)?;
|
||||
|
||||
let poll = Poll::new()?;
|
||||
let waker = Waker::new(poll.registry(), Token(0))?;
|
||||
let board = Board {
|
||||
vm,
|
||||
memory,
|
||||
arch,
|
||||
config,
|
||||
waker,
|
||||
state: AtomicU8::new(STATE_CREATED),
|
||||
};
|
||||
|
||||
let machine = Machine {
|
||||
board: Arc::new(board),
|
||||
payload: None,
|
||||
vcpu_threads: Vec::new(),
|
||||
poll,
|
||||
};
|
||||
Ok(machine)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn add_com1(&self) -> Result<(), Error> {
|
||||
let com1_intx_sender = self.board.vm.create_intx_sender(4)?;
|
||||
let com1 = Serial::new(0x3f8, com1_intx_sender)?;
|
||||
self.board.memory.add_io_dev(Some(0x3f8), Arc::new(com1))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_payload(&mut self, payload: Payload) {
|
||||
self.payload = Some(payload)
|
||||
}
|
||||
|
||||
fn load_payload(&self) -> Result<InitState, Error> {
|
||||
let Some(payload) = &self.payload else {
|
||||
return Ok(InitState::default());
|
||||
};
|
||||
let mem_regions = self.board.memory.to_mem_regions()?;
|
||||
let init_state = match payload.exec_type {
|
||||
ExecType::Linux => linux::load(
|
||||
self.board.memory.ram_bus(),
|
||||
&mem_regions,
|
||||
&payload.executable,
|
||||
payload.cmd_line.as_deref(),
|
||||
payload.initramfs.as_ref(),
|
||||
)?,
|
||||
};
|
||||
Ok(init_state)
|
||||
}
|
||||
|
||||
pub fn boot(&mut self) -> Result<(), Error> {
|
||||
self.create_ram()?;
|
||||
let init_state = Arc::new(self.load_payload()?);
|
||||
self.create_firmware_data(&init_state)?;
|
||||
self.board.state.store(STATE_RUNNING, Ordering::Release);
|
||||
let barrier = Arc::new(Barrier::new(self.board.config.num_cpu as usize));
|
||||
for vcpu_id in 0..self.board.config.num_cpu {
|
||||
let init_state = init_state.clone();
|
||||
let barrier = barrier.clone();
|
||||
let board = self.board.clone();
|
||||
let handle = thread::Builder::new()
|
||||
.name(format!("vcpu_{}", vcpu_id))
|
||||
.spawn(move || board.run_vcpu(vcpu_id, &init_state, &barrier))?;
|
||||
self.vcpu_threads.push(handle);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn wait(&mut self) -> Vec<Result<()>> {
|
||||
let mut events = Events::with_capacity(8);
|
||||
if let Err(e) = self.poll.poll(&mut events, None) {
|
||||
return vec![Err(e.into())];
|
||||
}
|
||||
self.vcpu_threads
|
||||
.drain(..)
|
||||
.enumerate()
|
||||
.map(|(id, handle)| {
|
||||
<H::Vm>::stop_vcpu(id as u32, &handle)?;
|
||||
match handle.join() {
|
||||
Err(e) => {
|
||||
log::error!("cannot join vcpu {}: {:?}", id, e);
|
||||
Ok(())
|
||||
}
|
||||
Ok(r) => r,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
113
alioth/src/vm/x86_64.rs
Normal file
113
alioth/src/vm/x86_64.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::acpi::create_acpi_tables;
|
||||
use crate::arch::layout::{
|
||||
BIOS_DATA_END, EBDA_END, EBDA_START, MEM_64_START, RAM_32_END, RAM_32_SIZE,
|
||||
};
|
||||
use crate::hv::arch::Cpuid;
|
||||
use crate::hv::{Hypervisor, Vcpu, Vm};
|
||||
use crate::loader::InitState;
|
||||
use crate::mem::ram::UserMem;
|
||||
use crate::mem::{AddrOpt, MemRegionType};
|
||||
use crate::vm::{Board, Error, Machine, Result};
|
||||
|
||||
pub struct ArchBoard {
|
||||
cpuids: Vec<Cpuid>,
|
||||
}
|
||||
|
||||
impl ArchBoard {
|
||||
pub fn new<H: Hypervisor>(hv: &H) -> Result<Self> {
|
||||
let mut cpuids = hv.get_supported_cpuids()?;
|
||||
for cpuid in &mut cpuids {
|
||||
if cpuid.func == 0x1 {
|
||||
cpuid.ecx |= (1 << 24) | (1 << 31);
|
||||
}
|
||||
}
|
||||
Ok(Self { cpuids })
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Board<V>
|
||||
where
|
||||
V: Vm,
|
||||
{
|
||||
pub fn init_vcpu(&self, id: u32, init_state: &InitState) -> Result<<V as Vm>::Vcpu, Error> {
|
||||
let mut vcpu = self.vm.create_vcpu(id)?;
|
||||
if id == 0 {
|
||||
vcpu.set_regs(&init_state.regs)?;
|
||||
vcpu.set_sregs(&init_state.sregs, &init_state.seg_regs, &init_state.dt_regs)?;
|
||||
}
|
||||
let mut cpuids = self.arch.cpuids.clone();
|
||||
for cpuid in &mut cpuids {
|
||||
if cpuid.func == 0x1 {
|
||||
cpuid.ebx &= 0x00ff_ffff;
|
||||
cpuid.ebx |= id << 24;
|
||||
} else if cpuid.func == 0xb {
|
||||
cpuid.edx = id;
|
||||
}
|
||||
}
|
||||
vcpu.set_cpuids(cpuids)?;
|
||||
Ok(vcpu)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> Machine<H>
|
||||
where
|
||||
H: Hypervisor,
|
||||
{
|
||||
pub fn create_firmware_data(&self, _init_state: &InitState) -> Result<()> {
|
||||
let acpi_bytes = create_acpi_tables(EBDA_START, self.board.config.num_cpu);
|
||||
if acpi_bytes.len() > EBDA_END - EBDA_START {
|
||||
return Err(Error::AcpiTooLong);
|
||||
}
|
||||
let ram = self.board.memory.ram_bus();
|
||||
ram.write_range(EBDA_START, acpi_bytes.len(), &*acpi_bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_ram(&self) -> Result<()> {
|
||||
let config = &self.board.config;
|
||||
let memory = &self.board.memory;
|
||||
if config.mem_size > RAM_32_SIZE {
|
||||
memory.add_ram(
|
||||
AddrOpt::Fixed(0),
|
||||
UserMem::new_anon(RAM_32_SIZE)?,
|
||||
&[
|
||||
(BIOS_DATA_END, MemRegionType::Reserved),
|
||||
(EBDA_START - BIOS_DATA_END, MemRegionType::Ram),
|
||||
(EBDA_END - EBDA_START, MemRegionType::Acpi),
|
||||
(RAM_32_END - EBDA_END, MemRegionType::Ram),
|
||||
],
|
||||
)?;
|
||||
memory.add_ram(
|
||||
AddrOpt::Fixed(MEM_64_START),
|
||||
UserMem::new_anon(config.mem_size - RAM_32_END)?,
|
||||
&[(config.mem_size - RAM_32_END, MemRegionType::Ram)],
|
||||
)?;
|
||||
} else {
|
||||
memory.add_ram(
|
||||
AddrOpt::Fixed(0),
|
||||
UserMem::new_anon(config.mem_size)?,
|
||||
&[
|
||||
(BIOS_DATA_END, MemRegionType::Reserved),
|
||||
(EBDA_START - BIOS_DATA_END, MemRegionType::Ram),
|
||||
(EBDA_END - EBDA_START, MemRegionType::Acpi),
|
||||
(config.mem_size - EBDA_END, MemRegionType::Ram),
|
||||
],
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
33
docs/contributing.md
Normal file
33
docs/contributing.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
# How to contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project.
|
||||
|
||||
## Before you begin
|
||||
|
||||
### Sign our Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a
|
||||
[Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
|
||||
You (or your employer) retain the copyright to your contribution; this simply
|
||||
gives us permission to use and redistribute your contributions as part of the
|
||||
project.
|
||||
|
||||
If you or your current employer have already signed the Google CLA (even if it
|
||||
was for a different project), you probably don't need to do it again.
|
||||
|
||||
Visit <https://cla.developers.google.com/> to see your current agreements or to
|
||||
sign a new one.
|
||||
|
||||
### Review our community guidelines
|
||||
|
||||
This project follows
|
||||
[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
|
||||
|
||||
## Contribution process
|
||||
|
||||
### Code reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
2
rustfmt.toml
Normal file
2
rustfmt.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
imports_granularity = "Module"
|
||||
|
Loading…
Reference in a new issue