Merge "Update virtio-drivers to 0.7.5" into main
diff --git a/crates/virtio-drivers/.android-checksum.json b/crates/virtio-drivers/.android-checksum.json
index ee40c06..14f1d79 100644
--- a/crates/virtio-drivers/.android-checksum.json
+++ b/crates/virtio-drivers/.android-checksum.json
@@ -1 +1 @@
-{"package":null,"files":{".cargo-checksum.json":"bb4cc152b51bcfd22685477bf28097cf86d45ed301ccf5ae70d8c9f64c5ecf00","Android.bp":"98827eb2ea6b361ddf7caf7fa0e5d8fb013c50d28d9164e0c7ea6135ebfcae0f","Cargo.toml":"9e1deaf88347c0ac121d9722f0fc8f9be91524bdbe36597735610fc8ec52347b","LICENSE":"ac7199d689b436833681f33b881e0b619be2053c06b122327caabb5a6bdb59f1","METADATA":"be4464e3027ab217d498625cb98dc4707f0e316b9aab0eea5b17de5abfe868ac","MODULE_LICENSE_MIT":"0d6f8afa3940b7f06bebee651376d43bc8b0d5b437337be2696d30377451e93a","README.md":"83f78247138f308184c7b9a649d1183d51d6c56c47cb0a8a7bc1e354d9a347c9","TEST_MAPPING":"7ffa0bced5a01703c33b82d5cfc6feebe888778bbffda68e4c1ecb9c142708ee","cargo_embargo.json":"8e1419c87a07e3391cc5da4d80c695e906b42c95874ff968f4be7a453faf756b","patches/mmio.patch":"67246832486c93662c4e604ff2c7bd4f0d4e7ca420eda6892e4eef0a958fec50","patches/rules.mk.diff":"afdb592f46d323d0e04ec3c033fb272fe7f79c28c5ad544f462bdac4c01f661b","rules.mk":"4db4d73612f66672ccbc2955979d99831fe195239e6ea51179b7ad7bb32d33f3","rust-toolchain.toml":"e29f2ad5748c6738b191d46ca6a4204014c51626eca5c106ab575225d8c915db","src/device/blk.rs":"7e63907d7653595dc9bb900c1b996c61ad1a09226b5a8ecbbea437c0ad57a7f1","src/device/common.rs":"090d853e88b893aebe01606fafcd9cab96d4601f3b7ce3cb6721975801616e0d","src/device/console.rs":"aa400f9035041f3c6bde186401466dec88785d6e0d03989cebe97224eedffb66","src/device/gpu.rs":"720ee5ccf00d4360897a14367964ccf7e3d0f75fb5f95fdb69ec1ec23212432a","src/device/input.rs":"44fb2d8866eadb5622b7495a05e96e1b5039a54249f175c3f258384a82d1c6c5","src/device/mod.rs":"2d7ff4b17e85539c2405aa62c6a007314f6a8b94ca1033fb6af158f8217f1d06","src/device/net/dev.rs":"9279bf99a856e20a58869403b82e463a05ab4c1e41591f06da6f9f1b9e6c6b99","src/device/net/dev_raw.rs":"041d333e1a38f9d6d7273c0ab9de59198f1ac6a387704c4e2cbf10cf05b47023","src/device/net/mod.rs":"89ce8151b27a93c2bbe958fe5b95c070b4edcbcc7c6fb9a691b1d9dc353e2fbd","src/device/net/net_buf.rs":"367b6d53840b1128659234796848e2808482c2a36c995960b0c41b4e92bf949b","src/device/socket/connectionmanager.rs":"6273d60f4894f87ab86e6e1340fa4feaa5a56654bb248b1ec44d7dd8a2fe0a72","src/device/socket/error.rs":"de5b97fc2d3b38e5980ab242a52d0b778500d5869758d6a32756b4cef0b61f86","src/device/socket/mod.rs":"46ba478462b1c003acf42d449763f0a7457d853ee282f9b9349a6749caa19d2b","src/device/socket/protocol.rs":"4de57579bbfeb219e8068bc7416522f3dc85f9519ddc768bf05a7ae2800cdd17","src/device/socket/vsock.rs":"9705585f4250f5ed54322149a85120ec8b77ae0bfd5ca29d89bd69b2c643987f","src/hal.rs":"f2d413f8924bfe50dbd2705f3febffd111a1ae17f160cdb5d9f12159ca3dc803","src/hal/fake.rs":"4c3ea7153f5627270041a42afe60256bdb52772923e6fdbc864f858c5346e368","src/lib.rs":"c9a768e1cf95a6027fd5da07c782681c2da6d1e5a96873e5ad7a878bf93097da","src/queue.rs":"bd3ab087263a9a30a8d4f319cfe4b3e0a78704296a29d363282cd6a785d73e95","src/transport/fake.rs":"0507bfc5203392ff0b430652787f8f19905ee0307d9e4f97ac32a049f7c4a493","src/transport/mmio.rs":"294e0a32cfb1daeb8be904061652db3465413c14aaaca43155f55d7325c66756","src/transport/mod.rs":"6412c72655e9807ce4a5539be38d350115d8c4c62fb8c75f465a9a7bef718765","src/transport/pci.rs":"42fbb0d2fe021f6dcc8490d5f168b900f1e2a75e5b39a6d8f97750857720eb5e","src/transport/pci/bus.rs":"fab9e0a81895becf29b1d482a9aba697a787188061032c286a5a84ca36dac99d","src/volatile.rs":"0b759bd807893075242115aa0f461fe5052e008b2022839e95a7602ce93cdd31"}}
\ No newline at end of file
+{"package":null,"files":{".cargo-checksum.json":"33715e646066334cbd3f9dc86080ec9075378fe91582612b3607d177e093b74f","Android.bp":"809904cc6a7c14b1055c0102751ae7b10fc523d87454fc4ae786ced34bd91238","Cargo.toml":"6d280212b0658d988e61b05a6e4eb1066034266b9b563804730fb4837d628bab","LICENSE":"ac7199d689b436833681f33b881e0b619be2053c06b122327caabb5a6bdb59f1","METADATA":"694e7c4911f26d49733e917267fec9b1f59aac272c5959bf22f3650bc65eed52","MODULE_LICENSE_MIT":"0d6f8afa3940b7f06bebee651376d43bc8b0d5b437337be2696d30377451e93a","README.md":"73fed1c071994c075530feb1dea1c0eca8b8a1959ff81a178530727ffaa4636f","TEST_MAPPING":"7ffa0bced5a01703c33b82d5cfc6feebe888778bbffda68e4c1ecb9c142708ee","cargo_embargo.json":"8e1419c87a07e3391cc5da4d80c695e906b42c95874ff968f4be7a453faf756b","patches/mmio.patch":"67246832486c93662c4e604ff2c7bd4f0d4e7ca420eda6892e4eef0a958fec50","patches/rules.mk.diff":"afdb592f46d323d0e04ec3c033fb272fe7f79c28c5ad544f462bdac4c01f661b","rules.mk":"ab5ae6f7e11b0b2845ee696180c6b68b4f32ba869d3fb16c6ccf650b9cfa8976","rust-toolchain.toml":"e29f2ad5748c6738b191d46ca6a4204014c51626eca5c106ab575225d8c915db","src/device/blk.rs":"cf2ec4daf867d9178ebbd3f284eb678819e99649c710332024f89be35b2930a6","src/device/common.rs":"090d853e88b893aebe01606fafcd9cab96d4601f3b7ce3cb6721975801616e0d","src/device/console.rs":"aa400f9035041f3c6bde186401466dec88785d6e0d03989cebe97224eedffb66","src/device/gpu.rs":"720ee5ccf00d4360897a14367964ccf7e3d0f75fb5f95fdb69ec1ec23212432a","src/device/input.rs":"44fb2d8866eadb5622b7495a05e96e1b5039a54249f175c3f258384a82d1c6c5","src/device/mod.rs":"84c2e13a08d50b7ba647b256a747acbe9cc5da5a15781f94084bf3e5e4be5a34","src/device/net/dev.rs":"9279bf99a856e20a58869403b82e463a05ab4c1e41591f06da6f9f1b9e6c6b99","src/device/net/dev_raw.rs":"9a9706113673beee0b1d63724abd160468997679044dc54152e415cdfabf1a32","src/device/net/mod.rs":"89ce8151b27a93c2bbe958fe5b95c070b4edcbcc7c6fb9a691b1d9dc353e2fbd","src/device/net/net_buf.rs":"367b6d53840b1128659234796848e2808482c2a36c995960b0c41b4e92bf949b","src/device/socket/connectionmanager.rs":"6273d60f4894f87ab86e6e1340fa4feaa5a56654bb248b1ec44d7dd8a2fe0a72","src/device/socket/error.rs":"de5b97fc2d3b38e5980ab242a52d0b778500d5869758d6a32756b4cef0b61f86","src/device/socket/mod.rs":"46ba478462b1c003acf42d449763f0a7457d853ee282f9b9349a6749caa19d2b","src/device/socket/protocol.rs":"4de57579bbfeb219e8068bc7416522f3dc85f9519ddc768bf05a7ae2800cdd17","src/device/socket/vsock.rs":"b169a876414ce10b5715306f7658e8fcecf577e9e59632ad52b21e15c32950eb","src/device/sound.rs":"cc40eff5af3562bff9662196af8018e84ef1cfce6a858a501dec4f4ab03b5a6f","src/device/sound/fake.rs":"bd7a01b7d08291c1ce6830f6720d44454d7021ebe8c36b83798faf5fe656fb6c","src/hal.rs":"f2d413f8924bfe50dbd2705f3febffd111a1ae17f160cdb5d9f12159ca3dc803","src/hal/fake.rs":"4c3ea7153f5627270041a42afe60256bdb52772923e6fdbc864f858c5346e368","src/lib.rs":"c9a768e1cf95a6027fd5da07c782681c2da6d1e5a96873e5ad7a878bf93097da","src/queue.rs":"8d9eabfd29203911883ea26ff8cbc5da6a20b181f2c05e82fe9ac2f7df9bdbd4","src/queue/owning.rs":"e293cfaa34dbe411e7a31079e013e05f11d5cf6a4100ae06979c0b7653fea3c0","src/transport/fake.rs":"4fa75709f281a37440fba484b3e3db27f8286fc2b576951bb9d0c8b0a25645f3","src/transport/mmio.rs":"294e0a32cfb1daeb8be904061652db3465413c14aaaca43155f55d7325c66756","src/transport/mod.rs":"f409a98810767e3623cc6a3b0e4aec9ec48f1102629d48056a81f722384067f2","src/transport/pci.rs":"25b887e554907bcd5038384498225ddbd52771ef614198fcc020f537cbdb268a","src/transport/pci/bus.rs":"7734960260150e0d928ae34be1aca37837cd2b74615aebfc757d18c7c15b825e","src/volatile.rs":"0b759bd807893075242115aa0f461fe5052e008b2022839e95a7602ce93cdd31"}}
\ No newline at end of file
diff --git a/crates/virtio-drivers/.cargo-checksum.json b/crates/virtio-drivers/.cargo-checksum.json
index 5513933..5879c40 100644
--- a/crates/virtio-drivers/.cargo-checksum.json
+++ b/crates/virtio-drivers/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"d3042da40785f988887a5372d639a30e2478cf42d423837374e2ed27abb36b41","LICENSE":"bf2a1b2f68528d6cb47939394b181236858d3e9c6c5e43b3af0650976567f152","README.md":"4ffd7b59f10a299ef4aec75a31a6c2631917ae45b1b44c11551d45896da05aca","rust-toolchain.toml":"3e18e70208ee460635e239a91c142cf67371feafb718b05617ff06f388bf96df","src/device/blk.rs":"ba2b090d15657285d4d97839fa906776c490b3935258c8749b705c3716e3f4f7","src/device/common.rs":"24277c9fba38a2c87fa6f12863e6e003d911cc2fedad2b6a053d869e26be0813","src/device/console.rs":"ef69c28c4759615ca47eddd844f6720113c3b504872c2de4ac9f0ac933618273","src/device/gpu.rs":"6feb344a18ec34bd4cc82a331befee6aefd56f07df63a5b19ffc3d34e69f7f9a","src/device/input.rs":"1d78997a21ec03f014f42ecd8ec6b5df43b482660c222212e85931da932af44d","src/device/mod.rs":"9e71cb1d3c43dca92f00969bc3af6a25fdf42efcdfa03418e9013360d9f47e6f","src/device/net/dev.rs":"1cd4d08e64fb112d2e24c9a6985d1914f9848ba61984c8fe6d9b4c1798554749","src/device/net/dev_raw.rs":"f737395c5e63f2440e54ac6f8382c6e77397dc42f4e23462d75f9db3a51e3ff7","src/device/net/mod.rs":"3c6a513f4b95c92e76b6475b56e8c922635d26f5c83bd404d6e5ea209cb60c40","src/device/net/net_buf.rs":"0e67f22587d9564fe0a0a52182333325be3db7d35ec33f1bb4190a6b89c058d5","src/device/socket/connectionmanager.rs":"9ad10edcef4e4e0ce1e57620057a3e478030421a6ca4dfe1a57627361f1f2658","src/device/socket/error.rs":"622c6d5b6ca15a2a574b6cb967a1afe999c9e7fc9a4bda83c78f90763690a2ec","src/device/socket/mod.rs":"891945d2e88002653a9588bcf0586d6c62792af8fc133c5d1d11262abffb47c1","src/device/socket/protocol.rs":"55857b9ee73513a3595dbca35ecde55bf081bc7d082b27a062b365b06cb8dda7","src/device/socket/vsock.rs":"105f24db7adb1b7460196f7049b0de4bb33cd59a8465e71b8801b98d32251152","src/hal.rs":"f6defc683d81b15cfbb825372f67702565c4424c9988f9b1abb7854c5b31a10c","src/hal/fake.rs":"f26fd9834a6d30bfea6ede5e94a832b574b6ab679578b77d662aea373532ee12","src/lib.rs":"6fa410dc70cd208895d1dc3b22c6546fd6698d11e219408682a537cd59cc7a27","src/queue.rs":"b9029dd19bac643b363ecc8eba3f96f3b3d600ee58682b66fe65acc278578d94","src/transport/fake.rs":"5a9c17f4213c67f1869c827f6263c5e5f94d733e5d1b66fded5c42b1d3ebd798","src/transport/mmio.rs":"d3b8973200da7d608dd0d0055a9a98c1154cc97de3530fbba7a783c63b9c95b3","src/transport/mod.rs":"63d58e00f3d5b7509d01405a6767b67079d93e80c888a2689241f53bab3e8a64","src/transport/pci.rs":"994b25ba4aa7286b6b73eaa3359a57de30a65ceff8f8b7c576afbe52a90d4301","src/transport/pci/bus.rs":"830f3e35a0bfdf83593d1045d2ca17b83c27e1ca86b7bbf2dda5faf497fda5ac","src/volatile.rs":"5240e2e9df29947cad86d68381326d36c034a5c36e552ec7d07f68a15ea0ef1f"},"package":"aa40e09453618c7a927c08c5a990497a2954da7c2aaa6c65e0d4f0fc975f6114"}
\ No newline at end of file
+{"files":{"Cargo.toml":"0dc0e15055e897e5b9b0fb92616c1f533db28d7f997ba978139fd7169143d3b6","LICENSE":"bf2a1b2f68528d6cb47939394b181236858d3e9c6c5e43b3af0650976567f152","README.md":"97d5ec352feb03542347b28fab9b77772163390c95b773d1dd4604dc8320d2c0","rust-toolchain.toml":"3e18e70208ee460635e239a91c142cf67371feafb718b05617ff06f388bf96df","src/device/blk.rs":"6fad0670d896aeb507a52280f8b147b9597348c246a4b59bd6b4134b028e72f1","src/device/common.rs":"24277c9fba38a2c87fa6f12863e6e003d911cc2fedad2b6a053d869e26be0813","src/device/console.rs":"ef69c28c4759615ca47eddd844f6720113c3b504872c2de4ac9f0ac933618273","src/device/gpu.rs":"6feb344a18ec34bd4cc82a331befee6aefd56f07df63a5b19ffc3d34e69f7f9a","src/device/input.rs":"1d78997a21ec03f014f42ecd8ec6b5df43b482660c222212e85931da932af44d","src/device/mod.rs":"225fd5d8c894df988d90e30df91477eeff60fefe9c8f5a789a0ae6c10ff1ea8d","src/device/net/dev.rs":"1cd4d08e64fb112d2e24c9a6985d1914f9848ba61984c8fe6d9b4c1798554749","src/device/net/dev_raw.rs":"dabd95dff8381d836a0e66ea0ec65b8324f9c7030fc372e81ffac5ac759e56c8","src/device/net/mod.rs":"3c6a513f4b95c92e76b6475b56e8c922635d26f5c83bd404d6e5ea209cb60c40","src/device/net/net_buf.rs":"0e67f22587d9564fe0a0a52182333325be3db7d35ec33f1bb4190a6b89c058d5","src/device/socket/connectionmanager.rs":"9ad10edcef4e4e0ce1e57620057a3e478030421a6ca4dfe1a57627361f1f2658","src/device/socket/error.rs":"622c6d5b6ca15a2a574b6cb967a1afe999c9e7fc9a4bda83c78f90763690a2ec","src/device/socket/mod.rs":"891945d2e88002653a9588bcf0586d6c62792af8fc133c5d1d11262abffb47c1","src/device/socket/protocol.rs":"55857b9ee73513a3595dbca35ecde55bf081bc7d082b27a062b365b06cb8dda7","src/device/socket/vsock.rs":"7fb8f14720cea7e3254c42de6b8e869b1af381974d9ba72b4f791383d8902b8f","src/device/sound.rs":"ece2e01e088a9f1358da9bfbebd21119109b81e3727dc5f1667096be0d23bce0","src/device/sound/fake.rs":"4d22e5445a253ff327581f711a1f0b500ddf6e4c46091385d9414c4ea7baba8e","src/hal.rs":"f6defc683d81b15cfbb825372f67702565c4424c9988f9b1abb7854c5b31a10c","src/hal/fake.rs":"f26fd9834a6d30bfea6ede5e94a832b574b6ab679578b77d662aea373532ee12","src/lib.rs":"6fa410dc70cd208895d1dc3b22c6546fd6698d11e219408682a537cd59cc7a27","src/queue.rs":"4b19ae32a6193be11347c079157bb03aaea4a99da769f1f6bb1c2b38c245f35e","src/queue/owning.rs":"f0a225338c21c4f818c982a4c836af3a9543f8cd6ad8d5191cca179836bdfa3c","src/transport/fake.rs":"f4c05883ffe04b6861a5fca73e3b49e7101d599a26e7173e2b0edcc376d687ac","src/transport/mmio.rs":"d3b8973200da7d608dd0d0055a9a98c1154cc97de3530fbba7a783c63b9c95b3","src/transport/mod.rs":"038bfc78bf00f8aa8fa19ca22ca74e8f2c5e2d278dc2adc26dbe80dbc13ce0bf","src/transport/pci.rs":"be73b0fbb40c589435e84ec608237446d17ab7387225fe9cf34c1b090f6aae05","src/transport/pci/bus.rs":"562e4a518ce525e3fce95cab8c60396e2afb0314f2af44a1a25aa9027b16e456","src/volatile.rs":"5240e2e9df29947cad86d68381326d36c034a5c36e552ec7d07f68a15ea0ef1f"},"package":"d6a39747311dabb3d37807037ed1c3c38d39f99198d091b5b79ecd5c8d82f799"}
\ No newline at end of file
diff --git a/crates/virtio-drivers/Android.bp b/crates/virtio-drivers/Android.bp
index d02e648..8530f1a 100644
--- a/crates/virtio-drivers/Android.bp
+++ b/crates/virtio-drivers/Android.bp
@@ -17,15 +17,16 @@
name: "libvirtio_drivers",
crate_name: "virtio_drivers",
cargo_env_compat: true,
- cargo_pkg_version: "0.7.4",
+ cargo_pkg_version: "0.7.5",
crate_root: "src/lib.rs",
- edition: "2018",
+ edition: "2021",
features: ["alloc"],
rustlibs: [
"libbitflags",
"liblog_rust_nostd",
"libzerocopy-0.7.35_nostd",
],
+ proc_macros: ["libenumn"],
apex_available: [
"//apex_available:platform",
"//apex_available:anyapex",
@@ -42,17 +43,18 @@
name: "virtio-drivers_test_src_lib",
crate_name: "virtio_drivers",
cargo_env_compat: true,
- cargo_pkg_version: "0.7.4",
+ cargo_pkg_version: "0.7.5",
crate_root: "src/lib.rs",
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ edition: "2021",
features: ["alloc"],
rustlibs: [
"libbitflags",
"liblog_rust_nostd",
"libzerocopy-0.7.35_nostd",
],
+ proc_macros: ["libenumn"],
}
dirgroup {
diff --git a/crates/virtio-drivers/Cargo.toml b/crates/virtio-drivers/Cargo.toml
index 33b96aa..f631e70 100644
--- a/crates/virtio-drivers/Cargo.toml
+++ b/crates/virtio-drivers/Cargo.toml
@@ -10,9 +10,9 @@
# See Cargo.toml.orig for the original contents.
[package]
-edition = "2018"
+edition = "2021"
name = "virtio-drivers"
-version = "0.7.4"
+version = "0.7.5"
authors = [
"Jiajie Chen <[email protected]>",
"Runji Wang <[email protected]>",
@@ -30,17 +30,20 @@
repository = "https://github.com/rcore-os/virtio-drivers"
[dependencies.bitflags]
-version = "2.3.0"
+version = "2.6.0"
+
+[dependencies.enumn]
+version = "0.1.14"
[dependencies.log]
-version = "0.4"
+version = "0.4.22"
[dependencies.zerocopy]
-version = "0.7.5"
+version = "0.7.35"
features = ["derive"]
[dev-dependencies.zerocopy]
-version = "0.7.5"
+version = "0.7.35"
features = ["alloc"]
[features]
diff --git a/crates/virtio-drivers/METADATA b/crates/virtio-drivers/METADATA
index 3c392a3..b43ce1e 100644
--- a/crates/virtio-drivers/METADATA
+++ b/crates/virtio-drivers/METADATA
@@ -1,17 +1,17 @@
name: "virtio-drivers"
description: "VirtIO guest drivers."
third_party {
- version: "0.7.4"
+ version: "0.7.5"
license_type: NOTICE
last_upgrade_date {
- year: 2024
- month: 7
- day: 15
+ year: 2025
+ month: 1
+ day: 16
}
homepage: "https://crates.io/crates/virtio-drivers"
identifier {
type: "Archive"
- value: "https://static.crates.io/crates/virtio-drivers/virtio-drivers-0.7.4.crate"
- version: "0.7.4"
+ value: "https://static.crates.io/crates/virtio-drivers/virtio-drivers-0.7.5.crate"
+ version: "0.7.5"
}
}
diff --git a/crates/virtio-drivers/README.md b/crates/virtio-drivers/README.md
index fc87378..b1f3797 100644
--- a/crates/virtio-drivers/README.md
+++ b/crates/virtio-drivers/README.md
@@ -18,6 +18,7 @@
| Input | ✅ |
| Console | ✅ |
| Socket | ✅ |
+| Sound | ✅ |
| ... | ❌ |
### Transports
diff --git a/crates/virtio-drivers/rules.mk b/crates/virtio-drivers/rules.mk
index 3ccd69f..efcb0f4 100644
--- a/crates/virtio-drivers/rules.mk
+++ b/crates/virtio-drivers/rules.mk
@@ -9,13 +9,14 @@
MODULE_RUST_CRATE_TYPES := rlib
MODULE_SRCS := $(LOCAL_DIR)/src/lib.rs
MODULE_ADD_IMPLICIT_DEPS := false
-MODULE_RUST_EDITION := 2018
+MODULE_RUST_EDITION := 2021
MODULE_RUSTFLAGS += \
--cfg 'feature="alloc"'
MODULE_LIBRARY_DEPS := \
trusty/user/base/lib/liballoc-rust \
$(call FIND_CRATE,bitflags) \
+ $(call FIND_CRATE,enumn) \
$(call FIND_CRATE,log) \
$(call FIND_CRATE,zerocopy) \
trusty/user/base/lib/libcompiler_builtins-rust \
diff --git a/crates/virtio-drivers/src/device/blk.rs b/crates/virtio-drivers/src/device/blk.rs
index cbd2dcc..bda6edc 100644
--- a/crates/virtio-drivers/src/device/blk.rs
+++ b/crates/virtio-drivers/src/device/blk.rs
@@ -632,7 +632,7 @@
State::wait_until_queue_notified(&state, QUEUE);
println!("Transmit queue was notified.");
- state
+ assert!(state
.lock()
.unwrap()
.read_write_queue::<{ QUEUE_SIZE as usize }>(QUEUE, |request| {
@@ -656,7 +656,7 @@
);
response
- });
+ }));
});
// Read a block from the device.
@@ -702,7 +702,7 @@
State::wait_until_queue_notified(&state, QUEUE);
println!("Transmit queue was notified.");
- state
+ assert!(state
.lock()
.unwrap()
.read_write_queue::<{ QUEUE_SIZE as usize }>(QUEUE, |request| {
@@ -728,7 +728,7 @@
);
response
- });
+ }));
});
// Write a block to the device.
@@ -777,7 +777,7 @@
State::wait_until_queue_notified(&state, QUEUE);
println!("Transmit queue was notified.");
- state
+ assert!(state
.lock()
.unwrap()
.read_write_queue::<{ QUEUE_SIZE as usize }>(QUEUE, |request| {
@@ -800,7 +800,7 @@
);
response
- });
+ }));
});
// Request to flush.
@@ -844,7 +844,7 @@
State::wait_until_queue_notified(&state, QUEUE);
println!("Transmit queue was notified.");
- state
+ assert!(state
.lock()
.unwrap()
.read_write_queue::<{ QUEUE_SIZE as usize }>(QUEUE, |request| {
@@ -868,7 +868,7 @@
);
response
- });
+ }));
});
let mut id = [0; 20];
diff --git a/crates/virtio-drivers/src/device/mod.rs b/crates/virtio-drivers/src/device/mod.rs
index d8e6389..5a4e9f2 100644
--- a/crates/virtio-drivers/src/device/mod.rs
+++ b/crates/virtio-drivers/src/device/mod.rs
@@ -11,5 +11,7 @@
pub mod net;
pub mod socket;
+#[cfg(feature = "alloc")]
+pub mod sound;
pub(crate) mod common;
diff --git a/crates/virtio-drivers/src/device/net/dev_raw.rs b/crates/virtio-drivers/src/device/net/dev_raw.rs
index e9834e0..70b3a17 100644
--- a/crates/virtio-drivers/src/device/net/dev_raw.rs
+++ b/crates/virtio-drivers/src/device/net/dev_raw.rs
@@ -8,11 +8,11 @@
use log::{debug, info, warn};
use zerocopy::AsBytes;
-/// Raw driver for a VirtIO block device.
+/// Raw driver for a VirtIO network device.
///
/// This is a raw version of the VirtIONet driver. It provides non-blocking
/// methods for transmitting and receiving raw slices, without the buffer
-/// management. For more higher-level fucntions such as receive buffer backing,
+/// management. For more higher-level functions such as receive buffer backing,
/// see [`VirtIONet`].
///
/// [`VirtIONet`]: super::VirtIONet
diff --git a/crates/virtio-drivers/src/device/socket/vsock.rs b/crates/virtio-drivers/src/device/socket/vsock.rs
index 6c5a3f2..4a9e33b 100644
--- a/crates/virtio-drivers/src/device/socket/vsock.rs
+++ b/crates/virtio-drivers/src/device/socket/vsock.rs
@@ -7,15 +7,13 @@
};
use super::DEFAULT_RX_BUFFER_SIZE;
use crate::hal::Hal;
-use crate::queue::VirtQueue;
+use crate::queue::{owning::OwningQueue, VirtQueue};
use crate::transport::Transport;
use crate::volatile::volread;
-use crate::{Error, Result};
-use alloc::boxed::Box;
+use crate::Result;
use core::mem::size_of;
-use core::ptr::{null_mut, NonNull};
use log::debug;
-use zerocopy::{AsBytes, FromBytes, FromZeroes};
+use zerocopy::{AsBytes, FromBytes};
pub(crate) const RX_QUEUE_IDX: u16 = 0;
pub(crate) const TX_QUEUE_IDX: u16 = 1;
@@ -222,30 +220,13 @@
{
transport: T,
/// Virtqueue to receive packets.
- rx: VirtQueue<H, { QUEUE_SIZE }>,
+ rx: OwningQueue<H, QUEUE_SIZE, RX_BUFFER_SIZE>,
tx: VirtQueue<H, { QUEUE_SIZE }>,
/// Virtqueue to receive events from the device.
event: VirtQueue<H, { QUEUE_SIZE }>,
/// The guest_cid field contains the guest’s context ID, which uniquely identifies
/// the device for its lifetime. The upper 32 bits of the CID are reserved and zeroed.
guest_cid: u64,
- rx_queue_buffers: [NonNull<[u8; RX_BUFFER_SIZE]>; QUEUE_SIZE],
-}
-
-// SAFETY: The `rx_queue_buffers` can be accessed from any thread.
-unsafe impl<H: Hal, T: Transport + Send, const RX_BUFFER_SIZE: usize> Send
- for VirtIOSocket<H, T, RX_BUFFER_SIZE>
-where
- VirtQueue<H, QUEUE_SIZE>: Send,
-{
-}
-
-// SAFETY: A `&VirtIOSocket` only allows reading the guest CID from a field.
-unsafe impl<H: Hal, T: Transport + Sync, const RX_BUFFER_SIZE: usize> Sync
- for VirtIOSocket<H, T, RX_BUFFER_SIZE>
-where
- VirtQueue<H, QUEUE_SIZE>: Sync,
-{
}
impl<H: Hal, T: Transport, const RX_BUFFER_SIZE: usize> Drop
@@ -257,12 +238,6 @@
self.transport.queue_unset(RX_QUEUE_IDX);
self.transport.queue_unset(TX_QUEUE_IDX);
self.transport.queue_unset(EVENT_QUEUE_IDX);
-
- for buffer in self.rx_queue_buffers {
- // Safe because we obtained the RX buffer pointer from Box::into_raw, and it won't be
- // used anywhere else after the driver is destroyed.
- unsafe { drop(Box::from_raw(buffer.as_ptr())) };
- }
}
}
@@ -281,7 +256,7 @@
};
debug!("guest cid: {guest_cid:?}");
- let mut rx = VirtQueue::new(
+ let rx = VirtQueue::new(
&mut transport,
RX_QUEUE_IDX,
negotiated_features.contains(Feature::RING_INDIRECT_DESC),
@@ -300,17 +275,7 @@
negotiated_features.contains(Feature::RING_EVENT_IDX),
)?;
- // Allocate and add buffers for the RX queue.
- let mut rx_queue_buffers = [null_mut(); QUEUE_SIZE];
- for (i, rx_queue_buffer) in rx_queue_buffers.iter_mut().enumerate() {
- let mut buffer: Box<[u8; RX_BUFFER_SIZE]> = FromZeroes::new_box_zeroed();
- // Safe because the buffer lives as long as the queue, as specified in the function
- // safety requirement, and we don't access it until it is popped.
- let token = unsafe { rx.add(&[], &mut [buffer.as_mut_slice()]) }?;
- assert_eq!(i, token.into());
- *rx_queue_buffer = Box::into_raw(buffer);
- }
- let rx_queue_buffers = rx_queue_buffers.map(|ptr| NonNull::new(ptr).unwrap());
+ let rx = OwningQueue::new(rx)?;
transport.finish_init();
if rx.should_notify() {
@@ -323,7 +288,6 @@
tx,
event,
guest_cid,
- rx_queue_buffers,
})
}
@@ -412,18 +376,10 @@
&mut self,
handler: impl FnOnce(VsockEvent, &[u8]) -> Result<Option<VsockEvent>>,
) -> Result<Option<VsockEvent>> {
- let Some((header, body, token)) = self.pop_packet_from_rx_queue()? else {
- return Ok(None);
- };
-
- let result = VsockEvent::from_header(&header).and_then(|event| handler(event, body));
-
- unsafe {
- // TODO: What about if both handler and this give errors?
- self.add_buffer_to_rx_queue(token)?;
- }
-
- result
+ self.rx.poll(&mut self.transport, |buffer| {
+ let (header, body) = read_header_and_body(buffer)?;
+ VsockEvent::from_header(&header).and_then(|event| handler(event, body))
+ })
}
/// Requests to shut down the connection cleanly, sending hints about whether we will send or
@@ -481,78 +437,19 @@
};
Ok(())
}
-
- /// Adds the buffer at the given index in `rx_queue_buffers` back to the RX queue.
- ///
- /// # Safety
- ///
- /// The buffer must not currently be in the RX queue, and no other references to it must exist
- /// between when this method is called and when it is popped from the queue.
- unsafe fn add_buffer_to_rx_queue(&mut self, index: u16) -> Result {
- // Safe because the buffer lives as long as the queue, and the caller guarantees that it's
- // not currently in the queue or referred to anywhere else until it is popped.
- unsafe {
- let buffer = self
- .rx_queue_buffers
- .get_mut(usize::from(index))
- .ok_or(Error::WrongToken)?
- .as_mut();
- let new_token = self.rx.add(&[], &mut [buffer])?;
- // If the RX buffer somehow gets assigned a different token, then our safety assumptions
- // are broken and we can't safely continue to do anything with the device.
- assert_eq!(new_token, index);
- }
-
- if self.rx.should_notify() {
- self.transport.notify(RX_QUEUE_IDX);
- }
-
- Ok(())
- }
-
- /// Pops one packet from the RX queue, if there is one pending. Returns the header, and a
- /// reference to the buffer containing the body.
- ///
- /// Returns `None` if there is no pending packet.
- fn pop_packet_from_rx_queue(&mut self) -> Result<Option<(VirtioVsockHdr, &[u8], u16)>> {
- let Some(token) = self.rx.peek_used() else {
- return Ok(None);
- };
-
- // Safe because we maintain a consistent mapping of tokens to buffers, so we pass the same
- // buffer to `pop_used` as we previously passed to `add` for the token. Once we add the
- // buffer back to the RX queue then we don't access it again until next time it is popped.
- let (header, body) = unsafe {
- let buffer = self.rx_queue_buffers[usize::from(token)].as_mut();
- let _len = self.rx.pop_used(token, &[], &mut [buffer])?;
-
- // Read the header and body from the buffer. Don't check the result yet, because we need
- // to add the buffer back to the queue either way.
- let header_result = read_header_and_body(buffer);
- if header_result.is_err() {
- // If there was an error, add the buffer back immediately. Ignore any errors, as we
- // need to return the first error.
- let _ = self.add_buffer_to_rx_queue(token);
- }
-
- header_result
- }?;
-
- debug!("Received packet {:?}. Op {:?}", header, header.op());
- Ok(Some((header, body, token)))
- }
}
fn read_header_and_body(buffer: &[u8]) -> Result<(VirtioVsockHdr, &[u8])> {
- // Shouldn't panic, because we know `RX_BUFFER_SIZE > size_of::<VirtioVsockHdr>()`.
- let header = VirtioVsockHdr::read_from_prefix(buffer).unwrap();
+ // This could fail if the device returns a buffer used length shorter than the header size.
+ let header = VirtioVsockHdr::read_from_prefix(buffer).ok_or(SocketError::BufferTooShort)?;
let body_length = header.len() as usize;
// This could fail if the device returns an unreasonably long body length.
let data_end = size_of::<VirtioVsockHdr>()
.checked_add(body_length)
.ok_or(SocketError::InvalidNumber)?;
- // This could fail if the device returns a body length longer than the buffer we gave it.
+ // This could fail if the device returns a body length longer than buffer used length it
+ // returned.
let data = buffer
.get(size_of::<VirtioVsockHdr>()..data_end)
.ok_or(SocketError::BufferTooShort)?;
diff --git a/crates/virtio-drivers/src/device/sound.rs b/crates/virtio-drivers/src/device/sound.rs
new file mode 100644
index 0000000..571a55a
--- /dev/null
+++ b/crates/virtio-drivers/src/device/sound.rs
@@ -0,0 +1,1780 @@
+//! Driver for VirtIO Sound devices.
+
+#[cfg(test)]
+mod fake;
+
+use super::common::Feature;
+use crate::{
+ queue::{owning::OwningQueue, VirtQueue},
+ transport::Transport,
+ volatile::{volread, ReadOnly},
+ Error, Hal, Result, PAGE_SIZE,
+};
+use alloc::{boxed::Box, collections::BTreeMap, vec, vec::Vec};
+use bitflags::bitflags;
+use core::{
+ array,
+ fmt::{self, Debug, Display, Formatter},
+ hint::spin_loop,
+ mem::size_of,
+ ops::RangeInclusive,
+};
+use enumn::N;
+use log::{error, info, warn};
+use zerocopy::{AsBytes, FromBytes, FromZeroes};
+
+/// Audio driver based on virtio v1.2.
+///
+/// Supports synchronous blocking and asynchronous non-blocking audio playback.
+///
+/// Currently, only audio playback functionality has been implemented.
+pub struct VirtIOSound<H: Hal, T: Transport> {
+ transport: T,
+
+ control_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
+ event_queue: OwningQueue<H, { QUEUE_SIZE as usize }, { size_of::<VirtIOSndEvent>() }>,
+ tx_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
+ rx_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
+
+ negotiated_features: Feature,
+
+ jacks: u32,
+ streams: u32,
+ chmaps: u32,
+
+ pcm_infos: Option<Vec<VirtIOSndPcmInfo>>,
+ jack_infos: Option<Vec<VirtIOSndJackInfo>>,
+ chmap_infos: Option<Vec<VirtIOSndChmapInfo>>,
+
+ // pcm params
+ pcm_parameters: Vec<PcmParameters>,
+
+ queue_buf_send: Box<[u8]>,
+ queue_buf_recv: Box<[u8]>,
+
+ set_up: bool,
+
+ token_rsp: BTreeMap<u16, Box<VirtIOSndPcmStatus>>, // includes pcm_xfer response msg
+
+ pcm_states: Vec<PCMState>,
+
+ token_buf: BTreeMap<u16, Vec<u8>>, // store token and its input buf
+}
+
+impl<H: Hal, T: Transport> VirtIOSound<H, T> {
+ /// Create a new VirtIO-Sound driver.
+ pub fn new(mut transport: T) -> Result<Self> {
+ let negotiated_features = transport.begin_init(SUPPORTED_FEATURES);
+ info!(
+ "[sound device] negotiated_features: {:?}",
+ negotiated_features
+ );
+
+ let control_queue = VirtQueue::new(
+ &mut transport,
+ CONTROL_QUEUE_IDX,
+ negotiated_features.contains(Feature::RING_INDIRECT_DESC),
+ negotiated_features.contains(Feature::RING_EVENT_IDX),
+ )?;
+ let event_queue = OwningQueue::new(VirtQueue::new(
+ &mut transport,
+ EVENT_QUEUE_IDX,
+ negotiated_features.contains(Feature::RING_INDIRECT_DESC),
+ negotiated_features.contains(Feature::RING_EVENT_IDX),
+ )?)?;
+ let tx_queue = VirtQueue::new(
+ &mut transport,
+ TX_QUEUE_IDX,
+ negotiated_features.contains(Feature::RING_INDIRECT_DESC),
+ negotiated_features.contains(Feature::RING_EVENT_IDX),
+ )?;
+ let rx_queue = VirtQueue::new(
+ &mut transport,
+ RX_QUEUE_IDX,
+ negotiated_features.contains(Feature::RING_INDIRECT_DESC),
+ negotiated_features.contains(Feature::RING_EVENT_IDX),
+ )?;
+
+ // read configuration space
+ let config_ptr = transport.config_space::<VirtIOSoundConfig>()?;
+ // SAFETY: config_ptr is a valid pointer to the device configuration space.
+ let (jacks, streams, chmaps) = unsafe {
+ (
+ volread!(config_ptr, jacks),
+ volread!(config_ptr, streams),
+ volread!(config_ptr, chmaps),
+ )
+ };
+ info!(
+ "[sound device] config: jacks: {}, streams: {}, chmaps: {}",
+ jacks, streams, chmaps
+ );
+
+ let queue_buf_send = FromZeroes::new_box_slice_zeroed(PAGE_SIZE);
+ let queue_buf_recv = FromZeroes::new_box_slice_zeroed(PAGE_SIZE);
+
+ // set pcm params to default
+ let mut pcm_parameters = vec![];
+ for _ in 0..streams {
+ pcm_parameters.push(PcmParameters::default());
+ }
+
+ transport.finish_init();
+
+ if event_queue.should_notify() {
+ transport.notify(EVENT_QUEUE_IDX);
+ }
+
+ Ok(VirtIOSound {
+ transport,
+ control_queue,
+ event_queue,
+ tx_queue,
+ rx_queue,
+ negotiated_features,
+ jacks,
+ streams,
+ chmaps,
+ pcm_infos: None,
+ jack_infos: None,
+ chmap_infos: None,
+ queue_buf_send,
+ queue_buf_recv,
+ pcm_parameters,
+ set_up: false,
+ token_rsp: BTreeMap::new(),
+ pcm_states: vec![],
+ token_buf: BTreeMap::new(),
+ })
+ }
+
+ /// Total jack num.
+ pub fn jacks(&self) -> u32 {
+ self.jacks
+ }
+
+ /// Total stream num.
+ pub fn streams(&self) -> u32 {
+ self.streams
+ }
+
+ /// Total chmap num.
+ pub fn chmaps(&self) -> u32 {
+ self.chmaps
+ }
+
+ /// Acknowledge interrupt.
+ pub fn ack_interrupt(&mut self) -> bool {
+ self.transport.ack_interrupt()
+ }
+
+ fn request<Req: AsBytes>(&mut self, req: Req) -> Result<VirtIOSndHdr> {
+ self.control_queue.add_notify_wait_pop(
+ &[req.as_bytes()],
+ &mut [self.queue_buf_recv.as_bytes_mut()],
+ &mut self.transport,
+ )?;
+ Ok(VirtIOSndHdr::read_from_prefix(&self.queue_buf_recv).unwrap())
+ }
+
+ /// Set up the driver, initate pcm_infos and jacks_infos
+ fn set_up(&mut self) -> Result<()> {
+ // init jack info
+ if let Ok(jack_infos) = self.jack_info(0, self.jacks) {
+ for jack_info in &jack_infos {
+ info!("[sound device] jack_info: {}", jack_info);
+ }
+ self.jack_infos = Some(jack_infos);
+ } else {
+ self.jack_infos = Some(vec![]);
+ warn!("[sound device] Error getting jack infos");
+ }
+
+ // init pcm info
+ let pcm_infos = self.pcm_info(0, self.streams)?;
+ for pcm_info in &pcm_infos {
+ info!("[sound device] pcm_info: {}", pcm_info);
+ }
+ self.pcm_infos = Some(pcm_infos);
+
+ // init chmap info
+ if let Ok(chmap_infos) = self.chmap_info(0, self.chmaps) {
+ for chmap_info in &chmap_infos {
+ info!("[sound device] chmap_info: {}", chmap_info);
+ }
+ self.chmap_infos = Some(chmap_infos);
+ } else {
+ self.chmap_infos = Some(vec![]);
+ warn!("[sound device] Error getting chmap infos");
+ }
+
+ // set pcm state to default
+ for _ in 0..self.streams {
+ self.pcm_states.push(PCMState::default());
+ }
+ Ok(())
+ }
+
+ /// Enables interrupts from the device.
+ pub fn enable_interrupts(&mut self, enable: bool) {
+ self.event_queue.set_dev_notify(enable);
+ }
+
+ /// Query information about the available jacks.
+ fn jack_info(&mut self, jack_start_id: u32, jack_count: u32) -> Result<Vec<VirtIOSndJackInfo>> {
+ if jack_start_id + jack_count > self.jacks {
+ error!("jack_start_id + jack_count > jacks! There are not enough jacks to be queried!");
+ return Err(Error::IoError);
+ }
+ let hdr = self.request(VirtIOSndQueryInfo {
+ hdr: ItemInformationRequestType::RJackInfo.into(),
+ start_id: jack_start_id,
+ count: jack_count,
+ size: size_of::<VirtIOSndJackInfo>() as u32,
+ })?;
+ if hdr != RequestStatusCode::Ok.into() {
+ return Err(Error::IoError);
+ }
+ // read struct VirtIOSndJackInfo
+ let mut jack_infos = vec![];
+ for i in 0..jack_count as usize {
+ const HDR_SIZE: usize = size_of::<VirtIOSndHdr>();
+ const JACK_INFO_SIZE: usize = size_of::<VirtIOSndJackInfo>();
+ let start_byte_idx = HDR_SIZE + i * JACK_INFO_SIZE;
+ let end_byte_idx = HDR_SIZE + (i + 1) * JACK_INFO_SIZE;
+ let jack_info =
+ VirtIOSndJackInfo::read_from(&self.queue_buf_recv[start_byte_idx..end_byte_idx])
+ .unwrap();
+ jack_infos.push(jack_info)
+ }
+ Ok(jack_infos)
+ }
+
+ /// Query information about the available streams.
+ fn pcm_info(
+ &mut self,
+ stream_start_id: u32,
+ stream_count: u32,
+ ) -> Result<Vec<VirtIOSndPcmInfo>> {
+ if stream_start_id + stream_count > self.streams {
+ error!("stream_start_id + stream_count > streams! There are not enough streams to be queried!");
+ return Err(Error::IoError);
+ }
+ let request_hdr = VirtIOSndHdr::from(ItemInformationRequestType::RPcmInfo);
+ let hdr = self.request(VirtIOSndQueryInfo {
+ hdr: request_hdr,
+ start_id: stream_start_id,
+ count: stream_count,
+ size: size_of::<VirtIOSndPcmInfo>() as u32,
+ })?;
+ if hdr != RequestStatusCode::Ok.into() {
+ return Err(Error::IoError);
+ }
+ // read struct VirtIOSndPcmInfo
+ let mut pcm_infos = vec![];
+ for i in 0..stream_count as usize {
+ const HDR_SIZE: usize = size_of::<VirtIOSndHdr>();
+ const PCM_INFO_SIZE: usize = size_of::<VirtIOSndPcmInfo>();
+ let start_byte_idx = HDR_SIZE + i * PCM_INFO_SIZE;
+ let end_byte_idx = HDR_SIZE + (i + 1) * PCM_INFO_SIZE;
+ let pcm_info =
+ VirtIOSndPcmInfo::read_from(&self.queue_buf_recv[start_byte_idx..end_byte_idx])
+ .unwrap();
+ pcm_infos.push(pcm_info);
+ }
+ Ok(pcm_infos)
+ }
+
+ /// Query information about the available chmaps.
+ fn chmap_info(
+ &mut self,
+ chmaps_start_id: u32,
+ chmaps_count: u32,
+ ) -> Result<Vec<VirtIOSndChmapInfo>> {
+ if chmaps_start_id + chmaps_count > self.chmaps {
+ error!("chmaps_start_id + chmaps_count > self.chmaps");
+ return Err(Error::IoError);
+ }
+ let hdr = self.request(VirtIOSndQueryInfo {
+ hdr: ItemInformationRequestType::RChmapInfo.into(),
+ start_id: chmaps_start_id,
+ count: chmaps_count,
+ size: size_of::<VirtIOSndChmapInfo>() as u32,
+ })?;
+ if hdr != RequestStatusCode::Ok.into() {
+ return Err(Error::IoError);
+ }
+ let mut chmap_infos = vec![];
+ for i in 0..chmaps_count as usize {
+ const OFFSET: usize = size_of::<VirtIOSndHdr>();
+ let start_byte = OFFSET + i * size_of::<VirtIOSndChmapInfo>();
+ let end_byte = OFFSET + (i + 1) * size_of::<VirtIOSndChmapInfo>();
+ let chmap_info =
+ VirtIOSndChmapInfo::read_from(&self.queue_buf_recv[start_byte..end_byte]).unwrap();
+ chmap_infos.push(chmap_info);
+ }
+ Ok(chmap_infos)
+ }
+
+ /// If the VIRTIO_SND_JACK_F_REMAP feature bit is set in the jack information, then the driver can send a
+ /// control request to change the association and/or sequence number for the specified jack ID.
+ /// # Arguments
+ ///
+ /// * `jack_id` - A u32 int which is in the range of [0, jacks)
+ pub fn jack_remap(&mut self, jack_id: u32, association: u32, sequence: u32) -> Result {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ if self.jacks == 0 {
+ error!("[sound device] There is no available jacks!");
+ return Err(Error::InvalidParam);
+ }
+ if jack_id >= self.jacks {
+ error!("jack_id >= self.jacks! Make sure jack_id is in the range of [0, jacks - 1)!");
+ return Err(Error::InvalidParam);
+ }
+
+ let jack_features = JackFeatures::from_bits_retain(
+ self.jack_infos
+ .as_ref()
+ .unwrap()
+ .get(jack_id as usize)
+ .unwrap()
+ .features,
+ );
+ if !jack_features.contains(JackFeatures::REMAP) {
+ error!("The jack selected does not support VIRTIO_SND_JACK_F_REMAP!");
+ return Err(Error::Unsupported);
+ }
+ let hdr = self.request(VirtIOSndJackRemap {
+ hdr: VirtIOSndJackHdr {
+ hdr: CommandCode::RJackRemap.into(),
+ jack_id,
+ },
+ association,
+ sequence,
+ })?;
+ if hdr == RequestStatusCode::Ok.into() {
+ Ok(())
+ } else {
+ Err(Error::Unsupported)
+ }
+ }
+
+ /// Set selected stream parameters for the specified stream ID.
+ pub fn pcm_set_params(
+ &mut self,
+ stream_id: u32,
+ buffer_bytes: u32,
+ period_bytes: u32,
+ features: PcmFeatures,
+ channels: u8,
+ format: PcmFormat,
+ rate: PcmRate,
+ ) -> Result {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ if period_bytes == 0 || period_bytes > buffer_bytes || buffer_bytes % period_bytes != 0 {
+ return Err(Error::InvalidParam);
+ }
+ let request_hdr = VirtIOSndHdr::from(CommandCode::RPcmSetParams);
+ let rsp = self.request(VirtIOSndPcmSetParams {
+ hdr: VirtIOSndPcmHdr {
+ hdr: request_hdr,
+ stream_id,
+ },
+ buffer_bytes,
+ period_bytes,
+ features: features.bits(),
+ channels,
+ format: format.into(),
+ rate: rate.into(),
+ _padding: 0,
+ })?;
+ // rsp is just a header, so it can be compared with VirtIOSndHdr
+ if rsp == VirtIOSndHdr::from(RequestStatusCode::Ok) {
+ self.pcm_parameters[stream_id as usize] = PcmParameters {
+ setup: true,
+ buffer_bytes,
+ period_bytes,
+ features,
+ channels,
+ format,
+ rate,
+ };
+ Ok(())
+ } else {
+ Err(Error::IoError)
+ }
+ }
+
+ /// Prepare a stream with specified stream ID.
+ pub fn pcm_prepare(&mut self, stream_id: u32) -> Result {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ let request_hdr = VirtIOSndHdr::from(CommandCode::RPcmPrepare);
+ let rsp = self.request(VirtIOSndPcmHdr {
+ hdr: request_hdr,
+ stream_id,
+ })?;
+ // rsp is just a header, so it can be compared with VirtIOSndHdr
+ if rsp == VirtIOSndHdr::from(RequestStatusCode::Ok) {
+ Ok(())
+ } else {
+ Err(Error::IoError)
+ }
+ }
+
+ /// Release a stream with specified stream ID.
+ pub fn pcm_release(&mut self, stream_id: u32) -> Result {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ let request_hdr = VirtIOSndHdr::from(CommandCode::RPcmRelease);
+ let rsp = self.request(VirtIOSndPcmHdr {
+ hdr: request_hdr,
+ stream_id,
+ })?;
+ // rsp is just a header, so it can be compared with VirtIOSndHdr
+ if rsp == VirtIOSndHdr::from(RequestStatusCode::Ok) {
+ Ok(())
+ } else {
+ Err(Error::IoError)
+ }
+ }
+
+ /// Start a stream with specified stream ID.
+ pub fn pcm_start(&mut self, stream_id: u32) -> Result {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ let request_hdr = VirtIOSndHdr::from(CommandCode::RPcmStart);
+ let rsp = self.request(VirtIOSndPcmHdr {
+ hdr: request_hdr,
+ stream_id,
+ })?;
+ // rsp is just a header, so it can be compared with VirtIOSndHdr
+ if rsp == VirtIOSndHdr::from(RequestStatusCode::Ok) {
+ Ok(())
+ } else {
+ Err(Error::IoError)
+ }
+ }
+
+ /// Stop a stream with specified stream ID.
+ pub fn pcm_stop(&mut self, stream_id: u32) -> Result {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ let request_hdr = VirtIOSndHdr::from(CommandCode::RPcmStop);
+ let rsp = self.request(VirtIOSndPcmHdr {
+ hdr: request_hdr,
+ stream_id,
+ })?;
+ // rsp is just a header, so it can be compared with VirtIOSndHdr
+ if rsp == VirtIOSndHdr::from(RequestStatusCode::Ok) {
+ Ok(())
+ } else {
+ Err(Error::IoError)
+ }
+ }
+
+ /// Transfer PCM frame to device, based on the stream type(OUTPUT/INPUT).
+ ///
+ /// Currently supports only output stream.
+ ///
+ /// This is a blocking method that will not return until the audio playback is complete.
+ pub fn pcm_xfer(&mut self, stream_id: u32, frames: &[u8]) -> Result {
+ const U32_SIZE: usize = size_of::<u32>();
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ if !self.pcm_parameters[stream_id as usize].setup {
+ warn!("Please set parameters for a stream before using it!");
+ return Err(Error::IoError);
+ }
+
+ let stream_id_bytes = stream_id.to_le_bytes();
+ let period_size = self.pcm_parameters[stream_id as usize].period_bytes as usize;
+
+ let mut remaining_buffers = frames.chunks(period_size);
+ let mut buffers: [Option<&[u8]>; QUEUE_SIZE as usize] = [None; QUEUE_SIZE as usize];
+ let mut statuses: [VirtIOSndPcmStatus; QUEUE_SIZE as usize] =
+ array::from_fn(|_| Default::default());
+ let mut tokens = [0; QUEUE_SIZE as usize];
+ // The next element of `statuses` and `tokens` to use for adding to the queue.
+ let mut head = 0;
+ // The next element of `status` and `tokens` to use for popping the queue.
+ let mut tail = 0;
+
+ loop {
+ // Add as buffers to the TX queue if possible. 3 descriptors are required for the 2
+ // input buffers and 1 output buffer.
+ if self.tx_queue.available_desc() >= 3 {
+ if let Some(buffer) = remaining_buffers.next() {
+ tokens[head] = unsafe {
+ self.tx_queue.add(
+ &[&stream_id_bytes, buffer],
+ &mut [statuses[head].as_bytes_mut()],
+ )?
+ };
+ if self.tx_queue.should_notify() {
+ self.transport.notify(TX_QUEUE_IDX);
+ }
+ buffers[head] = Some(buffer);
+ head += 1;
+ if head >= usize::from(QUEUE_SIZE) {
+ head = 0;
+ }
+ } else if head == tail {
+ break;
+ }
+ }
+ if self.tx_queue.can_pop() {
+ unsafe {
+ self.tx_queue.pop_used(
+ tokens[tail],
+ &[&stream_id_bytes, buffers[tail].unwrap()],
+ &mut [statuses[tail].as_bytes_mut()],
+ )?;
+ }
+ if statuses[tail].status != CommandCode::SOk.into() {
+ return Err(Error::IoError);
+ }
+ tail += 1;
+ if tail >= usize::from(QUEUE_SIZE) {
+ tail = 0;
+ }
+ }
+ spin_loop();
+ }
+
+ Ok(())
+ }
+
+ /// Transfer PCM frame to device, based on the stream type(OUTPUT/INPUT).
+ ///
+ /// Currently supports only output stream.
+ ///
+ /// This is a non-blocking method that returns a token.
+ ///
+ /// The length of the `frames` must be equal to the buffer size set for the stream corresponding to the `stream_id`.
+ pub fn pcm_xfer_nb(&mut self, stream_id: u32, frames: &[u8]) -> Result<u16> {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ if !self.pcm_parameters[stream_id as usize].setup {
+ warn!("Please set parameters for a stream before using it!");
+ return Err(Error::IoError);
+ }
+ const U32_SIZE: usize = size_of::<u32>();
+ let period_size: usize = self.pcm_parameters[stream_id as usize].period_bytes as usize;
+ assert_eq!(period_size, frames.len());
+ let mut buf = vec![0; U32_SIZE + period_size];
+ buf[..U32_SIZE].copy_from_slice(&stream_id.to_le_bytes());
+ buf[U32_SIZE..U32_SIZE + period_size].copy_from_slice(frames);
+ let mut rsp = VirtIOSndPcmStatus::new_box_zeroed();
+ let token = unsafe { self.tx_queue.add(&[&buf], &mut [rsp.as_bytes_mut()])? };
+ if self.tx_queue.should_notify() {
+ self.transport.notify(TX_QUEUE_IDX);
+ }
+ self.token_buf.insert(token, buf);
+ self.token_rsp.insert(token, rsp);
+ Ok(token)
+ }
+
+ /// The PCM frame transmission corresponding to the given token has been completed.
+ pub fn pcm_xfer_ok(&mut self, token: u16) -> Result {
+ assert!(self.token_buf.contains_key(&token));
+ assert!(self.token_rsp.contains_key(&token));
+ unsafe {
+ self.tx_queue.pop_used(
+ token,
+ &[&self.token_buf[&token]],
+ &mut [self.token_rsp.get_mut(&token).unwrap().as_bytes_mut()],
+ )?;
+ }
+
+ self.token_buf.remove(&token);
+ self.token_rsp.remove(&token);
+ Ok(())
+ }
+
+ /// Get all output streams.
+ pub fn output_streams(&mut self) -> Result<Vec<u32>> {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ Ok(self
+ .pcm_infos
+ .as_ref()
+ .unwrap()
+ .iter()
+ .enumerate()
+ .filter(|(_, info)| info.direction == VIRTIO_SND_D_OUTPUT)
+ .map(|(idx, _)| idx as u32)
+ .collect())
+ }
+
+ /// Get all input streams.
+ pub fn input_streams(&mut self) -> Result<Vec<u32>> {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ Ok(self
+ .pcm_infos
+ .as_ref()
+ .unwrap()
+ .iter()
+ .enumerate()
+ .filter(|(_, info)| info.direction == VIRTIO_SND_D_INPUT)
+ .map(|(idx, _)| idx as u32)
+ .collect())
+ }
+
+ /// Get the rates that a stream supports.
+ pub fn rates_supported(&mut self, stream_id: u32) -> Result<PcmRates> {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ if stream_id >= self.pcm_infos.as_ref().unwrap().len() as u32 {
+ return Err(Error::InvalidParam);
+ }
+ Ok(PcmRates::from_bits_retain(
+ self.pcm_infos.as_ref().unwrap()[stream_id as usize].rates,
+ ))
+ }
+
+ /// Get the formats that a stream supports.
+ pub fn formats_supported(&mut self, stream_id: u32) -> Result<PcmFormats> {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ if stream_id >= self.pcm_infos.as_ref().unwrap().len() as u32 {
+ return Err(Error::InvalidParam);
+ }
+ Ok(PcmFormats::from_bits_retain(
+ self.pcm_infos.as_ref().unwrap()[stream_id as usize].formats,
+ ))
+ }
+
+ /// Get channel range that a stream supports.
+ pub fn channel_range_supported(&mut self, stream_id: u32) -> Result<RangeInclusive<u8>> {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ if stream_id >= self.pcm_infos.as_ref().unwrap().len() as u32 {
+ return Err(Error::InvalidParam);
+ }
+ let pcm_info = &self.pcm_infos.as_ref().unwrap()[stream_id as usize];
+ Ok(pcm_info.channels_min..=pcm_info.channels_max)
+ }
+
+ /// Get features that a stream supports.
+ pub fn features_supported(&mut self, stream_id: u32) -> Result<PcmFeatures> {
+ if !self.set_up {
+ self.set_up()?;
+ self.set_up = true;
+ }
+ if stream_id >= self.pcm_infos.as_ref().unwrap().len() as u32 {
+ return Err(Error::InvalidParam);
+ }
+ let pcm_info = &self.pcm_infos.as_ref().unwrap()[stream_id as usize];
+ Ok(PcmFeatures::from_bits_retain(pcm_info.features))
+ }
+
+ /// Get the latest notification.
+ pub fn latest_notification(&mut self) -> Result<Option<Notification>> {
+ // If the device has written notifications to the event_queue,
+ // then the oldest notification should be at the front of the queue.
+ self.event_queue.poll(&mut self.transport, |buffer| {
+ if let Some(event) = VirtIOSndEvent::read_from(buffer) {
+ Ok(Some(Notification {
+ notification_type: NotificationType::n(event.hdr.command_code)
+ .ok_or(Error::IoError)?,
+ data: event.data,
+ }))
+ } else {
+ Ok(None)
+ }
+ })
+ }
+}
+
+/// The status of the PCM stream.
+#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
+enum PCMState {
+ #[default]
+ SetParams,
+ Prepare,
+ Release,
+ Start,
+ Stop,
+}
+
+const QUEUE_SIZE: u16 = 32;
+const CONTROL_QUEUE_IDX: u16 = 0;
+const EVENT_QUEUE_IDX: u16 = 1;
+const TX_QUEUE_IDX: u16 = 2;
+const RX_QUEUE_IDX: u16 = 3;
+
+const SUPPORTED_FEATURES: Feature = Feature::RING_INDIRECT_DESC.union(Feature::RING_EVENT_IDX);
+
+bitflags! {
+ #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+ #[repr(transparent)]
+ struct JackFeatures: u32 {
+ /// Jack remapping support.
+ const REMAP = 1 << 0;
+ }
+}
+
+bitflags! {
+ /// Supported PCM stream features.
+ #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+ #[repr(transparent)]
+ pub struct PcmFeatures: u32 {
+ /// Supports sharing a host memory with a guest.
+ const SHMEM_HOST = 1 << 0;
+ /// Supports sharing a guest memory with a host.
+ const SHMEM_GUEST = 1 << 1;
+ /// Supports polling mode for message-based transport.
+ const MSG_POLLING = 1 << 2;
+ /// Supports elapsed period notifications for shared memory transport.
+ const EVT_SHMEM_PERIODS = 1 << 3;
+ /// Supports underrun/overrun notifications.
+ const EVT_XRUNS = 1 << 4;
+ }
+}
+
+bitflags! {
+ /// Supported PCM sample formats.
+ #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+ #[repr(transparent)]
+ pub struct PcmFormats: u64 {
+ /// IMA ADPCM format.
+ const IMA_ADPCM = 1 << 0;
+ /// Mu-law format.
+ const MU_LAW = 1 << 1;
+ /// A-law format.
+ const A_LAW = 1 << 2;
+ /// Signed 8-bit format.
+ const S8 = 1 << 3;
+ /// Unsigned 8-bit format.
+ const U8 = 1 << 4;
+ /// Signed 16-bit format.
+ const S16 = 1 << 5;
+ /// Unsigned 16-bit format.
+ const U16 = 1 << 6;
+ /// Signed 18.3-bit format.
+ const S18_3 = 1 << 7;
+ /// Unsigned 18.3-bit format.
+ const U18_3 = 1 << 8;
+ /// Signed 20.3-bit format.
+ const S20_3 = 1 << 9;
+ /// Unsigned 20.3-bit format.
+ const U20_3 = 1 << 10;
+ /// Signed 24.3-bit format.
+ const S24_3 = 1 << 11;
+ /// Unsigned 24.3-bit format.
+ const U24_3 = 1 << 12;
+ /// Signed 20-bit format.
+ const S20 = 1 << 13;
+ /// Unsigned 20-bit format.
+ const U20 = 1 << 14;
+ /// Signed 24-bit format.
+ const S24 = 1 << 15;
+ /// Unsigned 24-bit format.
+ const U24 = 1 << 16;
+ /// Signed 32-bit format.
+ const S32 = 1 << 17;
+ /// Unsigned 32-bit format.
+ const U32 = 1 << 18;
+ /// 32-bit floating-point format.
+ const FLOAT = 1 << 19;
+ /// 64-bit floating-point format.
+ const FLOAT64 = 1 << 20;
+ /// DSD unsigned 8-bit format.
+ const DSD_U8 = 1 << 21;
+ /// DSD unsigned 16-bit format.
+ const DSD_U16 = 1 << 22;
+ /// DSD unsigned 32-bit format.
+ const DSD_U32 = 1 << 23;
+ /// IEC958 subframe format.
+ const IEC958_SUBFRAME = 1 << 24;
+ }
+}
+
+/// A single PCM sample format.
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+#[repr(u8)]
+pub enum PcmFormat {
+ /// IMA ADPCM format.
+ #[default]
+ ImaAdpcm = 0,
+ /// Mu-law format.
+ MuLaw = 1,
+ /// A-law format.
+ ALaw = 2,
+ /// Signed 8-bit format.
+ S8 = 3,
+ /// Unsigned 8-bit format.
+ U8 = 4,
+ /// Signed 16-bit format.
+ S16 = 5,
+ /// Unsigned 16-bit format.
+ U16 = 6,
+ /// Signed 18.3-bit format.
+ S18_3 = 7,
+ /// Unsigned 18.3-bit format.
+ U18_3 = 8,
+ /// Signed 20.3-bit format.
+ S20_3 = 9,
+ /// Unsigned 20.3-bit format.
+ U20_3 = 10,
+ /// Signed 24.3-bit format.
+ S24_3 = 11,
+ /// Unsigned 24.3-bit format.
+ U24_3 = 12,
+ /// Signed 20-bit format.
+ S20 = 13,
+ /// Unsigned 20-bit format.
+ U20 = 14,
+ /// Signed 24-bit format.
+ S24 = 15,
+ /// Unsigned 24-bit format.
+ U24 = 16,
+ /// Signed 32-bit format.
+ S32 = 17,
+ /// Unsigned 32-bit format.
+ U32 = 18,
+ /// 32-bit floating-point format.
+ FLOAT = 19,
+ /// 64-bit floating-point format.
+ FLOAT64 = 20,
+ /// DSD unsigned 8-bit format.
+ DsdU8 = 21,
+ /// DSD unsigned 16-bit format.
+ DsdU16 = 22,
+ /// DSD unsigned 32-bit format.
+ DsdU32 = 23,
+ /// IEC958 subframe format.
+ Iec958Subframe = 24,
+}
+
+impl From<PcmFormat> for PcmFormats {
+ fn from(format: PcmFormat) -> Self {
+ match format {
+ PcmFormat::ImaAdpcm => PcmFormats::IMA_ADPCM,
+ PcmFormat::MuLaw => PcmFormats::MU_LAW,
+ PcmFormat::ALaw => PcmFormats::A_LAW,
+ PcmFormat::S8 => PcmFormats::S8,
+ PcmFormat::U8 => PcmFormats::U8,
+ PcmFormat::S16 => PcmFormats::S16,
+ PcmFormat::U16 => PcmFormats::U16,
+ PcmFormat::S18_3 => PcmFormats::S18_3,
+ PcmFormat::U18_3 => PcmFormats::U18_3,
+ PcmFormat::S20_3 => PcmFormats::S20_3,
+ PcmFormat::U20_3 => PcmFormats::U20_3,
+ PcmFormat::S24_3 => PcmFormats::S24_3,
+ PcmFormat::U24_3 => PcmFormats::U24_3,
+ PcmFormat::S20 => PcmFormats::S20,
+ PcmFormat::U20 => PcmFormats::U20,
+ PcmFormat::S24 => PcmFormats::S24,
+ PcmFormat::U24 => PcmFormats::U24,
+ PcmFormat::S32 => PcmFormats::S32,
+ PcmFormat::U32 => PcmFormats::U32,
+ PcmFormat::FLOAT => PcmFormats::FLOAT,
+ PcmFormat::FLOAT64 => PcmFormats::FLOAT64,
+ PcmFormat::DsdU8 => PcmFormats::DSD_U8,
+ PcmFormat::DsdU16 => PcmFormats::DSD_U16,
+ PcmFormat::DsdU32 => PcmFormats::DSD_U32,
+ PcmFormat::Iec958Subframe => PcmFormats::IEC958_SUBFRAME,
+ }
+ }
+}
+
+impl From<PcmFormat> for u8 {
+ fn from(format: PcmFormat) -> u8 {
+ format as _
+ }
+}
+
+bitflags! {
+ /// Supported PCM frame rates.
+ #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+ #[repr(transparent)]
+ pub struct PcmRates: u64 {
+ /// 5512 Hz PCM rate.
+ const RATE_5512 = 1 << 0;
+ /// 8000 Hz PCM rate.
+ const RATE_8000 = 1 << 1;
+ /// 11025 Hz PCM rate.
+ const RATE_11025 = 1 << 2;
+ /// 16000 Hz PCM rate.
+ const RATE_16000 = 1 << 3;
+ /// 22050 Hz PCM rate.
+ const RATE_22050 = 1 << 4;
+ /// 32000 Hz PCM rate.
+ const RATE_32000 = 1 << 5;
+ /// 44100 Hz PCM rate.
+ const RATE_44100 = 1 << 6;
+ /// 48000 Hz PCM rate.
+ const RATE_48000 = 1 << 7;
+ /// 64000 Hz PCM rate.
+ const RATE_64000 = 1 << 8;
+ /// 88200 Hz PCM rate.
+ const RATE_88200 = 1 << 9;
+ /// 96000 Hz PCM rate.
+ const RATE_96000 = 1 << 10;
+ /// 176400 Hz PCM rate.
+ const RATE_176400 = 1 << 11;
+ /// 192000 Hz PCM rate.
+ const RATE_192000 = 1 << 12;
+ /// 384000 Hz PCM rate.
+ const RATE_384000 = 1 << 13;
+ }
+}
+
+/// A PCM frame rate.
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+#[repr(u8)]
+pub enum PcmRate {
+ /// 5512 Hz PCM rate.
+ #[default]
+ Rate5512 = 0,
+ /// 8000 Hz PCM rate.
+ Rate8000 = 1,
+ /// 11025 Hz PCM rate.
+ Rate11025 = 2,
+ /// 16000 Hz PCM rate.
+ Rate16000 = 3,
+ /// 22050 Hz PCM rate.
+ Rate22050 = 4,
+ /// 32000 Hz PCM rate.
+ Rate32000 = 5,
+ /// 44100 Hz PCM rate.
+ Rate44100 = 6,
+ /// 48000 Hz PCM rate.
+ Rate48000 = 7,
+ /// 64000 Hz PCM rate.
+ Rate64000 = 8,
+ /// 88200 Hz PCM rate.
+ Rate88200 = 9,
+ /// 96000 Hz PCM rate.
+ Rate96000 = 10,
+ /// 176400 Hz PCM rate.
+ Rate176400 = 11,
+ /// 192000 Hz PCM rate.
+ Rate192000 = 12,
+ /// 384000 Hz PCM rate.
+ Rate384000 = 13,
+}
+
+impl From<PcmRate> for PcmRates {
+ fn from(rate: PcmRate) -> Self {
+ match rate {
+ PcmRate::Rate5512 => Self::RATE_5512,
+ PcmRate::Rate8000 => Self::RATE_8000,
+ PcmRate::Rate11025 => Self::RATE_11025,
+ PcmRate::Rate16000 => Self::RATE_16000,
+ PcmRate::Rate22050 => Self::RATE_22050,
+ PcmRate::Rate32000 => Self::RATE_32000,
+ PcmRate::Rate44100 => Self::RATE_44100,
+ PcmRate::Rate48000 => Self::RATE_48000,
+ PcmRate::Rate64000 => Self::RATE_64000,
+ PcmRate::Rate88200 => Self::RATE_88200,
+ PcmRate::Rate96000 => Self::RATE_96000,
+ PcmRate::Rate176400 => Self::RATE_176400,
+ PcmRate::Rate192000 => Self::RATE_192000,
+ PcmRate::Rate384000 => Self::RATE_384000,
+ }
+ }
+}
+
+impl From<PcmRate> for u8 {
+ fn from(rate: PcmRate) -> Self {
+ rate as _
+ }
+}
+
+#[repr(C)]
+struct VirtIOSoundConfig {
+ jacks: ReadOnly<u32>,
+ streams: ReadOnly<u32>,
+ chmaps: ReadOnly<u32>,
+}
+
+/// In virtIO v1.2, this enum should be called "Code".
+///
+/// To avoid ambiguity in its meaning, I use the term "CommandCode" here.
+#[repr(u32)]
+#[derive(Copy, Clone, Debug, Eq, N, PartialEq)]
+enum CommandCode {
+ /* jack control request types */
+ RJackInfo = 1,
+ RJackRemap,
+
+ /* PCM control request types */
+ RPcmInfo = 0x0100,
+ RPcmSetParams,
+ RPcmPrepare,
+ RPcmRelease,
+ RPcmStart,
+ RPcmStop,
+
+ /* channel map control request types */
+ RChmapInfo = 0x0200,
+
+ /* jack event types */
+ EvtJackConnected = 0x1000,
+ EvtJackDisconnected,
+
+ /* PCM event types */
+ EvtPcmPeriodElapsed = 0x1100,
+ EvtPcmXrun,
+
+ /* common status codes */
+ /// success
+ SOk = 0x8000,
+ /// a control message is malformed or contains invalid parameters
+ SBadMsg,
+ /// requested operation or parameters are not supported
+ SNotSupp,
+ /// an I/O error occurred
+ SIoErr,
+}
+
+impl From<CommandCode> for u32 {
+ fn from(code: CommandCode) -> u32 {
+ code as u32
+ }
+}
+
+/// Enum representing the types of item information requests.
+#[repr(u32)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ItemInformationRequestType {
+ /// Represents a jack information request.
+ RJackInfo = 1,
+ /// Represents a PCM information request.
+ RPcmInfo = 0x0100,
+ /// Represents a channel map information request.
+ RChmapInfo = 0x0200,
+}
+
+impl From<ItemInformationRequestType> for VirtIOSndHdr {
+ fn from(value: ItemInformationRequestType) -> Self {
+ VirtIOSndHdr {
+ command_code: value.into(),
+ }
+ }
+}
+
+impl From<ItemInformationRequestType> for u32 {
+ fn from(request_type: ItemInformationRequestType) -> u32 {
+ request_type as _
+ }
+}
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+#[repr(u32)]
+enum RequestStatusCode {
+ /* common status codes */
+ Ok = 0x8000,
+ BadMsg,
+ NotSupp,
+ IoErr,
+}
+
+impl From<RequestStatusCode> for VirtIOSndHdr {
+ fn from(value: RequestStatusCode) -> Self {
+ VirtIOSndHdr {
+ command_code: value as _,
+ }
+ }
+}
+
+/// A common header
+#[repr(C)]
+#[derive(AsBytes, Clone, Debug, Eq, FromBytes, FromZeroes, PartialEq)]
+struct VirtIOSndHdr {
+ command_code: u32,
+}
+
+impl From<CommandCode> for VirtIOSndHdr {
+ fn from(value: CommandCode) -> Self {
+ VirtIOSndHdr {
+ command_code: value.into(),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(FromBytes, FromZeroes)]
+/// An event notification
+struct VirtIOSndEvent {
+ hdr: VirtIOSndHdr,
+ data: u32,
+}
+
+/// The notification type.
+#[repr(u32)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum NotificationType {
+ /// An external device has been connected to the jack.
+ JackConnected = 0x1000,
+ /// An external device has been disconnected from the jack.
+ JackDisconnected,
+ /// A hardware buffer period has elapsed, the period size is controlled using the `period_bytes` field.
+ PcmPeriodElapsed = 0x1100,
+ /// An underflow for the output stream or an overflow for the inputstream has occurred.
+ PcmXrun,
+}
+
+impl NotificationType {
+ /// Converts the given value to a variant of this enum, if any matches.
+ fn n(value: u32) -> Option<Self> {
+ match value {
+ 0x1100 => Some(Self::PcmPeriodElapsed),
+ 0x1101 => Some(Self::PcmXrun),
+ 0x1000 => Some(Self::JackConnected),
+ 0x1001 => Some(Self::JackDisconnected),
+ _ => None,
+ }
+ }
+}
+
+/// Notification from sound device.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Notification {
+ notification_type: NotificationType,
+ data: u32,
+}
+
+impl Notification {
+ /// Get the resource index.
+ pub fn data(&self) -> u32 {
+ self.data
+ }
+
+ /// Get the notification type.
+ pub fn notification_type(&self) -> NotificationType {
+ self.notification_type
+ }
+}
+
+const VIRTIO_SND_D_OUTPUT: u8 = 0;
+const VIRTIO_SND_D_INPUT: u8 = 1;
+
+#[repr(C)]
+#[derive(AsBytes, Debug, FromBytes, FromZeroes)]
+struct VirtIOSndQueryInfo {
+ /// specifies a particular item request type (VIRTIO_SND_R_*_INFO)
+ hdr: VirtIOSndHdr,
+ /// specifies the starting identifier for the item
+ /// (the range of available identifiers is limited
+ /// by the total number of particular items
+ /// that is indicated in the device configuration space)
+ start_id: u32,
+ /// specifies the number of items for which information is requested
+ /// (the total number of particular items is indicated
+ /// in the device configuration space).
+ count: u32,
+ /// specifies the size of the structure containing information for one item
+ /// (used for backward compatibility)
+ size: u32,
+}
+
+#[repr(C)]
+#[derive(AsBytes, Debug, FromBytes, FromZeroes)]
+struct VirtIOSndQueryInfoRsp {
+ hdr: VirtIOSndHdr,
+ info: VirtIOSndInfo,
+}
+
+/// Field `hda_fn_nid` indicates a function group node identifier.
+#[repr(C)]
+#[derive(AsBytes, Clone, Debug, Eq, FromBytes, FromZeroes, PartialEq)]
+pub struct VirtIOSndInfo {
+ hda_fn_nid: u32,
+}
+
+#[repr(C)]
+#[derive(AsBytes, Clone, Debug, FromBytes, FromZeroes)]
+struct VirtIOSndJackHdr {
+ hdr: VirtIOSndHdr,
+ /// specifies a jack identifier from 0 to jacks - 1
+ jack_id: u32,
+}
+
+/// Jack infomation.
+#[repr(C)]
+#[derive(AsBytes, Clone, Eq, FromBytes, FromZeroes, PartialEq)]
+pub struct VirtIOSndJackInfo {
+ hdr: VirtIOSndInfo,
+ features: u32,
+ /// indicates a pin default configuration value
+ hda_reg_defconf: u32,
+ /// indicates a pin capabilities value
+ hda_reg_caps: u32,
+ /// indicates the current jack connection status (1 - connected, 0 - disconnected)
+ connected: u8,
+
+ _padding: [u8; 7],
+}
+
+impl Debug for VirtIOSndJackInfo {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.debug_struct("VirtIOSndJackInfo")
+ .field("hdr", &self.hdr)
+ .field("features", &JackFeatures::from_bits_retain(self.features))
+ .field("hda_reg_defconf", &self.hda_reg_defconf)
+ .field("hda_reg_caps", &self.hda_reg_caps)
+ .field("connected", &self.connected)
+ .field("_padding", &self._padding)
+ .finish()
+ }
+}
+
+impl Display for VirtIOSndJackInfo {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ let connected_status = if self.connected == 1 {
+ "CONNECTED"
+ } else {
+ "DISCONNECTED"
+ };
+ write!(
+ f,
+ "features: {:?}, hda_reg_defconf: {}, hda_reg_caps: {}, connected: {}",
+ JackFeatures::from_bits_retain(self.features),
+ self.hda_reg_defconf,
+ self.hda_reg_caps,
+ connected_status
+ )
+ }
+}
+
+#[repr(C)]
+#[derive(AsBytes, FromBytes, FromZeroes)]
+struct VirtIOSndJackInfoRsp {
+ hdr: VirtIOSndHdr,
+ body: VirtIOSndJackInfo,
+}
+
+#[repr(C)]
+#[derive(AsBytes, FromBytes, FromZeroes)]
+struct VirtIOSndJackRemap {
+ hdr: VirtIOSndJackHdr,
+ association: u32,
+ sequence: u32,
+}
+
+#[repr(C)]
+#[derive(AsBytes, Debug, Eq, FromBytes, FromZeroes, PartialEq)]
+struct VirtIOSndPcmHdr {
+ /// specifies request type (VIRTIO_SND_R_PCM_*)
+ hdr: VirtIOSndHdr,
+ /// specifies a PCM stream identifier from 0 to streams - 1
+ stream_id: u32,
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+enum PcmStreamFeatures {
+ ShmemHost = 0,
+ ShmemGuest,
+ MsgPolling,
+ EvtShmemPeriods,
+ EvtXruns,
+}
+
+impl From<PcmStreamFeatures> for u32 {
+ fn from(value: PcmStreamFeatures) -> Self {
+ value as _
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[repr(u64)]
+enum PcmSampleFormat {
+ /* analog formats (width / physical width) */
+ ImaAdpcm = 0, /* 4 / 4 bits */
+ MuLaw, /* 8 / 8 bits */
+ ALaw, /* 8 / 8 bits */
+ S8, /* 8 / 8 bits */
+ U8, /* 8 / 8 bits */
+ S16, /* 16 / 16 bits */
+ U16, /* 16 / 16 bits */
+ S18_3, /* 18 / 24 bits */
+ U18_3, /* 18 / 24 bits */
+ S20_3, /* 20 / 24 bits */
+ U20_3, /* 20 / 24 bits */
+ S24_3, /* 24 / 24 bits */
+ U24_3, /* 24 / 24 bits */
+ S20, /* 20 / 32 bits */
+ U20, /* 20 / 32 bits */
+ S24, /* 24 / 32 bits */
+ U24, /* 24 / 32 bits */
+ S32, /* 32 / 32 bits */
+ U32, /* 32 / 32 bits */
+ Float, /* 32 / 32 bits */
+ Float64, /* 64 / 64 bits */
+ /* digital formats (width / physical width) */
+ DsdU8, /* 8 / 8 bits */
+ DsdU16, /* 16 / 16 bits */
+ DsdU32, /* 32 / 32 bits */
+ Iec958Subframe, /* 32 / 32 bits */
+}
+
+impl From<PcmSampleFormat> for u64 {
+ fn from(value: PcmSampleFormat) -> Self {
+ value as _
+ }
+}
+
+/// PCM information.
+#[repr(C)]
+#[derive(AsBytes, Clone, Eq, FromBytes, FromZeroes, PartialEq)]
+pub struct VirtIOSndPcmInfo {
+ hdr: VirtIOSndInfo,
+ features: u32, /* 1 << VIRTIO_SND_PCM_F_XXX */
+ formats: u64, /* 1 << VIRTIO_SND_PCM_FMT_XXX */
+ rates: u64, /* 1 << VIRTIO_SND_PCM_RATE_XXX */
+ /// indicates the direction of data flow (VIRTIO_SND_D_*)
+ direction: u8,
+ /// indicates a minimum number of supported channels
+ channels_min: u8,
+ /// indicates a maximum number of supported channels
+ channels_max: u8,
+ _padding: [u8; 5],
+}
+
+impl Debug for VirtIOSndPcmInfo {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.debug_struct("VirtIOSndPcmInfo")
+ .field("hdr", &self.hdr)
+ .field("features", &PcmFeatures::from_bits_retain(self.features))
+ .field("formats", &PcmFormats::from_bits_retain(self.formats))
+ .field("rates", &PcmRates::from_bits_retain(self.rates))
+ .field("direction", &self.direction)
+ .field("channels_min", &self.channels_min)
+ .field("channels_max", &self.channels_max)
+ .field("_padding", &self._padding)
+ .finish()
+ }
+}
+
+impl Display for VirtIOSndPcmInfo {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ let direction = if self.direction == VIRTIO_SND_D_INPUT {
+ "INPUT"
+ } else {
+ "OUTPUT"
+ };
+ write!(
+ f,
+ "features: {:?}, rates: {:?}, formats: {:?}, direction: {}",
+ PcmFeatures::from_bits_retain(self.features),
+ PcmRates::from_bits_retain(self.rates),
+ PcmFormats::from_bits_retain(self.formats),
+ direction
+ )
+ }
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+struct PcmParameters {
+ setup: bool,
+ buffer_bytes: u32,
+ period_bytes: u32,
+ features: PcmFeatures,
+ channels: u8,
+ format: PcmFormat,
+ rate: PcmRate,
+}
+
+#[repr(C)]
+#[derive(AsBytes, Debug, Eq, FromBytes, FromZeroes, PartialEq)]
+struct VirtIOSndPcmSetParams {
+ hdr: VirtIOSndPcmHdr, /* .code = VIRTIO_SND_R_PCM_SET_PARAMS */
+ buffer_bytes: u32,
+ period_bytes: u32,
+ features: u32, /* 1 << VIRTIO_SND_PCM_F_XXX */
+ channels: u8,
+ format: u8,
+ rate: u8,
+
+ _padding: u8,
+}
+
+/// An I/O header
+#[repr(C)]
+#[derive(AsBytes, FromBytes, FromZeroes)]
+struct VirtIOSndPcmXfer {
+ stream_id: u32,
+}
+
+/// An I/O status
+#[repr(C)]
+#[derive(AsBytes, Default, FromBytes, FromZeroes)]
+struct VirtIOSndPcmStatus {
+ status: u32,
+ latency_bytes: u32,
+}
+
+#[derive(Copy, Clone, Debug, Eq, N, PartialEq)]
+#[repr(u8)]
+enum ChannelPosition {
+ /// undefined
+ None = 0,
+ /// silent
+ Na,
+ /// mono stream
+ Mono,
+ /// front left
+ Fl,
+ /// front right
+ Fr,
+ /// rear left
+ Rl,
+ /// rear right
+ Rr,
+ /// front center
+ Fc,
+ /// low frequency (LFE)
+ Lfe,
+ /// side left
+ Sl,
+ /// side right
+ Sr,
+ /// rear center
+ Rc,
+ /// front left center
+ Flc,
+ /// front right center
+ Frc,
+ /// rear left center
+ Rlc,
+ /// rear right center
+ Rrc,
+ /// front left wide
+ Flw,
+ /// front right wide
+ Frw,
+ /// front left high
+ Flh,
+ /// front center high
+ Fch,
+ /// front right high
+ Frh,
+ /// top center
+ Tc,
+ /// top front left
+ Tfl,
+ /// top front right
+ Tfr,
+ /// top front center
+ Tfc,
+ /// top rear left
+ Trl,
+ /// top rear right
+ Trr,
+ /// top rear center
+ Trc,
+ /// top front left center
+ Tflc,
+ /// top front right center
+ Tfrc,
+ /// top side left
+ Tsl,
+ /// top side right
+ Tsr,
+ /// left LFE
+ Llfe,
+ /// right LFE
+ Rlfe,
+ /// bottom center
+ Bc,
+ /// bottom left center
+ Blc,
+ /// bottom right center
+ Brc,
+}
+
+/// maximum possible number of channels
+const VIRTIO_SND_CHMAP_MAX_SIZE: usize = 18;
+
+#[repr(C)]
+#[derive(AsBytes, Clone, Debug, FromBytes, FromZeroes)]
+struct VirtIOSndChmapInfo {
+ hdr: VirtIOSndInfo,
+ direction: u8,
+ channels: u8,
+ positions: [u8; VIRTIO_SND_CHMAP_MAX_SIZE],
+}
+
+impl Display for VirtIOSndChmapInfo {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ let direction = if self.direction == VIRTIO_SND_D_INPUT {
+ "INPUT"
+ } else {
+ "OUTPUT"
+ };
+ write!(
+ f,
+ "direction: {}, channels: {}, postions: [",
+ direction, self.channels
+ )?;
+ for i in 0..usize::from(self.channels) {
+ if i != 0 {
+ write!(f, ", ")?;
+ }
+ if let Some(position) = ChannelPosition::n(self.positions[i]) {
+ write!(f, "{:?}", position)?;
+ } else {
+ write!(f, "{}", self.positions[i])?;
+ }
+ }
+ write!(f, "]")?;
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ hal::fake::FakeHal,
+ transport::{
+ fake::{FakeTransport, QueueStatus, State},
+ DeviceType,
+ },
+ volatile::ReadOnly,
+ };
+ use alloc::{sync::Arc, vec};
+ use core::ptr::NonNull;
+ use fake::FakeSoundDevice;
+ use std::sync::Mutex;
+
+ #[test]
+ fn config() {
+ let mut config_space = VirtIOSoundConfig {
+ jacks: ReadOnly::new(3),
+ streams: ReadOnly::new(4),
+ chmaps: ReadOnly::new(2),
+ };
+ let state = Arc::new(Mutex::new(State {
+ queues: vec![
+ QueueStatus::default(),
+ QueueStatus::default(),
+ QueueStatus::default(),
+ QueueStatus::default(),
+ ],
+ ..Default::default()
+ }));
+ let transport = FakeTransport {
+ device_type: DeviceType::Sound,
+ max_queue_size: 32,
+ device_features: 0,
+ config_space: NonNull::from(&mut config_space),
+ state: state.clone(),
+ };
+ let sound =
+ VirtIOSound::<FakeHal, FakeTransport<VirtIOSoundConfig>>::new(transport).unwrap();
+ assert_eq!(sound.jacks(), 3);
+ assert_eq!(sound.streams(), 4);
+ assert_eq!(sound.chmaps(), 2);
+ }
+
+ #[test]
+ fn empty_info() {
+ let (fake, transport) = FakeSoundDevice::new(vec![], vec![], vec![]);
+ let mut sound =
+ VirtIOSound::<FakeHal, FakeTransport<VirtIOSoundConfig>>::new(transport).unwrap();
+ let handle = fake.spawn();
+
+ assert_eq!(sound.jacks(), 0);
+ assert_eq!(sound.streams(), 0);
+ assert_eq!(sound.chmaps(), 0);
+ assert_eq!(sound.output_streams().unwrap(), vec![]);
+ assert_eq!(sound.input_streams().unwrap(), vec![]);
+
+ fake.terminate();
+ handle.join().unwrap();
+ }
+
+ #[test]
+ fn stream_info() {
+ let (fake, transport) = FakeSoundDevice::new(
+ vec![VirtIOSndJackInfo {
+ hdr: VirtIOSndInfo { hda_fn_nid: 0 },
+ features: 0,
+ hda_reg_defconf: 0,
+ hda_reg_caps: 0,
+ connected: 0,
+ _padding: Default::default(),
+ }],
+ vec![
+ VirtIOSndPcmInfo {
+ hdr: VirtIOSndInfo { hda_fn_nid: 0 },
+ features: 0,
+ formats: (PcmFormats::U8 | PcmFormats::U32).bits(),
+ rates: (PcmRates::RATE_44100 | PcmRates::RATE_32000).bits(),
+ direction: VIRTIO_SND_D_OUTPUT,
+ channels_min: 1,
+ channels_max: 2,
+ _padding: Default::default(),
+ },
+ VirtIOSndPcmInfo {
+ hdr: VirtIOSndInfo { hda_fn_nid: 0 },
+ features: 0,
+ formats: 0,
+ rates: 0,
+ direction: VIRTIO_SND_D_INPUT,
+ channels_min: 0,
+ channels_max: 0,
+ _padding: Default::default(),
+ },
+ ],
+ vec![VirtIOSndChmapInfo {
+ hdr: VirtIOSndInfo { hda_fn_nid: 0 },
+ direction: 0,
+ channels: 0,
+ positions: [0; 18],
+ }],
+ );
+ let mut sound =
+ VirtIOSound::<FakeHal, FakeTransport<VirtIOSoundConfig>>::new(transport).unwrap();
+ let handle = fake.spawn();
+
+ assert_eq!(sound.output_streams().unwrap(), vec![0]);
+ assert_eq!(
+ sound.rates_supported(0).unwrap(),
+ PcmRates::RATE_44100 | PcmRates::RATE_32000
+ );
+ assert_eq!(
+ sound.formats_supported(0).unwrap(),
+ PcmFormats::U8 | PcmFormats::U32
+ );
+ assert_eq!(sound.channel_range_supported(0).unwrap(), 1..=2);
+ assert_eq!(sound.features_supported(0).unwrap(), PcmFeatures::empty());
+
+ assert_eq!(sound.input_streams().unwrap(), vec![1]);
+ assert_eq!(sound.rates_supported(1).unwrap(), PcmRates::empty());
+ assert_eq!(sound.formats_supported(1).unwrap(), PcmFormats::empty());
+ assert_eq!(sound.channel_range_supported(1).unwrap(), 0..=0);
+ assert_eq!(sound.features_supported(1).unwrap(), PcmFeatures::empty());
+
+ fake.terminate();
+ handle.join().unwrap();
+ }
+
+ #[test]
+ fn play() {
+ let (fake, transport) = FakeSoundDevice::new(
+ vec![],
+ vec![VirtIOSndPcmInfo {
+ hdr: VirtIOSndInfo { hda_fn_nid: 0 },
+ features: 0,
+ formats: (PcmFormats::U8 | PcmFormats::U32).bits(),
+ rates: (PcmRates::RATE_44100 | PcmRates::RATE_32000).bits(),
+ direction: VIRTIO_SND_D_OUTPUT,
+ channels_min: 1,
+ channels_max: 2,
+ _padding: Default::default(),
+ }],
+ vec![],
+ );
+ let mut sound =
+ VirtIOSound::<FakeHal, FakeTransport<VirtIOSoundConfig>>::new(transport).unwrap();
+ let handle = fake.spawn();
+
+ assert_eq!(sound.output_streams().unwrap(), vec![0]);
+ assert_eq!(sound.input_streams().unwrap(), vec![]);
+
+ sound
+ .pcm_set_params(
+ 0,
+ 100,
+ 100,
+ PcmFeatures::empty(),
+ 1,
+ PcmFormat::U8,
+ PcmRate::Rate8000,
+ )
+ .unwrap();
+ assert_eq!(
+ fake.params.lock().unwrap()[0],
+ Some(VirtIOSndPcmSetParams {
+ hdr: VirtIOSndPcmHdr {
+ hdr: VirtIOSndHdr {
+ command_code: CommandCode::RPcmSetParams.into(),
+ },
+ stream_id: 0,
+ },
+ buffer_bytes: 100,
+ period_bytes: 100,
+ features: 0,
+ channels: 1,
+ format: PcmFormat::U8.into(),
+ rate: PcmRate::Rate8000.into(),
+ _padding: Default::default(),
+ })
+ );
+
+ sound.pcm_prepare(0).unwrap();
+ sound.pcm_start(0).unwrap();
+
+ let mut expected_sound = vec![];
+
+ // Playing an empty set of frames should be a no-op.
+ println!("Playing empty");
+ sound.pcm_xfer(0, &[]).unwrap();
+ assert_eq!(fake.played_bytes.lock().unwrap()[0], expected_sound);
+
+ // Send one buffer worth.
+ println!("Playing 100");
+ sound.pcm_xfer(0, &[42; 100]).unwrap();
+ expected_sound.extend([42; 100]);
+ assert_eq!(fake.played_bytes.lock().unwrap()[0], expected_sound);
+
+ // Send two buffers worth.
+ println!("Playing 200");
+ sound.pcm_xfer(0, &[66; 200]).unwrap();
+ expected_sound.extend([66; 200]);
+ assert_eq!(fake.played_bytes.lock().unwrap()[0], expected_sound);
+
+ // Send half a buffer worth.
+ println!("Playing 50");
+ sound.pcm_xfer(0, &[55; 50]).unwrap();
+ expected_sound.extend([55; 50]);
+ assert_eq!(fake.played_bytes.lock().unwrap()[0], expected_sound);
+
+ // Send enough that the queue will fill up.
+ println!("Playing 5000");
+ sound.pcm_xfer(0, &[12; 5000]).unwrap();
+ expected_sound.extend([12; 5000]);
+ assert_eq!(fake.played_bytes.lock().unwrap()[0], expected_sound);
+
+ sound.pcm_stop(0).unwrap();
+ sound.pcm_release(0).unwrap();
+
+ fake.terminate();
+ handle.join().unwrap();
+ }
+}
diff --git a/crates/virtio-drivers/src/device/sound/fake.rs b/crates/virtio-drivers/src/device/sound/fake.rs
new file mode 100644
index 0000000..b4bfb8c
--- /dev/null
+++ b/crates/virtio-drivers/src/device/sound/fake.rs
@@ -0,0 +1,244 @@
+//! Fake VirtIO sound device for tests.
+
+use super::{
+ CommandCode, VirtIOSndChmapInfo, VirtIOSndHdr, VirtIOSndJackInfo, VirtIOSndPcmInfo,
+ VirtIOSndPcmStatus, VirtIOSndPcmXfer, VirtIOSndQueryInfo, VirtIOSoundConfig, CONTROL_QUEUE_IDX,
+ QUEUE_SIZE, TX_QUEUE_IDX,
+};
+use crate::{
+ device::sound::{VirtIOSndPcmHdr, VirtIOSndPcmSetParams},
+ transport::{
+ fake::{FakeTransport, QueueStatus, State},
+ DeviceType,
+ },
+ volatile::ReadOnly,
+};
+use alloc::{sync::Arc, vec};
+use core::{
+ convert::{TryFrom, TryInto},
+ mem::size_of,
+ ptr::NonNull,
+ time::Duration,
+};
+use std::{
+ iter::repeat_with,
+ sync::{
+ atomic::{AtomicBool, Ordering},
+ Mutex,
+ },
+ thread::{self, JoinHandle},
+};
+use zerocopy::{AsBytes, FromBytes};
+
+#[derive(Clone, Debug)]
+pub struct FakeSoundDevice {
+ pub state: Arc<Mutex<State>>,
+ pub terminate: Arc<AtomicBool>,
+ /// The paramaters set for each stream, if any.
+ pub params: Arc<Mutex<Vec<Option<VirtIOSndPcmSetParams>>>>,
+ /// The bytes send on the TX queue for each channel.
+ pub played_bytes: Arc<Mutex<Vec<Vec<u8>>>>,
+ pub jack_infos: Vec<VirtIOSndJackInfo>,
+ pub pcm_infos: Vec<VirtIOSndPcmInfo>,
+ pub chmap_infos: Vec<VirtIOSndChmapInfo>,
+}
+
+impl FakeSoundDevice {
+ pub fn new(
+ jack_infos: Vec<VirtIOSndJackInfo>,
+ pcm_infos: Vec<VirtIOSndPcmInfo>,
+ chmap_infos: Vec<VirtIOSndChmapInfo>,
+ ) -> (Self, FakeTransport<VirtIOSoundConfig>) {
+ let mut config_space = VirtIOSoundConfig {
+ jacks: ReadOnly::new(jack_infos.len().try_into().unwrap()),
+ streams: ReadOnly::new(pcm_infos.len().try_into().unwrap()),
+ chmaps: ReadOnly::new(chmap_infos.len().try_into().unwrap()),
+ };
+ let state = Arc::new(Mutex::new(State {
+ queues: vec![
+ QueueStatus::default(),
+ QueueStatus::default(),
+ QueueStatus::default(),
+ QueueStatus::default(),
+ ],
+ ..Default::default()
+ }));
+ let transport = FakeTransport {
+ device_type: DeviceType::Socket,
+ max_queue_size: 32,
+ device_features: 0,
+ config_space: NonNull::from(&mut config_space),
+ state: state.clone(),
+ };
+ let params = repeat_with(|| None).take(pcm_infos.len()).collect();
+ let played_bytes = vec![vec![]; pcm_infos.len()];
+
+ (
+ Self {
+ state,
+ terminate: Arc::new(AtomicBool::new(false)),
+ params: Arc::new(Mutex::new(params)),
+ played_bytes: Arc::new(Mutex::new(played_bytes)),
+ jack_infos,
+ pcm_infos,
+ chmap_infos,
+ },
+ transport,
+ )
+ }
+
+ /// Start a background thread simulating the device.
+ pub fn spawn(&self) -> JoinHandle<()> {
+ let clone = self.clone();
+ thread::spawn(move || clone.run())
+ }
+
+ /// Terminate the background thread for the device.
+ pub fn terminate(&self) {
+ self.terminate.store(true, Ordering::Release);
+ }
+
+ fn run(&self) {
+ while !self.terminate.load(Ordering::Acquire) {
+ if State::poll_queue_notified(&self.state, CONTROL_QUEUE_IDX) {
+ println!("Control queue was notified.");
+
+ while self
+ .state
+ .lock()
+ .unwrap()
+ .read_write_queue::<{ QUEUE_SIZE as usize }>(CONTROL_QUEUE_IDX, |request| {
+ self.handle_control_request(&request)
+ })
+ {}
+ } else if State::poll_queue_notified(&self.state, TX_QUEUE_IDX) {
+ println!("TX queue was notified");
+ while self
+ .state
+ .lock()
+ .unwrap()
+ .read_write_queue::<{ QUEUE_SIZE as usize }>(TX_QUEUE_IDX, |request| {
+ self.handle_tx(&request)
+ })
+ {}
+ } else {
+ thread::sleep(Duration::from_millis(10));
+ }
+ }
+ }
+
+ fn handle_tx(&self, request: &[u8]) -> Vec<u8> {
+ let header = VirtIOSndPcmXfer::read_from_prefix(&request).expect("TX request too short");
+ self.played_bytes.lock().unwrap()[usize::try_from(header.stream_id).unwrap()]
+ .extend(&request[size_of::<VirtIOSndPcmXfer>()..]);
+
+ VirtIOSndPcmStatus {
+ status: CommandCode::SOk.into(),
+ latency_bytes: 0,
+ }
+ .as_bytes()
+ .to_owned()
+ }
+
+ fn handle_control_request(&self, request: &[u8]) -> Vec<u8> {
+ {
+ let header =
+ VirtIOSndHdr::read_from_prefix(&request).expect("Control request too short");
+ let mut response = Vec::new();
+ const R_JACK_INFO: u32 = CommandCode::RJackInfo as u32;
+ const R_PCM_INFO: u32 = CommandCode::RPcmInfo as u32;
+ const R_CHMAP_INFO: u32 = CommandCode::RChmapInfo as u32;
+ const R_PCM_SET_PARAMS: u32 = CommandCode::RPcmSetParams as u32;
+ const R_PCM_PREPARE: u32 = CommandCode::RPcmPrepare as u32;
+ const R_PCM_START: u32 = CommandCode::RPcmStart as u32;
+ const R_PCM_STOP: u32 = CommandCode::RPcmStop as u32;
+ const R_PCM_RELEASE: u32 = CommandCode::RPcmRelease as u32;
+ match header.command_code {
+ R_JACK_INFO => {
+ let request = VirtIOSndQueryInfo::read_from(&request)
+ .expect("R_JACK_INFO control request wrong length");
+ assert_eq!(
+ request.size,
+ u32::try_from(size_of::<VirtIOSndJackInfo>()).unwrap()
+ );
+ response.extend_from_slice(
+ VirtIOSndHdr {
+ command_code: CommandCode::SOk.into(),
+ }
+ .as_bytes(),
+ );
+ for jack_info in &self.jack_infos[request.start_id as usize
+ ..request.start_id as usize + request.count as usize]
+ {
+ response.extend_from_slice(jack_info.as_bytes());
+ }
+ }
+ R_PCM_INFO => {
+ let request = VirtIOSndQueryInfo::read_from(&request)
+ .expect("R_PCM_INFO control request wrong length");
+ assert_eq!(
+ request.size,
+ u32::try_from(size_of::<VirtIOSndPcmInfo>()).unwrap()
+ );
+ response.extend_from_slice(
+ VirtIOSndHdr {
+ command_code: CommandCode::SOk.into(),
+ }
+ .as_bytes(),
+ );
+ for pcm_info in &self.pcm_infos[request.start_id as usize
+ ..request.start_id as usize + request.count as usize]
+ {
+ response.extend_from_slice(pcm_info.as_bytes());
+ }
+ }
+ R_CHMAP_INFO => {
+ let request = VirtIOSndQueryInfo::read_from(&request)
+ .expect("R_CHMAP_INFO control request wrong length");
+ assert_eq!(
+ request.size,
+ u32::try_from(size_of::<VirtIOSndChmapInfo>()).unwrap()
+ );
+ response.extend_from_slice(
+ VirtIOSndHdr {
+ command_code: CommandCode::SOk.into(),
+ }
+ .as_bytes(),
+ );
+ for chmap_info in &self.chmap_infos[request.start_id as usize
+ ..request.start_id as usize + request.count as usize]
+ {
+ response.extend_from_slice(chmap_info.as_bytes());
+ }
+ }
+ R_PCM_SET_PARAMS => {
+ let request = VirtIOSndPcmSetParams::read_from(&request)
+ .expect("R_PCM_SET_PARAMS request wrong length");
+ let stream_id = request.hdr.stream_id;
+ self.params.lock().unwrap()[usize::try_from(stream_id).unwrap()] =
+ Some(request);
+ response.extend_from_slice(
+ VirtIOSndHdr {
+ command_code: CommandCode::SOk.into(),
+ }
+ .as_bytes(),
+ );
+ }
+ R_PCM_PREPARE | R_PCM_START | R_PCM_STOP | R_PCM_RELEASE => {
+ let _request =
+ VirtIOSndPcmHdr::read_from(&request).expect("Request wrong length");
+ response.extend_from_slice(
+ VirtIOSndHdr {
+ command_code: CommandCode::SOk.into(),
+ }
+ .as_bytes(),
+ );
+ }
+ _ => {
+ panic!("Unexpected control request, header {:?}", header);
+ }
+ }
+ response
+ }
+ }
+}
diff --git a/crates/virtio-drivers/src/queue.rs b/crates/virtio-drivers/src/queue.rs
index 3573a39..ed7c856 100644
--- a/crates/virtio-drivers/src/queue.rs
+++ b/crates/virtio-drivers/src/queue.rs
@@ -1,5 +1,8 @@
#![deny(unsafe_op_in_unsafe_fn)]
+#[cfg(feature = "alloc")]
+pub mod owning;
+
use crate::hal::{BufferDirection, Dma, Hal, PhysAddr};
use crate::transport::Transport;
use crate::{align_up, nonnull_slice_from_raw_parts, pages, Error, Result, PAGE_SIZE};
@@ -837,13 +840,16 @@
/// Simulates the device reading from a VirtIO queue and writing a response back, for use in tests.
///
/// The fake device always uses descriptors in order.
+///
+/// Returns true if a descriptor chain was available and processed, or false if no descriptors were
+/// available.
#[cfg(test)]
pub(crate) fn fake_read_write_queue<const QUEUE_SIZE: usize>(
descriptors: *const [Descriptor; QUEUE_SIZE],
queue_driver_area: *const u8,
queue_device_area: *mut u8,
handler: impl FnOnce(Vec<u8>) -> Vec<u8>,
-) {
+) -> bool {
use core::{ops::Deref, slice};
let available_ring = queue_driver_area as *const AvailRing<QUEUE_SIZE>;
@@ -853,10 +859,10 @@
// nothing else accesses them during this block.
unsafe {
// Make sure there is actually at least one descriptor available to read from.
- assert_ne!(
- (*available_ring).idx.load(Ordering::Acquire),
- (*used_ring).idx.load(Ordering::Acquire)
- );
+ if (*available_ring).idx.load(Ordering::Acquire) == (*used_ring).idx.load(Ordering::Acquire)
+ {
+ return false;
+ }
// The fake device always uses descriptors in order, like VIRTIO_F_IN_ORDER, so
// `used_ring.idx` marks the next descriptor we should take from the available ring.
let next_slot = (*used_ring).idx.load(Ordering::Acquire) & (QUEUE_SIZE as u16 - 1);
@@ -961,6 +967,8 @@
(*used_ring).ring[next_slot as usize].id = head_descriptor_index.into();
(*used_ring).ring[next_slot as usize].len = (input_length + output.len()) as u32;
(*used_ring).idx.fetch_add(1, Ordering::AcqRel);
+
+ true
}
}
diff --git a/crates/virtio-drivers/src/queue/owning.rs b/crates/virtio-drivers/src/queue/owning.rs
new file mode 100644
index 0000000..9a0e17d
--- /dev/null
+++ b/crates/virtio-drivers/src/queue/owning.rs
@@ -0,0 +1,154 @@
+use super::VirtQueue;
+use crate::{transport::Transport, Error, Hal, Result};
+use alloc::boxed::Box;
+use core::convert::TryInto;
+use core::ptr::{null_mut, NonNull};
+use zerocopy::FromZeroes;
+
+/// A wrapper around [`Queue`] that owns all the buffers that are passed to the queue.
+#[derive(Debug)]
+pub struct OwningQueue<H: Hal, const SIZE: usize, const BUFFER_SIZE: usize> {
+ queue: VirtQueue<H, SIZE>,
+ buffers: [NonNull<[u8; BUFFER_SIZE]>; SIZE],
+}
+
+impl<H: Hal, const SIZE: usize, const BUFFER_SIZE: usize> OwningQueue<H, SIZE, BUFFER_SIZE> {
+ /// Constructs a new `OwningQueue` wrapping around the given `VirtQueue`.
+ ///
+ /// This will allocate `SIZE` buffers of `BUFFER_SIZE` bytes each and add them to the queue.
+ ///
+ /// The caller is responsible for notifying the device if `should_notify` returns true.
+ pub fn new(mut queue: VirtQueue<H, SIZE>) -> Result<Self> {
+ let mut buffers = [null_mut(); SIZE];
+ for (i, queue_buffer) in buffers.iter_mut().enumerate() {
+ let mut buffer: Box<[u8; BUFFER_SIZE]> = FromZeroes::new_box_zeroed();
+ // SAFETY: The buffer lives as long as the queue, as specified in the function safety
+ // requirement, and we don't access it until it is popped.
+ let token = unsafe { queue.add(&[], &mut [buffer.as_mut_slice()]) }?;
+ assert_eq!(i, token.into());
+ *queue_buffer = Box::into_raw(buffer);
+ }
+ let buffers = buffers.map(|ptr| NonNull::new(ptr).unwrap());
+
+ Ok(Self { queue, buffers })
+ }
+
+ /// Returns whether the driver should notify the device after adding a new buffer to the
+ /// virtqueue.
+ ///
+ /// This will be false if the device has supressed notifications.
+ pub fn should_notify(&self) -> bool {
+ self.queue.should_notify()
+ }
+
+ /// Tells the device whether to send used buffer notifications.
+ pub fn set_dev_notify(&mut self, enable: bool) {
+ self.queue.set_dev_notify(enable);
+ }
+
+ /// Adds the buffer at the given index in `buffers` back to the queue.
+ ///
+ /// Automatically notifies the device if required.
+ ///
+ /// # Safety
+ ///
+ /// The buffer must not currently be in the RX queue, and no other references to it must exist
+ /// between when this method is called and when it is popped from the queue.
+ unsafe fn add_buffer_to_queue(&mut self, index: u16, transport: &mut impl Transport) -> Result {
+ // SAFETY: The buffer lives as long as the queue, and the caller guarantees that it's not
+ // currently in the queue or referred to anywhere else until it is popped.
+ unsafe {
+ let buffer = self
+ .buffers
+ .get_mut(usize::from(index))
+ .ok_or(Error::WrongToken)?
+ .as_mut();
+ let new_token = self.queue.add(&[], &mut [buffer])?;
+ // If the RX buffer somehow gets assigned a different token, then our safety assumptions
+ // are broken and we can't safely continue to do anything with the device.
+ assert_eq!(new_token, index);
+ }
+
+ if self.queue.should_notify() {
+ transport.notify(self.queue.queue_idx);
+ }
+
+ Ok(())
+ }
+
+ fn pop(&mut self) -> Result<Option<(&[u8], u16)>> {
+ let Some(token) = self.queue.peek_used() else {
+ return Ok(None);
+ };
+
+ // SAFETY: The device has told us it has finished using the buffer, and there are no other
+ // references to it.
+ let buffer = unsafe { self.buffers[usize::from(token)].as_mut() };
+ // SAFETY: We maintain a consistent mapping of tokens to buffers, so we pass the same buffer
+ // to `pop_used` as we previously passed to `add` for the token. Once we add the buffer back
+ // to the RX queue then we don't access it again until next time it is popped.
+ let len = unsafe { self.queue.pop_used(token, &[], &mut [buffer])? }
+ .try_into()
+ .unwrap();
+
+ Ok(Some((&buffer[0..len], token)))
+ }
+
+ /// Checks whether there are any buffers which the device has marked as used so the driver
+ /// should now process. If so, passes the first one to the `handle` function and then adds it
+ /// back to the queue.
+ ///
+ /// Returns an error if there is an error accessing the queue or `handler` returns an error.
+ /// Returns `Ok(None)` if there are no pending buffers to handle, or if `handler` returns
+ /// `Ok(None)`.
+ ///
+ /// If `handler` panics then the buffer will not be added back to the queue, so this should be
+ /// avoided.
+ pub fn poll<T>(
+ &mut self,
+ transport: &mut impl Transport,
+ handler: impl FnOnce(&[u8]) -> Result<Option<T>>,
+ ) -> Result<Option<T>> {
+ let Some((buffer, token)) = self.pop()? else {
+ return Ok(None);
+ };
+
+ let result = handler(buffer);
+
+ // SAFETY: The buffer was just popped from the queue so it's not in it, and there won't be
+ // any other references until next time it's popped.
+ unsafe {
+ self.add_buffer_to_queue(token, transport)?;
+ }
+
+ result
+ }
+}
+
+// SAFETY: The `buffers` can be accessed from any thread.
+unsafe impl<H: Hal, const SIZE: usize, const BUFFER_SIZE: usize> Send
+ for OwningQueue<H, SIZE, BUFFER_SIZE>
+where
+ VirtQueue<H, SIZE>: Send,
+{
+}
+
+// SAFETY: An `&OwningQueue` only allows calling `should_notify`.
+unsafe impl<H: Hal, const SIZE: usize, const BUFFER_SIZE: usize> Sync
+ for OwningQueue<H, SIZE, BUFFER_SIZE>
+where
+ VirtQueue<H, SIZE>: Sync,
+{
+}
+
+impl<H: Hal, const SIZE: usize, const BUFFER_SIZE: usize> Drop
+ for OwningQueue<H, SIZE, BUFFER_SIZE>
+{
+ fn drop(&mut self) {
+ for buffer in self.buffers {
+ // Safe because we obtained the buffer pointer from Box::into_raw, and it won't be used
+ // anywhere else after the queue is destroyed.
+ unsafe { drop(Box::from_raw(buffer.as_ptr())) };
+ }
+ }
+}
diff --git a/crates/virtio-drivers/src/transport/fake.rs b/crates/virtio-drivers/src/transport/fake.rs
index 6ab61fc..77ed8f9 100644
--- a/crates/virtio-drivers/src/transport/fake.rs
+++ b/crates/virtio-drivers/src/transport/fake.rs
@@ -122,7 +122,7 @@
pub fn write_to_queue<const QUEUE_SIZE: usize>(&mut self, queue_index: u16, data: &[u8]) {
let queue = &self.queues[queue_index as usize];
assert_ne!(queue.descriptors, 0);
- fake_read_write_queue(
+ assert!(fake_read_write_queue(
queue.descriptors as *const [Descriptor; QUEUE_SIZE],
queue.driver_area as *const u8,
queue.device_area as *mut u8,
@@ -130,7 +130,7 @@
assert_eq!(input, Vec::new());
data.to_owned()
},
- );
+ ));
}
/// Simulates the device reading from the given queue.
@@ -145,7 +145,7 @@
let mut ret = None;
// Read data from the queue but don't write any response.
- fake_read_write_queue(
+ assert!(fake_read_write_queue(
queue.descriptors as *const [Descriptor; QUEUE_SIZE],
queue.driver_area as *const u8,
queue.device_area as *mut u8,
@@ -153,7 +153,7 @@
ret = Some(input);
Vec::new()
},
- );
+ ));
ret.unwrap()
}
@@ -161,11 +161,14 @@
/// Simulates the device reading data from the given queue and then writing a response back.
///
/// The fake device always uses descriptors in order.
+ ///
+ /// Returns true if a descriptor chain was available and processed, or false if no descriptors were
+ /// available.
pub fn read_write_queue<const QUEUE_SIZE: usize>(
&mut self,
queue_index: u16,
handler: impl FnOnce(Vec<u8>) -> Vec<u8>,
- ) {
+ ) -> bool {
let queue = &self.queues[queue_index as usize];
assert_ne!(queue.descriptors, 0);
fake_read_write_queue(
@@ -178,13 +181,20 @@
/// Waits until the given queue is notified.
pub fn wait_until_queue_notified(state: &Mutex<Self>, queue_index: u16) {
- while !state.lock().unwrap().queues[usize::from(queue_index)]
- .notified
- .swap(false, Ordering::SeqCst)
- {
+ while !Self::poll_queue_notified(state, queue_index) {
thread::sleep(Duration::from_millis(10));
}
}
+
+ /// Checks if the given queue has been notified.
+ ///
+ /// If it has, returns true and resets the status so this will return false until it is notified
+ /// again.
+ pub fn poll_queue_notified(state: &Mutex<Self>, queue_index: u16) -> bool {
+ state.lock().unwrap().queues[usize::from(queue_index)]
+ .notified
+ .swap(false, Ordering::SeqCst)
+ }
}
#[derive(Debug, Default)]
diff --git a/crates/virtio-drivers/src/transport/mod.rs b/crates/virtio-drivers/src/transport/mod.rs
index f6e9eae..6923475 100644
--- a/crates/virtio-drivers/src/transport/mod.rs
+++ b/crates/virtio-drivers/src/transport/mod.rs
@@ -160,6 +160,7 @@
Pstore = 22,
IOMMU = 23,
Memory = 24,
+ Sound = 25,
}
impl From<u32> for DeviceType {
@@ -187,6 +188,7 @@
22 => DeviceType::Pstore,
23 => DeviceType::IOMMU,
24 => DeviceType::Memory,
+ 25 => DeviceType::Sound,
_ => DeviceType::Invalid,
}
}
diff --git a/crates/virtio-drivers/src/transport/pci.rs b/crates/virtio-drivers/src/transport/pci.rs
index a987a9c..d6afa03 100644
--- a/crates/virtio-drivers/src/transport/pci.rs
+++ b/crates/virtio-drivers/src/transport/pci.rs
@@ -525,11 +525,12 @@
#[test]
fn offset_device_ids() {
+ assert_eq!(device_type(0x1040), DeviceType::Invalid);
assert_eq!(device_type(0x1045), DeviceType::MemoryBalloon);
assert_eq!(device_type(0x1049), DeviceType::_9P);
assert_eq!(device_type(0x1058), DeviceType::Memory);
- assert_eq!(device_type(0x1040), DeviceType::Invalid);
- assert_eq!(device_type(0x1059), DeviceType::Invalid);
+ assert_eq!(device_type(0x1059), DeviceType::Sound);
+ assert_eq!(device_type(0x1060), DeviceType::Invalid);
}
#[test]
diff --git a/crates/virtio-drivers/src/transport/pci/bus.rs b/crates/virtio-drivers/src/transport/pci/bus.rs
index 146a7d6..0682680 100644
--- a/crates/virtio-drivers/src/transport/pci/bus.rs
+++ b/crates/virtio-drivers/src/transport/pci/bus.rs
@@ -2,6 +2,7 @@
use bitflags::bitflags;
use core::{
+ array,
convert::TryFrom,
fmt::{self, Display, Formatter},
};
@@ -258,6 +259,22 @@
}
}
+ /// Returns information about all the given device function's BARs.
+ pub fn bars(
+ &mut self,
+ device_function: DeviceFunction,
+ ) -> Result<[Option<BarInfo>; 6], PciError> {
+ let mut bars = array::from_fn(|_| None);
+ let mut bar_index = 0;
+ while bar_index < 6 {
+ let info = self.bar_info(device_function, bar_index)?;
+ let takes_two_entries = info.takes_two_entries();
+ bars[usize::from(bar_index)] = Some(info);
+ bar_index += if takes_two_entries { 2 } else { 1 };
+ }
+ Ok(bars)
+ }
+
/// Gets information about the given BAR of the given device function.
pub fn bar_info(
&mut self,
diff --git a/pseudo_crate/Cargo.lock b/pseudo_crate/Cargo.lock
index 2bbaf94..45cde80 100644
--- a/pseudo_crate/Cargo.lock
+++ b/pseudo_crate/Cargo.lock
@@ -5777,11 +5777,12 @@
[[package]]
name = "virtio-drivers"
-version = "0.7.4"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa40e09453618c7a927c08c5a990497a2954da7c2aaa6c65e0d4f0fc975f6114"
+checksum = "d6a39747311dabb3d37807037ed1c3c38d39f99198d091b5b79ecd5c8d82f799"
dependencies = [
"bitflags 2.8.0",
+ "enumn",
"log",
"zerocopy 0.7.35",
]
diff --git a/pseudo_crate/Cargo.toml b/pseudo_crate/Cargo.toml
index b2c0a87..f858156 100644
--- a/pseudo_crate/Cargo.toml
+++ b/pseudo_crate/Cargo.toml
@@ -370,7 +370,7 @@
vhost = "=0.8.1"
vhost-user-backend = "=0.10.1"
virtio-bindings = "=0.2.2"
-virtio-drivers = "=0.7.4"
+virtio-drivers = "=0.7.5"
virtio-queue = "=0.12.0"
virtio-vsock = "=0.6.0"
vm-memory = "=0.12.2"