diff --git a/Cargo.lock b/Cargo.lock index 453d80380..7258ce15c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys", @@ -85,13 +85,13 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-trait" -version = "0.1.71" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.29", ] [[package]] @@ -136,9 +136,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "block-buffer" @@ -157,9 +157,12 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cexpr" @@ -189,9 +192,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.19" +version = "4.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3" dependencies = [ "clap_builder", "clap_derive", @@ -200,9 +203,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.19" +version = "4.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" +checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98" dependencies = [ "anstream", "anstyle", @@ -219,7 +222,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.29", ] [[package]] @@ -290,9 +293,9 @@ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "env_logger" @@ -313,15 +316,15 @@ version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "libc", ] [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" @@ -336,9 +339,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -357,12 +360,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "futures" @@ -421,7 +421,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.29", ] [[package]] @@ -533,15 +533,6 @@ dependencies = [ "hashbrown 0.14.0", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "intmap" version = "2.0.0" @@ -549,32 +540,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee87fd093563344074bacf24faa0bb0227fb6969fb223e922db798516de924d6" [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "is-terminal" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "libc", + "rustix", "windows-sys", ] [[package]] -name = "is-terminal" -version = "0.4.9" +name = "itertools" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ - "hermit-abi", - "rustix 0.38.3", - "windows-sys", + "either", ] [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "json5" @@ -646,21 +635,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" @@ -696,23 +679,23 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.29", ] [[package]] @@ -745,9 +728,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pest" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d2d1d55045829d65aad9d389139882ad623b33b904e7c9f1b10c5b8927298e5" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" dependencies = [ "thiserror", "ucd-trie", @@ -755,9 +738,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef623c9bbfa0eedf5a0efba11a5ee83209c326653ca31ff019bec3a95bfff2b" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" dependencies = [ "pest", "pest_generator", @@ -765,22 +748,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e8cba4ec22bada7fc55ffe51e2deb6a0e0db2d0b7ab0b103acc80d2510c190" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.29", ] [[package]] name = "pest_meta" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01f71cb40bd8bb94232df14b946909e14660e33fc05db3e50ae2a82d7ea0ca0" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" dependencies = [ "once_cell", "pest", @@ -789,9 +772,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -832,9 +815,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -880,9 +863,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", @@ -892,9 +875,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.2" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -903,9 +886,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "ron" @@ -936,28 +919,14 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.37.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" -dependencies = [ - "bitflags 1.3.2", - "errno 0.3.1", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys", -] - -[[package]] -name = "rustix" -version = "0.38.3" +version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags 2.3.3", - "errno 0.3.1", + "bitflags 2.4.0", + "errno 0.3.2", "libc", - "linux-raw-sys 0.4.3", + "linux-raw-sys", "windows-sys", ] @@ -969,29 +938,29 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "serde" -version = "1.0.168" +version = "1.0.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d614f89548720367ded108b3c843be93f3a341e22d5674ca0dd5cd57f34926af" +checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.168" +version = "1.0.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fe589678c688e44177da4f27152ee2d190757271dc7f1d5b6b9f68d869d641" +checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.29", ] [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -1000,9 +969,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.22" +version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "452e67b9c20c37fa79df53201dc03839651086ed9bbe92b3ca585ca9fdaa7d85" +checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ "indexmap", "itoa", @@ -1030,9 +999,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -1074,9 +1043,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -1100,15 +1069,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ - "autocfg", "cfg-if", "fastrand", "redox_syscall", - "rustix 0.37.23", + "rustix", "windows-sys", ] @@ -1123,22 +1091,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.29", ] [[package]] @@ -1158,9 +1126,9 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.12" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ "indexmap", "toml_datetime", @@ -1193,9 +1161,9 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unsafe-libyaml" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "utf8parse" @@ -1217,9 +1185,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vhost" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73832f4d8d636d63d9b145e8ef22a2c50b93f4d24eb7a99c9e6781b1b08549cf" +checksum = "61957aeb36daf0b00b87fff9c10dd28a161bd35ab157553d340d183b3d8756e6" dependencies = [ "bitflags 1.3.2", "libc", @@ -1285,6 +1253,24 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "vhost-device-scmi" +version = "0.1.0" +dependencies = [ + "assert_matches", + "clap", + "env_logger", + "itertools", + "log", + "thiserror", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-queue", + "vm-memory", + "vmm-sys-util", +] + [[package]] name = "vhost-device-scsi" version = "0.1.0" @@ -1331,9 +1317,9 @@ dependencies = [ [[package]] name = "vhost-user-backend" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ea9d5e8ec847cde4df1c04e586698a479706fd6beca37323f9d425b24b4c2f" +checksum = "ab069cdedaf18a0673766eb0a07a0f4ee3ed1b8e17fbfe4aafe5b988e2de1d01" dependencies = [ "libc", "log", @@ -1375,9 +1361,9 @@ dependencies = [ [[package]] name = "vm-memory" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c7a0891cbac53618f5f6eec650ed1dc4f7e506bbe14877aff49d94b8408b0" +checksum = "3750e9b70da7f2ce2f7bf942c886d45f9bae064135c398f05635bf77e926a2ef" dependencies = [ "arc-swap", "bitflags 1.3.2", @@ -1456,9 +1442,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1471,51 +1457,51 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.4.7" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 633fa4d3e..72e3adfe6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,11 @@ [workspace] +resolver = "2" members = [ "crates/gpio", "crates/i2c", "crates/rng", "crates/scsi", + "crates/scmi", "crates/vsock", ] diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 1773554ff..0586cbaae 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 68.2, + "coverage_score": 71.0, "exclude_path": "", "crate_features": "" } diff --git a/crates/i2c/src/i2c.rs b/crates/i2c/src/i2c.rs index ce55a3029..e214b3bbd 100644 --- a/crates/i2c/src/i2c.rs +++ b/crates/i2c/src/i2c.rs @@ -183,7 +183,7 @@ impl SmbusMsg { /// /// These smbus related functions try to reverse what Linux does, only /// support basic modes (up to word transfer). - fn new(reqs: &mut [I2cReq]) -> Result { + fn new(reqs: &[I2cReq]) -> Result { let mut data = I2cSmbusData { block: [0; I2C_SMBUS_BLOCK_MAX + 2], }; diff --git a/crates/scmi/CHANGELOG.md b/crates/scmi/CHANGELOG.md new file mode 100644 index 000000000..51d3f040d --- /dev/null +++ b/crates/scmi/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog +## [Unreleased] + +### Added + +### Changed + +### Fixed + +### Deprecated + +## [0.1.0] + +First release + diff --git a/crates/scmi/Cargo.toml b/crates/scmi/Cargo.toml new file mode 100644 index 000000000..be2929fbe --- /dev/null +++ b/crates/scmi/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "vhost-device-scmi" +version = "0.1.0" +authors = ["Milan Zamazal "] +description = "vhost-user SCMI backend device" +repository = "https://github.com/rust-vmm/vhost-device" +readme = "README.md" +keywords = ["scmi", "vhost", "virt", "backend"] +license = "Apache-2.0 OR BSD-3-Clause" +edition = "2021" + +[dependencies] +clap = { version = "4.3", features = ["derive"] } +env_logger = "0.10" +itertools = "0.10" +log = "0.4" +thiserror = "1.0" +vhost = { version = "0.8", features = ["vhost-user-slave"] } +vhost-user-backend = "0.10" +virtio-bindings = "0.2" +virtio-queue = "0.9" +vm-memory = "0.12" +vmm-sys-util = "0.11" + +[dev-dependencies] +assert_matches = "1.5" +virtio-queue = { version = "0.9", features = ["test-utils"] } diff --git a/crates/scmi/LICENSE-APACHE b/crates/scmi/LICENSE-APACHE new file mode 120000 index 000000000..1cd601d0a --- /dev/null +++ b/crates/scmi/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/scmi/LICENSE-BSD-3-Clause b/crates/scmi/LICENSE-BSD-3-Clause new file mode 120000 index 000000000..a60f1af6d --- /dev/null +++ b/crates/scmi/LICENSE-BSD-3-Clause @@ -0,0 +1 @@ +../../LICENSE-BSD-3-Clause \ No newline at end of file diff --git a/crates/scmi/README.md b/crates/scmi/README.md new file mode 100644 index 000000000..d16c090d8 --- /dev/null +++ b/crates/scmi/README.md @@ -0,0 +1,104 @@ +# vhost-device-scmi + +This program is a vhost-user backend for a VirtIO SCMI device. +It provides SCMI access to various entities on the host; not +necessarily only those providing an SCMI interface themselves. + +It is tested with QEMU's `-device vhost-user-scmi-pci` but should work +with any virtual machine monitor (VMM) that supports vhost-user. See +the Examples section below. + +## Synopsis + +**vhost-device-scmi** [*OPTIONS*] + +## Options + +.. program:: vhost-device-scmi + +.. option:: -h, --help + + Print help. + +.. option:: -s, --socket-path=PATH + + Location of the vhost-user Unix domain sockets. + +.. option:: -d, --device=SPEC + + SCMI device specification in the format `ID,PROPERTY=VALUE,...`. + For example: `-d iio,path=/sys/bus/iio/devices/iio:device0,channel=in_accel`. + Can be used multiple times for multiple exposed devices. + If no device is specified then no device will be provided to the + guest OS but VirtIO SCMI will be still available there. + Use `--help-devices` to list help on all the available devices. + +You can set `RUST_LOG` environment variable to `debug` to get maximum +messages on the standard error output. + +## Examples + +The daemon should be started first: + +:: + + host# vhost-device-scmi --socket-path=scmi.sock --device fake,name=foo + +The QEMU invocation needs to create a chardev socket the device can +use to communicate as well as share the guests memory over a memfd: + +:: + + host# qemu-system \ + -chardev socket,path=scmi.sock,id=scmi \ + -device vhost-user-scmi-pci,chardev=vscmi,id=scmi \ + -machine YOUR-MACHINE-OPTIONS,memory-backend=mem \ + -m 4096 \ + -object memory-backend-file,id=mem,size=4G,mem-path=/dev/shm,share=on \ + ... + +## Supported SCMI protocols + +The currently supported SCMI protocols are: + +- base +- sensor management + +Basically only the mandatory and necessary parts of the protocols are +implemented. + +See source code (`scmi` crate) documentation for details and how to +add more protocols, host device bindings or other functionality. + +## Testing + +SCMI is supported only on Arm in Linux. This restriction doesn't +apply to the host, which can be any architecture as long as the guest +is Arm. + +The easiest way to test it on the guest side is using the Linux SCMI +Industrial I/O driver there. If an 3-axes accelerometer or gyroscope +VirtIO SCMI device is present and the guest kernel is compiled with +`CONFIG_IIO_SCMI` enabled then the device should be available in +`/sys/bus/iio/devices/`. The vhost-device-scmi fake device is +suitable for this. + +Of course, other means of accessing SCMI devices can be used too. The +following Linux kernel command line can be useful to obtain SCMI trace +information, in addition to SCMI related messages in dmesg: +`trace_event=scmi:* ftrace=function ftrace_filter=scmi*`. + +### Kernel support for testing + +`kernel` subdirectory contains +[instructions](kernel/iio-dummy/README.md) how to create emulated +industrial I/O devices for testing. + +## License + +This project is licensed under either of + +- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0 +- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause) + +unless specified in particular files otherwise. diff --git a/crates/scmi/kernel/iio-dummy/.gitignore b/crates/scmi/kernel/iio-dummy/.gitignore new file mode 100644 index 000000000..a2337affb --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/.gitignore @@ -0,0 +1,7 @@ +*.cmd +*.ko +*.mod +*.mod.[co] +*.o +Module.symvers +modules.order diff --git a/crates/scmi/kernel/iio-dummy/Makefile b/crates/scmi/kernel/iio-dummy/Makefile new file mode 100644 index 000000000..f602b91cd --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the IIO Dummy Driver +# +# Modified by Milan Zamazal in 2023 for out of +# tree compilation. +# + +obj-m += iio_modified_dummy.o + +on_nixos = $(wildcard /etc/NIXOS) +ifeq ($(on_nixos), /etc/NIXOS) +nix_prefix = $(shell nix-build -E '(import {}).linux.dev' --no-out-link) +endif + +all: + make -C $(nix_prefix)/lib/modules/$(shell uname -r)/build M=$(PWD) modules +clean: + make -C $(nix_prefix)/lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/crates/scmi/kernel/iio-dummy/README.md b/crates/scmi/kernel/iio-dummy/README.md new file mode 100644 index 000000000..59e16f46d --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/README.md @@ -0,0 +1,185 @@ +# Using emulated industrial I/O devices + +This is a modified version of the Linux [industrial I/O dummy +driver](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/iio/dummy). +Both the original driver and this modification can provide emulated +industrial I/O devices for testing vhost-device-scmi. + +## Modifications in this module + +If the stock industrial I/O dummy driver is enough for you, use it +(but you may still want to read the instructions below). + +Otherwise, this alternative is provided with the following changes: + +- Simplified Makefile for out of tree compilation. +- The accelerometer has three axes instead of just one. +- The Y axis of the accelerometer has offset and scale. + +Of course, you can modified it further for your liking if needed. + +## How to create emulated industrial I/O devices + +Make sure your kernel supports software industrial I/O devices and +industrial I/O with configfs. You can check this by running `modprobe +industrialio_sw_device && modprobe industrialio_configfs`. If any of +the modules is not present, follow the [instructions for recompiling +kernel](#recompiling-kernel-with-industrial-io) below. + +Make sure you have the right kernel version. Since Linux 5.19, the +dummy industrial I/O driver is broken. This will be probably fixed in +Linux 6.6. + +If you have a broken kernel version, apply the +[fix](./iio-dummy-fix.patch) and compile and install the modified +kernel. + +If you want to use the modified module from here, compile it. In +order to do this, you must have kernel development environment +installed, for example: + +- Fedora or derivatives: `dnf install kernel-devel kernel-modules make` +- Debian or derivatives: `apt install linux-headers-$(uname -r) make` +- NixOS: `nix-shell '' -A linux.dev` + +Then you can compile the module, simply running `make` should work on +most distributions. + +Insert a dummy industrial I/O kernel module. Either the stock one: + +``` +# modprobe iio-dummy +``` + +or the modified one from here: + +``` +# modprobe industrialio +# modprobe industrialio_configfs +# modprobe industrialio_sw_device +# insmod ./iio-dummy-modified.ko +``` + +Find out where configfs is mounted: `mount | grep configfs`. It's +typically `/sys/kernel/config`. If configfs is not mounted, mount it +somewhere: `mount -t configfs none MOUNTPOINT`. + +Now you can create emulated industrial I/O devices with the stock driver: + +``` +# mkdir /sys/kernel/config/iio/devices/dummy/my-device +``` + +And/or with the modified driver from here: + +``` +# mkdir /sys/kernel/config/iio/devices/dummy-modified/my-device +``` + +If everything is OK then you can find the device in +`/sys/bus/iio/devices/`. + +## Recompiling kernel with industrial I/O + +Making a custom kernel is different on each GNU/Linux distribution. +The corresponding documentation can be found for example here: + +- Fedora: [https://fedoraproject.org/wiki/Building_a_custom_kernel](https://fedoraproject.org/wiki/Building_a_custom_kernel) +- CentOS Stream: [https://wiki.centos.org/HowTos/BuildingKernelModules](https://wiki.centos.org/HowTos/BuildingKernelModules) + (looks more useful for Fedora builds than CentOS) +- Debian: [https://kernel-team.pages.debian.net/kernel-handbook/ch-common-tasks.html#s-common-official](https://kernel-team.pages.debian.net/kernel-handbook/ch-common-tasks.html#s-common-official) +- NixOS: [https://nixos.wiki/wiki/Linux_kernel](https://nixos.wiki/wiki/Linux_kernel) + +Here are instructions for Fedora, similar steps can be used for other +distributions, with distribution specifics as described in the links +above. This is not necessarily the most official or the best way to +do it but it's a way that *actually works* for me. + +Note on CentOS Stream 9: The kernel there doesn't contain the needed +modules. Recompiling the kernel on CentOS Stream may be challenging +due to missing build dependencies. If it doesn't work for you, you +can try to use Fedora kernel and modules on CentOS Stream, including +the dummy module compiled on Fedora. + +### Install kernel sources + +``` +# dnf install 'dnf-command(download)' +$ dnf download --source kernel +$ rpm -i kernel-*.src.rpm +# dnf builddep ~/rpmbuild/SPECS/kernel.spec +``` + +### Change kernel configuration + +Not needed for current Fedora but may be needed for e.g. CentOS Stream. + +``` +# dnf install kernel-devel kernel-modules make rpm-build python3-devel ncurses-devel +$ rpmbuild -bp ~/rpmbuild/SPECS/kernel.spec +$ cd ~/rpmbuild/BUILD/kernel-*/linux-*/ +$ cp configs/kernel-VERSION-YOURARCH.config .config +$ make nconfig +``` + +Configuration options that must be enabled: + +- Device Drivers -> Industrial I/O Support -> Enable IIO configuration via configfs +- Device Drivers -> Industrial I/O Support -> Enable software IIO device support + +Optionally (you can use the alternative driver from here instead): + +- Device Drivers -> Industrial I/O Support -> IIO dummy drive -> An example driver with no hardware requirements + +Then copy `.config` back to its original file and don't forget to add +the original architecture specification line there. + +### Apply the kernel fix + +If the kernel fix from here is needed, copy it to the sources: + +``` +cp .../iio-dummy-fix.patch ~/rpmbuild/SOURCES/ +``` + +Edit `~/rpmbuild/SPECS/kernel.spec`: + +- Uncomment: `%define buildid .local`. + +- Add the patch file before: `Patch999999: linux-kernel-test.patch`. + +- Add the patch file before: `ApplyOptionalPatch linux-kernel-test.patch`. + +### Build the kernel + +You can use different options, if you don't need anything extra then +the following builds the most important rpm's: + +``` +$ rpmbuild -bb --with baseonly --without debug --without debuginfo ~/rpmbuild/SPECS/kernel.spec +``` + +## Adding industrial I/O dummy module to your kernel + +If all you need is to add a missing stock I/O dummy module, you can +try to compile just the module. Switch to kernel sources and run: + +``` +$ make oldconfig +$ make prepare +$ make modules_prepare +$ make M=drivers/iio/dummy +``` + +And insert the module: + +``` +# modprobe industrialio +# modprobe industrialio_configfs +# modprobe industrialio_sw_device +# insmod ./drivers/iio/dummy/iio-dummy.ko +``` + +If this fails, inspect `dmesg` output and try to figure out what's +wrong. If this fails too, rebuild the whole kernel with the given +module enabled. diff --git a/crates/scmi/kernel/iio-dummy/iio-dummy-fix.patch b/crates/scmi/kernel/iio-dummy/iio-dummy-fix.patch new file mode 100644 index 000000000..d74b7d5b6 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/iio-dummy-fix.patch @@ -0,0 +1,55 @@ +Commit 813665564b3d ("iio: core: Convert to use firmware node handle +instead of OF node") switched the kind of nodes to use for label +retrieval in device registration. Probably an unwanted change in that +commit was that if the device has no parent then NULL pointer is +accessed. This is what happens in the stock IIO dummy driver when a +new entry is created in configfs: + + # mkdir /sys/kernel/config/iio/devices/dummy/foo + BUG: kernel NULL pointer dereference, address: ... + ... + Call Trace: + __iio_device_register + iio_dummy_probe + +Since there seems to be no reason to make a parent device of an IIO +dummy device mandatory, let’s prevent the invalid memory access in +__iio_device_register when the parent device is NULL. With this +change, the IIO dummy driver works fine with configfs. + +Fixes: 813665564b3d ("iio: core: Convert to use firmware node handle instead of OF node") +Reviewed-by: Andy Shevchenko +Signed-off-by: Milan Zamazal +--- + drivers/iio/industrialio-core.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c +index c117f50d0cf3..adcba832e6fa 100644 +--- a/drivers/iio/industrialio-core.c ++++ b/drivers/iio/industrialio-core.c +@@ -1888,7 +1888,7 @@ static const struct iio_buffer_setup_ops noop_ring_setup_ops; + int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod) + { + struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); +- struct fwnode_handle *fwnode; ++ struct fwnode_handle *fwnode = NULL; + int ret; + + if (!indio_dev->info) +@@ -1899,7 +1899,8 @@ int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod) + /* If the calling driver did not initialize firmware node, do it here */ + if (dev_fwnode(&indio_dev->dev)) + fwnode = dev_fwnode(&indio_dev->dev); +- else ++ /* The default dummy IIO device has no parent */ ++ else if (indio_dev->dev.parent) + fwnode = dev_fwnode(indio_dev->dev.parent); + device_set_node(&indio_dev->dev, fwnode); + +-- + +2.40.1 + + + diff --git a/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c new file mode 100644 index 000000000..dd5e59468 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.c @@ -0,0 +1,706 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011 Jonathan Cameron + * + * A reference industrial I/O driver to illustrate the functionality available. + * + * There are numerous real drivers to illustrate the finer points. + * The purpose of this driver is to provide a driver with far more comments + * and explanatory notes than any 'real' driver would have. + * Anyone starting out writing an IIO driver should first make sure they + * understand all of this driver except those bits specifically marked + * as being present to allow us to 'fake' the presence of hardware. + * + * Changes by Milan Zamazal 2023, for testing + * with vhost-device-scmi: + * + * - Dropped conditional parts. + * - Use 3 axes in the accelerometer device. + * - Define offset and scale for some of the accelerometer axes. + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "iio_modified_dummy.h" + +static const struct config_item_type iio_dummy_type = { + .ct_owner = THIS_MODULE, +}; + +/** + * struct iio_dummy_accel_calibscale - realworld to register mapping + * @val: first value in read_raw - here integer part. + * @val2: second value in read_raw etc - here micro part. + * @regval: register value - magic device specific numbers. + */ +struct iio_dummy_accel_calibscale { + int val; + int val2; + int regval; /* what would be written to hardware */ +}; + +static const struct iio_dummy_accel_calibscale dummy_scales[] = { + { 0, 100, 0x8 }, /* 0.000100 */ + { 0, 133, 0x7 }, /* 0.000133 */ + { 733, 13, 0x9 }, /* 733.000013 */ +}; + +/* + * iio_dummy_channels - Description of available channels + * + * This array of structures tells the IIO core about what the device + * actually provides for a given channel. + */ +static const struct iio_chan_spec iio_dummy_channels[] = { + /* indexed ADC channel in_voltage0_raw etc */ + { + .type = IIO_VOLTAGE, + /* Channel has a numeric index of 0 */ + .indexed = 1, + .channel = 0, + /* What other information is available? */ + .info_mask_separate = + /* + * in_voltage0_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + BIT(IIO_CHAN_INFO_RAW) | + /* + * in_voltage0_offset + * Offset for userspace to apply prior to scale + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_OFFSET) | + /* + * in_voltage0_scale + * Multipler for userspace to apply post offset + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + /* The ordering of elements in the buffer via an enum */ + .scan_index = DUMMY_INDEX_VOLTAGE_0, + .scan_type = { /* Description of storage in buffer */ + .sign = 'u', /* unsigned */ + .realbits = 13, /* 13 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* Differential ADC channel in_voltage1-voltage2_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + /* + * Indexing for differential channels uses channel + * for the positive part, channel2 for the negative. + */ + .indexed = 1, + .channel = 1, + .channel2 = 2, + /* + * in_voltage1-voltage2_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + /* + * in_voltage-voltage_scale + * Shared version of scale - shared by differential + * input channels of type IIO_VOLTAGE. + */ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .scan_index = DUMMY_INDEX_DIFFVOLTAGE_1M2, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 12, /* 12 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* Differential ADC channel in_voltage3-voltage4_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 3, + .channel2 = 4, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_DIFFVOLTAGE_3M4, + .scan_type = { + .sign = 's', + .realbits = 11, + .storagebits = 16, + .shift = 0, + }, + }, + /* + * 'modified' (i.e. axis specified) acceleration channel + * in_accel_[xyz]_raw + */ + { + .type = IIO_ACCEL, + .modified = 1, + /* Channel 2 is use for modifiers */ + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + /* + * Internal bias and gain correction values. Applied + * by the hardware or driver prior to userspace + * seeing the readings. Typically part of hardware + * calibration. + */ + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_ACCEL_X, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 16, /* 16 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + { + .type = IIO_ACCEL, + .modified = 1, + /* Channel 2 is use for modifiers */ + .channel2 = IIO_MOD_Y, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_ACCEL_Y, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 16, /* 16 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + { + .type = IIO_ACCEL, + .modified = 1, + /* Channel 2 is use for modifiers */ + .channel2 = IIO_MOD_Z, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_ACCEL_Z, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 16, /* 16 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* + * Convenience macro for timestamps. 4 is the index in + * the buffer. + */ + IIO_CHAN_SOFT_TIMESTAMP(4), + /* DAC channel out_voltage0_raw */ + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = -1, /* No buffer support */ + .output = 1, + .indexed = 1, + .channel = 0, + }, + { + .type = IIO_STEPS, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_CALIBHEIGHT), + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_RUNNING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_WALKING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ + }, +}; + +/** + * iio_dummy_read_raw() - data read function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be read + * @val: first element of returned value (typically INT) + * @val2: second element of returned value (typically MICRO) + * @mask: what we actually want to read as per the info_mask_* + * in iio_chan_spec. + */ +static int iio_dummy_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: /* magic value - channel value read */ + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) { + /* Set integer part to cached value */ + *val = st->dac_val; + ret = IIO_VAL_INT; + } else if (chan->differential) { + if (chan->channel == 1) + *val = st->differential_adc_val[0]; + else + *val = st->differential_adc_val[1]; + ret = IIO_VAL_INT; + } else { + *val = st->single_ended_adc_val; + ret = IIO_VAL_INT; + } + break; + case IIO_ACCEL: + switch(chan->scan_index) { + case DUMMY_INDEX_ACCEL_X: + *val = st->accel_val[0]; + break; + case DUMMY_INDEX_ACCEL_Y: + *val = st->accel_val[1]; + break; + case DUMMY_INDEX_ACCEL_Z: + *val = st->accel_val[2]; + break; + default: + *val = 0; + } + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + *val = st->steps; + ret = IIO_VAL_INT; + break; + case IIO_ACTIVITY: + switch (chan->channel2) { + case IIO_MOD_RUNNING: + *val = st->activity_running; + ret = IIO_VAL_INT; + break; + case IIO_MOD_WALKING: + *val = st->activity_walking; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + default: + break; + } + break; + case IIO_CHAN_INFO_OFFSET: + /* only single ended adc -> 7 */ + *val = 7; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + switch (chan->differential) { + case 0: + /* only single ended adc -> 0.001333 */ + *val = 0; + *val2 = 1333; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case 1: + /* all differential adc -> 0.000001344 */ + *val = 0; + *val2 = 1344; + ret = IIO_VAL_INT_PLUS_NANO; + } + break; + case IIO_ACCEL: + switch(chan->scan_index) { + case DUMMY_INDEX_ACCEL_Y: + *val = 0; + *val2 = 1344; + break; + } + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + /* only the acceleration axis - read from cache */ + *val = st->accel_calibbias; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + *val = st->accel_calibscale->val; + *val2 = st->accel_calibscale->val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = 3; + *val2 = 33; + ret = IIO_VAL_INT_PLUS_NANO; + break; + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_STEPS: + *val = st->steps_enabled; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_CALIBHEIGHT: + switch (chan->type) { + case IIO_STEPS: + *val = st->height; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + + default: + break; + } + mutex_unlock(&st->lock); + return ret; +} + +/** + * iio_dummy_write_raw() - data write function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be written + * @val: first element of value to set (typically INT) + * @val2: second element of value to set (typically MICRO) + * @mask: what we actually want to write as per the info_mask_* + * in iio_chan_spec. + * + * Note that all raw writes are assumed IIO_VAL_INT and info mask elements + * are assumed to be IIO_INT_PLUS_MICRO unless the callback write_raw_get_fmt + * in struct iio_info is provided by the driver. + */ +static int iio_dummy_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int i; + int ret = 0; + struct iio_dummy_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output == 0) + return -EINVAL; + + /* Locking not required as writing single value */ + mutex_lock(&st->lock); + st->dac_val = val; + mutex_unlock(&st->lock); + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + mutex_lock(&st->lock); + st->steps = val; + mutex_unlock(&st->lock); + return 0; + case IIO_ACTIVITY: + if (val < 0) + val = 0; + if (val > 100) + val = 100; + switch (chan->channel2) { + case IIO_MOD_RUNNING: + st->activity_running = val; + return 0; + case IIO_MOD_WALKING: + st->activity_walking = val; + return 0; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBSCALE: + mutex_lock(&st->lock); + /* Compare against table - hard matching here */ + for (i = 0; i < ARRAY_SIZE(dummy_scales); i++) + if (val == dummy_scales[i].val && + val2 == dummy_scales[i].val2) + break; + if (i == ARRAY_SIZE(dummy_scales)) + ret = -EINVAL; + else + st->accel_calibscale = &dummy_scales[i]; + mutex_unlock(&st->lock); + return ret; + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&st->lock); + st->accel_calibbias = val; + mutex_unlock(&st->lock); + return 0; + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_STEPS: + mutex_lock(&st->lock); + st->steps_enabled = val; + mutex_unlock(&st->lock); + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBHEIGHT: + switch (chan->type) { + case IIO_STEPS: + st->height = val; + return 0; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +/* + * Device type specific information. + */ +static const struct iio_info iio_dummy_info = { + .read_raw = &iio_dummy_read_raw, + .write_raw = &iio_dummy_write_raw, +}; + +/** + * iio_dummy_init_device() - device instance specific init + * @indio_dev: the iio device structure + * + * Most drivers have one of these to set up default values, + * reset the device to known state etc. + */ +static int iio_dummy_init_device(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->dac_val = 0; + st->single_ended_adc_val = 73; + st->differential_adc_val[0] = 33; + st->differential_adc_val[1] = -34; + st->accel_val[0] = 34; + st->accel_val[1] = 37; + st->accel_val[2] = 40; + st->accel_calibbias = -7; + st->accel_calibscale = &dummy_scales[0]; + st->steps = 47; + st->activity_running = 98; + st->activity_walking = 4; + + return 0; +} + +/** + * iio_dummy_probe() - device instance probe + * @name: name of this instance. + * + * Arguments are bus type specific. + * I2C: iio_dummy_probe(struct i2c_client *client, + * const struct i2c_device_id *id) + * SPI: iio_dummy_probe(struct spi_device *spi) + */ +static struct iio_sw_device *iio_dummy_probe(const char *name) +{ + int ret; + struct iio_dev *indio_dev; + struct iio_dummy_state *st; + struct iio_sw_device *swd; + struct device *parent; + + /* + * With hardware: Set the parent device. + * parent = &spi->dev; + * parent = &client->dev; + */ + + swd = kzalloc(sizeof(*swd), GFP_KERNEL); + if (!swd) + return ERR_PTR(-ENOMEM); + + /* + * Allocate an IIO device. + * + * This structure contains all generic state + * information about the device instance. + * It also has a region (accessed by iio_priv() + * for chip specific state information. + */ + indio_dev = iio_device_alloc(parent, sizeof(*st)); + if (!indio_dev) { + ret = -ENOMEM; + goto error_free_swd; + } + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + + iio_dummy_init_device(indio_dev); + + /* + * Make the iio_dev struct available to remove function. + * Bus equivalents + * i2c_set_clientdata(client, indio_dev); + * spi_set_drvdata(spi, indio_dev); + */ + swd->device = indio_dev; + + /* + * Set the device name. + * + * This is typically a part number and obtained from the module + * id table. + * e.g. for i2c and spi: + * indio_dev->name = id->name; + * indio_dev->name = spi_get_device_id(spi)->name; + */ + indio_dev->name = kstrdup(name, GFP_KERNEL); + if (!indio_dev->name) { + ret = -ENOMEM; + goto error_free_device; + } + + /* Provide description of available channels */ + indio_dev->channels = iio_dummy_channels; + indio_dev->num_channels = ARRAY_SIZE(iio_dummy_channels); + + /* + * Provide device type specific interface functions and + * constant data. + */ + indio_dev->info = &iio_dummy_info; + + /* Specify that device provides sysfs type interfaces */ + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_free_name; + + iio_swd_group_init_type_name(swd, name, &iio_dummy_type); + + return swd; +error_free_name: + kfree(indio_dev->name); +error_free_device: + iio_device_free(indio_dev); +error_free_swd: + kfree(swd); + return ERR_PTR(ret); +} + +/** + * iio_dummy_remove() - device instance removal function + * @swd: pointer to software IIO device abstraction + * + * Parameters follow those of iio_dummy_probe for buses. + */ +static int iio_dummy_remove(struct iio_sw_device *swd) +{ + /* + * Get a pointer to the device instance iio_dev structure + * from the bus subsystem. E.g. + * struct iio_dev *indio_dev = i2c_get_clientdata(client); + * struct iio_dev *indio_dev = spi_get_drvdata(spi); + */ + struct iio_dev *indio_dev = swd->device; + + /* Unregister the device */ + iio_device_unregister(indio_dev); + + /* Free all structures */ + kfree(indio_dev->name); + iio_device_free(indio_dev); + + return 0; +} + +/* + * module_iio_sw_device_driver() - device driver registration + * + * Varies depending on bus type of the device. As there is no device + * here, call probe directly. For information on device registration + * i2c: + * Documentation/i2c/writing-clients.rst + * spi: + * Documentation/spi/spi-summary.rst + */ +static const struct iio_sw_device_ops iio_dummy_device_ops = { + .probe = iio_dummy_probe, + .remove = iio_dummy_remove, +}; + +static struct iio_sw_device_type iio_dummy_device = { + .name = "dummy-modified", + .owner = THIS_MODULE, + .ops = &iio_dummy_device_ops, +}; + +module_iio_sw_device_driver(iio_dummy_device); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/crates/scmi/kernel/iio-dummy/iio_modified_dummy.h b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.h new file mode 100644 index 000000000..9b0b8b171 --- /dev/null +++ b/crates/scmi/kernel/iio-dummy/iio_modified_dummy.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * Join together the various functionality of iio_modified_dummy driver + * + * Changes by Milan Zamazal 2023, for testing + * with vhost-device-scmi: + * + * - Dropped conditional parts. + * - Use 3 axes in the accelerometer device. + */ + +#ifndef _IIO_MODIFIED_DUMMY_H_ +#define _IIO_MODIFIED_DUMMY_H_ +#include + +struct iio_dummy_accel_calibscale; +struct iio_dummy_regs; + +/** + * struct iio_dummy_state - device instance specific state. + * @dac_val: cache for dac value + * @single_ended_adc_val: cache for single ended adc value + * @differential_adc_val: cache for differential adc value + * @accel_val: cache for acceleration value + * @accel_calibbias: cache for acceleration calibbias + * @accel_calibscale: cache for acceleration calibscale + * @lock: lock to ensure state is consistent + * @event_irq: irq number for event line (faked) + * @event_val: cache for event threshold value + * @event_en: cache of whether event is enabled + */ +struct iio_dummy_state { + int dac_val; + int single_ended_adc_val; + int differential_adc_val[2]; + int accel_val[3]; + int accel_calibbias; + int activity_running; + int activity_walking; + const struct iio_dummy_accel_calibscale *accel_calibscale; + struct mutex lock; + struct iio_dummy_regs *regs; + int steps_enabled; + int steps; + int height; +}; + +/** + * enum iio_modified_dummy_scan_elements - scan index enum + * @DUMMY_INDEX_VOLTAGE_0: the single ended voltage channel + * @DUMMY_INDEX_DIFFVOLTAGE_1M2: first differential channel + * @DUMMY_INDEX_DIFFVOLTAGE_3M4: second differential channel + * @DUMMY_INDEX_ACCELX: acceleration channel + * + * Enum provides convenient numbering for the scan index. + */ +enum iio_modified_dummy_scan_elements { + DUMMY_INDEX_VOLTAGE_0, + DUMMY_INDEX_DIFFVOLTAGE_1M2, + DUMMY_INDEX_DIFFVOLTAGE_3M4, + DUMMY_INDEX_ACCEL_X, + DUMMY_INDEX_ACCEL_Y, + DUMMY_INDEX_ACCEL_Z, +}; + +#endif /* _IIO_MODIFIED_DUMMY_H_ */ diff --git a/crates/scmi/src/devices/common.rs b/crates/scmi/src/devices/common.rs new file mode 100644 index 000000000..ec51b2cf5 --- /dev/null +++ b/crates/scmi/src/devices/common.rs @@ -0,0 +1,560 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! Common functionality for SCMI bindings to host devices. +//! +//! A new kind of devices can be added in [available_devices] using +//! [DeviceSpecification::new] calls. +//! +//! The module also defines common infrastructure to provide sensor devices to +//! SCMI, see [SensorT]. + +use std::collections::{HashMap, HashSet}; +use std::ffi::OsString; +use std::fmt::Write; + +use itertools::Itertools; +use log::debug; +use thiserror::Error as ThisError; + +use crate::scmi::{ + self, DeviceResult, MessageId, MessageValue, MessageValues, ProtocolId, ScmiDevice, + ScmiDeviceError, MAX_SIMPLE_STRING_LENGTH, SENSOR_AXIS_DESCRIPTION_GET, SENSOR_CONFIG_GET, + SENSOR_CONFIG_SET, SENSOR_CONTINUOUS_UPDATE_NOTIFY, SENSOR_DESCRIPTION_GET, SENSOR_PROTOCOL_ID, + SENSOR_READING_GET, +}; + +use super::{fake, iio}; + +/// Non-SCMI related device errors. +#[derive(Debug, ThisError)] +pub enum DeviceError { + #[error("{0}")] + GenericError(String), + #[error("Invalid device parameter: {0}")] + InvalidProperty(String), + #[error("I/O error on {0:?}: {1}")] + IOError(OsString, std::io::Error), + #[error("Missing device parameters: {}", .0.join(", "))] + MissingDeviceProperties(Vec), + #[error("Unexpected device parameters: {}", .0.join(", "))] + UnexpectedDeviceProperties(Vec), +} + +// [(NAME, [(PROPERTY, VALUE), ...]), ...] +pub type DeviceDescription = Vec<(String, DeviceProperties)>; +type PropertyPairs = Vec<(String, String)>; + +#[derive(Debug, Eq, PartialEq, Hash)] +pub struct DeviceProperties(PropertyPairs); + +impl DeviceProperties { + pub(crate) fn new(properties: PropertyPairs) -> Self { + Self(properties) + } + + pub(crate) fn get(&self, name: &str) -> Option<&str> { + self.0 + .iter() + .find(|(n, _)| n == name) + .map(|(_, v)| v.as_str()) + } + + fn names(&self) -> HashSet<&str> { + self.0.iter().map(|(n, _)| -> &str { n.as_str() }).collect() + } + + fn extra<'a>(&'a self, allowed: &[&'a str]) -> HashSet<&str> { + let allowed_set: HashSet<&str> = HashSet::from_iter(allowed.iter().copied()); + self.names().difference(&allowed_set).copied().collect() + } + + fn missing<'a>(&'a self, required: &[&'a str]) -> HashSet<&str> { + let required_set: HashSet<&str> = HashSet::from_iter(required.iter().copied()); + required_set.difference(&self.names()).copied().collect() + } + + pub(crate) fn check(&self, required: &[&str], optional: &[&str]) -> Result<(), DeviceError> { + let missing = self.missing(required); + if !missing.is_empty() { + return Err(DeviceError::MissingDeviceProperties( + missing + .iter() + .sorted() + .map(|s| (*s).to_owned()) + .collect::>(), + )); + } + let mut all_allowed = Vec::from(required); + all_allowed.extend(optional.iter()); + let extra = self.extra(&all_allowed); + if !extra.is_empty() { + return Err(DeviceError::UnexpectedDeviceProperties( + extra + .iter() + .sorted() + .map(|s| (*s).to_owned()) + .collect::>(), + )); + } + Ok(()) + } +} + +pub type MaybeDevice = Result, DeviceError>; +type DeviceConstructor = fn(&DeviceProperties) -> MaybeDevice; + +/// Definition of a device kind. +/// +/// Use [DeviceSpecification::new] to create it. +pub struct DeviceSpecification { + /// Function to call to create the device. + /// + /// The device properties are those provided on the command line by the + /// user. + pub(crate) constructor: DeviceConstructor, + /// Short description of the device. + /// + /// Single line, not a complete sentence. + short_help: String, + /// Long description of the device. + /// + /// Complete sentences, can span multiple lines. + long_help: String, + /// Description of the device parameters available to the user. + /// + /// Each item in the vector corresponds to a single parameter description + /// and should start with the parameter name and a followup colon. + parameters_help: Vec, +} + +impl DeviceSpecification { + /// Creates a new device specification. + /// + /// See [DeviceSpecification] for the meaning of the arguments. + /// The device specification must be used in [available_devices] to + /// actually add the device. + fn new( + constructor: DeviceConstructor, + short_help: &str, + long_help: &str, + parameters_help: &[&str], + ) -> Self { + Self { + constructor, + short_help: short_help.to_owned(), + long_help: long_help.to_owned(), + parameters_help: parameters_help + .iter() + .map(|s| String::from(*s)) + .collect::>(), + } + } +} + +/// Mapping of device identifiers (names) to device specifications. +/// +/// The string keys correspond to device identifiers specified on the command +/// line. +type NameDeviceMapping = HashMap<&'static str, DeviceSpecification>; + +/// Creates device mapping and adds all the supported devices to it. +/// +/// If you want to introduce a new kind of host device bindings, insert a +/// device identifier + [DeviceSpecification] to [NameDeviceMapping] here. +pub fn available_devices() -> NameDeviceMapping { + let mut devices: NameDeviceMapping = HashMap::new(); + devices.insert( + "fake", + DeviceSpecification::new( + fake::FakeSensor::new_device, + "fake accelerometer", + "A simple 3-axes sensor providing fake pre-defined values.", + &["name: an optional name of the sensor, max. 15 characters"], + ), + ); + devices.insert( + "iio", + DeviceSpecification::new( + iio::IIOSensor::new_device, + "industrial I/O sensor", + "", + &[ + "path: path to the device directory (e.g. /sys/bus/iio/devices/iio:device0)", + "channel: prefix of the device type (e.g. in_accel)", + "name: an optional name of the sensor, max. 15 characters", + ], + ), + ); + devices +} + +fn devices_help() -> String { + let mut help = String::new(); + writeln!(help, "Available devices:").unwrap(); + for (name, specification) in available_devices().iter() { + let short_help = &specification.short_help; + let long_help = &specification.long_help; + let parameters_help = &specification.parameters_help; + writeln!(help, "\n- {name}: {short_help}").unwrap(); + for line in long_help.lines() { + writeln!(help, " {line}").unwrap(); + } + if !parameters_help.is_empty() { + writeln!(help, " Parameters:").unwrap(); + for parameter in parameters_help { + writeln!(help, " - {parameter}").unwrap(); + } + } + } + writeln!(help, "\nDevice specification example:").unwrap(); + writeln!( + help, + "--device iio,path=/sys/bus/iio/devices/iio:device0,channel=in_accel" + ) + .unwrap(); + help +} + +pub fn print_devices_help() { + let help = devices_help(); + println!("{}", help); +} + +// Common sensor infrastructure + +/// Basic information about the sensor. +/// +/// It is typically used as a field in structs implementing sensor devices. +#[derive(Debug)] +pub struct Sensor { + /// The sensor name (possibly truncated) as reported to the guest. + pub name: String, + /// Whether the sensor is enabled. + /// + /// Sensors can be enabled and disabled using SCMI. [Sensor]s created + /// using [Sensor::new] are disabled initially. + enabled: bool, +} + +impl Sensor { + pub fn new(properties: &DeviceProperties, default_name: &str) -> Self { + let name = properties.get("name").unwrap_or(default_name); + Self { + name: name.to_owned(), + enabled: false, + } + } +} + +/// Common base that sensor devices can use to simplify their implementation. +/// +/// To add a new kind of sensor bindings, you must implement +/// [crate::scmi::ScmiDevice], define [DeviceSpecification] and add it to +/// [NameDeviceMapping] created in [available_devices]. You can do it fully +/// yourself or use this trait to simplify the implementation. +/// +/// The trait is typically used as follows: +/// +/// ```rust +/// struct MySensor { +/// sensor: Sensor, +/// // other fields as needed +/// } +/// +/// impl SensorT for MySensor { +/// // provide trait functions implementation as needed +/// } +/// +/// impl MySensor { +/// pub fn new_device(properties: &DeviceProperties) -> MaybeDevice { +/// check_device_properties(properties, &[], &["name"])?; +/// let sensor = Sensor::new(properties, "mydevice"); +/// let my_sensor = MySensor { sensor }; +/// let sensor_device = SensorDevice(Box::new(my_sensor)); +/// Ok(Box::new(sensor_device)) +/// } +/// } +/// ``` +/// +/// See [crate::devices::fake::FakeSensor] implementation for an example. +pub trait SensorT: Send { + /// Returns the inner [Sensor] instance, immutable. + fn sensor(&self) -> &Sensor; + /// Returns the inner [Sensor] instance, mutable. + fn sensor_mut(&mut self) -> &mut Sensor; + + /// Performs any non-default initialization on the sensor. + /// + /// If the initialization fails, a corresponding error message is + /// returned. + fn initialize(&mut self) -> Result<(), DeviceError> { + Ok(()) + } + + /// Returns the id of the SCMI protocol used to communicate with the + /// sensor. + /// + /// Usually no need to redefine this. + fn protocol(&self) -> ProtocolId { + SENSOR_PROTOCOL_ID + } + + /// Returns an error message about invalid property `name`. + /// + /// Usually no need to redefine this. + fn invalid_property(&self, name: &str) -> Result<(), DeviceError> { + Result::Err(DeviceError::InvalidProperty(name.to_owned())) + } + + /// Processes a device property specified on the command line. + /// + /// The function is called on all the device properties from the command line. + fn process_property(&mut self, name: &str, _value: &str) -> Result<(), DeviceError> { + self.invalid_property(name) + } + + /// Returns the number of axes of the given sensor. + /// + /// If the sensor provides just a scalar value, 0 must be returned (the + /// default return value here). Otherwise a non-zero value must be + /// returned, even for vector sensors with a single access. + fn number_of_axes(&self) -> u32 { + 0 + } + + /// Formats the unit of the given `axis` for SCMI protocol. + /// + /// Usually no need to redefine this. + fn format_unit(&self, axis: u32) -> u32 { + (self.unit_exponent(axis) as u32 & 0x1F) << 11 | u32::from(self.unit()) + } + + /// Returns SCMI description of the sensor. + /// + /// Usually no need to redefine this. + fn description_get(&self) -> DeviceResult { + // Continuous update required by Linux SCMI IIO driver + let low = 1 << 30; + let n_axes = self.number_of_axes(); + let high = if n_axes > 0 { + n_axes << 16 | 1 << 8 + } else { + self.format_unit(0) + }; + let name = self.sensor().name.clone(); + let values: MessageValues = vec![ + // attributes low + MessageValue::Unsigned(low), + // attributes high + MessageValue::Unsigned(high), + // name, up to 16 bytes with final NULL (non-extended version) + MessageValue::String(name, MAX_SIMPLE_STRING_LENGTH), + ]; + Ok(values) + } + + /// Returns the SCMI unit of the sensor. + fn unit(&self) -> u8 { + scmi::SENSOR_UNIT_UNSPECIFIED + } + + /// Returns the decadic exponent to apply to the sensor values. + fn unit_exponent(&self, _axis: u32) -> i8 { + 0 + } + + /// Returns the prefix of axes names. + /// + /// Usually no need to redefine this. + fn axis_name_prefix(&self) -> String { + "axis".to_owned() + } + + /// Returns the suffix of the given axis. + /// + /// Usually no need to redefine this. + fn axis_name_suffix(&self, axis: u32) -> char { + match axis { + 0 => 'X', + 1 => 'Y', + 2 => 'Z', + _ => 'N', // shouldn't be reached currently + } + } + + /// Returns the SCMI description of the given axis. + /// + /// Usually no need to redefine this. + fn axis_description(&self, axis: u32) -> Vec { + let mut values = vec![]; + values.push(MessageValue::Unsigned(axis)); // axis id + values.push(MessageValue::Unsigned(0)); // attributes low + values.push(MessageValue::Unsigned(self.format_unit(axis))); // attributes high + + // Name in the recommended format, 16 bytes: + let prefix = self.axis_name_prefix(); + let suffix = self.axis_name_suffix(axis); + values.push(MessageValue::String( + format!("{prefix}_{suffix}"), + MAX_SIMPLE_STRING_LENGTH, + )); + values + } + + /// Returns the SCMI configuration of the sensor. + /// + /// The default implementation here returns just whether the sensor is + /// enabled or not. + fn config_get(&self) -> DeviceResult { + let config = u32::from(self.sensor().enabled); + Ok(vec![MessageValue::Unsigned(config)]) + } + + /// Processes the SCMI configuration of the sensor. + /// + /// The default implementation here permits and implements only enabling + /// and disabling the sensor. + fn config_set(&mut self, config: u32) -> DeviceResult { + if config & 0xFFFFFFFE != 0 { + return Result::Err(ScmiDeviceError::UnsupportedRequest); + } + self.sensor_mut().enabled = config != 0; + debug!("Sensor enabled: {}", self.sensor().enabled); + Ok(vec![]) + } + + /// Returns SCMI reading of the sensor values. + /// + /// It is a sequence of [MessageValue::Unsigned] values, 4 of them for each + /// sensor axis. See the SCMI standard for the exact specification of the + /// result. + fn reading_get(&mut self) -> DeviceResult; + + /// Handles the given protocol message with the given parameters. + /// + /// Usually no need to redefine this, unless more than the basic + /// functionality is needed, in which case it would be probably better to + /// enhance this trait with additional functions and improved + /// implementation. + fn handle(&mut self, message_id: MessageId, parameters: &[MessageValue]) -> DeviceResult { + match message_id { + SENSOR_DESCRIPTION_GET => self.description_get(), + SENSOR_AXIS_DESCRIPTION_GET => { + let n_sensor_axes = self.number_of_axes(); + let axis_desc_index = parameters[0].get_unsigned(); + if axis_desc_index >= n_sensor_axes { + return Result::Err(ScmiDeviceError::InvalidParameters); + } + let mut values = vec![MessageValue::Unsigned(n_sensor_axes - axis_desc_index)]; + for i in axis_desc_index..n_sensor_axes { + let mut description = self.axis_description(i); + values.append(&mut description); + } + Ok(values) + } + SENSOR_CONFIG_GET => self.config_get(), + SENSOR_CONFIG_SET => { + let config = parameters[0].get_unsigned(); + self.config_set(config) + } + SENSOR_CONTINUOUS_UPDATE_NOTIFY => { + // Linux VIRTIO SCMI insists on this. + // We can accept it and ignore it, the sensor will be still working. + Ok(vec![]) + } + SENSOR_READING_GET => { + if !self.sensor().enabled { + return Result::Err(ScmiDeviceError::NotEnabled); + } + self.reading_get() + } + _ => Result::Err(ScmiDeviceError::UnsupportedRequest), + } + } +} + +// It's possible to impl ScmiDevice for SensorT but it is not very useful +// because it doesn't allow to pass SensorT as ScmiDevice directly. +// Hence this wrapper. +pub struct SensorDevice(pub(crate) Box); + +impl ScmiDevice for SensorDevice { + fn initialize(&mut self) -> Result<(), DeviceError> { + self.0.initialize() + } + + fn protocol(&self) -> ProtocolId { + self.0.protocol() + } + + fn handle(&mut self, message_id: MessageId, parameters: &[MessageValue]) -> DeviceResult { + self.0.handle(message_id, parameters) + } +} + +#[cfg(test)] +mod tests { + use std::assert_eq; + + use super::*; + + #[test] + fn test_help() { + let help = devices_help(); + assert!( + help.contains("Available devices:\n"), + "global label missing" + ); + assert!(help.contains("fake:"), "sensor name missing"); + assert!( + help.contains("fake accelerometer"), + "short description missing" + ); + assert!(help.contains("3-axes sensor"), "long description missing"); + assert!(help.contains("Parameters:\n"), "parameter label missing"); + assert!(help.contains("- name:"), "parameter `name' missing"); + } + + fn device_properties() -> DeviceProperties { + DeviceProperties::new(vec![ + ("foo".to_owned(), "val1".to_owned()), + ("def".to_owned(), "val2".to_owned()), + ("bar".to_owned(), "val3".to_owned()), + ]) + } + + #[test] + fn test_device_properties() { + let properties = device_properties(); + assert_eq!(properties.get("bar"), Some("val3")); + assert_eq!(properties.get("baz"), None); + assert_eq!(properties.names(), HashSet::from(["foo", "def", "bar"])); + let expected = ["abc", "def", "ghi"]; + let missing = properties.missing(&expected); + assert_eq!(missing, HashSet::from(["abc", "ghi"])); + let extra = properties.extra(&expected); + assert_eq!(extra, HashSet::from(["foo", "bar"])); + } + + #[test] + fn test_check_device_properties() { + let properties = device_properties(); + match properties.check(&["abc", "def", "ghi"], &["foo", "baz"]) { + Err(DeviceError::MissingDeviceProperties(missing)) => { + assert_eq!(missing, vec!["abc".to_owned(), "ghi".to_owned()]) + } + other => panic!("Unexpected result: {:?}", other), + } + match properties.check(&["def"], &["foo", "baz"]) { + Err(DeviceError::UnexpectedDeviceProperties(unexpected)) => { + assert_eq!(unexpected, vec!["bar".to_owned()]) + } + other => panic!("Unexpected result: {:?}", other), + } + match properties.check(&["def"], &["foo", "bar"]) { + Ok(()) => (), + other => panic!("Unexpected result: {:?}", other), + } + } +} diff --git a/crates/scmi/src/devices/fake.rs b/crates/scmi/src/devices/fake.rs new file mode 100644 index 000000000..c0936b424 --- /dev/null +++ b/crates/scmi/src/devices/fake.rs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! Fake sensor implementation. +//! +//! The fake sensor is completely implemented here rather than bound to a host +//! device. It emulates a dummy accelerometer device that increments an axis +//! reading value on each its retrieval. Useful for initial testing and +//! arranging SCMI virtualization setup without the need to bind real host +//! devices. + +use crate::scmi::{self, DeviceResult, MessageValue}; + +use super::common::{DeviceProperties, MaybeDevice, Sensor, SensorDevice, SensorT}; + +pub struct FakeSensor { + sensor: Sensor, + value: u8, +} + +impl SensorT for FakeSensor { + // TODO: Define a macro for this boilerplate? + fn sensor(&self) -> &Sensor { + &self.sensor + } + fn sensor_mut(&mut self) -> &mut Sensor { + &mut self.sensor + } + + fn number_of_axes(&self) -> u32 { + 3 + } + + fn unit(&self) -> u8 { + // The sensor type is "Meters per second squared", since this is the + // only, together with "Radians per second", what Google Linux IIO + // supports (accelerometers and gyroscopes only). + scmi::SENSOR_UNIT_METERS_PER_SECOND_SQUARED + } + + fn axis_name_prefix(&self) -> String { + "acc".to_owned() + } + + fn reading_get(&mut self) -> DeviceResult { + let value = self.value; + self.value = self.value.overflowing_add(1).0; + let mut result = vec![]; + for i in 0..3 { + result.push(MessageValue::Unsigned(u32::from(value) + 100 * i)); + result.push(MessageValue::Unsigned(0)); + result.push(MessageValue::Unsigned(0)); + result.push(MessageValue::Unsigned(0)); + } + Ok(result) + } +} + +impl FakeSensor { + pub fn new_device(properties: &DeviceProperties) -> MaybeDevice { + properties.check(&[], &["name"])?; + let sensor = Sensor::new(properties, "fake"); + let fake_sensor = Self { sensor, value: 0 }; + let sensor_device = SensorDevice(Box::new(fake_sensor)); + Ok(Box::new(sensor_device)) + } +} diff --git a/crates/scmi/src/devices/iio.rs b/crates/scmi/src/devices/iio.rs new file mode 100644 index 000000000..4b5522e20 --- /dev/null +++ b/crates/scmi/src/devices/iio.rs @@ -0,0 +1,827 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! Industrial I/O (IIO) sensors bindings. +//! +//! Basic functionality for exposing `/sys/bus/iio/devices/` stuff as guest +//! SCMI devices. Only some typical cases are supported. If you want more +//! functionality, you must enhance the implementation here. +//! +//! For some entry points, see [IIOSensor] and [Axis]. + +use std::cmp::{max, min}; +use std::ffi::{OsStr, OsString}; +use std::fs; +use std::io::ErrorKind; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +use log::{debug, error, warn}; + +use crate::scmi::{self, DeviceResult, MessageValue, ScmiDeviceError, MAX_SIMPLE_STRING_LENGTH}; + +use super::common::{DeviceError, DeviceProperties, MaybeDevice, Sensor, SensorDevice, SensorT}; + +/// Information about units used by the given Linux IIO channel. +struct UnitMapping<'a> { + /// IIO sysfs channel prefix, e.g. "in_accel". + channel: &'a str, + /// One of the SCMI unit constants from [crate::scmi] (enum is not used to + /// avoid type conversions everywhere). + unit: u8, + /// Decadic exponent to be used to convert the given unit to the SCMI unit. + /// For example, the exponent is 0 for no conversion, -3 to convert + /// milliamps here to amps in SCMI, or 3 to convert kilopascals here to + /// pascals in SCMI. + unit_exponent: i8, // max. 5 bits actually +} + +/// Specification of IIO channel units. +/// +/// Based on +/// . +/// Not everything from there is present -- channels here with more complicated +/// unit transformations (beyond using a decadic exponent; e.g. degrees to +/// radians or units not defined in SCMI) are omitted. If an IIO channel +/// doesn't have unit specification here, it can be still used by the unit +/// reported in SCMI will be [crate::scmi::SENSOR_UNIT_UNSPECIFIED]. +// TODO: Make some macro(s) for this? +const UNIT_MAPPING: &[UnitMapping] = &[ + UnitMapping { + channel: "in_accel", + unit: scmi::SENSOR_UNIT_METERS_PER_SECOND_SQUARED, + unit_exponent: 0, + }, + UnitMapping { + channel: "in_angle", + unit: scmi::SENSOR_UNIT_RADIANS, + unit_exponent: 0, + }, + UnitMapping { + channel: "in_anglevel", + unit: scmi::SENSOR_UNIT_RADIANS_PER_SECOND, + unit_exponent: 0, + }, + UnitMapping { + channel: "in_concentration", + unit: scmi::SENSOR_UNIT_PERCENTAGE, + unit_exponent: 0, + }, + UnitMapping { + channel: "in_current", + unit: scmi::SENSOR_UNIT_AMPS, + unit_exponent: -3, + }, + UnitMapping { + channel: "in_capacitance", + unit: scmi::SENSOR_UNIT_FARADS, + unit_exponent: -9, + }, + UnitMapping { + channel: "in_distance", + unit: scmi::SENSOR_UNIT_METERS, + unit_exponent: 0, + }, + UnitMapping { + channel: "in_electricalconductivity", + unit: scmi::SENSOR_UNIT_SIEMENS, // per meter + unit_exponent: 0, + }, + UnitMapping { + channel: "in_energy", + unit: scmi::SENSOR_UNIT_JOULS, + unit_exponent: 0, + }, + UnitMapping { + channel: "in_gravity", + unit: scmi::SENSOR_UNIT_METERS_PER_SECOND_SQUARED, + unit_exponent: 0, + }, + UnitMapping { + channel: "in_humidityrelative", + unit: scmi::SENSOR_UNIT_PERCENTAGE, + unit_exponent: -3, + }, + UnitMapping { + channel: "in_illuminance", + unit: scmi::SENSOR_UNIT_LUX, + unit_exponent: 0, + }, + UnitMapping { + channel: "in_magn", + unit: scmi::SENSOR_UNIT_GAUSS, + unit_exponent: 0, + }, + UnitMapping { + channel: "in_ph", + unit: scmi::SENSOR_UNIT_UNSPECIFIED, // SCMI doesn't define pH + unit_exponent: -3, + }, + UnitMapping { + channel: "in_positionrelative", + unit: scmi::SENSOR_UNIT_PERCENTAGE, + unit_exponent: -3, + }, + UnitMapping { + channel: "in_power", + unit: scmi::SENSOR_UNIT_WATTS, + unit_exponent: -3, + }, + UnitMapping { + channel: "in_pressure", + unit: scmi::SENSOR_UNIT_PASCALS, + unit_exponent: 3, + }, + UnitMapping { + channel: "in_proximity", + unit: scmi::SENSOR_UNIT_METERS, + unit_exponent: 0, + }, + UnitMapping { + channel: "in_resistance", + unit: scmi::SENSOR_UNIT_OHMS, + unit_exponent: 0, + }, + UnitMapping { + channel: "in_temp", + unit: scmi::SENSOR_UNIT_DEGREES_C, + unit_exponent: -3, + }, + UnitMapping { + channel: "in_velocity_sqrt(x^2+y^2+z^2)", + unit: scmi::SENSOR_UNIT_METERS_PER_SECOND, + unit_exponent: -3, + }, + UnitMapping { + channel: "in_voltage", + unit: scmi::SENSOR_UNIT_VOLTS, + unit_exponent: -3, + }, +]; + +/// Representation of an IIO channel axis. +/// +/// Used also for scalar values. +#[derive(PartialEq, Debug)] +struct Axis { + /// Full sysfs path to the axis value file stripped of "_raw". + path: OsString, // without "_raw" suffix + /// Axis unit exponent, see [UnitMapping::unit_exponent] and [UNIT_MAPPING]. + unit_exponent: i8, + /// Additional exponent to apply to the axis values. It is computed from + /// the axis value scaling (see [IIOSensor::custom_exponent] to provide a + /// sufficiently accurate SCMI value that is represented by an integer (not + /// a float) + decadic exponent. + custom_exponent: i8, +} + +/// Particular IIO sensor specification. +/// +/// An IIO sensor is specified by an IIO sysfs device directory and a channel +/// prefix within the directory (i.e. more devices can be defined for a single +/// IIO device directory). All other information about the sensor is retrieved +/// from the device directory and from [UNIT_MAPPING]. +#[derive(Debug)] +pub struct IIOSensor { + /// Common sensor instance. + sensor: Sensor, + /// Full sysfs path to the device directory. + /// + /// Provided by the user. + path: OsString, + /// Prefix of the device type in the device directory, e.g. "in_accel". + /// + /// Provided by the user. + channel: OsString, + /// Whether the sensor is scalar or has one or more axes. + /// + /// Determined automatically by looking for presence of `*_[xyz]_raw` files + /// with the given channel prefix. + scalar: bool, + /// Axes descriptions, see [Axis] for more details. + axes: Vec, +} + +impl SensorT for IIOSensor { + // TODO: Define a macro for this boilerplate? + fn sensor(&self) -> &Sensor { + &self.sensor + } + fn sensor_mut(&mut self) -> &mut Sensor { + &mut self.sensor + } + + fn initialize(&mut self) -> Result<(), DeviceError> { + let mut axes: Vec = vec![]; + match fs::read_dir(&self.path) { + Ok(iter) => { + for dir_entry in iter { + match dir_entry { + Ok(entry) => self.register_iio_file(entry, &mut axes), + Err(error) => return Err(DeviceError::IOError(self.path.clone(), error)), + } + } + } + Err(error) => return Err(DeviceError::IOError(self.path.clone(), error)), + } + if axes.is_empty() { + return Err(DeviceError::GenericError(format!( + "No {:?} channel found in {:?}", + &self.channel, &self.path + ))); + } + axes.sort_by(|a1, a2| a1.path.cmp(&a2.path)); + self.axes = axes; + Ok(()) + } + + fn unit(&self) -> u8 { + UNIT_MAPPING + .iter() + .find(|mapping| mapping.channel == self.channel) + .map_or(scmi::SENSOR_UNIT_UNSPECIFIED, |mapping| mapping.unit) + } + + fn unit_exponent(&self, axis_index: u32) -> i8 { + let axis: &Axis = self.axes.get(axis_index as usize).unwrap(); + axis.unit_exponent + axis.custom_exponent + } + + fn number_of_axes(&self) -> u32 { + if self.scalar { + 0 + } else { + self.axes.len() as u32 + } + } + + fn axis_name_prefix(&self) -> String { + let channel = self.channel.to_str().unwrap(); + let in_prefix = "in_"; + let out_prefix = "out_"; + let name: &str = if channel.starts_with(in_prefix) { + channel.strip_prefix(in_prefix).unwrap() + } else if channel.starts_with(out_prefix) { + channel.strip_prefix(out_prefix).unwrap() + } else { + channel + }; + let len = min(name.len(), MAX_SIMPLE_STRING_LENGTH - 1); + String::from(&name[..len]) + } + + fn reading_get(&mut self) -> DeviceResult { + let mut result = vec![]; + for axis in &self.axes { + let value = self.read_axis(axis)?; + result.push(MessageValue::Unsigned((value & 0xFFFFFFFF) as u32)); + result.push(MessageValue::Unsigned((value >> 32) as u32)); + result.push(MessageValue::Unsigned(0)); + result.push(MessageValue::Unsigned(0)); + } + Ok(result) + } +} + +fn read_number_from_file(path: &Path) -> Result, ScmiDeviceError> { + match fs::read_to_string(path) { + Ok(string) => match string.trim().parse() { + Ok(value) => Ok(Some(value)), + _ => { + error!( + "Failed to parse IIO numeric value from {}: {string}", + path.display() + ); + Err(ScmiDeviceError::GenericError) + } + }, + Err(error) => match error.kind() { + ErrorKind::NotFound => { + let raw = path.ends_with("_raw"); + let format = || { + format!( + "IIO {} file {} not found", + if raw { "value" } else { "data" }, + path.display() + ) + }; + if raw { + error!("{}", format()); + Err(ScmiDeviceError::GenericError) + } else { + debug!("{}", format()); + Ok(None) + } + } + other_error => { + error!( + "Failed to read IIO data from {}: {}", + path.display(), + other_error + ); + Err(ScmiDeviceError::GenericError) + } + }, + } +} + +impl IIOSensor { + #[allow(clippy::new_ret_no_self)] + pub fn new(properties: &DeviceProperties) -> Result { + properties.check(&["path", "channel"], &["name"])?; + let sensor = Sensor::new(properties, "iio"); + Ok(Self { + sensor, + path: OsString::from(properties.get("path").unwrap()), + channel: OsString::from(properties.get("channel").unwrap()), + scalar: true, + axes: vec![], + }) + } + + pub fn new_device(properties: &DeviceProperties) -> MaybeDevice { + let iio_sensor = Self::new(properties)?; + let sensor_device = SensorDevice(Box::new(iio_sensor)); + Ok(Box::new(sensor_device)) + } + + fn set_sensor_name_from_file(&mut self, path: &PathBuf) { + match fs::read_to_string(path) { + Ok(name) => self.sensor_mut().name = name, + Err(error) => warn!( + "Error reading IIO device name from {}: {}", + path.display(), + error + ), + } + } + + fn custom_exponent(&self, path: &OsStr, unit_exponent: i8) -> i8 { + let mut custom_exponent: i8 = 0; + if let Ok(Some(scale)) = self.read_axis_scale(path) { + // Crash completely OK if *this* doesn't fit: + custom_exponent = scale.log10() as i8; + if scale < 1.0 { + // The logarithm is truncated towards zero, we need floor + custom_exponent -= 1; + } + // The SCMI exponent (unit_exponent + custom_exponent) can have max. 5 bits: + custom_exponent = min(15 - unit_exponent, custom_exponent); + custom_exponent = max(-16 - unit_exponent, custom_exponent); + debug!( + "Setting custom scaling coefficient for {:?}: {}", + &path, custom_exponent + ); + } + custom_exponent + } + + fn add_axis(&mut self, axes: &mut Vec, path: &OsStr) { + let unit_exponent = UNIT_MAPPING + .iter() + .find(|mapping| mapping.channel == self.channel) + .map_or(0, |mapping| mapping.unit_exponent); + // To get meaningful integer values, we must adjust exponent to + // the provided scale if any. + let custom_exponent = self.custom_exponent(path, unit_exponent); + axes.push(Axis { + path: OsString::from(path), + unit_exponent, + custom_exponent, + }); + } + + fn register_iio_file(&mut self, file: fs::DirEntry, axes: &mut Vec) { + let channel = self.channel.to_str().unwrap(); + let os_file_name = file.file_name(); + let file_name = os_file_name.to_str().unwrap_or_default(); + let raw_suffix = "_raw"; + if file_name == "name" { + self.set_sensor_name_from_file(&file.path()); + } else if file_name.starts_with(channel) && file_name.ends_with(raw_suffix) { + let infix = &file_name[channel.len()..file_name.len() - raw_suffix.len()]; + let infix_len = infix.len(); + if infix_len == 0 || (infix_len == 2 && infix.starts_with('_')) { + let raw_axis_path = Path::new(&self.path) + .join(Path::new(&file_name)) + .to_str() + .unwrap() + .to_string(); + let axis_path = raw_axis_path.strip_suffix(raw_suffix).unwrap(); + self.add_axis(axes, &OsString::from(axis_path)); + if infix_len > 0 { + self.scalar = false; + } + } + } + } + + fn read_axis_file( + &self, + path: &OsStr, + name: &str, + ) -> Result, ScmiDeviceError> { + for value_path in [ + Path::new(&(String::from(path.to_str().unwrap()) + "_" + name)), + &Path::new(&path).parent().unwrap().join(name), + ] + .iter() + { + let value: Option = read_number_from_file(value_path)?; + if value.is_some() { + return Ok(value); + } + } + Ok(None) + } + + fn read_axis_offset(&self, path: &OsStr) -> Result, ScmiDeviceError> { + self.read_axis_file(path, "offset") + } + + fn read_axis_scale(&self, path: &OsStr) -> Result, ScmiDeviceError> { + self.read_axis_file(path, "scale") + } + + fn read_axis(&self, axis: &Axis) -> Result { + let path_result = axis.path.clone().into_string(); + let mut value: i64 = + read_number_from_file(Path::new(&(path_result.unwrap() + "_raw")))?.unwrap(); + let offset: Option = self.read_axis_offset(&axis.path)?; + if let Some(offset_value) = offset { + match value.checked_add(offset_value) { + Some(new_value) => value = new_value, + None => { + error!( + "IIO offset overflow in {:?}: {} + {}", + &axis.path, + value, + offset.unwrap() + ); + return Err(ScmiDeviceError::GenericError); + } + } + } + let scale: Option = self.read_axis_scale(&axis.path)?; + if let Some(scale_value) = scale { + let exponent_scale = 10.0_f64.powi(i32::from(axis.custom_exponent)); + value = (value as f64 * (scale_value / exponent_scale)).round() as i64; + } + Ok(value) + } +} + +#[cfg(test)] +mod tests { + use crate::scmi::ScmiDevice; + + use super::*; + use std::{ + assert_eq, fs, + path::{Path, PathBuf}, + }; + + fn make_directory(prefix: &str) -> PathBuf { + for i in 1..100 { + let path = Path::new(".").join(format!("{prefix}{i}")); + if fs::create_dir(&path).is_ok() { + return path; + } + } + panic!("Couldn't create test directory"); + } + + struct IIODirectory { + path: PathBuf, + } + + impl IIODirectory { + fn new(files: &[(&str, &str)]) -> IIODirectory { + let path = make_directory("_test"); + let directory = IIODirectory { path }; + for (file, content) in files.iter() { + fs::write(&directory.path.join(file), content).unwrap(); + } + directory + } + } + + impl Drop for IIODirectory { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.path); + } + } + + fn directory_path(directory: &IIODirectory) -> String { + directory + .path + .clone() + .into_os_string() + .into_string() + .unwrap() + } + + fn device_properties(path: String, channel: String, name: Option) -> DeviceProperties { + let mut pairs = vec![("path".to_owned(), path), ("channel".to_owned(), channel)]; + if let Some(name) = name { + pairs.push(("name".to_owned(), name)); + } + DeviceProperties::new(pairs) + } + + fn make_iio_sensor_from_path(path: String, channel: String, name: Option) -> IIOSensor { + let properties = device_properties(path, channel, name); + IIOSensor::new(&properties).unwrap() + } + + fn make_iio_sensor( + directory: &IIODirectory, + channel: String, + name: Option, + ) -> IIOSensor { + let path = directory_path(directory); + make_iio_sensor_from_path(path, channel, name) + } + + fn make_scmi_sensor_from_path( + path: String, + channel: String, + name: Option, + ) -> MaybeDevice { + let properties = device_properties(path, channel, name); + IIOSensor::new_device(&properties) + } + + fn make_scmi_sensor( + directory: &IIODirectory, + channel: String, + name: Option, + ) -> Box { + let path = directory_path(directory); + make_scmi_sensor_from_path(path, channel, name).unwrap() + } + + #[test] + fn test_missing_property() { + let properties = DeviceProperties::new(vec![("path".to_owned(), ".".to_owned())]); + let result = IIOSensor::new(&properties); + match result { + Ok(_) => panic!("Should fail on a missing property"), + Err(DeviceError::MissingDeviceProperties(missing)) => { + assert_eq!(missing, vec!["channel".to_owned()]) + } + other => panic!("Unexpected result: {:?}", other), + } + } + + #[test] + fn test_extra_property() { + let properties = DeviceProperties::new(vec![ + ("path".to_owned(), ".".to_owned()), + ("name".to_owned(), "test".to_owned()), + ("channel".to_owned(), "in_accel".to_owned()), + ("foo".to_owned(), "something".to_owned()), + ("bar".to_owned(), "baz".to_owned()), + ]); + let result = IIOSensor::new(&properties); + match result { + Ok(_) => panic!("Should fail on an extra property"), + Err(DeviceError::UnexpectedDeviceProperties(extra)) => { + assert_eq!(extra, ["bar".to_owned(), "foo".to_owned()]) + } + other => panic!("Unexpected result: {:?}", other), + } + } + + #[test] + fn test_iio_init() { + let directory = IIODirectory::new(&[("foo", "bar"), ("in_accel_raw", "123")]); + let mut sensor = + make_scmi_sensor(&directory, "in_accel".to_owned(), Some("accel".to_owned())); + sensor.initialize().unwrap(); + } + + #[test] + fn test_iio_init_no_directory() { + let mut sensor = + make_scmi_sensor_from_path("non-existent".to_owned(), "".to_owned(), None).unwrap(); + match sensor.initialize() { + Ok(_) => panic!("Should fail on an inaccessible path"), + Err(DeviceError::IOError(path, std::io::Error { .. })) => { + assert_eq!(path, "non-existent") + } + other => panic!("Unexpected result: {:?}", other), + } + } + + #[test] + fn test_iio_init_no_channel() { + let directory = IIODirectory::new(&[("foo", "bar")]); + let mut sensor = make_scmi_sensor(&directory, "in_accel".to_owned(), None); + match sensor.initialize() { + Ok(_) => panic!("Should fail on an inaccessible channel"), + Err(DeviceError::GenericError(message)) => { + assert!( + message.starts_with("No \"in_accel\" channel found in \"./_test"), + "Unexpected error: {}", + message + ) + } + other => panic!("Unexpected result: {:?}", other), + } + } + + #[test] + fn test_sensor_name_from_fs() { + let directory = IIODirectory::new(&[("in_accel_raw", "123"), ("name", "foo")]); + let mut sensor = + make_iio_sensor(&directory, "in_accel".to_owned(), Some("accel".to_owned())); + sensor.initialize().unwrap(); + assert_eq!(sensor.sensor.name, "foo"); + } + + #[test] + fn test_sensor_name_from_params() { + let directory = IIODirectory::new(&[("in_accel_raw", "123")]); + let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), Some("foo".to_owned())); + sensor.initialize().unwrap(); + assert_eq!(sensor.sensor.name, "foo"); + } + + #[test] + fn test_default_sensor_name() { + let directory = IIODirectory::new(&[("in_accel_raw", "123")]); + let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None); + sensor.initialize().unwrap(); + assert_eq!(sensor.sensor.name, "iio"); + } + + #[test] + fn test_units() { + let directory = IIODirectory::new(&[ + ("in_foo_raw", "123"), + ("in_accel_raw", "123"), + ("in_voltage_raw", "123"), + ]); + for (name, unit) in [ + ("foo", scmi::SENSOR_UNIT_UNSPECIFIED), + ("accel", scmi::SENSOR_UNIT_METERS_PER_SECOND_SQUARED), + ("voltage", scmi::SENSOR_UNIT_VOLTS), + ] + .iter() + { + let sensor = + make_iio_sensor(&directory, "in_".to_owned() + name, Some(name.to_string())); + assert_eq!(sensor.unit(), *unit); + } + } + + #[test] + fn test_unit_exponent() { + for (channel, scale, exponent) in [ + ("in_accel", 1.23, 0), + ("in_accel", 0.000123, -4), + ("in_accel", 123.0, 2), + ("in_voltage", 123.0, -1), + ] + .iter() + { + let raw_file = format!("{channel}_raw"); + let scale_file = format!("{channel}_scale"); + let directory = + IIODirectory::new(&[(&raw_file, "123"), (&scale_file, &scale.to_string())]); + let mut sensor = make_iio_sensor(&directory, channel.to_string(), None); + sensor.initialize().unwrap(); + assert_eq!(sensor.unit_exponent(0), *exponent); + } + } + + #[test] + fn test_unit_exponent_multiple_axes() { + let directory = IIODirectory::new(&[ + ("in_accel_x_raw", "123"), + ("in_accel_x_scale", "0.123"), + ("in_accel_y_raw", "123"), + ("in_accel_y_scale", "12.3"), + ]); + let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None); + sensor.initialize().unwrap(); + assert_eq!(sensor.unit_exponent(0), -1); + assert_eq!(sensor.unit_exponent(1), 1); + } + + #[test] + fn test_unit_exponent_single_scale() { + let directory = IIODirectory::new(&[("in_accel_raw", "123"), ("scale", "0.123")]); + let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None); + sensor.initialize().unwrap(); + assert_eq!(sensor.unit_exponent(0), -1); + } + + #[test] + fn test_number_of_axes_scalar() { + let directory = IIODirectory::new(&[("in_accel_raw", "123"), ("in_accel_scale", "123")]); + let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None); + sensor.initialize().unwrap(); + assert_eq!(sensor.number_of_axes(), 0); + } + + #[test] + fn test_number_of_axes_1() { + let directory = IIODirectory::new(&[("in_accel_x_raw", "123"), ("in_accel_scale", "123")]); + let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None); + sensor.initialize().unwrap(); + assert_eq!(sensor.number_of_axes(), 1); + } + + #[test] + fn test_number_of_axes_3() { + let directory = IIODirectory::new(&[ + ("in_accel_x_raw", "123"), + ("in_accel_y_raw", "123"), + ("in_accel_z_raw", "123"), + ("in_accel_x_scale", "123"), + ]); + let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None); + sensor.initialize().unwrap(); + assert_eq!(sensor.number_of_axes(), 3); + } + + #[test] + fn test_axis_name_prefix() { + for (channel, prefix) in [ + ("in_accel", "accel"), + ("out_voltage", "voltage"), + ("foo", "foo"), + ("name-longer-than-fifteen-characters", "name-longer-tha"), + ] + .iter() + { + let sensor = make_iio_sensor_from_path("".to_owned(), channel.to_string(), None); + assert_eq!(&sensor.axis_name_prefix(), prefix); + } + } + + #[test] + fn test_iio_reading_scalar() { + let directory = IIODirectory::new(&[ + ("in_voltage_raw", "9876543210"), + ("in_voltage_offset", "123"), + ("in_voltage_scale", "456"), + ]); + let mut sensor = make_iio_sensor(&directory, "in_voltage".to_owned(), None); + sensor.initialize().unwrap(); + let result = sensor.reading_get().unwrap(); + // (9876543210 + 123) * 456 = 4503703759848 + // custom exponent = 2 + // applied and rounded: 45037037598 = 0xA7C6AA81E + assert_eq!(result.len(), 4); + assert_eq!(result.get(0).unwrap(), &MessageValue::Unsigned(0x7C6AA81E)); + assert_eq!(result.get(1).unwrap(), &MessageValue::Unsigned(0xA)); + assert_eq!(result.get(2).unwrap(), &MessageValue::Unsigned(0)); + assert_eq!(result.get(3).unwrap(), &MessageValue::Unsigned(0)); + } + + #[test] + fn test_iio_reading_scalar_whitespace() { + let directory = IIODirectory::new(&[ + ("in_accel_raw", "10\n"), + ("in_accel_offset", "20\n"), + ("in_accel_scale", "0.3\n"), + ]); + let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None); + sensor.initialize().unwrap(); + let result = sensor.reading_get().unwrap(); + assert_eq!(result.len(), 4); + assert_eq!(result.get(0).unwrap(), &MessageValue::Unsigned(0x5A)); + assert_eq!(result.get(1).unwrap(), &MessageValue::Unsigned(0)); + assert_eq!(result.get(2).unwrap(), &MessageValue::Unsigned(0)); + assert_eq!(result.get(3).unwrap(), &MessageValue::Unsigned(0)); + } + + #[test] + fn test_iio_reading_axes() { + let directory = IIODirectory::new(&[ + ("in_accel_x_raw", "10"), + ("in_accel_x_offset", "1"), + ("in_accel_y_raw", "20"), + ("in_accel_y_offset", "10"), + ("in_accel_z_raw", "30"), + ("in_accel_z_offset", "20"), + ("in_accel_z_scale", "0.3"), + ("scale", "0.02"), + ]); + let mut sensor = make_iio_sensor(&directory, "in_accel".to_owned(), None); + sensor.initialize().unwrap(); + let result = sensor.reading_get().unwrap(); + assert_eq!(result.len(), 12); + assert_eq!(result.get(0).unwrap(), &MessageValue::Unsigned(22)); + assert_eq!(result.get(4).unwrap(), &MessageValue::Unsigned(60)); + assert_eq!(result.get(8).unwrap(), &MessageValue::Unsigned(150)); + for i in 0..12 { + if i % 4 != 0 { + assert_eq!(result.get(i).unwrap(), &MessageValue::Unsigned(0)); + } + } + } +} diff --git a/crates/scmi/src/devices/mod.rs b/crates/scmi/src/devices/mod.rs new file mode 100644 index 000000000..5b7ea6138 --- /dev/null +++ b/crates/scmi/src/devices/mod.rs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! Implementation of SCMI bindings to host devices. +//! +//! The general infrastructure is implemented in [crate::devices::common] module. +//! Access to particular kinds of devices is implemented in the other modules: +//! - [crate::devices::fake] provides a fake sensor. +//! - [crate::devices::iio] implements access to industrial I/O (IIO) devices. + +pub mod common; +pub mod fake; +pub mod iio; diff --git a/crates/scmi/src/main.rs b/crates/scmi/src/main.rs new file mode 100644 index 000000000..8a925d8f1 --- /dev/null +++ b/crates/scmi/src/main.rs @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 +// Based on implementation of other devices here, Copyright by Linaro Ltd. + +//! vhost-user daemon implementation for +//! [System Control and Management Interface](https://developer.arm.com/Architectures/System%20Control%20and%20Management%20Interface) +//! (SCMI). +//! +//! Currently, the mandatory parts of the following SCMI protocols are implemented: +//! +//! - base +//! - sensor management +//! +//! As for sensor management, support for industrial I/O (IIO) Linux devices +//! and a fake sensor device is implemented. +//! +//! The daemon listens on a socket that is specified using `--socket-path` +//! command line option. Usually at least one exposed device is specified, +//! which is done using `--device` command line option. It can be used more +//! than once, for different devices. `--help-devices` lists the available +//! devices and their options. +//! +//! The daemon normally logs info and higher messages to the standard error +//! output. To log more messages, you can set `RUST_LOG` environment variable, +//! e.g. to `debug`. +//! +//! Here is an example command line invocation of the daemon: +//! +//! ```sh +//! RUST_LOG=debug vhost-device-scmi \ +//! --socket ~/tmp/scmi.sock \ +//! --device iio,path=/sys/bus/iio/devices/iio:device0,channel=in_accel +//! ``` + +mod devices; +mod scmi; +mod vhu_scmi; + +use devices::common::{print_devices_help, DeviceDescription, DeviceProperties}; + +use std::{ + process::exit, + sync::{Arc, RwLock}, +}; + +use clap::{CommandFactory, Parser}; +use itertools::Itertools; +use log::{debug, error, info, warn}; + +use vhost::vhost_user; +use vhost::vhost_user::Listener; +use vhost_user_backend::VhostUserDaemon; +use vhu_scmi::VuScmiBackend; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; + +type Result = std::result::Result; + +#[derive(Parser)] +struct ScmiArgs { + // Location of vhost-user Unix domain socket. + // Required, unless one of the --help options is used. + #[clap(short, long, help = "vhost-user socket to use (required)")] + socket_path: Option, + // Specification of SCMI devices to create. + #[clap(short, long, help = "Devices to expose")] + #[arg(num_args(1..))] + device: Vec, + #[clap(long, help = "Print help on available devices")] + help_devices: bool, +} + +pub struct VuScmiConfig { + socket_path: String, + devices: DeviceDescription, +} + +impl TryFrom for VuScmiConfig { + type Error = String; + + fn try_from(cmd_args: ScmiArgs) -> Result { + if cmd_args.socket_path.is_none() { + return Result::Err("Required argument socket-path was not provided".to_string()); + } + let socket_path = cmd_args.socket_path.unwrap().trim().to_string(); + let mut devices: DeviceDescription = vec![]; + let device_iterator = cmd_args.device.iter(); + for d in device_iterator { + let mut split = d.split(','); + let name = split.next().unwrap().to_owned(); + let mut properties = vec![]; + for s in split { + if let Some((key, value)) = s.split('=').collect_tuple() { + properties.push((key.to_owned(), value.to_owned())); + } else { + return Result::Err(format!("Invalid device {name} property format: {s}")); + } + } + devices.push((name, DeviceProperties::new(properties))); + } + Ok(Self { + socket_path, + devices, + }) + } +} + +fn start_backend(config: VuScmiConfig) -> Result<()> { + loop { + debug!("Starting backend"); + let backend_instance = VuScmiBackend::new(&config); + if let Err(error) = backend_instance { + return Err(error.to_string()); + } + + let backend = Arc::new(RwLock::new(backend_instance.unwrap())); + let listener = Listener::new(config.socket_path.clone(), true).unwrap(); + let mut daemon = VhostUserDaemon::new( + "vhost-device-scmi".to_owned(), + backend.clone(), + GuestMemoryAtomic::new(GuestMemoryMmap::new()), + ) + .unwrap(); + + daemon.start(listener).unwrap(); + + match daemon.wait() { + Ok(()) => { + info!("Stopping cleanly"); + } + Err(vhost_user_backend::Error::HandleRequest(vhost_user::Error::PartialMessage)) => { + info!( + "vhost-user connection closed with partial message. + If the VM is shutting down, this is expected behavior; + otherwise, it might be a bug." + ); + } + Err(e) => { + warn!("Error running daemon: {:?}", e); + } + } + + // No matter the result, we need to shut down the worker thread. + backend.read().unwrap().exit_event.write(1).unwrap(); + debug!("Finishing backend"); + } +} + +fn process_args(args: ScmiArgs) -> Option { + if args.help_devices { + print_devices_help(); + None + } else { + Some(args) + } +} + +fn print_help(message: &String) { + println!("{message}\n"); + let mut command = ScmiArgs::command(); + command.print_help().unwrap(); +} + +fn main() { + env_logger::init(); + if let Some(args) = process_args(ScmiArgs::parse()) { + match VuScmiConfig::try_from(args) { + Ok(config) => { + if let Err(error) = start_backend(config) { + error!("{error}"); + println!("{error}"); + exit(1); + } + } + Err(message) => print_help(&message), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_command_line() { + let path = "/foo/scmi.sock".to_owned(); + let params_string = format!( + "binary \ + --device dummy \ + -s {path} \ + --device fake,name=foo,prop=value \ + -d fake,name=bar" + ); + let params: Vec<&str> = params_string.split_whitespace().collect(); + let args: ScmiArgs = process_args(Parser::parse_from(params)).unwrap(); + let config = VuScmiConfig::try_from(args).unwrap(); + assert_eq!(config.socket_path, path); + let devices = vec![ + ("dummy".to_owned(), DeviceProperties::new(vec![])), + ( + "fake".to_owned(), + DeviceProperties::new(vec![ + ("name".to_owned(), "foo".to_owned()), + ("prop".to_owned(), "value".to_owned()), + ]), + ), + ( + "fake".to_owned(), + DeviceProperties::new(vec![("name".to_owned(), "bar".to_owned())]), + ), + ]; + assert_eq!(config.devices, devices); + } + + #[test] + fn test_device_help_processing() { + let params_string = "binary --help-devices".to_string(); + let params: Vec<&str> = params_string.split_whitespace().collect(); + let args: ScmiArgs = Parser::parse_from(params); + let processed = process_args(args); + assert!(processed.is_none()); + } + + #[test] + fn test_help() { + // No way known to me to capture print_help() output from clap. + print_help(&String::from("test")); + } +} diff --git a/crates/scmi/src/scmi.rs b/crates/scmi/src/scmi.rs new file mode 100644 index 000000000..365352872 --- /dev/null +++ b/crates/scmi/src/scmi.rs @@ -0,0 +1,1380 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! Implementation of SCMI and some of its protocols. +//! +//! This module implements SCMI infrastructure and some of the SCMI protocols. +//! See [HandlerMap::new] how to add support for another SCMI protocol or to add +//! more functionality to an already implemented SCMI protocol. +//! +//! If you want to add new devices (e.g. SCMI bindings to some kinds of host +//! devices), see [crate::devices] modules. + +use std::{ + cmp::min, + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use itertools::Itertools; +use log::{debug, error, info, warn}; +use thiserror::Error as ThisError; + +use crate::devices::common::DeviceError; + +pub type MessageHeader = u32; + +pub const MAX_SIMPLE_STRING_LENGTH: usize = 16; // incl. NULL terminator + +/// Wrapper around SCMI values of the basic types SCMI defines. +/// +/// Everything communicating to/from SCMI must be composed of them. +// SCMI specification talks about Le32 parameter and return values. +// VirtIO SCMI specification talks about u8 SCMI values. +// Let's stick with SCMI specification for implementation simplicity. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum MessageValue { + Signed(i32), + Unsigned(u32), + String(String, usize), // string, expected characters +} + +impl MessageValue { + pub(crate) fn get_unsigned(&self) -> u32 { + match self { + Self::Unsigned(value) => *value, + _ => panic!("Wrong parameter"), + } + } +} + +pub type MessageValues = Vec; + +/// Enumeration of SCMI message types, mapped to the corresponding SCMI codes. +/// +/// The only one we currently support is [MessageType::Command]. +#[derive(Debug, PartialEq)] +enum MessageType { + // 4-bit unsigned integer + Command, // 0 + Unsupported, // anything else +} +pub type MessageId = u8; +pub type ProtocolId = u8; +type NParameters = u8; + +/// Mapping of return values to SCMI return status codes. +#[derive(Clone, Copy)] +// Not all the codes are currently used but let's have a complete return status +// enumeration from the SCMI specification here. +#[allow(dead_code)] +enum ReturnStatus { + // 32-bit signed integer + Success = 0, + NotSupported = -1, + InvalidParameters = -2, + Denied = -3, + NotFound = -4, + OutOfRange = -5, + Busy = -6, + CommsError = -7, + GenericError = -8, + HardwareError = -9, + ProtocolError = -10, + // -11..-127: reserved + // <-127: vendor specific +} + +impl ReturnStatus { + const fn as_value(&self) -> MessageValue { + MessageValue::Signed(*self as i32) + } +} + +/// Representation of [MessageValue] sequence used to construct [ScmiResponse]. +/// +/// The sequence includes the response code (see the helper constructors for +/// adding them) but it doesn't include the SCMI message header. The header is +/// added in [ScmiResponse]. +struct Response { + values: MessageValues, +} + +impl From for Response { + fn from(value: ReturnStatus) -> Self { + Self { + values: vec![value.as_value()], + } + } +} + +impl From for Response { + fn from(value: MessageValue) -> Self { + Self { + values: vec![ReturnStatus::Success.as_value(), value], + } + } +} + +impl From<&MessageValues> for Response { + fn from(value: &MessageValues) -> Self { + let mut response_values = vec![ReturnStatus::Success.as_value()]; + response_values.extend_from_slice(value.as_slice()); + Self { + values: response_values, + } + } +} + +/// SCMI response in SCMI representation byte. +/// +/// Use [ScmiResponse::from] function to construct it. +#[derive(Debug)] +pub struct ScmiResponse { + header: MessageHeader, + ret_bytes: Vec, +} + +impl ScmiResponse { + /// Creates [ScmiResponse] instance from the (unchanged) SCMI request + /// `header` and a [Response] composed of [MessageValue]s. + fn from(header: MessageHeader, response: Response) -> Self { + debug!("response arguments: {:?}", response.values); + let mut ret_bytes: Vec = vec![]; + ret_bytes.extend_from_slice(&header.to_le_bytes()); + for v in response.values { + let mut bytes = match v { + MessageValue::Signed(n) => n.to_le_bytes().to_vec(), + MessageValue::Unsigned(n) => n.to_le_bytes().to_vec(), + // Strings can be UTF-8 or ASCII and they must be + // null-terminated in either case. Let's put the + // null-terminator here rather than having to put it + // to all the strings anywhere. + MessageValue::String(s, size) => { + let mut v = s.as_bytes().to_vec(); + let v_len = v.len(); + // The string must be NULL terminated, at least one NULL must be present. + assert!( + v_len < size, + "String longer than specified: {v_len} >= {size}" + ); + v.resize(size, b'\0'); + v + } + }; + ret_bytes.append(&mut bytes) + } + debug!("ret bytes: {:?}", ret_bytes); + Self { header, ret_bytes } + } + + pub(crate) fn as_slice(&self) -> &[u8] { + self.ret_bytes.as_slice() + } + + pub(crate) fn len(&self) -> usize { + self.ret_bytes.len() + } + + pub(crate) fn communication_error(&self) -> Self { + Self::from(self.header, Response::from(ReturnStatus::CommsError)) + } +} + +/// Representation of a parsed SCMI request. +/// +/// Use [ScmiRequest::get_unsigned] and [ScmiRequest::get_usize] functions to +/// retrieve its parameters as `u32` and `usize` values respectively. +pub struct ScmiRequest { + header: MessageHeader, // 32-bit unsigned integer, split below: + message_id: MessageId, // bits 7:0 + message_type: MessageType, // bits 9:8 + protocol_id: ProtocolId, // bits 17:10 + // token: u16, // bits 27:18 + // bits 31:28 are reserved, must be 0 + parameters: Option, // set later based on the number of parameters +} + +impl ScmiRequest { + pub(crate) fn new(header: MessageHeader) -> Self { + let protocol_id: u8 = ((header >> 10) & 0xFF).try_into().unwrap(); + let message_id: u8 = (header & 0xFF).try_into().unwrap(); + // Token is an arbitrary info, the Linux SCMI driver uses it as a sequence number. + // No actual meaning for vhost except copying the unchanged header in the response + // as required by SCMI specification. We extract it here only for debugging purposes. + let token: u16 = ((header >> 18) & 0x3FF).try_into().unwrap(); + let message_type = match (header >> 8) & 0x3 { + 0 => MessageType::Command, + _ => MessageType::Unsupported, + }; + debug!( + "SCMI request: protocol id={}, message id={}, message_type={:?}, token={}", + protocol_id, message_id, message_type, token + ); + Self { + header, + message_id, + message_type, + protocol_id, + parameters: None, + } + } + + fn get_unsigned(&self, parameter: usize) -> u32 { + self.parameters.as_ref().expect("Missing parameters")[parameter].get_unsigned() + } + + fn get_usize(&self, parameter: usize) -> usize { + self.get_unsigned(parameter) as usize + } +} + +const BASE_PROTOCOL_ID: ProtocolId = 0x10; +const BASE_VERSION: MessageId = 0x0; +const BASE_PROTOCOL_ATTRIBUTES: MessageId = 0x1; +const BASE_MESSAGE_ATTRIBUTES: MessageId = 0x2; +const BASE_DISCOVER_VENDOR: MessageId = 0x3; +const BASE_DISCOVER_IMPLEMENTATION_VERSION: MessageId = 0x5; +const BASE_DISCOVER_LIST_PROTOCOLS: MessageId = 0x6; + +pub const SENSOR_PROTOCOL_ID: ProtocolId = 0x15; +const SENSOR_VERSION: MessageId = 0x0; +const SENSOR_ATTRIBUTES: MessageId = 0x1; +const SENSOR_MESSAGE_ATTRIBUTES: MessageId = 0x2; +pub const SENSOR_DESCRIPTION_GET: MessageId = 0x3; +pub const SENSOR_READING_GET: MessageId = 0x6; +pub const SENSOR_AXIS_DESCRIPTION_GET: MessageId = 0x7; +pub const SENSOR_CONFIG_GET: MessageId = 0x9; +pub const SENSOR_CONFIG_SET: MessageId = 0xA; +pub const SENSOR_CONTINUOUS_UPDATE_NOTIFY: MessageId = 0xB; + +#[allow(dead_code)] +pub const SENSOR_UNIT_NONE: u8 = 0; +pub const SENSOR_UNIT_UNSPECIFIED: u8 = 1; +pub const SENSOR_UNIT_DEGREES_C: u8 = 2; +pub const SENSOR_UNIT_VOLTS: u8 = 5; +pub const SENSOR_UNIT_AMPS: u8 = 6; +pub const SENSOR_UNIT_WATTS: u8 = 7; +pub const SENSOR_UNIT_JOULS: u8 = 8; +pub const SENSOR_UNIT_LUX: u8 = 13; +pub const SENSOR_UNIT_METERS: u8 = 31; +pub const SENSOR_UNIT_RADIANS: u8 = 36; +pub const SENSOR_UNIT_GAUSS: u8 = 45; +pub const SENSOR_UNIT_FARADS: u8 = 48; +pub const SENSOR_UNIT_OHMS: u8 = 49; +pub const SENSOR_UNIT_SIEMENS: u8 = 50; +pub const SENSOR_UNIT_PERCENTAGE: u8 = 65; +pub const SENSOR_UNIT_PASCALS: u8 = 66; +pub const SENSOR_UNIT_RADIANS_PER_SECOND: u8 = 87; +pub const SENSOR_UNIT_METERS_PER_SECOND: u8 = 90; +pub const SENSOR_UNIT_METERS_PER_SECOND_SQUARED: u8 = 89; + +enum ParameterType { + _SignedInt32, + UnsignedInt32, +} +type ParameterSpecification = Vec; + +type HandlerFunction = fn(&ScmiHandler, &ScmiRequest) -> Response; + +/// Specification of an SCMI message handler. +/// +/// No need to create this directly, use [HandlerMap::bind] to add message +/// handlers. +struct HandlerInfo { + name: String, + parameters: ParameterSpecification, + function: HandlerFunction, +} + +/// Mapping of SCMI protocols and messages to handlers. +/// +/// See [HandlerMap::new] and [HandlerMap::bind] how to add new handlers. +// HandlerMap layout is suboptimal but let's prefer simplicity for now. +struct HandlerMap(HashMap<(ProtocolId, MessageId), HandlerInfo>); + +impl HandlerMap { + fn new() -> Self { + let mut map = Self(HashMap::new()); + map.make_base_handlers(); + map.make_sensor_handlers(); + map + } + + fn keys(&self) -> std::collections::hash_map::Keys<(u8, u8), HandlerInfo> { + self.0.keys() + } + + fn get(&self, protocol_id: ProtocolId, message_id: MessageId) -> Option<&HandlerInfo> { + self.0.get(&(protocol_id, message_id)) + } + + /// Add a handler for a SCMI protocol message. + /// + /// `protocol_id` & `message_id` specify the corresponding SCMI protocol + /// and message codes identifying the request to handle using `function`. + /// Expected SCMI parameters (unsigned or signed 32-bit integers) are + /// specified in `parameters`. `name` serves just for identifying the + /// handlers easily in logs and error messages. + fn bind( + &mut self, + protocol_id: ProtocolId, + message_id: MessageId, + name: &str, + parameters: ParameterSpecification, + function: HandlerFunction, + ) { + assert!( + self.get(protocol_id, message_id).is_none(), + "Multiple handlers defined for SCMI message {}/{}", + protocol_id, + message_id + ); + self.0.insert( + (protocol_id, message_id), + HandlerInfo { + name: name.to_string(), + parameters, + function, + }, + ); + } + + /// Adds SCMI base protocol handlers. + fn make_base_handlers(&mut self) { + self.bind( + BASE_PROTOCOL_ID, + BASE_VERSION, + "base/version", + vec![], + |_, _| -> Response { + // 32-bit unsigned integer + // major: upper 16 bits + // minor: lower 16 bits + Response::from(MessageValue::Unsigned(0x20000)) + }, + ); + self.bind( + BASE_PROTOCOL_ID, + BASE_PROTOCOL_ATTRIBUTES, + "base/protocol_attributes", + vec![], + |handler, _| -> Response { + // The base protocol doesn't count. + Response::from(MessageValue::Unsigned(handler.number_of_protocols() - 1)) + }, + ); + self.bind( + BASE_PROTOCOL_ID, + BASE_MESSAGE_ATTRIBUTES, + "base/message_attributes", + vec![ParameterType::UnsignedInt32], + ScmiHandler::message_attributes, + ); + self.bind( + BASE_PROTOCOL_ID, + BASE_DISCOVER_VENDOR, + "base/discover_vendor", + vec![], + |_, _| -> Response { + Response::from(MessageValue::String( + "rust-vmm".to_string(), + MAX_SIMPLE_STRING_LENGTH, + )) + }, + ); + self.bind( + BASE_PROTOCOL_ID, + BASE_DISCOVER_IMPLEMENTATION_VERSION, + "base/discover_implementation_version", + vec![], + |_, _| -> Response { Response::from(MessageValue::Unsigned(0)) }, + ); + self.bind( + BASE_PROTOCOL_ID, + BASE_DISCOVER_LIST_PROTOCOLS, + "base/discover_list_protocols", + vec![ParameterType::UnsignedInt32], + ScmiHandler::discover_list_protocols, + ); + } + + /// Adds SCMI sensor protocol handlers. + fn make_sensor_handlers(&mut self) { + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_VERSION, + "sensor/version", + vec![], + |_, _| -> Response { + // 32-bit unsigned integer + // major: upper 16 bits + // minor: lower 16 bits + Response::from(MessageValue::Unsigned(0x30000)) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_ATTRIBUTES, + "sensor/attributes", + vec![], + |handler: &ScmiHandler, _| -> Response { + let n_sensors = u32::from(handler.devices.number_of_devices(SENSOR_PROTOCOL_ID)); + let values: MessageValues = vec![ + MessageValue::Unsigned(n_sensors), // # of sensors, no async commands + MessageValue::Unsigned(0), // lower shared memory address -- not supported + MessageValue::Unsigned(0), // higer shared memory address -- not supported + MessageValue::Unsigned(0), // length of shared memory -- not supported + ]; + Response::from(&values) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_MESSAGE_ATTRIBUTES, + "sensor/message_attributes", + vec![ParameterType::UnsignedInt32], + ScmiHandler::message_attributes, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_DESCRIPTION_GET, + "sensor/description_get", + vec![ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + let first_index = request.get_usize(0); + let n_sensors = handler.devices.number_of_devices(SENSOR_PROTOCOL_ID) as usize; + if first_index >= n_sensors { + return Response::from(ReturnStatus::InvalidParameters); + } + // Let's use something reasonable to fit into the available VIRTIO buffers: + let max_sensors_to_return = 256; + let sensors_to_return = min(n_sensors - first_index, max_sensors_to_return); + let last_non_returned_sensor = first_index + sensors_to_return; + let remaining_sensors = if n_sensors > last_non_returned_sensor { + n_sensors - last_non_returned_sensor + } else { + 0 + }; + let mut values = vec![MessageValue::Unsigned( + sensors_to_return as u32 | (remaining_sensors as u32) << 16, + )]; + for index in first_index..last_non_returned_sensor { + values.push(MessageValue::Unsigned(index as u32)); + let result = handler.handle_device( + index, + SENSOR_PROTOCOL_ID, + SENSOR_DESCRIPTION_GET, + &[], + ); + if result.is_err() { + return handler.device_response(result, index); + } + let mut sensor_values = result.unwrap(); + values.append(&mut sensor_values); + } + Response::from(&values) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_READING_GET, + "sensor/reading_get", + vec![ParameterType::UnsignedInt32, ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + // Check flags + if request.get_unsigned(1) != 0 { + // Asynchronous reporting not supported + return Response::from(ReturnStatus::NotSupported); + } + handler.handle_device_response(request, &[]) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_AXIS_DESCRIPTION_GET, + "sensor/axis_description_get", + vec![ParameterType::UnsignedInt32, ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + handler.handle_device_response(request, &[1]) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_CONFIG_GET, + "sensor/config_get", + vec![ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + handler.handle_device_response(request, &[]) + }, + ); + + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_CONFIG_SET, + "sensor/config_set", + vec![ParameterType::UnsignedInt32, ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + handler.handle_device_response(request, &[1]) + }, + ); + + // Linux VIRTIO SCMI seems to insist on presence of this: + self.bind( + SENSOR_PROTOCOL_ID, + SENSOR_CONTINUOUS_UPDATE_NOTIFY, + "sensor/continuous_update_notify", + vec![ParameterType::UnsignedInt32, ParameterType::UnsignedInt32], + |handler: &ScmiHandler, request: &ScmiRequest| -> Response { + handler.handle_device_response(request, &[1]) + }, + ); + } +} + +#[derive(Debug, PartialEq, Eq, ThisError)] +pub enum ScmiDeviceError { + #[error("Generic error")] + GenericError, + #[error("Invalid parameters")] + InvalidParameters, + #[error("No such device")] + NoSuchDevice, + #[error("Device not enabled")] + NotEnabled, + #[error("Unsupported request")] + UnsupportedRequest, +} + +/// The highest representation of an SCMI device. +/// +/// A device is an entity bound to a SCMI protocol that can take an SCMI +/// message id and parameters and respond with [MessageValue]s. See +/// [crate::devices] how devices are defined and created. +pub trait ScmiDevice: Send { + /// Initializes the device (if needed). + /// + /// If any error occurs preventing the operation of the device, a + /// corresponding error message must be returned. + fn initialize(&mut self) -> Result<(), DeviceError>; + /// Returns the SCMI protocol id that the device is attached to. + fn protocol(&self) -> ProtocolId; + /// Handles an SCMI request. + /// + /// `message_id` is an SCMI message id from the + /// given SCMI protocol and `parameters` are the SCMI request parameters + /// already represented as [MessageValue]s. + fn handle( + &mut self, + message_id: MessageId, + parameters: &[MessageValue], + ) -> Result; +} + +type DeviceList = Vec>; + +/// Mapping of SCMI protocols to devices that can handle them. +struct DeviceMap(Arc>>); + +impl DeviceMap { + fn new() -> Self { + Self(Arc::new(Mutex::new(HashMap::new()))) + } + + // This is the maximum number of the remaining sensors + // SENSOR_DESCRIPTION_GET supports -- the upper 16 bits of the response. + const MAX_NUMBER_OF_PROTOCOL_DEVICES: usize = 0xFFFF; + + fn insert(&mut self, device: Box) { + let mut device_map = self.0.lock().unwrap(); + let devices = device_map.entry(device.protocol()).or_default(); + if devices.len() >= Self::MAX_NUMBER_OF_PROTOCOL_DEVICES { + panic!( + "Too many devices defined for protocol {}", + device.protocol() + ); + } + devices.push(device); + } + + fn number_of_devices(&self, protocol_id: ProtocolId) -> u16 { + match self.0.lock().unwrap().get(&protocol_id) { + Some(devices) => devices.len() as u16, + None => 0, + } + } + + fn handle( + &self, + device_index: usize, + protocol_id: ProtocolId, + message_id: MessageId, + parameters: &[MessageValue], + ) -> Result { + match self.0.lock().unwrap().get_mut(&protocol_id) { + Some(devices) => match devices.get_mut(device_index) { + Some(device) => device.handle(message_id, parameters), + None => Result::Err(ScmiDeviceError::NoSuchDevice), + }, + None => Result::Err(ScmiDeviceError::NoSuchDevice), + } + } +} + +pub type DeviceResult = Result; + +pub struct ScmiHandler { + handlers: HandlerMap, + devices: DeviceMap, +} + +impl ScmiHandler { + /// Creates an instance for handling SCMI requests. + /// + /// The function also defines handlers for particular SCMI protocols. + /// It creates a [HandlerMap] and then adds SCMI message handlers to + /// it using [HandlerMap::bind] function. This is the place (i.e. the + /// functions called from here) where to add bindings for SCMI protocols and + /// messages. + pub fn new() -> Self { + Self { + handlers: HandlerMap::new(), + devices: DeviceMap::new(), + } + } + + fn request_handler(&self, request: &ScmiRequest) -> Option<&HandlerInfo> { + self.handlers.get(request.protocol_id, request.message_id) + } + + pub fn handle(&mut self, request: ScmiRequest) -> ScmiResponse { + let response = match request.message_type { + MessageType::Command => match self.request_handler(&request) { + Some(info) => { + debug!( + "Calling handler for {}({:?})", + info.name, + request.parameters.as_ref().unwrap_or(&vec![]) + ); + (info.function)(self, &request) + } + _ => Response::from(ReturnStatus::NotSupported), + }, + MessageType::Unsupported => Response::from(ReturnStatus::NotSupported), + }; + ScmiResponse::from(request.header, response) + } + + pub fn number_of_parameters(&self, request: &ScmiRequest) -> Option { + self.request_handler(request).map(|info| { + info.parameters + .len() + .try_into() + .expect("Invalid parameter specification") + }) + } + + pub fn store_parameters(&self, request: &mut ScmiRequest, buffer: &[u8]) { + let handler = &self + .request_handler(request) + .expect("Attempt to process an unsupported SCMI message"); + let n_parameters = handler.parameters.len(); + debug!( + "SCMI request {}/{} parameters length: {}, buffer length: {}", + request.message_id, + request.protocol_id, + n_parameters, + buffer.len() + ); + let value_size = 4; + assert!( + buffer.len() == n_parameters * value_size, + "Unexpected parameters buffer size: buffer={} parameters={}", + buffer.len(), + n_parameters + ); + let mut values: MessageValues = Vec::with_capacity(n_parameters); + for n in 0..n_parameters { + let slice: [u8; 4] = buffer[4 * n..4 * (n + 1)] + .try_into() + .expect("Insufficient data for parameters"); + let v = match handler.parameters[n] { + ParameterType::_SignedInt32 => MessageValue::Signed(i32::from_le_bytes(slice)), + ParameterType::UnsignedInt32 => MessageValue::Unsigned(u32::from_le_bytes(slice)), + }; + debug!("SCMI parameter {}: {:?}", n, v); + values.push(v); + } + request.parameters = Some(values); + } + + fn number_of_protocols(&self) -> u32 { + let n: usize = self.handlers.keys().unique_by(|k| k.0).count(); + n.try_into() + .expect("Impossibly large number of SCMI protocols") + } + + pub fn register_device(&mut self, device: Box) { + self.devices.insert(device); + } + + fn handle_device( + &self, + device_index: usize, + protocol_id: ProtocolId, + message_id: MessageId, + parameters: &[MessageValue], + ) -> DeviceResult { + self.devices + .handle(device_index, protocol_id, message_id, parameters) + } + + fn device_response(&self, result: DeviceResult, device_index: usize) -> Response { + match result { + Ok(values) => Response::from(&values), + Err(error) => match error { + ScmiDeviceError::NoSuchDevice + | ScmiDeviceError::NotEnabled + | ScmiDeviceError::InvalidParameters => { + info!("Invalid device access: {}, {}", device_index, error); + Response::from(ReturnStatus::InvalidParameters) + } + ScmiDeviceError::UnsupportedRequest => { + info!("Unsupported request for {}", device_index); + Response::from(ReturnStatus::NotSupported) + } + ScmiDeviceError::GenericError => { + warn!("Device error in {}", device_index); + Response::from(ReturnStatus::GenericError) + } + }, + } + } + + fn handle_device_response(&self, request: &ScmiRequest, parameters: &[usize]) -> Response { + let device_index = request.get_usize(0); + let protocol_id = request.protocol_id; + let message_id = request.message_id; + let parameter_values: Vec = parameters + .iter() + .map(|i| MessageValue::Unsigned(request.get_unsigned(*i))) + .collect(); + let result = self.handle_device( + device_index, + protocol_id, + message_id, + parameter_values.as_slice(), + ); + self.device_response(result, device_index) + } + + fn discover_list_protocols(&self, request: &ScmiRequest) -> Response { + // Base protocol is skipped + let skip: usize = request + .get_unsigned(0) + .try_into() + .expect("Extremely many protocols"); + let protocols: Vec = self + .handlers + .keys() + .filter(|(protocol_id, _)| *protocol_id != BASE_PROTOCOL_ID) + .map(|(protocol_id, _)| *protocol_id) + .unique() + .sorted() + .skip(skip) + .collect(); + let n_protocols = protocols.len(); + debug!("Number of listed protocols after {}: {}", skip, n_protocols); + let mut values: Vec = vec![MessageValue::Unsigned(n_protocols as u32)]; + if n_protocols > 0 { + let mut compressed: Vec = vec![0; 1 + (n_protocols - 1) / 4]; + for i in 0..n_protocols { + debug!("Adding protocol: {}", protocols[i]); + compressed[i % 4] |= u32::from(protocols[i]) << ((i % 4) * 8); + } + for item in compressed { + values.push(MessageValue::Unsigned(item)); + } + } + Response::from(&values) + } + + fn message_attributes(&self, request: &ScmiRequest) -> Response { + let message_id: Result = request.get_unsigned(0).try_into(); + if message_id.is_err() { + return Response::from(ReturnStatus::InvalidParameters); + } + match self.handlers.get(request.protocol_id, message_id.unwrap()) { + Some(_) => Response::from(MessageValue::Unsigned(0)), + None => Response::from(ReturnStatus::NotFound), + } + } +} + +#[cfg(test)] +mod tests { + use crate::devices::{common::DeviceProperties, fake::FakeSensor}; + + use super::*; + + #[test] + fn test_response_from_status() { + let status = ReturnStatus::Busy; + let response = Response::from(status); + assert_eq!(response.values.len(), 1); + assert_eq!(response.values[0], MessageValue::Signed(status as i32)); + } + + #[test] + fn test_response_from_value() { + let value = MessageValue::Unsigned(28); + let status = ReturnStatus::Success; + let response = Response::from(value.clone()); + assert_eq!(response.values.len(), 2); + assert_eq!(response.values[0], MessageValue::Signed(status as i32)); + assert_eq!(response.values[1], value); + } + + #[test] + fn test_response_from_values() { + let status = ReturnStatus::Success; + let values = vec![ + MessageValue::Signed(-2), + MessageValue::Unsigned(8), + MessageValue::String("foo".to_owned(), MAX_SIMPLE_STRING_LENGTH), + ]; + let len = values.len() + 1; + let response = Response::from(&values); + assert_eq!(response.values.len(), len); + assert_eq!(response.values[0], MessageValue::Signed(status as i32)); + for i in 1..len { + assert_eq!(response.values[i], values[i - 1]); + } + } + + fn make_response(header: MessageHeader) -> ScmiResponse { + let values = vec![ + MessageValue::Signed(-2), + MessageValue::Unsigned(800_000_000), + MessageValue::String("foo".to_owned(), MAX_SIMPLE_STRING_LENGTH), + ]; + let response = Response::from(&values); + ScmiResponse::from(header, response) + } + + #[test] + fn test_response() { + let header: MessageHeader = 1_000_000; + let scmi_response = make_response(header); + assert_eq!(scmi_response.header, header); + let bytes: Vec = vec![ + 0x40, 0x42, 0x0F, 0x00, // header + 0x00, 0x00, 0x00, 0x00, // SUCCESS + 0xFE, 0xFF, 0xFF, 0xFF, // -2 + 0x00, 0x08, 0xAF, 0x2F, // 800 000 000 + 0x66, 0x6F, 0x6F, 0x00, // "foo" + NULLs + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + assert_eq!(scmi_response.ret_bytes, bytes); + assert_eq!(scmi_response.len(), bytes.len()); + assert_eq!(scmi_response.as_slice(), bytes.as_slice()); + } + + #[test] + fn test_communication_error_response() { + let header: MessageHeader = 1_000_000; + let scmi_response = make_response(header).communication_error(); + assert_eq!(scmi_response.header, header); + let bytes: Vec = vec![ + 0x40, 0x42, 0x0F, 0x00, // header + 0xF9, 0xFF, 0xFF, 0xFF, // ComsError + ]; + assert_eq!(scmi_response.ret_bytes, bytes); + } + + #[test] + fn test_request() { + let header: MessageHeader = 0x000304AB; + let request = ScmiRequest::new(header); + assert_eq!(request.header, header); + assert_eq!(request.message_id, 0xAB); + assert_eq!(request.message_type, MessageType::Command); + assert_eq!(request.protocol_id, 0xC1); + } + + #[test] + fn test_request_unsupported() { + let header: MessageHeader = 0x000102AB; + let request = ScmiRequest::new(header); + assert_eq!(request.header, header); + assert_eq!(request.message_id, 0xAB); + assert_eq!(request.message_type, MessageType::Unsupported); + assert_eq!(request.protocol_id, 0x40); + } + + fn make_request(protocol_id: ProtocolId, message_id: MessageId) -> ScmiRequest { + let header: MessageHeader = u32::from(message_id) | (u32::from(protocol_id) << 10); + ScmiRequest::new(header) + } + + fn store_parameters( + handler: &ScmiHandler, + request: &mut ScmiRequest, + parameters: &[MessageValue], + ) { + let mut bytes: Vec = vec![]; + for p in parameters { + let value = match p { + MessageValue::Unsigned(n) => u32::to_le_bytes(*n), + MessageValue::Signed(n) => i32::to_le_bytes(*n), + _ => panic!("Unsupported parameter type"), + }; + bytes.append(&mut value.to_vec()); + } + handler.store_parameters(request, bytes.as_slice()); + } + + #[test] + fn test_handler_parameters() { + let handler = ScmiHandler::new(); + let mut request = make_request(BASE_PROTOCOL_ID, BASE_DISCOVER_LIST_PROTOCOLS); + assert_eq!(handler.number_of_parameters(&request), Some(1)); + + let value: u32 = 1234567890; + let parameters = [MessageValue::Unsigned(value)]; + store_parameters(&handler, &mut request, ¶meters); + assert_eq!(request.parameters, Some(parameters.to_vec())); + assert_eq!(request.get_unsigned(0), value); + } + + #[test] + fn test_unsupported_parameters() { + let handler = ScmiHandler::new(); + let request = make_request(BASE_PROTOCOL_ID, 0x4); + assert_eq!(handler.number_of_parameters(&request), None); + } + + fn make_handler() -> ScmiHandler { + let mut handler = ScmiHandler::new(); + for i in 0..2 { + let properties = DeviceProperties::new(vec![("name".to_owned(), format!("fake{i}"))]); + let fake_sensor = FakeSensor::new_device(&properties).unwrap(); + handler.register_device(fake_sensor); + } + handler + } + + fn test_message( + protocol_id: ProtocolId, + message_id: MessageId, + parameters: Vec, + result_code: ReturnStatus, + result_values: Vec, + ) { + let mut handler = make_handler(); + test_message_with_handler( + protocol_id, + message_id, + parameters, + result_code, + result_values, + &mut handler, + ); + } + + fn test_message_with_handler( + protocol_id: ProtocolId, + message_id: MessageId, + parameters: Vec, + result_code: ReturnStatus, + result_values: Vec, + handler: &mut ScmiHandler, + ) { + let mut request = make_request(protocol_id, message_id); + let header = request.header; + if !parameters.is_empty() { + let parameter_slice = parameters.as_slice(); + store_parameters(handler, &mut request, parameter_slice); + } + + let response = handler.handle(request); + assert_eq!(response.header, header); + let mut bytes: Vec = vec![]; + bytes.append(&mut header.to_le_bytes().to_vec()); + bytes.append(&mut (result_code as i32).to_le_bytes().to_vec()); + for value in result_values { + let mut value_vec = match value { + MessageValue::Unsigned(n) => n.to_le_bytes().to_vec(), + MessageValue::Signed(n) => n.to_le_bytes().to_vec(), + MessageValue::String(s, size) => { + let mut v = s.as_bytes().to_vec(); + let v_len = v.len(); + assert!( + v_len < size, + "String longer than specified: {v_len} >= {size}" + ); + v.resize(size, b'\0'); + v + } + }; + bytes.append(&mut value_vec); + } + assert_eq!(response.ret_bytes, bytes.as_slice()); + } + + #[test] + fn test_base_version() { + let values = vec![MessageValue::Unsigned(0x20000)]; + test_message( + BASE_PROTOCOL_ID, + BASE_VERSION, + vec![], + ReturnStatus::Success, + values, + ); + } + + #[test] + fn test_base_protocol_attributes() { + let result = vec![MessageValue::Unsigned(1)]; + test_message( + BASE_PROTOCOL_ID, + BASE_PROTOCOL_ATTRIBUTES, + vec![], + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_base_protocol_message_attributes_supported() { + let parameters = vec![MessageValue::Unsigned(u32::from(BASE_DISCOVER_VENDOR))]; + let result = vec![MessageValue::Unsigned(0)]; + test_message( + BASE_PROTOCOL_ID, + BASE_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_base_protocol_message_attributes_unsupported() { + let parameters = vec![MessageValue::Unsigned(0x4)]; + test_message( + BASE_PROTOCOL_ID, + BASE_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::NotFound, + vec![], + ); + } + + #[test] + fn test_base_protocol_message_attributes_invalid() { + let parameters = vec![MessageValue::Unsigned(0x100)]; + test_message( + BASE_PROTOCOL_ID, + BASE_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::InvalidParameters, + vec![], + ); + } + + #[test] + fn test_base_discover_vendor() { + let result = vec![MessageValue::String( + "rust-vmm".to_owned(), + MAX_SIMPLE_STRING_LENGTH, + )]; + test_message( + BASE_PROTOCOL_ID, + BASE_DISCOVER_VENDOR, + vec![], + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_base_discover_implementation_version() { + let values = vec![MessageValue::Unsigned(0)]; + test_message( + BASE_PROTOCOL_ID, + BASE_DISCOVER_IMPLEMENTATION_VERSION, + vec![], + ReturnStatus::Success, + values, + ); + } + + #[test] + fn test_base_discover_list_protocols() { + let parameters = vec![MessageValue::Unsigned(0)]; + let result = vec![MessageValue::Unsigned(1), MessageValue::Unsigned(21)]; + test_message( + BASE_PROTOCOL_ID, + BASE_DISCOVER_LIST_PROTOCOLS, + parameters, + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_sensor_version() { + let values = vec![MessageValue::Unsigned(0x30000)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_VERSION, + vec![], + ReturnStatus::Success, + values, + ); + } + + #[test] + fn test_sensor_attributes() { + let result = vec![ + MessageValue::Unsigned(2), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + ]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_ATTRIBUTES, + vec![], + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_sensor_message_attributes_supported() { + let parameters = vec![MessageValue::Unsigned(u32::from(SENSOR_DESCRIPTION_GET))]; + let result = vec![MessageValue::Unsigned(0)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_sensor_message_attributes_unsupported() { + let parameters = vec![MessageValue::Unsigned(0x5)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::NotFound, + vec![], + ); + } + + #[test] + fn test_sensor_protocol_message_attributes_invalid() { + let parameters = vec![MessageValue::Unsigned(0x100)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_MESSAGE_ATTRIBUTES, + parameters, + ReturnStatus::InvalidParameters, + vec![], + ); + } + + fn check_sensor_description(sensor_index: u32) { + let n_sensors = 2; + let parameters = vec![MessageValue::Unsigned(sensor_index)]; + let mut result = vec![MessageValue::Unsigned(n_sensors - sensor_index)]; + for i in sensor_index..n_sensors { + let mut description = vec![ + MessageValue::Unsigned(i), + MessageValue::Unsigned(1 << 30), + MessageValue::Unsigned(3 << 16 | 1 << 8), + MessageValue::String(format!("fake{i}"), MAX_SIMPLE_STRING_LENGTH), + ]; + result.append(&mut description); + } + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_DESCRIPTION_GET, + parameters, + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_sensor_description_get() { + check_sensor_description(0); + check_sensor_description(1); + } + + #[test] + fn test_sensor_description_get_invalid() { + let parameters = vec![MessageValue::Unsigned(2)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_DESCRIPTION_GET, + parameters, + ReturnStatus::InvalidParameters, + vec![], + ); + } + + fn check_sensor_axis_description(axis_index: u32) { + let n_axes = 3; + let parameters = vec![ + MessageValue::Unsigned(0), + MessageValue::Unsigned(axis_index), + ]; + let mut result = vec![MessageValue::Unsigned(n_axes - axis_index)]; + for i in axis_index..n_axes { + let name = format!("acc_{}", char::from_u32('X' as u32 + i).unwrap()).to_string(); + let mut description = vec![ + MessageValue::Unsigned(i), + MessageValue::Unsigned(0), + MessageValue::Unsigned(u32::from(SENSOR_UNIT_METERS_PER_SECOND_SQUARED)), + MessageValue::String(name, MAX_SIMPLE_STRING_LENGTH), + ]; + result.append(&mut description); + } + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_AXIS_DESCRIPTION_GET, + parameters, + ReturnStatus::Success, + result, + ); + } + + #[test] + fn test_sensor_axis_description_get() { + check_sensor_axis_description(0); + check_sensor_axis_description(1); + check_sensor_axis_description(2); + } + + #[test] + fn test_sensor_axis_description_get_invalid() { + let parameters = vec![MessageValue::Unsigned(0), MessageValue::Unsigned(3)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_AXIS_DESCRIPTION_GET, + parameters, + ReturnStatus::InvalidParameters, + vec![], + ); + } + + fn check_enabled(sensor: u32, enabled: bool, handler: &mut ScmiHandler) { + let enabled_flag = u32::from(enabled); + let parameters = vec![MessageValue::Unsigned(sensor)]; + let result = vec![MessageValue::Unsigned(enabled_flag)]; + test_message_with_handler( + SENSOR_PROTOCOL_ID, + SENSOR_CONFIG_GET, + parameters, + ReturnStatus::Success, + result, + handler, + ); + } + + #[test] + fn test_sensor_config_get() { + let mut handler = make_handler(); + check_enabled(0, false, &mut handler); + } + + fn enable_sensor(sensor: u32, enable: bool, handler: &mut ScmiHandler) { + let enable_flag = u32::from(enable); + let parameters = vec![ + MessageValue::Unsigned(sensor), + MessageValue::Unsigned(enable_flag), + ]; + let result = vec![]; + test_message_with_handler( + SENSOR_PROTOCOL_ID, + SENSOR_CONFIG_SET, + parameters, + ReturnStatus::Success, + result, + handler, + ); + } + + #[test] + fn test_sensor_config_set() { + let mut handler = make_handler(); + enable_sensor(0, true, &mut handler); + check_enabled(0, true, &mut handler); + check_enabled(1, false, &mut handler); + enable_sensor(1, true, &mut handler); + check_enabled(1, true, &mut handler); + enable_sensor(0, true, &mut handler); + check_enabled(0, true, &mut handler); + enable_sensor(0, false, &mut handler); + check_enabled(0, false, &mut handler); + } + + #[test] + fn test_sensor_config_set_invalid() { + let parameters = vec![MessageValue::Unsigned(0), MessageValue::Unsigned(3)]; + test_message( + SENSOR_PROTOCOL_ID, + SENSOR_CONFIG_SET, + parameters, + ReturnStatus::NotSupported, + vec![], + ); + } + + #[test] + fn test_sensor_reading_get() { + let mut handler = make_handler(); + for sensor in 0..2 { + enable_sensor(sensor, true, &mut handler); + } + for iteration in 0..2 { + for sensor in 0..2 { + let parameters = vec![MessageValue::Unsigned(sensor), MessageValue::Unsigned(0)]; + let result = vec![ + MessageValue::Unsigned(iteration), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(iteration + 100), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(iteration + 200), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + MessageValue::Unsigned(0), + ]; + test_message_with_handler( + SENSOR_PROTOCOL_ID, + SENSOR_READING_GET, + parameters, + ReturnStatus::Success, + result, + &mut handler, + ); + } + } + } +} diff --git a/crates/scmi/src/vhu_scmi.rs b/crates/scmi/src/vhu_scmi.rs new file mode 100644 index 000000000..856a1b169 --- /dev/null +++ b/crates/scmi/src/vhu_scmi.rs @@ -0,0 +1,963 @@ +// SPDX-FileCopyrightText: Red Hat, Inc. +// SPDX-License-Identifier: Apache-2.0 +// Based on https://github.com/rust-vmm/vhost-device, Copyright by Linaro Ltd. + +//! General part of the vhost-user SCMI backend. Nothing very different from +//! the other rust-vmm backends. + +use log::{debug, error, warn}; +use std::io; +use std::io::Result as IoResult; +use std::mem::size_of; +use thiserror::Error as ThisError; +use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; +use vhost_user_backend::{VhostUserBackendMut, VringRwLock, VringT}; +use virtio_bindings::bindings::virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1}; +use virtio_bindings::bindings::virtio_ring::{ + VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC, +}; +use virtio_queue::{DescriptorChain, QueueOwnedT}; +use vm_memory::{ + Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, +}; +use vmm_sys_util::epoll::EventSet; +use vmm_sys_util::eventfd::{EventFd, EFD_NONBLOCK}; + +use crate::devices::common::{available_devices, DeviceError}; +use crate::scmi::{MessageHeader, ScmiHandler, ScmiRequest}; +use crate::VuScmiConfig; + +// QUEUE_SIZE must be apparently at least 1024 for MMIO. +// There is probably a maximum size per descriptor defined in the kernel. +const QUEUE_SIZE: usize = 1024; +const NUM_QUEUES: usize = 2; + +const COMMAND_QUEUE: u16 = 0; +const EVENT_QUEUE: u16 = 1; + +const VIRTIO_SCMI_F_P2A_CHANNELS: u16 = 0; + +#[derive(Debug, ThisError)] +pub enum VuScmiError { + #[error("Descriptor not found")] + DescriptorNotFound, + #[error("Descriptor read failed")] + DescriptorReadFailed, + #[error("Descriptor write failed")] + DescriptorWriteFailed, + #[error("Error when configuring device {0}: {1}")] + DeviceConfigurationError(String, DeviceError), + #[error("Failed to create new EventFd")] + EventFdFailed, + #[error("Failed to handle event, didn't match EPOLLIN")] + HandleEventNotEpollIn, + #[error("Failed to handle unknown event")] + HandleEventUnknownEvent, + #[error("Isufficient descriptor size, required: {0}, found: {1}")] + InsufficientDescriptorSize(usize, usize), + #[error("Failed to send notification")] + SendNotificationFailed, + #[error("Invalid descriptor count {0}")] + UnexpectedDescriptorCount(usize), + #[error("Invalid descriptor size, expected: {0}, found: {1}")] + UnexpectedDescriptorSize(usize, usize), + #[error("Invalid descriptor size, expected at least: {0}, found: {1}")] + UnexpectedMinimumDescriptorSize(usize, usize), + #[error("Received unexpected readable descriptor at index {0}")] + UnexpectedReadableDescriptor(usize), + #[error("Received unexpected write only descriptor at index {0}")] + UnexpectedWriteOnlyDescriptor(usize), + #[error("Unknown device requested: {0}")] + UnknownDeviceRequested(String), +} + +impl From for io::Error { + fn from(e: VuScmiError) -> Self { + Self::new(io::ErrorKind::Other, e) + } +} + +type Result = std::result::Result; + +type ScmiDescriptorChain = DescriptorChain>>; + +pub struct VuScmiBackend { + event_idx: bool, + pub exit_event: EventFd, + mem: Option>, + /// Event vring and descriptors serve for asynchronous responses and notifications. + /// They are obtained from the driver and we store them here for later use. + /// (We currently don't implement asynchronous responses or notifications but we support + /// the event queue because the Linux VIRTIO SCMI driver seems to be unhappy if it is not + /// present. And it doesn't harm to be ready for possible event queue use in future.) + event_vring: Option, + event_descriptors: Vec>>, + /// The abstraction of request handling, with all the needed information stored inside. + scmi_handler: ScmiHandler, +} + +impl VuScmiBackend { + pub fn new(config: &VuScmiConfig) -> Result { + let mut handler = ScmiHandler::new(); + let device_mapping = available_devices(); + for (name, properties) in config.devices.iter() { + match device_mapping.get(name.as_str()) { + Some(specification) => match (specification.constructor)(properties) { + Ok(mut device) => { + if let Err(error) = device.initialize() { + return Result::Err(VuScmiError::DeviceConfigurationError( + name.clone(), + error, + )); + } + handler.register_device(device); + } + Err(error) => { + return Result::Err(VuScmiError::DeviceConfigurationError( + name.clone(), + error, + )); + } + }, + None => return Result::Err(VuScmiError::UnknownDeviceRequested(name.clone())), + }; + } + Ok(Self { + event_idx: false, + exit_event: EventFd::new(EFD_NONBLOCK).map_err(|_| VuScmiError::EventFdFailed)?, + mem: None, + event_vring: None, + event_descriptors: vec![], + scmi_handler: handler, + }) + } + + pub fn process_requests( + &mut self, + requests: Vec, + vring: &VringRwLock, + ) -> Result { + if requests.is_empty() { + return Ok(true); + } + + for desc_chain in requests { + let descriptors: Vec<_> = desc_chain.clone().collect(); + if descriptors.len() != 2 { + return Err(VuScmiError::UnexpectedDescriptorCount(descriptors.len())); + } + + let desc_request = descriptors[0]; + if desc_request.is_write_only() { + return Err(VuScmiError::UnexpectedWriteOnlyDescriptor(0)); + } + + let read_desc_len: usize = desc_request.len() as usize; + let header_size = size_of::(); + if read_desc_len < header_size { + return Err(VuScmiError::UnexpectedMinimumDescriptorSize( + header_size, + read_desc_len, + )); + } + + let header = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| VuScmiError::DescriptorReadFailed)?; + let mut scmi_request = ScmiRequest::new(header); + let n_parameters = self.scmi_handler.number_of_parameters(&scmi_request); + debug!("SCMI request with n parameters: {:?}", n_parameters); + let value_size = 4; + if let Some(expected_parameters) = n_parameters { + if expected_parameters > 0 { + let param_bytes = (expected_parameters as usize) * value_size; + let total_size = value_size + param_bytes; + if read_desc_len != total_size { + return Err(VuScmiError::UnexpectedDescriptorSize( + total_size, + read_desc_len, + )); + } + let mut buffer: Vec = vec![0; header_size + param_bytes]; + desc_chain + .memory() + .read_slice(&mut buffer, desc_request.addr()) + .map_err(|_| VuScmiError::DescriptorReadFailed)?; + self.scmi_handler + .store_parameters(&mut scmi_request, &buffer[header_size..]); + } else if read_desc_len != value_size { + return Err(VuScmiError::UnexpectedDescriptorSize( + value_size, + read_desc_len, + )); + } + } + + debug!("Calling SCMI request handler"); + let mut response = self.scmi_handler.handle(scmi_request); + debug!("SCMI response: {:?}", response); + + let desc_response = descriptors[1]; + if !desc_response.is_write_only() { + return Err(VuScmiError::UnexpectedReadableDescriptor(1)); + } + + let write_desc_len: usize = desc_response.len() as usize; + if response.len() > write_desc_len { + error!( + "Response of length {} cannot fit into the descriptor size {}", + response.len(), + write_desc_len + ); + response = response.communication_error(); + if response.len() > write_desc_len { + return Err(VuScmiError::InsufficientDescriptorSize( + response.len(), + write_desc_len, + )); + } + } + desc_chain + .memory() + .write_slice(response.as_slice(), desc_response.addr()) + .map_err(|_| VuScmiError::DescriptorWriteFailed)?; + + if vring + .add_used(desc_chain.head_index(), response.len() as u32) + .is_err() + { + error!("Couldn't return used descriptors to the ring"); + } + } + Ok(true) + } + + fn process_command_queue(&mut self, vring: &VringRwLock) -> Result<()> { + debug!("Processing command queue"); + let requests: Vec<_> = vring + .get_mut() + .get_queue_mut() + .iter(self.mem.as_ref().unwrap().memory()) + .map_err(|_| VuScmiError::DescriptorNotFound)? + .collect(); + + debug!("Requests to process: {}", requests.len()); + match self.process_requests(requests, vring) { + Ok(_) => { + // Send notification once all the requests are processed + debug!("Sending processed request notification"); + vring + .signal_used_queue() + .map_err(|_| VuScmiError::SendNotificationFailed)?; + debug!("Notification sent"); + } + Err(err) => { + warn!("Failed SCMI request: {}", err); + return Err(err); + } + } + debug!("Processing command queue finished"); + Ok(()) + } + + fn start_event_queue(&mut self, vring: &VringRwLock) { + if self.event_vring.is_none() { + self.event_vring = Some(vring.clone()); + } + } + + pub fn process_event_requests( + &mut self, + requests: Vec, + _vring: &VringRwLock, + ) -> Result { + // The requests here are notifications from the guest about adding + // fresh buffers for the used ring. The Linux driver allocates 256 + // buffers for the event queue initially (arriving here in several + // batches) and then adds a free buffer after each message delivered + // through the event queue. + for desc_chain in requests { + let descriptors: Vec<_> = desc_chain.clone().collect(); + debug!( + "SCMI event request with n descriptors: {}", + descriptors.len() + ); + if descriptors.len() != 1 { + return Err(VuScmiError::UnexpectedDescriptorCount(descriptors.len())); + } + + let desc = descriptors[0]; + if !desc.is_write_only() { + return Err(VuScmiError::UnexpectedReadableDescriptor(0)); + } + debug!("SCMI event request avail descriptor length: {}", desc.len()); + + self.event_descriptors.push(desc_chain); + } + Ok(true) + } + + fn process_event_queue(&mut self, vring: &VringRwLock) -> Result<()> { + debug!("Processing event queue"); + + let requests: Vec<_> = vring + .get_mut() + .get_queue_mut() + .iter(self.mem.as_ref().unwrap().memory()) + .map_err(|_| VuScmiError::DescriptorNotFound)? + .collect(); + debug!("Requests to process: {}", requests.len()); + match self.process_event_requests(requests, vring) { + Ok(_) => { + // Send notification once all the requests are processed + debug!("Sending processed request notification"); + vring + .signal_used_queue() + .map_err(|_| VuScmiError::SendNotificationFailed)?; + debug!("Notification sent"); + } + Err(err) => { + warn!("Failed SCMI request: {}", err); + return Err(err); + } + } + self.start_event_queue(vring); + debug!("Processing event queue finished"); + Ok(()) + } +} + +/// VhostUserBackend trait methods +impl VhostUserBackendMut for VuScmiBackend { + fn num_queues(&self) -> usize { + debug!("Num queues called"); + NUM_QUEUES + } + + fn max_queue_size(&self) -> usize { + debug!("Max queue size called"); + QUEUE_SIZE + } + + fn features(&self) -> u64 { + debug!("Features called"); + 1 << VIRTIO_F_VERSION_1 + | 1 << VIRTIO_F_NOTIFY_ON_EMPTY + | 1 << VIRTIO_RING_F_INDIRECT_DESC + | 1 << VIRTIO_RING_F_EVENT_IDX + | 1 << VIRTIO_SCMI_F_P2A_CHANNELS + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + debug!("Protocol features called"); + VhostUserProtocolFeatures::MQ + } + + fn set_event_idx(&mut self, enabled: bool) { + self.event_idx = enabled; + debug!("Event idx set to: {}", enabled); + } + + fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()> { + debug!("Update memory called"); + self.mem = Some(mem); + Ok(()) + } + + fn handle_event( + &mut self, + device_event: u16, + evset: EventSet, + vrings: &[VringRwLock], + _thread_id: usize, + ) -> IoResult { + debug!("Handle event called"); + if evset != EventSet::IN { + warn!("Non-input event"); + return Err(VuScmiError::HandleEventNotEpollIn.into()); + } + + match device_event { + COMMAND_QUEUE => { + let vring = &vrings[COMMAND_QUEUE as usize]; + + if self.event_idx { + // vm-virtio's Queue implementation only checks avail_index + // once, so to properly support EVENT_IDX we need to keep + // calling process_queue() until it stops finding new + // requests on the queue. + loop { + vring.disable_notification().unwrap(); + self.process_command_queue(vring)?; + if !vring.enable_notification().unwrap() { + break; + } + } + } else { + // Without EVENT_IDX, a single call is enough. + self.process_command_queue(vring)?; + } + } + + EVENT_QUEUE => { + let vring = &vrings[EVENT_QUEUE as usize]; + + if self.event_idx { + // vm-virtio's Queue implementation only checks avail_index + // once, so to properly support EVENT_IDX we need to keep + // calling process_queue() until it stops finding new + // requests on the queue. + loop { + vring.disable_notification().unwrap(); + self.process_event_queue(vring)?; + if !vring.enable_notification().unwrap() { + break; + } + } + } else { + // Without EVENT_IDX, a single call is enough. + self.process_event_queue(vring)?; + } + } + + _ => { + warn!("unhandled device_event: {}", device_event); + return Err(VuScmiError::HandleEventUnknownEvent.into()); + } + } + debug!("Handle event finished"); + Ok(false) + } + + fn exit_event(&self, _thread_index: usize) -> Option { + debug!("Exit event called"); + self.exit_event.try_clone().ok() + } +} + +#[cfg(test)] +mod tests { + use virtio_bindings::virtio_ring::{VRING_DESC_F_NEXT, VRING_DESC_F_WRITE}; + use virtio_queue::{mock::MockSplitQueue, Descriptor, Queue}; + use vm_memory::{Address, GuestAddress, GuestMemoryAtomic, GuestMemoryMmap}; + + use super::*; + + fn scmi_header(message_id: u8, protocol_id: u8) -> u32 { + u32::from(message_id) | u32::from(protocol_id) << 10 + } + + fn build_cmd_desc_chain( + protocol_id: u8, + message_id: u8, + parameters: Vec, + ) -> ScmiDescriptorChain { + let mem = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let vq = MockSplitQueue::new(mem, 16); + let mut next_addr = vq.desc_table().total_size() + 0x100; + let mut index = 0; + let request_size: u32 = (4 + parameters.len() * 4) as u32; + + // Descriptor for the SCMI request + let desc_request = + Descriptor::new(next_addr, request_size, VRING_DESC_F_NEXT as u16, index + 1); + let mut bytes: Vec = vec![]; + bytes.append(&mut scmi_header(message_id, protocol_id).to_le_bytes().to_vec()); + for p in parameters { + bytes.append(&mut p.to_le_bytes().to_vec()); + } + mem.write_slice(bytes.as_slice(), desc_request.addr()) + .unwrap(); + vq.desc_table().store(index, desc_request).unwrap(); + next_addr += u64::from(desc_request.len()); + index += 1; + + // Descriptor for the SCMI response + let desc_response = Descriptor::new(next_addr, 0x100, VRING_DESC_F_WRITE as u16, 0); + vq.desc_table().store(index, desc_response).unwrap(); + + // Put the descriptor index 0 in the first available ring position. + mem.write_obj(0u16, vq.avail_addr().unchecked_add(4)) + .unwrap(); + // Set `avail_idx` to 1. + mem.write_obj(1u16, vq.avail_addr().unchecked_add(2)) + .unwrap(); + // Create descriptor chain from pre-filled memory. + vq.create_queue::() + .unwrap() + .iter(GuestMemoryAtomic::new(mem.clone()).memory()) + .unwrap() + .next() + .unwrap() + } + + fn build_event_desc_chain() -> ScmiDescriptorChain { + let mem = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let vq = MockSplitQueue::new(mem, 16); + let next_addr = vq.desc_table().total_size() + 0x100; + + // Descriptor for the SCMI event + let desc_response = Descriptor::new(next_addr, 0x100, VRING_DESC_F_WRITE as u16, 0); + vq.desc_table().store(0, desc_response).unwrap(); + + // Put the descriptor index 0 in the first available ring position. + mem.write_obj(0u16, vq.avail_addr().unchecked_add(4)) + .unwrap(); + // Set `avail_idx` to 1. + mem.write_obj(1u16, vq.avail_addr().unchecked_add(2)) + .unwrap(); + // Create descriptor chain from pre-filled memory. + vq.create_queue::() + .unwrap() + .iter(GuestMemoryAtomic::new(mem.clone()).memory()) + .unwrap() + .next() + .unwrap() + } + + // Build just empty descriptors + struct DescParameters { + addr: Option, + flags: u16, + len: u32, + } + fn build_dummy_desc_chain(parameters: Vec<&DescParameters>) -> ScmiDescriptorChain { + let mem = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(); + let vq = MockSplitQueue::new(mem, 16); + + for (i, p) in parameters.iter().enumerate() { + let mut f: u16 = if i == parameters.len() - 1 { + 0 + } else { + VRING_DESC_F_NEXT as u16 + }; + f |= p.flags; + let offset = match p.addr { + Some(addr) => addr, + _ => 0x100, + }; + let desc = Descriptor::new(offset, p.len, f, (i + 1) as u16); + vq.desc_table().store(i as u16, desc).unwrap(); + } + + // Put the descriptor index 0 in the first available ring position. + mem.write_obj(0u16, vq.avail_addr().unchecked_add(4)) + .unwrap(); + // Set `avail_idx` to 1. + mem.write_obj(1u16, vq.avail_addr().unchecked_add(2)) + .unwrap(); + // Create descriptor chain from pre-filled memory + vq.create_queue::() + .unwrap() + .iter(GuestMemoryAtomic::new(mem.clone()).memory()) + .unwrap() + .next() + .unwrap() + } + + fn validate_desc_chains( + desc_chains: &[ScmiDescriptorChain], + chain_index: usize, + protocol_id: u8, + message_id: u8, + status: i32, + data: Vec, + ) { + let desc_chain = &desc_chains[chain_index]; + let descriptors: Vec<_> = desc_chain.clone().collect(); + let mut response = vec![0; descriptors[1].len() as usize]; + + desc_chain + .memory() + .read(&mut response, descriptors[1].addr()) + .unwrap(); + + let mut result: Vec = scmi_header(message_id, protocol_id).to_le_bytes().to_vec(); + result.append(&mut status.to_le_bytes().to_vec()); + for d in &data { + result.append(&mut d.to_le_bytes().to_vec()); + } + assert_eq!(response[0..result.len()], result); + } + + fn make_backend() -> VuScmiBackend { + let config = VuScmiConfig { + socket_path: "/foo/scmi.sock".to_owned(), + devices: vec![], + }; + VuScmiBackend::new(&config).unwrap() + } + + #[test] + fn test_process_requests() { + let mut backend = make_backend(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + // Descriptor chain size zero, shouldn't fail + backend + .process_requests(Vec::::new(), &vring) + .unwrap(); + + // Valid single SCMI request: base protocol version + let desc_chains = vec![build_cmd_desc_chain(0x10, 0x0, vec![])]; + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(&desc_chains, 0, 0x10, 0x0, 0, vec![0x20000]); + + // Valid multi SCMI request: base protocol version + implementation version + let desc_chains = vec![ + build_cmd_desc_chain(0x10, 0x0, vec![]), + build_cmd_desc_chain(0x10, 0x5, vec![]), + ]; + backend + .process_requests(desc_chains.clone(), &vring) + .unwrap(); + validate_desc_chains(&desc_chains, 0, 0x10, 0x0, 0, vec![0x20000]); + validate_desc_chains(&desc_chains, 1, 0x10, 0x5, 0, vec![0]); + } + + #[test] + fn test_process_requests_failure() { + let mut backend = make_backend(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + let default = DescParameters { + addr: None, + flags: 0, + len: 0, + }; + + // Have only one descriptor, expected two. + let parameters = vec![&default]; + let desc_chain = build_dummy_desc_chain(parameters); + match backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::UnexpectedDescriptorCount(1) => (), + other => panic!("Unexpected result: {:?}", other), + } + + // Have three descriptors, expected two. + let parameters = vec![&default, &default, &default]; + let desc_chain = build_dummy_desc_chain(parameters); + match backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::UnexpectedDescriptorCount(3) => (), + other => panic!("Unexpected result: {:?}", other), + } + + // Write only descriptors. + let p = DescParameters { + addr: None, + flags: VRING_DESC_F_WRITE as u16, + len: 0, + }; + let parameters = vec![&p, &p]; + let desc_chain = build_dummy_desc_chain(parameters); + match backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::UnexpectedWriteOnlyDescriptor(0) => (), + other => panic!("Unexpected result: {:?}", other), + } + + // Invalid request address. + let parameters = vec![ + &DescParameters { + addr: Some(0x10000), + flags: 0, + len: 4, + }, + &DescParameters { + addr: None, + flags: VRING_DESC_F_WRITE as u16, + len: 4, + }, + ]; + let desc_chain = build_dummy_desc_chain(parameters); + match backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::DescriptorReadFailed => (), + other => panic!("Unexpected result: {:?}", other), + } + + // Invalid request length (very small). + let parameters = vec![ + &DescParameters { + addr: None, + flags: 0, + len: 2, + }, + &DescParameters { + addr: None, + flags: VRING_DESC_F_WRITE as u16, + len: 4, + }, + ]; + let desc_chain = build_dummy_desc_chain(parameters); + match backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::UnexpectedMinimumDescriptorSize(4, 2) => (), + other => panic!("Unexpected result: {:?}", other), + } + + // Invalid request length (too small). + let desc_chain = build_cmd_desc_chain(0x10, 0x2, vec![]); + match backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::UnexpectedDescriptorSize(8, 4) => (), + other => panic!("Unexpected result: {:?}", other), + } + + // Invalid request length (too large). + let desc_chain = build_cmd_desc_chain(0x10, 0x0, vec![0]); + match backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::UnexpectedDescriptorSize(4, 8) => (), + other => panic!("Unexpected result: {:?}", other), + } + + // Read only descriptors. + let p = DescParameters { + addr: None, + flags: 0, + len: 4, + }; + let parameters = vec![&p, &p]; + let desc_chain = build_dummy_desc_chain(parameters); + match backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::UnexpectedReadableDescriptor(1) => (), + other => panic!("Unexpected result: {:?}", other), + } + + // Invalid response address. + let parameters = vec![ + &DescParameters { + addr: None, + flags: 0, + len: 4, + }, + &DescParameters { + addr: Some(0x10000), + flags: VRING_DESC_F_WRITE as u16, + len: 8, + }, + ]; + let desc_chain = build_dummy_desc_chain(parameters); + match backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::DescriptorWriteFailed => (), + other => panic!("Unexpected result: {:?}", other), + } + + // Invalid response length. + let parameters = vec![ + &DescParameters { + addr: None, + flags: 0, + len: 4, + }, + &DescParameters { + addr: None, + flags: VRING_DESC_F_WRITE as u16, + len: 6, + }, + ]; + let desc_chain = build_dummy_desc_chain(parameters); + match backend + .process_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::InsufficientDescriptorSize(8, 6) => (), + other => panic!("Unexpected result: {:?}", other), + } + } + + #[test] + fn test_event_requests() { + let mut backend = make_backend(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + // Descriptor chain size zero, shouldn't fail and should be no-op + backend + .process_event_requests(Vec::::new(), &vring) + .unwrap(); + assert_eq!(backend.event_descriptors.len(), 0); + + // Valid event descriptors, should get stored + let desc_chains = vec![build_event_desc_chain(), build_event_desc_chain()]; + backend.process_event_requests(desc_chains, &vring).unwrap(); + assert_eq!(backend.event_descriptors.len(), 2); + + // Some more event descriptors + let desc_chains = vec![ + build_event_desc_chain(), + build_event_desc_chain(), + build_event_desc_chain(), + ]; + backend.process_event_requests(desc_chains, &vring).unwrap(); + assert_eq!(backend.event_descriptors.len(), 5); + } + + #[test] + fn test_event_requests_failure() { + let mut backend = make_backend(); + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + let vring = VringRwLock::new(mem, 0x1000).unwrap(); + + // Invalid number of desc chains + let p = DescParameters { + addr: None, + flags: 0, + len: 0, + }; + let desc_chain = build_dummy_desc_chain(vec![&p, &p]); + match backend + .process_event_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::UnexpectedDescriptorCount(2) => (), + other => panic!("Unexpected result: {:?}", other), + } + + // Read only descriptor + let p = DescParameters { + addr: None, + flags: 0, + len: 0, + }; + let desc_chain = build_dummy_desc_chain(vec![&p]); + match backend + .process_event_requests(vec![desc_chain], &vring) + .unwrap_err() + { + VuScmiError::UnexpectedReadableDescriptor(0) => (), + other => panic!("Unexpected result: {:?}", other), + } + } + + #[test] + fn test_backend() { + let mut backend = make_backend(); + + assert_eq!(backend.num_queues(), NUM_QUEUES); + assert_eq!(backend.max_queue_size(), QUEUE_SIZE); + assert_eq!(backend.features(), 0x171000001); + assert_eq!(backend.protocol_features(), VhostUserProtocolFeatures::MQ); + + assert_eq!(backend.queues_per_thread(), vec![0xffff_ffff]); + + backend.set_event_idx(true); + assert!(backend.event_idx); + + assert!(backend.exit_event(0).is_some()); + + let mem = GuestMemoryAtomic::new( + GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x1000)]).unwrap(), + ); + backend.update_memory(mem.clone()).unwrap(); + + let vring_request = VringRwLock::new(mem.clone(), 0x1000).unwrap(); + vring_request.set_queue_info(0x100, 0x200, 0x300).unwrap(); + vring_request.set_queue_ready(true); + + let vring_event = VringRwLock::new(mem, 0x1000).unwrap(); + vring_event.set_queue_info(0x100, 0x200, 0x300).unwrap(); + vring_event.set_queue_ready(true); + + assert_eq!( + backend + .handle_event( + 0, + EventSet::OUT, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap_err() + .kind(), + io::ErrorKind::Other + ); + + assert_eq!( + backend + .handle_event( + 2, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap_err() + .kind(), + io::ErrorKind::Other + ); + + // Hit the loop part + backend.set_event_idx(true); + backend + .handle_event( + 0, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap(); + + // Hit the non-loop part + backend.set_event_idx(false); + backend + .handle_event( + 0, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap(); + + // Hit the loop part + backend.set_event_idx(true); + backend + .handle_event( + 1, + EventSet::IN, + &[vring_request.clone(), vring_event.clone()], + 0, + ) + .unwrap(); + + // Hit the non-loop part + backend.set_event_idx(false); + backend + .handle_event(1, EventSet::IN, &[vring_request, vring_event], 0) + .unwrap(); + } +} diff --git a/crates/scsi/Cargo.toml b/crates/scsi/Cargo.toml index ae3ff8ab4..3cba3c96d 100644 --- a/crates/scsi/Cargo.toml +++ b/crates/scsi/Cargo.toml @@ -19,7 +19,7 @@ clap = { version = "4.3", features = ["derive"] } env_logger = "0.10" epoll = "4.3" log = "0.4" -num_enum = "0.6" +num_enum = "0.7" thiserror = "1.0" vhost = { version = "0.8", features = ["vhost-user-slave"] } vhost-user-backend = "0.10" diff --git a/crates/vsock/Cargo.toml b/crates/vsock/Cargo.toml index 2b37c5613..46d4e166f 100644 --- a/crates/vsock/Cargo.toml +++ b/crates/vsock/Cargo.toml @@ -7,7 +7,7 @@ repository = "https://github.com/rust-vmm/vhost-device" readme = "README.md" keywords = ["vhost", "vsock"] license = "Apache-2.0 OR BSD-3-Clause" -edition = "2018" +edition = "2021" [features] xen = ["vm-memory/xen", "vhost/xen", "vhost-user-backend/xen"] diff --git a/crates/vsock/README.md b/crates/vsock/README.md index f4e946dc6..241397195 100644 --- a/crates/vsock/README.md +++ b/crates/vsock/README.md @@ -43,17 +43,18 @@ Run the vhost-device-vsock device: vhost-device-vsock --guest-cid= \ --socket= \ --uds-path= \ - [--tx-buffer-size=host packets)>] + [--tx-buffer-size=host packets)>] \ + [--groups=] ``` or ``` -vhost-device-vsock --vm guest_cid=,socket=,uds-path=[,tx-buffer-size=host packets)>] +vhost-device-vsock --vm guest_cid=,socket=,uds-path=[,tx-buffer-size=host packets)>][,groups=] ``` Specify the `--vm` argument multiple times to specify multiple devices like this: ``` vhost-device-vsock \ ---vm guest-cid=3,socket=/tmp/vhost3.socket,uds-path=/tmp/vm3.vsock \ +--vm guest-cid=3,socket=/tmp/vhost3.socket,uds-path=/tmp/vm3.vsock,groups=group1+groupA \ --vm guest-cid=4,socket=/tmp/vhost4.socket,uds-path=/tmp/vm4.vsock,tx-buffer-size=32768 ``` @@ -69,10 +70,12 @@ vms: socket: /tmp/vhost3.socket uds_path: /tmp/vm3.sock tx_buffer_size: 65536 + groups: group1+groupA - guest_cid: 4 socket: /tmp/vhost4.socket uds_path: /tmp/vm4.sock tx_buffer_size: 32768 + groups: group2+groupB ``` Run VMM (e.g. QEMU): @@ -144,12 +147,17 @@ guest$ nc --vsock 2 1234 ### Sibling VM communication -If you add multiple VMs, they can communicate with each other. For example, if you have two VMs with -CID 3 and 4, you can run the following commands to make them communicate: +If you add multiple VMs with their devices configured with at least one common group name, they can communicate with +each other. If you don't explicitly specify a group name, a default group will be assigned to the device with name +`default`, and all such devices will be able to communicate with each other. Or you can choose a different list of +group names for each device, and only devices with the at least one group in commmon will be able to communicate with +each other. + +For example, if you have two VMs with CID 3 and 4, you can run the following commands to make them communicate: ```sh -shell1$ vhost-device-vsock --vm guest-cid=3,uds-path=/tmp/vm3.vsock,socket=/tmp/vhost3.socket \ - --vm guest-cid=4,uds-path=/tmp/vm4.vsock,socket=/tmp/vhost4.socket +shell1$ vhost-device-vsock --vm guest-cid=3,uds-path=/tmp/vm3.vsock,socket=/tmp/vhost3.socket,groups=group1+group2 \ + --vm guest-cid=4,uds-path=/tmp/vm4.vsock,socket=/tmp/vhost4.socket,groups=group1 shell2$ qemu-system-x86_64 \ -drive file=vm1.qcow2,format=qcow2,if=virtio -smp 2 -m 512M -mem-prealloc \ -object memory-backend-file,share=on,id=mem0,size=512M,mem-path="/dev/hugepages" \ @@ -164,6 +172,10 @@ shell3$ qemu-system-x86_64 \ -device vhost-user-vsock-pci,chardev=char0 ``` +Please note that here the `groups` parameter is specified just for clarity, but it is not necessary to specify it if you want +to use the default group and make all the devices communicate with one another. It is useful to specify a list of groups +when you want fine-grained control over which devices can communicate with each other. + ```sh # nc-vsock patched to set `.svm_flags = VMADDR_FLAG_TO_HOST` guest_cid3$ nc-vsock -l 1234 diff --git a/crates/vsock/src/main.rs b/crates/vsock/src/main.rs index 51f375943..fa38bfb64 100644 --- a/crates/vsock/src/main.rs +++ b/crates/vsock/src/main.rs @@ -27,6 +27,7 @@ use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; const DEFAULT_GUEST_CID: u64 = 3; const DEFAULT_TX_BUFFER_SIZE: u32 = 64 * 1024; +const DEFAULT_GROUP_NAME: &str = "default"; #[derive(Debug, ThisError)] enum CliError { @@ -56,7 +57,7 @@ enum BackendError { CouldNotCreateDaemon(vhost_user_backend::Error), } -#[derive(Args, Clone, Debug, Deserialize)] +#[derive(Args, Clone, Debug)] struct VsockParam { /// Context identifier of the guest which uniquely identifies the device for its lifetime. #[arg( @@ -78,6 +79,26 @@ struct VsockParam { /// The size of the buffer used for the TX virtqueue #[clap(long, default_value_t = DEFAULT_TX_BUFFER_SIZE, conflicts_with = "config", conflicts_with = "vm")] tx_buffer_size: u32, + + /// The list of group names to which the device belongs. + /// A group is a set of devices that allow sibling communication between their guests. + #[arg( + long, + default_value_t = String::from(DEFAULT_GROUP_NAME), + conflicts_with = "config", + conflicts_with = "vm", + verbatim_doc_comment + )] + groups: String, +} + +#[derive(Clone, Debug, Deserialize)] +struct ConfigFileVsockParam { + guest_cid: Option, + socket: String, + uds_path: String, + tx_buffer_size: Option, + groups: Option, } #[derive(Parser, Debug)] @@ -87,8 +108,9 @@ struct VsockArgs { param: Option, /// Device parameters corresponding to a VM in the form of comma separated key=value pairs. - /// The allowed keys are: guest_cid, socket, uds_path and tx_buffer_size - /// Example: --vm guest-cid=3,socket=/tmp/vhost3.socket,uds-path=/tmp/vm3.vsock,tx-buffer-size=65536 + /// The allowed keys are: guest_cid, socket, uds_path, tx_buffer_size and group. + /// Example: + /// --vm guest-cid=3,socket=/tmp/vhost3.socket,uds-path=/tmp/vm3.vsock,tx-buffer-size=65536,groups=group1+group2 /// Multiple instances of this argument can be provided to configure devices for multiple guests. #[arg(long, conflicts_with = "config", verbatim_doc_comment, value_parser = parse_vm_params)] vm: Option>, @@ -103,6 +125,7 @@ fn parse_vm_params(s: &str) -> Result { let mut socket = None; let mut uds_path = None; let mut tx_buffer_size = None; + let mut groups = None; for arg in s.trim().split(',') { let mut parts = arg.split('='); @@ -118,6 +141,7 @@ fn parse_vm_params(s: &str) -> Result { "tx_buffer_size" | "tx-buffer-size" => { tx_buffer_size = Some(val.parse().map_err(VmArgsParseError::ParseInteger)?) } + "groups" => groups = Some(val.split('+').map(String::from).collect()), _ => return Err(VmArgsParseError::InvalidKey(key.to_string())), } } @@ -127,6 +151,7 @@ fn parse_vm_params(s: &str) -> Result { socket.ok_or_else(|| VmArgsParseError::RequiredKeyNotFound("socket".to_string()))?, uds_path.ok_or_else(|| VmArgsParseError::RequiredKeyNotFound("uds-path".to_string()))?, tx_buffer_size.unwrap_or(DEFAULT_TX_BUFFER_SIZE), + groups.unwrap_or(vec![DEFAULT_GROUP_NAME.to_string()]), )) } @@ -137,16 +162,19 @@ impl VsockArgs { .add_source(config::File::new(c.as_str(), config::FileFormat::Yaml)) .build(); if let Ok(s) = b { - let mut v = s.get::>("vms").unwrap(); + let mut v = s.get::>("vms").unwrap(); if !v.is_empty() { let parsed: Vec = v .drain(..) .map(|p| { VsockConfig::new( - p.guest_cid, + p.guest_cid.unwrap_or(DEFAULT_GUEST_CID), p.socket.trim().to_string(), p.uds_path.trim().to_string(), - p.tx_buffer_size, + p.tx_buffer_size.unwrap_or(DEFAULT_TX_BUFFER_SIZE), + p.groups.map_or(vec![DEFAULT_GROUP_NAME.to_string()], |g| { + g.trim().split('+').map(String::from).collect() + }), ) }) .collect(); @@ -177,6 +205,7 @@ impl TryFrom for Vec { p.socket.trim().to_string(), p.uds_path.trim().to_string(), p.tx_buffer_size, + p.groups.trim().split('+').map(String::from).collect(), )]) }), }, @@ -281,13 +310,20 @@ mod tests { use tempfile::tempdir; impl VsockArgs { - fn from_args(guest_cid: u64, socket: &str, uds_path: &str, tx_buffer_size: u32) -> Self { + fn from_args( + guest_cid: u64, + socket: &str, + uds_path: &str, + tx_buffer_size: u32, + groups: &str, + ) -> Self { VsockArgs { param: Some(VsockParam { guest_cid, socket: socket.to_string(), uds_path: uds_path.to_string(), tx_buffer_size, + groups: groups.to_string(), }), vm: None, config: None, @@ -308,7 +344,7 @@ mod tests { let socket_path = test_dir.path().join("vhost4.socket").display().to_string(); let uds_path = test_dir.path().join("vm4.vsock").display().to_string(); - let args = VsockArgs::from_args(3, &socket_path, &uds_path, 64 * 1024); + let args = VsockArgs::from_args(3, &socket_path, &uds_path, 64 * 1024, "group1"); let configs = Vec::::try_from(args); assert!(configs.is_ok()); @@ -321,6 +357,7 @@ mod tests { assert_eq!(config.get_socket_path(), socket_path); assert_eq!(config.get_uds_path(), uds_path); assert_eq!(config.get_tx_buffer_size(), 64 * 1024); + assert_eq!(config.get_groups(), vec!["group1".to_string()]); test_dir.close().unwrap(); } @@ -341,8 +378,8 @@ mod tests { ]; let params = format!( "--vm socket={vhost3_socket},uds_path={vm3_vsock} \ - --vm socket={vhost4_socket},uds-path={vm4_vsock},guest-cid=4,tx_buffer_size=65536 \ - --vm guest-cid=5,socket={vhost5_socket},uds_path={vm5_vsock},tx-buffer-size=32768", + --vm socket={vhost4_socket},uds-path={vm4_vsock},guest-cid=4,tx_buffer_size=65536,groups=group1 \ + --vm groups=group2+group3,guest-cid=5,socket={vhost5_socket},uds_path={vm5_vsock},tx-buffer-size=32768", vhost3_socket = socket_paths[0].display(), vhost4_socket = socket_paths[1].display(), vhost5_socket = socket_paths[2].display(), @@ -370,6 +407,7 @@ mod tests { ); assert_eq!(config.get_uds_path(), uds_paths[0].display().to_string()); assert_eq!(config.get_tx_buffer_size(), 65536); + assert_eq!(config.get_groups(), vec![DEFAULT_GROUP_NAME.to_string()]); let config = configs.get(1).unwrap(); assert_eq!(config.get_guest_cid(), 4); @@ -379,6 +417,7 @@ mod tests { ); assert_eq!(config.get_uds_path(), uds_paths[1].display().to_string()); assert_eq!(config.get_tx_buffer_size(), 65536); + assert_eq!(config.get_groups(), vec!["group1".to_string()]); let config = configs.get(2).unwrap(); assert_eq!(config.get_guest_cid(), 5); @@ -388,6 +427,10 @@ mod tests { ); assert_eq!(config.get_uds_path(), uds_paths[2].display().to_string()); assert_eq!(config.get_tx_buffer_size(), 32768); + assert_eq!( + config.get_groups(), + vec!["group2".to_string(), "group3".to_string()] + ); test_dir.close().unwrap(); } @@ -407,7 +450,8 @@ mod tests { - guest_cid: 4 socket: {} uds_path: {} - tx_buffer_size: 65536", + tx_buffer_size: 32768 + groups: group1+group2", socket_path.display(), uds_path.display(), ) @@ -423,8 +467,38 @@ mod tests { assert_eq!(config.get_guest_cid(), 4); assert_eq!(config.get_socket_path(), socket_path.display().to_string()); assert_eq!(config.get_uds_path(), uds_path.display().to_string()); - std::fs::remove_file(&config_path).unwrap(); + assert_eq!(config.get_tx_buffer_size(), 32768); + assert_eq!( + config.get_groups(), + vec!["group1".to_string(), "group2".to_string()] + ); + + // Now test that optional parameters are correctly set to their default values. + let mut yaml = File::create(&config_path).unwrap(); + yaml.write_all( + format!( + "vms: + - socket: {} + uds_path: {}", + socket_path.display(), + uds_path.display(), + ) + .as_bytes(), + ) + .unwrap(); + let args = VsockArgs::from_file(&config_path.display().to_string()); + let configs = Vec::::try_from(args).unwrap(); + assert_eq!(configs.len(), 1); + + let config = &configs[0]; + assert_eq!(config.get_guest_cid(), DEFAULT_GUEST_CID); + assert_eq!(config.get_socket_path(), socket_path.display().to_string()); + assert_eq!(config.get_uds_path(), uds_path.display().to_string()); + assert_eq!(config.get_tx_buffer_size(), DEFAULT_TX_BUFFER_SIZE); + assert_eq!(config.get_groups(), vec![DEFAULT_GROUP_NAME.to_string()]); + + std::fs::remove_file(&config_path).unwrap(); test_dir.close().unwrap(); } @@ -446,7 +520,13 @@ mod tests { .display() .to_string(); - let config = VsockConfig::new(CID, vhost_socket_path, vsock_socket_path, CONN_TX_BUF_SIZE); + let config = VsockConfig::new( + CID, + vhost_socket_path, + vsock_socket_path, + CONN_TX_BUF_SIZE, + vec![DEFAULT_GROUP_NAME.to_string()], + ); let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); diff --git a/crates/vsock/src/thread_backend.rs b/crates/vsock/src/thread_backend.rs index 136912286..6d5e80ee0 100644 --- a/crates/vsock/src/thread_backend.rs +++ b/crates/vsock/src/thread_backend.rs @@ -2,6 +2,7 @@ use std::{ collections::{HashMap, HashSet, VecDeque}, + ops::Deref, os::unix::{ net::UnixStream, prelude::{AsRawFd, RawFd}, @@ -70,6 +71,8 @@ pub(crate) struct VsockThreadBackend { pub cid_map: Arc>, /// Queue of raw vsock packets recieved from sibling VMs to be sent to the guest. pub raw_pkts_queue: Arc>, + /// Set of groups assigned to the device which it is allowed to communicate with. + groups_set: Arc>>, } impl VsockThreadBackend { @@ -79,6 +82,7 @@ impl VsockThreadBackend { epoll_fd: i32, guest_cid: u64, tx_buffer_size: u32, + groups_set: Arc>>, cid_map: Arc>, ) -> Self { Self { @@ -95,6 +99,7 @@ impl VsockThreadBackend { tx_buffer_size, cid_map, raw_pkts_queue: Arc::new(RwLock::new(VecDeque::new())), + groups_set, } } @@ -180,7 +185,21 @@ impl VsockThreadBackend { if dst_cid != VSOCK_HOST_CID { let cid_map = self.cid_map.read().unwrap(); if cid_map.contains_key(&dst_cid) { - let (sibling_raw_pkts_queue, sibling_event_fd) = cid_map.get(&dst_cid).unwrap(); + let (sibling_raw_pkts_queue, sibling_groups_set, sibling_event_fd) = + cid_map.get(&dst_cid).unwrap(); + + if self + .groups_set + .read() + .unwrap() + .is_disjoint(sibling_groups_set.read().unwrap().deref()) + { + info!( + "vsock: dropping packet for cid: {:?} due to group mismatch", + dst_cid + ); + return Ok(()); + } sibling_raw_pkts_queue .write() @@ -337,6 +356,7 @@ mod tests { const DATA_LEN: usize = 16; const CONN_TX_BUF_SIZE: u32 = 64 * 1024; + const GROUP_NAME: &str = "default"; #[test] fn test_vsock_thread_backend() { @@ -352,6 +372,8 @@ mod tests { let epoll_fd = epoll::create(false).unwrap(); + let groups_set: HashSet = vec![GROUP_NAME.to_string()].into_iter().collect(); + let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); let mut vtp = VsockThreadBackend::new( @@ -359,6 +381,7 @@ mod tests { epoll_fd, CID, CONN_TX_BUF_SIZE, + Arc::new(RwLock::new(groups_set)), cid_map, ); @@ -434,14 +457,30 @@ mod tests { sibling_vhost_socket_path, sibling_vsock_socket_path, CONN_TX_BUF_SIZE, + vec!["group1", "group2", "group3"] + .into_iter() + .map(String::from) + .collect(), ); let sibling_backend = Arc::new(VhostUserVsockBackend::new(sibling_config, cid_map.clone()).unwrap()); let epoll_fd = epoll::create(false).unwrap(); - let mut vtp = - VsockThreadBackend::new(vsock_socket_path, epoll_fd, CID, CONN_TX_BUF_SIZE, cid_map); + + let groups_set: HashSet = vec!["groupA", "groupB", "group3"] + .into_iter() + .map(String::from) + .collect(); + + let mut vtp = VsockThreadBackend::new( + vsock_socket_path, + epoll_fd, + CID, + CONN_TX_BUF_SIZE, + Arc::new(RwLock::new(groups_set)), + cid_map, + ); assert!(!vtp.pending_raw_pkts()); diff --git a/crates/vsock/src/vhu_vsock.rs b/crates/vsock/src/vhu_vsock.rs index c64f11fe2..0d25b386b 100644 --- a/crates/vsock/src/vhu_vsock.rs +++ b/crates/vsock/src/vhu_vsock.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, io::{self, Result as IoResult}, sync::{Arc, Mutex, RwLock}, u16, u32, u64, u8, @@ -24,7 +24,8 @@ use vmm_sys_util::{ use crate::thread_backend::RawPktsQ; use crate::vhu_vsock_thread::*; -pub(crate) type CidMap = HashMap>, EventFd)>; +pub(crate) type CidMap = + HashMap>, Arc>>, EventFd)>; const NUM_QUEUES: usize = 3; const QUEUE_SIZE: usize = 256; @@ -150,17 +151,25 @@ pub(crate) struct VsockConfig { socket: String, uds_path: String, tx_buffer_size: u32, + groups: Vec, } impl VsockConfig { /// Create a new instance of the VsockConfig struct, containing the /// parameters to be fed into the vsock-backend server. - pub fn new(guest_cid: u64, socket: String, uds_path: String, tx_buffer_size: u32) -> Self { + pub fn new( + guest_cid: u64, + socket: String, + uds_path: String, + tx_buffer_size: u32, + groups: Vec, + ) -> Self { Self { guest_cid, socket, uds_path, tx_buffer_size, + groups, } } @@ -184,6 +193,10 @@ impl VsockConfig { pub fn get_tx_buffer_size(&self) -> u32 { self.tx_buffer_size } + + pub fn get_groups(&self) -> Vec { + self.groups.clone() + } } /// A local port and peer port pair used to retrieve @@ -227,6 +240,7 @@ impl VhostUserVsockBackend { config.get_uds_path(), config.get_guest_cid(), config.get_tx_buffer_size(), + config.get_groups(), cid_map, )?); let queues_per_thread = vec![QUEUE_MASK]; @@ -364,6 +378,8 @@ mod tests { fn test_vsock_backend() { const CID: u64 = 3; + let groups_list: Vec = vec![String::from("default")]; + let test_dir = tempdir().expect("Could not create a temp test directory."); let vhost_socket_path = test_dir @@ -382,6 +398,7 @@ mod tests { vhost_socket_path.to_string(), vsock_socket_path.to_string(), CONN_TX_BUF_SIZE, + groups_list, ); let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); @@ -451,6 +468,8 @@ mod tests { fn test_vsock_backend_failures() { const CID: u64 = 3; + let groups: Vec = vec![String::from("default")]; + let test_dir = tempdir().expect("Could not create a temp test directory."); let vhost_socket_path = test_dir @@ -469,6 +488,7 @@ mod tests { "/sys/not_allowed.socket".to_string(), "/sys/not_allowed.vsock".to_string(), CONN_TX_BUF_SIZE, + groups.clone(), ); let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); @@ -481,6 +501,7 @@ mod tests { vhost_socket_path.to_string(), vsock_socket_path.to_string(), CONN_TX_BUF_SIZE, + groups, ); let backend = VhostUserVsockBackend::new(config, cid_map).unwrap(); diff --git a/crates/vsock/src/vhu_vsock_thread.rs b/crates/vsock/src/vhu_vsock_thread.rs index 5513cc64f..a3790468f 100644 --- a/crates/vsock/src/vhu_vsock_thread.rs +++ b/crates/vsock/src/vhu_vsock_thread.rs @@ -1,9 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use std::{ + collections::HashSet, fs::File, io, io::Read, + iter::FromIterator, num::Wrapping, ops::Deref, os::unix::{ @@ -79,6 +81,7 @@ impl VhostUserVsockThread { uds_path: String, guest_cid: u64, tx_buffer_size: u32, + groups: Vec, cid_map: Arc>, ) -> Result { // TODO: better error handling, maybe add a param to force the unlink @@ -93,6 +96,10 @@ impl VhostUserVsockThread { let host_raw_fd = host_sock.as_raw_fd(); + let mut groups = groups; + let groups_set: Arc>> = + Arc::new(RwLock::new(HashSet::from_iter(groups.drain(..)))); + let sibling_event_fd = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?; let thread_backend = VsockThreadBackend::new( @@ -100,6 +107,7 @@ impl VhostUserVsockThread { epoll_fd, guest_cid, tx_buffer_size, + groups_set.clone(), cid_map.clone(), ); @@ -107,6 +115,7 @@ impl VhostUserVsockThread { guest_cid, ( thread_backend.raw_pkts_queue.clone(), + groups_set, sibling_event_fd.try_clone().unwrap(), ), ); @@ -735,6 +744,8 @@ mod tests { #[test] fn test_vsock_thread() { + let groups: Vec = vec![String::from("default")]; + let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); let test_dir = tempdir().expect("Could not create a temp test directory."); @@ -747,6 +758,7 @@ mod tests { .to_string(), 3, CONN_TX_BUF_SIZE, + groups, cid_map, ); assert!(t.is_ok()); @@ -804,6 +816,8 @@ mod tests { #[test] fn test_vsock_thread_failures() { + let groups: Vec = vec![String::from("default")]; + let cid_map: Arc> = Arc::new(RwLock::new(HashMap::new())); let test_dir = tempdir().expect("Could not create a temp test directory."); @@ -812,6 +826,7 @@ mod tests { "/sys/not_allowed.vsock".to_string(), 3, CONN_TX_BUF_SIZE, + groups.clone(), cid_map.clone(), ); assert!(t.is_err()); @@ -822,7 +837,8 @@ mod tests { .display() .to_string(); let mut t = - VhostUserVsockThread::new(vsock_socket_path, 3, CONN_TX_BUF_SIZE, cid_map).unwrap(); + VhostUserVsockThread::new(vsock_socket_path, 3, CONN_TX_BUF_SIZE, groups, cid_map) + .unwrap(); assert!(VhostUserVsockThread::epoll_register(-1, -1, epoll::Events::EPOLLIN).is_err()); assert!(VhostUserVsockThread::epoll_modify(-1, -1, epoll::Events::EPOLLIN).is_err()); assert!(VhostUserVsockThread::epoll_unregister(-1, -1).is_err());