diff --git a/Cargo.lock b/Cargo.lock index 74b8420d2..c066dd4eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,6 +268,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -309,6 +320,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -425,6 +445,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -449,8 +475,22 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", + "wasip3", ] [[package]] @@ -459,6 +499,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -467,7 +516,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.2.0", ] [[package]] @@ -476,6 +525,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "imago" version = "0.2.2" @@ -503,7 +558,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -563,7 +620,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom", + "getrandom 0.3.4", "libc", ] @@ -657,11 +714,13 @@ dependencies = [ "lru", "nix 0.30.1", "pipewire", - "rand", + "rand 0.9.2", "thiserror 2.0.18", + "vhost", "virtio-bindings", "vm-fdt", "vm-memory", + "vmm-sys-util 0.15.0", "zerocopy", ] @@ -808,6 +867,12 @@ dependencies = [ "vmm-sys-util 0.14.0", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.183" @@ -835,7 +900,7 @@ dependencies = [ "log", "nitro-enclaves 0.5.0", "once_cell", - "rand", + "rand 0.9.2", "vm-memory", ] @@ -916,7 +981,7 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -968,7 +1033,7 @@ dependencies = [ "bitflags 2.11.0", "libc", "nix 0.26.4", - "rand", + "rand 0.9.2", "vsock", ] @@ -1140,6 +1205,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1164,6 +1239,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.9.2" @@ -1171,7 +1252,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] @@ -1181,7 +1273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", ] [[package]] @@ -1190,9 +1282,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom", + "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "redox_syscall" version = "0.7.3" @@ -1341,7 +1439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -1618,6 +1716,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unty" version = "0.0.4" @@ -1636,7 +1740,9 @@ version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ + "getrandom 0.4.2", "js-sys", + "rand 0.10.1", "wasm-bindgen", ] @@ -1652,6 +1758,19 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vhost" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c76d90ce3c6b37d610a5304c9a445cfff580cf8b4b9fd02fb256aaf68552c28a" +dependencies = [ + "bitflags 2.11.0", + "libc", + "uuid", + "vm-memory", + "vmm-sys-util 0.15.0", +] + [[package]] name = "virtio-bindings" version = "0.2.7" @@ -1701,6 +1820,16 @@ dependencies = [ "libc", ] +[[package]] +name = "vmm-sys-util" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "506c62fdf617a5176827c2f9afbcf1be155b03a9b4bf9617a60dbc07e3a1642f" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "vsock" version = "0.5.3" @@ -1720,6 +1849,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.114" @@ -1765,6 +1903,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1813,6 +1985,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "xattr" diff --git a/Makefile b/Makefile index d7f5a981b..17ec2f2d3 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,9 @@ endif ifeq ($(INPUT),1) FEATURE_FLAGS += --features input endif +ifeq ($(VHOST_USER),1) + FEATURE_FLAGS += --features vhost-user +endif ifeq ($(AWS_NITRO),1) VARIANT = -awsnitro FEATURE_FLAGS := --features aws-nitro,net diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 48f7d878f..c4c63539f 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -4,18 +4,18 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.6.19" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -28,44 +28,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "atty" @@ -90,7 +90,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "cexpr", "clang-sys", "itertools", @@ -110,17 +110,17 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "cairo-rs" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6466a563dea2e99f59f6ffbb749fd0bdf75764f5e6e93976b5e7bd73c4c9efb" +checksum = "b01fe135c0bd16afe262b6dea349bd5ea30e6de50708cec639aae7c5c14cc7e4" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "cairo-sys-rs", "glib", "libc", @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "cairo-sys-rs" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab7e9f13c802625aad1ad2b4ae3989f4ce9339ff388f335a6f109f9338705e2" +checksum = "06c28280c6b12055b5e39e4554271ae4e6630b27c0da9148c4cf6485fc6d245c" dependencies = [ "glib-sys", "libc", @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.20.1" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0390889d58f934f01cd49736275b4c2da15bcfc328c78ff2349907e6cabf22" +checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" dependencies = [ "smallvec", "target-lexicon", @@ -158,9 +158,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -180,18 +180,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.41" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -213,15 +213,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "crossbeam-channel" @@ -275,24 +275,24 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -301,15 +301,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -318,29 +318,28 @@ dependencies = [ [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-macro", "futures-task", "pin-project-lite", - "pin-utils", "slab", ] [[package]] name = "gdk-pixbuf" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688dc7eaf551dbac1f5b11d000d089c3db29feb25562455f47c1a2080cc60bda" +checksum = "debb0d39e3cdd84626edfd54d6e4a6ba2da9a0ef2e796e691c4e9f8646fda00c" dependencies = [ "gdk-pixbuf-sys", "gio", @@ -350,9 +349,9 @@ dependencies = [ [[package]] name = "gdk-pixbuf-sys" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5af1823d3d1cb72616873ba0a593bd440eb92da700fdfb047505a21ee3ec3e10" +checksum = "bd95ad50b9a3d2551e25dd4f6892aff0b772fe5372d84514e9d0583af60a0ce7" dependencies = [ "gio-sys", "glib-sys", @@ -363,9 +362,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.10.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a67b064d2f35e649232455c7724f56f977555d2608c43300eabc530eaa4e359" +checksum = "756564212bbe4a4ce05d88ffbd2582581ac6003832d0d32822d0825cca84bfbf" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -378,9 +377,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.10.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2edbda0d879eb85317bdb49a3da591ed70a804a10776e358ef416be38c6db2c5" +checksum = "a6d4e5b3ccf591826a4adcc83f5f57b4e59d1925cb4bf620b0d645f79498b034" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -395,9 +394,9 @@ dependencies = [ [[package]] name = "gio" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273d64c833fbbf7cd86c4cdced893c5d3f2f5d6aeb30fd0c30d172456ce8be2e" +checksum = "c5ff48bf600c68b476e61dc6b7c762f2f4eb91deef66583ba8bb815c30b5811a" dependencies = [ "futures-channel", "futures-core", @@ -412,24 +411,24 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8130f5810a839d74afc3a929c34a700bf194972bb034f2ecfe639682dd13cc" +checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "glib" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "690e8bcf8a819b5911d6ae79879226191d01253a4f602748072603defd5b9553" +checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "futures-channel", "futures-core", "futures-executor", @@ -446,9 +445,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e772291ebea14c28eb11bb75741f62f4a4894f25e60ce80100797b6b010ef0f9" +checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" dependencies = [ "heck", "proc-macro-crate", @@ -459,9 +458,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2be4c74454fb4a6bd3328320737d0fa3d6939e2d570f5d846da00cb222f6a0" +checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" dependencies = [ "libc", "system-deps", @@ -469,15 +468,15 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gobject-sys" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab318a786f9abd49d388013b9161fa0ef8218ea6118ee7111c95e62186f7d31f" +checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" dependencies = [ "glib-sys", "libc", @@ -486,9 +485,9 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0487f78e8a772ec89020458fbabadd1332bc1e3236ca1c63ef1d61afd4e5f2cc" +checksum = "2730030ac9db663fd8bfe1e7093742c1cafb92db9c315c9417c29032341fe2f9" dependencies = [ "glib", "graphene-sys", @@ -497,9 +496,9 @@ dependencies = [ [[package]] name = "graphene-sys" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "270cefb6b270fcb2ef9708c3a35c0e25c2e831dac28d75c4f87e5ad3540c9543" +checksum = "915e32091ea9ad241e4b044af62b7351c2d68aeb24f489a0d7f37a0fc484fd93" dependencies = [ "glib-sys", "libc", @@ -509,9 +508,9 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.10.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5dbe33ceed6fc20def67c03d36e532f5a4a569ae437ae015a7146094f31e10c" +checksum = "e755de9d8c5896c5beaa028b89e1969d067f1b9bf1511384ede971f5983aa153" dependencies = [ "cairo-rs", "gdk4", @@ -524,9 +523,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.10.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d76011d55dd19fde16ffdedee08877ae6ec942818cfa7bc08a91259bc0b9fc9" +checksum = "7ce91472391146f482065f1041876d8f869057b195b95399414caa163d72f4f7" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -540,9 +539,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.10.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938d68ad43080ad5ee710c30d467c1bc022ee5947856f593855691d726305b3e" +checksum = "acb21d53cfc6f7bfaf43549731c43b67ca47d87348d81c8cfc4dcdd44828e1a4" dependencies = [ "cairo-rs", "field-offset", @@ -561,9 +560,9 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.10.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0912d2068695633002b92c5966edc108b2e4f54b58c509d1eeddd4cbceb7315c" +checksum = "3ccfb5a14a3d941244815d5f8101fa12d4577b59cc47245778d8d907b0003e42" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -573,9 +572,9 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.10.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a923bdcf00e46723801162de24432cbce38a6810e0178a2d0b6dd4ecc26a1c74" +checksum = "842577fe5a1ee15d166cd3afe804ce0cab6173bc789ca32e21308834f20088dd" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -621,9 +620,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" @@ -642,15 +641,15 @@ dependencies = [ [[package]] name = "humantime" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "indexmap" -version = "2.10.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", @@ -658,9 +657,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -676,7 +675,7 @@ name = "krun-display" version = "0.1.0" dependencies = [ "bindgen", - "bitflags 2.9.1", + "bitflags 2.11.1", "log", "static_assertions", "thiserror", @@ -687,7 +686,7 @@ name = "krun-input" version = "0.1.0" dependencies = [ "bindgen", - "bitflags 2.9.1", + "bitflags 2.11.1", "libc", "log", "static_assertions", @@ -713,7 +712,7 @@ dependencies = [ "log", "nix", "vmm-sys-util", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -727,21 +726,21 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.5" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" @@ -764,7 +763,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", @@ -782,15 +781,15 @@ dependencies = [ [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "pango" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d4803f086c4f49163c31ac14db162112a22401c116435080e4be8678c507d61" +checksum = "52d1d85e2078077a065bb7fc072783d5bcd4e51b379f22d67107d0a16937eb69" dependencies = [ "gio", "glib", @@ -800,9 +799,9 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.21.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66872b3cfd328ad6d1a4f89ebd5357119bd4c592a4ddbb8f6bc2386f8ce7b898" +checksum = "b4f06627d36ed5ff303d2df65211fc2e52ba5b17bf18dd80ff3d9628d6e06cfd" dependencies = [ "glib-sys", "gobject-sys", @@ -812,54 +811,48 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -869,9 +862,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -880,15 +873,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -901,24 +894,24 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] -name = "serde" -version = "1.0.219" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -927,11 +920,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -942,9 +935,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -966,9 +959,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -977,9 +970,9 @@ dependencies = [ [[package]] name = "system-deps" -version = "7.0.5" +version = "7.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" dependencies = [ "cfg-expr", "heck", @@ -990,9 +983,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "termcolor" @@ -1005,18 +998,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1025,43 +1018,60 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ - "serde", + "indexmap", + "serde_core", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ "indexmap", - "serde", - "serde_spanned", "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "utf8parse" @@ -1071,9 +1081,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version-compare" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" [[package]] name = "vmm-sys-util" @@ -1103,11 +1113,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -1122,15 +1132,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -1140,75 +1141,11 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "winnow" -version = "0.7.12" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ "memchr", ] diff --git a/examples/chroot_vm.c b/examples/chroot_vm.c index 4c25dabd3..b0ab0a05e 100644 --- a/examples/chroot_vm.c +++ b/examples/chroot_vm.c @@ -38,7 +38,14 @@ static void print_help(char *const name) " --log=PATH Write libkrun log to file or named pipe at PATH\n" " --color-log=PATH Write libkrun log to file or named pipe at PATH, use color\n" " --net=NET_MODE Set network mode\n" - " --passt-socket=PATH Instead of starting passt, connect to passt socket at PATH" + " --passt-socket=PATH Instead of starting passt, connect to passt socket at PATH\n" + " --vhost-user-rng=PATH Use vhost-user RNG backend at socket PATH\n" + " --vhost-user-rtc=PATH Use vhost-user RTC backend at socket PATH\n" + " --vhost-user-input=PATH Use vhost-user input backend at socket PATH\n" + " --vhost-user-snd=PATH Use vhost-user sound backend at socket PATH\n" + " --vhost-user-vsock=PATH Use vhost-user vsock backend at socket PATH\n" + " --vhost-user-can=PATH Use vhost-user CAN backend at socket PATH\n" + " --vhost-user-console=PATH Use vhost-user console backend at socket PATH\n" "NET_MODE can be either TSI (default) or PASST\n" "\n" "NEWROOT: the root directory of the vm\n" @@ -48,12 +55,29 @@ static void print_help(char *const name) ); } +static bool check_krun_error(int err, const char *msg) +{ + if (err) { + errno = -err; + perror(msg); + return false; + } + return true; +} + static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "log", required_argument, NULL, 'L' }, { "color-log", required_argument, NULL, 'C' }, { "net_mode", required_argument, NULL, 'N' }, { "passt-socket", required_argument, NULL, 'P' }, + { "vhost-user-rng", required_argument, NULL, 'V' }, + { "vhost-user-rtc", required_argument, NULL, 'R' }, + { "vhost-user-input", required_argument, NULL, 'I' }, + { "vhost-user-snd", required_argument, NULL, 'S' }, + { "vhost-user-vsock", required_argument, NULL, 'K' }, + { "vhost-user-can", required_argument, NULL, 'A' }, + { "vhost-user-console", required_argument, NULL, 'O' }, { NULL, 0, NULL, 0 } }; @@ -63,6 +87,13 @@ struct cmdline { uint32_t log_style; enum net_mode net_mode; char const *passt_socket_path; + char const *vhost_user_rng_socket; + char const *vhost_user_rtc_socket; + char const *vhost_user_input_socket; + char const *vhost_user_snd_socket; + char const *vhost_user_vsock_socket; + char const *vhost_user_can_socket; + char const *vhost_user_console_socket; char const *new_root; char *const *guest_argv; }; @@ -89,6 +120,13 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline) .show_help = false, .net_mode = NET_MODE_TSI, .passt_socket_path = NULL, + .vhost_user_rng_socket = NULL, + .vhost_user_rtc_socket = NULL, + .vhost_user_input_socket = NULL, + .vhost_user_snd_socket = NULL, + .vhost_user_vsock_socket = NULL, + .vhost_user_can_socket = NULL, + .vhost_user_console_socket = NULL, .new_root = NULL, .guest_argv = NULL, .log_target = KRUN_LOG_TARGET_DEFAULT, @@ -124,6 +162,27 @@ bool parse_cmdline(int argc, char *const argv[], struct cmdline *cmdline) case 'P': cmdline->passt_socket_path = optarg; break; + case 'V': + cmdline->vhost_user_rng_socket = optarg; + break; + case 'R': + cmdline->vhost_user_rtc_socket = optarg; + break; + case 'I': + cmdline->vhost_user_input_socket = optarg; + break; + case 'S': + cmdline->vhost_user_snd_socket = optarg; + break; + case 'K': + cmdline->vhost_user_vsock_socket = optarg; + break; + case 'A': + cmdline->vhost_user_can_socket = optarg; + break; + case 'O': + cmdline->vhost_user_console_socket = optarg; + break; case '?': return false; default: @@ -249,6 +308,98 @@ int main(int argc, char *const argv[]) return -1; } + // Configure vhost-user RNG if requested + if (cmdline.vhost_user_rng_socket != NULL) { + // Test sentinel-terminated array: auto-detect queue count, use custom size + uint16_t custom_sizes[] = {512, 0}; // 0 = sentinel terminator + + if (!check_krun_error(krun_add_vhost_user_device(ctx_id, KRUN_VIRTIO_DEVICE_RNG, + cmdline.vhost_user_rng_socket, NULL, 0, custom_sizes), + "Error adding vhost-user RNG device")) { + return -1; + } + printf("Using vhost-user RNG backend at %s (custom queue size: 512)\n", cmdline.vhost_user_rng_socket); + } + + // Configure vhost-user RTC if requested + if (cmdline.vhost_user_rtc_socket != NULL) { + if (!check_krun_error(krun_add_vhost_user_device(ctx_id, KRUN_VIRTIO_DEVICE_RTC, + cmdline.vhost_user_rtc_socket, NULL, + KRUN_VHOST_USER_RTC_NUM_QUEUES, + KRUN_VHOST_USER_RTC_QUEUE_SIZES), + "Error adding vhost-user RTC device")) { + return -1; + } + printf("Using vhost-user RTC backend at %s (available as /dev/ptp* and /dev/rtc* in guest)\n", cmdline.vhost_user_rtc_socket); + } + + // Configure vhost-user input if requested + if (cmdline.vhost_user_input_socket != NULL) { + if (!check_krun_error(krun_add_vhost_user_device(ctx_id, KRUN_VIRTIO_DEVICE_INPUT, + cmdline.vhost_user_input_socket, NULL, + KRUN_VHOST_USER_INPUT_NUM_QUEUES, + KRUN_VHOST_USER_INPUT_QUEUE_SIZES), + "Error adding vhost-user input device")) { + return -1; + } + printf("Using vhost-user input backend at %s\n", cmdline.vhost_user_input_socket); + } + + // Configure vhost-user sound if requested + if (cmdline.vhost_user_snd_socket != NULL) { + if (!check_krun_error(krun_add_vhost_user_device(ctx_id, KRUN_VIRTIO_DEVICE_SND, + cmdline.vhost_user_snd_socket, NULL, + KRUN_VHOST_USER_SND_NUM_QUEUES, + KRUN_VHOST_USER_SND_QUEUE_SIZES), + "Error adding vhost-user sound device")) { + return -1; + } + printf("Using vhost-user sound backend at %s\n", cmdline.vhost_user_snd_socket); + } + + // Configure vhost-user vsock if requested + if (cmdline.vhost_user_vsock_socket != NULL) { + // Disable the implicit vsock device to avoid conflict + if (!check_krun_error(krun_disable_implicit_vsock(ctx_id), + "Error disabling implicit vsock")) { + return -1; + } + + if (!check_krun_error(krun_add_vhost_user_device(ctx_id, KRUN_VIRTIO_DEVICE_VSOCK, + cmdline.vhost_user_vsock_socket, NULL, + KRUN_VHOST_USER_VSOCK_NUM_QUEUES, + KRUN_VHOST_USER_VSOCK_QUEUE_SIZES), + "Error adding vhost-user vsock device")) { + return -1; + } + printf("Using vhost-user vsock backend at %s\n", cmdline.vhost_user_vsock_socket); + } + + // Configure vhost-user CAN if requested + if (cmdline.vhost_user_can_socket != NULL) { + if (!check_krun_error(krun_add_vhost_user_device(ctx_id, KRUN_VIRTIO_DEVICE_CAN, + cmdline.vhost_user_can_socket, NULL, + KRUN_VHOST_USER_CAN_NUM_QUEUES, + KRUN_VHOST_USER_CAN_QUEUE_SIZES), + "Error adding vhost-user CAN device")) { + return -1; + } + printf("Using vhost-user CAN backend at %s\n", cmdline.vhost_user_can_socket); + } + + // Configure vhost-user console if requested + if (cmdline.vhost_user_console_socket != NULL) { + if (!check_krun_error(krun_add_vhost_user_device(ctx_id, KRUN_VIRTIO_DEVICE_CONSOLE, + cmdline.vhost_user_console_socket, NULL, + KRUN_VHOST_USER_CONSOLE_NUM_QUEUES, + KRUN_VHOST_USER_CONSOLE_QUEUE_SIZES), + "Error adding vhost-user console device")) { + return -1; + } + printf("Using vhost-user console backend at %s (available as /dev/hvc1 in guest)\n", cmdline.vhost_user_console_socket); + printf("Test with: echo 'hello' > /dev/hvc1\n"); + } + // Raise RLIMIT_NOFILE to the maximum allowed to create some room for virtio-fs getrlimit(RLIMIT_NOFILE, &rlim); rlim.rlim_cur = rlim.rlim_max; @@ -269,7 +420,8 @@ int main(int argc, char *const argv[]) } // Map port 18000 in the host to 8000 in the guest (if networking uses TSI) - if (cmdline.net_mode == NET_MODE_TSI) { + // Skip port mapping when using vhost-user-vsock (TSI requires built-in vsock) + if (cmdline.net_mode == NET_MODE_TSI && cmdline.vhost_user_vsock_socket == NULL) { if (err = krun_set_port_map(ctx_id, &port_map[0])) { errno = -err; perror("Error configuring port map"); diff --git a/include/libkrun.h b/include/libkrun.h index b8f8008a5..87d5e1fa1 100644 --- a/include/libkrun.h +++ b/include/libkrun.h @@ -747,6 +747,123 @@ int krun_add_input_device_fd(uint32_t ctx_id, int input_fd); */ int32_t krun_set_snd_device(uint32_t ctx_id, bool enable); +/** + * Vhost-user device types. + * These correspond to virtio device type IDs for devices. + */ +#define KRUN_VIRTIO_DEVICE_CONSOLE 3 +#define KRUN_VIRTIO_DEVICE_RNG 4 +#define KRUN_VIRTIO_DEVICE_RTC 17 +#define KRUN_VIRTIO_DEVICE_INPUT 18 +#define KRUN_VIRTIO_DEVICE_VSOCK 19 +#define KRUN_VIRTIO_DEVICE_SND 25 +#define KRUN_VIRTIO_DEVICE_CAN 36 + +/** + * Vhost-user console device default queue configuration. + * Console device uses 4 queues for multiport support: + * receiveq (idx 0), transmitq (idx 1), control receiveq (idx 2), control transmitq (idx 3). + */ +#define KRUN_VHOST_USER_CONSOLE_NUM_QUEUES 4 +#define KRUN_VHOST_USER_CONSOLE_QUEUE_SIZES ((uint16_t[]){128, 128, 64, 64}) + +/** + * Vhost-user RNG device default queue configuration. + * Use these when you want explicit defaults instead of auto-detection. + */ +#define KRUN_VHOST_USER_RNG_NUM_QUEUES 1 +#define KRUN_VHOST_USER_RNG_QUEUE_SIZES ((uint16_t[]){256}) + +/** + * Vhost-user RTC device default queue configuration. + * RTC device uses 2 queues: requestq (idx 0), alarmq (idx 1). + */ +#define KRUN_VHOST_USER_RTC_NUM_QUEUES 2 +#define KRUN_VHOST_USER_RTC_QUEUE_SIZES ((uint16_t[]){1024, 1024}) + +/** + * Vhost-user input device default queue configuration. + * Input device uses 2 queues: eventq (idx 0), statusq (idx 1). + */ +#define KRUN_VHOST_USER_INPUT_NUM_QUEUES 2 +#define KRUN_VHOST_USER_INPUT_QUEUE_SIZES ((uint16_t[]){1024, 1024}) + +/** + * Vhost-user sound device default queue configuration. + * Sound device uses 4 queues: control (idx 0), event (idx 1), TX/playback (idx 2), RX/capture (idx 3). + */ +#define KRUN_VHOST_USER_SND_NUM_QUEUES 4 +#define KRUN_VHOST_USER_SND_QUEUE_SIZES ((uint16_t[]){64, 64, 64, 64}) + +/** + * Vhost-user vsock device default queue configuration. + * Vsock device uses 3 queues: RX (idx 0), TX (idx 1), event (idx 2). + */ +#define KRUN_VHOST_USER_VSOCK_NUM_QUEUES 3 +#define KRUN_VHOST_USER_VSOCK_QUEUE_SIZES ((uint16_t[]){128, 128, 128}) + +/** + * Vhost-user CAN device default queue configuration. + * CAN device uses 3 queues: TX (idx 0), RX (idx 1), control (idx 2). + */ +#define KRUN_VHOST_USER_CAN_NUM_QUEUES 3 +#define KRUN_VHOST_USER_CAN_QUEUE_SIZES ((uint16_t[]){64, 64, 64}) + +/** + * Add a vhost-user device to the VM. + * + * This function adds a vhost-user device by connecting to an external + * backend process (e.g., vhost-device-rng, vhost-device-snd). The backend + * must be running and listening on the specified socket before starting the VM. + * + * This API is designed for devices like RNG, sound, and CAN. + * + * Arguments: + * "ctx_id" - the configuration context ID. + * "device_type" - type of vhost-user device (e.g., KRUN_VHOST_USER_DEVICE_RNG). + * "socket_path" - path to the vhost-user Unix domain socket (e.g., "/tmp/vhost-rng.sock"). + * "name" - device name for logging/debugging (e.g., "vhost-rng", "vhost-snd"). + * NULL = auto-generate from device_type ("vhost-user-4", "vhost-user-25", etc.) + * "num_queues" - number of virtqueues. + * 0 = auto-detect from backend (requires backend MQ support). + * >0 = explicit queue count. + * Or use device-specific constants like KRUN_VHOST_USER_RNG_NUM_QUEUES. + * "queue_sizes" - array of queue sizes for each queue. + * NULL = use default size (256) for all queues. + * When num_queues=0 (auto-detect): array must be 0-terminated (sentinel). + * When num_queues>0 (explicit): array must have exactly num_queues elements. + * Use device-specific constants like KRUN_VHOST_USER_RNG_QUEUE_SIZES for defaults. + * + * Examples: + * // Auto-detect queue count, use default size (256) + * krun_add_vhost_user_device(ctx, KRUN_VHOST_USER_DEVICE_RNG, "/tmp/rng.sock", NULL, 0, NULL); + * + * // Auto-detect queue count, use custom size (512) for all queues + * uint16_t custom_size[] = {512, 0}; // 0 = sentinel terminator + * krun_add_vhost_user_device(ctx, KRUN_VHOST_USER_DEVICE_RNG, "/tmp/rng.sock", NULL, 0, custom_size); + * + * // Explicit defaults using #define constants + * krun_add_vhost_user_device(ctx, KRUN_VHOST_USER_DEVICE_RNG, "/tmp/rng.sock", "vhost-rng", + * KRUN_VHOST_USER_RNG_NUM_QUEUES, + * KRUN_VHOST_USER_RNG_QUEUE_SIZES); + * + * // Explicit queue count with custom sizes + * uint16_t sizes[] = {256, 512}; + * krun_add_vhost_user_device(ctx, KRUN_VHOST_USER_DEVICE_SND, "/tmp/snd.sock", "vhost-snd", 2, sizes); + * + * Returns: + * Zero on success or a negative error number on failure. + * -EINVAL - Invalid parameters (device_type, socket_path, etc.) + * -ENOENT - Context doesn't exist + * -ENOTSUP - vhost-user support not compiled in + */ +int32_t krun_add_vhost_user_device(uint32_t ctx_id, + uint32_t device_type, + const char *socket_path, + const char *name, + uint16_t num_queues, + const uint16_t *queue_sizes); + /** * Configures a map of rlimits to be set in the guest before starting the isolated binary. * diff --git a/src/devices/Cargo.toml b/src/devices/Cargo.toml index 4f5125d9a..ab6ecfe2a 100644 --- a/src/devices/Cargo.toml +++ b/src/devices/Cargo.toml @@ -20,6 +20,7 @@ input = ["zerocopy", "krun_input"] virgl_resource_map2 = [] aws-nitro = [] test_utils = [] +vhost-user = ["vhost", "vmm-sys-util"] [dependencies] bitflags = "1.2.0" @@ -31,6 +32,8 @@ nix = { version = "0.30.1", features = ["ioctl", "net", "poll", "socket", "fs"] pw = { package = "pipewire", version = "0.9.2", optional = true } rand = "0.9.2" thiserror = { version = "2.0", optional = true } +vhost = { version = "0.15", optional = true, features = ["vhost-user-frontend"] } +vmm-sys-util = { version = "0.15", optional = true } virtio-bindings = "0.2.0" vm-memory = { version = "0.17", features = ["backend-mmap"] } zerocopy = { version = "0.8.26", optional = true, features = ["derive"] } diff --git a/src/devices/src/virtio/mod.rs b/src/devices/src/virtio/mod.rs index 384aef5ac..64f19c35b 100644 --- a/src/devices/src/virtio/mod.rs +++ b/src/devices/src/virtio/mod.rs @@ -36,6 +36,8 @@ mod queue; pub mod rng; #[cfg(feature = "snd")] pub mod snd; +#[cfg(feature = "vhost-user")] +pub mod vhost_user; pub mod vsock; #[cfg(not(feature = "tee"))] @@ -56,6 +58,8 @@ pub use self::queue::{Descriptor, DescriptorChain, Queue}; pub use self::rng::*; #[cfg(feature = "snd")] pub use self::snd::Snd; +#[cfg(feature = "vhost-user")] +pub use self::vhost_user::VhostUserDevice; pub use self::vsock::*; /// When the driver initializes the device, it lets the device know about the diff --git a/src/devices/src/virtio/vhost_user/device.rs b/src/devices/src/virtio/vhost_user/device.rs new file mode 100644 index 000000000..973efce43 --- /dev/null +++ b/src/devices/src/virtio/vhost_user/device.rs @@ -0,0 +1,601 @@ +// Copyright 2026, Red Hat Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Generic vhost-user device wrapper. +//! +//! This module provides a wrapper around the vhost crate's Frontend, +//! adapting it to work with libkrun's VirtioDevice trait. + +use std::io::{self, ErrorKind, Result as IoResult}; +use std::os::fd::{AsRawFd, FromRawFd}; +use std::os::unix::net::UnixStream; +use std::sync::{Arc, Mutex}; + +use log::{debug, error, warn}; +use polly::event_manager::{EventManager, Subscriber}; +use utils::epoll::{EpollEvent, EventSet}; +use utils::eventfd::{EventFd, EFD_NONBLOCK}; +use vhost::vhost_user::message::VhostUserConfigFlags; +use vhost::vhost_user::{Frontend, VhostUserFrontend, VhostUserProtocolFeatures}; +use vhost::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData}; +use vm_memory::{Address, GuestMemory, GuestMemoryMmap, GuestMemoryRegion}; +use vmm_sys_util::eventfd::EventFd as VhostEventFd; + +use crate::virtio::{ + ActivateError, ActivateResult, DeviceQueue, DeviceState, InterruptTransport, QueueConfig, + VirtioDevice, +}; + +/// VHOST_USER_F_PROTOCOL_FEATURES (bit 30) is a backend-only feature +/// that enables vhost-user protocol extensions. It's not a virtio feature. +const VHOST_USER_F_PROTOCOL_FEATURES: u64 = 1 << 30; + +/// Generic vhost-user device wrapper. +/// +/// This wraps a vhost-user backend connection and implements the VirtioDevice +/// trait, allowing it to be used like any other virtio device in libkrun. +pub struct VhostUserDevice { + /// Vhost-user frontend connection + frontend: Arc>, + + /// Device type (e.g., VIRTIO_ID_RNG = 4) + device_type: u32, + + /// Device name for logging + device_name: String, + + /// Queue configurations + queue_configs: Vec, + + /// Available features from the backend + avail_features: u64, + + /// Whether the backend supports protocol features + has_protocol_features: bool, + + /// Acknowledged features + acked_features: u64, + + /// Device state + device_state: DeviceState, + + /// Activation event (registered with EventManager) + activate_evt: EventFd, + + /// Vring call event (backend->VMM interrupt notification) + vring_call_event: Option, +} + +impl VhostUserDevice { + /// Create a new vhost-user device by connecting to a socket. + /// + /// # Arguments + /// + /// * `socket_path` - Path to the vhost-user Unix domain socket + /// * `device_type` - Virtio device type ID + /// * `device_name` - Human-readable device name for logging + /// * `num_queues` - Number of queues (0 = query backend via MQ protocol) + /// * `queue_sizes` - Size for each queue (empty = use default 256) + /// + /// # Returns + /// + /// A new VhostUserDevice or an error if connection fails. + pub fn new( + socket_path: impl AsRef, + device_type: u32, + device_name: String, + num_queues: u16, + queue_sizes: &[u16], + ) -> IoResult { + debug!( + "Connecting to vhost-user backend at {}", + socket_path.as_ref().display() + ); + + // Connect to the vhost-user backend + let stream = UnixStream::connect(socket_path)?; + // NOTE: `num_queues` could be 0 here, but this is actually fine + // because if `VhostUserProtocolFeatures::MQ` is supported the negotiated + // value will be used automatically by Frontend + let mut frontend = Frontend::from_stream(stream, num_queues as u64); + + // Get available features from backend + let avail_features = frontend + .get_features() + .map_err(|e| io::Error::new(ErrorKind::Other, e))?; + + debug!("{}: backend features: 0x{:x}", device_name, avail_features); + + // Strip the vhost specific bit to leave only standard virtio features + let has_protocol_features = avail_features & VHOST_USER_F_PROTOCOL_FEATURES != 0; + let avail_features = avail_features & !VHOST_USER_F_PROTOCOL_FEATURES; + + if has_protocol_features { + let protocol_features = frontend + .get_protocol_features() + .map_err(|e| io::Error::new(ErrorKind::Other, e))?; + + let mut our_protocol_features = VhostUserProtocolFeatures::empty(); + if protocol_features.contains(VhostUserProtocolFeatures::CONFIG) { + our_protocol_features |= VhostUserProtocolFeatures::CONFIG; + } + if protocol_features.contains(VhostUserProtocolFeatures::MQ) { + our_protocol_features |= VhostUserProtocolFeatures::MQ; + } + + frontend + .set_protocol_features(our_protocol_features) + .map_err(|e| io::Error::new(ErrorKind::Other, e))?; + } + + // Determine actual queue count - may require protocol feature negotiation + let actual_num_queues = if num_queues == 0 { + if has_protocol_features { + let backend_queue_num = frontend + .get_queue_num() + .map_err(|e| io::Error::new(ErrorKind::Other, e))?; + + debug!( + "{}: backend reports {} queues available", + device_name, backend_queue_num + ); + + backend_queue_num as usize + } else { + return Err(io::Error::new( + ErrorKind::InvalidInput, + "Backend doesn't support protocol features, must specify queue count", + )); + } + } else { + num_queues as usize + }; + + let default_size = queue_sizes.last().copied().unwrap_or(256); + let queue_configs: Vec<_> = (0..actual_num_queues) + .map(|i| { + let size = queue_sizes.get(i).copied().unwrap_or(default_size); + QueueConfig::new(size) + }) + .collect(); + + Ok(Self { + frontend: Arc::new(Mutex::new(frontend)), + device_type, + device_name, + queue_configs, + avail_features, + has_protocol_features, + acked_features: 0, + device_state: DeviceState::Inactive, + activate_evt: EventFd::new(EFD_NONBLOCK)?, + vring_call_event: None, + }) + } + + /// Activate the vhost-user device by setting up memory and vrings. + fn activate_vhost_user( + &mut self, + mem: &GuestMemoryMmap, + queues: &[DeviceQueue], + ) -> IoResult<()> { + let mut frontend = self.frontend.lock().unwrap(); + + debug!("{}: activating vhost-user device", self.device_name); + + // Combine guest-acked features with backend-only features (QEMU approach) + let backend_feature_bits = if self.has_protocol_features { + self.acked_features | VHOST_USER_F_PROTOCOL_FEATURES + } else { + self.acked_features + }; + + frontend + .set_owner() + .map_err(|e| io::Error::new(ErrorKind::Other, e))?; + + // Only share memory regions that have file backing (memfd) + let regions: Vec = mem + .iter() + .filter_map(|region| { + if region.file_offset().is_some() { + Some(VhostUserMemoryRegionInfo::from_guest_region(region)) + } else { + None + } + }) + .collect::, _>>() + .map_err(|e| { + error!( + "{}: failed to convert memory regions: {:?}", + self.device_name, e + ); + io::Error::new(ErrorKind::Other, e) + })?; + + debug!( + "{}: sharing {} file-backed regions with backend", + self.device_name, + regions.len() + ); + + frontend.set_mem_table(®ions).map_err(|e| { + error!("{}: set_mem_table failed: {:?}", self.device_name, e); + io::Error::new(ErrorKind::Other, e) + })?; + + // If protocol features not negotiated, this triggers automatic ring enabling + frontend + .set_features(backend_feature_bits) + .map_err(|e| io::Error::new(ErrorKind::Other, e))?; + + let vring_call_event = EventFd::new(EFD_NONBLOCK)?; + + for (queue_index, device_queue) in queues.iter().enumerate() { + let queue = &device_queue.queue; + + frontend + .set_vring_num(queue_index, queue.actual_size()) + .map_err(|e| io::Error::new(ErrorKind::Other, e))?; + + // Set vring base + frontend + .set_vring_base(queue_index, 0) + .map_err(|e| io::Error::new(ErrorKind::Other, e))?; + + // Vring addresses in queue are GPAs, but vhost-user protocol expects VMM VAs + let desc_table_gpa = queue.desc_table.0; + let avail_ring_gpa = queue.avail_ring.0; + let used_ring_gpa = queue.used_ring.0; + + let desc_table_vmm = + mem.get_host_address(Address::new(desc_table_gpa)) + .map_err(|_| { + io::Error::new( + ErrorKind::InvalidInput, + format!("GPA 0x{:x} not found in any memory region", desc_table_gpa), + ) + })? as u64; + let avail_ring_vmm = + mem.get_host_address(Address::new(avail_ring_gpa)) + .map_err(|_| { + io::Error::new( + ErrorKind::InvalidInput, + format!("GPA 0x{:x} not found in any memory region", avail_ring_gpa), + ) + })? as u64; + let used_ring_vmm = mem + .get_host_address(Address::new(used_ring_gpa)) + .map_err(|_| { + io::Error::new( + ErrorKind::InvalidInput, + format!("GPA 0x{:x} not found in any memory region", used_ring_gpa), + ) + })? as u64; + + let vring_config = VringConfigData { + flags: 0, + queue_max_size: queue.get_max_size(), + queue_size: queue.actual_size(), + desc_table_addr: desc_table_vmm, + used_ring_addr: used_ring_vmm, + avail_ring_addr: avail_ring_vmm, + log_addr: None, + }; + + frontend + .set_vring_addr(queue_index, &vring_config) + .map_err(|e| { + error!("{}: set_vring_addr failed: {:?}", self.device_name, e); + io::Error::new(ErrorKind::Other, e) + })?; + + // Create vhost-compatible EventFd from the raw fd + // (bridges krun_utils::EventFd with vmm_sys_util::EventFd type mismatch) + let kick_fd = unsafe { VhostEventFd::from_raw_fd(device_queue.event.as_raw_fd()) }; + frontend + .set_vring_kick(queue_index, &kick_fd) + .map_err(|e| { + error!("{}: set_vring_kick failed: {:?}", self.device_name, e); + io::Error::new(ErrorKind::Other, e) + })?; + std::mem::forget(kick_fd); // Don't close the fd twice + + let call_fd = unsafe { VhostEventFd::from_raw_fd(vring_call_event.as_raw_fd()) }; + frontend + .set_vring_call(queue_index, &call_fd) + .map_err(|e| { + error!("{}: set_vring_call failed: {:?}", self.device_name, e); + io::Error::new(ErrorKind::Other, e) + })?; + std::mem::forget(call_fd); // Don't close the fd twice + + // Per QEMU vhost.c: when VHOST_USER_F_PROTOCOL_FEATURES is not negotiated, + // the rings start directly in the enabled state, and set_vring_enable will fail. + if self.has_protocol_features { + frontend + .set_vring_enable(queue_index, true) + .map_err(|e| io::Error::new(ErrorKind::Other, e))?; + } else { + debug!( + "{}: vring {} already enabled (protocol features not negotiated)", + self.device_name, queue_index + ); + } + } + + self.vring_call_event = Some(vring_call_event); + + debug!( + "{}: vhost-user device activated successfully", + self.device_name + ); + + Ok(()) + } +} + +impl VirtioDevice for VhostUserDevice { + fn device_type(&self) -> u32 { + self.device_type + } + + fn device_name(&self) -> &str { + &self.device_name + } + + fn queue_config(&self) -> &[QueueConfig] { + &self.queue_configs + } + + fn avail_features(&self) -> u64 { + self.avail_features + } + + fn acked_features(&self) -> u64 { + self.acked_features + } + + fn set_acked_features(&mut self, acked_features: u64) { + self.acked_features = acked_features; + } + + fn read_config(&self, offset: u64, data: &mut [u8]) { + // Fetch config from backend on every read (same as QEMU/crosvm) + // No caching to avoid invalidation issues + if self.has_protocol_features { + if let Ok(mut frontend) = self.frontend.lock() { + match frontend.get_config( + offset as u32, + data.len() as u32, + VhostUserConfigFlags::empty(), + data, + ) { + Ok((_, returned_buf)) => { + if data.len() <= returned_buf.len() { + data.copy_from_slice(&returned_buf[..data.len()]); + debug!( + "{}: read {} bytes from config at offset {}", + self.device_name, + data.len(), + offset + ); + return; + } + } + Err(e) => { + debug!( + "{}: failed to read config from backend: {:?}", + self.device_name, e + ); + } + } + } + } + + debug!( + "{}: config read at offset {} returning zeros (backend not available)", + self.device_name, offset + ); + data.fill(0); + } + + fn write_config(&mut self, offset: u64, data: &[u8]) { + if !self.has_protocol_features { + debug!( + "{}: config write at offset {} skipped (no protocol features)", + self.device_name, offset + ); + return; + } + + if let Ok(mut frontend) = self.frontend.lock() { + match frontend.set_config(offset as u32, VhostUserConfigFlags::empty(), data) { + Ok(_) => { + debug!( + "{}: wrote {} bytes to config at offset {}", + self.device_name, + data.len(), + offset + ); + } + Err(e) => { + warn!( + "{}: failed to write config at offset {}: {:?}", + self.device_name, offset, e + ); + } + } + } + } + + fn activate( + &mut self, + mem: GuestMemoryMmap, + interrupt: InterruptTransport, + queues: Vec, + ) -> ActivateResult { + if let Err(e) = self.activate_vhost_user(&mem, &queues) { + error!( + "{}: failed to activate vhost-user device: {}", + self.device_name, e + ); + return Err(ActivateError::BadActivate); + } + + self.device_state = DeviceState::Activated(mem, interrupt); + + if let Err(e) = self.activate_evt.write(1) { + error!( + "{}: failed to write activate event: {}", + self.device_name, e + ); + return Err(ActivateError::BadActivate); + } + + Ok(()) + } + + fn is_activated(&self) -> bool { + matches!(self.device_state, DeviceState::Activated(_, _)) + } + + fn reset(&mut self) -> bool { + debug!("{}: resetting vhost-user device", self.device_name); + + // Disable all vrings + if let Ok(mut frontend) = self.frontend.lock() { + for queue_index in 0..self.queue_configs.len() { + if let Err(e) = frontend.set_vring_enable(queue_index, false) { + debug!( + "{}: failed to disable vring {} during reset: {}", + self.device_name, queue_index, e + ); + } + } + } + + self.vring_call_event = None; + self.device_state = DeviceState::Inactive; + true + } +} + +impl VhostUserDevice { + fn handle_vring_call_event(&mut self, event: &EpollEvent) { + debug!("{}: vring call event received", self.device_name); + + let event_set = event.event_set(); + if event_set != EventSet::IN { + warn!( + "{}: vring call unexpected event {event_set:?}", + self.device_name + ); + return; + } + + if let Some(ref vring_call_event) = self.vring_call_event { + if let Err(e) = vring_call_event.read() { + error!( + "{}: failed to read vring_call_event: {}", + self.device_name, e + ); + return; + } + } else { + error!("{}: vring_call_event is None", self.device_name); + return; + } + + if let DeviceState::Activated(_, ref interrupt) = self.device_state { + debug!( + "{}: interrupt received from backend, signaling guest", + self.device_name + ); + interrupt.signal_used_queue(); + } + } + + fn handle_activate_event(&mut self, event_manager: &mut EventManager) { + debug!("{}: activate event", self.device_name); + + if let Err(e) = self.activate_evt.read() { + error!( + "{}: failed to consume activate event: {}", + self.device_name, e + ); + } + + if let Some(ref vring_call_event) = self.vring_call_event { + let self_subscriber = event_manager + .subscriber(self.activate_evt.as_raw_fd()) + .unwrap(); + + event_manager + .register( + vring_call_event.as_raw_fd(), + EpollEvent::new(EventSet::IN, vring_call_event.as_raw_fd() as u64), + self_subscriber.clone(), + ) + .unwrap_or_else(|e| { + error!( + "{}: failed to register vring_call_event with event manager: {e:?}", + self.device_name + ); + }); + } else { + error!( + "{}: vring_call_event is None during activation", + self.device_name + ); + } + + // Unregister activate_evt as it's only needed once + event_manager + .unregister(self.activate_evt.as_raw_fd()) + .unwrap_or_else(|e| { + error!( + "{}: failed to unregister activate event: {e:?}", + self.device_name + ); + }); + } +} + +impl Subscriber for VhostUserDevice { + fn process(&mut self, event: &EpollEvent, event_manager: &mut EventManager) { + let source = event.fd(); + let activate_evt_fd = self.activate_evt.as_raw_fd(); + let vring_call_fd = self + .vring_call_event + .as_ref() + .map(|e| e.as_raw_fd()) + .unwrap_or(-1); + + if self.is_activated() { + match source { + _ if source == vring_call_fd => self.handle_vring_call_event(event), + _ if source == activate_evt_fd => self.handle_activate_event(event_manager), + _ => warn!( + "{}: unexpected event received: {source:?}", + self.device_name + ), + } + } else if source == activate_evt_fd { + // Allow activation event even before device is activated + self.handle_activate_event(event_manager); + } else { + warn!( + "{}: device not yet activated, spurious event received: {source:?}", + self.device_name + ); + } + } + + fn interest_list(&self) -> Vec { + vec![EpollEvent::new( + EventSet::IN, + self.activate_evt.as_raw_fd() as u64, + )] + } +} diff --git a/src/devices/src/virtio/vhost_user/mod.rs b/src/devices/src/virtio/vhost_user/mod.rs new file mode 100644 index 000000000..96e0c6883 --- /dev/null +++ b/src/devices/src/virtio/vhost_user/mod.rs @@ -0,0 +1,11 @@ +// Copyright 2026, Red Hat Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Vhost-user device implementations for libkrun. +//! +//! This module provides vhost-user frontend support, allowing virtio devices +//! to run in separate processes for better isolation and flexibility. + +mod device; + +pub use device::VhostUserDevice; diff --git a/src/libkrun/Cargo.toml b/src/libkrun/Cargo.toml index dc9000916..24db7a9ff 100644 --- a/src/libkrun/Cargo.toml +++ b/src/libkrun/Cargo.toml @@ -19,6 +19,7 @@ snd = ["vmm/snd", "devices/snd"] input = ["krun_input", "vmm/input", "devices/input"] virgl_resource_map2 = ["devices/virgl_resource_map2"] aws-nitro = ["vmm/aws-nitro", "devices/aws-nitro", "dep:aws-nitro", "dep:nitro-enclaves"] +vhost-user = ["vmm/vhost-user", "devices/vhost-user"] [dependencies] crossbeam-channel = ">=0.5.15" diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index 8acf6d205..1d8b3fcb1 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -70,6 +70,9 @@ use krun_input::{InputConfigBackend, InputEventProviderBackend}; const KRUN_SUCCESS: i32 = 0; // Maximum number of arguments/environment variables we allow const MAX_ARGS: usize = 4096; +/// Maximum number of virtqueues allowed by virtio spec (16-bit queue index: 0-65535) +#[cfg(feature = "vhost-user")] +const VIRTIO_MAX_QUEUES: usize = 65536; // krunfw library name for each context #[cfg(all(target_os = "linux", not(feature = "tee")))] @@ -1798,6 +1801,91 @@ pub unsafe extern "C" fn krun_set_snd_device(ctx_id: u32, enable: bool) -> i32 { KRUN_SUCCESS } +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +#[cfg(feature = "vhost-user")] +pub unsafe extern "C" fn krun_add_vhost_user_device( + ctx_id: u32, + device_type: u32, + socket_path: *const c_char, + name: *const c_char, + num_queues: u16, + queue_sizes: *const u16, +) -> i32 { + use vmm::resources::VhostUserDeviceConfig; + + let socket_path_str = match CStr::from_ptr(socket_path).to_str() { + Ok(s) => s, + Err(_) => return -libc::EINVAL, + }; + + if socket_path_str.is_empty() { + return -libc::EINVAL; + } + + let name_opt = if name.is_null() { + None + } else { + match CStr::from_ptr(name).to_str() { + Ok(s) if !s.is_empty() => Some(s.to_string()), + _ => None, + } + }; + + let queue_sizes_vec = if queue_sizes.is_null() { + Vec::new() + } else if num_queues == 0 { + // Auto-detect mode: read queue_sizes until we hit 0 (sentinel) + let mut sizes = Vec::new(); + let mut i = 0; + loop { + let size = *queue_sizes.add(i); + if size == 0 { + break; + } + sizes.push(size); + i += 1; + + // Safety: prevent infinite loop if user forgets sentinel terminator + if i >= VIRTIO_MAX_QUEUES { + return -libc::EINVAL; + } + } + sizes + } else { + std::slice::from_raw_parts(queue_sizes, num_queues as usize).to_vec() + }; + + match CTX_MAP.lock().unwrap().entry(ctx_id) { + Entry::Occupied(mut ctx_cfg) => { + let cfg = ctx_cfg.get_mut(); + cfg.vmr.vhost_user_devices.push(VhostUserDeviceConfig { + device_type, + socket_path: socket_path_str.to_string(), + name: name_opt, + num_queues, + queue_sizes: queue_sizes_vec, + }); + KRUN_SUCCESS + } + Entry::Vacant(_) => -libc::ENOENT, + } +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +#[cfg(not(feature = "vhost-user"))] +pub unsafe extern "C" fn krun_add_vhost_user_device( + _ctx_id: u32, + _device_type: u32, + _socket_path: *const c_char, + _name: *const c_char, + _num_queues: u16, + _queue_sizes: *const u16, +) -> i32 { + -libc::ENOTSUP +} + #[allow(unused_assignments)] #[no_mangle] pub extern "C" fn krun_get_shutdown_eventfd(ctx_id: u32) -> i32 { diff --git a/src/vmm/Cargo.toml b/src/vmm/Cargo.toml index 10e3d3674..2385bc343 100644 --- a/src/vmm/Cargo.toml +++ b/src/vmm/Cargo.toml @@ -18,6 +18,7 @@ gpu = ["devices/gpu", "krun_display"] snd = ["devices/snd"] input = ["devices/input", "krun_input"] aws-nitro = [] +vhost-user = ["devices/vhost-user"] [dependencies] crossbeam-channel = ">=0.5.15" diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index 80609eb6b..1aa9c5c48 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -51,6 +51,8 @@ use devices::virtio::{port_io, MmioTransport, PortDescription, VirtioDevice, Vso use kbs_types::Tee; use crate::device_manager; +#[cfg(all(feature = "vhost-user", target_os = "linux"))] +use crate::resources::VhostUserDeviceConfig; #[cfg(target_os = "linux")] use crate::signal_handler::register_sigint_handler; #[cfg(target_os = "linux")] @@ -93,6 +95,8 @@ use vm_memory::mmap::MmapRegion; #[cfg(not(any(feature = "tee", feature = "aws-nitro")))] use vm_memory::Address; use vm_memory::Bytes; +#[cfg(all(feature = "vhost-user", target_os = "linux"))] +use vm_memory::FileOffset; #[cfg(not(feature = "aws-nitro"))] use vm_memory::GuestMemory; #[cfg(all(target_arch = "x86_64", not(feature = "tee")))] @@ -205,6 +209,8 @@ pub enum StartMicrovmError { RegisterRngDevice(device_manager::mmio::Error), /// Cannot initialize a MMIO Snd device or add a device to the MMIO Bus. RegisterSndDevice(device_manager::mmio::Error), + /// Cannot initialize a vhost-user device or add a device to the MMIO Bus. + RegisterVhostUserDevice(device_manager::mmio::Error), /// Cannot initialize a MMIO Vsock Device or add a device to the MMIO Bus. RegisterVsockDevice(device_manager::mmio::Error), /// Cannot attest the VM in the Secure Virtualization context. @@ -457,6 +463,14 @@ impl Display for StartMicrovmError { "Cannot initialize a MMIO Snd Device or add a device to the MMIO Bus. {err_msg}" ) } + RegisterVhostUserDevice(ref err) => { + let mut err_msg = err.to_string(); + err_msg = err_msg.replace('\"', ""); + write!( + f, + "Cannot initialize a vhost-user device or add a device to the MMIO Bus. {err_msg}" + ) + } RegisterVsockDevice(ref err) => { let mut err_msg = format!("{err}"); err_msg = err_msg.replace('\"', ""); @@ -967,7 +981,29 @@ pub fn build_microvm( #[cfg(not(feature = "tee"))] attach_balloon_device(&mut vmm, event_manager, intc.clone())?; #[cfg(not(feature = "tee"))] - attach_rng_device(&mut vmm, event_manager, intc.clone())?; + { + #[cfg(all(feature = "vhost-user", target_os = "linux"))] + { + const VIRTIO_ID_RNG: u32 = 4; + for device_config in &vm_resources.vhost_user_devices { + attach_vhost_user_device(&mut vmm, event_manager, intc.clone(), device_config)?; + } + + let has_vhost_user_rng = vm_resources + .vhost_user_devices + .iter() + .any(|dev| dev.device_type == VIRTIO_ID_RNG); + + if !has_vhost_user_rng { + attach_rng_device(&mut vmm, event_manager, intc.clone())?; + } + } + + #[cfg(not(all(feature = "vhost-user", target_os = "linux")))] + { + attach_rng_device(&mut vmm, event_manager, intc.clone())?; + } + } let mut console_id = 0; if !vm_resources.disable_implicit_console { attach_console_devices( @@ -1330,9 +1366,76 @@ fn load_payload( return Err(StartMicrovmError::MissingKernelConfig); }; - let kernel_region = unsafe { - MmapRegion::build_raw(kernel_host_addr as *mut u8, kernel_size, 0, 0) - .map_err(StartMicrovmError::InvalidKernelBundle)? + #[cfg(all(feature = "vhost-user", target_os = "linux"))] + let use_vhost_user = !_vm_resources.vhost_user_devices.is_empty(); + #[cfg(not(all(feature = "vhost-user", target_os = "linux")))] + let use_vhost_user = false; + + let kernel_region = if use_vhost_user { + #[cfg(all(feature = "vhost-user", target_os = "linux"))] + { + debug!( + "Creating file-backed kernel region for vhost-user (size=0x{:x})", + kernel_size + ); + // SAFETY: memfd_create is called with a valid null-terminated C string and valid flags. + // File descriptor ownership is transferred to File::from_raw_fd below. + let memfd = unsafe { + let fd = libc::memfd_create(c"kernel".as_ptr(), libc::MFD_CLOEXEC); + if fd < 0 { + error!( + "Failed to create memfd for kernel: {:?}", + io::Error::last_os_error() + ); + return Err(StartMicrovmError::GuestMemoryMmap(format!( + "memfd_create failed: {:?}", + io::Error::last_os_error() + ))); + } + if libc::ftruncate(fd, kernel_size as i64) < 0 { + error!( + "Failed to ftruncate kernel memfd: {:?}", + io::Error::last_os_error() + ); + libc::close(fd); + return Err(StartMicrovmError::GuestMemoryMmap(format!( + "ftruncate failed: {:?}", + io::Error::last_os_error() + ))); + } + debug!("Created kernel memfd with fd={}", fd); + File::from_raw_fd(fd) + }; + + let file_offset = FileOffset::new(memfd, 0); + let region = MmapRegion::from_file(file_offset, kernel_size) + .map_err(StartMicrovmError::InvalidKernelBundle)?; + + // SAFETY: kernel_host_addr points to valid kernel data of size kernel_size, + // provided by the kernel bundle loader. + let kernel_data = unsafe { + std::slice::from_raw_parts(kernel_host_addr as *const u8, kernel_size) + }; + // SAFETY: Both source (kernel_data) and destination (region) are valid for + // kernel_size bytes. Regions don't overlap as dest is newly allocated memfd-backed + // memory and source is from kernel bundle. + unsafe { + let dest = region.as_ptr() as *mut u8; + std::ptr::copy_nonoverlapping(kernel_data.as_ptr(), dest, kernel_size); + } + debug!("Copied kernel data to file-backed region"); + + region + } + #[cfg(not(all(feature = "vhost-user", target_os = "linux")))] + unreachable!() + } else { + // SAFETY: kernel_host_addr points to valid kernel data of size kernel_size. + // The memory region is managed by the kernel bundle and remains valid. + unsafe { + MmapRegion::build_raw(kernel_host_addr as *mut u8, kernel_size, 0, 0) + .map_err(StartMicrovmError::InvalidKernelBundle)? + } }; Ok(( @@ -1498,10 +1601,71 @@ pub fn create_guest_memory( .map_err(StartMicrovmError::ShmCreate)?; } + // For vhost-user devices, we need file-backed memory so the backend can mmap it + #[cfg(all(feature = "vhost-user", target_os = "linux"))] + let use_vhost_user = !vm_resources.vhost_user_devices.is_empty(); + #[cfg(not(all(feature = "vhost-user", target_os = "linux")))] + let use_vhost_user = false; + + // Add SHM regions before creating guest memory arch_mem_regions.extend(shm_manager.regions()); - let guest_mem = GuestMemoryMmap::from_ranges(&arch_mem_regions) - .map_err(|e| StartMicrovmError::GuestMemoryMmap(format!("{e:?}")))?; + let guest_mem = if use_vhost_user { + #[cfg(all(feature = "vhost-user", target_os = "linux"))] + { + debug!( + "Creating file-backed memory for vhost-user (regions: {})", + arch_mem_regions.len() + ); + // Create file-backed memory regions using memfd + let regions_with_files: Vec<_> = arch_mem_regions + .iter() + .map(|(addr, size)| { + debug!( + "Creating memfd for region: addr=0x{:x}, size=0x{:x}", + addr.0, size + ); + // SAFETY: memfd_create is called with a valid null-terminated C string and valid flags. + // File descriptor ownership is transferred to File::from_raw_fd below. + let memfd = unsafe { + let fd = libc::memfd_create(c"guest_mem".as_ptr(), libc::MFD_CLOEXEC); + if fd < 0 { + error!("Failed to create memfd: {:?}", io::Error::last_os_error()); + return Err(io::Error::last_os_error()); + } + if libc::ftruncate(fd, *size as i64) < 0 { + error!( + "Failed to ftruncate memfd: {:?}", + io::Error::last_os_error() + ); + libc::close(fd); + return Err(io::Error::last_os_error()); + } + debug!("Created memfd with fd={}", fd); + File::from_raw_fd(fd) + }; + + let file_offset = FileOffset::new(memfd, 0); + Ok((*addr, *size, Some(file_offset))) + }) + .collect::, io::Error>>() + .map_err(|e| { + StartMicrovmError::GuestMemoryMmap(format!("memfd creation failed: {e:?}")) + })?; + + debug!( + "Created {} file-backed memory regions", + regions_with_files.len() + ); + GuestMemoryMmap::from_ranges_with_files(®ions_with_files) + .map_err(|e| StartMicrovmError::GuestMemoryMmap(format!("{e:?}")))? + } + #[cfg(not(all(feature = "vhost-user", target_os = "linux")))] + unreachable!() + } else { + GuestMemoryMmap::from_ranges(&arch_mem_regions) + .map_err(|e| StartMicrovmError::GuestMemoryMmap(format!("{e:?}")))? + }; let (guest_mem, entry_addr, initrd_config, cmdline) = load_payload(vm_resources, guest_mem, &arch_mem_info, payload)?; @@ -2249,6 +2413,41 @@ fn attach_rng_device( Ok(()) } +#[cfg(not(feature = "tee"))] +#[cfg(all(feature = "vhost-user", target_os = "linux"))] +fn attach_vhost_user_device( + vmm: &mut Vmm, + event_manager: &mut EventManager, + intc: IrqChip, + device_config: &VhostUserDeviceConfig, +) -> std::result::Result<(), StartMicrovmError> { + use self::StartMicrovmError::*; + + let device_name = device_config + .name + .clone() + .unwrap_or_else(|| format!("vhost-user-{}", device_config.device_type)); + + let device = Arc::new(Mutex::new( + devices::virtio::VhostUserDevice::new( + &device_config.socket_path, + device_config.device_type, + device_name.clone(), + device_config.num_queues, + &device_config.queue_sizes, + ) + .map_err(|e| RegisterVhostUserDevice(device_manager::mmio::Error::VhostUserDevice(e)))?, + )); + + event_manager + .add_subscriber(device.clone()) + .map_err(RegisterEvent)?; + + attach_mmio_device(vmm, device_name, intc.clone(), device).map_err(RegisterVhostUserDevice)?; + + Ok(()) +} + #[cfg(feature = "gpu")] #[allow(clippy::too_many_arguments)] fn attach_gpu_device( diff --git a/src/vmm/src/device_manager/kvm/mmio.rs b/src/vmm/src/device_manager/kvm/mmio.rs index e739afb42..f2e0a687b 100644 --- a/src/vmm/src/device_manager/kvm/mmio.rs +++ b/src/vmm/src/device_manager/kvm/mmio.rs @@ -41,6 +41,9 @@ pub enum Error { DeviceNotFound, /// Failed to update the mmio device. UpdateFailed, + /// Failed to create vhost-user device. + #[cfg(all(feature = "vhost-user", target_os = "linux"))] + VhostUserDevice(io::Error), } impl fmt::Display for Error { @@ -59,6 +62,8 @@ impl fmt::Display for Error { Error::RegisterIrqFd(ref e) => write!(f, "failed to register irqfd: {e}"), Error::DeviceNotFound => write!(f, "the device couldn't be found"), Error::UpdateFailed => write!(f, "failed to update the mmio device"), + #[cfg(all(feature = "vhost-user", target_os = "linux"))] + Error::VhostUserDevice(ref e) => write!(f, "failed to create vhost-user device: {e}"), } } } diff --git a/src/vmm/src/resources.rs b/src/vmm/src/resources.rs index 2d3e5ef2b..62756ca1c 100644 --- a/src/vmm/src/resources.rs +++ b/src/vmm/src/resources.rs @@ -40,6 +40,22 @@ type Result = std::result::Result<(), E>; // Re-export TsiFlags from devices crate pub use devices::virtio::TsiFlags; +#[cfg(feature = "vhost-user")] +/// Configuration for a vhost-user device. +#[derive(Debug, Clone)] +pub struct VhostUserDeviceConfig { + /// Virtio device type ID (e.g., 4 for RNG, 25 for sound, 36 for CAN) + pub device_type: u32, + /// Path to the vhost-user Unix domain socket + pub socket_path: String, + /// Device name for logging/debugging (None = auto-generate from type) + pub name: Option, + /// Number of virtqueues (0 = use device default) + pub num_queues: u16, + /// Size of each queue (empty = use device defaults) + pub queue_sizes: Vec, +} + /// Errors encountered when configuring microVM resources. #[derive(Debug)] pub enum Error { @@ -173,6 +189,9 @@ pub struct VmResources { #[cfg(feature = "snd")] /// Enable the virtio-snd device. pub snd_device: bool, + #[cfg(feature = "vhost-user")] + /// Vhost-user device configurations + pub vhost_user_devices: Vec, /// File to send console output. pub console_output: Option, /// SMBIOS OEM Strings @@ -429,6 +448,8 @@ mod tests { input_backends: Vec::new(), #[cfg(feature = "snd")] snd_device: false, + #[cfg(feature = "vhost-user")] + vhost_user_devices: Vec::new(), console_output: None, smbios_oem_strings: None, nested_enabled: false,