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"