From 0a78103b4110040b365c8d6222ba15c4d61b7f9d Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Thu, 30 Mar 2023 18:47:15 +0200 Subject: [PATCH 01/44] sound: initial commit --- Cargo.lock | 16 +++ Cargo.toml | 1 + README.md | 1 + crates/sound/Cargo.toml | 24 +++++ crates/sound/README.md | 14 +++ crates/sound/src/main.rs | 103 ++++++++++++++++++ crates/sound/src/vhu_sound.rs | 191 ++++++++++++++++++++++++++++++++++ 7 files changed, 350 insertions(+) create mode 100644 crates/sound/Cargo.toml create mode 100644 crates/sound/README.md create mode 100644 crates/sound/src/main.rs create mode 100644 crates/sound/src/vhu_sound.rs diff --git a/Cargo.lock b/Cargo.lock index 9f8e2ca..b22025e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -864,6 +864,22 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "vhost-user-sound" +version = "0.1.0" +dependencies = [ + "clap", + "env_logger", + "log", + "serial_test", + "thiserror", + "vhost", + "vhost-user-backend", + "virtio-bindings 0.2.0", + "vm-memory", + "vmm-sys-util", +] + [[package]] name = "vhost-user-vsock" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 4d1a538..aee431d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,6 @@ members = [ "crates/gpio", "crates/i2c", "crates/rng", + "crates/sound", "crates/vsock", ] diff --git a/README.md b/README.md index 06673f7..443bc97 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Here is the list of device backends that we support: - [GPIO](https://github.com/rust-vmm/vhost-device/blob/main/crates/gpio/README.md) - [I2C](https://github.com/rust-vmm/vhost-device/blob/main/crates/i2c/README.md) - [RNG](https://github.com/rust-vmm/vhost-device/blob/main/crates/rng/README.md) +- [Sound](https://github.com/rust-vmm/vhost-device/blob/main/crates/sound/README.md) - [VSOCK](https://github.com/rust-vmm/vhost-device/blob/main/crates/vsock/README.md) ## Testing and Code Coverage diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml new file mode 100644 index 0000000..c387a36 --- /dev/null +++ b/crates/sound/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "vhost-user-sound" +version = "0.1.0" +authors = ["Stefano Garzarella "] +description = "A virtio-sound device using the vhost-user protocol." +repository = "https://github.com/rust-vmm/vhost-device" +readme = "README.md" +keywords = ["vhost", "sound", "virtio-sound"] +license = "Apache-2.0 OR BSD-3-Clause" +edition = "2018" + +[dependencies] +clap = { version = "4.1", features = ["derive"] } +env_logger = "0.10" +log = "0.4" +thiserror = "1.0" +vhost = { version = "0.6", features = ["vhost-user-slave"] } +vhost-user-backend = "0.8" +virtio-bindings = "0.2" +vm-memory = "0.10" +vmm-sys-util = "0.11" + +[dev-dependencies] +serial_test = "1.0" diff --git a/crates/sound/README.md b/crates/sound/README.md new file mode 100644 index 0000000..ea2fbce --- /dev/null +++ b/crates/sound/README.md @@ -0,0 +1,14 @@ +# vhost-user-sound + +## Design + +## Usage + +## Working example + +## License + +This project is licensed under either of + +- [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0 +- [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause) diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs new file mode 100644 index 0000000..8fb8c43 --- /dev/null +++ b/crates/sound/src/main.rs @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +mod vhu_sound; + +use std::{ + convert::TryFrom, + sync::{Arc, RwLock}, +}; + +use clap::Parser; +use log::{info, warn}; +use vhost::{vhost_user, vhost_user::Listener}; +use vhost_user_backend::VhostUserDaemon; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; + +use crate::vhu_sound::{Error, Result, SoundConfig, VhostUserSoundBackend}; + +#[derive(Parser, Debug)] +#[clap(version, about, long_about = None)] +struct SoundArgs { + /// vhost-user Unix domain socket path. + #[clap(long)] + socket: String, +} + +impl TryFrom for SoundConfig { + type Error = Error; + + fn try_from(cmd_args: SoundArgs) -> Result { + let socket = cmd_args.socket.trim().to_string(); + + Ok(SoundConfig::new(socket)) + } +} + +/// This is the public API through which an external program starts the +/// vhost-user-sound backend server. +pub(crate) fn start_backend_server(config: SoundConfig) { + loop { + let backend = Arc::new(RwLock::new( + VhostUserSoundBackend::new(config.clone()).unwrap(), + )); + + let listener = Listener::new(config.get_socket_path(), true).unwrap(); + + let mut daemon = VhostUserDaemon::new( + String::from("vhost-user-sound"), + backend.clone(), + GuestMemoryAtomic::new(GuestMemoryMmap::new()), + ) + .unwrap(); + + daemon.start(listener).unwrap(); + + match daemon.wait() { + Ok(()) => { + info!("Stopping cleanly"); + } + Err(vhost_user_backend::Error::HandleRequest(vhost_user::Error::PartialMessage)) => { + info!("vhost-user connection closed with partial message. If the VM is shutting down, this is expected behavior; otherwise, it might be a bug."); + } + Err(e) => { + warn!("Error running daemon: {:?}", e); + } + } + + // No matter the result, we need to shut down the worker thread. + backend.read().unwrap().exit_event.write(1).unwrap(); + } +} + +fn main() { + env_logger::init(); + + let config = SoundConfig::try_from(SoundArgs::parse()).unwrap(); + start_backend_server(config); +} + +#[cfg(test)] +mod tests { + use super::*; + use serial_test::serial; + + impl SoundArgs { + fn from_args(socket: &str) -> Self { + SoundArgs { + socket: socket.to_string(), + } + } + } + + #[test] + #[serial] + fn test_vsock_config_setup() { + let args = SoundArgs::from_args("/tmp/vhost-sound.socket"); + + let config = SoundConfig::try_from(args); + assert!(config.is_ok()); + + let config = config.unwrap(); + assert_eq!(config.get_socket_path(), "/tmp/vhost-sound.socket"); + } +} diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs new file mode 100644 index 0000000..77d6ae4 --- /dev/null +++ b/crates/sound/src/vhu_sound.rs @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use std::{ + io::{self, Result as IoResult}, + u16, u32, u64, u8, +}; +use thiserror::Error as ThisError; +use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; +use vhost_user_backend::{VhostUserBackendMut, VringRwLock}; +use virtio_bindings::bindings::{ + virtio_config::VIRTIO_F_NOTIFY_ON_EMPTY, virtio_config::VIRTIO_F_VERSION_1, + virtio_ring::VIRTIO_RING_F_EVENT_IDX, virtio_ring::VIRTIO_RING_F_INDIRECT_DESC, +}; +use vm_memory::{ByteValued, GuestMemoryAtomic, GuestMemoryMmap, Le32}; +use vmm_sys_util::{ + epoll::EventSet, + eventfd::{EventFd, EFD_NONBLOCK}, +}; + +const CONTROL_Q: u16 = 0; +const EVENT_Q: u16 = 1; +const TX_Q: u16 = 2; +const RX_Q: u16 = 3; + +pub(crate) type Result = std::result::Result; + +/// Custom error types +#[derive(Debug, ThisError)] +pub(crate) enum Error { + #[error("Failed to handle event other than EPOLLIN event")] + HandleEventNotEpollIn, + #[error("Failed to handle unknown event")] + HandleUnknownEvent, + #[error("Failed to create a new EventFd")] + EventFdCreate(std::io::Error), +} + +impl std::convert::From for std::io::Error { + fn from(e: Error) -> Self { + std::io::Error::new(io::ErrorKind::Other, e) + } +} + +#[derive(Debug, Clone)] +/// This structure is the public API through which an external program +/// is allowed to configure the backend. +pub(crate) struct SoundConfig { + socket: String, +} + +impl SoundConfig { + /// Create a new instance of the SoundConfig struct, containing the + /// parameters to be fed into the sound-backend server. + pub fn new(socket: String) -> Self { + Self { socket } + } + + /// Return the path of the unix domain socket which is listening to + /// requests from the guest. + pub fn get_socket_path(&self) -> String { + String::from(&self.socket) + } +} + +/// Virtio Sound Configuration +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +struct VirtioSoundConfig { + /// total number of all available jacks + jacks: Le32, + /// total number of all available PCM streams + streams: Le32, + /// total number of all available channel maps + chmpas: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundConfig {} + +/// Virtio Sound Request / Response common header +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +struct VirtioSoundHeader { + /// request type / response status + code: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundHeader {} + +pub(crate) struct VhostUserSoundBackend { + config: VirtioSoundConfig, + queues_per_thread: Vec, + event_idx: bool, + pub(crate) exit_event: EventFd, + mem: Option>, +} + +impl VhostUserSoundBackend { + pub fn new(_config: SoundConfig) -> Result { + let queues_per_thread = vec![0b1111]; + + Ok(Self { + config: VirtioSoundConfig { + jacks: 0.into(), + streams: 1.into(), + chmpas: 0.into(), + }, + queues_per_thread, + event_idx: false, + exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, + mem: None, + }) + } +} + +impl VhostUserBackendMut for VhostUserSoundBackend { + fn num_queues(&self) -> usize { + 4 + } + + fn max_queue_size(&self) -> usize { + 256 + } + + fn features(&self) -> u64 { + 1 << VIRTIO_F_VERSION_1 + | 1 << VIRTIO_F_NOTIFY_ON_EMPTY + | 1 << VIRTIO_RING_F_INDIRECT_DESC + | 1 << VIRTIO_RING_F_EVENT_IDX + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + VhostUserProtocolFeatures::CONFIG + } + + fn set_event_idx(&mut self, enabled: bool) { + self.event_idx = enabled; + } + + fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()> { + self.mem = Some(mem); + Ok(()) + } + + fn handle_event( + &mut self, + device_event: u16, + evset: EventSet, + _vrings: &[VringRwLock], + _thread_id: usize, + ) -> IoResult { + if evset != EventSet::IN { + return Err(Error::HandleEventNotEpollIn.into()); + } + + match device_event { + CONTROL_Q => {} + EVENT_Q => {} + TX_Q => {} + RX_Q => {} + _ => { + return Err(Error::HandleUnknownEvent.into()); + } + } + + Ok(false) + } + + fn get_config(&self, offset: u32, size: u32) -> Vec { + let offset = offset as usize; + let size = size as usize; + + let buf = self.config.as_slice(); + + if offset + size > buf.len() { + return Vec::new(); + } + + buf[offset..offset + size].to_vec() + } + + fn queues_per_thread(&self) -> Vec { + self.queues_per_thread.clone() + } + + fn exit_event(&self, _thread_index: usize) -> Option { + self.exit_event.try_clone().ok() + } +} From a1b49a76a74a5e3a34bd451dc582f16e1a0cb12a Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Thu, 30 Mar 2023 20:23:51 +0200 Subject: [PATCH 02/44] sound: use interior mutability --- crates/sound/src/main.rs | 11 ++--- crates/sound/src/vhu_sound.rs | 77 ++++++++++++++++++++++++----------- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs index 8fb8c43..3b52ab4 100644 --- a/crates/sound/src/main.rs +++ b/crates/sound/src/main.rs @@ -2,10 +2,7 @@ mod vhu_sound; -use std::{ - convert::TryFrom, - sync::{Arc, RwLock}, -}; +use std::{convert::TryFrom, sync::Arc}; use clap::Parser; use log::{info, warn}; @@ -37,9 +34,7 @@ impl TryFrom for SoundConfig { /// vhost-user-sound backend server. pub(crate) fn start_backend_server(config: SoundConfig) { loop { - let backend = Arc::new(RwLock::new( - VhostUserSoundBackend::new(config.clone()).unwrap(), - )); + let backend = Arc::new(VhostUserSoundBackend::new(config.clone()).unwrap()); let listener = Listener::new(config.get_socket_path(), true).unwrap(); @@ -65,7 +60,7 @@ pub(crate) fn start_backend_server(config: SoundConfig) { } // No matter the result, we need to shut down the worker thread. - backend.read().unwrap().exit_event.write(1).unwrap(); + backend.exit_event.write(1).unwrap(); } } diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index 77d6ae4..b8a7bbf 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -2,11 +2,12 @@ use std::{ io::{self, Result as IoResult}, + sync::RwLock, u16, u32, u64, u8, }; use thiserror::Error as ThisError; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; -use vhost_user_backend::{VhostUserBackendMut, VringRwLock}; +use vhost_user_backend::{VhostUserBackend, VringRwLock}; use virtio_bindings::bindings::{ virtio_config::VIRTIO_F_NOTIFY_ON_EMPTY, virtio_config::VIRTIO_F_VERSION_1, virtio_ring::VIRTIO_RING_F_EVENT_IDX, virtio_ring::VIRTIO_RING_F_INDIRECT_DESC, @@ -88,33 +89,69 @@ struct VirtioSoundHeader { // reading its content from byte array. unsafe impl ByteValued for VirtioSoundHeader {} +struct VhostUserSoundThread { + mem: Option>, + event_idx: bool, +} + +impl VhostUserSoundThread { + pub fn new() -> Result { + Ok(VhostUserSoundThread { + event_idx: false, + mem: None, + }) + } + + fn set_event_idx(&mut self, enabled: bool) { + self.event_idx = enabled; + } + + fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()> { + self.mem = Some(mem); + Ok(()) + } + + fn handle_event(&self, device_event: u16, _vrings: &[VringRwLock]) -> IoResult { + match device_event { + CONTROL_Q => {} + EVENT_Q => {} + TX_Q => {} + RX_Q => {} + _ => { + return Err(Error::HandleUnknownEvent.into()); + } + } + + Ok(false) + } +} + pub(crate) struct VhostUserSoundBackend { + thread: RwLock, config: VirtioSoundConfig, queues_per_thread: Vec, - event_idx: bool, pub(crate) exit_event: EventFd, - mem: Option>, } impl VhostUserSoundBackend { pub fn new(_config: SoundConfig) -> Result { let queues_per_thread = vec![0b1111]; + let thread = RwLock::new(VhostUserSoundThread::new()?); Ok(Self { + thread, config: VirtioSoundConfig { jacks: 0.into(), streams: 1.into(), chmpas: 0.into(), }, queues_per_thread, - event_idx: false, exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, - mem: None, }) } } -impl VhostUserBackendMut for VhostUserSoundBackend { +impl VhostUserBackend for VhostUserSoundBackend { fn num_queues(&self) -> usize { 4 } @@ -135,37 +172,29 @@ impl VhostUserBackendMut for VhostUserSoundBackend { VhostUserProtocolFeatures::CONFIG } - fn set_event_idx(&mut self, enabled: bool) { - self.event_idx = enabled; + fn set_event_idx(&self, enabled: bool) { + self.thread.write().unwrap().set_event_idx(enabled); } - fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()> { - self.mem = Some(mem); - Ok(()) + fn update_memory(&self, mem: GuestMemoryAtomic) -> IoResult<()> { + self.thread.write().unwrap().update_memory(mem) } fn handle_event( - &mut self, + &self, device_event: u16, evset: EventSet, - _vrings: &[VringRwLock], + vrings: &[VringRwLock], _thread_id: usize, ) -> IoResult { if evset != EventSet::IN { return Err(Error::HandleEventNotEpollIn.into()); } - match device_event { - CONTROL_Q => {} - EVENT_Q => {} - TX_Q => {} - RX_Q => {} - _ => { - return Err(Error::HandleUnknownEvent.into()); - } - } - - Ok(false) + self.thread + .read() + .unwrap() + .handle_event(device_event, vrings) } fn get_config(&self, offset: u32, size: u32) -> Vec { From 3ac6c160c1544c27ed694269afa67ccb7cc6bbc1 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Thu, 30 Mar 2023 20:56:42 +0200 Subject: [PATCH 03/44] sound: use thread trait --- crates/sound/src/vhu_sound.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index b8a7bbf..8e9dbb5 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -89,18 +89,30 @@ struct VirtioSoundHeader { // reading its content from byte array. unsafe impl ByteValued for VirtioSoundHeader {} -struct VhostUserSoundThread { +trait VhostUserSoundThread { + fn queue_mask(&self) -> u64; + fn set_event_idx(&mut self, enabled: bool); + fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()>; + fn handle_event(&self, device_event: u16, vrings: &[VringRwLock]) -> IoResult; +} +struct VhostUserSoundSingleThread { mem: Option>, event_idx: bool, } -impl VhostUserSoundThread { +impl VhostUserSoundSingleThread { pub fn new() -> Result { - Ok(VhostUserSoundThread { + Ok(VhostUserSoundSingleThread { event_idx: false, mem: None, }) } +} + +impl VhostUserSoundThread for VhostUserSoundSingleThread { + fn queue_mask(&self) -> u64 { + 0xffff_ffff + } fn set_event_idx(&mut self, enabled: bool) { self.event_idx = enabled; @@ -127,7 +139,7 @@ impl VhostUserSoundThread { } pub(crate) struct VhostUserSoundBackend { - thread: RwLock, + thread: RwLock>, config: VirtioSoundConfig, queues_per_thread: Vec, pub(crate) exit_event: EventFd, @@ -136,10 +148,10 @@ pub(crate) struct VhostUserSoundBackend { impl VhostUserSoundBackend { pub fn new(_config: SoundConfig) -> Result { let queues_per_thread = vec![0b1111]; - let thread = RwLock::new(VhostUserSoundThread::new()?); + let thread = Box::new(VhostUserSoundSingleThread::new()?); Ok(Self { - thread, + thread: RwLock::new(thread), config: VirtioSoundConfig { jacks: 0.into(), streams: 1.into(), From 72c31053ffca5b2d6e2ec05bc342c8adbf36d616 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Fri, 31 Mar 2023 12:05:58 +0200 Subject: [PATCH 04/44] Revert "sound: use thread trait" This reverts commit 3ac6c160c1544c27ed694269afa67ccb7cc6bbc1. --- crates/sound/src/vhu_sound.rs | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index 8e9dbb5..b8a7bbf 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -89,30 +89,18 @@ struct VirtioSoundHeader { // reading its content from byte array. unsafe impl ByteValued for VirtioSoundHeader {} -trait VhostUserSoundThread { - fn queue_mask(&self) -> u64; - fn set_event_idx(&mut self, enabled: bool); - fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()>; - fn handle_event(&self, device_event: u16, vrings: &[VringRwLock]) -> IoResult; -} -struct VhostUserSoundSingleThread { +struct VhostUserSoundThread { mem: Option>, event_idx: bool, } -impl VhostUserSoundSingleThread { +impl VhostUserSoundThread { pub fn new() -> Result { - Ok(VhostUserSoundSingleThread { + Ok(VhostUserSoundThread { event_idx: false, mem: None, }) } -} - -impl VhostUserSoundThread for VhostUserSoundSingleThread { - fn queue_mask(&self) -> u64 { - 0xffff_ffff - } fn set_event_idx(&mut self, enabled: bool) { self.event_idx = enabled; @@ -139,7 +127,7 @@ impl VhostUserSoundThread for VhostUserSoundSingleThread { } pub(crate) struct VhostUserSoundBackend { - thread: RwLock>, + thread: RwLock, config: VirtioSoundConfig, queues_per_thread: Vec, pub(crate) exit_event: EventFd, @@ -148,10 +136,10 @@ pub(crate) struct VhostUserSoundBackend { impl VhostUserSoundBackend { pub fn new(_config: SoundConfig) -> Result { let queues_per_thread = vec![0b1111]; - let thread = Box::new(VhostUserSoundSingleThread::new()?); + let thread = RwLock::new(VhostUserSoundThread::new()?); Ok(Self { - thread: RwLock::new(thread), + thread, config: VirtioSoundConfig { jacks: 0.into(), streams: 1.into(), From 8d3ebf35e17a2da7eeddf4c0b657ae4e6e8d8486 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Fri, 31 Mar 2023 15:49:46 +0200 Subject: [PATCH 05/44] sound: support multiple threads --- crates/sound/src/main.rs | 2 +- crates/sound/src/vhu_sound.rs | 126 +++++++++++++++++++++++++--------- 2 files changed, 94 insertions(+), 34 deletions(-) diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs index 3b52ab4..408eef9 100644 --- a/crates/sound/src/main.rs +++ b/crates/sound/src/main.rs @@ -26,7 +26,7 @@ impl TryFrom for SoundConfig { fn try_from(cmd_args: SoundArgs) -> Result { let socket = cmd_args.socket.trim().to_string(); - Ok(SoundConfig::new(socket)) + Ok(SoundConfig::new(socket, false)) } } diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index b8a7bbf..24643bc 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -18,10 +18,11 @@ use vmm_sys_util::{ eventfd::{EventFd, EFD_NONBLOCK}, }; -const CONTROL_Q: u16 = 0; -const EVENT_Q: u16 = 1; -const TX_Q: u16 = 2; -const RX_Q: u16 = 3; +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 NUM_QUEUES: u16 = 4; pub(crate) type Result = std::result::Result; @@ -46,14 +47,19 @@ impl std::convert::From for std::io::Error { /// This structure is the public API through which an external program /// is allowed to configure the backend. pub(crate) struct SoundConfig { + /// vhost-user Unix domain socket socket: String, + multi_thread: bool, } impl SoundConfig { /// Create a new instance of the SoundConfig struct, containing the /// parameters to be fed into the sound-backend server. - pub fn new(socket: String) -> Self { - Self { socket } + pub fn new(socket: String, multi_thread: bool) -> Self { + Self { + socket, + multi_thread, + } } /// Return the path of the unix domain socket which is listening to @@ -92,16 +98,30 @@ unsafe impl ByteValued for VirtioSoundHeader {} struct VhostUserSoundThread { mem: Option>, event_idx: bool, + queue_indexes: Vec, } impl VhostUserSoundThread { - pub fn new() -> Result { + pub fn new(mut queue_indexes: Vec) -> Result { + queue_indexes.sort(); + Ok(VhostUserSoundThread { event_idx: false, mem: None, + queue_indexes, }) } + fn queues_per_thread(&self) -> u64 { + let mut queues_per_thread = 0u64; + + for idx in self.queue_indexes.iter() { + queues_per_thread |= 1u64 << idx + } + + queues_per_thread + } + fn set_event_idx(&mut self, enabled: bool) { self.event_idx = enabled; } @@ -111,41 +131,69 @@ impl VhostUserSoundThread { Ok(()) } - fn handle_event(&self, device_event: u16, _vrings: &[VringRwLock]) -> IoResult { - match device_event { - CONTROL_Q => {} - EVENT_Q => {} - TX_Q => {} - RX_Q => {} - _ => { - return Err(Error::HandleUnknownEvent.into()); - } - } + fn handle_event(&self, device_event: u16, vrings: &[VringRwLock]) -> IoResult { + let vring = &vrings[device_event as usize]; + let queue_idx = self.queue_indexes[device_event as usize]; + match queue_idx { + CONTROL_QUEUE_IDX => self.process_control(vring), + EVENT_QUEUE_IDX => self.process_event(vring), + TX_QUEUE_IDX => self.process_tx(vring), + RX_QUEUE_IDX => self.process_rx(vring), + _ => Err(Error::HandleUnknownEvent.into()), + } + } + + fn process_control(&self, _vring: &VringRwLock) -> IoResult { + Ok(false) + } + + fn process_event(&self, _vring: &VringRwLock) -> IoResult { + Ok(false) + } + + fn process_tx(&self, _vring: &VringRwLock) -> IoResult { + Ok(false) + } + + fn process_rx(&self, _vring: &VringRwLock) -> IoResult { Ok(false) } } pub(crate) struct VhostUserSoundBackend { - thread: RwLock, - config: VirtioSoundConfig, - queues_per_thread: Vec, + threads: Vec>, + virtio_cfg: VirtioSoundConfig, pub(crate) exit_event: EventFd, } impl VhostUserSoundBackend { - pub fn new(_config: SoundConfig) -> Result { - let queues_per_thread = vec![0b1111]; - let thread = RwLock::new(VhostUserSoundThread::new()?); + pub fn new(config: SoundConfig) -> Result { + let threads = if config.multi_thread { + vec![ + RwLock::new(VhostUserSoundThread::new(vec![ + CONTROL_QUEUE_IDX, + EVENT_QUEUE_IDX, + ])?), + RwLock::new(VhostUserSoundThread::new(vec![TX_QUEUE_IDX])?), + RwLock::new(VhostUserSoundThread::new(vec![RX_QUEUE_IDX])?), + ] + } else { + vec![RwLock::new(VhostUserSoundThread::new(vec![ + CONTROL_QUEUE_IDX, + EVENT_QUEUE_IDX, + TX_QUEUE_IDX, + RX_QUEUE_IDX, + ])?)] + }; Ok(Self { - thread, - config: VirtioSoundConfig { + threads, + virtio_cfg: VirtioSoundConfig { jacks: 0.into(), streams: 1.into(), chmpas: 0.into(), }, - queues_per_thread, exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, }) } @@ -153,7 +201,7 @@ impl VhostUserSoundBackend { impl VhostUserBackend for VhostUserSoundBackend { fn num_queues(&self) -> usize { - 4 + NUM_QUEUES as usize } fn max_queue_size(&self) -> usize { @@ -173,11 +221,17 @@ impl VhostUserBackend for VhostUserSoundBackend { } fn set_event_idx(&self, enabled: bool) { - self.thread.write().unwrap().set_event_idx(enabled); + for thread in self.threads.iter() { + thread.write().unwrap().set_event_idx(enabled); + } } fn update_memory(&self, mem: GuestMemoryAtomic) -> IoResult<()> { - self.thread.write().unwrap().update_memory(mem) + for thread in self.threads.iter() { + thread.write().unwrap().update_memory(mem.clone())?; + } + + Ok(()) } fn handle_event( @@ -185,13 +239,13 @@ impl VhostUserBackend for VhostUserSoundBackend { device_event: u16, evset: EventSet, vrings: &[VringRwLock], - _thread_id: usize, + thread_id: usize, ) -> IoResult { if evset != EventSet::IN { return Err(Error::HandleEventNotEpollIn.into()); } - self.thread + self.threads[thread_id] .read() .unwrap() .handle_event(device_event, vrings) @@ -201,7 +255,7 @@ impl VhostUserBackend for VhostUserSoundBackend { let offset = offset as usize; let size = size as usize; - let buf = self.config.as_slice(); + let buf = self.virtio_cfg.as_slice(); if offset + size > buf.len() { return Vec::new(); @@ -211,7 +265,13 @@ impl VhostUserBackend for VhostUserSoundBackend { } fn queues_per_thread(&self) -> Vec { - self.queues_per_thread.clone() + let mut vec = Vec::with_capacity(self.threads.len()); + + for thread in self.threads.iter() { + vec.push(thread.read().unwrap().queues_per_thread()) + } + + vec } fn exit_event(&self, _thread_index: usize) -> Option { From b81c55fbe2a375804b5047a836c572097f0fb730 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Fri, 31 Mar 2023 16:45:41 +0200 Subject: [PATCH 06/44] sound: code refactoring --- crates/sound/src/main.rs | 49 +++--------------- crates/sound/src/vhu_sound.rs | 97 ++++------------------------------- 2 files changed, 17 insertions(+), 129 deletions(-) diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs index 408eef9..542fdb6 100644 --- a/crates/sound/src/main.rs +++ b/crates/sound/src/main.rs @@ -1,16 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -mod vhu_sound; - -use std::{convert::TryFrom, sync::Arc}; +use std::convert::TryFrom; use clap::Parser; -use log::{info, warn}; -use vhost::{vhost_user, vhost_user::Listener}; -use vhost_user_backend::VhostUserDaemon; -use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; -use crate::vhu_sound::{Error, Result, SoundConfig, VhostUserSoundBackend}; +use vhost_user_sound::{start_backend_server, Error, Result, SoundConfig}; #[derive(Parser, Debug)] #[clap(version, about, long_about = None)] @@ -30,45 +24,14 @@ impl TryFrom for SoundConfig { } } -/// This is the public API through which an external program starts the -/// vhost-user-sound backend server. -pub(crate) fn start_backend_server(config: SoundConfig) { - loop { - let backend = Arc::new(VhostUserSoundBackend::new(config.clone()).unwrap()); - - let listener = Listener::new(config.get_socket_path(), true).unwrap(); - - let mut daemon = VhostUserDaemon::new( - String::from("vhost-user-sound"), - backend.clone(), - GuestMemoryAtomic::new(GuestMemoryMmap::new()), - ) - .unwrap(); - - daemon.start(listener).unwrap(); - - match daemon.wait() { - Ok(()) => { - info!("Stopping cleanly"); - } - Err(vhost_user_backend::Error::HandleRequest(vhost_user::Error::PartialMessage)) => { - info!("vhost-user connection closed with partial message. If the VM is shutting down, this is expected behavior; otherwise, it might be a bug."); - } - Err(e) => { - warn!("Error running daemon: {:?}", e); - } - } - - // No matter the result, we need to shut down the worker thread. - backend.exit_event.write(1).unwrap(); - } -} - fn main() { env_logger::init(); let config = SoundConfig::try_from(SoundArgs::parse()).unwrap(); - start_backend_server(config); + + loop { + start_backend_server(config.clone()); + } } #[cfg(test)] diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index 24643bc..9a91b68 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -1,100 +1,21 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use std::{ - io::{self, Result as IoResult}, - sync::RwLock, - u16, u32, u64, u8, -}; -use thiserror::Error as ThisError; +use crate::virtio_sound::*; +use crate::{Error, Result, SoundConfig}; + +use std::{io::Result as IoResult, sync::RwLock, u16, u32, u64, u8}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost_user_backend::{VhostUserBackend, VringRwLock}; use virtio_bindings::bindings::{ virtio_config::VIRTIO_F_NOTIFY_ON_EMPTY, virtio_config::VIRTIO_F_VERSION_1, virtio_ring::VIRTIO_RING_F_EVENT_IDX, virtio_ring::VIRTIO_RING_F_INDIRECT_DESC, }; -use vm_memory::{ByteValued, GuestMemoryAtomic, GuestMemoryMmap, Le32}; +use vm_memory::{ByteValued, GuestMemoryAtomic, GuestMemoryMmap}; use vmm_sys_util::{ epoll::EventSet, eventfd::{EventFd, EFD_NONBLOCK}, }; -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 NUM_QUEUES: u16 = 4; - -pub(crate) type Result = std::result::Result; - -/// Custom error types -#[derive(Debug, ThisError)] -pub(crate) enum Error { - #[error("Failed to handle event other than EPOLLIN event")] - HandleEventNotEpollIn, - #[error("Failed to handle unknown event")] - HandleUnknownEvent, - #[error("Failed to create a new EventFd")] - EventFdCreate(std::io::Error), -} - -impl std::convert::From for std::io::Error { - fn from(e: Error) -> Self { - std::io::Error::new(io::ErrorKind::Other, e) - } -} - -#[derive(Debug, Clone)] -/// This structure is the public API through which an external program -/// is allowed to configure the backend. -pub(crate) struct SoundConfig { - /// vhost-user Unix domain socket - socket: String, - multi_thread: bool, -} - -impl SoundConfig { - /// Create a new instance of the SoundConfig struct, containing the - /// parameters to be fed into the sound-backend server. - pub fn new(socket: String, multi_thread: bool) -> Self { - Self { - socket, - multi_thread, - } - } - - /// Return the path of the unix domain socket which is listening to - /// requests from the guest. - pub fn get_socket_path(&self) -> String { - String::from(&self.socket) - } -} - -/// Virtio Sound Configuration -#[derive(Copy, Clone, Debug, Default, PartialEq)] -#[repr(C)] -struct VirtioSoundConfig { - /// total number of all available jacks - jacks: Le32, - /// total number of all available PCM streams - streams: Le32, - /// total number of all available channel maps - chmpas: Le32, -} -// SAFETY: The layout of the structure is fixed and can be initialized by -// reading its content from byte array. -unsafe impl ByteValued for VirtioSoundConfig {} - -/// Virtio Sound Request / Response common header -#[derive(Copy, Clone, Debug, Default, PartialEq)] -#[repr(C)] -struct VirtioSoundHeader { - /// request type / response status - code: Le32, -} -// SAFETY: The layout of the structure is fixed and can be initialized by -// reading its content from byte array. -unsafe impl ByteValued for VirtioSoundHeader {} - struct VhostUserSoundThread { mem: Option>, event_idx: bool, @@ -161,10 +82,10 @@ impl VhostUserSoundThread { } } -pub(crate) struct VhostUserSoundBackend { +pub struct VhostUserSoundBackend { threads: Vec>, virtio_cfg: VirtioSoundConfig, - pub(crate) exit_event: EventFd, + exit_event: EventFd, } impl VhostUserSoundBackend { @@ -197,6 +118,10 @@ impl VhostUserSoundBackend { exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, }) } + + pub fn send_exit_event(&self) { + self.exit_event.write(1).unwrap(); + } } impl VhostUserBackend for VhostUserSoundBackend { From 2366b767186eca6c1e33d1a0bdfef239810db4f2 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Mon, 3 Apr 2023 14:33:53 +0200 Subject: [PATCH 07/44] sound: add missing files --- crates/sound/src/lib.rs | 92 ++++++++++++++++++++++++ crates/sound/src/virtio_sound.rs | 119 +++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 crates/sound/src/lib.rs create mode 100644 crates/sound/src/virtio_sound.rs diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs new file mode 100644 index 0000000..325266e --- /dev/null +++ b/crates/sound/src/lib.rs @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +mod vhu_sound; +mod virtio_sound; + +use std::io::{Error as IoError, ErrorKind}; +use std::sync::Arc; + +use log::{info, warn}; +use thiserror::Error as ThisError; +use vhost::{vhost_user, vhost_user::Listener}; +use vhost_user_backend::VhostUserDaemon; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; + +use crate::vhu_sound::VhostUserSoundBackend; + +pub type Result = std::result::Result; + +/// Custom error types +#[derive(Debug, ThisError)] +pub enum Error { + #[error("Failed to handle event other than EPOLLIN event")] + HandleEventNotEpollIn, + #[error("Failed to handle unknown event")] + HandleUnknownEvent, + #[error("Failed to create a new EventFd")] + EventFdCreate(IoError), +} + +impl std::convert::From for IoError { + fn from(e: Error) -> Self { + IoError::new(ErrorKind::Other, e) + } +} + +#[derive(Debug, Clone)] +/// This structure is the public API through which an external program +/// is allowed to configure the backend. +pub struct SoundConfig { + /// vhost-user Unix domain socket + socket: String, + /// use multiple threads to hanlde the virtqueues + multi_thread: bool, +} + +impl SoundConfig { + /// Create a new instance of the SoundConfig struct, containing the + /// parameters to be fed into the sound-backend server. + pub fn new(socket: String, multi_thread: bool) -> Self { + Self { + socket, + multi_thread, + } + } + + /// Return the path of the unix domain socket which is listening to + /// requests from the guest. + pub fn get_socket_path(&self) -> String { + String::from(&self.socket) + } +} + +/// This is the public API through which an external program starts the +/// vhost-user-sound backend server. +pub fn start_backend_server(config: SoundConfig) { + let listener = Listener::new(config.get_socket_path(), true).unwrap(); + let backend = Arc::new(VhostUserSoundBackend::new(config).unwrap()); + + let mut daemon = VhostUserDaemon::new( + String::from("vhost-user-sound"), + backend.clone(), + GuestMemoryAtomic::new(GuestMemoryMmap::new()), + ) + .unwrap(); + + daemon.start(listener).unwrap(); + + match daemon.wait() { + Ok(()) => { + info!("Stopping cleanly"); + } + Err(vhost_user_backend::Error::HandleRequest(vhost_user::Error::PartialMessage)) => { + info!("vhost-user connection closed with partial message. If the VM is shutting down, this is expected behavior; otherwise, it might be a bug."); + } + Err(e) => { + warn!("Error running daemon: {:?}", e); + } + } + + // No matter the result, we need to shut down the worker thread. + backend.send_exit_event(); +} diff --git a/crates/sound/src/virtio_sound.rs b/crates/sound/src/virtio_sound.rs new file mode 100644 index 0000000..60f9139 --- /dev/null +++ b/crates/sound/src/virtio_sound.rs @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause +#![allow(dead_code)] //TODO: remove + +use vm_memory::{ByteValued, Le32}; + +// virtqueues + +pub const CONTROL_QUEUE_IDX: u16 = 0; +pub const EVENT_QUEUE_IDX: u16 = 1; +pub const TX_QUEUE_IDX: u16 = 2; +pub const RX_QUEUE_IDX: u16 = 3; +pub const NUM_QUEUES: u16 = 4; + +// jack control request types + +pub const VIRTIO_SND_R_JACK_INFO: u32 = 1; +pub const VIRTIO_SND_R_JACK_REMAP: u32 = 2; + +// PCM control request types + +pub const VIRTIO_SND_R_PCM_INFO: u32 = 0x0100; +pub const VIRTIO_SND_R_PCM_SET_PARAMS: u32 = 0x0101; +pub const VIRTIO_SND_R_PCM_PREPARE: u32 = 0x0102; +pub const VIRTIO_SND_R_PCM_RELEASE: u32 = 0x0103; +pub const VIRTIO_SND_R_PCM_START: u32 = 0x0104; +pub const VIRTIO_SND_R_PCM_STOP: u32 = 0x0105; + +// channel map control request types + +pub const VIRTIO_SND_R_CHMAP_INFO: u32 = 0x0200; + +// jack event types + +pub const VIRTIO_SND_EVT_JACK_CONNECTED: u32 = 0x1000; +pub const VIRTIO_SND_EVT_JACK_DISCONNECTED: u32 = 0x1001; + +// PCM event types + +pub const VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED: u32 = 0x1100; +pub const VIRTIO_SND_EVT_PCM_XRUN: u32 = 0x1101; + +// common status codes + +pub const VIRTIO_SND_S_OK: u32 = 0x8000; +pub const VIRTIO_SND_S_BAD_MSG: u32 = 0x8001; +pub const VIRTIO_SND_S_NOT_SUPP: u32 = 0x8002; +pub const VIRTIO_SND_S_IO_ERR: u32 = 0x8003; + +// device data flow directions + +pub const VIRTIO_SND_D_OUTPUT: u32 = 0; +pub const VIRTIO_SND_D_INPUT: u32 = 1; + +/// Virtio Sound Configuration +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundConfig { + /// total number of all available jacks + pub jacks: Le32, + /// total number of all available PCM streams + pub streams: Le32, + /// total number of all available channel maps + pub chmpas: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundConfig {} + +/// Virtio Sound Request / Response common header +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundHeader { + /// request type / response status + pub code: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundHeader {} + +/// Virtio Sound event notification +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundEvent { + /// PCM stream event type + pub hdr: VirtioSoundHeader, + /// PCM stream identifier from 0 to streams - 1 + pub data: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundEvent {} + +/// Virtio Sound request information about any kind of configuration item +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundQueryInfo { + /// item request type (VIRTIO_SND_R_*_INFO) + pub hdr: VirtioSoundHeader, + /// starting identifier for the item + pub start_id: Le32, + /// number of items for which information is requested + pub cound: Le32, + /// size of the structure containing information for one item + pub size: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundQueryInfo {} + +/// Virtio Sound response common information header +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundInfo { + /// function group node identifier + pub hda_fn_nid: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundInfo {} From 16e927e10e8315ca0b4c7ac298fdc94bfd83fb19 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Thu, 27 Apr 2023 08:39:15 +0200 Subject: [PATCH 08/44] virtio_sound.rs: Added device configuration and common definitions Added device configuration and common definitions from the virtio sound specifications to the header file Signed-off-by: Dorinda Bassey --- crates/sound/src/virtio_sound.rs | 230 ++++++++++++++++++++++++++++++- 1 file changed, 228 insertions(+), 2 deletions(-) diff --git a/crates/sound/src/virtio_sound.rs b/crates/sound/src/virtio_sound.rs index 60f9139..e38ddc7 100644 --- a/crates/sound/src/virtio_sound.rs +++ b/crates/sound/src/virtio_sound.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause #![allow(dead_code)] //TODO: remove -use vm_memory::{ByteValued, Le32}; +use vm_memory::{ByteValued, Le32, Le64}; // virtqueues @@ -51,6 +51,106 @@ pub const VIRTIO_SND_S_IO_ERR: u32 = 0x8003; pub const VIRTIO_SND_D_OUTPUT: u32 = 0; pub const VIRTIO_SND_D_INPUT: u32 = 1; +// supported jack features + +pub const VIRTIO_SND_JACK_F_REMAP: u32 = 0; + +// supported PCM stream features + +pub const VIRTIO_SND_PCM_F_SHMEM_HOST: u8 = 0; +pub const VIRTIO_SND_PCM_F_SHMEM_GUEST: u8 = 1; +pub const VIRTIO_SND_PCM_F_MSG_POLLING: u8 = 2; +pub const VIRTIO_SND_PCM_F_EVT_SHMEM_PERIODS: u8 = 3; +pub const VIRTIO_SND_PCM_F_EVT_XRUNS: u8 = 4; + +// supported PCM sample formats + +pub const VIRTIO_SND_PCM_FMT_IMA_ADPCM: u8 = 0; +pub const VIRTIO_SND_PCM_FMT_MU_LAW: u8 = 1; +pub const VIRTIO_SND_PCM_FMT_A_LAW: u8 = 2; +pub const VIRTIO_SND_PCM_FMT_S8: u8 = 3; +pub const VIRTIO_SND_PCM_FMT_U8: u8 = 4; +pub const VIRTIO_SND_PCM_FMT_S16: u8 = 5; +pub const VIRTIO_SND_PCM_FMT_U16: u8 = 6; +pub const VIRTIO_SND_PCM_FMT_S18_3: u8 = 7; +pub const VIRTIO_SND_PCM_FMT_U18_3: u8 = 8; +pub const VIRTIO_SND_PCM_FMT_S20_3: u8 = 9; +pub const VIRTIO_SND_PCM_FMT_U20_3: u8 = 10; +pub const VIRTIO_SND_PCM_FMT_S24_3: u8 = 11; +pub const VIRTIO_SND_PCM_FMT_U24_3: u8 = 12; +pub const VIRTIO_SND_PCM_FMT_S20: u8 = 13; +pub const VIRTIO_SND_PCM_FMT_U20: u8 = 14; +pub const VIRTIO_SND_PCM_FMT_S24: u8 = 15; +pub const VIRTIO_SND_PCM_FMT_U24: u8 = 16; +pub const VIRTIO_SND_PCM_FMT_S32: u8 = 17; +pub const VIRTIO_SND_PCM_FMT_U32: u8 = 18; +pub const VIRTIO_SND_PCM_FMT_FLOAT: u8 = 19; +pub const VIRTIO_SND_PCM_FMT_FLOAT64: u8 = 20; +// digital formats (width / physical width) +pub const VIRTIO_SND_PCM_FMT_DSD_U8: u8 = 21; +pub const VIRTIO_SND_PCM_FMT_DSD_U16: u8 = 22; +pub const VIRTIO_SND_PCM_FMT_DSD_U32: u8 = 23; +pub const VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME: u8 = 24; + +// supported PCM frame rates + +pub const VIRTIO_SND_PCM_RATE_5512: u8 = 0; +pub const VIRTIO_SND_PCM_RATE_8000: u8 = 1; +pub const VIRTIO_SND_PCM_RATE_11025: u8 = 2; +pub const VIRTIO_SND_PCM_RATE_16000: u8 = 3; +pub const VIRTIO_SND_PCM_RATE_22050: u8 = 4; +pub const VIRTIO_SND_PCM_RATE_32000: u8 = 5; +pub const VIRTIO_SND_PCM_RATE_44100: u8 = 6; +pub const VIRTIO_SND_PCM_RATE_48000: u8 = 7; +pub const VIRTIO_SND_PCM_RATE_64000: u8 = 8; +pub const VIRTIO_SND_PCM_RATE_88200: u8 = 9; +pub const VIRTIO_SND_PCM_RATE_96000: u8 = 10; +pub const VIRTIO_SND_PCM_RATE_176400: u8 = 11; +pub const VIRTIO_SND_PCM_RATE_192000: u8 = 12; +pub const VIRTIO_SND_PCM_RATE_384000: u8 = 13; + +// standard channel position definition + +pub const VIRTIO_SND_CHMAP_NONE: u8 = 0; /* undefined */ +pub const VIRTIO_SND_CHMAP_NA: u8 = 1; /* silent */ +pub const VIRTIO_SND_CHMAP_MONO: u8 = 2; /* mono stream */ +pub const VIRTIO_SND_CHMAP_FL: u8 = 3; /* front left */ +pub const VIRTIO_SND_CHMAP_FR: u8 = 4; /* front right */ +pub const VIRTIO_SND_CHMAP_RL: u8 = 5; /* rear left */ +pub const VIRTIO_SND_CHMAP_RR: u8 = 6; /* rear right */ +pub const VIRTIO_SND_CHMAP_FC: u8 = 7; /* front center */ +pub const VIRTIO_SND_CHMAP_LFE: u8 = 8; /* low frequency (LFE) */ +pub const VIRTIO_SND_CHMAP_SL: u8 = 9; /* side left */ +pub const VIRTIO_SND_CHMAP_SR: u8 = 10; /* side right */ +pub const VIRTIO_SND_CHMAP_RC: u8 = 11; /* rear center */ +pub const VIRTIO_SND_CHMAP_FLC: u8 = 12; /* front left center */ +pub const VIRTIO_SND_CHMAP_FRC: u8 = 13; /* front right center */ +pub const VIRTIO_SND_CHMAP_RLC: u8 = 14; /* rear left center */ +pub const VIRTIO_SND_CHMAP_RRC: u8 = 15; /* rear right center */ +pub const VIRTIO_SND_CHMAP_FLW: u8 = 16; /* front left wide */ +pub const VIRTIO_SND_CHMAP_FRW: u8 = 17; /* front right wide */ +pub const VIRTIO_SND_CHMAP_FLH: u8 = 18; /* front left high */ +pub const VIRTIO_SND_CHMAP_FCH: u8 = 19; /* front center high */ +pub const VIRTIO_SND_CHMAP_FRH: u8 = 20; /* front right high */ +pub const VIRTIO_SND_CHMAP_TC: u8 = 21; /* top center */ +pub const VIRTIO_SND_CHMAP_TFL: u8 = 22; /* top front left */ +pub const VIRTIO_SND_CHMAP_TFR: u8 = 23; /* top front right */ +pub const VIRTIO_SND_CHMAP_TFC: u8 = 24; /* top front center */ +pub const VIRTIO_SND_CHMAP_TRL: u8 = 25; /* top rear left */ +pub const VIRTIO_SND_CHMAP_TRR: u8 = 26; /* top rear right */ +pub const VIRTIO_SND_CHMAP_TRC: u8 = 27; /* top rear center */ +pub const VIRTIO_SND_CHMAP_TFLC: u8 = 28; /* top front left center */ +pub const VIRTIO_SND_CHMAP_TFRC: u8 = 29; /* top front right center */ +pub const VIRTIO_SND_CHMAP_TSL: u8 = 34; /* top side left */ +pub const VIRTIO_SND_CHMAP_TSR: u8 = 35; /* top side right */ +pub const VIRTIO_SND_CHMAP_LLFE: u8 = 36; /* left LFE */ +pub const VIRTIO_SND_CHMAP_RLFE: u8 = 37; /* right LFE */ +pub const VIRTIO_SND_CHMAP_BC: u8 = 38; /* bottom center */ +pub const VIRTIO_SND_CHMAP_BLC: u8 = 39; /* bottom left center */ +pub const VIRTIO_SND_CHMAP_BRC: u8 = 40; /* bottom right center */ +// maximum possible number of channels +pub const VIRTIO_SND_CHMAP_MAX_SIZE: usize = 18; + /// Virtio Sound Configuration #[derive(Copy, Clone, Debug, Default, PartialEq)] #[repr(C)] @@ -99,7 +199,7 @@ pub struct VirtioSoundQueryInfo { /// starting identifier for the item pub start_id: Le32, /// number of items for which information is requested - pub cound: Le32, + pub count: Le32, /// size of the structure containing information for one item pub size: Le32, } @@ -117,3 +217,129 @@ pub struct VirtioSoundInfo { // SAFETY: The layout of the structure is fixed and can be initialized by // reading its content from byte array. unsafe impl ByteValued for VirtioSoundInfo {} + +/// Jack control request / Jack common header +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundJackHeader { + /// jack request type (VIRTIO_SND_R_JACK_*) + pub hdr: VirtioSoundHeader, + /// jack identifier + pub jack_id: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundJackHeader {} + +/// Jack response information about available jacks +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundJackInfo { + /// jack response header type + pub hdr: VirtioSoundInfo, + /// supported feature bit map (VIRTIO_SND_JACK_F_XXX) + pub feature: Le32, + /// pin default configuration value + pub hda_reg_defconf: Le32, + /// pin capabilities value + pub hda_reg_caps: Le32, + /// current jack connection status + pub connected: u8, + pub padding: [u8; 7], +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundJackInfo {} + +///If the VIRTIO_SND_JACK_F_REMAP feature bit is set in the jack information +/// Remap control request +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundJackRemap { + pub hdr: VirtioSoundJackHeader, /* .code = VIRTIO_SND_R_JACK_REMAP */ + pub association: Le32, + pub sequence: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundJackRemap {} + +/// PCM control request / PCM common header +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundPcmHeader { + pub hdr: VirtioSoundHeader, + pub stream_id: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundPcmHeader {} + +/// PCM response information +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundPcmInfo { + pub hdr: VirtioSoundInfo, + pub features: Le32, /* 1 << VIRTIO_SND_PCM_F_XXX */ + pub formats: Le64, /* 1 << VIRTIO_SND_PCM_FMT_XXX */ + pub rates: Le64, /* 1 << VIRTIO_SND_PCM_RATE_XXX */ + pub direction: u8, + pub channels_min: u8, + pub channels_max: u8, + + pub padding: [u8; 5], +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundPcmInfo {} + +/// Set selected stream parameters for the specified stream ID +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSndPcmSetParams { + pub hdr: VirtioSoundPcmHeader, + pub buffer_bytes: Le32, + pub period_bytes: Le32, + pub features: Le32, /* 1 << VIRTIO_SND_PCM_F_XXX */ + pub channels: u8, + pub format: u8, + pub rate: u8, + pub padding: u8, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSndPcmSetParams {} + +/// PCM I/O header +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundPcmXfer { + pub stream_id: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundPcmXfer {} + +/// PCM I/O status +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundPcmStatus { + pub status: Le32, + pub latency_bytes: Le32, +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundPcmStatus {} + +/// channel maps response information +#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[repr(C)] +pub struct VirtioSoundChmapInfo { + pub hdr: VirtioSoundInfo, + pub direction: u8, + pub channels: u8, + pub positions: [u8; VIRTIO_SND_CHMAP_MAX_SIZE], +} +// SAFETY: The layout of the structure is fixed and can be initialized by +// reading its content from byte array. +unsafe impl ByteValued for VirtioSoundChmapInfo {} \ No newline at end of file From 5e8fd650e91f6afe7256ea69e83f30e522e1867d Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Fri, 19 May 2023 16:23:15 +0200 Subject: [PATCH 09/44] sound: add AudioBackend trait --- crates/sound/src/audio_backends.rs | 15 +++++++++++++++ crates/sound/src/lib.rs | 25 +++++++++++++++++++++++-- crates/sound/src/vhu_sound.rs | 8 +++++++- 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 crates/sound/src/audio_backends.rs diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs new file mode 100644 index 0000000..3c1f4ce --- /dev/null +++ b/crates/sound/src/audio_backends.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use crate::{Error, Result, SoundRequest}; + +pub trait AudioBackend { + fn write(&self, req: &SoundRequest) -> Result<()>; + + fn read(&self, req: &mut SoundRequest) -> Result<()>; +} + +pub fn allocate_audio_backend(name: String) -> Result> { + match name.as_str() { + _ => Err(Error::AudioBackendNotSupported), + } +} diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index 325266e..3c73338 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause +mod audio_backends; mod vhu_sound; mod virtio_sound; @@ -10,7 +11,7 @@ use log::{info, warn}; use thiserror::Error as ThisError; use vhost::{vhost_user, vhost_user::Listener}; use vhost_user_backend::VhostUserDaemon; -use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap}; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap, VolatileSlice}; use crate::vhu_sound::VhostUserSoundBackend; @@ -25,6 +26,10 @@ pub enum Error { HandleUnknownEvent, #[error("Failed to create a new EventFd")] EventFdCreate(IoError), + #[error("Request missing data buffer")] + SoundReqMissingData, + #[error("Audio backend not supported")] + AudioBackendNotSupported, } impl std::convert::From for IoError { @@ -41,6 +46,8 @@ pub struct SoundConfig { socket: String, /// use multiple threads to hanlde the virtqueues multi_thread: bool, + /// audio backend name + audio_backend_name: String, } impl SoundConfig { @@ -50,6 +57,7 @@ impl SoundConfig { Self { socket, multi_thread, + audio_backend_name: "null".to_string(), } } @@ -60,6 +68,19 @@ impl SoundConfig { } } +pub type SoundBitmap = (); + +#[derive(Debug)] +pub struct SoundRequest<'a> { + data_slice: Option>, +} + +impl<'a> SoundRequest<'a> { + pub fn data_slice(&self) -> Option<&VolatileSlice<'a, SoundBitmap>> { + self.data_slice.as_ref() + } +} + /// This is the public API through which an external program starts the /// vhost-user-sound backend server. pub fn start_backend_server(config: SoundConfig) { @@ -69,7 +90,7 @@ pub fn start_backend_server(config: SoundConfig) { let mut daemon = VhostUserDaemon::new( String::from("vhost-user-sound"), backend.clone(), - GuestMemoryAtomic::new(GuestMemoryMmap::new()), + GuestMemoryAtomic::new(GuestMemoryMmap::::new()), ) .unwrap(); diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index 9a91b68..0e3434d 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -1,9 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause +use crate::audio_backends::{allocate_audio_backend, AudioBackend}; use crate::virtio_sound::*; use crate::{Error, Result, SoundConfig}; -use std::{io::Result as IoResult, sync::RwLock, u16, u32, u64, u8}; +use std::sync::RwLock; +use std::{io::Result as IoResult, u16, u32, u64, u8}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost_user_backend::{VhostUserBackend, VringRwLock}; use virtio_bindings::bindings::{ @@ -86,6 +88,7 @@ pub struct VhostUserSoundBackend { threads: Vec>, virtio_cfg: VirtioSoundConfig, exit_event: EventFd, + _audio_backend: RwLock>, } impl VhostUserSoundBackend { @@ -108,6 +111,8 @@ impl VhostUserSoundBackend { ])?)] }; + let audio_backend = allocate_audio_backend(config.audio_backend_name)?; + Ok(Self { threads, virtio_cfg: VirtioSoundConfig { @@ -116,6 +121,7 @@ impl VhostUserSoundBackend { chmpas: 0.into(), }, exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, + _audio_backend: RwLock::new(audio_backend), }) } From 82660840cab40bd494631b565cb657e4269d00b0 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Fri, 19 May 2023 16:23:38 +0200 Subject: [PATCH 10/44] sound: add NullBackend skeleton --- crates/sound/Cargo.toml | 4 ++++ crates/sound/src/audio_backends.rs | 7 +++++++ crates/sound/src/audio_backends/null.rs | 27 +++++++++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 crates/sound/src/audio_backends/null.rs diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index c387a36..0d36703 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -9,6 +9,10 @@ keywords = ["vhost", "sound", "virtio-sound"] license = "Apache-2.0 OR BSD-3-Clause" edition = "2018" +[features] +default = ["null-backend"] +null-backend = [] + [dependencies] clap = { version = "4.1", features = ["derive"] } env_logger = "0.10" diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 3c1f4ce..6a14b82 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -1,5 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause +#[cfg(feature = "null-backend")] +mod null; + +#[cfg(feature = "null-backend")] +use self::null::NullBackend; use crate::{Error, Result, SoundRequest}; pub trait AudioBackend { @@ -10,6 +15,8 @@ pub trait AudioBackend { pub fn allocate_audio_backend(name: String) -> Result> { match name.as_str() { + #[cfg(feature = "null-backend")] + "null" => Ok(Box::new(NullBackend::new())), _ => Err(Error::AudioBackendNotSupported), } } diff --git a/crates/sound/src/audio_backends/null.rs b/crates/sound/src/audio_backends/null.rs new file mode 100644 index 0000000..e12c36d --- /dev/null +++ b/crates/sound/src/audio_backends/null.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use super::AudioBackend; +use crate::{Error, Result, SoundRequest}; + +pub struct NullBackend {} + +impl NullBackend { + pub fn new() -> Self { + NullBackend {} + } +} + +impl AudioBackend for NullBackend { + fn write(&self, _req: &SoundRequest) -> Result<()> { + Ok(()) + } + + fn read(&self, req: &mut SoundRequest) -> Result<()> { + let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?; + let zero_mem = vec![0u8; buf.len()]; + + buf.copy_from(&zero_mem); + + Ok(()) + } +} From 65f7c78dbd8093e4501a4c3085d98f59befc182a Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Fri, 19 May 2023 18:16:21 +0200 Subject: [PATCH 11/44] sound: support --backend option --- crates/sound/src/lib.rs | 4 ++-- crates/sound/src/main.rs | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index 3c73338..fb3b176 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -53,11 +53,11 @@ pub struct SoundConfig { impl SoundConfig { /// Create a new instance of the SoundConfig struct, containing the /// parameters to be fed into the sound-backend server. - pub fn new(socket: String, multi_thread: bool) -> Self { + pub fn new(socket: String, multi_thread: bool, audio_backend_name: String) -> Self { Self { socket, multi_thread, - audio_backend_name: "null".to_string(), + audio_backend_name, } } diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs index 542fdb6..7f3c844 100644 --- a/crates/sound/src/main.rs +++ b/crates/sound/src/main.rs @@ -12,6 +12,9 @@ struct SoundArgs { /// vhost-user Unix domain socket path. #[clap(long)] socket: String, + /// audio backend to be used (supported: null) + #[clap(long)] + backend: String, } impl TryFrom for SoundConfig { @@ -19,8 +22,9 @@ impl TryFrom for SoundConfig { fn try_from(cmd_args: SoundArgs) -> Result { let socket = cmd_args.socket.trim().to_string(); + let backend = cmd_args.backend.trim().to_string(); - Ok(SoundConfig::new(socket, false)) + Ok(SoundConfig::new(socket, false, backend)) } } @@ -43,6 +47,7 @@ mod tests { fn from_args(socket: &str) -> Self { SoundArgs { socket: socket.to_string(), + backend: "null".to_string(), } } } From a198f48cde7f002689292ea4a5fffd16897b86c5 Mon Sep 17 00:00:00 2001 From: Stefano Garzarella Date: Fri, 19 May 2023 18:19:47 +0200 Subject: [PATCH 12/44] sound: rename alloc_audio_backend --- crates/sound/src/audio_backends.rs | 2 +- crates/sound/src/vhu_sound.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 6a14b82..2ac2db4 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -13,7 +13,7 @@ pub trait AudioBackend { fn read(&self, req: &mut SoundRequest) -> Result<()>; } -pub fn allocate_audio_backend(name: String) -> Result> { +pub fn alloc_audio_backend(name: String) -> Result> { match name.as_str() { #[cfg(feature = "null-backend")] "null" => Ok(Box::new(NullBackend::new())), diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index 0e3434d..e0c9be3 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use crate::audio_backends::{allocate_audio_backend, AudioBackend}; +use crate::audio_backends::{alloc_audio_backend, AudioBackend}; use crate::virtio_sound::*; use crate::{Error, Result, SoundConfig}; @@ -111,7 +111,7 @@ impl VhostUserSoundBackend { ])?)] }; - let audio_backend = allocate_audio_backend(config.audio_backend_name)?; + let audio_backend = alloc_audio_backend(config.audio_backend_name)?; Ok(Self { threads, From 05a959d7039d10af00dc4585825357439df22800 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Fri, 26 May 2023 17:44:05 +0200 Subject: [PATCH 13/44] sound: add PipeWire backend skeleton to enable the pipewire backend feature, build with `--features pw-backend` Signed-off-by: Dorinda Bassey --- Cargo.lock | 346 +++++++++++++++++- crates/sound/Cargo.toml | 6 + crates/sound/src/audio_backends.rs | 7 + crates/sound/src/audio_backends/pw_backend.rs | 28 ++ crates/sound/src/virtio_sound.rs | 2 +- 5 files changed, 370 insertions(+), 19 deletions(-) create mode 100644 crates/sound/src/audio_backends/pw_backend.rs diff --git a/Cargo.lock b/Cargo.lock index b22025e..f5f0a78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + [[package]] name = "arc-swap" version = "1.6.0" @@ -45,6 +51,28 @@ dependencies = [ "which", ] +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -72,6 +100,16 @@ dependencies = [ "nom", ] +[[package]] +name = "cfg-expr" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8790cf1286da485c72cf5fc7aeba308438800036ec67d89425924c4807268c9" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -126,6 +164,12 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "cookie-factory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" + [[package]] name = "dashmap" version = "5.4.0" @@ -179,6 +223,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -338,6 +393,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -399,7 +464,7 @@ name = "libgpiod" version = "0.1.0" source = "git+https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/?rev=d8d3a84b2ddf#d8d3a84b2ddfc29670430fc73ff8483a44b8f61e" dependencies = [ - "errno", + "errno 0.2.8", "intmap", "libc", "libgpiod-sys", @@ -411,7 +476,7 @@ name = "libgpiod-sys" version = "0.1.0" source = "git+https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/?rev=d8d3a84b2ddf#d8d3a84b2ddfc29670430fc73ff8483a44b8f61e" dependencies = [ - "bindgen", + "bindgen 0.63.0", "cc", ] @@ -425,6 +490,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "libspa" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667dfbb50c3d1f7ee1d33afdc04d1255923ece7642db3303046e7d63d997d77d" +dependencies = [ + "bitflags", + "cc", + "cookie-factory", + "errno 0.3.1", + "libc", + "libspa-sys", + "nom", + "system-deps", +] + +[[package]] +name = "libspa-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5b88f52534df7ca88d451ae9628e22124e3cc5c60966465a7db479534c7a" +dependencies = [ + "bindgen 0.64.0", + "cc", + "system-deps", +] + [[package]] name = "linux-raw-sys" version = "0.1.4" @@ -456,12 +548,35 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", + "static_assertions", +] + [[package]] name = "nom" version = "7.1.3" @@ -535,6 +650,41 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pipewire" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2180a4a84b855be86e6cd72fa6fd4318278871d2b1082e7cd05fe64b135ccb" +dependencies = [ + "anyhow", + "bitflags", + "errno 0.3.1", + "libc", + "libspa", + "libspa-sys", + "nix", + "once_cell", + "pipewire-sys", + "thiserror", +] + +[[package]] +name = "pipewire-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a95290eedb7fb6aa3922fdc0261cd0ddeb940abcdbdef28778928106554d2123" +dependencies = [ + "bindgen 0.64.0", + "libspa-sys", + "system-deps", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -652,7 +802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" dependencies = [ "bitflags", - "errno", + "errno 0.2.8", "io-lifetimes", "libc", "linux-raw-sys", @@ -665,6 +815,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" + +[[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", +] + [[package]] name = "serial_test" version = "1.0.0" @@ -711,6 +876,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -728,6 +899,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-deps" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" + [[package]] name = "tempfile" version = "3.4.0" @@ -770,12 +960,52 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + [[package]] name = "version_check" version = "0.9.4" @@ -868,9 +1098,14 @@ dependencies = [ name = "vhost-user-sound" version = "0.1.0" dependencies = [ + "bindgen 0.64.0", "clap", "env_logger", + "libspa", + "libspa-sys", "log", + "pipewire", + "pipewire-sys", "serial_test", "thiserror", "vhost", @@ -1011,13 +1246,13 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.1", ] [[package]] @@ -1026,7 +1261,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -1035,13 +1279,28 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -1050,38 +1309,89 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +dependencies = [ + "memchr", +] diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index 0d36703..927aa40 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -12,6 +12,7 @@ edition = "2018" [features] default = ["null-backend"] null-backend = [] +pw-backend = ["pipewire", "libspa", "pipewire-sys", "libspa-sys", "bindgen"] [dependencies] clap = { version = "4.1", features = ["derive"] } @@ -23,6 +24,11 @@ vhost-user-backend = "0.8" virtio-bindings = "0.2" vm-memory = "0.10" vmm-sys-util = "0.11" +pipewire = { version = "0.6.0", optional = true } +libspa = { version = "0.6.0", optional = true } +pipewire-sys = { version = "0.6.0", optional = true } +libspa-sys = { version = "0.6.0", optional = true } +bindgen = { version = "0.64.0", optional = true } [dev-dependencies] serial_test = "1.0" diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 2ac2db4..18f3bed 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -3,8 +3,13 @@ #[cfg(feature = "null-backend")] mod null; +#[cfg(feature = "pw-backend")] +mod pw_backend; + #[cfg(feature = "null-backend")] use self::null::NullBackend; +#[cfg(feature = "pw-backend")] +use self::pw_backend::PwBackend; use crate::{Error, Result, SoundRequest}; pub trait AudioBackend { @@ -17,6 +22,8 @@ pub fn alloc_audio_backend(name: String) -> Result Ok(Box::new(NullBackend::new())), + #[cfg(feature = "pw-backend")] + "pipewire" => Ok(Box::new(PwBackend::new())), _ => Err(Error::AudioBackendNotSupported), } } diff --git a/crates/sound/src/audio_backends/pw_backend.rs b/crates/sound/src/audio_backends/pw_backend.rs new file mode 100644 index 0000000..a939365 --- /dev/null +++ b/crates/sound/src/audio_backends/pw_backend.rs @@ -0,0 +1,28 @@ +// Pipewire backend device +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use super::AudioBackend; +use crate::{Error, Result, SoundRequest}; + +pub struct PwBackend {} + +impl PwBackend { + pub fn new() -> Self { + PwBackend {} + } +} + +impl AudioBackend for PwBackend { + fn write(&self, _req: &SoundRequest) -> Result<()> { + Ok(()) + } + + fn read(&self, req: &mut SoundRequest) -> Result<()> { + let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?; + let zero_mem = vec![0u8; buf.len()]; + + buf.copy_from(&zero_mem); + + Ok(()) + } +} diff --git a/crates/sound/src/virtio_sound.rs b/crates/sound/src/virtio_sound.rs index e38ddc7..484c06e 100644 --- a/crates/sound/src/virtio_sound.rs +++ b/crates/sound/src/virtio_sound.rs @@ -342,4 +342,4 @@ pub struct VirtioSoundChmapInfo { } // SAFETY: The layout of the structure is fixed and can be initialized by // reading its content from byte array. -unsafe impl ByteValued for VirtioSoundChmapInfo {} \ No newline at end of file +unsafe impl ByteValued for VirtioSoundChmapInfo {} From d39140f82a05fcbfb0805a528e09a0f966737e9d Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Mon, 5 Jun 2023 13:53:37 +0300 Subject: [PATCH 14/44] sound: rename typo virtio_snd_config.chmpas -> chmaps --- crates/sound/src/vhu_sound.rs | 2 +- crates/sound/src/virtio_sound.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index e0c9be3..aa9164a 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -118,7 +118,7 @@ impl VhostUserSoundBackend { virtio_cfg: VirtioSoundConfig { jacks: 0.into(), streams: 1.into(), - chmpas: 0.into(), + chmaps: 0.into(), }, exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, _audio_backend: RwLock::new(audio_backend), diff --git a/crates/sound/src/virtio_sound.rs b/crates/sound/src/virtio_sound.rs index 484c06e..06885d9 100644 --- a/crates/sound/src/virtio_sound.rs +++ b/crates/sound/src/virtio_sound.rs @@ -160,8 +160,9 @@ pub struct VirtioSoundConfig { /// total number of all available PCM streams pub streams: Le32, /// total number of all available channel maps - pub chmpas: Le32, + pub chmaps: Le32, } + // SAFETY: The layout of the structure is fixed and can be initialized by // reading its content from byte array. unsafe impl ByteValued for VirtioSoundConfig {} From 582a66ff07eb1a2de04b313ba2757fd1a8b99707 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Wed, 21 Jun 2023 13:02:58 +0200 Subject: [PATCH 15/44] Sound: Handle control queue Handle ctrl msgs from the control Queue in method for handling control queue Signed-off-by: Dorinda Bassey --- Cargo.lock | 1 + crates/sound/Cargo.toml | 1 + crates/sound/src/lib.rs | 20 +++ crates/sound/src/vhu_sound.rs | 265 ++++++++++++++++++++++++++++--- crates/sound/src/virtio_sound.rs | 4 +- 5 files changed, 270 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5f0a78..a33e345 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1111,6 +1111,7 @@ dependencies = [ "vhost", "vhost-user-backend", "virtio-bindings 0.2.0", + "virtio-queue", "vm-memory", "vmm-sys-util", ] diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index 927aa40..330c188 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -22,6 +22,7 @@ thiserror = "1.0" vhost = { version = "0.6", features = ["vhost-user-slave"] } vhost-user-backend = "0.8" virtio-bindings = "0.2" +virtio-queue = "0.7" vm-memory = "0.10" vmm-sys-util = "0.11" pipewire = { version = "0.6.0", optional = true } diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index fb3b176..9c8c797 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -30,6 +30,26 @@ pub enum Error { SoundReqMissingData, #[error("Audio backend not supported")] AudioBackendNotSupported, + #[error("Descriptor not found")] + DescriptorNotFound, + #[error("Descriptor read failed")] + DescriptorReadFailed, + #[error("Descriptor write failed")] + DescriptorWriteFailed, + #[error("Isufficient descriptor size, required: {0}, found: {1}")] + InsufficientDescriptorSize(usize, usize), + #[error("Failed to send notification")] + SendNotificationFailed, + #[error("Invalid descriptor count {0}")] + UnexpectedDescriptorCount(usize), + #[error("Invalid descriptor size, expected: {0}, found: {1}")] + UnexpectedDescriptorSize(usize, usize), + #[error("Invalid descriptor size, expected at least: {0}, found: {1}")] + UnexpectedMinimumDescriptorSize(usize, usize), + #[error("Received unexpected readable descriptor at index {0}")] + UnexpectedReadableDescriptor(usize), + #[error("Received unexpected write only descriptor at index {0}")] + UnexpectedWriteOnlyDescriptor(usize), } impl std::convert::From for IoError { diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index aa9164a..6f66a36 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -1,22 +1,39 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause +use std::mem::size_of; +use std::sync::RwLock; +use std::{io::Result as IoResult, u16, u32, u64, u8}; + +use log::{error, debug}; +use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; +use vhost_user_backend::{VhostUserBackend, VringRwLock, VringT}; +use virtio_bindings::bindings::{ + virtio_config::VIRTIO_F_NOTIFY_ON_EMPTY, virtio_config::VIRTIO_F_VERSION_1, + virtio_ring::VIRTIO_RING_F_EVENT_IDX, virtio_ring::VIRTIO_RING_F_INDIRECT_DESC, +}; +use virtio_queue::{DescriptorChain, QueueOwnedT}; +use vm_memory::{Bytes, ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, GuestMemoryLoadGuard}; +use vmm_sys_util::{ + epoll::EventSet, + eventfd::{EventFd, EFD_NONBLOCK}, +}; + use crate::audio_backends::{alloc_audio_backend, AudioBackend}; use crate::virtio_sound::*; use crate::{Error, Result, SoundConfig}; -use std::sync::RwLock; -use std::{io::Result as IoResult, u16, u32, u64, u8}; -use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; -use vhost_user_backend::{VhostUserBackend, VringRwLock}; -use virtio_bindings::bindings::{ - virtio_config::VIRTIO_F_NOTIFY_ON_EMPTY, virtio_config::VIRTIO_F_VERSION_1, - virtio_ring::VIRTIO_RING_F_EVENT_IDX, virtio_ring::VIRTIO_RING_F_INDIRECT_DESC, -}; -use vm_memory::{ByteValued, GuestMemoryAtomic, GuestMemoryMmap}; -use vmm_sys_util::{ - epoll::EventSet, - eventfd::{EventFd, EFD_NONBLOCK}, -}; +pub const SUPPORTED_FORMATS: u64 = 1 << VIRTIO_SND_PCM_FMT_U8 + | 1 << VIRTIO_SND_PCM_FMT_S16 + | 1 << VIRTIO_SND_PCM_FMT_S24 + | 1 << VIRTIO_SND_PCM_FMT_S32; + +pub const SUPPORTED_RATES: u64 = 1 << VIRTIO_SND_PCM_RATE_8000 + | 1 << VIRTIO_SND_PCM_RATE_11025 + | 1 << VIRTIO_SND_PCM_RATE_16000 + | 1 << VIRTIO_SND_PCM_RATE_22050 + | 1 << VIRTIO_SND_PCM_RATE_32000 + | 1 << VIRTIO_SND_PCM_RATE_44100 + | 1 << VIRTIO_SND_PCM_RATE_48000; struct VhostUserSoundThread { mem: Option>, @@ -50,6 +67,7 @@ impl VhostUserSoundThread { } fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()> { + debug!("update memory"); self.mem = Some(mem); Ok(()) } @@ -57,17 +75,223 @@ impl VhostUserSoundThread { fn handle_event(&self, device_event: u16, vrings: &[VringRwLock]) -> IoResult { let vring = &vrings[device_event as usize]; let queue_idx = self.queue_indexes[device_event as usize]; + debug!("handle event call queue: {}", queue_idx); match queue_idx { - CONTROL_QUEUE_IDX => self.process_control(vring), - EVENT_QUEUE_IDX => self.process_event(vring), - TX_QUEUE_IDX => self.process_tx(vring), - RX_QUEUE_IDX => self.process_rx(vring), - _ => Err(Error::HandleUnknownEvent.into()), + CONTROL_QUEUE_IDX => { + debug!("control queue: {}", CONTROL_QUEUE_IDX); + let vring = &vrings[0]; + if self.event_idx { + // vm-virtio's Queue implementation only checks avail_index + // once, so to properly support EVENT_IDX we need to keep + // calling process_request_queue() until it stops finding + // new requests on the queue. + loop { + vring.disable_notification().unwrap(); + self.process_control(vring)?; + if !vring.enable_notification().unwrap() { + break; + } + } + } else { + // Without EVENT_IDX, a single call is enough. + self.process_control(vring)?; + } + } + EVENT_QUEUE_IDX => { + self.process_event(vring)?; + } + TX_QUEUE_IDX => { + self.process_tx(vring)?; + } + RX_QUEUE_IDX => { + self.process_rx(vring)?; + } + _ => { + return Err(Error::HandleUnknownEvent.into()); + } } + Ok(false) } - fn process_control(&self, _vring: &VringRwLock) -> IoResult { + /// Process the messages in the vring and dispatch replies + fn process_control(&self, vring: &VringRwLock) -> Result { + let requests: Vec = vring + .get_mut() + .get_queue_mut() + .iter(self.mem.as_ref().unwrap().memory()) + .map_err(|_| Error::DescriptorNotFound)? + .collect(); + + debug!("Requests to process: {}", requests.len()); + if requests.is_empty() { + debug!("yes, it's empty"); + return Ok(true); + } + //iterate over each sound request + for desc_chain in requests { + let descriptors: Vec<_> = desc_chain.clone().collect(); + debug!("Sound request with n descriptors: {}", descriptors.len()); + + let desc_request = descriptors[0]; + if desc_request.is_write_only() { + return Err(Error::UnexpectedWriteOnlyDescriptor(0)); + } + let read_desc_len: usize = desc_request.len() as usize; + let header_size = size_of::(); + if (read_desc_len as usize) < header_size { + return Err(Error::UnexpectedMinimumDescriptorSize( + header_size, + read_desc_len, + )); + } + let hdr_request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + let desc_response = descriptors[1]; + if !desc_response.is_write_only() { + return Err(Error::UnexpectedReadableDescriptor(1)); + } + + let response = VirtioSoundHeader { code: VIRTIO_SND_S_OK.into(), }; + + let mut len = desc_response.len() as u32; + let request_type = hdr_request.code.to_native(); + match request_type { + VIRTIO_SND_R_JACK_INFO => todo!(), + VIRTIO_SND_R_PCM_INFO => { + if descriptors.len() != 3 { + return Err(Error::UnexpectedDescriptorCount(descriptors.len())); + } + let desc_pcm = descriptors[2]; + if !desc_pcm.is_write_only() { + return Err(Error::UnexpectedReadableDescriptor(2)); + } + let query_info = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + let start_id: usize = u32::from(query_info.start_id) as usize; + let count: usize = u32::from(query_info.count) as usize; + let mut pcm_info = vec![VirtioSoundPcmInfo::default(); count]; + for pcm in &mut pcm_info { + pcm.hdr.hda_fn_nid = 0.into(); + pcm.features = 0.into(); + pcm.formats = SUPPORTED_FORMATS.into(); + pcm.rates = SUPPORTED_RATES.into(); + pcm.direction = VIRTIO_SND_D_OUTPUT; + pcm.channels_min = 1; + pcm.channels_max = 6; + pcm.padding = [0; 5]; + } + if start_id + count > pcm_info.len() { + error!( + "start_id({}) + count({}) must be smaller than the number of streams ({})", + start_id, + count, + pcm_info.len() + ); + desc_chain + .memory() + .write_obj(VIRTIO_SND_S_BAD_MSG, desc_response.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + } + desc_chain + .memory() + .write_obj(response, desc_response.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + //let mut len = desc_response.len() as u32; + + for i in start_id..(start_id + count) { + desc_chain + .memory() + .write_slice(pcm_info[i].as_slice(), desc_pcm.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + } + len += desc_pcm.len(); + }, + VIRTIO_SND_R_CHMAP_INFO => todo!(), + VIRTIO_SND_R_JACK_REMAP => todo!(), + VIRTIO_SND_R_PCM_SET_PARAMS => { + if descriptors.len() != 2 { + return Err(Error::UnexpectedDescriptorCount(descriptors.len())); + } + + let set_params = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + let stream_id: usize = u32::from(set_params.hdr.stream_id) as usize; + let buffer_bytes: u32 = set_params.buffer_bytes.into(); + let period_bytes: u32 = set_params.period_bytes.into(); + let features: u32 = set_params.features.into(); + + dbg!("stream_id: {}", stream_id ); + dbg!("set params format: {}", set_params.format); + dbg!("set params rate: {} ", set_params.rate); + dbg!("set params channels: {} ", set_params.channels); + if features != 0 { + error!("No feature is supported"); + desc_chain + .memory() + .write_obj(VIRTIO_SND_S_NOT_SUPP, desc_response.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + } + if buffer_bytes % period_bytes != 0 { + error!("buffer_bytes({}) must be dividable by period_bytes({})", + buffer_bytes, period_bytes); + desc_chain + .memory() + .write_obj(VIRTIO_SND_S_BAD_MSG, desc_response.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + } + desc_chain + .memory() + .write_obj(response, desc_response.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + len = desc_response.len() as u32; + }, + VIRTIO_SND_R_PCM_PREPARE + | VIRTIO_SND_R_PCM_START + | VIRTIO_SND_R_PCM_STOP + | VIRTIO_SND_R_PCM_RELEASE => { + let pcm_hdr = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + let stream_id: usize = u32::from(pcm_hdr.stream_id) as usize; + dbg!("stream_id: {}", stream_id ); + + desc_chain + .memory() + .write_obj(response, desc_response.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + len = desc_response.len() as u32; + }, + _ => { + error!( + "virtio-snd: Unknown control queue message code: {}", + request_type + ); + } + }; + if vring + .add_used(desc_chain.head_index(), len) + .is_err() + { + error!("Couldn't return used descriptors to the ring"); + } + } + // Send notification once all the requests are processed + debug!("Sending processed request notification"); + vring + .signal_used_queue() + .map_err(|_| Error::SendNotificationFailed)?; + debug!("Process control queue finished"); + Ok(false) } @@ -82,6 +306,7 @@ impl VhostUserSoundThread { fn process_rx(&self, _vring: &VringRwLock) -> IoResult { Ok(false) } + } pub struct VhostUserSoundBackend { @@ -91,6 +316,8 @@ pub struct VhostUserSoundBackend { _audio_backend: RwLock>, } +type SndDescriptorChain = DescriptorChain>>; + impl VhostUserSoundBackend { pub fn new(config: SoundConfig) -> Result { let threads = if config.multi_thread { diff --git a/crates/sound/src/virtio_sound.rs b/crates/sound/src/virtio_sound.rs index 06885d9..b605049 100644 --- a/crates/sound/src/virtio_sound.rs +++ b/crates/sound/src/virtio_sound.rs @@ -48,8 +48,8 @@ pub const VIRTIO_SND_S_IO_ERR: u32 = 0x8003; // device data flow directions -pub const VIRTIO_SND_D_OUTPUT: u32 = 0; -pub const VIRTIO_SND_D_INPUT: u32 = 1; +pub const VIRTIO_SND_D_OUTPUT: u8 = 0; +pub const VIRTIO_SND_D_INPUT: u8 = 1; // supported jack features From c4b253c433d320b01b8dd145d1ce7ea13decf195 Mon Sep 17 00:00:00 2001 From: Matias Ezequiel Vara Larsen Date: Wed, 21 Jun 2023 14:52:54 +0200 Subject: [PATCH 16/44] Sound: Handle tx queue This commit handles the pcm i/o messages to the tx transmission queue. These msgs contains three descriptors: hdr, data and status. The data descriptor shall be processed by the audio backend. The data may be split in multiples descriptors. Signed-off-by: Matias Ezequiel Vara Larsen --- crates/sound/src/vhu_sound.rs | 115 +++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index 6f66a36..2bb19b1 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -102,7 +102,23 @@ impl VhostUserSoundThread { self.process_event(vring)?; } TX_QUEUE_IDX => { - self.process_tx(vring)?; + let vring = &vrings[2]; + if self.event_idx { + // vm-virtio's Queue implementation only checks avail_index + // once, so to properly support EVENT_IDX we need to keep + // calling process_request_queue() until it stops finding + // new requests on the queue. + loop { + vring.disable_notification().unwrap(); + self.process_tx(vring)?; + if !vring.enable_notification().unwrap() { + break; + } + } + } else { + // Without EVENT_IDX, a single call is enough. + self.process_tx(vring)?; + } } RX_QUEUE_IDX => { self.process_rx(vring)?; @@ -299,7 +315,102 @@ impl VhostUserSoundThread { Ok(false) } - fn process_tx(&self, _vring: &VringRwLock) -> IoResult { + fn process_tx(&self, vring: &VringRwLock) -> Result { + let requests: Vec = vring + .get_mut() + .get_queue_mut() + .iter(self.mem.as_ref().unwrap().memory()) + .map_err(|_| Error::DescriptorNotFound)? + .collect(); + + debug!("Requests to tx: {}", requests.len()); + + for desc_chain in requests { + let descriptors: Vec<_> = desc_chain.clone().collect(); + debug!("Sound request with n descriptors: {}", descriptors.len()); + + // TODO: to handle the case in which READ_ONLY descs + // have both the header and the data + + let last_desc = descriptors.len() - 1; + let desc_response = descriptors[last_desc]; + + if desc_response.len() as usize != size_of::() { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + desc_response.len() as usize, + )); + } + + if !desc_response.is_write_only() { + return Err(Error::UnexpectedReadableDescriptor(1)); + } + + let response = VirtioSoundPcmStatus { status: VIRTIO_SND_S_OK.into(), latency_bytes: 0.into() }; + + let desc_request = descriptors[0]; + + if desc_request.len() as usize != size_of::() { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + desc_request.len() as usize, + )); + } + + if desc_request.is_write_only() { + return Err(Error::UnexpectedWriteOnlyDescriptor(1)); + } + + let mut all_bufs=Vec::::new(); + let data_descs = &descriptors[1..descriptors.len() -1]; + + for data in data_descs{ + if data.is_write_only(){ + return Err(Error::UnexpectedWriteOnlyDescriptor(1)); + } + + let mut buf = vec![0u8; data.len() as usize]; + + desc_chain + .memory() + .read_slice(&mut buf, data.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + all_bufs.extend(buf); + } + + let hdr_request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + let _stream_id = hdr_request.stream_id.to_native(); + + // TODO: to invoke audio_backend.write(stream_id, all_bufs, len) + + // 5.14.6.8.1.1 + // The device MUST NOT complete the I/O request until the buffer is + // totally consumed. + desc_chain + .memory() + .write_obj(response, desc_response.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + + let len = desc_response.len() as u32; + + if vring + .add_used(desc_chain.head_index(), len) + .is_err() + { + error!("Couldn't return used descriptors to the ring"); + } + } + // Send notification once all the requests are processed + debug!("Sending processed tx notification"); + vring + .signal_used_queue() + .map_err(|_| Error::SendNotificationFailed)?; + debug!("Process tx queue finished"); Ok(false) } From e3d50712466266c966743b0ad1104a0c8cba76c6 Mon Sep 17 00:00:00 2001 From: Matias Ezequiel Vara Larsen Date: Wed, 21 Jun 2023 15:29:48 +0200 Subject: [PATCH 17/44] Sound: add audio_backend parameter Signed-off-by: Matias Ezequiel Vara Larsen --- crates/sound/src/vhu_sound.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index 2bb19b1..4f42df5 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -72,7 +72,7 @@ impl VhostUserSoundThread { Ok(()) } - fn handle_event(&self, device_event: u16, vrings: &[VringRwLock]) -> IoResult { + fn handle_event(&self, device_event: u16, vrings: &[VringRwLock], audio_backend: &RwLock>) -> IoResult { let vring = &vrings[device_event as usize]; let queue_idx = self.queue_indexes[device_event as usize]; debug!("handle event call queue: {}", queue_idx); @@ -110,14 +110,14 @@ impl VhostUserSoundThread { // new requests on the queue. loop { vring.disable_notification().unwrap(); - self.process_tx(vring)?; + self.process_tx(vring, &audio_backend)?; if !vring.enable_notification().unwrap() { break; } } } else { // Without EVENT_IDX, a single call is enough. - self.process_tx(vring)?; + self.process_tx(vring, &audio_backend)?; } } RX_QUEUE_IDX => { @@ -315,7 +315,7 @@ impl VhostUserSoundThread { Ok(false) } - fn process_tx(&self, vring: &VringRwLock) -> Result { + fn process_tx(&self, vring: &VringRwLock, _audio_backend: &RwLock>) -> Result { let requests: Vec = vring .get_mut() .get_queue_mut() @@ -517,7 +517,7 @@ impl VhostUserBackend for VhostUserSoundBackend { self.threads[thread_id] .read() .unwrap() - .handle_event(device_event, vrings) + .handle_event(device_event, vrings, &self._audio_backend) } fn get_config(&self, offset: u32, size: u32) -> Vec { From 110ccd736cca0077b4791ee68940f5c8502d7c35 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Mon, 3 Jul 2023 16:02:37 +0200 Subject: [PATCH 18/44] instantiate new backend Signed-off-by: Dorinda Bassey --- crates/sound/Cargo.toml | 2 +- crates/sound/src/audio_backends.rs | 27 +++++- crates/sound/src/audio_backends/null.rs | 10 +- crates/sound/src/audio_backends/pw_backend.rs | 96 +++++++++++++++++-- 4 files changed, 121 insertions(+), 14 deletions(-) diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index 330c188..2a8f621 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -10,7 +10,7 @@ license = "Apache-2.0 OR BSD-3-Clause" edition = "2018" [features] -default = ["null-backend"] +default = ["null-backend", "pw-backend"] null-backend = [] pw-backend = ["pipewire", "libspa", "pipewire-sys", "libspa-sys", "bindgen"] diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 18f3bed..205d753 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -9,13 +9,32 @@ mod pw_backend; #[cfg(feature = "null-backend")] use self::null::NullBackend; #[cfg(feature = "pw-backend")] -use self::pw_backend::PwBackend; -use crate::{Error, Result, SoundRequest}; +use self::pw_backend::{PwBackend, PCMParams}; +use crate::{Error, Result}; pub trait AudioBackend { - fn write(&self, req: &SoundRequest) -> Result<()>; + fn write(&self, stream_id: u32) -> Result<()>; + fn read(&self, stream_id: u32) -> Result<()>; - fn read(&self, req: &mut SoundRequest) -> Result<()>; + fn set_param(&self, _stream_id: u32, _params: PCMParams) -> Result<()> { + Ok(()) + } + + fn prepare(&self, _stream_id: u32) -> Result<()> { + Ok(()) + } + + fn release(&self, _stream_id: u32) -> Result<()> { + Ok(()) + } + + fn start(&self, _stream_id: u32) -> Result<()> { + Ok(()) + } + + fn stop(&self, _stream_id: u32) -> Result<()> { + Ok(()) + } } pub fn alloc_audio_backend(name: String) -> Result> { diff --git a/crates/sound/src/audio_backends/null.rs b/crates/sound/src/audio_backends/null.rs index e12c36d..de4f3e1 100644 --- a/crates/sound/src/audio_backends/null.rs +++ b/crates/sound/src/audio_backends/null.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use super::AudioBackend; -use crate::{Error, Result, SoundRequest}; +use crate::{Error, Result}; pub struct NullBackend {} @@ -12,16 +12,20 @@ impl NullBackend { } impl AudioBackend for NullBackend { - fn write(&self, _req: &SoundRequest) -> Result<()> { + fn write(&self, stream_id: u32) -> Result<()> { + println!("null backend, writting to stream: {}", stream_id); Ok(()) } - fn read(&self, req: &mut SoundRequest) -> Result<()> { + fn read(&self, _stream_id: u32) -> Result<()> { + + /* let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?; let zero_mem = vec![0u8; buf.len()]; buf.copy_from(&zero_mem); + */ Ok(()) } } diff --git a/crates/sound/src/audio_backends/pw_backend.rs b/crates/sound/src/audio_backends/pw_backend.rs index a939365..6e9194f 100644 --- a/crates/sound/src/audio_backends/pw_backend.rs +++ b/crates/sound/src/audio_backends/pw_backend.rs @@ -2,27 +2,111 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use super::AudioBackend; -use crate::{Error, Result, SoundRequest}; +use std::{thread}; +use std::{cell::Cell, rc::Rc}; +use crate::{Error, Result}; -pub struct PwBackend {} +use vm_memory::{Le32, Le64}; +use pipewire as pw; +use pw::{sys::*}; + +#[derive(Default, Debug)] +pub struct StreamInfo { + pub id: usize, + pub params: PCMParams, + pub formats: Le64, + pub rates: Le64, + pub direction: u8, + pub channels_min: u8, + pub channels_max: u8, +} + +#[derive(Default, Debug)] +pub struct PCMParams { + pub features: Le32, + /// size of hardware buffer in bytes + pub buffer_bytes: Le32, + /// size of hardware period in bytes + pub period_bytes: Le32, + pub channels: u8, + pub format: u8, + pub rate: u8, +} + +// SAFETY: Safe as the structure can be sent to another thread. +unsafe impl Send for WrapMainLoop {} + +// SAFETY: Safe as the structure can be shared with another thread as the state +// is protected with a lock. +unsafe impl Sync for WrapMainLoop {} + +#[derive(Clone, Debug)] +pub struct WrapMainLoop { + mainloop: pipewire::MainLoop, +} +pub struct PwBackend { + //pub streams: Arc>>, +} impl PwBackend { pub fn new() -> Self { - PwBackend {} + pw::init(); + + let wrap_mainloop = WrapMainLoop { + mainloop : pw::MainLoop::new().expect("we can't create mainloop") + }; + //let mainloop = pw::MainLoop::new().expect("Failed to create Pipewire Mainloop"); + let context = pw::Context::new(&wrap_mainloop.mainloop).expect("Failed to create Pipewire Context"); + let core = context + .connect(None) + .expect("Failed to connect to Pipewire Core"); + + // To comply with Rust's safety rules, we wrap this variable in an `Rc` and a `Cell`. + let done = Rc::new(Cell::new(false)); + + // Create new reference for each variable so that they can be moved into the closure. + let done_clone = done.clone(); + let loop_clone = wrap_mainloop.mainloop.clone(); + + let pending = core.sync(0).expect("sync failed"); + let _listener_core = core + .add_listener_local() + .done(move |id, seq| { + if id == PW_ID_CORE && seq == pending { + done_clone.set(true); + loop_clone.quit(); + } + }) + .register(); + + thread::spawn(move || { + wrap_mainloop.mainloop.run(); + }); + + println!("pipewire backend running"); + + Self { + } + } } impl AudioBackend for PwBackend { - fn write(&self, _req: &SoundRequest) -> Result<()> { + fn write(&self, stream_id: u32) -> Result<()> { + println!("pipewire backend, writting to stream: {}", stream_id); Ok(()) } - fn read(&self, req: &mut SoundRequest) -> Result<()> { + fn read(&self, _stream_id: u32) -> Result<()> { + /* let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?; let zero_mem = vec![0u8; buf.len()]; buf.copy_from(&zero_mem); - + */ + Ok(()) + } + fn set_param(&self, _stream_id: u32, _params: PCMParams) -> Result<()> { Ok(()) } } From b3b83c1f41933a386cd9089bebce11acf5ef342b Mon Sep 17 00:00:00 2001 From: Matias Ezequiel Vara Larsen Date: Tue, 4 Jul 2023 15:58:26 +0200 Subject: [PATCH 19/44] Sound: initialize StreamInfo This commit adds a vector named StreamInfo that contains the supported configuration for the audio backends, e.g., rate, format. This information is stored in the context of VhostUserSoundBackend. The device reponses this information when getting the VIRTIO_SND_R_PCM_INFO msg. The number of streams that are exposed in the device configuration is got from this table. Signed-off-by: Matias Ezequiel Vara Larsen --- crates/sound/src/audio_backends/null.rs | 2 +- crates/sound/src/audio_backends/pw_backend.rs | 17 +-- crates/sound/src/vhu_sound.rs | 105 ++++++++++++------ 3 files changed, 78 insertions(+), 46 deletions(-) diff --git a/crates/sound/src/audio_backends/null.rs b/crates/sound/src/audio_backends/null.rs index de4f3e1..9b1ec24 100644 --- a/crates/sound/src/audio_backends/null.rs +++ b/crates/sound/src/audio_backends/null.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use super::AudioBackend; -use crate::{Error, Result}; +use crate::Result; pub struct NullBackend {} diff --git a/crates/sound/src/audio_backends/pw_backend.rs b/crates/sound/src/audio_backends/pw_backend.rs index 6e9194f..945401a 100644 --- a/crates/sound/src/audio_backends/pw_backend.rs +++ b/crates/sound/src/audio_backends/pw_backend.rs @@ -4,22 +4,11 @@ use super::AudioBackend; use std::{thread}; use std::{cell::Cell, rc::Rc}; -use crate::{Error, Result}; +use crate::Result; -use vm_memory::{Le32, Le64}; +use vm_memory::Le32; use pipewire as pw; -use pw::{sys::*}; - -#[derive(Default, Debug)] -pub struct StreamInfo { - pub id: usize, - pub params: PCMParams, - pub formats: Le64, - pub rates: Le64, - pub direction: u8, - pub channels_min: u8, - pub channels_max: u8, -} +use pw::sys::PW_ID_CORE; #[derive(Default, Debug)] pub struct PCMParams { diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index 4f42df5..d0a3653 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -21,6 +21,7 @@ use vmm_sys_util::{ use crate::audio_backends::{alloc_audio_backend, AudioBackend}; use crate::virtio_sound::*; use crate::{Error, Result, SoundConfig}; +use vm_memory::{Le32, Le64}; pub const SUPPORTED_FORMATS: u64 = 1 << VIRTIO_SND_PCM_FMT_U8 | 1 << VIRTIO_SND_PCM_FMT_S16 @@ -35,6 +36,30 @@ pub const SUPPORTED_RATES: u64 = 1 << VIRTIO_SND_PCM_RATE_8000 | 1 << VIRTIO_SND_PCM_RATE_44100 | 1 << VIRTIO_SND_PCM_RATE_48000; +pub const NR_STREAMS: usize = 1; + +pub struct StreamInfo { + pub features: Le32, /* 1 << VIRTIO_SND_PCM_F_XXX */ + pub formats: Le64, /* 1 << VIRTIO_SND_PCM_FMT_XXX */ + pub rates: Le64, /* 1 << VIRTIO_SND_PCM_RATE_XXX */ + pub direction: u8, + pub channels_min: u8, + pub channels_max: u8, +} + +impl StreamInfo { + pub fn output() -> Self { + Self { + features : 0.into(), + formats : SUPPORTED_FORMATS.into(), + rates : SUPPORTED_RATES.into(), + direction : VIRTIO_SND_D_OUTPUT, + channels_min : 1, + channels_max : 6, + } + } +} + struct VhostUserSoundThread { mem: Option>, event_idx: bool, @@ -72,7 +97,7 @@ impl VhostUserSoundThread { Ok(()) } - fn handle_event(&self, device_event: u16, vrings: &[VringRwLock], audio_backend: &RwLock>) -> IoResult { + fn handle_event(&self, device_event: u16, vrings: &[VringRwLock], audio_backend: &RwLock>, stream_info: &[StreamInfo]) -> IoResult { let vring = &vrings[device_event as usize]; let queue_idx = self.queue_indexes[device_event as usize]; debug!("handle event call queue: {}", queue_idx); @@ -88,14 +113,14 @@ impl VhostUserSoundThread { // new requests on the queue. loop { vring.disable_notification().unwrap(); - self.process_control(vring)?; + self.process_control(vring, stream_info)?; if !vring.enable_notification().unwrap() { break; } } } else { // Without EVENT_IDX, a single call is enough. - self.process_control(vring)?; + self.process_control(vring, stream_info)?; } } EVENT_QUEUE_IDX => { @@ -131,7 +156,7 @@ impl VhostUserSoundThread { } /// Process the messages in the vring and dispatch replies - fn process_control(&self, vring: &VringRwLock) -> Result { + fn process_control(&self, vring: &VringRwLock, stream_info: &[StreamInfo]) -> Result { let requests: Vec = vring .get_mut() .get_queue_mut() @@ -174,6 +199,7 @@ impl VhostUserSoundThread { let response = VirtioSoundHeader { code: VIRTIO_SND_S_OK.into(), }; let mut len = desc_response.len() as u32; + let request_type = hdr_request.code.to_native(); match request_type { VIRTIO_SND_R_JACK_INFO => todo!(), @@ -192,42 +218,51 @@ impl VhostUserSoundThread { let start_id: usize = u32::from(query_info.start_id) as usize; let count: usize = u32::from(query_info.count) as usize; - let mut pcm_info = vec![VirtioSoundPcmInfo::default(); count]; - for pcm in &mut pcm_info { - pcm.hdr.hda_fn_nid = 0.into(); - pcm.features = 0.into(); - pcm.formats = SUPPORTED_FORMATS.into(); - pcm.rates = SUPPORTED_RATES.into(); - pcm.direction = VIRTIO_SND_D_OUTPUT; - pcm.channels_min = 1; - pcm.channels_max = 6; - pcm.padding = [0; 5]; - } - if start_id + count > pcm_info.len() { + + if start_id + count > stream_info.len() { error!( "start_id({}) + count({}) must be smaller than the number of streams ({})", start_id, count, - pcm_info.len() + stream_info.len() ); desc_chain .memory() .write_obj(VIRTIO_SND_S_BAD_MSG, desc_response.addr()) .map_err(|_| Error::DescriptorWriteFailed)?; - } - desc_chain - .memory() - .write_obj(response, desc_response.addr()) - .map_err(|_| Error::DescriptorWriteFailed)?; - //let mut len = desc_response.len() as u32; - - for i in start_id..(start_id + count) { + } else { desc_chain .memory() - .write_slice(pcm_info[i].as_slice(), desc_pcm.addr()) + .write_obj(response, desc_response.addr()) .map_err(|_| Error::DescriptorWriteFailed)?; + + let mut buf = vec![]; + + for i in start_id..start_id+count { + let pcm_info = VirtioSoundPcmInfo { + hdr : VirtioSoundInfo { + hda_fn_nid : Le32::from(i as u32) + }, + features : stream_info[i].features, + formats : stream_info[i].formats, + rates : stream_info[i].rates, + direction : stream_info[i].direction, + channels_min : stream_info[i].channels_min, + channels_max : stream_info[i].channels_max, + padding : [0; 5] + }; + buf.extend_from_slice(pcm_info.as_slice()); + }; + + // TODO: to support the case when the number of items + // do not fit in a single descriptor + desc_chain + .memory() + .write_slice(&buf, desc_pcm.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + + len += desc_pcm.len(); } - len += desc_pcm.len(); }, VIRTIO_SND_R_CHMAP_INFO => todo!(), VIRTIO_SND_R_JACK_REMAP => todo!(), @@ -424,7 +459,8 @@ pub struct VhostUserSoundBackend { threads: Vec>, virtio_cfg: VirtioSoundConfig, exit_event: EventFd, - _audio_backend: RwLock>, + audio_backend: RwLock>, + streams_info: Vec } type SndDescriptorChain = DescriptorChain>>; @@ -451,15 +487,22 @@ impl VhostUserSoundBackend { let audio_backend = alloc_audio_backend(config.audio_backend_name)?; + let mut streams = Vec::::with_capacity(NR_STREAMS); + + let stream_out_info = StreamInfo::output(); + // TODO: to add a input stream + streams.push(stream_out_info); + Ok(Self { threads, virtio_cfg: VirtioSoundConfig { jacks: 0.into(), - streams: 1.into(), + streams: Le32::from(streams.len() as u32), chmaps: 0.into(), }, + streams_info: streams, exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, - _audio_backend: RwLock::new(audio_backend), + audio_backend: RwLock::new(audio_backend), }) } @@ -517,7 +560,7 @@ impl VhostUserBackend for VhostUserSoundBackend { self.threads[thread_id] .read() .unwrap() - .handle_event(device_event, vrings, &self._audio_backend) + .handle_event(device_event, vrings, &self.audio_backend, &self.streams_info) } fn get_config(&self, offset: u32, size: u32) -> Vec { From 08078b752ce9f8a5b70a7a7c941fd094d4de4aa4 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Mon, 10 Jul 2023 18:58:06 +0200 Subject: [PATCH 20/44] instantiate new backend with PW thread loop functions Signed-off-by: Dorinda Bassey --- crates/sound/src/audio_backends/pw_backend.rs | 140 +++++++++++++----- 1 file changed, 103 insertions(+), 37 deletions(-) diff --git a/crates/sound/src/audio_backends/pw_backend.rs b/crates/sound/src/audio_backends/pw_backend.rs index 945401a..3684185 100644 --- a/crates/sound/src/audio_backends/pw_backend.rs +++ b/crates/sound/src/audio_backends/pw_backend.rs @@ -2,13 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use super::AudioBackend; -use std::{thread}; -use std::{cell::Cell, rc::Rc}; use crate::Result; +use std::ops::Deref; +use std::ptr; +use std::ptr::NonNull; +use std::sync::Arc; -use vm_memory::Le32; use pipewire as pw; -use pw::sys::PW_ID_CORE; +use pw::sys::{pw_loop, pw_thread_loop_new, pw_thread_loop_signal, PW_ID_CORE}; +use pw::sys::{pw_thread_loop, pw_thread_loop_start, pw_thread_loop_wait}; +use pw::sys::{pw_thread_loop_get_loop, pw_thread_loop_lock, pw_thread_loop_unlock}; +use pw::Core; +use pw::LoopRef; +use vm_memory::Le32; #[derive(Default, Debug)] pub struct PCMParams { @@ -22,61 +28,121 @@ pub struct PCMParams { pub rate: u8, } +pub struct PwThreadLoop(NonNull); + +impl PwThreadLoop { + pub fn new(name: Option<&str>) -> Option { + let inner = unsafe { + pw_thread_loop_new( + name.map_or(ptr::null(), |p| p.as_ptr() as *const _), + std::ptr::null_mut(), + ) + }; + if inner.is_null() { + None + } else { + Some(Self( + NonNull::new(inner).expect("pw_thread_loop can't be null"), + )) + } + } + + pub fn get_loop(&self) -> PwThreadLoopTheLoop { + let inner = unsafe { pw_thread_loop_get_loop(self.0.as_ptr()) }; + PwThreadLoopTheLoop(NonNull::new(inner).unwrap()) + } + + pub fn unlock(&self) { + unsafe { pw_thread_loop_unlock(self.0.as_ptr()) } + } + + pub fn lock(&self) { + unsafe { pw_thread_loop_lock(self.0.as_ptr()) } + } + + pub fn start(&self) { + unsafe { + pw_thread_loop_start(self.0.as_ptr()); + } + } + + pub fn signal(&self) { + unsafe { + pw_thread_loop_signal(self.0.as_ptr(), false); + } + } + + pub fn wait(&self) { + unsafe { + pw_thread_loop_wait(self.0.as_ptr()); + } + } +} + +#[derive(Debug, Clone)] +pub struct PwThreadLoopTheLoop(NonNull); + +impl AsRef for PwThreadLoopTheLoop { + fn as_ref(&self) -> &LoopRef { + self.deref() + } +} + +impl Deref for PwThreadLoopTheLoop { + type Target = LoopRef; + + fn deref(&self) -> &Self::Target { + unsafe { &*(self.0.as_ptr() as *mut LoopRef) } + } +} + // SAFETY: Safe as the structure can be sent to another thread. -unsafe impl Send for WrapMainLoop {} +unsafe impl Send for PwBackend {} // SAFETY: Safe as the structure can be shared with another thread as the state // is protected with a lock. -unsafe impl Sync for WrapMainLoop {} +unsafe impl Sync for PwBackend {} -#[derive(Clone, Debug)] -pub struct WrapMainLoop { - mainloop: pipewire::MainLoop, -} pub struct PwBackend { //pub streams: Arc>>, + pub thread_loop: Arc, + pub core: Core, } impl PwBackend { pub fn new() -> Self { pw::init(); - let wrap_mainloop = WrapMainLoop { - mainloop : pw::MainLoop::new().expect("we can't create mainloop") - }; - //let mainloop = pw::MainLoop::new().expect("Failed to create Pipewire Mainloop"); - let context = pw::Context::new(&wrap_mainloop.mainloop).expect("Failed to create Pipewire Context"); - let core = context - .connect(None) - .expect("Failed to connect to Pipewire Core"); + let thread_loop = Arc::new(PwThreadLoop::new(Some("Pipewire thread loop")).unwrap()); + let get_loop = thread_loop.get_loop(); - // To comply with Rust's safety rules, we wrap this variable in an `Rc` and a `Cell`. - let done = Rc::new(Cell::new(false)); + thread_loop.lock(); - // Create new reference for each variable so that they can be moved into the closure. - let done_clone = done.clone(); - let loop_clone = wrap_mainloop.mainloop.clone(); + let context = pw::Context::new(&get_loop).expect("failed to create context"); + thread_loop.start(); + let core = context.connect(None).expect("Failed to connect to core"); + // Create new reference for the variable so that it can be moved into the closure. + let thread_clone = thread_loop.clone(); + + // Trigger the sync event. The server's answer won't be processed until we start the thread loop, + // so we can safely do this before setting up a callback. This lets us avoid using a Cell. let pending = core.sync(0).expect("sync failed"); let _listener_core = core - .add_listener_local() - .done(move |id, seq| { - if id == PW_ID_CORE && seq == pending { - done_clone.set(true); - loop_clone.quit(); - } - }) - .register(); + .add_listener_local() + .done(move |id, seq| { + if id == PW_ID_CORE && seq == pending { + thread_clone.signal(); + } + }) + .register(); - thread::spawn(move || { - wrap_mainloop.mainloop.run(); - }); + thread_loop.wait(); + thread_loop.unlock(); println!("pipewire backend running"); - Self { - } - + Self { thread_loop, core } } } From 03241612a7e9961ee788e35b4a6f00bd78f257d1 Mon Sep 17 00:00:00 2001 From: Matias Ezequiel Vara Larsen Date: Wed, 5 Jul 2023 17:19:04 +0200 Subject: [PATCH 21/44] Sound: Add set_param() This commit adds the set_param() method. This method can only be called if the stream is in the "set parameters", "prepare" or "release" state. For the pw backend, this method only updates the internal values for the given stream. Signed-off-by: Matias Ezequiel Vara Larsen --- crates/sound/src/audio_backends.rs | 3 +- crates/sound/src/audio_backends/pw_backend.rs | 33 +++--- crates/sound/src/lib.rs | 14 ++- crates/sound/src/vhu_sound.rs | 101 +++++++++--------- 4 files changed, 85 insertions(+), 66 deletions(-) diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 205d753..9eb5aa2 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -9,7 +9,8 @@ mod pw_backend; #[cfg(feature = "null-backend")] use self::null::NullBackend; #[cfg(feature = "pw-backend")] -use self::pw_backend::{PwBackend, PCMParams}; +use crate::PCMParams; +use self::pw_backend::PwBackend; use crate::{Error, Result}; pub trait AudioBackend { diff --git a/crates/sound/src/audio_backends/pw_backend.rs b/crates/sound/src/audio_backends/pw_backend.rs index 3684185..dbf034b 100644 --- a/crates/sound/src/audio_backends/pw_backend.rs +++ b/crates/sound/src/audio_backends/pw_backend.rs @@ -7,6 +7,9 @@ use std::ops::Deref; use std::ptr; use std::ptr::NonNull; use std::sync::Arc; +use crate::vhu_sound::NR_STREAMS; +use crate::PCMParams; +use std::sync::RwLock; use pipewire as pw; use pw::sys::{pw_loop, pw_thread_loop_new, pw_thread_loop_signal, PW_ID_CORE}; @@ -14,19 +17,6 @@ use pw::sys::{pw_thread_loop, pw_thread_loop_start, pw_thread_loop_wait}; use pw::sys::{pw_thread_loop_get_loop, pw_thread_loop_lock, pw_thread_loop_unlock}; use pw::Core; use pw::LoopRef; -use vm_memory::Le32; - -#[derive(Default, Debug)] -pub struct PCMParams { - pub features: Le32, - /// size of hardware buffer in bytes - pub buffer_bytes: Le32, - /// size of hardware period in bytes - pub period_bytes: Le32, - pub channels: u8, - pub format: u8, - pub rate: u8, -} pub struct PwThreadLoop(NonNull); @@ -104,9 +94,9 @@ unsafe impl Send for PwBackend {} unsafe impl Sync for PwBackend {} pub struct PwBackend { - //pub streams: Arc>>, pub thread_loop: Arc, pub core: Core, + pub stream_params: RwLock>, } impl PwBackend { @@ -142,7 +132,14 @@ impl PwBackend { println!("pipewire backend running"); - Self { thread_loop, core } + let streams_param = vec![PCMParams::default(); NR_STREAMS]; + + Self { + thread_loop, + core, + stream_params : RwLock::new(streams_param) + } + } } @@ -161,7 +158,11 @@ impl AudioBackend for PwBackend { */ Ok(()) } - fn set_param(&self, _stream_id: u32, _params: PCMParams) -> Result<()> { + + fn set_param(&self, stream_id: u32, params: PCMParams) -> Result<()> { + let mut stream_params = self.stream_params.write().unwrap(); + stream_params[stream_id as usize] = params; Ok(()) } + } diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index 9c8c797..3459937 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -11,7 +11,7 @@ use log::{info, warn}; use thiserror::Error as ThisError; use vhost::{vhost_user, vhost_user::Listener}; use vhost_user_backend::VhostUserDaemon; -use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap, VolatileSlice}; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap, VolatileSlice, Le32}; use crate::vhu_sound::VhostUserSoundBackend; @@ -58,6 +58,18 @@ impl std::convert::From for IoError { } } +#[derive(Default, Clone)] +pub struct PCMParams { + pub features: Le32, + /// size of hardware buffer in bytes + pub buffer_bytes: Le32, + /// size of hardware period in bytes + pub period_bytes: Le32, + pub channels: u8, + pub format: u8, + pub rate: u8, +} + #[derive(Debug, Clone)] /// This structure is the public API through which an external program /// is allowed to configure the backend. diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index d0a3653..ee10975 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -3,6 +3,7 @@ use std::mem::size_of; use std::sync::RwLock; use std::{io::Result as IoResult, u16, u32, u64, u8}; +use std::sync::Arc; use log::{error, debug}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; @@ -22,6 +23,7 @@ use crate::audio_backends::{alloc_audio_backend, AudioBackend}; use crate::virtio_sound::*; use crate::{Error, Result, SoundConfig}; use vm_memory::{Le32, Le64}; +use crate::PCMParams; pub const SUPPORTED_FORMATS: u64 = 1 << VIRTIO_SND_PCM_FMT_U8 | 1 << VIRTIO_SND_PCM_FMT_S16 @@ -64,16 +66,18 @@ struct VhostUserSoundThread { mem: Option>, event_idx: bool, queue_indexes: Vec, + audio_backend: Arc> } impl VhostUserSoundThread { - pub fn new(mut queue_indexes: Vec) -> Result { + pub fn new(mut queue_indexes: Vec, audio_backend: Arc>) -> Result { queue_indexes.sort(); Ok(VhostUserSoundThread { event_idx: false, mem: None, queue_indexes, + audio_backend }) } @@ -97,7 +101,7 @@ impl VhostUserSoundThread { Ok(()) } - fn handle_event(&self, device_event: u16, vrings: &[VringRwLock], audio_backend: &RwLock>, stream_info: &[StreamInfo]) -> IoResult { + fn handle_event(&self, device_event: u16, vrings: &[VringRwLock], stream_info: &[StreamInfo]) -> IoResult { let vring = &vrings[device_event as usize]; let queue_idx = self.queue_indexes[device_event as usize]; debug!("handle event call queue: {}", queue_idx); @@ -135,14 +139,14 @@ impl VhostUserSoundThread { // new requests on the queue. loop { vring.disable_notification().unwrap(); - self.process_tx(vring, &audio_backend)?; + self.process_tx(vring)?; if !vring.enable_notification().unwrap() { break; } } } else { // Without EVENT_IDX, a single call is enough. - self.process_tx(vring, &audio_backend)?; + self.process_tx(vring)?; } } RX_QUEUE_IDX => { @@ -164,6 +168,8 @@ impl VhostUserSoundThread { .map_err(|_| Error::DescriptorNotFound)? .collect(); + let audio_backend = &self.audio_backend; + debug!("Requests to process: {}", requests.len()); if requests.is_empty() { debug!("yes, it's empty"); @@ -180,7 +186,7 @@ impl VhostUserSoundThread { } let read_desc_len: usize = desc_request.len() as usize; let header_size = size_of::(); - if (read_desc_len as usize) < header_size { + if read_desc_len < header_size { return Err(Error::UnexpectedMinimumDescriptorSize( header_size, read_desc_len, @@ -196,9 +202,9 @@ impl VhostUserSoundThread { return Err(Error::UnexpectedReadableDescriptor(1)); } - let response = VirtioSoundHeader { code: VIRTIO_SND_S_OK.into(), }; + let mut response = VirtioSoundHeader { code: VIRTIO_SND_S_OK.into(), }; - let mut len = desc_response.len() as u32; + let mut len = desc_response.len(); let request_type = hdr_request.code.to_native(); match request_type { @@ -238,17 +244,17 @@ impl VhostUserSoundThread { let mut buf = vec![]; - for i in start_id..start_id+count { + for (i, stream) in stream_info.iter().enumerate().skip(start_id).take(count) { let pcm_info = VirtioSoundPcmInfo { hdr : VirtioSoundInfo { hda_fn_nid : Le32::from(i as u32) }, - features : stream_info[i].features, - formats : stream_info[i].formats, - rates : stream_info[i].rates, - direction : stream_info[i].direction, - channels_min : stream_info[i].channels_min, - channels_max : stream_info[i].channels_max, + features : stream.features, + formats : stream.formats, + rates : stream.rates, + direction : stream.direction, + channels_min : stream.channels_min, + channels_max : stream.channels_max, padding : [0; 5] }; buf.extend_from_slice(pcm_info.as_slice()); @@ -275,35 +281,36 @@ impl VhostUserSoundThread { .memory() .read_obj::(desc_request.addr()) .map_err(|_| Error::DescriptorReadFailed)?; - let stream_id: usize = u32::from(set_params.hdr.stream_id) as usize; - let buffer_bytes: u32 = set_params.buffer_bytes.into(); - let period_bytes: u32 = set_params.period_bytes.into(); - let features: u32 = set_params.features.into(); - dbg!("stream_id: {}", stream_id ); - dbg!("set params format: {}", set_params.format); - dbg!("set params rate: {} ", set_params.rate); - dbg!("set params channels: {} ", set_params.channels); - if features != 0 { + let params = PCMParams { + buffer_bytes : set_params.buffer_bytes, + period_bytes : set_params.period_bytes, + features : set_params.features, + rate : set_params.rate, + format : set_params.format, + channels : set_params.channels + }; + + let stream_id = set_params.hdr.stream_id.to_native(); + + if params.features != 0 { error!("No feature is supported"); - desc_chain - .memory() - .write_obj(VIRTIO_SND_S_NOT_SUPP, desc_response.addr()) - .map_err(|_| Error::DescriptorWriteFailed)?; - } - if buffer_bytes % period_bytes != 0 { + response = VirtioSoundHeader { code: VIRTIO_SND_S_NOT_SUPP.into() }; + } else if set_params.buffer_bytes.to_native() % set_params.period_bytes.to_native() != 0 { + response = VirtioSoundHeader { code: VIRTIO_SND_S_BAD_MSG.into() }; error!("buffer_bytes({}) must be dividable by period_bytes({})", - buffer_bytes, period_bytes); - desc_chain - .memory() - .write_obj(VIRTIO_SND_S_BAD_MSG, desc_response.addr()) - .map_err(|_| Error::DescriptorWriteFailed)?; + set_params.buffer_bytes.to_native(), set_params.period_bytes.to_native()); + } else if audio_backend.set_param(stream_id, params) + .is_err() { + error!("IO error during set_param()"); + response = VirtioSoundHeader { code: VIRTIO_SND_S_IO_ERR.into() }; } desc_chain .memory() .write_obj(response, desc_response.addr()) .map_err(|_| Error::DescriptorWriteFailed)?; - len = desc_response.len() as u32; + + len = desc_response.len(); }, VIRTIO_SND_R_PCM_PREPARE | VIRTIO_SND_R_PCM_START @@ -320,7 +327,7 @@ impl VhostUserSoundThread { .memory() .write_obj(response, desc_response.addr()) .map_err(|_| Error::DescriptorWriteFailed)?; - len = desc_response.len() as u32; + len = desc_response.len(); }, _ => { error!( @@ -350,7 +357,7 @@ impl VhostUserSoundThread { Ok(false) } - fn process_tx(&self, vring: &VringRwLock, _audio_backend: &RwLock>) -> Result { + fn process_tx(&self, vring: &VringRwLock) -> Result { let requests: Vec = vring .get_mut() .get_queue_mut() @@ -431,7 +438,7 @@ impl VhostUserSoundThread { .write_obj(response, desc_response.addr()) .map_err(|_| Error::DescriptorWriteFailed)?; - let len = desc_response.len() as u32; + let len = desc_response.len(); if vring .add_used(desc_chain.head_index(), len) @@ -459,7 +466,6 @@ pub struct VhostUserSoundBackend { threads: Vec>, virtio_cfg: VirtioSoundConfig, exit_event: EventFd, - audio_backend: RwLock>, streams_info: Vec } @@ -467,14 +473,16 @@ type SndDescriptorChain = DescriptorChain Result { + let audio_backend = alloc_audio_backend(config.audio_backend_name)?; + let audio_backend_arc = Arc::new(audio_backend); let threads = if config.multi_thread { vec![ RwLock::new(VhostUserSoundThread::new(vec![ CONTROL_QUEUE_IDX, EVENT_QUEUE_IDX, - ])?), - RwLock::new(VhostUserSoundThread::new(vec![TX_QUEUE_IDX])?), - RwLock::new(VhostUserSoundThread::new(vec![RX_QUEUE_IDX])?), + ], audio_backend_arc.clone())?), + RwLock::new(VhostUserSoundThread::new(vec![TX_QUEUE_IDX], Arc::clone(&audio_backend_arc))?), + RwLock::new(VhostUserSoundThread::new(vec![RX_QUEUE_IDX], Arc::clone(&audio_backend_arc))?), ] } else { vec![RwLock::new(VhostUserSoundThread::new(vec![ @@ -482,11 +490,9 @@ impl VhostUserSoundBackend { EVENT_QUEUE_IDX, TX_QUEUE_IDX, RX_QUEUE_IDX, - ])?)] + ], Arc::clone(&audio_backend_arc))?)] }; - let audio_backend = alloc_audio_backend(config.audio_backend_name)?; - let mut streams = Vec::::with_capacity(NR_STREAMS); let stream_out_info = StreamInfo::output(); @@ -501,8 +507,7 @@ impl VhostUserSoundBackend { chmaps: 0.into(), }, streams_info: streams, - exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, - audio_backend: RwLock::new(audio_backend), + exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)? }) } @@ -560,7 +565,7 @@ impl VhostUserBackend for VhostUserSoundBackend { self.threads[thread_id] .read() .unwrap() - .handle_event(device_event, vrings, &self.audio_backend, &self.streams_info) + .handle_event(device_event, vrings, &self.streams_info) } fn get_config(&self, offset: u32, size: u32) -> Vec { From e49e8117d7cab941686b22de46e721f934c39ec6 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Tue, 11 Jul 2023 18:23:33 +0200 Subject: [PATCH 22/44] cargo fmt Signed-off-by: Dorinda Bassey --- crates/sound/src/audio_backends.rs | 2 +- crates/sound/src/audio_backends/null.rs | 1 - crates/sound/src/audio_backends/pw_backend.rs | 18 +- crates/sound/src/lib.rs | 2 +- crates/sound/src/vhu_sound.rs | 190 ++++++++++-------- 5 files changed, 123 insertions(+), 90 deletions(-) diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 9eb5aa2..1a49d8f 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -8,9 +8,9 @@ mod pw_backend; #[cfg(feature = "null-backend")] use self::null::NullBackend; +use self::pw_backend::PwBackend; #[cfg(feature = "pw-backend")] use crate::PCMParams; -use self::pw_backend::PwBackend; use crate::{Error, Result}; pub trait AudioBackend { diff --git a/crates/sound/src/audio_backends/null.rs b/crates/sound/src/audio_backends/null.rs index 9b1ec24..e4cfb2a 100644 --- a/crates/sound/src/audio_backends/null.rs +++ b/crates/sound/src/audio_backends/null.rs @@ -18,7 +18,6 @@ impl AudioBackend for NullBackend { } fn read(&self, _stream_id: u32) -> Result<()> { - /* let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?; let zero_mem = vec![0u8; buf.len()]; diff --git a/crates/sound/src/audio_backends/pw_backend.rs b/crates/sound/src/audio_backends/pw_backend.rs index dbf034b..b6c2c58 100644 --- a/crates/sound/src/audio_backends/pw_backend.rs +++ b/crates/sound/src/audio_backends/pw_backend.rs @@ -2,13 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use super::AudioBackend; +use crate::vhu_sound::NR_STREAMS; +use crate::PCMParams; use crate::Result; use std::ops::Deref; use std::ptr; use std::ptr::NonNull; use std::sync::Arc; -use crate::vhu_sound::NR_STREAMS; -use crate::PCMParams; use std::sync::RwLock; use pipewire as pw; @@ -18,7 +18,7 @@ use pw::sys::{pw_thread_loop_get_loop, pw_thread_loop_lock, pw_thread_loop_unloc use pw::Core; use pw::LoopRef; -pub struct PwThreadLoop(NonNull); +struct PwThreadLoop(NonNull); impl PwThreadLoop { pub fn new(name: Option<&str>) -> Option { @@ -70,7 +70,7 @@ impl PwThreadLoop { } #[derive(Debug, Clone)] -pub struct PwThreadLoopTheLoop(NonNull); +struct PwThreadLoopTheLoop(NonNull); impl AsRef for PwThreadLoopTheLoop { fn as_ref(&self) -> &LoopRef { @@ -94,7 +94,7 @@ unsafe impl Send for PwBackend {} unsafe impl Sync for PwBackend {} pub struct PwBackend { - pub thread_loop: Arc, + thread_loop: Arc, pub core: Core, pub stream_params: RwLock>, } @@ -137,9 +137,8 @@ impl PwBackend { Self { thread_loop, core, - stream_params : RwLock::new(streams_param) + stream_params: RwLock::new(streams_param), } - } } @@ -165,4 +164,9 @@ impl AudioBackend for PwBackend { Ok(()) } + fn prepare(&self, _stream_id: u32) -> Result<()> { + self.thread_loop.lock(); + self.thread_loop.unlock(); + Ok(()) + } } diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index 3459937..e53cd61 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -11,7 +11,7 @@ use log::{info, warn}; use thiserror::Error as ThisError; use vhost::{vhost_user, vhost_user::Listener}; use vhost_user_backend::VhostUserDaemon; -use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap, VolatileSlice, Le32}; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap, Le32, VolatileSlice}; use crate::vhu_sound::VhostUserSoundBackend; diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs index ee10975..b98399d 100644 --- a/crates/sound/src/vhu_sound.rs +++ b/crates/sound/src/vhu_sound.rs @@ -1,11 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use std::mem::size_of; +use std::sync::Arc; use std::sync::RwLock; use std::{io::Result as IoResult, u16, u32, u64, u8}; -use std::sync::Arc; -use log::{error, debug}; +use log::{debug, error}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost_user_backend::{VhostUserBackend, VringRwLock, VringT}; use virtio_bindings::bindings::{ @@ -13,7 +13,9 @@ use virtio_bindings::bindings::{ virtio_ring::VIRTIO_RING_F_EVENT_IDX, virtio_ring::VIRTIO_RING_F_INDIRECT_DESC, }; use virtio_queue::{DescriptorChain, QueueOwnedT}; -use vm_memory::{Bytes, ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap, GuestMemoryLoadGuard}; +use vm_memory::{ + ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, +}; use vmm_sys_util::{ epoll::EventSet, eventfd::{EventFd, EFD_NONBLOCK}, @@ -21,9 +23,9 @@ use vmm_sys_util::{ use crate::audio_backends::{alloc_audio_backend, AudioBackend}; use crate::virtio_sound::*; +use crate::PCMParams; use crate::{Error, Result, SoundConfig}; use vm_memory::{Le32, Le64}; -use crate::PCMParams; pub const SUPPORTED_FORMATS: u64 = 1 << VIRTIO_SND_PCM_FMT_U8 | 1 << VIRTIO_SND_PCM_FMT_S16 @@ -52,12 +54,12 @@ pub struct StreamInfo { impl StreamInfo { pub fn output() -> Self { Self { - features : 0.into(), - formats : SUPPORTED_FORMATS.into(), - rates : SUPPORTED_RATES.into(), - direction : VIRTIO_SND_D_OUTPUT, - channels_min : 1, - channels_max : 6, + features: 0.into(), + formats: SUPPORTED_FORMATS.into(), + rates: SUPPORTED_RATES.into(), + direction: VIRTIO_SND_D_OUTPUT, + channels_min: 1, + channels_max: 6, } } } @@ -66,18 +68,21 @@ struct VhostUserSoundThread { mem: Option>, event_idx: bool, queue_indexes: Vec, - audio_backend: Arc> + audio_backend: Arc>, } impl VhostUserSoundThread { - pub fn new(mut queue_indexes: Vec, audio_backend: Arc>) -> Result { + pub fn new( + mut queue_indexes: Vec, + audio_backend: Arc>, + ) -> Result { queue_indexes.sort(); Ok(VhostUserSoundThread { event_idx: false, mem: None, queue_indexes, - audio_backend + audio_backend, }) } @@ -101,7 +106,12 @@ impl VhostUserSoundThread { Ok(()) } - fn handle_event(&self, device_event: u16, vrings: &[VringRwLock], stream_info: &[StreamInfo]) -> IoResult { + fn handle_event( + &self, + device_event: u16, + vrings: &[VringRwLock], + stream_info: &[StreamInfo], + ) -> IoResult { let vring = &vrings[device_event as usize]; let queue_idx = self.queue_indexes[device_event as usize]; debug!("handle event call queue: {}", queue_idx); @@ -202,7 +212,9 @@ impl VhostUserSoundThread { return Err(Error::UnexpectedReadableDescriptor(1)); } - let mut response = VirtioSoundHeader { code: VIRTIO_SND_S_OK.into(), }; + let mut response = VirtioSoundHeader { + code: VIRTIO_SND_S_OK.into(), + }; let mut len = desc_response.len(); @@ -244,21 +256,22 @@ impl VhostUserSoundThread { let mut buf = vec![]; - for (i, stream) in stream_info.iter().enumerate().skip(start_id).take(count) { + for (i, stream) in stream_info.iter().enumerate().skip(start_id).take(count) + { let pcm_info = VirtioSoundPcmInfo { - hdr : VirtioSoundInfo { - hda_fn_nid : Le32::from(i as u32) + hdr: VirtioSoundInfo { + hda_fn_nid: Le32::from(i as u32), }, - features : stream.features, - formats : stream.formats, - rates : stream.rates, - direction : stream.direction, - channels_min : stream.channels_min, - channels_max : stream.channels_max, - padding : [0; 5] + features: stream.features, + formats: stream.formats, + rates: stream.rates, + direction: stream.direction, + channels_min: stream.channels_min, + channels_max: stream.channels_max, + padding: [0; 5], }; buf.extend_from_slice(pcm_info.as_slice()); - }; + } // TODO: to support the case when the number of items // do not fit in a single descriptor @@ -269,7 +282,7 @@ impl VhostUserSoundThread { len += desc_pcm.len(); } - }, + } VIRTIO_SND_R_CHMAP_INFO => todo!(), VIRTIO_SND_R_JACK_REMAP => todo!(), VIRTIO_SND_R_PCM_SET_PARAMS => { @@ -283,27 +296,38 @@ impl VhostUserSoundThread { .map_err(|_| Error::DescriptorReadFailed)?; let params = PCMParams { - buffer_bytes : set_params.buffer_bytes, - period_bytes : set_params.period_bytes, - features : set_params.features, - rate : set_params.rate, - format : set_params.format, - channels : set_params.channels + buffer_bytes: set_params.buffer_bytes, + period_bytes: set_params.period_bytes, + features: set_params.features, + rate: set_params.rate, + format: set_params.format, + channels: set_params.channels, }; let stream_id = set_params.hdr.stream_id.to_native(); if params.features != 0 { error!("No feature is supported"); - response = VirtioSoundHeader { code: VIRTIO_SND_S_NOT_SUPP.into() }; - } else if set_params.buffer_bytes.to_native() % set_params.period_bytes.to_native() != 0 { - response = VirtioSoundHeader { code: VIRTIO_SND_S_BAD_MSG.into() }; - error!("buffer_bytes({}) must be dividable by period_bytes({})", - set_params.buffer_bytes.to_native(), set_params.period_bytes.to_native()); - } else if audio_backend.set_param(stream_id, params) - .is_err() { - error!("IO error during set_param()"); - response = VirtioSoundHeader { code: VIRTIO_SND_S_IO_ERR.into() }; + response = VirtioSoundHeader { + code: VIRTIO_SND_S_NOT_SUPP.into(), + }; + } else if set_params.buffer_bytes.to_native() + % set_params.period_bytes.to_native() + != 0 + { + response = VirtioSoundHeader { + code: VIRTIO_SND_S_BAD_MSG.into(), + }; + error!( + "buffer_bytes({}) must be dividable by period_bytes({})", + set_params.buffer_bytes.to_native(), + set_params.period_bytes.to_native() + ); + } else if audio_backend.set_param(stream_id, params).is_err() { + error!("IO error during set_param()"); + response = VirtioSoundHeader { + code: VIRTIO_SND_S_IO_ERR.into(), + }; } desc_chain .memory() @@ -311,7 +335,7 @@ impl VhostUserSoundThread { .map_err(|_| Error::DescriptorWriteFailed)?; len = desc_response.len(); - }, + } VIRTIO_SND_R_PCM_PREPARE | VIRTIO_SND_R_PCM_START | VIRTIO_SND_R_PCM_STOP @@ -321,14 +345,14 @@ impl VhostUserSoundThread { .read_obj::(desc_request.addr()) .map_err(|_| Error::DescriptorReadFailed)?; let stream_id: usize = u32::from(pcm_hdr.stream_id) as usize; - dbg!("stream_id: {}", stream_id ); + dbg!("stream_id: {}", stream_id); desc_chain .memory() .write_obj(response, desc_response.addr()) .map_err(|_| Error::DescriptorWriteFailed)?; len = desc_response.len(); - }, + } _ => { error!( "virtio-snd: Unknown control queue message code: {}", @@ -336,10 +360,7 @@ impl VhostUserSoundThread { ); } }; - if vring - .add_used(desc_chain.head_index(), len) - .is_err() - { + if vring.add_used(desc_chain.head_index(), len).is_err() { error!("Couldn't return used descriptors to the ring"); } } @@ -388,7 +409,10 @@ impl VhostUserSoundThread { return Err(Error::UnexpectedReadableDescriptor(1)); } - let response = VirtioSoundPcmStatus { status: VIRTIO_SND_S_OK.into(), latency_bytes: 0.into() }; + let response = VirtioSoundPcmStatus { + status: VIRTIO_SND_S_OK.into(), + latency_bytes: 0.into(), + }; let desc_request = descriptors[0]; @@ -403,11 +427,11 @@ impl VhostUserSoundThread { return Err(Error::UnexpectedWriteOnlyDescriptor(1)); } - let mut all_bufs=Vec::::new(); - let data_descs = &descriptors[1..descriptors.len() -1]; + let mut all_bufs = Vec::::new(); + let data_descs = &descriptors[1..descriptors.len() - 1]; - for data in data_descs{ - if data.is_write_only(){ + for data in data_descs { + if data.is_write_only() { return Err(Error::UnexpectedWriteOnlyDescriptor(1)); } @@ -422,9 +446,9 @@ impl VhostUserSoundThread { } let hdr_request = desc_chain - .memory() - .read_obj::(desc_request.addr()) - .map_err(|_| Error::DescriptorReadFailed)?; + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; let _stream_id = hdr_request.stream_id.to_native(); @@ -440,10 +464,7 @@ impl VhostUserSoundThread { let len = desc_response.len(); - if vring - .add_used(desc_chain.head_index(), len) - .is_err() - { + if vring.add_used(desc_chain.head_index(), len).is_err() { error!("Couldn't return used descriptors to the ring"); } } @@ -459,14 +480,13 @@ impl VhostUserSoundThread { fn process_rx(&self, _vring: &VringRwLock) -> IoResult { Ok(false) } - } pub struct VhostUserSoundBackend { threads: Vec>, virtio_cfg: VirtioSoundConfig, exit_event: EventFd, - streams_info: Vec + streams_info: Vec, } type SndDescriptorChain = DescriptorChain>>; @@ -477,20 +497,29 @@ impl VhostUserSoundBackend { let audio_backend_arc = Arc::new(audio_backend); let threads = if config.multi_thread { vec![ - RwLock::new(VhostUserSoundThread::new(vec![ - CONTROL_QUEUE_IDX, - EVENT_QUEUE_IDX, - ], audio_backend_arc.clone())?), - RwLock::new(VhostUserSoundThread::new(vec![TX_QUEUE_IDX], Arc::clone(&audio_backend_arc))?), - RwLock::new(VhostUserSoundThread::new(vec![RX_QUEUE_IDX], Arc::clone(&audio_backend_arc))?), + RwLock::new(VhostUserSoundThread::new( + vec![CONTROL_QUEUE_IDX, EVENT_QUEUE_IDX], + audio_backend_arc.clone(), + )?), + RwLock::new(VhostUserSoundThread::new( + vec![TX_QUEUE_IDX], + Arc::clone(&audio_backend_arc), + )?), + RwLock::new(VhostUserSoundThread::new( + vec![RX_QUEUE_IDX], + Arc::clone(&audio_backend_arc), + )?), ] } else { - vec![RwLock::new(VhostUserSoundThread::new(vec![ - CONTROL_QUEUE_IDX, - EVENT_QUEUE_IDX, - TX_QUEUE_IDX, - RX_QUEUE_IDX, - ], Arc::clone(&audio_backend_arc))?)] + vec![RwLock::new(VhostUserSoundThread::new( + vec![ + CONTROL_QUEUE_IDX, + EVENT_QUEUE_IDX, + TX_QUEUE_IDX, + RX_QUEUE_IDX, + ], + Arc::clone(&audio_backend_arc), + )?)] }; let mut streams = Vec::::with_capacity(NR_STREAMS); @@ -507,7 +536,7 @@ impl VhostUserSoundBackend { chmaps: 0.into(), }, streams_info: streams, - exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)? + exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, }) } @@ -562,10 +591,11 @@ impl VhostUserBackend for VhostUserSoundBackend { return Err(Error::HandleEventNotEpollIn.into()); } - self.threads[thread_id] - .read() - .unwrap() - .handle_event(device_event, vrings, &self.streams_info) + self.threads[thread_id].read().unwrap().handle_event( + device_event, + vrings, + &self.streams_info, + ) } fn get_config(&self, offset: u32, size: u32) -> Vec { From 91a5259ccebce1fe4d6f282658a2016365441e94 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 4 Jul 2023 17:53:38 +0300 Subject: [PATCH 23/44] Add vhost-user-sound crate Signed-off-by: Emmanouil Pitsidianakis --- Cargo.lock | 543 +++++++++-------- crates/sound/CHANGELOG.md | 3 + crates/sound/Cargo.toml | 19 +- crates/sound/LICENSE-APACHE | 1 + crates/sound/LICENSE-BSD-3-Clause | 1 + crates/sound/README.md | 40 +- crates/sound/rustfmt.toml | 7 + crates/sound/src/audio_backends.rs | 31 +- crates/sound/src/audio_backends/null.rs | 11 +- crates/sound/src/audio_backends/pipewire.rs | 28 + crates/sound/src/device.rs | 215 +++++++ crates/sound/src/lib.rs | 56 +- crates/sound/src/main.rs | 7 +- crates/sound/src/vhu_sound.rs | 627 -------------------- crates/sound/src/virtio_sound.rs | 34 +- 15 files changed, 612 insertions(+), 1011 deletions(-) create mode 100644 crates/sound/CHANGELOG.md create mode 120000 crates/sound/LICENSE-APACHE create mode 120000 crates/sound/LICENSE-BSD-3-Clause create mode 100644 crates/sound/rustfmt.toml create mode 100644 crates/sound/src/audio_backends/pipewire.rs create mode 100644 crates/sound/src/device.rs delete mode 100644 crates/sound/src/vhu_sound.rs diff --git a/Cargo.lock b/Cargo.lock index a33e345..aae3814 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,67 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] [[package]] -name = "anyhow" -version = "1.0.71" +name = "anstream" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "arc-swap" @@ -35,7 +84,7 @@ version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "lazy_static", @@ -47,7 +96,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 1.0.109", "which", ] @@ -57,7 +106,7 @@ version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "lazy_static", @@ -69,7 +118,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 1.0.109", "which", ] @@ -79,6 +128,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + [[package]] name = "byteorder" version = "1.4.3" @@ -102,9 +157,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.1" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8790cf1286da485c72cf5fc7aeba308438800036ec67d89425924c4807268c9" +checksum = "215c0072ecc28f92eeb0eea38ba63ddfcb65c2828c46311d646f1a3ff5f9841c" dependencies = [ "smallvec", "target-lexicon", @@ -118,9 +173,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", @@ -129,40 +184,50 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.8" +version = "4.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" +checksum = "5b0827b011f6f8ab38590295339817b0d26f344aa4932c3ced71b45b0c54b4a9" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", - "clap_lex", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441b403be87be858db6a23edb493e7f694761acdc3343d5a0fcaafd304cbc9e" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", "strsim", - "termcolor", ] [[package]] name = "clap_derive" -version = "4.1.8" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 2.0.26", ] [[package]] name = "clap_lex" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" -dependencies = [ - "os_str_bytes", -] +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "cookie-factory" @@ -172,12 +237,12 @@ checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.0", "lock_api", "once_cell", "parking_lot_core", @@ -204,11 +269,11 @@ dependencies = [ [[package]] name = "epoll" -version = "4.3.1" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20df693c700404f7e19d4d6fae6b15215d2913c27955d2b9d6f2c0f537511cd0" +checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79" dependencies = [ - "bitflags", + "bitflags 2.3.3", "libc", ] @@ -231,7 +296,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -255,9 +320,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -270,9 +335,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -280,15 +345,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -298,38 +363,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.26", ] [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -345,9 +410,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -366,6 +431,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.4.1" @@ -374,18 +445,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "humantime" @@ -400,7 +462,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -420,25 +482,24 @@ checksum = "ee87fd093563344074bacf24faa0bb0227fb6969fb223e922db798516de924d6" [[package]] name = "io-lifetimes" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd6da19f25979c7270e70fa95ab371ec3b701cd0eefc47667a09785b3c59155" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi", "libc", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] name = "is-terminal" -version = "0.4.3" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.45.0", + "hermit-abi", + "rustix 0.38.4", + "windows-sys", ] [[package]] @@ -455,9 +516,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libgpiod" @@ -496,7 +557,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "667dfbb50c3d1f7ee1d33afdc04d1255923ece7642db3303046e7d63d997d77d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cc", "cookie-factory", "errno 0.3.1", @@ -519,15 +580,21 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -535,12 +602,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "memchr" @@ -569,7 +633,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset", @@ -589,25 +653,19 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" - -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "parking_lot" @@ -621,15 +679,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] @@ -640,9 +698,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" [[package]] name = "pin-utils" @@ -657,7 +715,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc2180a4a84b855be86e6cd72fa6fd4318278871d2b1082e7cd05fe64b135ccb" dependencies = [ "anyhow", - "bitflags", + "bitflags 1.3.2", "errno 0.3.1", "libc", "libspa", @@ -691,44 +749,20 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" dependencies = [ "proc-macro2", ] @@ -765,18 +799,30 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ "aho-corasick", "memchr", @@ -785,9 +831,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "rustc-hash" @@ -797,23 +843,36 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.36.9" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ - "bitflags", - "errno 0.2.8", + "bitflags 1.3.2", + "errno 0.3.1", "io-lifetimes", "libc", - "linux-raw-sys", - "windows-sys 0.45.0", + "linux-raw-sys 0.3.8", + "windows-sys", +] + +[[package]] +name = "rustix" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +dependencies = [ + "bitflags 2.3.3", + "errno 0.3.1", + "libc", + "linux-raw-sys 0.4.3", + "windows-sys", ] [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" @@ -852,7 +911,7 @@ checksum = "079a83df15f85d89a68d64ae1238f142f172b1fa915d0d76b26a7cba1b659a69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -872,9 +931,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "static_assertions" @@ -900,10 +959,21 @@ dependencies = [ ] [[package]] -name = "system-deps" -version = "6.1.0" +name = "syn" +version = "2.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" dependencies = [ "cfg-expr", "heck", @@ -914,21 +984,22 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.7" +version = "0.12.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" +checksum = "1d2faeef5759ab89935255b1a4cd98e0baf99d1085e37d36599c625dac49ae8e" [[package]] name = "tempfile" -version = "3.4.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", "redox_syscall", - "rustix", - "windows-sys 0.42.0", + "rustix 0.37.23", + "windows-sys", ] [[package]] @@ -942,22 +1013,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.26", ] [[package]] @@ -996,9 +1067,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "version-compare" @@ -1006,19 +1083,13 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "vhost" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9b791c5b0717a0558888a4cf7240cea836f39a99cb342e12ce633dcaa078072" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "vm-memory", "vmm-sys-util", @@ -1036,7 +1107,7 @@ dependencies = [ "thiserror", "vhost", "vhost-user-backend", - "virtio-bindings 0.2.0", + "virtio-bindings 0.2.1", "virtio-queue", "vm-memory", "vmm-sys-util", @@ -1053,7 +1124,7 @@ dependencies = [ "thiserror", "vhost", "vhost-user-backend", - "virtio-bindings 0.2.0", + "virtio-bindings 0.2.1", "virtio-queue", "vm-memory", "vmm-sys-util", @@ -1073,7 +1144,7 @@ dependencies = [ "thiserror", "vhost", "vhost-user-backend", - "virtio-bindings 0.2.0", + "virtio-bindings 0.2.1", "virtio-queue", "vm-memory", "vmm-sys-util", @@ -1110,8 +1181,7 @@ dependencies = [ "thiserror", "vhost", "vhost-user-backend", - "virtio-bindings 0.2.0", - "virtio-queue", + "virtio-bindings 0.2.1", "vm-memory", "vmm-sys-util", ] @@ -1130,7 +1200,7 @@ dependencies = [ "thiserror", "vhost", "vhost-user-backend", - "virtio-bindings 0.2.0", + "virtio-bindings 0.2.1", "virtio-queue", "virtio-vsock", "vm-memory", @@ -1145,9 +1215,9 @@ checksum = "3ff512178285488516ed85f15b5d0113a7cdb89e9e8a760b269ae4f02b84bd6b" [[package]] name = "virtio-bindings" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9084faf91b9aa9676ae2cac8f1432df2839d9566e6f19f29dbc13a8b831dff" +checksum = "c18d7b74098a946470ea265b5bacbbf877abc3373021388454de0d47735a5b98" [[package]] name = "virtio-queue" @@ -1189,7 +1259,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd64fe09d8e880e600c324e7d664760a17f56e9672b7495a86381b49e4f72f46" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", ] @@ -1241,52 +1311,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.1", -] - [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.1", + "windows-targets", ] [[package]] @@ -1295,93 +1326,51 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_i686_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" - [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" - [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" - [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" - [[package]] name = "windows_x86_64_msvc" version = "0.48.0" @@ -1390,9 +1379,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" dependencies = [ "memchr", ] diff --git a/crates/sound/CHANGELOG.md b/crates/sound/CHANGELOG.md new file mode 100644 index 0000000..d471959 --- /dev/null +++ b/crates/sound/CHANGELOG.md @@ -0,0 +1,3 @@ +# Upcoming Release + +- First initial daemon implementation. diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index 2a8f621..6809ccd 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "vhost-user-sound" version = "0.1.0" -authors = ["Stefano Garzarella "] +authors = ["Stefano Garzarella ", "Manos Pitsidianakis "] description = "A virtio-sound device using the vhost-user protocol." repository = "https://github.com/rust-vmm/vhost-device" readme = "README.md" -keywords = ["vhost", "sound", "virtio-sound"] +keywords = ["vhost", "sound", "virtio-sound", "virtio-snd", "virtio"] license = "Apache-2.0 OR BSD-3-Clause" edition = "2018" @@ -15,21 +15,20 @@ null-backend = [] pw-backend = ["pipewire", "libspa", "pipewire-sys", "libspa-sys", "bindgen"] [dependencies] -clap = { version = "4.1", features = ["derive"] } +bindgen = { version = "0.64.0", optional = true } +clap = { version = "4.1", features = ["derive"] } env_logger = "0.10" +libspa = { version = "0.6.0", optional = true } +libspa-sys = { version = "0.6.0", optional = true } log = "0.4" +pipewire = { version = "0.6.0", optional = true } +pipewire-sys = { version = "0.6.0", optional = true } thiserror = "1.0" vhost = { version = "0.6", features = ["vhost-user-slave"] } vhost-user-backend = "0.8" -virtio-bindings = "0.2" -virtio-queue = "0.7" +virtio-bindings = "0.2.1" vm-memory = "0.10" vmm-sys-util = "0.11" -pipewire = { version = "0.6.0", optional = true } -libspa = { version = "0.6.0", optional = true } -pipewire-sys = { version = "0.6.0", optional = true } -libspa-sys = { version = "0.6.0", optional = true } -bindgen = { version = "0.64.0", optional = true } [dev-dependencies] serial_test = "1.0" diff --git a/crates/sound/LICENSE-APACHE b/crates/sound/LICENSE-APACHE new file mode 120000 index 0000000..1cd601d --- /dev/null +++ b/crates/sound/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/sound/LICENSE-BSD-3-Clause b/crates/sound/LICENSE-BSD-3-Clause new file mode 120000 index 0000000..a60f1af --- /dev/null +++ b/crates/sound/LICENSE-BSD-3-Clause @@ -0,0 +1 @@ +../../LICENSE-BSD-3-Clause \ No newline at end of file diff --git a/crates/sound/README.md b/crates/sound/README.md index ea2fbce..faea312 100644 --- a/crates/sound/README.md +++ b/crates/sound/README.md @@ -1,10 +1,44 @@ # vhost-user-sound -## Design + +## Synopsis + vhost-user-sound --socket --backend -## Usage +## Description + A virtio-sound device using the vhost-user protocol. -## Working example +## Options + +```text + --socket + vhost-user Unix domain socket path + + --backend + audio backend to be used (supported: null) + + -h, --help + Print help + + -V, --version + Print version +``` + +## Examples + +Launch the backend on the host machine: + +```shell +host# vhost-user-sound --socket /tmp/snd.sock --backend null +``` + +With QEMU, you can add a `virtio` device that uses the backend's socket with the following flags: + +```text +-chardev socket,id=vsnd,path=/tmp/snd.sock \ +-device vhost-user-snd-pci,chardev=vsnd,id=snd +``` ## License diff --git a/crates/sound/rustfmt.toml b/crates/sound/rustfmt.toml new file mode 100644 index 0000000..c6f0942 --- /dev/null +++ b/crates/sound/rustfmt.toml @@ -0,0 +1,7 @@ +edition = "2018" +format_generated_files = false +format_code_in_doc_comments = true +format_strings = true +imports_granularity = "Crate" +group_imports = "StdExternalCrate" +wrap_comments = true diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 1a49d8f..c8ad633 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -1,41 +1,22 @@ +// Manos Pitsidianakis // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause #[cfg(feature = "null-backend")] mod null; #[cfg(feature = "pw-backend")] -mod pw_backend; +mod pipewire; #[cfg(feature = "null-backend")] use self::null::NullBackend; -use self::pw_backend::PwBackend; #[cfg(feature = "pw-backend")] -use crate::PCMParams; -use crate::{Error, Result}; +use self::pipewire::PwBackend; +use crate::{Error, Result, SoundRequest}; pub trait AudioBackend { - fn write(&self, stream_id: u32) -> Result<()>; - fn read(&self, stream_id: u32) -> Result<()>; + fn write(&self, req: &SoundRequest) -> Result<()>; - fn set_param(&self, _stream_id: u32, _params: PCMParams) -> Result<()> { - Ok(()) - } - - fn prepare(&self, _stream_id: u32) -> Result<()> { - Ok(()) - } - - fn release(&self, _stream_id: u32) -> Result<()> { - Ok(()) - } - - fn start(&self, _stream_id: u32) -> Result<()> { - Ok(()) - } - - fn stop(&self, _stream_id: u32) -> Result<()> { - Ok(()) - } + fn read(&self, req: &mut SoundRequest) -> Result<()>; } pub fn alloc_audio_backend(name: String) -> Result> { diff --git a/crates/sound/src/audio_backends/null.rs b/crates/sound/src/audio_backends/null.rs index e4cfb2a..07cbb38 100644 --- a/crates/sound/src/audio_backends/null.rs +++ b/crates/sound/src/audio_backends/null.rs @@ -1,30 +1,27 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use super::AudioBackend; -use crate::Result; +use crate::{Error, Result, SoundRequest}; pub struct NullBackend {} impl NullBackend { pub fn new() -> Self { - NullBackend {} + Self {} } } impl AudioBackend for NullBackend { - fn write(&self, stream_id: u32) -> Result<()> { - println!("null backend, writting to stream: {}", stream_id); + fn write(&self, _req: &SoundRequest) -> Result<()> { Ok(()) } - fn read(&self, _stream_id: u32) -> Result<()> { - /* + fn read(&self, req: &mut SoundRequest) -> Result<()> { let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?; let zero_mem = vec![0u8; buf.len()]; buf.copy_from(&zero_mem); - */ Ok(()) } } diff --git a/crates/sound/src/audio_backends/pipewire.rs b/crates/sound/src/audio_backends/pipewire.rs new file mode 100644 index 0000000..e288c39 --- /dev/null +++ b/crates/sound/src/audio_backends/pipewire.rs @@ -0,0 +1,28 @@ +// Pipewire backend device +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use super::AudioBackend; +use crate::{Error, Result, SoundRequest}; + +pub struct PwBackend {} + +impl PwBackend { + pub fn new() -> Self { + Self {} + } +} + +impl AudioBackend for PwBackend { + fn write(&self, _req: &SoundRequest) -> Result<()> { + Ok(()) + } + + fn read(&self, req: &mut SoundRequest) -> Result<()> { + let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?; + let zero_mem = vec![0u8; buf.len()]; + + buf.copy_from(&zero_mem); + + Ok(()) + } +} diff --git a/crates/sound/src/device.rs b/crates/sound/src/device.rs new file mode 100644 index 0000000..9ac2216 --- /dev/null +++ b/crates/sound/src/device.rs @@ -0,0 +1,215 @@ +// Manos Pitsidianakis +// Stefano Garzarella +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use std::{io::Result as IoResult, sync::RwLock, u16, u32, u64, u8}; + +use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; +use vhost_user_backend::{VhostUserBackend, VringRwLock}; +use virtio_bindings::bindings::{ + virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1}, + virtio_ring::{VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC}, +}; +use vm_memory::{ByteValued, GuestMemoryAtomic, GuestMemoryMmap}; +use vmm_sys_util::{ + epoll::EventSet, + eventfd::{EventFd, EFD_NONBLOCK}, +}; + +use crate::{ + audio_backends::{alloc_audio_backend, AudioBackend}, + virtio_sound::*, + Error, Result, SoundConfig, +}; + +struct VhostUserSoundThread { + mem: Option>, + event_idx: bool, + queue_indexes: Vec, +} + +impl VhostUserSoundThread { + pub fn new(mut queue_indexes: Vec) -> Result { + queue_indexes.sort(); + + Ok(Self { + event_idx: false, + mem: None, + queue_indexes, + }) + } + + fn queues_per_thread(&self) -> u64 { + let mut queues_per_thread = 0u64; + + for idx in self.queue_indexes.iter() { + queues_per_thread |= 1u64 << idx + } + + queues_per_thread + } + + fn set_event_idx(&mut self, enabled: bool) { + self.event_idx = enabled; + } + + fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()> { + self.mem = Some(mem); + Ok(()) + } + + fn handle_event(&self, device_event: u16, vrings: &[VringRwLock]) -> IoResult { + let vring = &vrings[device_event as usize]; + let queue_idx = self.queue_indexes[device_event as usize]; + + match queue_idx { + CONTROL_QUEUE_IDX => self.process_control(vring), + EVENT_QUEUE_IDX => self.process_event(vring), + TX_QUEUE_IDX => self.process_tx(vring), + RX_QUEUE_IDX => self.process_rx(vring), + _ => Err(Error::HandleUnknownEvent.into()), + } + } + + fn process_control(&self, _vring: &VringRwLock) -> IoResult { + Ok(false) + } + + fn process_event(&self, _vring: &VringRwLock) -> IoResult { + Ok(false) + } + + fn process_tx(&self, _vring: &VringRwLock) -> IoResult { + Ok(false) + } + + fn process_rx(&self, _vring: &VringRwLock) -> IoResult { + Ok(false) + } +} + +pub struct VhostUserSoundBackend { + threads: Vec>, + virtio_cfg: VirtioSoundConfig, + exit_event: EventFd, + _audio_backend: RwLock>, +} + +impl VhostUserSoundBackend { + pub fn new(config: SoundConfig) -> Result { + let threads = if config.multi_thread { + vec![ + RwLock::new(VhostUserSoundThread::new(vec![ + CONTROL_QUEUE_IDX, + EVENT_QUEUE_IDX, + ])?), + RwLock::new(VhostUserSoundThread::new(vec![TX_QUEUE_IDX])?), + RwLock::new(VhostUserSoundThread::new(vec![RX_QUEUE_IDX])?), + ] + } else { + vec![RwLock::new(VhostUserSoundThread::new(vec![ + CONTROL_QUEUE_IDX, + EVENT_QUEUE_IDX, + TX_QUEUE_IDX, + RX_QUEUE_IDX, + ])?)] + }; + + let audio_backend = alloc_audio_backend(config.audio_backend_name)?; + + Ok(Self { + threads, + virtio_cfg: VirtioSoundConfig { + jacks: 0.into(), + streams: 1.into(), + chmaps: 0.into(), + }, + exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, + _audio_backend: RwLock::new(audio_backend), + }) + } + + pub fn send_exit_event(&self) { + self.exit_event.write(1).unwrap(); + } +} + +impl VhostUserBackend for VhostUserSoundBackend { + fn num_queues(&self) -> usize { + NUM_QUEUES as usize + } + + fn max_queue_size(&self) -> usize { + 256 + } + + fn features(&self) -> u64 { + 1 << VIRTIO_F_VERSION_1 + | 1 << VIRTIO_F_NOTIFY_ON_EMPTY + | 1 << VIRTIO_RING_F_INDIRECT_DESC + | 1 << VIRTIO_RING_F_EVENT_IDX + | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() + } + + fn protocol_features(&self) -> VhostUserProtocolFeatures { + VhostUserProtocolFeatures::CONFIG + } + + fn set_event_idx(&self, enabled: bool) { + for thread in self.threads.iter() { + thread.write().unwrap().set_event_idx(enabled); + } + } + + fn update_memory(&self, mem: GuestMemoryAtomic) -> IoResult<()> { + for thread in self.threads.iter() { + thread.write().unwrap().update_memory(mem.clone())?; + } + + Ok(()) + } + + fn handle_event( + &self, + device_event: u16, + evset: EventSet, + vrings: &[VringRwLock], + thread_id: usize, + ) -> IoResult { + if evset != EventSet::IN { + return Err(Error::HandleEventNotEpollIn.into()); + } + + self.threads[thread_id] + .read() + .unwrap() + .handle_event(device_event, vrings) + } + + fn get_config(&self, offset: u32, size: u32) -> Vec { + let offset = offset as usize; + let size = size as usize; + + let buf = self.virtio_cfg.as_slice(); + + if offset + size > buf.len() { + return Vec::new(); + } + + buf[offset..offset + size].to_vec() + } + + fn queues_per_thread(&self) -> Vec { + let mut vec = Vec::with_capacity(self.threads.len()); + + for thread in self.threads.iter() { + vec.push(thread.read().unwrap().queues_per_thread()) + } + + vec + } + + fn exit_event(&self, _thread_index: usize) -> Option { + self.exit_event.try_clone().ok() + } +} diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index e53cd61..8d20eb7 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -1,19 +1,22 @@ +// Manos Pitsidianakis // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -mod audio_backends; -mod vhu_sound; -mod virtio_sound; +pub mod audio_backends; +pub mod device; +pub mod virtio_sound; -use std::io::{Error as IoError, ErrorKind}; -use std::sync::Arc; +use std::{ + io::{Error as IoError, ErrorKind}, + sync::Arc, +}; use log::{info, warn}; use thiserror::Error as ThisError; use vhost::{vhost_user, vhost_user::Listener}; use vhost_user_backend::VhostUserDaemon; -use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap, Le32, VolatileSlice}; +use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap, VolatileSlice}; -use crate::vhu_sound::VhostUserSoundBackend; +use crate::device::VhostUserSoundBackend; pub type Result = std::result::Result; @@ -30,46 +33,14 @@ pub enum Error { SoundReqMissingData, #[error("Audio backend not supported")] AudioBackendNotSupported, - #[error("Descriptor not found")] - DescriptorNotFound, - #[error("Descriptor read failed")] - DescriptorReadFailed, - #[error("Descriptor write failed")] - DescriptorWriteFailed, - #[error("Isufficient descriptor size, required: {0}, found: {1}")] - InsufficientDescriptorSize(usize, usize), - #[error("Failed to send notification")] - SendNotificationFailed, - #[error("Invalid descriptor count {0}")] - UnexpectedDescriptorCount(usize), - #[error("Invalid descriptor size, expected: {0}, found: {1}")] - UnexpectedDescriptorSize(usize, usize), - #[error("Invalid descriptor size, expected at least: {0}, found: {1}")] - UnexpectedMinimumDescriptorSize(usize, usize), - #[error("Received unexpected readable descriptor at index {0}")] - UnexpectedReadableDescriptor(usize), - #[error("Received unexpected write only descriptor at index {0}")] - UnexpectedWriteOnlyDescriptor(usize), } impl std::convert::From for IoError { fn from(e: Error) -> Self { - IoError::new(ErrorKind::Other, e) + Self::new(ErrorKind::Other, e) } } -#[derive(Default, Clone)] -pub struct PCMParams { - pub features: Le32, - /// size of hardware buffer in bytes - pub buffer_bytes: Le32, - /// size of hardware period in bytes - pub period_bytes: Le32, - pub channels: u8, - pub format: u8, - pub rate: u8, -} - #[derive(Debug, Clone)] /// This structure is the public API through which an external program /// is allowed to configure the backend. @@ -133,7 +104,10 @@ pub fn start_backend_server(config: SoundConfig) { info!("Stopping cleanly"); } Err(vhost_user_backend::Error::HandleRequest(vhost_user::Error::PartialMessage)) => { - info!("vhost-user connection closed with partial message. If the VM is shutting down, this is expected behavior; otherwise, it might be a bug."); + info!( + "vhost-user connection closed with partial message. If the VM is shutting down, \ + this is expected behavior; otherwise, it might be a bug." + ); } Err(e) => { warn!("Error running daemon: {:?}", e); diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs index 7f3c844..11bed61 100644 --- a/crates/sound/src/main.rs +++ b/crates/sound/src/main.rs @@ -1,9 +1,9 @@ +// Manos Pitsidianakis +// Stefano Garzarella // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause - use std::convert::TryFrom; use clap::Parser; - use vhost_user_sound::{start_backend_server, Error, Result, SoundConfig}; #[derive(Parser, Debug)] @@ -40,9 +40,10 @@ fn main() { #[cfg(test)] mod tests { - use super::*; use serial_test::serial; + use super::*; + impl SoundArgs { fn from_args(socket: &str) -> Self { SoundArgs { diff --git a/crates/sound/src/vhu_sound.rs b/crates/sound/src/vhu_sound.rs deleted file mode 100644 index b98399d..0000000 --- a/crates/sound/src/vhu_sound.rs +++ /dev/null @@ -1,627 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause - -use std::mem::size_of; -use std::sync::Arc; -use std::sync::RwLock; -use std::{io::Result as IoResult, u16, u32, u64, u8}; - -use log::{debug, error}; -use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; -use vhost_user_backend::{VhostUserBackend, VringRwLock, VringT}; -use virtio_bindings::bindings::{ - virtio_config::VIRTIO_F_NOTIFY_ON_EMPTY, virtio_config::VIRTIO_F_VERSION_1, - virtio_ring::VIRTIO_RING_F_EVENT_IDX, virtio_ring::VIRTIO_RING_F_INDIRECT_DESC, -}; -use virtio_queue::{DescriptorChain, QueueOwnedT}; -use vm_memory::{ - ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, -}; -use vmm_sys_util::{ - epoll::EventSet, - eventfd::{EventFd, EFD_NONBLOCK}, -}; - -use crate::audio_backends::{alloc_audio_backend, AudioBackend}; -use crate::virtio_sound::*; -use crate::PCMParams; -use crate::{Error, Result, SoundConfig}; -use vm_memory::{Le32, Le64}; - -pub const SUPPORTED_FORMATS: u64 = 1 << VIRTIO_SND_PCM_FMT_U8 - | 1 << VIRTIO_SND_PCM_FMT_S16 - | 1 << VIRTIO_SND_PCM_FMT_S24 - | 1 << VIRTIO_SND_PCM_FMT_S32; - -pub const SUPPORTED_RATES: u64 = 1 << VIRTIO_SND_PCM_RATE_8000 - | 1 << VIRTIO_SND_PCM_RATE_11025 - | 1 << VIRTIO_SND_PCM_RATE_16000 - | 1 << VIRTIO_SND_PCM_RATE_22050 - | 1 << VIRTIO_SND_PCM_RATE_32000 - | 1 << VIRTIO_SND_PCM_RATE_44100 - | 1 << VIRTIO_SND_PCM_RATE_48000; - -pub const NR_STREAMS: usize = 1; - -pub struct StreamInfo { - pub features: Le32, /* 1 << VIRTIO_SND_PCM_F_XXX */ - pub formats: Le64, /* 1 << VIRTIO_SND_PCM_FMT_XXX */ - pub rates: Le64, /* 1 << VIRTIO_SND_PCM_RATE_XXX */ - pub direction: u8, - pub channels_min: u8, - pub channels_max: u8, -} - -impl StreamInfo { - pub fn output() -> Self { - Self { - features: 0.into(), - formats: SUPPORTED_FORMATS.into(), - rates: SUPPORTED_RATES.into(), - direction: VIRTIO_SND_D_OUTPUT, - channels_min: 1, - channels_max: 6, - } - } -} - -struct VhostUserSoundThread { - mem: Option>, - event_idx: bool, - queue_indexes: Vec, - audio_backend: Arc>, -} - -impl VhostUserSoundThread { - pub fn new( - mut queue_indexes: Vec, - audio_backend: Arc>, - ) -> Result { - queue_indexes.sort(); - - Ok(VhostUserSoundThread { - event_idx: false, - mem: None, - queue_indexes, - audio_backend, - }) - } - - fn queues_per_thread(&self) -> u64 { - let mut queues_per_thread = 0u64; - - for idx in self.queue_indexes.iter() { - queues_per_thread |= 1u64 << idx - } - - queues_per_thread - } - - fn set_event_idx(&mut self, enabled: bool) { - self.event_idx = enabled; - } - - fn update_memory(&mut self, mem: GuestMemoryAtomic) -> IoResult<()> { - debug!("update memory"); - self.mem = Some(mem); - Ok(()) - } - - fn handle_event( - &self, - device_event: u16, - vrings: &[VringRwLock], - stream_info: &[StreamInfo], - ) -> IoResult { - let vring = &vrings[device_event as usize]; - let queue_idx = self.queue_indexes[device_event as usize]; - debug!("handle event call queue: {}", queue_idx); - - match queue_idx { - CONTROL_QUEUE_IDX => { - debug!("control queue: {}", CONTROL_QUEUE_IDX); - let vring = &vrings[0]; - if self.event_idx { - // vm-virtio's Queue implementation only checks avail_index - // once, so to properly support EVENT_IDX we need to keep - // calling process_request_queue() until it stops finding - // new requests on the queue. - loop { - vring.disable_notification().unwrap(); - self.process_control(vring, stream_info)?; - if !vring.enable_notification().unwrap() { - break; - } - } - } else { - // Without EVENT_IDX, a single call is enough. - self.process_control(vring, stream_info)?; - } - } - EVENT_QUEUE_IDX => { - self.process_event(vring)?; - } - TX_QUEUE_IDX => { - let vring = &vrings[2]; - if self.event_idx { - // vm-virtio's Queue implementation only checks avail_index - // once, so to properly support EVENT_IDX we need to keep - // calling process_request_queue() until it stops finding - // new requests on the queue. - loop { - vring.disable_notification().unwrap(); - self.process_tx(vring)?; - if !vring.enable_notification().unwrap() { - break; - } - } - } else { - // Without EVENT_IDX, a single call is enough. - self.process_tx(vring)?; - } - } - RX_QUEUE_IDX => { - self.process_rx(vring)?; - } - _ => { - return Err(Error::HandleUnknownEvent.into()); - } - } - Ok(false) - } - - /// Process the messages in the vring and dispatch replies - fn process_control(&self, vring: &VringRwLock, stream_info: &[StreamInfo]) -> Result { - let requests: Vec = vring - .get_mut() - .get_queue_mut() - .iter(self.mem.as_ref().unwrap().memory()) - .map_err(|_| Error::DescriptorNotFound)? - .collect(); - - let audio_backend = &self.audio_backend; - - debug!("Requests to process: {}", requests.len()); - if requests.is_empty() { - debug!("yes, it's empty"); - return Ok(true); - } - //iterate over each sound request - for desc_chain in requests { - let descriptors: Vec<_> = desc_chain.clone().collect(); - debug!("Sound request with n descriptors: {}", descriptors.len()); - - let desc_request = descriptors[0]; - if desc_request.is_write_only() { - return Err(Error::UnexpectedWriteOnlyDescriptor(0)); - } - let read_desc_len: usize = desc_request.len() as usize; - let header_size = size_of::(); - if read_desc_len < header_size { - return Err(Error::UnexpectedMinimumDescriptorSize( - header_size, - read_desc_len, - )); - } - let hdr_request = desc_chain - .memory() - .read_obj::(desc_request.addr()) - .map_err(|_| Error::DescriptorReadFailed)?; - - let desc_response = descriptors[1]; - if !desc_response.is_write_only() { - return Err(Error::UnexpectedReadableDescriptor(1)); - } - - let mut response = VirtioSoundHeader { - code: VIRTIO_SND_S_OK.into(), - }; - - let mut len = desc_response.len(); - - let request_type = hdr_request.code.to_native(); - match request_type { - VIRTIO_SND_R_JACK_INFO => todo!(), - VIRTIO_SND_R_PCM_INFO => { - if descriptors.len() != 3 { - return Err(Error::UnexpectedDescriptorCount(descriptors.len())); - } - let desc_pcm = descriptors[2]; - if !desc_pcm.is_write_only() { - return Err(Error::UnexpectedReadableDescriptor(2)); - } - let query_info = desc_chain - .memory() - .read_obj::(desc_request.addr()) - .map_err(|_| Error::DescriptorReadFailed)?; - - let start_id: usize = u32::from(query_info.start_id) as usize; - let count: usize = u32::from(query_info.count) as usize; - - if start_id + count > stream_info.len() { - error!( - "start_id({}) + count({}) must be smaller than the number of streams ({})", - start_id, - count, - stream_info.len() - ); - desc_chain - .memory() - .write_obj(VIRTIO_SND_S_BAD_MSG, desc_response.addr()) - .map_err(|_| Error::DescriptorWriteFailed)?; - } else { - desc_chain - .memory() - .write_obj(response, desc_response.addr()) - .map_err(|_| Error::DescriptorWriteFailed)?; - - let mut buf = vec![]; - - for (i, stream) in stream_info.iter().enumerate().skip(start_id).take(count) - { - let pcm_info = VirtioSoundPcmInfo { - hdr: VirtioSoundInfo { - hda_fn_nid: Le32::from(i as u32), - }, - features: stream.features, - formats: stream.formats, - rates: stream.rates, - direction: stream.direction, - channels_min: stream.channels_min, - channels_max: stream.channels_max, - padding: [0; 5], - }; - buf.extend_from_slice(pcm_info.as_slice()); - } - - // TODO: to support the case when the number of items - // do not fit in a single descriptor - desc_chain - .memory() - .write_slice(&buf, desc_pcm.addr()) - .map_err(|_| Error::DescriptorWriteFailed)?; - - len += desc_pcm.len(); - } - } - VIRTIO_SND_R_CHMAP_INFO => todo!(), - VIRTIO_SND_R_JACK_REMAP => todo!(), - VIRTIO_SND_R_PCM_SET_PARAMS => { - if descriptors.len() != 2 { - return Err(Error::UnexpectedDescriptorCount(descriptors.len())); - } - - let set_params = desc_chain - .memory() - .read_obj::(desc_request.addr()) - .map_err(|_| Error::DescriptorReadFailed)?; - - let params = PCMParams { - buffer_bytes: set_params.buffer_bytes, - period_bytes: set_params.period_bytes, - features: set_params.features, - rate: set_params.rate, - format: set_params.format, - channels: set_params.channels, - }; - - let stream_id = set_params.hdr.stream_id.to_native(); - - if params.features != 0 { - error!("No feature is supported"); - response = VirtioSoundHeader { - code: VIRTIO_SND_S_NOT_SUPP.into(), - }; - } else if set_params.buffer_bytes.to_native() - % set_params.period_bytes.to_native() - != 0 - { - response = VirtioSoundHeader { - code: VIRTIO_SND_S_BAD_MSG.into(), - }; - error!( - "buffer_bytes({}) must be dividable by period_bytes({})", - set_params.buffer_bytes.to_native(), - set_params.period_bytes.to_native() - ); - } else if audio_backend.set_param(stream_id, params).is_err() { - error!("IO error during set_param()"); - response = VirtioSoundHeader { - code: VIRTIO_SND_S_IO_ERR.into(), - }; - } - desc_chain - .memory() - .write_obj(response, desc_response.addr()) - .map_err(|_| Error::DescriptorWriteFailed)?; - - len = desc_response.len(); - } - VIRTIO_SND_R_PCM_PREPARE - | VIRTIO_SND_R_PCM_START - | VIRTIO_SND_R_PCM_STOP - | VIRTIO_SND_R_PCM_RELEASE => { - let pcm_hdr = desc_chain - .memory() - .read_obj::(desc_request.addr()) - .map_err(|_| Error::DescriptorReadFailed)?; - let stream_id: usize = u32::from(pcm_hdr.stream_id) as usize; - dbg!("stream_id: {}", stream_id); - - desc_chain - .memory() - .write_obj(response, desc_response.addr()) - .map_err(|_| Error::DescriptorWriteFailed)?; - len = desc_response.len(); - } - _ => { - error!( - "virtio-snd: Unknown control queue message code: {}", - request_type - ); - } - }; - if vring.add_used(desc_chain.head_index(), len).is_err() { - error!("Couldn't return used descriptors to the ring"); - } - } - // Send notification once all the requests are processed - debug!("Sending processed request notification"); - vring - .signal_used_queue() - .map_err(|_| Error::SendNotificationFailed)?; - debug!("Process control queue finished"); - - Ok(false) - } - - fn process_event(&self, _vring: &VringRwLock) -> IoResult { - Ok(false) - } - - fn process_tx(&self, vring: &VringRwLock) -> Result { - let requests: Vec = vring - .get_mut() - .get_queue_mut() - .iter(self.mem.as_ref().unwrap().memory()) - .map_err(|_| Error::DescriptorNotFound)? - .collect(); - - debug!("Requests to tx: {}", requests.len()); - - for desc_chain in requests { - let descriptors: Vec<_> = desc_chain.clone().collect(); - debug!("Sound request with n descriptors: {}", descriptors.len()); - - // TODO: to handle the case in which READ_ONLY descs - // have both the header and the data - - let last_desc = descriptors.len() - 1; - let desc_response = descriptors[last_desc]; - - if desc_response.len() as usize != size_of::() { - return Err(Error::UnexpectedDescriptorSize( - size_of::(), - desc_response.len() as usize, - )); - } - - if !desc_response.is_write_only() { - return Err(Error::UnexpectedReadableDescriptor(1)); - } - - let response = VirtioSoundPcmStatus { - status: VIRTIO_SND_S_OK.into(), - latency_bytes: 0.into(), - }; - - let desc_request = descriptors[0]; - - if desc_request.len() as usize != size_of::() { - return Err(Error::UnexpectedDescriptorSize( - size_of::(), - desc_request.len() as usize, - )); - } - - if desc_request.is_write_only() { - return Err(Error::UnexpectedWriteOnlyDescriptor(1)); - } - - let mut all_bufs = Vec::::new(); - let data_descs = &descriptors[1..descriptors.len() - 1]; - - for data in data_descs { - if data.is_write_only() { - return Err(Error::UnexpectedWriteOnlyDescriptor(1)); - } - - let mut buf = vec![0u8; data.len() as usize]; - - desc_chain - .memory() - .read_slice(&mut buf, data.addr()) - .map_err(|_| Error::DescriptorReadFailed)?; - - all_bufs.extend(buf); - } - - let hdr_request = desc_chain - .memory() - .read_obj::(desc_request.addr()) - .map_err(|_| Error::DescriptorReadFailed)?; - - let _stream_id = hdr_request.stream_id.to_native(); - - // TODO: to invoke audio_backend.write(stream_id, all_bufs, len) - - // 5.14.6.8.1.1 - // The device MUST NOT complete the I/O request until the buffer is - // totally consumed. - desc_chain - .memory() - .write_obj(response, desc_response.addr()) - .map_err(|_| Error::DescriptorWriteFailed)?; - - let len = desc_response.len(); - - if vring.add_used(desc_chain.head_index(), len).is_err() { - error!("Couldn't return used descriptors to the ring"); - } - } - // Send notification once all the requests are processed - debug!("Sending processed tx notification"); - vring - .signal_used_queue() - .map_err(|_| Error::SendNotificationFailed)?; - debug!("Process tx queue finished"); - Ok(false) - } - - fn process_rx(&self, _vring: &VringRwLock) -> IoResult { - Ok(false) - } -} - -pub struct VhostUserSoundBackend { - threads: Vec>, - virtio_cfg: VirtioSoundConfig, - exit_event: EventFd, - streams_info: Vec, -} - -type SndDescriptorChain = DescriptorChain>>; - -impl VhostUserSoundBackend { - pub fn new(config: SoundConfig) -> Result { - let audio_backend = alloc_audio_backend(config.audio_backend_name)?; - let audio_backend_arc = Arc::new(audio_backend); - let threads = if config.multi_thread { - vec![ - RwLock::new(VhostUserSoundThread::new( - vec![CONTROL_QUEUE_IDX, EVENT_QUEUE_IDX], - audio_backend_arc.clone(), - )?), - RwLock::new(VhostUserSoundThread::new( - vec![TX_QUEUE_IDX], - Arc::clone(&audio_backend_arc), - )?), - RwLock::new(VhostUserSoundThread::new( - vec![RX_QUEUE_IDX], - Arc::clone(&audio_backend_arc), - )?), - ] - } else { - vec![RwLock::new(VhostUserSoundThread::new( - vec![ - CONTROL_QUEUE_IDX, - EVENT_QUEUE_IDX, - TX_QUEUE_IDX, - RX_QUEUE_IDX, - ], - Arc::clone(&audio_backend_arc), - )?)] - }; - - let mut streams = Vec::::with_capacity(NR_STREAMS); - - let stream_out_info = StreamInfo::output(); - // TODO: to add a input stream - streams.push(stream_out_info); - - Ok(Self { - threads, - virtio_cfg: VirtioSoundConfig { - jacks: 0.into(), - streams: Le32::from(streams.len() as u32), - chmaps: 0.into(), - }, - streams_info: streams, - exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, - }) - } - - pub fn send_exit_event(&self) { - self.exit_event.write(1).unwrap(); - } -} - -impl VhostUserBackend for VhostUserSoundBackend { - fn num_queues(&self) -> usize { - NUM_QUEUES as usize - } - - fn max_queue_size(&self) -> usize { - 256 - } - - fn features(&self) -> u64 { - 1 << VIRTIO_F_VERSION_1 - | 1 << VIRTIO_F_NOTIFY_ON_EMPTY - | 1 << VIRTIO_RING_F_INDIRECT_DESC - | 1 << VIRTIO_RING_F_EVENT_IDX - | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() - } - - fn protocol_features(&self) -> VhostUserProtocolFeatures { - VhostUserProtocolFeatures::CONFIG - } - - fn set_event_idx(&self, enabled: bool) { - for thread in self.threads.iter() { - thread.write().unwrap().set_event_idx(enabled); - } - } - - fn update_memory(&self, mem: GuestMemoryAtomic) -> IoResult<()> { - for thread in self.threads.iter() { - thread.write().unwrap().update_memory(mem.clone())?; - } - - Ok(()) - } - - fn handle_event( - &self, - device_event: u16, - evset: EventSet, - vrings: &[VringRwLock], - thread_id: usize, - ) -> IoResult { - if evset != EventSet::IN { - return Err(Error::HandleEventNotEpollIn.into()); - } - - self.threads[thread_id].read().unwrap().handle_event( - device_event, - vrings, - &self.streams_info, - ) - } - - fn get_config(&self, offset: u32, size: u32) -> Vec { - let offset = offset as usize; - let size = size as usize; - - let buf = self.virtio_cfg.as_slice(); - - if offset + size > buf.len() { - return Vec::new(); - } - - buf[offset..offset + size].to_vec() - } - - fn queues_per_thread(&self) -> Vec { - let mut vec = Vec::with_capacity(self.threads.len()); - - for thread in self.threads.iter() { - vec.push(thread.read().unwrap().queues_per_thread()) - } - - vec - } - - fn exit_event(&self, _thread_index: usize) -> Option { - self.exit_event.try_clone().ok() - } -} diff --git a/crates/sound/src/virtio_sound.rs b/crates/sound/src/virtio_sound.rs index b605049..5dc11d5 100644 --- a/crates/sound/src/virtio_sound.rs +++ b/crates/sound/src/virtio_sound.rs @@ -1,6 +1,4 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -#![allow(dead_code)] //TODO: remove - use vm_memory::{ByteValued, Le32, Le64}; // virtqueues @@ -48,8 +46,8 @@ pub const VIRTIO_SND_S_IO_ERR: u32 = 0x8003; // device data flow directions -pub const VIRTIO_SND_D_OUTPUT: u8 = 0; -pub const VIRTIO_SND_D_INPUT: u8 = 1; +pub const VIRTIO_SND_D_OUTPUT: u32 = 0; +pub const VIRTIO_SND_D_INPUT: u32 = 1; // supported jack features @@ -152,7 +150,7 @@ pub const VIRTIO_SND_CHMAP_BRC: u8 = 40; /* bottom right center */ pub const VIRTIO_SND_CHMAP_MAX_SIZE: usize = 18; /// Virtio Sound Configuration -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundConfig { /// total number of all available jacks @@ -168,7 +166,7 @@ pub struct VirtioSoundConfig { unsafe impl ByteValued for VirtioSoundConfig {} /// Virtio Sound Request / Response common header -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundHeader { /// request type / response status @@ -179,7 +177,7 @@ pub struct VirtioSoundHeader { unsafe impl ByteValued for VirtioSoundHeader {} /// Virtio Sound event notification -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundEvent { /// PCM stream event type @@ -192,7 +190,7 @@ pub struct VirtioSoundEvent { unsafe impl ByteValued for VirtioSoundEvent {} /// Virtio Sound request information about any kind of configuration item -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundQueryInfo { /// item request type (VIRTIO_SND_R_*_INFO) @@ -209,7 +207,7 @@ pub struct VirtioSoundQueryInfo { unsafe impl ByteValued for VirtioSoundQueryInfo {} /// Virtio Sound response common information header -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundInfo { /// function group node identifier @@ -220,7 +218,7 @@ pub struct VirtioSoundInfo { unsafe impl ByteValued for VirtioSoundInfo {} /// Jack control request / Jack common header -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundJackHeader { /// jack request type (VIRTIO_SND_R_JACK_*) @@ -233,7 +231,7 @@ pub struct VirtioSoundJackHeader { unsafe impl ByteValued for VirtioSoundJackHeader {} /// Jack response information about available jacks -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundJackInfo { /// jack response header type @@ -254,7 +252,7 @@ unsafe impl ByteValued for VirtioSoundJackInfo {} ///If the VIRTIO_SND_JACK_F_REMAP feature bit is set in the jack information /// Remap control request -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundJackRemap { pub hdr: VirtioSoundJackHeader, /* .code = VIRTIO_SND_R_JACK_REMAP */ @@ -266,7 +264,7 @@ pub struct VirtioSoundJackRemap { unsafe impl ByteValued for VirtioSoundJackRemap {} /// PCM control request / PCM common header -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundPcmHeader { pub hdr: VirtioSoundHeader, @@ -277,7 +275,7 @@ pub struct VirtioSoundPcmHeader { unsafe impl ByteValued for VirtioSoundPcmHeader {} /// PCM response information -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundPcmInfo { pub hdr: VirtioSoundInfo, @@ -295,7 +293,7 @@ pub struct VirtioSoundPcmInfo { unsafe impl ByteValued for VirtioSoundPcmInfo {} /// Set selected stream parameters for the specified stream ID -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSndPcmSetParams { pub hdr: VirtioSoundPcmHeader, @@ -312,7 +310,7 @@ pub struct VirtioSndPcmSetParams { unsafe impl ByteValued for VirtioSndPcmSetParams {} /// PCM I/O header -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundPcmXfer { pub stream_id: Le32, @@ -322,7 +320,7 @@ pub struct VirtioSoundPcmXfer { unsafe impl ByteValued for VirtioSoundPcmXfer {} /// PCM I/O status -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundPcmStatus { pub status: Le32, @@ -333,7 +331,7 @@ pub struct VirtioSoundPcmStatus { unsafe impl ByteValued for VirtioSoundPcmStatus {} /// channel maps response information -#[derive(Copy, Clone, Debug, Default, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct VirtioSoundChmapInfo { pub hdr: VirtioSoundInfo, From d385dcd1b29334797002b32f976233fcf599ac8d Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 4 Jul 2023 17:52:01 +0300 Subject: [PATCH 24/44] sound: add Stream, ControlMessage and other types Signed-off-by: Emmanouil Pitsidianakis --- Cargo.lock | 1 + crates/sound/Cargo.toml | 1 + crates/sound/src/audio_backends.rs | 1 + crates/sound/src/audio_backends/null.rs | 2 + crates/sound/src/device.rs | 44 ++++- crates/sound/src/lib.rs | 163 ++++++++++++++++- crates/sound/src/stream.rs | 230 ++++++++++++++++++++++++ crates/sound/src/virtio_sound.rs | 4 +- 8 files changed, 437 insertions(+), 9 deletions(-) create mode 100644 crates/sound/src/stream.rs diff --git a/Cargo.lock b/Cargo.lock index aae3814..1581d9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1182,6 +1182,7 @@ dependencies = [ "vhost", "vhost-user-backend", "virtio-bindings 0.2.1", + "virtio-queue", "vm-memory", "vmm-sys-util", ] diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index 6809ccd..ee197a7 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -27,6 +27,7 @@ thiserror = "1.0" vhost = { version = "0.6", features = ["vhost-user-slave"] } vhost-user-backend = "0.8" virtio-bindings = "0.2.1" +virtio-queue = "0.7" vm-memory = "0.10" vmm-sys-util = "0.11" diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index c8ad633..86859e0 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -20,6 +20,7 @@ pub trait AudioBackend { } pub fn alloc_audio_backend(name: String) -> Result> { + log::trace!("allocating audio backend {}", name); match name.as_str() { #[cfg(feature = "null-backend")] "null" => Ok(Box::new(NullBackend::new())), diff --git a/crates/sound/src/audio_backends/null.rs b/crates/sound/src/audio_backends/null.rs index 07cbb38..3cb7e69 100644 --- a/crates/sound/src/audio_backends/null.rs +++ b/crates/sound/src/audio_backends/null.rs @@ -13,10 +13,12 @@ impl NullBackend { impl AudioBackend for NullBackend { fn write(&self, _req: &SoundRequest) -> Result<()> { + log::trace!("NullBackend writing {:?}", _req); Ok(()) } fn read(&self, req: &mut SoundRequest) -> Result<()> { + log::trace!("NullBackend reading {:?}", req); let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?; let zero_mem = vec![0u8; buf.len()]; diff --git a/crates/sound/src/device.rs b/crates/sound/src/device.rs index 9ac2216..32c8c18 100644 --- a/crates/sound/src/device.rs +++ b/crates/sound/src/device.rs @@ -5,12 +5,13 @@ use std::{io::Result as IoResult, sync::RwLock, u16, u32, u64, u8}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; -use vhost_user_backend::{VhostUserBackend, VringRwLock}; +use vhost_user_backend::{VhostUserBackend, VringRwLock, VringT}; use virtio_bindings::bindings::{ virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1}, virtio_ring::{VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC}, }; -use vm_memory::{ByteValued, GuestMemoryAtomic, GuestMemoryMmap}; +use virtio_queue::QueueOwnedT; +use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; use vmm_sys_util::{ epoll::EventSet, eventfd::{EventFd, EFD_NONBLOCK}, @@ -28,6 +29,17 @@ struct VhostUserSoundThread { queue_indexes: Vec, } +#[cfg(debug_assertions)] +const fn queue_idx_as_str(q: u16) -> &'static str { + match q { + CONTROL_QUEUE_IDX => stringify!(CONTROL_QUEUE_IDX), + EVENT_QUEUE_IDX => stringify!(EVENT_QUEUE_IDX), + TX_QUEUE_IDX => stringify!(TX_QUEUE_IDX), + RX_QUEUE_IDX => stringify!(RX_QUEUE_IDX), + _ => "unknown queue idx", + } +} + impl VhostUserSoundThread { pub fn new(mut queue_indexes: Vec) -> Result { queue_indexes.sort(); @@ -59,8 +71,15 @@ impl VhostUserSoundThread { } fn handle_event(&self, device_event: u16, vrings: &[VringRwLock]) -> IoResult { + log::trace!("handle_event device_event {}", device_event); + let vring = &vrings[device_event as usize]; let queue_idx = self.queue_indexes[device_event as usize]; + log::trace!( + "handle_event queue_idx {} == {}", + queue_idx, + queue_idx_as_str(queue_idx) + ); match queue_idx { CONTROL_QUEUE_IDX => self.process_control(vring), @@ -72,18 +91,29 @@ impl VhostUserSoundThread { } fn process_control(&self, _vring: &VringRwLock) -> IoResult { - Ok(false) + let requests: Vec<_> = _vring + .get_mut() + .get_queue_mut() + .iter(self.mem.as_ref().unwrap().memory()) + .map_err(|_| Error::DescriptorNotFound)? + .collect(); + dbg!(&requests); + + Ok(true) } fn process_event(&self, _vring: &VringRwLock) -> IoResult { + log::trace!("process_event"); Ok(false) } fn process_tx(&self, _vring: &VringRwLock) -> IoResult { + log::trace!("process_tx"); Ok(false) } fn process_rx(&self, _vring: &VringRwLock) -> IoResult { + log::trace!("process_rx"); Ok(false) } } @@ -97,6 +127,7 @@ pub struct VhostUserSoundBackend { impl VhostUserSoundBackend { pub fn new(config: SoundConfig) -> Result { + log::trace!("VhostUserSoundBackend::new config {:?}", &config); let threads = if config.multi_thread { vec![ RwLock::new(VhostUserSoundThread::new(vec![ @@ -152,16 +183,18 @@ impl VhostUserBackend for VhostUserSoundBackend { } fn protocol_features(&self) -> VhostUserProtocolFeatures { - VhostUserProtocolFeatures::CONFIG + VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ } fn set_event_idx(&self, enabled: bool) { + log::trace!("set_event_idx enabled {:?}", enabled); for thread in self.threads.iter() { thread.write().unwrap().set_event_idx(enabled); } } fn update_memory(&self, mem: GuestMemoryAtomic) -> IoResult<()> { + log::trace!("update_memory"); for thread in self.threads.iter() { thread.write().unwrap().update_memory(mem.clone())?; } @@ -176,6 +209,7 @@ impl VhostUserBackend for VhostUserSoundBackend { vrings: &[VringRwLock], thread_id: usize, ) -> IoResult { + log::trace!("handle_event device_event {}", device_event); if evset != EventSet::IN { return Err(Error::HandleEventNotEpollIn.into()); } @@ -187,6 +221,7 @@ impl VhostUserBackend for VhostUserSoundBackend { } fn get_config(&self, offset: u32, size: u32) -> Vec { + log::trace!("get_config offset {} size {}", offset, size); let offset = offset as usize; let size = size as usize; @@ -210,6 +245,7 @@ impl VhostUserBackend for VhostUserSoundBackend { } fn exit_event(&self, _thread_index: usize) -> Option { + log::trace!("exit_event"); self.exit_event.try_clone().ok() } } diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index 8d20eb7..82cbc86 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -3,44 +3,199 @@ pub mod audio_backends; pub mod device; +pub mod stream; pub mod virtio_sound; use std::{ + convert::TryFrom, io::{Error as IoError, ErrorKind}, sync::Arc, }; use log::{info, warn}; +pub use stream::Stream; use thiserror::Error as ThisError; use vhost::{vhost_user, vhost_user::Listener}; -use vhost_user_backend::VhostUserDaemon; -use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap, VolatileSlice}; +use vhost_user_backend::{VhostUserDaemon, VringRwLock, VringT}; +use virtio_sound::*; +use vm_memory::{ + ByteValued, Bytes, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, Le32, + VolatileSlice, +}; use crate::device::VhostUserSoundBackend; +pub const SUPPORTED_FORMATS: u64 = 1 << VIRTIO_SND_PCM_FMT_U8 + | 1 << VIRTIO_SND_PCM_FMT_S16 + | 1 << VIRTIO_SND_PCM_FMT_S24 + | 1 << VIRTIO_SND_PCM_FMT_S32; + +pub const SUPPORTED_RATES: u64 = 1 << VIRTIO_SND_PCM_RATE_8000 + | 1 << VIRTIO_SND_PCM_RATE_11025 + | 1 << VIRTIO_SND_PCM_RATE_16000 + | 1 << VIRTIO_SND_PCM_RATE_22050 + | 1 << VIRTIO_SND_PCM_RATE_32000 + | 1 << VIRTIO_SND_PCM_RATE_44100 + | 1 << VIRTIO_SND_PCM_RATE_48000; + +use virtio_queue::DescriptorChain; +pub type SoundDescriptorChain = DescriptorChain>>; pub type Result = std::result::Result; /// Custom error types #[derive(Debug, ThisError)] pub enum Error { + #[error("Notification send failed")] + SendNotificationFailed, + #[error("Descriptor not found")] + DescriptorNotFound, + #[error("Descriptor read failed")] + DescriptorReadFailed, + #[error("Descriptor write failed")] + DescriptorWriteFailed, #[error("Failed to handle event other than EPOLLIN event")] HandleEventNotEpollIn, #[error("Failed to handle unknown event")] HandleUnknownEvent, + #[error("Invalid control message code {0}")] + InvalidControlMessage(u32), #[error("Failed to create a new EventFd")] EventFdCreate(IoError), #[error("Request missing data buffer")] SoundReqMissingData, #[error("Audio backend not supported")] AudioBackendNotSupported, + #[error("Invalid virtio_snd_hdr size, expected: {0}, found: {1}")] + UnexpectedSoundHeaderSize(usize, u32), + #[error("Received unexpected write only descriptor at index {0}")] + UnexpectedWriteOnlyDescriptor(usize), + #[error("Received unexpected readable descriptor at index {0}")] + UnexpectedReadableDescriptor(usize), + #[error("Invalid descriptor count {0}")] + UnexpectedDescriptorCount(usize), + #[error("Invalid descriptor size, expected: {0}, found: {1}")] + UnexpectedDescriptorSize(usize, u32), + #[error("Protocol or device error: {0}")] + Stream(stream::Error), } -impl std::convert::From for IoError { +impl From for IoError { fn from(e: Error) -> Self { Self::new(ErrorKind::Other, e) } } +impl From for Error { + fn from(val: stream::Error) -> Self { + Self::Stream(val) + } +} + +#[derive(Debug)] +pub struct InvalidControlMessage(u32); + +impl std::fmt::Display for InvalidControlMessage { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "Invalid control message code {}", self.0) + } +} + +impl From for crate::Error { + fn from(val: InvalidControlMessage) -> Self { + Self::InvalidControlMessage(val.0) + } +} + +impl std::error::Error for InvalidControlMessage {} + +#[derive(Copy, Debug, Clone, Eq, PartialEq)] +#[repr(u32)] +pub enum ControlMessageKind { + JackInfo = 1, + JackRemap = 2, + PcmInfo = 0x0100, + PcmSetParams = 0x0101, + PcmPrepare = 0x0102, + PcmRelease = 0x0103, + PcmStart = 0x0104, + PcmStop = 0x0105, + ChmapInfo = 0x0200, +} + +impl TryFrom for ControlMessageKind { + type Error = InvalidControlMessage; + + fn try_from(val: Le32) -> std::result::Result { + Ok(match u32::from(val) { + VIRTIO_SND_R_JACK_INFO => Self::JackInfo, + VIRTIO_SND_R_JACK_REMAP => Self::JackRemap, + VIRTIO_SND_R_PCM_INFO => Self::PcmInfo, + VIRTIO_SND_R_PCM_SET_PARAMS => Self::PcmSetParams, + VIRTIO_SND_R_PCM_PREPARE => Self::PcmPrepare, + VIRTIO_SND_R_PCM_RELEASE => Self::PcmRelease, + VIRTIO_SND_R_PCM_START => Self::PcmStart, + VIRTIO_SND_R_PCM_STOP => Self::PcmStop, + VIRTIO_SND_R_CHMAP_INFO => Self::ChmapInfo, + other => return Err(InvalidControlMessage(other)), + }) + } +} + +pub struct ControlMessage { + pub kind: ControlMessageKind, + pub code: u32, + pub desc_chain: SoundDescriptorChain, + pub descriptor: virtio_queue::Descriptor, + pub vring: VringRwLock, +} + +impl std::fmt::Debug for ControlMessage { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct(stringify!(ControlMessage)) + .field("kind", &self.kind) + .field("code", &self.code) + .finish() + } +} + +impl Drop for ControlMessage { + fn drop(&mut self) { + log::trace!( + "dropping ControlMessage {:?} reply = {}", + self.kind, + match self.code { + virtio_sound::VIRTIO_SND_S_OK => "VIRTIO_SND_S_OK", + virtio_sound::VIRTIO_SND_S_BAD_MSG => "VIRTIO_SND_S_BAD_MSG", + virtio_sound::VIRTIO_SND_S_NOT_SUPP => "VIRTIO_SND_S_NOT_SUPP", + virtio_sound::VIRTIO_SND_S_IO_ERR => "VIRTIO_SND_S_IO_ERR", + _ => "other", + } + ); + let resp = VirtioSoundHeader { + code: self.code.into(), + }; + + if let Err(err) = self + .desc_chain + .memory() + .write_obj(resp, self.descriptor.addr()) + { + log::error!("Error::DescriptorWriteFailed: {}", err); + return; + } + if self + .vring + .add_used(self.desc_chain.head_index(), resp.as_slice().len() as u32) + .is_err() + { + log::error!("Couldn't add used"); + } + if self.vring.signal_used_queue().is_err() { + log::error!("Couldn't signal used queue"); + } + } +} + #[derive(Debug, Clone)] /// This structure is the public API through which an external program /// is allowed to configure the backend. @@ -87,6 +242,7 @@ impl<'a> SoundRequest<'a> { /// This is the public API through which an external program starts the /// vhost-user-sound backend server. pub fn start_backend_server(config: SoundConfig) { + log::trace!("Using config {:?}", &config); let listener = Listener::new(config.get_socket_path(), true).unwrap(); let backend = Arc::new(VhostUserSoundBackend::new(config).unwrap()); @@ -97,6 +253,7 @@ pub fn start_backend_server(config: SoundConfig) { ) .unwrap(); + log::trace!("Starting daemon"); daemon.start(listener).unwrap(); match daemon.wait() { diff --git a/crates/sound/src/stream.rs b/crates/sound/src/stream.rs new file mode 100644 index 0000000..ba7b796 --- /dev/null +++ b/crates/sound/src/stream.rs @@ -0,0 +1,230 @@ +// Manos Pitsidianakis +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause + +use thiserror::Error as ThisError; +use vm_memory::{Le32, Le64}; + +use crate::{virtio_sound::*, SUPPORTED_FORMATS, SUPPORTED_RATES}; + +/// Stream errors. +#[derive(Debug, ThisError)] +pub enum Error { + #[error("Guest driver request an invalid stream state transition from {0} to {1}.")] + InvalidStateTransition(PCMState, PCMState), + #[error("Guest requested an invalid stream id: {0}")] + InvalidStreamId(u32), +} + +type Result = std::result::Result; + +/// PCM stream state machine. +/// +/// ## 5.14.6.6.1 PCM Command Lifecycle +/// +/// A PCM stream has the following command lifecycle: +/// +/// - `SET PARAMETERS` +/// +/// The driver negotiates the stream parameters (format, transport, etc) with +/// the device. +/// +/// Possible valid transitions: `SET PARAMETERS`, `PREPARE`. +/// +/// - `PREPARE` +/// +/// The device prepares the stream (allocates resources, etc). +/// +/// Possible valid transitions: `SET PARAMETERS`, `PREPARE`, `START`, +/// `RELEASE`. Output only: the driver transfers data for pre-buffing. +/// +/// - `START` +/// +/// The device starts the stream (unmute, putting into running state, etc). +/// +/// Possible valid transitions: `STOP`. +/// The driver transfers data to/from the stream. +/// +/// - `STOP` +/// +/// The device stops the stream (mute, putting into non-running state, etc). +/// +/// Possible valid transitions: `START`, `RELEASE`. +/// +/// - `RELEASE` +/// +/// The device releases the stream (frees resources, etc). +/// +/// Possible valid transitions: `SET PARAMETERS`, `PREPARE`. +/// +/// ```text +/// +---------------+ +---------+ +---------+ +-------+ +-------+ +/// | SetParameters | | Prepare | | Release | | Start | | Stop | +/// +---------------+ +---------+ +---------+ +-------+ +-------+ +/// | | | | | +/// |- | | | | +/// || | | | | +/// |< | | | | +/// | | | | | +/// |------------->| | | | +/// | | | | | +/// |<-------------| | | | +/// | | | | | +/// | |- | | | +/// | || | | | +/// | |< | | | +/// | | | | | +/// | |--------------------->| | +/// | | | | | +/// | |---------->| | | +/// | | | | | +/// | | | |-------->| +/// | | | | | +/// | | | |<--------| +/// | | | | | +/// | | |<-------------------| +/// | | | | | +/// |<-------------------------| | | +/// | | | | | +/// | |<----------| | | +/// ``` +#[derive(Debug, Default, Copy, Clone)] +pub enum PCMState { + #[default] + #[doc(alias = "VIRTIO_SND_R_PCM_SET_PARAMS")] + SetParameters, + #[doc(alias = "VIRTIO_SND_R_PCM_PREPARE")] + Prepare, + #[doc(alias = "VIRTIO_SND_R_PCM_RELEASE")] + Release, + #[doc(alias = "VIRTIO_SND_R_PCM_START")] + Start, + #[doc(alias = "VIRTIO_SND_R_PCM_STOP")] + Stop, +} + +macro_rules! set_new_state { + ($new_state_fn:ident, $new_state:expr, $($valid_source_states:tt)*) => { + pub fn $new_state_fn(&mut self) -> Result<()> { + if !matches!(self, $($valid_source_states)*) { + return Err(Error::InvalidStateTransition(*self, $new_state)); + } + *self = $new_state; + Ok(()) + } + }; +} + +impl PCMState { + pub fn new() -> Self { + Self::default() + } + + set_new_state!( + set_parameters, + Self::SetParameters, + Self::SetParameters | Self::Prepare | Self::Release + ); + + set_new_state!( + prepare, + Self::Prepare, + Self::SetParameters | Self::Prepare | Self::Release + ); + + set_new_state!(start, Self::Start, Self::Prepare | Self::Stop); + + set_new_state!(stop, Self::Stop, Self::Start); + + set_new_state!(release, Self::Release, Self::Prepare | Self::Stop); +} + +impl std::fmt::Display for PCMState { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + use PCMState::*; + match *self { + SetParameters => { + write!(fmt, "VIRTIO_SND_R_PCM_SET_PARAMS") + } + Prepare => { + write!(fmt, "VIRTIO_SND_R_PCM_PREPARE") + } + Release => { + write!(fmt, "VIRTIO_SND_R_PCM_RELEASE") + } + Start => { + write!(fmt, "VIRTIO_SND_R_PCM_START") + } + Stop => { + write!(fmt, "VIRTIO_SND_R_PCM_STOP") + } + } + } +} + +/// Internal state of a PCM stream of the VIRTIO Sound device. +#[derive(Debug)] +pub struct Stream { + pub id: usize, + pub params: PcmParams, + pub formats: Le64, + pub rates: Le64, + pub direction: u8, + pub channels_min: u8, + pub channels_max: u8, + pub state: PCMState, +} + +impl Default for Stream { + fn default() -> Self { + Self { + id: 0, + direction: VIRTIO_SND_D_OUTPUT, + formats: SUPPORTED_FORMATS.into(), + rates: SUPPORTED_RATES.into(), + params: PcmParams::default(), + channels_min: 1, + channels_max: 6, + state: Default::default(), + } + } +} + +impl Stream { + #[inline] + pub fn supports_format(&self, format: u8) -> bool { + let formats: u64 = self.formats.into(); + (formats & (1_u64 << format)) != 0 + } + + #[inline] + pub fn supports_rate(&self, rate: u8) -> bool { + let rates: u64 = self.rates.into(); + (rates & (1_u64 << rate)) != 0 + } +} + +/// Stream params +#[derive(Debug)] +pub struct PcmParams { + /// size of hardware buffer in bytes + pub buffer_bytes: Le32, + /// size of hardware period in bytes + pub period_bytes: Le32, + pub features: Le32, + pub channels: u8, + pub format: u8, + pub rate: u8, +} + +impl Default for PcmParams { + fn default() -> Self { + Self { + buffer_bytes: 8192.into(), + period_bytes: 4096.into(), + features: 0.into(), + channels: 1, + format: VIRTIO_SND_PCM_FMT_S16, + rate: VIRTIO_SND_PCM_RATE_44100, + } + } +} diff --git a/crates/sound/src/virtio_sound.rs b/crates/sound/src/virtio_sound.rs index 5dc11d5..252aa76 100644 --- a/crates/sound/src/virtio_sound.rs +++ b/crates/sound/src/virtio_sound.rs @@ -46,8 +46,8 @@ pub const VIRTIO_SND_S_IO_ERR: u32 = 0x8003; // device data flow directions -pub const VIRTIO_SND_D_OUTPUT: u32 = 0; -pub const VIRTIO_SND_D_INPUT: u32 = 1; +pub const VIRTIO_SND_D_OUTPUT: u8 = 0; +pub const VIRTIO_SND_D_INPUT: u8 = 1; // supported jack features From ecf88fb60284edd0ac44c75c3c0c6cdd8712f6b1 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 4 Jul 2023 17:52:08 +0300 Subject: [PATCH 25/44] sound: Add CTRL queue handler Signed-off-by: Emmanouil Pitsidianakis --- crates/sound/src/audio_backends.rs | 37 +- crates/sound/src/audio_backends/null.rs | 27 +- crates/sound/src/audio_backends/pipewire.rs | 25 +- crates/sound/src/device.rs | 481 +++++++++++++++++--- 4 files changed, 474 insertions(+), 96 deletions(-) diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 86859e0..3f0a938 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -7,25 +7,50 @@ mod null; #[cfg(feature = "pw-backend")] mod pipewire; +use std::sync::{Arc, RwLock}; + #[cfg(feature = "null-backend")] use self::null::NullBackend; #[cfg(feature = "pw-backend")] use self::pipewire::PwBackend; -use crate::{Error, Result, SoundRequest}; +use crate::{device::ControlMessage, stream::Stream, Error, Result}; pub trait AudioBackend { - fn write(&self, req: &SoundRequest) -> Result<()>; + fn write(&self, stream_id: u32) -> Result<()>; - fn read(&self, req: &mut SoundRequest) -> Result<()>; + fn read(&self, stream_id: u32) -> Result<()>; + + fn set_parameters(&self, _stream_id: u32, _: ControlMessage) -> Result<()> { + Ok(()) + } + + fn prepare(&self, _stream_id: u32) -> Result<()> { + Ok(()) + } + + fn release(&self, _stream_id: u32, _: ControlMessage) -> Result<()> { + Ok(()) + } + + fn start(&self, _stream_id: u32) -> Result<()> { + Ok(()) + } + + fn stop(&self, _stream_id: u32) -> Result<()> { + Ok(()) + } } -pub fn alloc_audio_backend(name: String) -> Result> { +pub fn alloc_audio_backend( + name: String, + streams: Arc>>, +) -> Result> { log::trace!("allocating audio backend {}", name); match name.as_str() { #[cfg(feature = "null-backend")] - "null" => Ok(Box::new(NullBackend::new())), + "null" => Ok(Box::new(NullBackend::new(streams))), #[cfg(feature = "pw-backend")] - "pipewire" => Ok(Box::new(PwBackend::new())), + "pipewire" => Ok(Box::new(PwBackend::new(streams))), _ => Err(Error::AudioBackendNotSupported), } } diff --git a/crates/sound/src/audio_backends/null.rs b/crates/sound/src/audio_backends/null.rs index 3cb7e69..f925cc7 100644 --- a/crates/sound/src/audio_backends/null.rs +++ b/crates/sound/src/audio_backends/null.rs @@ -1,29 +1,28 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use super::AudioBackend; -use crate::{Error, Result, SoundRequest}; +use std::sync::{Arc, RwLock}; -pub struct NullBackend {} +use super::AudioBackend; +use crate::{Result, Stream}; + +pub struct NullBackend { + streams: Arc>>, +} impl NullBackend { - pub fn new() -> Self { - Self {} + pub fn new(streams: Arc>>) -> Self { + Self { streams } } } impl AudioBackend for NullBackend { - fn write(&self, _req: &SoundRequest) -> Result<()> { - log::trace!("NullBackend writing {:?}", _req); + fn write(&self, stream_id: u32) -> Result<()> { + log::trace!("NullBackend write stream_id {}", stream_id); Ok(()) } - fn read(&self, req: &mut SoundRequest) -> Result<()> { - log::trace!("NullBackend reading {:?}", req); - let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?; - let zero_mem = vec![0u8; buf.len()]; - - buf.copy_from(&zero_mem); - + fn read(&self, _id: u32) -> Result<()> { + log::trace!("NullBackend read stream_id {}", _id); Ok(()) } } diff --git a/crates/sound/src/audio_backends/pipewire.rs b/crates/sound/src/audio_backends/pipewire.rs index e288c39..e98c1c7 100644 --- a/crates/sound/src/audio_backends/pipewire.rs +++ b/crates/sound/src/audio_backends/pipewire.rs @@ -1,28 +1,29 @@ // Pipewire backend device // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use super::AudioBackend; -use crate::{Error, Result, SoundRequest}; +use std::sync::{Arc, RwLock}; -pub struct PwBackend {} +use super::AudioBackend; +use crate::{Result, Stream}; + +pub struct PwBackend { + streams: Arc>>, +} impl PwBackend { - pub fn new() -> Self { - Self {} + pub fn new(streams: Arc>>) -> Self { + Self { streams } } } impl AudioBackend for PwBackend { - fn write(&self, _req: &SoundRequest) -> Result<()> { + fn write(&self, stream_id: u32) -> Result<()> { + log::trace!("PipewireBackend write stream_id {}", stream_id); Ok(()) } - fn read(&self, req: &mut SoundRequest) -> Result<()> { - let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?; - let zero_mem = vec![0u8; buf.len()]; - - buf.copy_from(&zero_mem); - + fn read(&self, _id: u32) -> Result<()> { + log::trace!("PipewireBackend read stream_id {}", _id); Ok(()) } } diff --git a/crates/sound/src/device.rs b/crates/sound/src/device.rs index 32c8c18..82a3d5e 100644 --- a/crates/sound/src/device.rs +++ b/crates/sound/src/device.rs @@ -2,16 +2,22 @@ // Stefano Garzarella // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use std::{io::Result as IoResult, sync::RwLock, u16, u32, u64, u8}; +use std::{ + convert::TryFrom, + io::Result as IoResult, + sync::{Arc, RwLock}, +}; use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures}; use vhost_user_backend::{VhostUserBackend, VringRwLock, VringT}; -use virtio_bindings::bindings::{ - virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1}, +use virtio_bindings::{ + bindings::virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1}, virtio_ring::{VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC}, }; -use virtio_queue::QueueOwnedT; -use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap}; +use virtio_queue::{DescriptorChain, QueueOwnedT}; +use vm_memory::{ + ByteValued, Bytes, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, +}; use vmm_sys_util::{ epoll::EventSet, eventfd::{EventFd, EFD_NONBLOCK}, @@ -19,35 +25,35 @@ use vmm_sys_util::{ use crate::{ audio_backends::{alloc_audio_backend, AudioBackend}, - virtio_sound::*, - Error, Result, SoundConfig, + stream::{Error as StreamError, Stream}, + virtio_sound::{self, *}, + ControlMessageKind, Error, Result, SoundConfig, }; struct VhostUserSoundThread { mem: Option>, event_idx: bool, queue_indexes: Vec, + streams: Arc>>, + streams_no: usize, } -#[cfg(debug_assertions)] -const fn queue_idx_as_str(q: u16) -> &'static str { - match q { - CONTROL_QUEUE_IDX => stringify!(CONTROL_QUEUE_IDX), - EVENT_QUEUE_IDX => stringify!(EVENT_QUEUE_IDX), - TX_QUEUE_IDX => stringify!(TX_QUEUE_IDX), - RX_QUEUE_IDX => stringify!(RX_QUEUE_IDX), - _ => "unknown queue idx", - } -} +type SoundDescriptorChain = DescriptorChain>>; impl VhostUserSoundThread { - pub fn new(mut queue_indexes: Vec) -> Result { + pub fn new( + mut queue_indexes: Vec, + streams: Arc>>, + streams_no: usize, + ) -> Result { queue_indexes.sort(); Ok(Self { event_idx: false, mem: None, queue_indexes, + streams, + streams_no, }) } @@ -70,36 +76,296 @@ impl VhostUserSoundThread { Ok(()) } - fn handle_event(&self, device_event: u16, vrings: &[VringRwLock]) -> IoResult { - log::trace!("handle_event device_event {}", device_event); - + fn handle_event( + &self, + device_event: u16, + vrings: &[VringRwLock], + audio_backend: &RwLock>, + ) -> IoResult { let vring = &vrings[device_event as usize]; let queue_idx = self.queue_indexes[device_event as usize]; - log::trace!( - "handle_event queue_idx {} == {}", - queue_idx, - queue_idx_as_str(queue_idx) - ); - - match queue_idx { - CONTROL_QUEUE_IDX => self.process_control(vring), - EVENT_QUEUE_IDX => self.process_event(vring), - TX_QUEUE_IDX => self.process_tx(vring), - RX_QUEUE_IDX => self.process_rx(vring), - _ => Err(Error::HandleUnknownEvent.into()), + if self.event_idx { + // vm-virtio's Queue implementation only checks avail_index + // once, so to properly support EVENT_IDX we need to keep + // calling process_request_queue() until it stops finding + // new requests on the queue. + loop { + vring.disable_notification().unwrap(); + match queue_idx { + CONTROL_QUEUE_IDX => self.process_control(vring, audio_backend), + EVENT_QUEUE_IDX => self.process_event(vring), + TX_QUEUE_IDX => self.process_tx(vring, audio_backend), + RX_QUEUE_IDX => self.process_rx(vring, audio_backend), + _ => Err(Error::HandleUnknownEvent.into()), + }?; + if !vring.enable_notification().unwrap() { + break; + } + } + } else { + // Without EVENT_IDX, a single call is enough. + match queue_idx { + CONTROL_QUEUE_IDX => self.process_control(vring, audio_backend), + EVENT_QUEUE_IDX => self.process_event(vring), + TX_QUEUE_IDX => self.process_tx(vring, audio_backend), + RX_QUEUE_IDX => self.process_rx(vring, audio_backend), + _ => Err(Error::HandleUnknownEvent.into()), + }?; } + Ok(false) } - fn process_control(&self, _vring: &VringRwLock) -> IoResult { - let requests: Vec<_> = _vring + fn process_control( + &self, + vring: &VringRwLock, + audio_backend: &RwLock>, + ) -> IoResult { + let requests: Vec = vring .get_mut() .get_queue_mut() .iter(self.mem.as_ref().unwrap().memory()) .map_err(|_| Error::DescriptorNotFound)? .collect(); - dbg!(&requests); - Ok(true) + if requests.is_empty() { + return Ok(true); + } + + // Reply to some requests right away, and defer others to the audio backend (for + // example PcmRelease needs to complete all I/O before replying. + // + // Mark `any` as true if we need to reply to any request right away so we can + // signal the queue as used. + let mut any = false; + + for desc_chain in requests { + let descriptors: Vec<_> = desc_chain.clone().collect(); + if descriptors.len() < 2 { + return Err(Error::UnexpectedDescriptorCount(descriptors.len()).into()); + } + + // Request descriptor. + let desc_request = descriptors[0]; + if desc_request.is_write_only() { + return Err(Error::UnexpectedWriteOnlyDescriptor(0).into()); + } + + let request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + // Keep track of bytes that will be written in the VQ. + let mut used_len = 0; + + // Reply header descriptor. + let desc_hdr = descriptors[1]; + if !desc_hdr.is_write_only() { + return Err(Error::UnexpectedReadableDescriptor(1).into()); + } + + let mut resp = VirtioSoundHeader { + code: VIRTIO_SND_S_OK.into(), + }; + + let code = ControlMessageKind::try_from(request.code).map_err(Error::from)?; + match code { + ControlMessageKind::JackInfo => { + resp.code = VIRTIO_SND_S_NOT_SUPP.into(); + } + ControlMessageKind::JackRemap => { + resp.code = VIRTIO_SND_S_NOT_SUPP.into(); + } + ControlMessageKind::PcmInfo => { + if descriptors.len() != 3 { + log::error!("a PCM_INFO request should have three descriptors total."); + return Err(Error::UnexpectedDescriptorCount(descriptors.len()).into()); + } else if !descriptors[2].is_write_only() { + log::error!( + "a PCM_INFO request should have a writeable descriptor for the info \ + payload response after the header status response" + ); + return Err(Error::UnexpectedReadableDescriptor(2).into()); + } + + let request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + let start_id = u32::from(request.start_id) as usize; + let count = u32::from(request.count) as usize; + let streams = self.streams.read().unwrap(); + if streams.len() <= start_id || streams.len() < start_id + count { + resp.code = VIRTIO_SND_S_BAD_MSG.into(); + } else { + let desc_response = descriptors[2]; + + let mut buf = vec![]; + let mut p: VirtioSoundPcmInfo; + + for s in streams + .iter() + .skip(u32::from(request.start_id) as usize) + .take(u32::from(request.count) as usize) + { + p = VirtioSoundPcmInfo::default(); + p.hdr.hda_fn_nid = 0.into(); + p.features = s.params.features; + p.formats = s.formats; + p.rates = s.rates; + p.direction = s.direction; + p.channels_min = s.channels_min; + p.channels_max = s.channels_max; + buf.extend_from_slice(p.as_slice()); + } + desc_chain + .memory() + .write_slice(&buf, desc_response.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + used_len += desc_response.len(); + } + } + ControlMessageKind::PcmSetParams => { + let request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + let stream_id: u32 = request.hdr.stream_id.into(); + + if stream_id as usize >= self.streams_no { + log::error!("{}", Error::from(StreamError::InvalidStreamId(stream_id))); + resp.code = VIRTIO_SND_S_BAD_MSG.into(); + } else { + let b = audio_backend.read().unwrap(); + b.set_parameters( + stream_id, + ControlMessage { + kind: code, + code: VIRTIO_SND_S_OK, + desc_chain, + descriptor: desc_hdr, + vring: vring.clone(), + }, + ) + .unwrap(); + + // PcmSetParams needs check valid formats/rates; the audio backend will + // reply when it drops the ControlMessage. + continue; + } + } + ControlMessageKind::PcmPrepare => { + let request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + let stream_id = request.stream_id.into(); + + if stream_id as usize >= self.streams_no { + log::error!("{}", Error::from(StreamError::InvalidStreamId(stream_id))); + resp.code = VIRTIO_SND_S_BAD_MSG.into(); + } else { + let b = audio_backend.write().unwrap(); + b.prepare(stream_id).unwrap(); + } + } + ControlMessageKind::PcmRelease => { + let request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + let stream_id = request.stream_id.into(); + + if stream_id as usize >= self.streams_no { + log::error!("{}", Error::from(StreamError::InvalidStreamId(stream_id))); + resp.code = VIRTIO_SND_S_BAD_MSG.into(); + } else { + let b = audio_backend.write().unwrap(); + b.release( + stream_id, + ControlMessage { + kind: code, + code: VIRTIO_SND_S_OK, + desc_chain, + descriptor: desc_hdr, + vring: vring.clone(), + }, + ) + .unwrap(); + + // PcmRelease needs to flush IO messages; the audio backend will reply when + // it drops the ControlMessage. + continue; + } + } + ControlMessageKind::PcmStart => { + let request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + let stream_id = request.stream_id.into(); + + if stream_id as usize >= self.streams_no { + log::error!("{}", Error::from(StreamError::InvalidStreamId(stream_id))); + resp.code = VIRTIO_SND_S_BAD_MSG.into(); + } else { + let b = audio_backend.write().unwrap(); + b.start(stream_id).unwrap(); + } + } + ControlMessageKind::PcmStop => { + let request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + let stream_id = request.stream_id.into(); + + if stream_id as usize >= self.streams_no { + log::error!("{}", Error::from(StreamError::InvalidStreamId(stream_id))); + resp.code = VIRTIO_SND_S_BAD_MSG.into(); + } else { + let b = audio_backend.write().unwrap(); + b.stop(stream_id).unwrap(); + } + } + ControlMessageKind::ChmapInfo => { + resp.code = VIRTIO_SND_S_NOT_SUPP.into(); + } + } + log::trace!( + "returned {} for ctrl msg {:?}", + match u32::from(resp.code) { + v if v == VIRTIO_SND_S_OK => "OK", + v if v == VIRTIO_SND_S_BAD_MSG => "BAD_MSG", + v if v == VIRTIO_SND_S_NOT_SUPP => "NOT_SUPP", + v if v == VIRTIO_SND_S_IO_ERR => "IO_ERR", + _ => unreachable!(), + }, + code + ); + desc_chain + .memory() + .write_obj(resp, desc_hdr.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + used_len += desc_hdr.len(); + + if vring.add_used(desc_chain.head_index(), used_len).is_err() { + log::error!("Couldn't return used descriptors to the ring"); + } + any |= true; + } + + // In which cases can happen that we get here and any is false? + // PCM_RELEASE and PCM_SET_PARAMS need to be handled asynchronously, therefore + // it will be their responsibility to `signal_used_queue`. + + // Send notification if any request was processed + if any && vring.signal_used_queue().is_err() { + log::error!("Couldn't signal used queue"); + } + + Ok(!any) } fn process_event(&self, _vring: &VringRwLock) -> IoResult { @@ -107,12 +373,20 @@ impl VhostUserSoundThread { Ok(false) } - fn process_tx(&self, _vring: &VringRwLock) -> IoResult { + fn process_tx( + &self, + _vring: &VringRwLock, + _audio_backend: &RwLock>, + ) -> IoResult { log::trace!("process_tx"); Ok(false) } - fn process_rx(&self, _vring: &VringRwLock) -> IoResult { + fn process_rx( + &self, + _vring: &VringRwLock, + _audio_backend: &RwLock>, + ) -> IoResult { log::trace!("process_rx"); Ok(false) } @@ -122,31 +396,58 @@ pub struct VhostUserSoundBackend { threads: Vec>, virtio_cfg: VirtioSoundConfig, exit_event: EventFd, - _audio_backend: RwLock>, + audio_backend: RwLock>, } impl VhostUserSoundBackend { pub fn new(config: SoundConfig) -> Result { + let streams = vec![ + Stream { + id: 0, + direction: VIRTIO_SND_D_OUTPUT, + ..Stream::default() + }, + Stream { + id: 1, + direction: VIRTIO_SND_D_INPUT, + ..Stream::default() + }, + ]; + let streams_no = streams.len(); + let streams = Arc::new(RwLock::new(streams)); log::trace!("VhostUserSoundBackend::new config {:?}", &config); let threads = if config.multi_thread { vec![ - RwLock::new(VhostUserSoundThread::new(vec![ - CONTROL_QUEUE_IDX, - EVENT_QUEUE_IDX, - ])?), - RwLock::new(VhostUserSoundThread::new(vec![TX_QUEUE_IDX])?), - RwLock::new(VhostUserSoundThread::new(vec![RX_QUEUE_IDX])?), + RwLock::new(VhostUserSoundThread::new( + vec![CONTROL_QUEUE_IDX, EVENT_QUEUE_IDX], + streams.clone(), + streams_no, + )?), + RwLock::new(VhostUserSoundThread::new( + vec![TX_QUEUE_IDX], + streams.clone(), + streams_no, + )?), + RwLock::new(VhostUserSoundThread::new( + vec![RX_QUEUE_IDX], + streams.clone(), + streams_no, + )?), ] } else { - vec![RwLock::new(VhostUserSoundThread::new(vec![ - CONTROL_QUEUE_IDX, - EVENT_QUEUE_IDX, - TX_QUEUE_IDX, - RX_QUEUE_IDX, - ])?)] + vec![RwLock::new(VhostUserSoundThread::new( + vec![ + CONTROL_QUEUE_IDX, + EVENT_QUEUE_IDX, + TX_QUEUE_IDX, + RX_QUEUE_IDX, + ], + streams.clone(), + streams_no, + )?)] }; - let audio_backend = alloc_audio_backend(config.audio_backend_name)?; + let audio_backend = alloc_audio_backend(config.audio_backend_name, streams)?; Ok(Self { threads, @@ -156,7 +457,7 @@ impl VhostUserSoundBackend { chmaps: 0.into(), }, exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, - _audio_backend: RwLock::new(audio_backend), + audio_backend: RwLock::new(audio_backend), }) } @@ -171,7 +472,8 @@ impl VhostUserBackend for VhostUserSoundBackend { } fn max_queue_size(&self) -> usize { - 256 + // TODO: Investigate if an alternative value makes any difference. + 64 } fn features(&self) -> u64 { @@ -187,14 +489,12 @@ impl VhostUserBackend for VhostUserSoundBackend { } fn set_event_idx(&self, enabled: bool) { - log::trace!("set_event_idx enabled {:?}", enabled); for thread in self.threads.iter() { thread.write().unwrap().set_event_idx(enabled); } } fn update_memory(&self, mem: GuestMemoryAtomic) -> IoResult<()> { - log::trace!("update_memory"); for thread in self.threads.iter() { thread.write().unwrap().update_memory(mem.clone())?; } @@ -209,19 +509,18 @@ impl VhostUserBackend for VhostUserSoundBackend { vrings: &[VringRwLock], thread_id: usize, ) -> IoResult { - log::trace!("handle_event device_event {}", device_event); if evset != EventSet::IN { return Err(Error::HandleEventNotEpollIn.into()); } - self.threads[thread_id] - .read() - .unwrap() - .handle_event(device_event, vrings) + self.threads[thread_id].read().unwrap().handle_event( + device_event, + vrings, + &self.audio_backend, + ) } fn get_config(&self, offset: u32, size: u32) -> Vec { - log::trace!("get_config offset {} size {}", offset, size); let offset = offset as usize; let size = size as usize; @@ -245,7 +544,61 @@ impl VhostUserBackend for VhostUserSoundBackend { } fn exit_event(&self, _thread_index: usize) -> Option { - log::trace!("exit_event"); self.exit_event.try_clone().ok() } } + +pub struct ControlMessage { + pub kind: ControlMessageKind, + pub code: u32, + pub desc_chain: SoundDescriptorChain, + pub descriptor: virtio_queue::Descriptor, + pub vring: VringRwLock, +} + +impl std::fmt::Debug for ControlMessage { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct(stringify!(ControlMessage)) + .field("kind", &self.kind) + .field("code", &self.code) + .finish() + } +} + +impl Drop for ControlMessage { + fn drop(&mut self) { + log::trace!( + "dropping ControlMessage {:?} reply = {}", + self.kind, + match self.code { + virtio_sound::VIRTIO_SND_S_OK => "VIRTIO_SND_S_OK", + virtio_sound::VIRTIO_SND_S_BAD_MSG => "VIRTIO_SND_S_BAD_MSG", + virtio_sound::VIRTIO_SND_S_NOT_SUPP => "VIRTIO_SND_S_NOT_SUPP", + virtio_sound::VIRTIO_SND_S_IO_ERR => "VIRTIO_SND_S_IO_ERR", + _ => "other", + } + ); + let resp = VirtioSoundHeader { + code: self.code.into(), + }; + + if let Err(err) = self + .desc_chain + .memory() + .write_obj(resp, self.descriptor.addr()) + { + log::error!("Error::DescriptorWriteFailed: {}", err); + return; + } + if self + .vring + .add_used(self.desc_chain.head_index(), resp.as_slice().len() as u32) + .is_err() + { + log::error!("Couldn't add used"); + } + if self.vring.signal_used_queue().is_err() { + log::error!("Couldn't signal used queue"); + } + } +} From b7122e66af5ae3cc9ea4fa5369cc1abe54969b17 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 4 Jul 2023 17:52:13 +0300 Subject: [PATCH 26/44] sound: Add TX queue handler Signed-off-by: Emmanouil Pitsidianakis --- crates/sound/src/audio_backends/null.rs | 1 + crates/sound/src/audio_backends/pipewire.rs | 1 + crates/sound/src/device.rs | 194 +++++++++++++++----- crates/sound/src/lib.rs | 42 ++++- crates/sound/src/stream.rs | 38 +++- 5 files changed, 223 insertions(+), 53 deletions(-) diff --git a/crates/sound/src/audio_backends/null.rs b/crates/sound/src/audio_backends/null.rs index f925cc7..66edb5e 100644 --- a/crates/sound/src/audio_backends/null.rs +++ b/crates/sound/src/audio_backends/null.rs @@ -18,6 +18,7 @@ impl NullBackend { impl AudioBackend for NullBackend { fn write(&self, stream_id: u32) -> Result<()> { log::trace!("NullBackend write stream_id {}", stream_id); + _ = std::mem::take(&mut self.streams.write().unwrap()[stream_id as usize].buffers); Ok(()) } diff --git a/crates/sound/src/audio_backends/pipewire.rs b/crates/sound/src/audio_backends/pipewire.rs index e98c1c7..c051c08 100644 --- a/crates/sound/src/audio_backends/pipewire.rs +++ b/crates/sound/src/audio_backends/pipewire.rs @@ -19,6 +19,7 @@ impl PwBackend { impl AudioBackend for PwBackend { fn write(&self, stream_id: u32) -> Result<()> { log::trace!("PipewireBackend write stream_id {}", stream_id); + _ = std::mem::take(&mut self.streams.write().unwrap()[stream_id as usize].buffers); Ok(()) } diff --git a/crates/sound/src/device.rs b/crates/sound/src/device.rs index 82a3d5e..c2f40c2 100644 --- a/crates/sound/src/device.rs +++ b/crates/sound/src/device.rs @@ -3,8 +3,10 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause use std::{ + collections::BTreeSet, convert::TryFrom, io::Result as IoResult, + mem::size_of, sync::{Arc, RwLock}, }; @@ -25,9 +27,9 @@ use vmm_sys_util::{ use crate::{ audio_backends::{alloc_audio_backend, AudioBackend}, - stream::{Error as StreamError, Stream}, + stream::{Buffer, Error as StreamError, Stream}, virtio_sound::{self, *}, - ControlMessageKind, Error, Result, SoundConfig, + ControlMessageKind, Error, IOMessage, Result, SoundConfig, }; struct VhostUserSoundThread { @@ -170,10 +172,9 @@ impl VhostUserSoundThread { let code = ControlMessageKind::try_from(request.code).map_err(Error::from)?; match code { - ControlMessageKind::JackInfo => { - resp.code = VIRTIO_SND_S_NOT_SUPP.into(); - } - ControlMessageKind::JackRemap => { + ControlMessageKind::ChmapInfo + | ControlMessageKind::JackInfo + | ControlMessageKind::JackRemap => { resp.code = VIRTIO_SND_S_NOT_SUPP.into(); } ControlMessageKind::PcmInfo => { @@ -237,18 +238,20 @@ impl VhostUserSoundThread { log::error!("{}", Error::from(StreamError::InvalidStreamId(stream_id))); resp.code = VIRTIO_SND_S_BAD_MSG.into(); } else { - let b = audio_backend.read().unwrap(); - b.set_parameters( - stream_id, - ControlMessage { - kind: code, - code: VIRTIO_SND_S_OK, - desc_chain, - descriptor: desc_hdr, - vring: vring.clone(), - }, - ) - .unwrap(); + audio_backend + .read() + .unwrap() + .set_parameters( + stream_id, + ControlMessage { + kind: code, + code: VIRTIO_SND_S_OK, + desc_chain, + descriptor: desc_hdr, + vring: vring.clone(), + }, + ) + .unwrap(); // PcmSetParams needs check valid formats/rates; the audio backend will // reply when it drops the ControlMessage. @@ -266,8 +269,7 @@ impl VhostUserSoundThread { log::error!("{}", Error::from(StreamError::InvalidStreamId(stream_id))); resp.code = VIRTIO_SND_S_BAD_MSG.into(); } else { - let b = audio_backend.write().unwrap(); - b.prepare(stream_id).unwrap(); + audio_backend.write().unwrap().prepare(stream_id).unwrap(); } } ControlMessageKind::PcmRelease => { @@ -281,18 +283,20 @@ impl VhostUserSoundThread { log::error!("{}", Error::from(StreamError::InvalidStreamId(stream_id))); resp.code = VIRTIO_SND_S_BAD_MSG.into(); } else { - let b = audio_backend.write().unwrap(); - b.release( - stream_id, - ControlMessage { - kind: code, - code: VIRTIO_SND_S_OK, - desc_chain, - descriptor: desc_hdr, - vring: vring.clone(), - }, - ) - .unwrap(); + audio_backend + .write() + .unwrap() + .release( + stream_id, + ControlMessage { + kind: code, + code: VIRTIO_SND_S_OK, + desc_chain, + descriptor: desc_hdr, + vring: vring.clone(), + }, + ) + .unwrap(); // PcmRelease needs to flush IO messages; the audio backend will reply when // it drops the ControlMessage. @@ -310,8 +314,7 @@ impl VhostUserSoundThread { log::error!("{}", Error::from(StreamError::InvalidStreamId(stream_id))); resp.code = VIRTIO_SND_S_BAD_MSG.into(); } else { - let b = audio_backend.write().unwrap(); - b.start(stream_id).unwrap(); + audio_backend.write().unwrap().start(stream_id).unwrap(); } } ControlMessageKind::PcmStop => { @@ -325,13 +328,9 @@ impl VhostUserSoundThread { log::error!("{}", Error::from(StreamError::InvalidStreamId(stream_id))); resp.code = VIRTIO_SND_S_BAD_MSG.into(); } else { - let b = audio_backend.write().unwrap(); - b.stop(stream_id).unwrap(); + audio_backend.write().unwrap().stop(stream_id).unwrap(); } } - ControlMessageKind::ChmapInfo => { - resp.code = VIRTIO_SND_S_NOT_SUPP.into(); - } } log::trace!( "returned {} for ctrl msg {:?}", @@ -375,10 +374,121 @@ impl VhostUserSoundThread { fn process_tx( &self, - _vring: &VringRwLock, - _audio_backend: &RwLock>, + vring: &VringRwLock, + audio_backend: &RwLock>, ) -> IoResult { - log::trace!("process_tx"); + let requests: Vec = vring + .get_mut() + .get_queue_mut() + .iter(self.mem.as_ref().unwrap().memory()) + .map_err(|_| Error::DescriptorNotFound)? + .collect(); + + if requests.is_empty() { + return Ok(true); + } + + #[derive(Copy, Clone, PartialEq, Debug)] + enum TxState { + Ready, + WaitingBufferForStreamId(u32), + Done, + } + + let mut stream_ids = BTreeSet::default(); + + for desc_chain in requests { + let mut state = TxState::Ready; + let mut buffers = vec![]; + let descriptors: Vec<_> = desc_chain.clone().collect(); + let message = Arc::new(IOMessage { + vring: vring.clone(), + status: VIRTIO_SND_S_OK.into(), + desc_chain: desc_chain.clone(), + descriptor: descriptors.last().cloned().unwrap(), + }); + for descriptor in &descriptors { + match state { + TxState::Done => { + return Err(Error::UnexpectedDescriptorCount(descriptors.len()).into()); + } + TxState::Ready if descriptor.is_write_only() => { + if descriptor.len() as usize != size_of::() { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + descriptor.len(), + ) + .into()); + } + state = TxState::Done; + } + TxState::WaitingBufferForStreamId(stream_id) if descriptor.is_write_only() => { + if descriptor.len() as usize != size_of::() { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + descriptor.len(), + ) + .into()); + } + let mut streams = self.streams.write().unwrap(); + for b in std::mem::take(&mut buffers) { + streams[stream_id as usize].buffers.push_back(b); + } + state = TxState::Done; + } + TxState::Ready + if descriptor.len() as usize != size_of::() => + { + return Err(Error::UnexpectedDescriptorSize( + size_of::(), + descriptor.len(), + ) + .into()); + } + TxState::Ready => { + let xfer = desc_chain + .memory() + .read_obj::(descriptor.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + let stream_id: u32 = xfer.stream_id.into(); + stream_ids.insert(stream_id); + + state = TxState::WaitingBufferForStreamId(stream_id); + } + TxState::WaitingBufferForStreamId(stream_id) + if descriptor.len() as usize == size_of::() => + { + return Err(Error::UnexpectedDescriptorSize( + u32::from( + self.streams.read().unwrap()[stream_id as usize] + .params + .buffer_bytes, + ) as usize, + descriptor.len(), + ) + .into()); + } + TxState::WaitingBufferForStreamId(_stream_id) => { + let mut buf = vec![0; descriptor.len() as usize]; + let bytes_read = desc_chain + .memory() + .read(&mut buf, descriptor.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + buf.truncate(bytes_read); + + buffers.push(Buffer::new(buf, Arc::clone(&message))); + } + } + } + } + + if !stream_ids.is_empty() { + let b = audio_backend.write().unwrap(); + for id in stream_ids { + b.write(id).unwrap(); + } + } + Ok(false) } diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index 82cbc86..474add1 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -20,7 +20,6 @@ use vhost_user_backend::{VhostUserDaemon, VringRwLock, VringT}; use virtio_sound::*; use vm_memory::{ ByteValued, Bytes, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, Le32, - VolatileSlice, }; use crate::device::VhostUserSoundBackend; @@ -226,16 +225,39 @@ impl SoundConfig { } } -pub type SoundBitmap = (); - -#[derive(Debug)] -pub struct SoundRequest<'a> { - data_slice: Option>, +pub struct IOMessage { + status: std::sync::atomic::AtomicU32, + desc_chain: SoundDescriptorChain, + descriptor: virtio_queue::Descriptor, + vring: VringRwLock, } -impl<'a> SoundRequest<'a> { - pub fn data_slice(&self) -> Option<&VolatileSlice<'a, SoundBitmap>> { - self.data_slice.as_ref() +impl Drop for IOMessage { + fn drop(&mut self) { + log::trace!("dropping IOMessage"); + let resp = VirtioSoundPcmStatus { + status: self.status.load(std::sync::atomic::Ordering::SeqCst).into(), + latency_bytes: 0.into(), + }; + + if let Err(err) = self + .desc_chain + .memory() + .write_obj(resp, self.descriptor.addr()) + { + log::error!("Error::DescriptorWriteFailed: {}", err); + return; + } + if self + .vring + .add_used(self.desc_chain.head_index(), resp.as_slice().len() as u32) + .is_err() + { + log::error!("Couldn't add used"); + } + if self.vring.signal_used_queue().is_err() { + log::error!("Couldn't signal used queue"); + } } } @@ -249,7 +271,7 @@ pub fn start_backend_server(config: SoundConfig) { let mut daemon = VhostUserDaemon::new( String::from("vhost-user-sound"), backend.clone(), - GuestMemoryAtomic::new(GuestMemoryMmap::::new()), + GuestMemoryAtomic::new(GuestMemoryMmap::new()), ) .unwrap(); diff --git a/crates/sound/src/stream.rs b/crates/sound/src/stream.rs index ba7b796..35d5e3f 100644 --- a/crates/sound/src/stream.rs +++ b/crates/sound/src/stream.rs @@ -1,10 +1,12 @@ // Manos Pitsidianakis // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause +use std::{collections::VecDeque, sync::Arc}; + use thiserror::Error as ThisError; use vm_memory::{Le32, Le64}; -use crate::{virtio_sound::*, SUPPORTED_FORMATS, SUPPORTED_RATES}; +use crate::{virtio_sound::*, IOMessage, SUPPORTED_FORMATS, SUPPORTED_RATES}; /// Stream errors. #[derive(Debug, ThisError)] @@ -172,6 +174,7 @@ pub struct Stream { pub channels_min: u8, pub channels_max: u8, pub state: PCMState, + pub buffers: VecDeque, } impl Default for Stream { @@ -185,6 +188,7 @@ impl Default for Stream { channels_min: 1, channels_max: 6, state: Default::default(), + buffers: VecDeque::new(), } } } @@ -228,3 +232,35 @@ impl Default for PcmParams { } } } + +pub struct Buffer { + pub bytes: Vec, + pub pos: usize, + pub message: Arc, +} + +impl std::fmt::Debug for Buffer { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct(stringify!(Buffer)) + .field("bytes", &self.bytes.len()) + .field("pos", &self.pos) + .field("message", &Arc::as_ptr(&self.message)) + .finish() + } +} + +impl Buffer { + pub fn new(bytes: Vec, message: Arc) -> Self { + Self { + bytes, + pos: 0, + message, + } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + log::trace!("dropping buffer {:?}", self); + } +} From 855eefb380c3f17120eec72d23b5f86ca8509db2 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 4 Jul 2023 17:52:23 +0300 Subject: [PATCH 27/44] sound: add ALSA backend Signed-off-by: Emmanouil Pitsidianakis --- Cargo.lock | 36 +- crates/sound/Cargo.toml | 4 +- crates/sound/src/audio_backends.rs | 6 + crates/sound/src/audio_backends/alsa.rs | 516 ++++++++++++++++++++++++ 4 files changed, 560 insertions(+), 2 deletions(-) create mode 100644 crates/sound/src/audio_backends/alsa.rs diff --git a/Cargo.lock b/Cargo.lock index 1581d9b..76f9d89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "alsa" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44" +dependencies = [ + "alsa-sys", + "bitflags 1.3.2", + "libc", + "nix 0.24.3", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "anstream" version = "0.3.2" @@ -627,6 +649,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + [[package]] name = "nix" version = "0.26.2" @@ -720,7 +753,7 @@ dependencies = [ "libc", "libspa", "libspa-sys", - "nix", + "nix 0.26.2", "once_cell", "pipewire-sys", "thiserror", @@ -1169,6 +1202,7 @@ dependencies = [ name = "vhost-user-sound" version = "0.1.0" dependencies = [ + "alsa", "bindgen 0.64.0", "clap", "env_logger", diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index ee197a7..962fe01 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -10,11 +10,13 @@ license = "Apache-2.0 OR BSD-3-Clause" edition = "2018" [features] -default = ["null-backend", "pw-backend"] +default = ["null-backend", "alsa-backend", "pw-backend"] null-backend = [] +alsa-backend = ["dep:alsa"] pw-backend = ["pipewire", "libspa", "pipewire-sys", "libspa-sys", "bindgen"] [dependencies] +alsa = { version = "0.7", optional = true } bindgen = { version = "0.64.0", optional = true } clap = { version = "4.1", features = ["derive"] } env_logger = "0.10" diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 3f0a938..950cd3f 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -1,6 +1,8 @@ // Manos Pitsidianakis // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause +#[cfg(feature = "alsa-backend")] +mod alsa; #[cfg(feature = "null-backend")] mod null; @@ -9,6 +11,8 @@ mod pipewire; use std::sync::{Arc, RwLock}; +#[cfg(feature = "alsa-backend")] +use self::alsa::AlsaBackend; #[cfg(feature = "null-backend")] use self::null::NullBackend; #[cfg(feature = "pw-backend")] @@ -51,6 +55,8 @@ pub fn alloc_audio_backend( "null" => Ok(Box::new(NullBackend::new(streams))), #[cfg(feature = "pw-backend")] "pipewire" => Ok(Box::new(PwBackend::new(streams))), + #[cfg(feature = "alsa-backend")] + "alsa" => Ok(Box::new(AlsaBackend::new(streams))), _ => Err(Error::AudioBackendNotSupported), } } diff --git a/crates/sound/src/audio_backends/alsa.rs b/crates/sound/src/audio_backends/alsa.rs new file mode 100644 index 0000000..91a8820 --- /dev/null +++ b/crates/sound/src/audio_backends/alsa.rs @@ -0,0 +1,516 @@ +/// Alsa backend +// +// Manos Pitsidianakis +// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause +use std::{ + convert::{TryFrom, TryInto}, + sync::mpsc::{channel, Receiver, Sender}, + sync::{Arc, Mutex, RwLock}, + thread, + thread::sleep, + time::Duration, +}; + +use alsa::{ + pcm::{Access, Format, HwParams, State, PCM}, + Direction, PollDescriptors, ValueOr, +}; +use virtio_queue::Descriptor; +use vm_memory::Bytes; + +use super::AudioBackend; +use crate::{ + device::ControlMessage, + stream::{PCMState, Stream}, + virtio_sound::{ + self, VirtioSndPcmSetParams, VIRTIO_SND_D_INPUT, VIRTIO_SND_D_OUTPUT, VIRTIO_SND_S_BAD_MSG, + VIRTIO_SND_S_NOT_SUPP, + }, + Result as CrateResult, +}; + +type AResult = std::result::Result; + +#[derive(Clone, Debug)] +pub struct AlsaBackend { + sender: Arc>>, +} + +#[derive(Debug)] +enum AlsaAction { + SetParameters(usize, ControlMessage), + Prepare(usize), + Release(usize, ControlMessage), + Start(usize), + Stop(usize), + Write(usize), + Read(usize), +} + +fn update_pcm( + pcm_: &Arc>, + stream_id: usize, + streams: &RwLock>, +) -> AResult<()> { + *pcm_.lock().unwrap() = { + let streams = streams.read().unwrap(); + let s = &streams[stream_id]; + let pcm = PCM::new( + "default", + match s.direction { + d if d == VIRTIO_SND_D_OUTPUT => Direction::Playback, + d if d == VIRTIO_SND_D_INPUT => Direction::Capture, + other => panic!("Invalid virtio-sound stream: {}", other), + }, + false, + )?; + + { + let hwp = HwParams::any(&pcm)?; + hwp.set_channels(s.params.channels.into())?; + hwp.set_rate( + match s.params.rate { + virtio_sound::VIRTIO_SND_PCM_RATE_5512 => 5512, + virtio_sound::VIRTIO_SND_PCM_RATE_8000 => 8000, + virtio_sound::VIRTIO_SND_PCM_RATE_11025 => 11025, + virtio_sound::VIRTIO_SND_PCM_RATE_16000 => 16000, + virtio_sound::VIRTIO_SND_PCM_RATE_22050 => 22050, + virtio_sound::VIRTIO_SND_PCM_RATE_32000 => 32000, + virtio_sound::VIRTIO_SND_PCM_RATE_44100 => 44100, + virtio_sound::VIRTIO_SND_PCM_RATE_48000 => 48000, + virtio_sound::VIRTIO_SND_PCM_RATE_64000 => 64000, + virtio_sound::VIRTIO_SND_PCM_RATE_88200 => 88200, + virtio_sound::VIRTIO_SND_PCM_RATE_96000 => 96000, + virtio_sound::VIRTIO_SND_PCM_RATE_176400 => 176400, + virtio_sound::VIRTIO_SND_PCM_RATE_192000 => 192000, + virtio_sound::VIRTIO_SND_PCM_RATE_384000 => 384000, + _ => 44100, + }, + ValueOr::Nearest, + )?; + hwp.set_format(match s.params.format { + virtio_sound::VIRTIO_SND_PCM_FMT_IMA_ADPCM => Format::ImaAdPCM, + virtio_sound::VIRTIO_SND_PCM_FMT_MU_LAW => Format::MuLaw, + virtio_sound::VIRTIO_SND_PCM_FMT_A_LAW => Format::ALaw, + virtio_sound::VIRTIO_SND_PCM_FMT_S8 => Format::S8, + virtio_sound::VIRTIO_SND_PCM_FMT_U8 => Format::U8, + virtio_sound::VIRTIO_SND_PCM_FMT_S16 => Format::s16(), + virtio_sound::VIRTIO_SND_PCM_FMT_U16 => Format::r#u16(), + virtio_sound::VIRTIO_SND_PCM_FMT_S18_3 => Format::S183LE, + virtio_sound::VIRTIO_SND_PCM_FMT_U18_3 => Format::U183LE, + virtio_sound::VIRTIO_SND_PCM_FMT_S20_3 => Format::S203LE, + virtio_sound::VIRTIO_SND_PCM_FMT_U20_3 => Format::U203LE, + virtio_sound::VIRTIO_SND_PCM_FMT_S24_3 => Format::S24LE, + virtio_sound::VIRTIO_SND_PCM_FMT_U24_3 => Format::U24LE, + virtio_sound::VIRTIO_SND_PCM_FMT_S20 => Format::s20_3(), + virtio_sound::VIRTIO_SND_PCM_FMT_U20 => Format::u20_3(), + virtio_sound::VIRTIO_SND_PCM_FMT_S24 => Format::s24(), + virtio_sound::VIRTIO_SND_PCM_FMT_U24 => Format::u24(), + virtio_sound::VIRTIO_SND_PCM_FMT_S32 => Format::s32(), + virtio_sound::VIRTIO_SND_PCM_FMT_U32 => Format::r#u32(), + virtio_sound::VIRTIO_SND_PCM_FMT_FLOAT => Format::float(), + virtio_sound::VIRTIO_SND_PCM_FMT_FLOAT64 => Format::float64(), + virtio_sound::VIRTIO_SND_PCM_FMT_DSD_U8 => Format::DSDU8, + virtio_sound::VIRTIO_SND_PCM_FMT_DSD_U16 => Format::DSDU16LE, + virtio_sound::VIRTIO_SND_PCM_FMT_DSD_U32 => Format::DSDU32LE, + virtio_sound::VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME => Format::iec958_subframe(), + _ => Format::Unknown, + })?; + + hwp.set_access(Access::RWInterleaved)?; + + // > A period is the number of frames in between each hardware interrupt. + // - https://www.alsa-project.org/wiki/FramesPeriods + // + // FIXME: What values should we set for buffer size and period size? (Should we + // set them at all?) virtio-sound spec deals in bytes but ALSA deals + // in frames. The alsa bindings sometimes use frames and sometimes bytes. + + pcm.hw_params(&hwp)?; + } + pcm + }; + Ok(()) +} + +// Returns `true` if the function should be called again, because there are are +// more data left to write. +fn write_samples_direct( + pcm: &alsa::PCM, + stream: &mut Stream, + mmap: &mut alsa::direct::pcm::MmapPlayback, +) -> AResult { + while mmap.avail() > 0 { + // Write samples to DMA area from iterator + let Some(buffer) = stream.buffers.front_mut() else { + return Ok(false); + }; + let mut iter = buffer.bytes[buffer.pos..].iter().cloned(); + let frames = mmap.write(&mut iter); + let written_bytes = pcm.frames_to_bytes(frames); + if let Ok(written_bytes) = usize::try_from(written_bytes) { + buffer.pos += written_bytes; + } + if buffer.pos >= buffer.bytes.len() { + stream.buffers.pop_front(); + } + } + match mmap.status().state() { + State::Running => { + return Ok(false); + } + State::Prepared => {} + State::XRun => { + log::trace!("Underrun in audio output stream!"); + pcm.prepare()? + } + State::Suspended => {} + n => panic!("Unexpected pcm state {:?}", n), + } + Ok(true) +} + +fn write_samples_io( + p: &alsa::PCM, + stream: &mut Stream, + io: &mut alsa::pcm::IO, +) -> AResult { + loop { + let avail = match p.avail_update() { + Ok(n) => n, + Err(err) => { + log::trace!("Recovering from {}", err); + p.recover(err.errno() as std::os::raw::c_int, true)?; + p.avail_update()? + } + }; + if avail == 0 { + break; + } + let written = io.mmap(avail as usize, |buf| { + let Some(buffer) = stream.buffers.front_mut() else { + return 0; + }; + let mut iter = buffer.bytes[buffer.pos..].iter().cloned(); + + let mut written_bytes = 0; + for (sample, byte) in buf.iter_mut().zip(&mut iter) { + *sample = byte; + written_bytes += 1; + } + buffer.pos += written_bytes as usize; + if buffer.pos >= buffer.bytes.len() { + stream.buffers.pop_front(); + } + p.bytes_to_frames(written_bytes) + .try_into() + .unwrap_or_default() + })?; + if written == 0 { + break; + }; + } + + match p.state() { + State::Suspended | State::Running => Ok(false), + State::Prepared => Ok(false), + State::XRun => Ok(true), // Recover from this in next round + n => panic!("Unexpected pcm state {:?}", n), + } +} + +fn alsa_worker( + pcm: Arc>, + streams: Arc>>, + receiver: &Receiver, + stream_id: usize, +) -> AResult<()> { + loop { + let Ok(do_write) = receiver.recv() else { + return Ok(()); + }; + if do_write { + loop { + if matches!(receiver.try_recv(), Ok(false)) { + break; + } + + let mut fds = { + let lck = pcm.lock().unwrap(); + if matches!(lck.state(), State::Running | State::Prepared | State::XRun) { + let mut mmap = lck.direct_mmap_playback::().ok(); + + if let Some(ref mut mmap) = mmap { + if write_samples_direct( + &lck, + &mut streams.write().unwrap()[stream_id], + mmap, + )? { + continue; + } + } else { + let mut io = lck.io_bytes(); + // Direct mode unavailable, use alsa-lib's mmap emulation instead + if write_samples_io( + &lck, + &mut streams.write().unwrap()[stream_id], + &mut io, + )? { + continue; + } + } + lck.get()? + } else { + drop(lck); + sleep(Duration::from_millis(500)); + continue; + } + }; + // Nothing to do, sleep until woken up by the kernel. + alsa::poll::poll(&mut fds, 100)?; + } + } + } +} + +impl AlsaBackend { + pub fn new(streams: Arc>>) -> Self { + let (sender, receiver) = channel(); + let sender = Arc::new(Mutex::new(sender)); + + thread::spawn(move || { + if let Err(err) = Self::run(streams, receiver) { + log::error!("Main thread exited with error: {}", err); + } + }); + + Self { sender } + } + + fn run( + streams: Arc>>, + receiver: Receiver, + ) -> Result<(), Box> { + let streams_no: usize; + + let (mut pcms, senders) = { + streams_no = streams.read().unwrap().len(); + let mut vec = Vec::with_capacity(streams_no); + let mut senders = Vec::with_capacity(streams_no); + for i in 0..streams_no { + let (sender, receiver) = channel(); + let pcm = Arc::new(Mutex::new(PCM::new("default", Direction::Playback, false)?)); + + let mtx = Arc::clone(&pcm); + let streams = Arc::clone(&streams); + thread::spawn(move || { + // TODO: exponential backoff? send fatal error to daemon? + while let Err(err) = alsa_worker(mtx.clone(), streams.clone(), &receiver, i) { + log::error!( + "Worker thread exited with error: {}, sleeping for 500ms", + err + ); + sleep(Duration::from_millis(500)); + } + }); + + senders.push(sender); + vec.push(pcm); + } + (vec, senders) + }; + for (i, pcm) in pcms.iter_mut().enumerate() { + update_pcm(pcm, i, &streams)?; + } + + while let Ok(action) = receiver.recv() { + match action { + AlsaAction::Read(_) => {} + AlsaAction::Write(stream_id) => { + if stream_id >= streams_no { + log::error!( + "Received Write action for stream id {} but there are only {} PCM \ + streams.", + stream_id, + pcms.len() + ); + continue; + }; + if matches!( + streams.write().unwrap()[stream_id].state, + PCMState::Start | PCMState::Prepare + ) { + senders[stream_id].send(true).unwrap(); + } + } + AlsaAction::Start(stream_id) => { + if stream_id >= streams_no { + log::error!( + "Received Start action for stream id {} but there are only {} PCM \ + streams.", + stream_id, + pcms.len() + ); + continue; + }; + + let start_result = streams.write().unwrap()[stream_id].state.start(); + if let Err(err) = start_result { + log::error!("Stream {} start {}", stream_id, err); + } else { + let pcm = &pcms[stream_id]; + let lck = pcm.lock().unwrap(); + match lck.state() { + State::Running => {} + _ => lck.start()?, + } + } + } + AlsaAction::Stop(stream_id) => { + if stream_id >= streams_no { + log::error!( + "Received Stop action for stream id {} but there are only {} PCM \ + streams.", + stream_id, + pcms.len() + ); + continue; + }; + let stop_result = streams.write().unwrap()[stream_id].state.stop(); + if let Err(err) = stop_result { + log::error!("Stream {} stop {}", stream_id, err); + } + } + AlsaAction::Prepare(stream_id) => { + if stream_id >= streams_no { + log::error!( + "Received Prepare action for stream id {} but there are only {} PCM \ + streams.", + stream_id, + pcms.len() + ); + continue; + }; + let prepare_result = streams.write().unwrap()[stream_id].state.prepare(); + if let Err(err) = prepare_result { + log::error!("Stream {} prepare {}", stream_id, err); + } else { + let pcm = &pcms[stream_id]; + let lck = pcm.lock().unwrap(); + match lck.state() { + State::Running => {} + _ => lck.prepare()?, + } + } + } + AlsaAction::Release(stream_id, mut msg) => { + if stream_id >= streams_no { + log::error!( + "Received Release action for stream id {} but there are only {} PCM \ + streams.", + stream_id, + pcms.len() + ); + msg.code = VIRTIO_SND_S_BAD_MSG; + continue; + }; + let release_result = streams.write().unwrap()[stream_id].state.release(); + if let Err(err) = release_result { + log::error!("Stream {} release {}", stream_id, err); + msg.code = VIRTIO_SND_S_BAD_MSG; + } else { + senders[stream_id].send(false).unwrap(); + let mut streams = streams.write().unwrap(); + std::mem::take(&mut streams[stream_id].buffers); + } + } + AlsaAction::SetParameters(stream_id, mut msg) => { + if stream_id >= streams_no { + log::error!( + "Received SetParameters action for stream id {} but there are only {} \ + PCM streams.", + stream_id, + pcms.len() + ); + msg.code = VIRTIO_SND_S_BAD_MSG; + continue; + }; + let descriptors: Vec = msg.desc_chain.clone().collect(); + let desc_request = &descriptors[0]; + let request = msg + .desc_chain + .memory() + .read_obj::(desc_request.addr()) + .unwrap(); + { + let mut streams = streams.write().unwrap(); + let st = &mut streams[stream_id]; + if let Err(err) = st.state.set_parameters() { + log::error!("Stream {} set_parameters {}", stream_id, err); + msg.code = VIRTIO_SND_S_BAD_MSG; + continue; + } else if !st.supports_format(request.format) + || !st.supports_rate(request.rate) + { + msg.code = VIRTIO_SND_S_NOT_SUPP; + continue; + } else { + st.params.buffer_bytes = request.buffer_bytes; + st.params.period_bytes = request.period_bytes; + st.params.features = request.features; + st.params.channels = request.channels; + st.params.format = request.format; + st.params.rate = request.rate; + } + } + // Manually drop msg for faster response: the kernel has a timeout. + drop(msg); + update_pcm(&pcms[stream_id], stream_id, &streams)?; + } + } + } + + Ok(()) + } +} + +macro_rules! send_action { + ($($fn_name:ident $action:tt),+$(,)?) => { + $( + fn $fn_name(&self, id: u32) -> CrateResult<()> { + self.sender + .lock() + .unwrap() + .send(AlsaAction::$action(id as usize)) + .unwrap(); + Ok(()) + } + )* + }; + ($(ctrl $fn_name:ident $action:tt),+$(,)?) => { + $( + fn $fn_name(&self, id: u32, msg: ControlMessage) -> CrateResult<()> { + self.sender + .lock() + .unwrap() + .send(AlsaAction::$action(id as usize, msg)) + .unwrap(); + Ok(()) + } + )* + } +} + +impl AudioBackend for AlsaBackend { + send_action! { + write Write, + read Read, + prepare Prepare, + start Start, + stop Stop, + } + send_action! { + ctrl set_parameters SetParameters, + ctrl release Release, + } +} From a48bff27b1b7578e0f7ae1f2b3a457e28c226705 Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Tue, 29 Aug 2023 12:38:13 +0200 Subject: [PATCH 28/44] sound: cli backend selector Change the CLI backend option to receive values listed in a ValueEnum. Current '--help' output: ``` A virtio-sound device using the vhost-user protocol. Usage: vhost-user-sound --socket --backend Options: --socket vhost-user Unix domain socket path --backend audio backend to be used [possible values: null, pipewire, alsa] -h, --help Print help -V, --version Print version ``` If a wrong backend is given, it give hints: ``` $ cargo run -- --socket /tmp/sound.sock --backend nul error: invalid value 'nul' for '' [possible values: null, pipewire, alsa] tip: a similar value exists: 'null' ``` Signed-off-by: Albert Esteve --- crates/sound/README.md | 2 +- crates/sound/src/audio_backends.rs | 20 +++++++++++++------- crates/sound/src/device.rs | 2 +- crates/sound/src/lib.rs | 17 +++++++++++++---- crates/sound/src/main.rs | 12 ++++++------ 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/crates/sound/README.md b/crates/sound/README.md index faea312..52ad5ad 100644 --- a/crates/sound/README.md +++ b/crates/sound/README.md @@ -16,7 +16,7 @@ generated with help2man target/debug/vhost-user-sound |mandoc vhost-user Unix domain socket path --backend - audio backend to be used (supported: null) + audio backend to be used [possible values: null, pipewire, alsa] -h, --help Print help diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 950cd3f..56b5280 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -17,7 +17,7 @@ use self::alsa::AlsaBackend; use self::null::NullBackend; #[cfg(feature = "pw-backend")] use self::pipewire::PwBackend; -use crate::{device::ControlMessage, stream::Stream, Error, Result}; +use crate::{device::ControlMessage, stream::Stream, BackendType, Error, Result}; pub trait AudioBackend { fn write(&self, stream_id: u32) -> Result<()>; @@ -46,17 +46,23 @@ pub trait AudioBackend { } pub fn alloc_audio_backend( - name: String, + backend: BackendType, streams: Arc>>, ) -> Result> { - log::trace!("allocating audio backend {}", name); - match name.as_str() { + log::trace!("allocating audio backend {:?}", backend); + match backend { #[cfg(feature = "null-backend")] - "null" => Ok(Box::new(NullBackend::new(streams))), + BackendType::Null => Ok(Box::new(NullBackend::new(streams))), #[cfg(feature = "pw-backend")] - "pipewire" => Ok(Box::new(PwBackend::new(streams))), + BackendType::Pipewire => Ok(Box::new(PwBackend::new(streams))), #[cfg(feature = "alsa-backend")] - "alsa" => Ok(Box::new(AlsaBackend::new(streams))), + BackendType::Alsa => Ok(Box::new(AlsaBackend::new(streams))), + // By default all features are enabled and this branch is unreachable. + // Nonetheless, it is required when inidividual features (or no features + // at all) are enabled. + // To avoid having a complicated compilation condition and make the + // code more maintainable, we supress the unreachable_patterns warning. + #[allow(unreachable_patterns)] _ => Err(Error::AudioBackendNotSupported), } } diff --git a/crates/sound/src/device.rs b/crates/sound/src/device.rs index c2f40c2..314e55b 100644 --- a/crates/sound/src/device.rs +++ b/crates/sound/src/device.rs @@ -557,7 +557,7 @@ impl VhostUserSoundBackend { )?)] }; - let audio_backend = alloc_audio_backend(config.audio_backend_name, streams)?; + let audio_backend = alloc_audio_backend(config.audio_backend, streams)?; Ok(Self { threads, diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index 474add1..2ad4dde 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -12,6 +12,7 @@ use std::{ sync::Arc, }; +use clap::ValueEnum; use log::{info, warn}; pub use stream::Stream; use thiserror::Error as ThisError; @@ -90,6 +91,14 @@ impl From for Error { } } +#[derive(ValueEnum, Clone, Default, Debug)] +pub enum BackendType { + #[default] + Null, + Pipewire, + Alsa, +} + #[derive(Debug)] pub struct InvalidControlMessage(u32); @@ -203,18 +212,18 @@ pub struct SoundConfig { socket: String, /// use multiple threads to hanlde the virtqueues multi_thread: bool, - /// audio backend name - audio_backend_name: String, + /// audio backend variant + audio_backend: BackendType, } impl SoundConfig { /// Create a new instance of the SoundConfig struct, containing the /// parameters to be fed into the sound-backend server. - pub fn new(socket: String, multi_thread: bool, audio_backend_name: String) -> Self { + pub fn new(socket: String, multi_thread: bool, audio_backend: BackendType) -> Self { Self { socket, multi_thread, - audio_backend_name, + audio_backend, } } diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs index 11bed61..8f2c627 100644 --- a/crates/sound/src/main.rs +++ b/crates/sound/src/main.rs @@ -4,7 +4,7 @@ use std::convert::TryFrom; use clap::Parser; -use vhost_user_sound::{start_backend_server, Error, Result, SoundConfig}; +use vhost_user_sound::{start_backend_server, BackendType, Error, Result, SoundConfig}; #[derive(Parser, Debug)] #[clap(version, about, long_about = None)] @@ -12,9 +12,10 @@ struct SoundArgs { /// vhost-user Unix domain socket path. #[clap(long)] socket: String, - /// audio backend to be used (supported: null) + /// audio backend to be used #[clap(long)] - backend: String, + #[clap(value_enum)] + backend: BackendType, } impl TryFrom for SoundConfig { @@ -22,9 +23,8 @@ impl TryFrom for SoundConfig { fn try_from(cmd_args: SoundArgs) -> Result { let socket = cmd_args.socket.trim().to_string(); - let backend = cmd_args.backend.trim().to_string(); - Ok(SoundConfig::new(socket, false, backend)) + Ok(SoundConfig::new(socket, false, cmd_args.backend)) } } @@ -48,7 +48,7 @@ mod tests { fn from_args(socket: &str) -> Self { SoundArgs { socket: socket.to_string(), - backend: "null".to_string(), + backend: BackendType::default(), } } } From 6c5b2db7db824df316161a17131f6ac960013248 Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Thu, 31 Aug 2023 11:19:44 +0200 Subject: [PATCH 29/44] sound: audio backend allow unused When compiled with no features, the `streams` parameter of the alloc_audio_backend() function is not used. Mark it as allowed to avoid the compilation warning. Signed-off-by: Albert Esteve --- crates/sound/src/audio_backends.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 56b5280..9319bac 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -47,7 +47,8 @@ pub trait AudioBackend { pub fn alloc_audio_backend( backend: BackendType, - streams: Arc>>, + // Unused when compiled with no features. + #[allow(unused_variables)] streams: Arc>>, ) -> Result> { log::trace!("allocating audio backend {:?}", backend); match backend { From 4867edc009af4a2db9e51b5e715bf66fa78fd529 Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Tue, 29 Aug 2023 13:12:46 +0200 Subject: [PATCH 30/44] sound: test cli backend argument Signed-off-by: Albert Esteve --- crates/sound/src/lib.rs | 6 +++++- crates/sound/src/main.rs | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index 2ad4dde..23fed0a 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -91,7 +91,7 @@ impl From for Error { } } -#[derive(ValueEnum, Clone, Default, Debug)] +#[derive(ValueEnum, Clone, Copy, Default, Debug, Eq, PartialEq)] pub enum BackendType { #[default] Null, @@ -232,6 +232,10 @@ impl SoundConfig { pub fn get_socket_path(&self) -> String { String::from(&self.socket) } + + pub fn get_audio_backend(&self) -> BackendType { + self.audio_backend + } } pub struct IOMessage { diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs index 8f2c627..0d52321 100644 --- a/crates/sound/src/main.rs +++ b/crates/sound/src/main.rs @@ -55,7 +55,7 @@ mod tests { #[test] #[serial] - fn test_vsock_config_setup() { + fn test_sound_config_setup() { let args = SoundArgs::from_args("/tmp/vhost-sound.socket"); let config = SoundConfig::try_from(args); @@ -64,4 +64,22 @@ mod tests { let config = config.unwrap(); assert_eq!(config.get_socket_path(), "/tmp/vhost-sound.socket"); } + + #[test] + #[serial] + fn test_cli_backend_arg() { + let args: SoundArgs = Parser::parse_from([ + "", + "--socket", + "/tmp/vhost-sound.socket ", + "--backend", + "null", + ]); + + let config = SoundConfig::try_from(args); + assert!(config.is_ok()); + + let config = config.unwrap(); + assert_eq!(config.get_audio_backend(), BackendType::Null); + } } From cd955a71324d888d33b670f7c6a0cca278d76047 Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Tue, 29 Aug 2023 13:20:38 +0200 Subject: [PATCH 31/44] sound: introduce rstest crate Introduce 'rstest'[1] crate dependency to allow run multiple parametrized tests (and fixtures). [1] - https://docs.rs/rstest/latest/rstest/ Signed-off-by: Albert Esteve --- Cargo.lock | 57 ++++++++++++++++++++++++++++++++++++++++ crates/sound/Cargo.toml | 1 + crates/sound/src/main.rs | 12 ++++++--- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76f9d89..5007d0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,6 +412,12 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.28" @@ -868,12 +874,56 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +[[package]] +name = "relative-path" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.26", + "unicode-ident", +] + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.37.23" @@ -907,6 +957,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + [[package]] name = "serde" version = "1.0.163" @@ -1211,6 +1267,7 @@ dependencies = [ "log", "pipewire", "pipewire-sys", + "rstest", "serial_test", "thiserror", "vhost", diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index 962fe01..55a6a6d 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -35,3 +35,4 @@ vmm-sys-util = "0.11" [dev-dependencies] serial_test = "1.0" +rstest = "0.18.2" diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs index 0d52321..f4dcced 100644 --- a/crates/sound/src/main.rs +++ b/crates/sound/src/main.rs @@ -43,6 +43,7 @@ mod tests { use serial_test::serial; use super::*; + use rstest::*; impl SoundArgs { fn from_args(socket: &str) -> Self { @@ -65,21 +66,24 @@ mod tests { assert_eq!(config.get_socket_path(), "/tmp/vhost-sound.socket"); } - #[test] + #[rstest] #[serial] - fn test_cli_backend_arg() { + #[case::null_backend("null", BackendType::Null)] + #[case::pipewire("pipewire", BackendType::Pipewire)] + #[case::alsa("alsa", BackendType::Alsa)] + fn test_cli_backend_arg(#[case] backend_name: &str, #[case] backend: BackendType) { let args: SoundArgs = Parser::parse_from([ "", "--socket", "/tmp/vhost-sound.socket ", "--backend", - "null", + backend_name, ]); let config = SoundConfig::try_from(args); assert!(config.is_ok()); let config = config.unwrap(); - assert_eq!(config.get_audio_backend(), BackendType::Null); + assert_eq!(config.get_audio_backend(), backend); } } From bf18c3ee62a48f83b983a1f1889b1d098f7fb705 Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Mon, 4 Sep 2023 10:25:41 +0200 Subject: [PATCH 32/44] sound: always compile null backend Make Null backend compiled unconditionally, removing the null-backend feature. That allows having the other backend choices in the BackendType enum to be conditional, and make them not available if their features is opted out at compile time. For example, compiled with no backend features, there is only choice (i.e., null) for --backend: ``` $ target/debug/vhost-user-sound --help Usage: vhost-user-sound --socket --backend Options: --socket vhost-user Unix domain socket path --backend audio backend to be used [possible values: null] -h, --help Print help -V, --version Print version ``` Fixes: #26 Signed-off-by: Albert Esteve --- crates/sound/Cargo.toml | 3 +-- crates/sound/src/audio_backends.rs | 15 ++------------- crates/sound/src/lib.rs | 2 ++ crates/sound/src/main.rs | 7 +++++-- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index 55a6a6d..e8512e0 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -10,8 +10,7 @@ license = "Apache-2.0 OR BSD-3-Clause" edition = "2018" [features] -default = ["null-backend", "alsa-backend", "pw-backend"] -null-backend = [] +default = ["alsa-backend", "pw-backend"] alsa-backend = ["dep:alsa"] pw-backend = ["pipewire", "libspa", "pipewire-sys", "libspa-sys", "bindgen"] diff --git a/crates/sound/src/audio_backends.rs b/crates/sound/src/audio_backends.rs index 9319bac..ad67818 100644 --- a/crates/sound/src/audio_backends.rs +++ b/crates/sound/src/audio_backends.rs @@ -3,7 +3,6 @@ #[cfg(feature = "alsa-backend")] mod alsa; -#[cfg(feature = "null-backend")] mod null; #[cfg(feature = "pw-backend")] @@ -13,11 +12,10 @@ use std::sync::{Arc, RwLock}; #[cfg(feature = "alsa-backend")] use self::alsa::AlsaBackend; -#[cfg(feature = "null-backend")] use self::null::NullBackend; #[cfg(feature = "pw-backend")] use self::pipewire::PwBackend; -use crate::{device::ControlMessage, stream::Stream, BackendType, Error, Result}; +use crate::{device::ControlMessage, stream::Stream, BackendType, Result}; pub trait AudioBackend { fn write(&self, stream_id: u32) -> Result<()>; @@ -47,23 +45,14 @@ pub trait AudioBackend { pub fn alloc_audio_backend( backend: BackendType, - // Unused when compiled with no features. - #[allow(unused_variables)] streams: Arc>>, + streams: Arc>>, ) -> Result> { log::trace!("allocating audio backend {:?}", backend); match backend { - #[cfg(feature = "null-backend")] BackendType::Null => Ok(Box::new(NullBackend::new(streams))), #[cfg(feature = "pw-backend")] BackendType::Pipewire => Ok(Box::new(PwBackend::new(streams))), #[cfg(feature = "alsa-backend")] BackendType::Alsa => Ok(Box::new(AlsaBackend::new(streams))), - // By default all features are enabled and this branch is unreachable. - // Nonetheless, it is required when inidividual features (or no features - // at all) are enabled. - // To avoid having a complicated compilation condition and make the - // code more maintainable, we supress the unreachable_patterns warning. - #[allow(unreachable_patterns)] - _ => Err(Error::AudioBackendNotSupported), } } diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index 23fed0a..eee1250 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -95,7 +95,9 @@ impl From for Error { pub enum BackendType { #[default] Null, + #[cfg(feature = "pw-backend")] Pipewire, + #[cfg(feature = "alsa-backend")] Alsa, } diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs index f4dcced..b7403e2 100644 --- a/crates/sound/src/main.rs +++ b/crates/sound/src/main.rs @@ -69,8 +69,11 @@ mod tests { #[rstest] #[serial] #[case::null_backend("null", BackendType::Null)] - #[case::pipewire("pipewire", BackendType::Pipewire)] - #[case::alsa("alsa", BackendType::Alsa)] + #[cfg_attr( + feature = "pw-backend", + case::pipewire("pipewire", BackendType::Pipewire) + )] + #[cfg_attr(feature = "alsa-backend", case::alsa("alsa", BackendType::Alsa))] fn test_cli_backend_arg(#[case] backend_name: &str, #[case] backend: BackendType) { let args: SoundArgs = Parser::parse_from([ "", From 917d24c7fa247e151a0ed44fe055f747d0c7f67e Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Mon, 11 Sep 2023 16:47:08 +0200 Subject: [PATCH 33/44] crates/sound: Rebase pipewire backend Rebase pipewire Backend Co-authored-by: Matias Ezequiel Vara Larsen Co-authored-by: Dorinda Bassey Signed-off-by: Dorinda Bassey --- Cargo.lock | 73 +-- crates/sound/Cargo.toml | 8 +- crates/sound/src/audio_backends/pipewire.rs | 538 +++++++++++++++++- crates/sound/src/audio_backends/pw_backend.rs | 172 ------ crates/sound/src/lib.rs | 4 + 5 files changed, 573 insertions(+), 222 deletions(-) delete mode 100644 crates/sound/src/audio_backends/pw_backend.rs diff --git a/Cargo.lock b/Cargo.lock index 5007d0c..f59e7ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,24 +124,22 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.64.0" +version = "0.66.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.3.3", "cexpr", "clang-sys", "lazy_static", "lazycell", - "log", "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 1.0.109", - "which", + "syn 2.0.31", ] [[package]] @@ -236,7 +234,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.31", ] [[package]] @@ -251,6 +249,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie-factory" version = "0.3.2" @@ -397,7 +404,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.31", ] [[package]] @@ -581,27 +588,26 @@ dependencies = [ [[package]] name = "libspa" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667dfbb50c3d1f7ee1d33afdc04d1255923ece7642db3303046e7d63d997d77d" +version = "0.7.2" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=068f16e4bcc2a58657ceb53bd134acb5b00a5391#068f16e4bcc2a58657ceb53bd134acb5b00a5391" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.3.3", "cc", + "convert_case", "cookie-factory", - "errno 0.3.1", "libc", "libspa-sys", + "nix 0.26.2", "nom", "system-deps", ] [[package]] name = "libspa-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5b88f52534df7ca88d451ae9628e22124e3cc5c60966465a7db479534c7a" +version = "0.7.2" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=068f16e4bcc2a58657ceb53bd134acb5b00a5391#068f16e4bcc2a58657ceb53bd134acb5b00a5391" dependencies = [ - "bindgen 0.64.0", + "bindgen 0.66.1", "cc", "system-deps", ] @@ -749,13 +755,11 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pipewire" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2180a4a84b855be86e6cd72fa6fd4318278871d2b1082e7cd05fe64b135ccb" +version = "0.7.2" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=068f16e4bcc2a58657ceb53bd134acb5b00a5391#068f16e4bcc2a58657ceb53bd134acb5b00a5391" dependencies = [ "anyhow", - "bitflags 1.3.2", - "errno 0.3.1", + "bitflags 2.3.3", "libc", "libspa", "libspa-sys", @@ -767,11 +771,10 @@ dependencies = [ [[package]] name = "pipewire-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a95290eedb7fb6aa3922fdc0261cd0ddeb940abcdbdef28778928106554d2123" +version = "0.7.2" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=068f16e4bcc2a58657ceb53bd134acb5b00a5391#068f16e4bcc2a58657ceb53bd134acb5b00a5391" dependencies = [ - "bindgen 0.64.0", + "bindgen 0.66.1", "libspa-sys", "system-deps", ] @@ -905,7 +908,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.26", + "syn 2.0.31", "unicode-ident", ] @@ -1049,9 +1052,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.26" +version = "2.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" dependencies = [ "proc-macro2", "quote", @@ -1117,7 +1120,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.31", ] [[package]] @@ -1160,6 +1163,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "utf8parse" version = "0.2.1" @@ -1259,14 +1268,10 @@ name = "vhost-user-sound" version = "0.1.0" dependencies = [ "alsa", - "bindgen 0.64.0", "clap", "env_logger", - "libspa", - "libspa-sys", "log", "pipewire", - "pipewire-sys", "rstest", "serial_test", "thiserror", diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index 55a6a6d..5d010d3 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -13,18 +13,14 @@ edition = "2018" default = ["null-backend", "alsa-backend", "pw-backend"] null-backend = [] alsa-backend = ["dep:alsa"] -pw-backend = ["pipewire", "libspa", "pipewire-sys", "libspa-sys", "bindgen"] +pw-backend = ["pw"] [dependencies] alsa = { version = "0.7", optional = true } -bindgen = { version = "0.64.0", optional = true } clap = { version = "4.1", features = ["derive"] } env_logger = "0.10" -libspa = { version = "0.6.0", optional = true } -libspa-sys = { version = "0.6.0", optional = true } log = "0.4" -pipewire = { version = "0.6.0", optional = true } -pipewire-sys = { version = "0.6.0", optional = true } +pw = { package = "pipewire", git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs.git", rev = "068f16e4bcc2a58657ceb53bd134acb5b00a5391", optional = true } thiserror = "1.0" vhost = { version = "0.6", features = ["vhost-user-slave"] } vhost-user-backend = "0.8" diff --git a/crates/sound/src/audio_backends/pipewire.rs b/crates/sound/src/audio_backends/pipewire.rs index c051c08..247eaf1 100644 --- a/crates/sound/src/audio_backends/pipewire.rs +++ b/crates/sound/src/audio_backends/pipewire.rs @@ -1,30 +1,548 @@ // Pipewire backend device // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use std::sync::{Arc, RwLock}; +use crate::device::ControlMessage; +use crate::Result; +use virtio_queue::Descriptor; +use vm_memory::Bytes; use super::AudioBackend; -use crate::{Result, Stream}; +use crate::virtio_sound::{ + VirtioSndPcmSetParams, VIRTIO_SND_D_INPUT, VIRTIO_SND_D_OUTPUT, VIRTIO_SND_PCM_FMT_A_LAW, + VIRTIO_SND_PCM_FMT_FLOAT, VIRTIO_SND_PCM_FMT_FLOAT64, VIRTIO_SND_PCM_FMT_MU_LAW, + VIRTIO_SND_PCM_FMT_S16, VIRTIO_SND_PCM_FMT_S18_3, VIRTIO_SND_PCM_FMT_S20, + VIRTIO_SND_PCM_FMT_S20_3, VIRTIO_SND_PCM_FMT_S24, VIRTIO_SND_PCM_FMT_S24_3, + VIRTIO_SND_PCM_FMT_S32, VIRTIO_SND_PCM_FMT_S8, VIRTIO_SND_PCM_FMT_U16, + VIRTIO_SND_PCM_FMT_U18_3, VIRTIO_SND_PCM_FMT_U20, VIRTIO_SND_PCM_FMT_U20_3, + VIRTIO_SND_PCM_FMT_U24, VIRTIO_SND_PCM_FMT_U24_3, VIRTIO_SND_PCM_FMT_U32, + VIRTIO_SND_PCM_FMT_U8, VIRTIO_SND_PCM_RATE_11025, VIRTIO_SND_PCM_RATE_16000, + VIRTIO_SND_PCM_RATE_176400, VIRTIO_SND_PCM_RATE_192000, VIRTIO_SND_PCM_RATE_22050, + VIRTIO_SND_PCM_RATE_32000, VIRTIO_SND_PCM_RATE_384000, VIRTIO_SND_PCM_RATE_44100, + VIRTIO_SND_PCM_RATE_48000, VIRTIO_SND_PCM_RATE_5512, VIRTIO_SND_PCM_RATE_64000, + VIRTIO_SND_PCM_RATE_8000, VIRTIO_SND_PCM_RATE_88200, VIRTIO_SND_PCM_RATE_96000, + VIRTIO_SND_S_BAD_MSG, VIRTIO_SND_S_NOT_SUPP, +}; +use crate::{Error, Stream}; +use std::{ + cmp, + collections::HashMap, + convert::TryInto, + mem::size_of, + ops::Deref, + ptr, + ptr::NonNull, + rc::Rc, + sync::{Arc, RwLock}, +}; + +use log::debug; +use spa::param::{audio::AudioFormat, audio::AudioInfoRaw, ParamType}; +use spa::pod::{serialize::PodSerializer, Object, Pod, Value}; + +use spa::sys::{ + spa_audio_info_raw, SPA_PARAM_EnumFormat, SPA_TYPE_OBJECT_Format, SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_MONO, + SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_UNKNOWN, + SPA_AUDIO_FORMAT_ALAW, SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F64, SPA_AUDIO_FORMAT_S16, + SPA_AUDIO_FORMAT_S18_LE, SPA_AUDIO_FORMAT_S20, SPA_AUDIO_FORMAT_S20_LE, SPA_AUDIO_FORMAT_S24, + SPA_AUDIO_FORMAT_S24_LE, SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_U16, + SPA_AUDIO_FORMAT_U18_LE, SPA_AUDIO_FORMAT_U20, SPA_AUDIO_FORMAT_U20_LE, SPA_AUDIO_FORMAT_U24, + SPA_AUDIO_FORMAT_U24_LE, SPA_AUDIO_FORMAT_U32, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_ULAW, + SPA_AUDIO_FORMAT_UNKNOWN, +}; + +use pw::sys::{ + pw_buffer, pw_loop, pw_thread_loop, pw_thread_loop_get_loop, pw_thread_loop_lock, + pw_thread_loop_new, pw_thread_loop_signal, pw_thread_loop_start, pw_thread_loop_unlock, + pw_thread_loop_wait, PW_ID_CORE, +}; +use pw::{properties, spa, Context, Core, LoopRef}; + +struct PwThreadLoop(NonNull); + +impl PwThreadLoop { + pub fn new(name: Option<&str>) -> Option { + let inner = unsafe { + pw_thread_loop_new( + name.map_or(ptr::null(), |p| p.as_ptr() as *const _), + std::ptr::null_mut(), + ) + }; + if inner.is_null() { + None + } else { + Some(Self( + NonNull::new(inner).expect("pw_thread_loop can't be null"), + )) + } + } + + pub fn get_loop(&self) -> PwInnerLoop { + let inner = unsafe { pw_thread_loop_get_loop(self.0.as_ptr()) }; + PwInnerLoop { + inner: Rc::new(NonNull::new(inner).unwrap()), + } + } + + pub fn unlock(&self) { + unsafe { pw_thread_loop_unlock(self.0.as_ptr()) } + } + + pub fn lock(&self) { + unsafe { pw_thread_loop_lock(self.0.as_ptr()) } + } + + pub fn start(&self) { + unsafe { + pw_thread_loop_start(self.0.as_ptr()); + } + } + + pub fn signal(&self) { + unsafe { + pw_thread_loop_signal(self.0.as_ptr(), false); + } + } + + pub fn wait(&self) { + unsafe { + pw_thread_loop_wait(self.0.as_ptr()); + } + } +} + +#[derive(Debug, Clone)] +struct PwInnerLoop { + inner: Rc>, +} + +impl AsRef for PwInnerLoop { + fn as_ref(&self) -> &LoopRef { + self.deref() + } +} + +impl Deref for PwInnerLoop { + type Target = LoopRef; + + fn deref(&self) -> &Self::Target { + unsafe { &*(self.inner.as_ptr() as *mut LoopRef) } + } +} + +// SAFETY: Safe as the structure can be sent to another thread. +unsafe impl Send for PwBackend {} + +// SAFETY: Safe as the structure can be shared with another thread as the state +// is protected with a lock. +unsafe impl Sync for PwBackend {} pub struct PwBackend { - streams: Arc>>, + pub stream_params: Arc>>, + thread_loop: Arc, + pub core: Core, + #[allow(dead_code)] + context: Context, + pub stream_hash: RwLock>, + pub stream_listener: RwLock>>, } impl PwBackend { - pub fn new(streams: Arc>>) -> Self { - Self { streams } + pub fn new(stream_params: Arc>>) -> Self { + pw::init(); + + let thread_loop = Arc::new(PwThreadLoop::new(Some("Pipewire thread loop")).unwrap()); + let get_loop = thread_loop.get_loop(); + + thread_loop.lock(); + + let context = pw::Context::new(&get_loop).expect("failed to create context"); + thread_loop.start(); + let core = context.connect(None).expect("Failed to connect to core"); + + // Create new reference for the variable so that it can be moved into the closure. + let thread_clone = thread_loop.clone(); + + // Trigger the sync event. The server's answer won't be processed until we start the thread loop, + // so we can safely do this before setting up a callback. This lets us avoid using a Cell. + let pending = core.sync(0).expect("sync failed"); + let _listener_core = core + .add_listener_local() + .done(move |id, seq| { + if id == PW_ID_CORE && seq == pending { + thread_clone.signal(); + } + }) + .register(); + + thread_loop.wait(); + thread_loop.unlock(); + + log::trace!("pipewire backend running"); + + Self { + stream_params, + thread_loop, + core, + context, + stream_hash: RwLock::new(HashMap::new()), + stream_listener: RwLock::new(HashMap::new()), + } } } impl AudioBackend for PwBackend { - fn write(&self, stream_id: u32) -> Result<()> { - log::trace!("PipewireBackend write stream_id {}", stream_id); - _ = std::mem::take(&mut self.streams.write().unwrap()[stream_id as usize].buffers); + fn write(&self, _stream_id: u32) -> Result<()> { Ok(()) } - fn read(&self, _id: u32) -> Result<()> { - log::trace!("PipewireBackend read stream_id {}", _id); + fn read(&self, _stream_id: u32) -> Result<()> { + log::trace!("PipewireBackend read stream_id {}", _stream_id); + Ok(()) + } + + fn set_parameters(&self, stream_id: u32, mut msg: ControlMessage) -> Result<()> { + let descriptors: Vec = msg.desc_chain.clone().collect(); + let desc_request = &descriptors[0]; + let request = msg + .desc_chain + .memory() + .read_obj::(desc_request.addr()) + .unwrap(); + { + let stream_clone = self.stream_params.clone(); + let mut stream_params = stream_clone.write().unwrap(); + let st = stream_params + .get_mut(stream_id as usize) + .expect("Stream does not exist"); + if let Err(err) = st.state.set_parameters() { + log::error!("Stream {} set_parameters {}", stream_id, err); + msg.code = VIRTIO_SND_S_BAD_MSG; + } else if !st.supports_format(request.format) || !st.supports_rate(request.rate) { + msg.code = VIRTIO_SND_S_NOT_SUPP; + } else { + st.params.features = request.features; + st.params.buffer_bytes = request.buffer_bytes; + st.params.period_bytes = request.period_bytes; + st.params.channels = request.channels; + st.params.format = request.format; + st.params.rate = request.rate; + } + } + drop(msg); + + Ok(()) + } + + fn prepare(&self, stream_id: u32) -> Result<()> { + debug!("pipewire prepare"); + let prepare_result = self.stream_params.write().unwrap()[stream_id as usize] + .state + .prepare(); + if let Err(err) = prepare_result { + log::error!("Stream {} prepare {}", stream_id, err); + } else { + let mut stream_hash = self.stream_hash.write().unwrap(); + let mut stream_listener = self.stream_listener.write().unwrap(); + self.thread_loop.lock(); + let stream_params = self.stream_params.read().unwrap(); + + let params = &stream_params[stream_id as usize].params; + + let mut pos: [u32; 64] = [SPA_AUDIO_CHANNEL_UNKNOWN; 64]; + + match params.channels { + 6 => { + pos[0] = SPA_AUDIO_CHANNEL_FL; + pos[1] = SPA_AUDIO_CHANNEL_FR; + pos[2] = SPA_AUDIO_CHANNEL_FC; + pos[3] = SPA_AUDIO_CHANNEL_LFE; + pos[4] = SPA_AUDIO_CHANNEL_RL; + pos[5] = SPA_AUDIO_CHANNEL_RR; + } + 5 => { + pos[0] = SPA_AUDIO_CHANNEL_FL; + pos[1] = SPA_AUDIO_CHANNEL_FR; + pos[2] = SPA_AUDIO_CHANNEL_FC; + pos[3] = SPA_AUDIO_CHANNEL_LFE; + pos[4] = SPA_AUDIO_CHANNEL_RC; + } + 4 => { + pos[0] = SPA_AUDIO_CHANNEL_FL; + pos[1] = SPA_AUDIO_CHANNEL_FR; + pos[2] = SPA_AUDIO_CHANNEL_FC; + pos[3] = SPA_AUDIO_CHANNEL_RC; + } + 3 => { + pos[0] = SPA_AUDIO_CHANNEL_FL; + pos[1] = SPA_AUDIO_CHANNEL_FR; + pos[2] = SPA_AUDIO_CHANNEL_LFE; + } + 2 => { + pos[0] = SPA_AUDIO_CHANNEL_FL; + pos[1] = SPA_AUDIO_CHANNEL_FR; + } + 1 => { + pos[0] = SPA_AUDIO_CHANNEL_MONO; + } + _ => { + return Err(Error::ChannelNotSupported(params.channels)); + } + } + + let info = spa_audio_info_raw { + format: match params.format { + VIRTIO_SND_PCM_FMT_MU_LAW => SPA_AUDIO_FORMAT_ULAW, + VIRTIO_SND_PCM_FMT_A_LAW => SPA_AUDIO_FORMAT_ALAW, + VIRTIO_SND_PCM_FMT_S8 => SPA_AUDIO_FORMAT_S8, + VIRTIO_SND_PCM_FMT_U8 => SPA_AUDIO_FORMAT_U8, + VIRTIO_SND_PCM_FMT_S16 => SPA_AUDIO_FORMAT_S16, + VIRTIO_SND_PCM_FMT_U16 => SPA_AUDIO_FORMAT_U16, + VIRTIO_SND_PCM_FMT_S18_3 => SPA_AUDIO_FORMAT_S18_LE, + VIRTIO_SND_PCM_FMT_U18_3 => SPA_AUDIO_FORMAT_U18_LE, + VIRTIO_SND_PCM_FMT_S20_3 => SPA_AUDIO_FORMAT_S20_LE, + VIRTIO_SND_PCM_FMT_U20_3 => SPA_AUDIO_FORMAT_U20_LE, + VIRTIO_SND_PCM_FMT_S24_3 => SPA_AUDIO_FORMAT_S24_LE, + VIRTIO_SND_PCM_FMT_U24_3 => SPA_AUDIO_FORMAT_U24_LE, + VIRTIO_SND_PCM_FMT_S20 => SPA_AUDIO_FORMAT_S20, + VIRTIO_SND_PCM_FMT_U20 => SPA_AUDIO_FORMAT_U20, + VIRTIO_SND_PCM_FMT_S24 => SPA_AUDIO_FORMAT_S24, + VIRTIO_SND_PCM_FMT_U24 => SPA_AUDIO_FORMAT_U24, + VIRTIO_SND_PCM_FMT_S32 => SPA_AUDIO_FORMAT_S32, + VIRTIO_SND_PCM_FMT_U32 => SPA_AUDIO_FORMAT_U32, + VIRTIO_SND_PCM_FMT_FLOAT => SPA_AUDIO_FORMAT_F32, + VIRTIO_SND_PCM_FMT_FLOAT64 => SPA_AUDIO_FORMAT_F64, + _ => SPA_AUDIO_FORMAT_UNKNOWN, + }, + rate: match params.rate { + VIRTIO_SND_PCM_RATE_5512 => 5512, + VIRTIO_SND_PCM_RATE_8000 => 8000, + VIRTIO_SND_PCM_RATE_11025 => 11025, + VIRTIO_SND_PCM_RATE_16000 => 16000, + VIRTIO_SND_PCM_RATE_22050 => 22050, + VIRTIO_SND_PCM_RATE_32000 => 32000, + VIRTIO_SND_PCM_RATE_44100 => 44100, + VIRTIO_SND_PCM_RATE_48000 => 48000, + VIRTIO_SND_PCM_RATE_64000 => 64000, + VIRTIO_SND_PCM_RATE_88200 => 88200, + VIRTIO_SND_PCM_RATE_96000 => 96000, + VIRTIO_SND_PCM_RATE_176400 => 176400, + VIRTIO_SND_PCM_RATE_192000 => 192000, + VIRTIO_SND_PCM_RATE_384000 => 384000, + _ => 44100, + }, + flags: 0, + channels: params.channels as u32, + position: pos, + }; + + let mut audio_info = AudioInfoRaw::new(); + audio_info.set_format(AudioFormat::S16LE); + audio_info.set_rate(info.rate); + audio_info.set_channels(info.channels); + + let values: Vec = PodSerializer::serialize( + std::io::Cursor::new(Vec::new()), + &Value::Object(Object { + type_: SPA_TYPE_OBJECT_Format, + id: SPA_PARAM_EnumFormat, + properties: audio_info.into(), + }), + ) + .unwrap() + .0 + .into_inner(); + + let value_clone = values.clone(); + + let mut param = [Pod::from_bytes(&values).unwrap()]; + + let props = properties! { + *pw::keys::MEDIA_TYPE => "Audio", + *pw::keys::MEDIA_CATEGORY => "Playback", + }; + + let stream = pw::stream::Stream::new(&self.core, "audio-output", props) + .expect("could not create new stream"); + + let streams = self.stream_params.clone(); + + let listener_stream = stream + .add_local_listener() + .state_changed(|old, new| { + debug!("State changed: {:?} -> {:?}", old, new); + }) + .param_changed(move |stream, id, _data, param| { + let Some(_param) = param else { + return; + }; + if id != ParamType::Format.as_raw() { + return; + } + let mut param = [Pod::from_bytes(&value_clone).unwrap()]; + + //callback to negotiate new set of streams + stream + .update_params(&mut param) + .expect("could not update params"); + }) + .process(move |stream, _data| { + //todo: use safe dequeue_buffer(), contribute queue_buffer() + unsafe { + let b: *mut pw_buffer = stream.dequeue_raw_buffer(); + if b.is_null() { + return; + } + let buf = (*b).buffer; + let datas = (*buf).datas; + + let p = (*datas).data as *mut u8; + + if p.is_null() { + return; + } + + // to calculate as sizeof(int16_t) * NR_CHANNELS + let frame_size = info.channels as u32 * size_of::() as u32; + let req = (*b).requested * (frame_size as u64); + let mut n_bytes = cmp::min(req as u32, (*datas).maxsize); + + let mut streams = streams.write().unwrap(); + let streams = streams + .get_mut(stream_id as usize) + .expect("Stream does not exist"); + let Some(buffer) = streams.buffers.front_mut() else { + return; + }; + + let mut start = buffer.pos; + + let avail = (buffer.bytes.len() - start) as i32; + + if avail <= 0 { + // pad with silence + ptr::write_bytes(p, 0, n_bytes as usize); + } else { + if avail < n_bytes as i32 { + n_bytes = avail.try_into().unwrap(); + } + + let slice = &buffer.bytes[buffer.pos..]; + p.copy_from(slice.as_ptr(), slice.len()); + + start += n_bytes as usize; + + buffer.pos = start; + + if start >= buffer.bytes.len() { + streams.buffers.pop_front(); + } + } + + (*(*datas).chunk).offset = 0; + (*(*datas).chunk).stride = frame_size as i32; + (*(*datas).chunk).size = n_bytes; + + stream.queue_raw_buffer(b); + } + }) + .register() + .expect("failed to register stream listener"); + + stream_listener.insert(stream_id, listener_stream); + + let direction = match stream_params[stream_id as usize].direction { + VIRTIO_SND_D_OUTPUT => spa::Direction::Output, + VIRTIO_SND_D_INPUT => spa::Direction::Input, + _ => panic!("Invalid direction"), + }; + + stream + .connect( + direction, + Some(pw::constants::ID_ANY), + pw::stream::StreamFlags::RT_PROCESS + | pw::stream::StreamFlags::AUTOCONNECT + | pw::stream::StreamFlags::INACTIVE + | pw::stream::StreamFlags::MAP_BUFFERS, + &mut param, + ) + .expect("could not connect to the stream"); + + self.thread_loop.unlock(); + + // insert created stream in a hash table + stream_hash.insert(stream_id, stream); + } + + Ok(()) + } + + fn release(&self, stream_id: u32, mut msg: ControlMessage) -> Result<()> { + debug!("pipewire backend, release function"); + let release_result = self.stream_params.write().unwrap()[stream_id as usize] + .state + .release(); + if let Err(err) = release_result { + log::error!("Stream {} release {}", stream_id, err); + msg.code = VIRTIO_SND_S_BAD_MSG; + } else { + self.thread_loop.lock(); + let mut stream_hash = self.stream_hash.write().unwrap(); + let mut stream_listener = self.stream_listener.write().unwrap(); + let st_buffer = &mut self.stream_params.write().unwrap(); + + let Some(stream) = stream_hash.get(&stream_id) else { + return Err(Error::StreamWithIdNotFound(stream_id)); + }; + stream.disconnect().expect("could not disconnect stream"); + std::mem::take(&mut st_buffer[stream_id as usize].buffers); + stream_hash.remove(&stream_id); + stream_listener.remove(&stream_id); + + self.thread_loop.unlock(); + } + + Ok(()) + } + + fn start(&self, stream_id: u32) -> Result<()> { + debug!("pipewire start"); + let start_result = self.stream_params.write().unwrap()[stream_id as usize] + .state + .start(); + if let Err(err) = start_result { + // log the error and continue + log::error!("Stream {} start {}", stream_id, err); + } else { + self.thread_loop.lock(); + let stream_hash = self.stream_hash.read().unwrap(); + let Some(stream) = stream_hash.get(&stream_id) else { + return Err(Error::StreamWithIdNotFound(stream_id)); + }; + stream.set_active(true).expect("could not start stream"); + self.thread_loop.unlock(); + } + Ok(()) + } + + fn stop(&self, stream_id: u32) -> Result<()> { + debug!("pipewire stop"); + let stop_result = self.stream_params.write().unwrap()[stream_id as usize] + .state + .stop(); + if let Err(err) = stop_result { + log::error!("Stream {} stop {}", stream_id, err); + } else { + self.thread_loop.lock(); + let stream_hash = self.stream_hash.read().unwrap(); + let Some(stream) = stream_hash.get(&stream_id) else { + return Err(Error::StreamWithIdNotFound(stream_id)); + }; + stream.set_active(false).expect("could not stop stream"); + self.thread_loop.unlock(); + } + Ok(()) } } diff --git a/crates/sound/src/audio_backends/pw_backend.rs b/crates/sound/src/audio_backends/pw_backend.rs deleted file mode 100644 index b6c2c58..0000000 --- a/crates/sound/src/audio_backends/pw_backend.rs +++ /dev/null @@ -1,172 +0,0 @@ -// Pipewire backend device -// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause - -use super::AudioBackend; -use crate::vhu_sound::NR_STREAMS; -use crate::PCMParams; -use crate::Result; -use std::ops::Deref; -use std::ptr; -use std::ptr::NonNull; -use std::sync::Arc; -use std::sync::RwLock; - -use pipewire as pw; -use pw::sys::{pw_loop, pw_thread_loop_new, pw_thread_loop_signal, PW_ID_CORE}; -use pw::sys::{pw_thread_loop, pw_thread_loop_start, pw_thread_loop_wait}; -use pw::sys::{pw_thread_loop_get_loop, pw_thread_loop_lock, pw_thread_loop_unlock}; -use pw::Core; -use pw::LoopRef; - -struct PwThreadLoop(NonNull); - -impl PwThreadLoop { - pub fn new(name: Option<&str>) -> Option { - let inner = unsafe { - pw_thread_loop_new( - name.map_or(ptr::null(), |p| p.as_ptr() as *const _), - std::ptr::null_mut(), - ) - }; - if inner.is_null() { - None - } else { - Some(Self( - NonNull::new(inner).expect("pw_thread_loop can't be null"), - )) - } - } - - pub fn get_loop(&self) -> PwThreadLoopTheLoop { - let inner = unsafe { pw_thread_loop_get_loop(self.0.as_ptr()) }; - PwThreadLoopTheLoop(NonNull::new(inner).unwrap()) - } - - pub fn unlock(&self) { - unsafe { pw_thread_loop_unlock(self.0.as_ptr()) } - } - - pub fn lock(&self) { - unsafe { pw_thread_loop_lock(self.0.as_ptr()) } - } - - pub fn start(&self) { - unsafe { - pw_thread_loop_start(self.0.as_ptr()); - } - } - - pub fn signal(&self) { - unsafe { - pw_thread_loop_signal(self.0.as_ptr(), false); - } - } - - pub fn wait(&self) { - unsafe { - pw_thread_loop_wait(self.0.as_ptr()); - } - } -} - -#[derive(Debug, Clone)] -struct PwThreadLoopTheLoop(NonNull); - -impl AsRef for PwThreadLoopTheLoop { - fn as_ref(&self) -> &LoopRef { - self.deref() - } -} - -impl Deref for PwThreadLoopTheLoop { - type Target = LoopRef; - - fn deref(&self) -> &Self::Target { - unsafe { &*(self.0.as_ptr() as *mut LoopRef) } - } -} - -// SAFETY: Safe as the structure can be sent to another thread. -unsafe impl Send for PwBackend {} - -// SAFETY: Safe as the structure can be shared with another thread as the state -// is protected with a lock. -unsafe impl Sync for PwBackend {} - -pub struct PwBackend { - thread_loop: Arc, - pub core: Core, - pub stream_params: RwLock>, -} - -impl PwBackend { - pub fn new() -> Self { - pw::init(); - - let thread_loop = Arc::new(PwThreadLoop::new(Some("Pipewire thread loop")).unwrap()); - let get_loop = thread_loop.get_loop(); - - thread_loop.lock(); - - let context = pw::Context::new(&get_loop).expect("failed to create context"); - thread_loop.start(); - let core = context.connect(None).expect("Failed to connect to core"); - - // Create new reference for the variable so that it can be moved into the closure. - let thread_clone = thread_loop.clone(); - - // Trigger the sync event. The server's answer won't be processed until we start the thread loop, - // so we can safely do this before setting up a callback. This lets us avoid using a Cell. - let pending = core.sync(0).expect("sync failed"); - let _listener_core = core - .add_listener_local() - .done(move |id, seq| { - if id == PW_ID_CORE && seq == pending { - thread_clone.signal(); - } - }) - .register(); - - thread_loop.wait(); - thread_loop.unlock(); - - println!("pipewire backend running"); - - let streams_param = vec![PCMParams::default(); NR_STREAMS]; - - Self { - thread_loop, - core, - stream_params: RwLock::new(streams_param), - } - } -} - -impl AudioBackend for PwBackend { - fn write(&self, stream_id: u32) -> Result<()> { - println!("pipewire backend, writting to stream: {}", stream_id); - Ok(()) - } - - fn read(&self, _stream_id: u32) -> Result<()> { - /* - let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?; - let zero_mem = vec![0u8; buf.len()]; - - buf.copy_from(&zero_mem); - */ - Ok(()) - } - - fn set_param(&self, stream_id: u32, params: PCMParams) -> Result<()> { - let mut stream_params = self.stream_params.write().unwrap(); - stream_params[stream_id as usize] = params; - Ok(()) - } - - fn prepare(&self, _stream_id: u32) -> Result<()> { - self.thread_loop.lock(); - self.thread_loop.unlock(); - Ok(()) - } -} diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index 23fed0a..fd277e9 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -77,6 +77,10 @@ pub enum Error { UnexpectedDescriptorSize(usize, u32), #[error("Protocol or device error: {0}")] Stream(stream::Error), + #[error("Stream with id {0} not found")] + StreamWithIdNotFound(u32), + #[error("Channel number not supported: {0}")] + ChannelNotSupported(u8), } impl From for IoError { From 5984715806815a8835ff63b98f14bc5404fa9009 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Thu, 14 Sep 2023 14:52:09 +0200 Subject: [PATCH 34/44] audio_backends/pipewire.rs: use safe queue API use safe queue API Signed-off-by: Dorinda Bassey --- crates/sound/src/audio_backends/pipewire.rs | 96 +++++++++------------ 1 file changed, 43 insertions(+), 53 deletions(-) diff --git a/crates/sound/src/audio_backends/pipewire.rs b/crates/sound/src/audio_backends/pipewire.rs index 247eaf1..355dfa6 100644 --- a/crates/sound/src/audio_backends/pipewire.rs +++ b/crates/sound/src/audio_backends/pipewire.rs @@ -24,7 +24,6 @@ use crate::virtio_sound::{ }; use crate::{Error, Stream}; use std::{ - cmp, collections::HashMap, convert::TryInto, mem::size_of, @@ -52,9 +51,9 @@ use spa::sys::{ }; use pw::sys::{ - pw_buffer, pw_loop, pw_thread_loop, pw_thread_loop_get_loop, pw_thread_loop_lock, - pw_thread_loop_new, pw_thread_loop_signal, pw_thread_loop_start, pw_thread_loop_unlock, - pw_thread_loop_wait, PW_ID_CORE, + pw_loop, pw_thread_loop, pw_thread_loop_get_loop, pw_thread_loop_lock, pw_thread_loop_new, + pw_thread_loop_signal, pw_thread_loop_start, pw_thread_loop_unlock, pw_thread_loop_wait, + PW_ID_CORE, }; use pw::{properties, spa, Context, Core, LoopRef}; @@ -139,7 +138,7 @@ unsafe impl Sync for PwBackend {} pub struct PwBackend { pub stream_params: Arc>>, - thread_loop: Arc, + thread_loop: Rc, pub core: Core, #[allow(dead_code)] context: Context, @@ -151,7 +150,7 @@ impl PwBackend { pub fn new(stream_params: Arc>>) -> Self { pw::init(); - let thread_loop = Arc::new(PwThreadLoop::new(Some("Pipewire thread loop")).unwrap()); + let thread_loop = Rc::new(PwThreadLoop::new(Some("Pipewire thread loop")).unwrap()); let get_loop = thread_loop.get_loop(); thread_loop.lock(); @@ -386,64 +385,55 @@ impl AudioBackend for PwBackend { .update_params(&mut param) .expect("could not update params"); }) - .process(move |stream, _data| { - //todo: use safe dequeue_buffer(), contribute queue_buffer() - unsafe { - let b: *mut pw_buffer = stream.dequeue_raw_buffer(); - if b.is_null() { - return; - } - let buf = (*b).buffer; - let datas = (*buf).datas; - - let p = (*datas).data as *mut u8; - - if p.is_null() { - return; - } - - // to calculate as sizeof(int16_t) * NR_CHANNELS + .process(move |stream, _data| match stream.dequeue_buffer() { + None => debug!("No buffer recieved"), + Some(mut buf) => { + let datas = buf.datas_mut(); let frame_size = info.channels as u32 * size_of::() as u32; - let req = (*b).requested * (frame_size as u64); - let mut n_bytes = cmp::min(req as u32, (*datas).maxsize); + let data = &mut datas[0]; + let n_bytes = if let Some(slice) = data.data() { + let mut n_bytes = slice.len(); + let mut streams = streams.write().unwrap(); + let streams = streams + .get_mut(stream_id as usize) + .expect("Stream does not exist"); + let Some(buffer) = streams.buffers.front_mut() else { + return; + }; - let mut streams = streams.write().unwrap(); - let streams = streams - .get_mut(stream_id as usize) - .expect("Stream does not exist"); - let Some(buffer) = streams.buffers.front_mut() else { - return; - }; + let mut start = buffer.pos; - let mut start = buffer.pos; + let avail = (buffer.bytes.len() - start) as i32; - let avail = (buffer.bytes.len() - start) as i32; - - if avail <= 0 { - // pad with silence - ptr::write_bytes(p, 0, n_bytes as usize); - } else { if avail < n_bytes as i32 { n_bytes = avail.try_into().unwrap(); } + let p = &mut slice[buffer.pos..start + n_bytes]; + if avail <= 0 { + // pad with silence + unsafe { + ptr::write_bytes(p.as_mut_ptr(), 0, n_bytes); + } + } else { + let slice = &buffer.bytes[buffer.pos..start + n_bytes]; + p.copy_from_slice(slice); - let slice = &buffer.bytes[buffer.pos..]; - p.copy_from(slice.as_ptr(), slice.len()); + start += n_bytes; - start += n_bytes as usize; + buffer.pos = start; - buffer.pos = start; - - if start >= buffer.bytes.len() { - streams.buffers.pop_front(); + if start >= buffer.bytes.len() { + streams.buffers.pop_front(); + } } - } - - (*(*datas).chunk).offset = 0; - (*(*datas).chunk).stride = frame_size as i32; - (*(*datas).chunk).size = n_bytes; - - stream.queue_raw_buffer(b); + n_bytes + } else { + 0 + }; + let chunk = data.chunk_mut(); + *chunk.offset_mut() = 0; + *chunk.stride_mut() = frame_size as _; + *chunk.size_mut() = n_bytes as _; } }) .register() From be904f93fab45120d8972e528e8e4c1f8c9e0d54 Mon Sep 17 00:00:00 2001 From: Matias Ezequiel Vara Larsen Date: Wed, 20 Sep 2023 10:25:45 +0200 Subject: [PATCH 35/44] remove unnecessary casting to the same type Signed-off-by: Matias Ezequiel Vara Larsen --- crates/sound/src/audio_backends/pipewire.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sound/src/audio_backends/pipewire.rs b/crates/sound/src/audio_backends/pipewire.rs index 355dfa6..7cf703b 100644 --- a/crates/sound/src/audio_backends/pipewire.rs +++ b/crates/sound/src/audio_backends/pipewire.rs @@ -389,7 +389,7 @@ impl AudioBackend for PwBackend { None => debug!("No buffer recieved"), Some(mut buf) => { let datas = buf.datas_mut(); - let frame_size = info.channels as u32 * size_of::() as u32; + let frame_size = info.channels * size_of::() as u32; let data = &mut datas[0]; let n_bytes = if let Some(slice) = data.data() { let mut n_bytes = slice.len(); From 18eace76f8b7142f08709e91d240aff88376fbc0 Mon Sep 17 00:00:00 2001 From: Matias Ezequiel Vara Larsen Date: Wed, 20 Sep 2023 10:38:06 +0200 Subject: [PATCH 36/44] cargo fmt Signed-off-by: Matias Ezequiel Vara Larsen --- crates/sound/src/audio_backends/pipewire.rs | 98 ++++++++++++--------- crates/sound/src/main.rs | 2 +- 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/crates/sound/src/audio_backends/pipewire.rs b/crates/sound/src/audio_backends/pipewire.rs index 7cf703b..91201a4 100644 --- a/crates/sound/src/audio_backends/pipewire.rs +++ b/crates/sound/src/audio_backends/pipewire.rs @@ -1,28 +1,6 @@ // Pipewire backend device // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause -use crate::device::ControlMessage; -use crate::Result; -use virtio_queue::Descriptor; -use vm_memory::Bytes; - -use super::AudioBackend; -use crate::virtio_sound::{ - VirtioSndPcmSetParams, VIRTIO_SND_D_INPUT, VIRTIO_SND_D_OUTPUT, VIRTIO_SND_PCM_FMT_A_LAW, - VIRTIO_SND_PCM_FMT_FLOAT, VIRTIO_SND_PCM_FMT_FLOAT64, VIRTIO_SND_PCM_FMT_MU_LAW, - VIRTIO_SND_PCM_FMT_S16, VIRTIO_SND_PCM_FMT_S18_3, VIRTIO_SND_PCM_FMT_S20, - VIRTIO_SND_PCM_FMT_S20_3, VIRTIO_SND_PCM_FMT_S24, VIRTIO_SND_PCM_FMT_S24_3, - VIRTIO_SND_PCM_FMT_S32, VIRTIO_SND_PCM_FMT_S8, VIRTIO_SND_PCM_FMT_U16, - VIRTIO_SND_PCM_FMT_U18_3, VIRTIO_SND_PCM_FMT_U20, VIRTIO_SND_PCM_FMT_U20_3, - VIRTIO_SND_PCM_FMT_U24, VIRTIO_SND_PCM_FMT_U24_3, VIRTIO_SND_PCM_FMT_U32, - VIRTIO_SND_PCM_FMT_U8, VIRTIO_SND_PCM_RATE_11025, VIRTIO_SND_PCM_RATE_16000, - VIRTIO_SND_PCM_RATE_176400, VIRTIO_SND_PCM_RATE_192000, VIRTIO_SND_PCM_RATE_22050, - VIRTIO_SND_PCM_RATE_32000, VIRTIO_SND_PCM_RATE_384000, VIRTIO_SND_PCM_RATE_44100, - VIRTIO_SND_PCM_RATE_48000, VIRTIO_SND_PCM_RATE_5512, VIRTIO_SND_PCM_RATE_64000, - VIRTIO_SND_PCM_RATE_8000, VIRTIO_SND_PCM_RATE_88200, VIRTIO_SND_PCM_RATE_96000, - VIRTIO_SND_S_BAD_MSG, VIRTIO_SND_S_NOT_SUPP, -}; -use crate::{Error, Stream}; use std::{ collections::HashMap, convert::TryInto, @@ -35,27 +13,57 @@ use std::{ }; use log::debug; -use spa::param::{audio::AudioFormat, audio::AudioInfoRaw, ParamType}; -use spa::pod::{serialize::PodSerializer, Object, Pod, Value}; - -use spa::sys::{ - spa_audio_info_raw, SPA_PARAM_EnumFormat, SPA_TYPE_OBJECT_Format, SPA_AUDIO_CHANNEL_FC, - SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_MONO, - SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_UNKNOWN, - SPA_AUDIO_FORMAT_ALAW, SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F64, SPA_AUDIO_FORMAT_S16, - SPA_AUDIO_FORMAT_S18_LE, SPA_AUDIO_FORMAT_S20, SPA_AUDIO_FORMAT_S20_LE, SPA_AUDIO_FORMAT_S24, - SPA_AUDIO_FORMAT_S24_LE, SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_U16, - SPA_AUDIO_FORMAT_U18_LE, SPA_AUDIO_FORMAT_U20, SPA_AUDIO_FORMAT_U20_LE, SPA_AUDIO_FORMAT_U24, - SPA_AUDIO_FORMAT_U24_LE, SPA_AUDIO_FORMAT_U32, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_ULAW, - SPA_AUDIO_FORMAT_UNKNOWN, +use pw::{ + properties, spa, + sys::{ + pw_loop, pw_thread_loop, pw_thread_loop_get_loop, pw_thread_loop_lock, pw_thread_loop_new, + pw_thread_loop_signal, pw_thread_loop_start, pw_thread_loop_unlock, pw_thread_loop_wait, + PW_ID_CORE, + }, + Context, Core, LoopRef, }; - -use pw::sys::{ - pw_loop, pw_thread_loop, pw_thread_loop_get_loop, pw_thread_loop_lock, pw_thread_loop_new, - pw_thread_loop_signal, pw_thread_loop_start, pw_thread_loop_unlock, pw_thread_loop_wait, - PW_ID_CORE, +use spa::{ + param::{ + audio::{AudioFormat, AudioInfoRaw}, + ParamType, + }, + pod::{serialize::PodSerializer, Object, Pod, Value}, + sys::{ + spa_audio_info_raw, SPA_PARAM_EnumFormat, SPA_TYPE_OBJECT_Format, SPA_AUDIO_CHANNEL_FC, + SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_MONO, + SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, + SPA_AUDIO_CHANNEL_UNKNOWN, SPA_AUDIO_FORMAT_ALAW, SPA_AUDIO_FORMAT_F32, + SPA_AUDIO_FORMAT_F64, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S18_LE, SPA_AUDIO_FORMAT_S20, + SPA_AUDIO_FORMAT_S20_LE, SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_S24_LE, + SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_U16, SPA_AUDIO_FORMAT_U18_LE, + SPA_AUDIO_FORMAT_U20, SPA_AUDIO_FORMAT_U20_LE, SPA_AUDIO_FORMAT_U24, + SPA_AUDIO_FORMAT_U24_LE, SPA_AUDIO_FORMAT_U32, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_ULAW, + SPA_AUDIO_FORMAT_UNKNOWN, + }, +}; +use virtio_queue::Descriptor; +use vm_memory::Bytes; + +use super::AudioBackend; +use crate::{ + device::ControlMessage, + virtio_sound::{ + VirtioSndPcmSetParams, VIRTIO_SND_D_INPUT, VIRTIO_SND_D_OUTPUT, VIRTIO_SND_PCM_FMT_A_LAW, + VIRTIO_SND_PCM_FMT_FLOAT, VIRTIO_SND_PCM_FMT_FLOAT64, VIRTIO_SND_PCM_FMT_MU_LAW, + VIRTIO_SND_PCM_FMT_S16, VIRTIO_SND_PCM_FMT_S18_3, VIRTIO_SND_PCM_FMT_S20, + VIRTIO_SND_PCM_FMT_S20_3, VIRTIO_SND_PCM_FMT_S24, VIRTIO_SND_PCM_FMT_S24_3, + VIRTIO_SND_PCM_FMT_S32, VIRTIO_SND_PCM_FMT_S8, VIRTIO_SND_PCM_FMT_U16, + VIRTIO_SND_PCM_FMT_U18_3, VIRTIO_SND_PCM_FMT_U20, VIRTIO_SND_PCM_FMT_U20_3, + VIRTIO_SND_PCM_FMT_U24, VIRTIO_SND_PCM_FMT_U24_3, VIRTIO_SND_PCM_FMT_U32, + VIRTIO_SND_PCM_FMT_U8, VIRTIO_SND_PCM_RATE_11025, VIRTIO_SND_PCM_RATE_16000, + VIRTIO_SND_PCM_RATE_176400, VIRTIO_SND_PCM_RATE_192000, VIRTIO_SND_PCM_RATE_22050, + VIRTIO_SND_PCM_RATE_32000, VIRTIO_SND_PCM_RATE_384000, VIRTIO_SND_PCM_RATE_44100, + VIRTIO_SND_PCM_RATE_48000, VIRTIO_SND_PCM_RATE_5512, VIRTIO_SND_PCM_RATE_64000, + VIRTIO_SND_PCM_RATE_8000, VIRTIO_SND_PCM_RATE_88200, VIRTIO_SND_PCM_RATE_96000, + VIRTIO_SND_S_BAD_MSG, VIRTIO_SND_S_NOT_SUPP, + }, + Error, Result, Stream, }; -use pw::{properties, spa, Context, Core, LoopRef}; struct PwThreadLoop(NonNull); @@ -159,11 +167,13 @@ impl PwBackend { thread_loop.start(); let core = context.connect(None).expect("Failed to connect to core"); - // Create new reference for the variable so that it can be moved into the closure. + // Create new reference for the variable so that it can be moved into the + // closure. let thread_clone = thread_loop.clone(); - // Trigger the sync event. The server's answer won't be processed until we start the thread loop, - // so we can safely do this before setting up a callback. This lets us avoid using a Cell. + // Trigger the sync event. The server's answer won't be processed until we start + // the thread loop, so we can safely do this before setting up a + // callback. This lets us avoid using a Cell. let pending = core.sync(0).expect("sync failed"); let _listener_core = core .add_listener_local() diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs index b7403e2..0939669 100644 --- a/crates/sound/src/main.rs +++ b/crates/sound/src/main.rs @@ -40,10 +40,10 @@ fn main() { #[cfg(test)] mod tests { + use rstest::*; use serial_test::serial; use super::*; - use rstest::*; impl SoundArgs { fn from_args(socket: &str) -> Self { From a80d6ccf2228f398d6bb331f0f460a5d6675b7af Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Tue, 26 Sep 2023 10:39:46 +0200 Subject: [PATCH 37/44] pipewire.rs: Remove unsafe threadloop bindings contributed these bindings upstream, use the bindings in pipewire rust Signed-off-by: Dorinda Bassey --- Cargo.lock | 8 +- crates/sound/Cargo.toml | 2 +- crates/sound/src/audio_backends/pipewire.rs | 116 +++----------------- 3 files changed, 21 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f59e7ff..1304f00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -589,7 +589,7 @@ dependencies = [ [[package]] name = "libspa" version = "0.7.2" -source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=068f16e4bcc2a58657ceb53bd134acb5b00a5391#068f16e4bcc2a58657ceb53bd134acb5b00a5391" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=5fe090b3ac8f6fed756c4871ac18f26edda3ac89#5fe090b3ac8f6fed756c4871ac18f26edda3ac89" dependencies = [ "bitflags 2.3.3", "cc", @@ -605,7 +605,7 @@ dependencies = [ [[package]] name = "libspa-sys" version = "0.7.2" -source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=068f16e4bcc2a58657ceb53bd134acb5b00a5391#068f16e4bcc2a58657ceb53bd134acb5b00a5391" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=5fe090b3ac8f6fed756c4871ac18f26edda3ac89#5fe090b3ac8f6fed756c4871ac18f26edda3ac89" dependencies = [ "bindgen 0.66.1", "cc", @@ -756,7 +756,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pipewire" version = "0.7.2" -source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=068f16e4bcc2a58657ceb53bd134acb5b00a5391#068f16e4bcc2a58657ceb53bd134acb5b00a5391" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=5fe090b3ac8f6fed756c4871ac18f26edda3ac89#5fe090b3ac8f6fed756c4871ac18f26edda3ac89" dependencies = [ "anyhow", "bitflags 2.3.3", @@ -772,7 +772,7 @@ dependencies = [ [[package]] name = "pipewire-sys" version = "0.7.2" -source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=068f16e4bcc2a58657ceb53bd134acb5b00a5391#068f16e4bcc2a58657ceb53bd134acb5b00a5391" +source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=5fe090b3ac8f6fed756c4871ac18f26edda3ac89#5fe090b3ac8f6fed756c4871ac18f26edda3ac89" dependencies = [ "bindgen 0.66.1", "libspa-sys", diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index cc7a451..8df35e2 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -19,7 +19,7 @@ alsa = { version = "0.7", optional = true } clap = { version = "4.1", features = ["derive"] } env_logger = "0.10" log = "0.4" -pw = { package = "pipewire", git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs.git", rev = "068f16e4bcc2a58657ceb53bd134acb5b00a5391", optional = true } +pw = { package = "pipewire", git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs.git", rev = "5fe090b3ac8f6fed756c4871ac18f26edda3ac89", optional = true } thiserror = "1.0" vhost = { version = "0.6", features = ["vhost-user-slave"] } vhost-user-backend = "0.8" diff --git a/crates/sound/src/audio_backends/pipewire.rs b/crates/sound/src/audio_backends/pipewire.rs index 91201a4..8748e8e 100644 --- a/crates/sound/src/audio_backends/pipewire.rs +++ b/crates/sound/src/audio_backends/pipewire.rs @@ -5,23 +5,12 @@ use std::{ collections::HashMap, convert::TryInto, mem::size_of, - ops::Deref, ptr, - ptr::NonNull, - rc::Rc, sync::{Arc, RwLock}, }; use log::debug; -use pw::{ - properties, spa, - sys::{ - pw_loop, pw_thread_loop, pw_thread_loop_get_loop, pw_thread_loop_lock, pw_thread_loop_new, - pw_thread_loop_signal, pw_thread_loop_start, pw_thread_loop_unlock, pw_thread_loop_wait, - PW_ID_CORE, - }, - Context, Core, LoopRef, -}; +use pw::{properties, spa, sys::PW_ID_CORE, Context, Core, ThreadLoop}; use spa::{ param::{ audio::{AudioFormat, AudioInfoRaw}, @@ -65,78 +54,6 @@ use crate::{ Error, Result, Stream, }; -struct PwThreadLoop(NonNull); - -impl PwThreadLoop { - pub fn new(name: Option<&str>) -> Option { - let inner = unsafe { - pw_thread_loop_new( - name.map_or(ptr::null(), |p| p.as_ptr() as *const _), - std::ptr::null_mut(), - ) - }; - if inner.is_null() { - None - } else { - Some(Self( - NonNull::new(inner).expect("pw_thread_loop can't be null"), - )) - } - } - - pub fn get_loop(&self) -> PwInnerLoop { - let inner = unsafe { pw_thread_loop_get_loop(self.0.as_ptr()) }; - PwInnerLoop { - inner: Rc::new(NonNull::new(inner).unwrap()), - } - } - - pub fn unlock(&self) { - unsafe { pw_thread_loop_unlock(self.0.as_ptr()) } - } - - pub fn lock(&self) { - unsafe { pw_thread_loop_lock(self.0.as_ptr()) } - } - - pub fn start(&self) { - unsafe { - pw_thread_loop_start(self.0.as_ptr()); - } - } - - pub fn signal(&self) { - unsafe { - pw_thread_loop_signal(self.0.as_ptr(), false); - } - } - - pub fn wait(&self) { - unsafe { - pw_thread_loop_wait(self.0.as_ptr()); - } - } -} - -#[derive(Debug, Clone)] -struct PwInnerLoop { - inner: Rc>, -} - -impl AsRef for PwInnerLoop { - fn as_ref(&self) -> &LoopRef { - self.deref() - } -} - -impl Deref for PwInnerLoop { - type Target = LoopRef; - - fn deref(&self) -> &Self::Target { - unsafe { &*(self.inner.as_ptr() as *mut LoopRef) } - } -} - // SAFETY: Safe as the structure can be sent to another thread. unsafe impl Send for PwBackend {} @@ -146,10 +63,10 @@ unsafe impl Sync for PwBackend {} pub struct PwBackend { pub stream_params: Arc>>, - thread_loop: Rc, + thread_loop: ThreadLoop, pub core: Core, #[allow(dead_code)] - context: Context, + context: Context, pub stream_hash: RwLock>, pub stream_listener: RwLock>>, } @@ -158,12 +75,11 @@ impl PwBackend { pub fn new(stream_params: Arc>>) -> Self { pw::init(); - let thread_loop = Rc::new(PwThreadLoop::new(Some("Pipewire thread loop")).unwrap()); - let get_loop = thread_loop.get_loop(); + let thread_loop = unsafe { ThreadLoop::new(Some("Pipewire thread loop")).unwrap() }; - thread_loop.lock(); + let lock_guard = thread_loop.lock(); - let context = pw::Context::new(&get_loop).expect("failed to create context"); + let context = pw::Context::new(&thread_loop).expect("failed to create context"); thread_loop.start(); let core = context.connect(None).expect("Failed to connect to core"); @@ -179,13 +95,13 @@ impl PwBackend { .add_listener_local() .done(move |id, seq| { if id == PW_ID_CORE && seq == pending { - thread_clone.signal(); + thread_clone.signal(false); } }) .register(); thread_loop.wait(); - thread_loop.unlock(); + lock_guard.unlock(); log::trace!("pipewire backend running"); @@ -253,7 +169,7 @@ impl AudioBackend for PwBackend { } else { let mut stream_hash = self.stream_hash.write().unwrap(); let mut stream_listener = self.stream_listener.write().unwrap(); - self.thread_loop.lock(); + let lock_guard = self.thread_loop.lock(); let stream_params = self.stream_params.read().unwrap(); let params = &stream_params[stream_id as usize].params; @@ -469,7 +385,7 @@ impl AudioBackend for PwBackend { ) .expect("could not connect to the stream"); - self.thread_loop.unlock(); + lock_guard.unlock(); // insert created stream in a hash table stream_hash.insert(stream_id, stream); @@ -487,7 +403,7 @@ impl AudioBackend for PwBackend { log::error!("Stream {} release {}", stream_id, err); msg.code = VIRTIO_SND_S_BAD_MSG; } else { - self.thread_loop.lock(); + let lock_guard = self.thread_loop.lock(); let mut stream_hash = self.stream_hash.write().unwrap(); let mut stream_listener = self.stream_listener.write().unwrap(); let st_buffer = &mut self.stream_params.write().unwrap(); @@ -500,7 +416,7 @@ impl AudioBackend for PwBackend { stream_hash.remove(&stream_id); stream_listener.remove(&stream_id); - self.thread_loop.unlock(); + lock_guard.unlock(); } Ok(()) @@ -515,13 +431,13 @@ impl AudioBackend for PwBackend { // log the error and continue log::error!("Stream {} start {}", stream_id, err); } else { - self.thread_loop.lock(); + let lock_guard = self.thread_loop.lock(); let stream_hash = self.stream_hash.read().unwrap(); let Some(stream) = stream_hash.get(&stream_id) else { return Err(Error::StreamWithIdNotFound(stream_id)); }; stream.set_active(true).expect("could not start stream"); - self.thread_loop.unlock(); + lock_guard.unlock(); } Ok(()) } @@ -534,13 +450,13 @@ impl AudioBackend for PwBackend { if let Err(err) = stop_result { log::error!("Stream {} stop {}", stream_id, err); } else { - self.thread_loop.lock(); + let lock_guard = self.thread_loop.lock(); let stream_hash = self.stream_hash.read().unwrap(); let Some(stream) = stream_hash.get(&stream_id) else { return Err(Error::StreamWithIdNotFound(stream_id)); }; stream.set_active(false).expect("could not stop stream"); - self.thread_loop.unlock(); + lock_guard.unlock(); } Ok(()) From c9c86bd9aa133e04b039a422cefec43fec96dd8b Mon Sep 17 00:00:00 2001 From: Sergio Lopez Date: Wed, 27 Sep 2023 17:00:31 +0200 Subject: [PATCH 38/44] sound/pipewire: move stream_hash insert into lock Inserting a new stream into stream_hash can potentially move out of context a stream previously stored in the hash with the same id, triggering pw::stream::Stream::Drop, which calls pw_stream_destroy. As calls to the pipewire library must happen either in the pipewire thread or under thread_loop lock protection, move the insert into stream_hash under the latter. Signed-off-by: Sergio Lopez --- crates/sound/src/audio_backends/pipewire.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/sound/src/audio_backends/pipewire.rs b/crates/sound/src/audio_backends/pipewire.rs index 8748e8e..0c8e61a 100644 --- a/crates/sound/src/audio_backends/pipewire.rs +++ b/crates/sound/src/audio_backends/pipewire.rs @@ -385,10 +385,10 @@ impl AudioBackend for PwBackend { ) .expect("could not connect to the stream"); - lock_guard.unlock(); - // insert created stream in a hash table stream_hash.insert(stream_id, stream); + + lock_guard.unlock(); } Ok(()) From 3d0f785d66e8127d1908f78b462dc4a316ad206f Mon Sep 17 00:00:00 2001 From: Matias Ezequiel Vara Larsen Date: Wed, 13 Sep 2023 13:09:52 +0200 Subject: [PATCH 39/44] crates/sound: read buffers from guest memory rather than copying them In this commit, the buffers are only read from the available ring when the audio backend will consume them. The guest driver moves buffers from the used to the avail ring without knowing if the user application has updated the content. As a result, when a request is enqueued into the available ring, the device only reads the buffer from guest memory when it is required by the audio engine. By doing this, we add enough delay so that we can read the updated buffer. Signed-off-by: Matias Ezequiel Vara Larsen --- crates/sound/src/audio_backends/alsa.rs | 19 +++++++++++--- crates/sound/src/audio_backends/pipewire.rs | 11 ++++---- crates/sound/src/device.rs | 18 +++++++------ crates/sound/src/stream.rs | 28 +++++++++++++++++---- 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/crates/sound/src/audio_backends/alsa.rs b/crates/sound/src/audio_backends/alsa.rs index 91a8820..e158ddd 100644 --- a/crates/sound/src/audio_backends/alsa.rs +++ b/crates/sound/src/audio_backends/alsa.rs @@ -145,13 +145,17 @@ fn write_samples_direct( let Some(buffer) = stream.buffers.front_mut() else { return Ok(false); }; - let mut iter = buffer.bytes[buffer.pos..].iter().cloned(); + let mut buf = vec![0; buffer.data_descriptor.len() as usize]; + let read_bytes = buffer + .consume(&mut buf) + .expect("failed to read buffer from guest"); + let mut iter = buf[0..read_bytes as usize].iter().cloned(); let frames = mmap.write(&mut iter); let written_bytes = pcm.frames_to_bytes(frames); if let Ok(written_bytes) = usize::try_from(written_bytes) { buffer.pos += written_bytes; } - if buffer.pos >= buffer.bytes.len() { + if buffer.pos >= buffer.data_descriptor.len() as usize { stream.buffers.pop_front(); } } @@ -191,7 +195,14 @@ fn write_samples_io( let Some(buffer) = stream.buffers.front_mut() else { return 0; }; - let mut iter = buffer.bytes[buffer.pos..].iter().cloned(); + let mut data = vec![0; buffer.data_descriptor.len() as usize]; + + // consume() always reads (buffer.data_descriptor.len() - + // buffer.pos) bytes + let read_bytes = buffer + .consume(&mut data) + .expect("failed to read buffer from guest"); + let mut iter = data[0..read_bytes as usize].iter().cloned(); let mut written_bytes = 0; for (sample, byte) in buf.iter_mut().zip(&mut iter) { @@ -199,7 +210,7 @@ fn write_samples_io( written_bytes += 1; } buffer.pos += written_bytes as usize; - if buffer.pos >= buffer.bytes.len() { + if buffer.pos >= buffer.data_descriptor.len() as usize { stream.buffers.pop_front(); } p.bytes_to_frames(written_bytes) diff --git a/crates/sound/src/audio_backends/pipewire.rs b/crates/sound/src/audio_backends/pipewire.rs index 0c8e61a..624d5fa 100644 --- a/crates/sound/src/audio_backends/pipewire.rs +++ b/crates/sound/src/audio_backends/pipewire.rs @@ -329,26 +329,27 @@ impl AudioBackend for PwBackend { let mut start = buffer.pos; - let avail = (buffer.bytes.len() - start) as i32; + let avail = (buffer.data_descriptor.len() - start as u32) as i32; if avail < n_bytes as i32 { n_bytes = avail.try_into().unwrap(); } - let p = &mut slice[buffer.pos..start + n_bytes]; + let p = &mut slice[0..n_bytes]; if avail <= 0 { // pad with silence unsafe { ptr::write_bytes(p.as_mut_ptr(), 0, n_bytes); } } else { - let slice = &buffer.bytes[buffer.pos..start + n_bytes]; - p.copy_from_slice(slice); + // consume() always reads (buffer.data_descriptor.len() - + // buffer.pos) bytes + buffer.consume(p).expect("failed to read buffer from guest"); start += n_bytes; buffer.pos = start; - if start >= buffer.bytes.len() { + if start >= buffer.data_descriptor.len() as usize { streams.buffers.pop_front(); } } diff --git a/crates/sound/src/device.rs b/crates/sound/src/device.rs index 314e55b..c7b26c9 100644 --- a/crates/sound/src/device.rs +++ b/crates/sound/src/device.rs @@ -469,14 +469,16 @@ impl VhostUserSoundThread { .into()); } TxState::WaitingBufferForStreamId(_stream_id) => { - let mut buf = vec![0; descriptor.len() as usize]; - let bytes_read = desc_chain - .memory() - .read(&mut buf, descriptor.addr()) - .map_err(|_| Error::DescriptorReadFailed)?; - buf.truncate(bytes_read); - - buffers.push(Buffer::new(buf, Arc::clone(&message))); + /* + Rather than copying the content of a descriptor, buffer keeps a pointer to it. + When we copy just after the request is enqueued, the guest's userspace may or + may not have updated the buffer contents. Guest driver simply moves buffers + from the used ring to the available ring without knowing whether the content + has been updated. The device only reads the buffer from guest memory when the + audio engine requires it, which is about after a period thus ensuring that the + buffer is up-to-date. + */ + buffers.push(Buffer::new(*descriptor, Arc::clone(&message))); } } } diff --git a/crates/sound/src/stream.rs b/crates/sound/src/stream.rs index 35d5e3f..1e07f89 100644 --- a/crates/sound/src/stream.rs +++ b/crates/sound/src/stream.rs @@ -4,7 +4,7 @@ use std::{collections::VecDeque, sync::Arc}; use thiserror::Error as ThisError; -use vm_memory::{Le32, Le64}; +use vm_memory::{Address, Bytes, Le32, Le64}; use crate::{virtio_sound::*, IOMessage, SUPPORTED_FORMATS, SUPPORTED_RATES}; @@ -15,6 +15,8 @@ pub enum Error { InvalidStateTransition(PCMState, PCMState), #[error("Guest requested an invalid stream id: {0}")] InvalidStreamId(u32), + #[error("Descriptor read failed")] + DescriptorReadFailed, } type Result = std::result::Result; @@ -234,7 +236,8 @@ impl Default for PcmParams { } pub struct Buffer { - pub bytes: Vec, + // TODO: to make private and add len usize + pub data_descriptor: virtio_queue::Descriptor, pub pos: usize, pub message: Arc, } @@ -242,7 +245,6 @@ pub struct Buffer { impl std::fmt::Debug for Buffer { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { fmt.debug_struct(stringify!(Buffer)) - .field("bytes", &self.bytes.len()) .field("pos", &self.pos) .field("message", &Arc::as_ptr(&self.message)) .finish() @@ -250,13 +252,29 @@ impl std::fmt::Debug for Buffer { } impl Buffer { - pub fn new(bytes: Vec, message: Arc) -> Self { + pub fn new(data_descriptor: virtio_queue::Descriptor, message: Arc) -> Self { Self { - bytes, pos: 0, + data_descriptor, message, } } + + pub fn consume(&self, buf: &mut [u8]) -> Result { + let addr = self.data_descriptor.addr(); + let offset = self.pos as u64; + let len = self + .message + .desc_chain + .memory() + .read( + buf, + addr.checked_add(offset) + .expect("invalid guest memory address"), + ) + .map_err(|_| Error::DescriptorReadFailed)?; + Ok(len as u32) + } } impl Drop for Buffer { From 19cc0a318dc53cb21ff52ad4296ba5d3696d5ff9 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Wed, 4 Oct 2023 08:17:44 +0200 Subject: [PATCH 40/44] device.rs: Add Chmap_info and Jack_info support Added the supported features for Chmap_info and Jack_info control messages. Signed-off-by: Dorinda Bassey --- crates/sound/src/device.rs | 116 +++++++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 4 deletions(-) diff --git a/crates/sound/src/device.rs b/crates/sound/src/device.rs index c7b26c9..ed880c8 100644 --- a/crates/sound/src/device.rs +++ b/crates/sound/src/device.rs @@ -35,6 +35,8 @@ use crate::{ struct VhostUserSoundThread { mem: Option>, event_idx: bool, + chmaps: Arc>>, + jacks: Arc>>, queue_indexes: Vec, streams: Arc>>, streams_no: usize, @@ -44,6 +46,8 @@ type SoundDescriptorChain = DescriptorChain>>, + jacks: Arc>>, mut queue_indexes: Vec, streams: Arc>>, streams_no: usize, @@ -53,6 +57,8 @@ impl VhostUserSoundThread { Ok(Self { event_idx: false, mem: None, + chmaps, + jacks, queue_indexes, streams, streams_no, @@ -172,9 +178,84 @@ impl VhostUserSoundThread { let code = ControlMessageKind::try_from(request.code).map_err(Error::from)?; match code { - ControlMessageKind::ChmapInfo - | ControlMessageKind::JackInfo - | ControlMessageKind::JackRemap => { + ControlMessageKind::ChmapInfo => { + if descriptors.len() != 3 { + log::error!("a CHMAP_INFO request should have three descriptors total."); + return Err(Error::UnexpectedDescriptorCount(descriptors.len()).into()); + } else if !descriptors[2].is_write_only() { + log::error!( + "a CHMAP_INFO request should have a writeable descriptor for the info \ + payload response after the header status response" + ); + return Err(Error::UnexpectedReadableDescriptor(2).into()); + } + let request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + let start_id = u32::from(request.start_id) as usize; + let count = u32::from(request.count) as usize; + let chmaps = self.chmaps.read().unwrap(); + if chmaps.len() <= start_id || chmaps.len() < start_id + count { + resp.code = VIRTIO_SND_S_BAD_MSG.into(); + } else { + let desc_response = descriptors[2]; + let mut buf = vec![]; + + for i in chmaps + .iter() + .skip(start_id) + .take(count) + { + buf.extend_from_slice(i.as_slice()); + } + desc_chain + .memory() + .write_slice(&buf, desc_response.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + used_len += desc_response.len(); + } + } + ControlMessageKind::JackInfo => { + if descriptors.len() != 3 { + log::error!("a JACK_INFO request should have three descriptors total."); + return Err(Error::UnexpectedDescriptorCount(descriptors.len()).into()); + } else if !descriptors[2].is_write_only() { + log::error!( + "a JACK_INFO request should have a writeable descriptor for the info \ + payload response after the header status response" + ); + return Err(Error::UnexpectedReadableDescriptor(2).into()); + } + let request = desc_chain + .memory() + .read_obj::(desc_request.addr()) + .map_err(|_| Error::DescriptorReadFailed)?; + + let start_id = u32::from(request.start_id) as usize; + let count = u32::from(request.count) as usize; + let jacks = self.jacks.read().unwrap(); + if jacks.len() <= start_id || jacks.len() < start_id + count { + resp.code = VIRTIO_SND_S_BAD_MSG.into(); + } else { + let desc_response = descriptors[2]; + let mut buf = vec![]; + + for i in jacks + .iter() + .skip(start_id) + .take(count) + { + buf.extend_from_slice(i.as_slice()); + } + desc_chain + .memory() + .write_slice(&buf, desc_response.addr()) + .map_err(|_| Error::DescriptorWriteFailed)?; + used_len += desc_response.len(); + } + } + ControlMessageKind::JackRemap => { resp.code = VIRTIO_SND_S_NOT_SUPP.into(); } ControlMessageKind::PcmInfo => { @@ -527,20 +608,45 @@ impl VhostUserSoundBackend { ]; let streams_no = streams.len(); let streams = Arc::new(RwLock::new(streams)); + let jacks: Arc>> = Arc::new(RwLock::new(Vec::new())); + let mut positions = [VIRTIO_SND_CHMAP_NONE; VIRTIO_SND_CHMAP_MAX_SIZE]; + positions[0] = VIRTIO_SND_CHMAP_FL; + positions[1] = VIRTIO_SND_CHMAP_FR; + let chmaps_info: Vec = vec![ + VirtioSoundChmapInfo { + direction: VIRTIO_SND_D_OUTPUT, + channels: 2, + positions, + ..VirtioSoundChmapInfo::default() + }, + VirtioSoundChmapInfo { + direction: VIRTIO_SND_D_INPUT, + channels: 2, + positions, + ..VirtioSoundChmapInfo::default() + }, + ]; + let chmaps: Arc>> = Arc::new(RwLock::new(chmaps_info)); log::trace!("VhostUserSoundBackend::new config {:?}", &config); let threads = if config.multi_thread { vec![ RwLock::new(VhostUserSoundThread::new( + chmaps.clone(), + jacks.clone(), vec![CONTROL_QUEUE_IDX, EVENT_QUEUE_IDX], streams.clone(), streams_no, )?), RwLock::new(VhostUserSoundThread::new( + chmaps.clone(), + jacks.clone(), vec![TX_QUEUE_IDX], streams.clone(), streams_no, )?), RwLock::new(VhostUserSoundThread::new( + chmaps.clone(), + jacks.clone(), vec![RX_QUEUE_IDX], streams.clone(), streams_no, @@ -548,6 +654,8 @@ impl VhostUserSoundBackend { ] } else { vec![RwLock::new(VhostUserSoundThread::new( + chmaps.clone(), + jacks.clone(), vec![ CONTROL_QUEUE_IDX, EVENT_QUEUE_IDX, @@ -566,7 +674,7 @@ impl VhostUserSoundBackend { virtio_cfg: VirtioSoundConfig { jacks: 0.into(), streams: 1.into(), - chmaps: 0.into(), + chmaps: 1.into(), }, exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?, audio_backend: RwLock::new(audio_backend), From d76c30c3b446d93768f29b3c8b6ef3f83cb0fe04 Mon Sep 17 00:00:00 2001 From: Dorinda Bassey Date: Wed, 11 Oct 2023 09:54:38 +0200 Subject: [PATCH 41/44] sound/device.rs: cargo fmt Signed-off-by: Dorinda Bassey --- crates/sound/src/device.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/sound/src/device.rs b/crates/sound/src/device.rs index ed880c8..63cec41 100644 --- a/crates/sound/src/device.rs +++ b/crates/sound/src/device.rs @@ -202,11 +202,7 @@ impl VhostUserSoundThread { let desc_response = descriptors[2]; let mut buf = vec![]; - for i in chmaps - .iter() - .skip(start_id) - .take(count) - { + for i in chmaps.iter().skip(start_id).take(count) { buf.extend_from_slice(i.as_slice()); } desc_chain @@ -241,11 +237,7 @@ impl VhostUserSoundThread { let desc_response = descriptors[2]; let mut buf = vec![]; - for i in jacks - .iter() - .skip(start_id) - .take(count) - { + for i in jacks.iter().skip(start_id).take(count) { buf.extend_from_slice(i.as_slice()); } desc_chain From de1c308c62c73579a7db62f78c927be251063c8a Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Mon, 16 Oct 2023 14:53:32 +0300 Subject: [PATCH 42/44] Rename vhost-user-sound to vhost-device-sound Signed-off-by: Manos Pitsidianakis --- Cargo.lock | 40 ++++++++++++++++++++-------------------- crates/sound/Cargo.toml | 2 +- crates/sound/README.md | 8 ++++---- crates/sound/src/lib.rs | 4 ++-- crates/sound/src/main.rs | 2 +- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3bea431..fbd38e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1446,6 +1446,26 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "vhost-device-sound" +version = "0.1.0" +dependencies = [ + "alsa", + "clap", + "env_logger", + "log", + "pipewire", + "rstest", + "serial_test", + "thiserror", + "vhost 0.6.1", + "vhost-user-backend 0.8.0", + "virtio-bindings 0.2.1", + "virtio-queue 0.7.1", + "vm-memory 0.10.0", + "vmm-sys-util", +] + [[package]] name = "vhost-device-vsock" version = "0.1.0" @@ -1499,26 +1519,6 @@ dependencies = [ "vmm-sys-util", ] -[[package]] -name = "vhost-user-sound" -version = "0.1.0" -dependencies = [ - "alsa", - "clap", - "env_logger", - "log", - "pipewire", - "rstest", - "serial_test", - "thiserror", - "vhost 0.6.1", - "vhost-user-backend 0.8.0", - "virtio-bindings 0.2.1", - "virtio-queue 0.7.1", - "vm-memory 0.10.0", - "vmm-sys-util", -] - [[package]] name = "virtio-bindings" version = "0.1.0" diff --git a/crates/sound/Cargo.toml b/crates/sound/Cargo.toml index 8df35e2..598d55d 100644 --- a/crates/sound/Cargo.toml +++ b/crates/sound/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "vhost-user-sound" +name = "vhost-device-sound" version = "0.1.0" authors = ["Stefano Garzarella ", "Manos Pitsidianakis "] description = "A virtio-sound device using the vhost-user protocol." diff --git a/crates/sound/README.md b/crates/sound/README.md index 52ad5ad..a06356e 100644 --- a/crates/sound/README.md +++ b/crates/sound/README.md @@ -1,10 +1,10 @@ -# vhost-user-sound +# vhost-device-sound ## Synopsis - vhost-user-sound --socket --backend + vhost-device-sound --socket --backend ## Description A virtio-sound device using the vhost-user protocol. @@ -30,7 +30,7 @@ generated with help2man target/debug/vhost-user-sound |mandoc Launch the backend on the host machine: ```shell -host# vhost-user-sound --socket /tmp/snd.sock --backend null +host# vhost-device-sound --socket /tmp/snd.sock --backend null ``` With QEMU, you can add a `virtio` device that uses the backend's socket with the following flags: diff --git a/crates/sound/src/lib.rs b/crates/sound/src/lib.rs index f34f759..d15be90 100644 --- a/crates/sound/src/lib.rs +++ b/crates/sound/src/lib.rs @@ -281,14 +281,14 @@ impl Drop for IOMessage { } /// This is the public API through which an external program starts the -/// vhost-user-sound backend server. +/// vhost-device-sound backend server. pub fn start_backend_server(config: SoundConfig) { log::trace!("Using config {:?}", &config); let listener = Listener::new(config.get_socket_path(), true).unwrap(); let backend = Arc::new(VhostUserSoundBackend::new(config).unwrap()); let mut daemon = VhostUserDaemon::new( - String::from("vhost-user-sound"), + String::from("vhost-device-sound"), backend.clone(), GuestMemoryAtomic::new(GuestMemoryMmap::new()), ) diff --git a/crates/sound/src/main.rs b/crates/sound/src/main.rs index 0939669..8689b39 100644 --- a/crates/sound/src/main.rs +++ b/crates/sound/src/main.rs @@ -4,7 +4,7 @@ use std::convert::TryFrom; use clap::Parser; -use vhost_user_sound::{start_backend_server, BackendType, Error, Result, SoundConfig}; +use vhost_device_sound::{start_backend_server, BackendType, Error, Result, SoundConfig}; #[derive(Parser, Debug)] #[clap(version, about, long_about = None)] From 3a089149be6a6595440c16ab0d7244d49e85485f Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Mon, 16 Oct 2023 14:56:03 +0300 Subject: [PATCH 43/44] Move crates/sound to vhost-device-sound Signed-off-by: Manos Pitsidianakis --- Cargo.toml | 2 +- README.md | 2 +- {crates/sound => vhost-device-sound}/CHANGELOG.md | 0 {crates/sound => vhost-device-sound}/Cargo.toml | 0 {crates/sound => vhost-device-sound}/LICENSE-APACHE | 0 {crates/sound => vhost-device-sound}/LICENSE-BSD-3-Clause | 0 {crates/sound => vhost-device-sound}/README.md | 0 {crates/sound => vhost-device-sound}/rustfmt.toml | 0 {crates/sound => vhost-device-sound}/src/audio_backends.rs | 0 {crates/sound => vhost-device-sound}/src/audio_backends/alsa.rs | 0 {crates/sound => vhost-device-sound}/src/audio_backends/null.rs | 0 .../sound => vhost-device-sound}/src/audio_backends/pipewire.rs | 0 {crates/sound => vhost-device-sound}/src/device.rs | 0 {crates/sound => vhost-device-sound}/src/lib.rs | 0 {crates/sound => vhost-device-sound}/src/main.rs | 0 {crates/sound => vhost-device-sound}/src/stream.rs | 0 {crates/sound => vhost-device-sound}/src/virtio_sound.rs | 0 17 files changed, 2 insertions(+), 2 deletions(-) rename {crates/sound => vhost-device-sound}/CHANGELOG.md (100%) rename {crates/sound => vhost-device-sound}/Cargo.toml (100%) rename {crates/sound => vhost-device-sound}/LICENSE-APACHE (100%) rename {crates/sound => vhost-device-sound}/LICENSE-BSD-3-Clause (100%) rename {crates/sound => vhost-device-sound}/README.md (100%) rename {crates/sound => vhost-device-sound}/rustfmt.toml (100%) rename {crates/sound => vhost-device-sound}/src/audio_backends.rs (100%) rename {crates/sound => vhost-device-sound}/src/audio_backends/alsa.rs (100%) rename {crates/sound => vhost-device-sound}/src/audio_backends/null.rs (100%) rename {crates/sound => vhost-device-sound}/src/audio_backends/pipewire.rs (100%) rename {crates/sound => vhost-device-sound}/src/device.rs (100%) rename {crates/sound => vhost-device-sound}/src/lib.rs (100%) rename {crates/sound => vhost-device-sound}/src/main.rs (100%) rename {crates/sound => vhost-device-sound}/src/stream.rs (100%) rename {crates/sound => vhost-device-sound}/src/virtio_sound.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index ea1de8f..d4ecbe6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,6 @@ members = [ "vhost-device-rng", "vhost-device-scsi", "vhost-device-scmi", - "crates/sound", + "vhost-device-sound", "vhost-device-vsock", ] diff --git a/README.md b/README.md index 2caf199..b410ef9 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Here is the list of device backends that we support: - [RNG](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-rng/README.md) - [SCMI](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-scmi/README.md) - [SCSI](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-scsi/README.md) -- [Sound](https://github.com/rust-vmm/vhost-device/blob/main/crates/sound/README.md) +- [Sound](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-sound/README.md) - [VSOCK](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-vsock/README.md) For `vhost-user` device backends that have no final specification merged into diff --git a/crates/sound/CHANGELOG.md b/vhost-device-sound/CHANGELOG.md similarity index 100% rename from crates/sound/CHANGELOG.md rename to vhost-device-sound/CHANGELOG.md diff --git a/crates/sound/Cargo.toml b/vhost-device-sound/Cargo.toml similarity index 100% rename from crates/sound/Cargo.toml rename to vhost-device-sound/Cargo.toml diff --git a/crates/sound/LICENSE-APACHE b/vhost-device-sound/LICENSE-APACHE similarity index 100% rename from crates/sound/LICENSE-APACHE rename to vhost-device-sound/LICENSE-APACHE diff --git a/crates/sound/LICENSE-BSD-3-Clause b/vhost-device-sound/LICENSE-BSD-3-Clause similarity index 100% rename from crates/sound/LICENSE-BSD-3-Clause rename to vhost-device-sound/LICENSE-BSD-3-Clause diff --git a/crates/sound/README.md b/vhost-device-sound/README.md similarity index 100% rename from crates/sound/README.md rename to vhost-device-sound/README.md diff --git a/crates/sound/rustfmt.toml b/vhost-device-sound/rustfmt.toml similarity index 100% rename from crates/sound/rustfmt.toml rename to vhost-device-sound/rustfmt.toml diff --git a/crates/sound/src/audio_backends.rs b/vhost-device-sound/src/audio_backends.rs similarity index 100% rename from crates/sound/src/audio_backends.rs rename to vhost-device-sound/src/audio_backends.rs diff --git a/crates/sound/src/audio_backends/alsa.rs b/vhost-device-sound/src/audio_backends/alsa.rs similarity index 100% rename from crates/sound/src/audio_backends/alsa.rs rename to vhost-device-sound/src/audio_backends/alsa.rs diff --git a/crates/sound/src/audio_backends/null.rs b/vhost-device-sound/src/audio_backends/null.rs similarity index 100% rename from crates/sound/src/audio_backends/null.rs rename to vhost-device-sound/src/audio_backends/null.rs diff --git a/crates/sound/src/audio_backends/pipewire.rs b/vhost-device-sound/src/audio_backends/pipewire.rs similarity index 100% rename from crates/sound/src/audio_backends/pipewire.rs rename to vhost-device-sound/src/audio_backends/pipewire.rs diff --git a/crates/sound/src/device.rs b/vhost-device-sound/src/device.rs similarity index 100% rename from crates/sound/src/device.rs rename to vhost-device-sound/src/device.rs diff --git a/crates/sound/src/lib.rs b/vhost-device-sound/src/lib.rs similarity index 100% rename from crates/sound/src/lib.rs rename to vhost-device-sound/src/lib.rs diff --git a/crates/sound/src/main.rs b/vhost-device-sound/src/main.rs similarity index 100% rename from crates/sound/src/main.rs rename to vhost-device-sound/src/main.rs diff --git a/crates/sound/src/stream.rs b/vhost-device-sound/src/stream.rs similarity index 100% rename from crates/sound/src/stream.rs rename to vhost-device-sound/src/stream.rs diff --git a/crates/sound/src/virtio_sound.rs b/vhost-device-sound/src/virtio_sound.rs similarity index 100% rename from crates/sound/src/virtio_sound.rs rename to vhost-device-sound/src/virtio_sound.rs From eb2e2227e41d48a52e4e6346189b772c5363879d Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Mon, 16 Oct 2023 14:58:40 +0300 Subject: [PATCH 44/44] Move vhost-device-sound to staging workspace Signed-off-by: Manos Pitsidianakis --- Cargo.lock | 641 +-------- Cargo.toml | 1 - README.md | 3 +- staging/Cargo.lock | 1174 +++++++++++++++++ staging/Cargo.toml | 4 +- .../vhost-device-sound}/CHANGELOG.md | 0 .../vhost-device-sound}/Cargo.toml | 0 .../vhost-device-sound}/LICENSE-APACHE | 0 .../vhost-device-sound}/LICENSE-BSD-3-Clause | 0 .../vhost-device-sound}/README.md | 0 .../vhost-device-sound}/rustfmt.toml | 0 .../vhost-device-sound}/src/audio_backends.rs | 0 .../src/audio_backends/alsa.rs | 0 .../src/audio_backends/null.rs | 0 .../src/audio_backends/pipewire.rs | 0 .../vhost-device-sound}/src/device.rs | 0 .../vhost-device-sound}/src/lib.rs | 0 .../vhost-device-sound}/src/main.rs | 0 .../vhost-device-sound}/src/stream.rs | 0 .../vhost-device-sound}/src/virtio_sound.rs | 0 20 files changed, 1223 insertions(+), 600 deletions(-) create mode 100644 staging/Cargo.lock rename {vhost-device-sound => staging/vhost-device-sound}/CHANGELOG.md (100%) rename {vhost-device-sound => staging/vhost-device-sound}/Cargo.toml (100%) rename {vhost-device-sound => staging/vhost-device-sound}/LICENSE-APACHE (100%) rename {vhost-device-sound => staging/vhost-device-sound}/LICENSE-BSD-3-Clause (100%) rename {vhost-device-sound => staging/vhost-device-sound}/README.md (100%) rename {vhost-device-sound => staging/vhost-device-sound}/rustfmt.toml (100%) rename {vhost-device-sound => staging/vhost-device-sound}/src/audio_backends.rs (100%) rename {vhost-device-sound => staging/vhost-device-sound}/src/audio_backends/alsa.rs (100%) rename {vhost-device-sound => staging/vhost-device-sound}/src/audio_backends/null.rs (100%) rename {vhost-device-sound => staging/vhost-device-sound}/src/audio_backends/pipewire.rs (100%) rename {vhost-device-sound => staging/vhost-device-sound}/src/device.rs (100%) rename {vhost-device-sound => staging/vhost-device-sound}/src/lib.rs (100%) rename {vhost-device-sound => staging/vhost-device-sound}/src/main.rs (100%) rename {vhost-device-sound => staging/vhost-device-sound}/src/stream.rs (100%) rename {vhost-device-sound => staging/vhost-device-sound}/src/virtio_sound.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index fbd38e6..245bc23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,28 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alsa" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2562ad8dcf0f789f65c6fdaad8a8a9708ed6b488e649da28c01656ad66b8b47" -dependencies = [ - "alsa-sys", - "bitflags 1.3.2", - "libc", - "nix 0.24.3", -] - -[[package]] -name = "alsa-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "anstream" version = "0.6.4" @@ -81,12 +59,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "anyhow" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" - [[package]] name = "arc-swap" version = "1.6.0" @@ -110,12 +82,6 @@ dependencies = [ "syn 2.0.38", ] -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - [[package]] name = "bindgen" version = "0.63.0" @@ -138,26 +104,6 @@ dependencies = [ "which", ] -[[package]] -name = "bindgen" -version = "0.66.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" -dependencies = [ - "bitflags 2.4.1", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.38", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -194,16 +140,6 @@ dependencies = [ "nom", ] -[[package]] -name = "cfg-expr" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -281,34 +217,6 @@ dependencies = [ "yaml-rust", ] -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cookie-factory" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "either" version = "1.9.0" @@ -381,101 +289,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-executor" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.38", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "getrandom" version = "0.2.10" @@ -614,8 +427,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa282e1da652deaeed776f6ef36d443689aeda19e5c0a3a2335c50b4611ce489" dependencies = [ - "bindgen 0.63.0", - "system-deps 2.0.3", + "bindgen", + "system-deps", ] [[package]] @@ -628,32 +441,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "libspa" -version = "0.7.2" -source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=5fe090b3ac8f6fed756c4871ac18f26edda3ac89#5fe090b3ac8f6fed756c4871ac18f26edda3ac89" -dependencies = [ - "bitflags 2.4.1", - "cc", - "convert_case", - "cookie-factory", - "libc", - "libspa-sys", - "nix 0.26.4", - "nom", - "system-deps 6.1.2", -] - -[[package]] -name = "libspa-sys" -version = "0.7.2" -source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=5fe090b3ac8f6fed756c4871ac18f26edda3ac89#5fe090b3ac8f6fed756c4871ac18f26edda3ac89" -dependencies = [ - "bindgen 0.66.1", - "cc", - "system-deps 6.1.2", -] - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -666,16 +453,6 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" -[[package]] -name = "lock_api" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.20" @@ -688,45 +465,12 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", -] - -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset", - "pin-utils", -] - [[package]] name = "nom" version = "7.1.3" @@ -764,29 +508,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - [[package]] name = "pathdiff" version = "0.2.1" @@ -799,44 +520,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" -[[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pipewire" -version = "0.7.2" -source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=5fe090b3ac8f6fed756c4871ac18f26edda3ac89#5fe090b3ac8f6fed756c4871ac18f26edda3ac89" -dependencies = [ - "anyhow", - "bitflags 2.4.1", - "libc", - "libspa", - "libspa-sys", - "nix 0.26.4", - "once_cell", - "pipewire-sys", - "thiserror", -] - -[[package]] -name = "pipewire-sys" -version = "0.7.2" -source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git?rev=5fe090b3ac8f6fed756c4871ac18f26edda3ac89#5fe090b3ac8f6fed756c4871ac18f26edda3ac89" -dependencies = [ - "bindgen 0.66.1", - "libspa-sys", - "system-deps 6.1.2", -] - [[package]] name = "pkg-config" version = "0.3.27" @@ -856,7 +539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit 0.19.15", + "toml_edit", ] [[package]] @@ -945,56 +628,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" -[[package]] -name = "relative-path" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" - -[[package]] -name = "rstest" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" -dependencies = [ - "futures", - "futures-timer", - "rstest_macros", - "rustc_version", -] - -[[package]] -name = "rstest_macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" -dependencies = [ - "cfg-if", - "glob", - "proc-macro2", - "quote", - "regex", - "relative-path", - "rustc_version", - "syn 2.0.38", - "unicode-ident", -] - [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.38.19" @@ -1014,18 +653,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" - [[package]] name = "serde" version = "1.0.189" @@ -1046,15 +673,6 @@ dependencies = [ "syn 2.0.38", ] -[[package]] -name = "serde_spanned" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" -dependencies = [ - "serde", -] - [[package]] name = "serde_yaml" version = "0.9.25" @@ -1068,52 +686,12 @@ dependencies = [ "unsafe-libyaml", ] -[[package]] -name = "serial_test" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "538c30747ae860d6fb88330addbbd3e0ddbe46d662d032855596d8a8ca260611" -dependencies = [ - "dashmap", - "futures", - "lazy_static", - "log", - "parking_lot", - "serial_test_derive", -] - -[[package]] -name = "serial_test_derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "079a83df15f85d89a68d64ae1238f142f172b1fa915d0d76b26a7cba1b659a69" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "shlex" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" - [[package]] name = "strsim" version = "0.10.0" @@ -1171,29 +749,10 @@ dependencies = [ "strum", "strum_macros", "thiserror", - "toml 0.5.11", - "version-compare 0.0.11", + "toml", + "version-compare", ] -[[package]] -name = "system-deps" -version = "6.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94af52f9402f94aac4948a2518b43359be8d9ce6cd9efc1c4de3b2f7b7e897d6" -dependencies = [ - "cfg-expr", - "heck 0.4.1", - "pkg-config", - "toml 0.8.2", - "version-compare 0.1.1", -] - -[[package]] -name = "target-lexicon" -version = "0.12.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" - [[package]] name = "tempfile" version = "3.8.0" @@ -1245,26 +804,11 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.20.2", -] - [[package]] name = "toml_datetime" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] [[package]] name = "toml_edit" @@ -1277,19 +821,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "unicode-ident" version = "1.0.12" @@ -1320,24 +851,6 @@ version = "0.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" -[[package]] -name = "version-compare" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" - -[[package]] -name = "vhost" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6769e8dbf5276b4376439fbf36bb880d203bf614bf7ef444198edc24b5a9f35" -dependencies = [ - "bitflags 1.3.2", - "libc", - "vm-memory 0.10.0", - "vmm-sys-util", -] - [[package]] name = "vhost" version = "0.8.1" @@ -1346,7 +859,7 @@ checksum = "61957aeb36daf0b00b87fff9c10dd28a161bd35ab157553d340d183b3d8756e6" dependencies = [ "bitflags 1.3.2", "libc", - "vm-memory 0.12.2", + "vm-memory", "vmm-sys-util", ] @@ -1361,11 +874,11 @@ dependencies = [ "libgpiod", "log", "thiserror", - "vhost 0.8.1", - "vhost-user-backend 0.10.1", - "virtio-bindings 0.2.1", - "virtio-queue 0.9.0", - "vm-memory 0.12.2", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-queue", + "vm-memory", "vmm-sys-util", ] @@ -1379,11 +892,11 @@ dependencies = [ "libc", "log", "thiserror", - "vhost 0.8.1", - "vhost-user-backend 0.10.1", - "virtio-bindings 0.2.1", - "virtio-queue 0.9.0", - "vm-memory 0.12.2", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-queue", + "vm-memory", "vmm-sys-util", ] @@ -1400,11 +913,11 @@ dependencies = [ "rand", "tempfile", "thiserror", - "vhost 0.8.1", - "vhost-user-backend 0.10.1", - "virtio-bindings 0.2.1", - "virtio-queue 0.9.0", - "vm-memory 0.12.2", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-queue", + "vm-memory", "vmm-sys-util", ] @@ -1418,11 +931,11 @@ dependencies = [ "itertools", "log", "thiserror", - "vhost 0.8.1", - "vhost-user-backend 0.10.1", - "virtio-bindings 0.2.1", - "virtio-queue 0.9.0", - "vm-memory 0.12.2", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-queue", + "vm-memory", "vmm-sys-util", ] @@ -1438,31 +951,11 @@ dependencies = [ "num_enum", "tempfile", "thiserror", - "vhost 0.8.1", - "vhost-user-backend 0.10.1", - "virtio-bindings 0.2.1", - "virtio-queue 0.9.0", - "vm-memory 0.12.2", - "vmm-sys-util", -] - -[[package]] -name = "vhost-device-sound" -version = "0.1.0" -dependencies = [ - "alsa", - "clap", - "env_logger", - "log", - "pipewire", - "rstest", - "serial_test", - "thiserror", - "vhost 0.6.1", - "vhost-user-backend 0.8.0", - "virtio-bindings 0.2.1", - "virtio-queue 0.7.1", - "vm-memory 0.10.0", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-queue", + "vm-memory", "vmm-sys-util", ] @@ -1480,27 +973,12 @@ dependencies = [ "serde_yaml", "tempfile", "thiserror", - "vhost 0.8.1", - "vhost-user-backend 0.10.1", - "virtio-bindings 0.2.1", - "virtio-queue 0.9.0", + "vhost", + "vhost-user-backend", + "virtio-bindings", + "virtio-queue", "virtio-vsock", - "vm-memory 0.12.2", - "vmm-sys-util", -] - -[[package]] -name = "vhost-user-backend" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f237b91db4ac339d639fb43398b52d785fa51e3c7760ac9425148863c1f4303" -dependencies = [ - "libc", - "log", - "vhost 0.6.1", - "virtio-bindings 0.1.0", - "virtio-queue 0.7.1", - "vm-memory 0.10.0", + "vm-memory", "vmm-sys-util", ] @@ -1512,37 +990,19 @@ checksum = "ab069cdedaf18a0673766eb0a07a0f4ee3ed1b8e17fbfe4aafe5b988e2de1d01" dependencies = [ "libc", "log", - "vhost 0.8.1", - "virtio-bindings 0.2.1", - "virtio-queue 0.9.0", - "vm-memory 0.12.2", + "vhost", + "virtio-bindings", + "virtio-queue", + "vm-memory", "vmm-sys-util", ] -[[package]] -name = "virtio-bindings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff512178285488516ed85f15b5d0113a7cdb89e9e8a760b269ae4f02b84bd6b" - [[package]] name = "virtio-bindings" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c18d7b74098a946470ea265b5bacbbf877abc3373021388454de0d47735a5b98" -[[package]] -name = "virtio-queue" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ba81e2bcc21c0d2fc5e6683e79367e26ad219197423a498df801d79d5ba77bd" -dependencies = [ - "log", - "virtio-bindings 0.1.0", - "vm-memory 0.10.0", - "vmm-sys-util", -] - [[package]] name = "virtio-queue" version = "0.9.0" @@ -1550,8 +1010,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35aca00da06841bd99162c381ec65893cace23ca0fb89254302cfe4bec4c300f" dependencies = [ "log", - "virtio-bindings 0.2.1", - "vm-memory 0.12.2", + "virtio-bindings", + "vm-memory", "vmm-sys-util", ] @@ -1561,20 +1021,9 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c92d1d0c0db339e03dc275e86e5de2654ed94b351f02d405a3a0260dfc1b839f" dependencies = [ - "virtio-bindings 0.2.1", - "virtio-queue 0.9.0", - "vm-memory 0.12.2", -] - -[[package]] -name = "vm-memory" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688a70366615b45575a424d9c665561c1b5ab2224d494f706b6a6812911a827c" -dependencies = [ - "arc-swap", - "libc", - "winapi", + "virtio-bindings", + "virtio-queue", + "vm-memory", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d4ecbe6..6037899 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,5 @@ members = [ "vhost-device-rng", "vhost-device-scsi", "vhost-device-scmi", - "vhost-device-sound", "vhost-device-vsock", ] diff --git a/README.md b/README.md index b410ef9..5b7c5a4 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ Here is the list of device backends that we support: - [RNG](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-rng/README.md) - [SCMI](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-scmi/README.md) - [SCSI](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-scsi/README.md) -- [Sound](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-sound/README.md) - [VSOCK](https://github.com/rust-vmm/vhost-device/blob/main/vhost-device-vsock/README.md) For `vhost-user` device backends that have no final specification merged into @@ -23,7 +22,7 @@ More information may be found in its [README file](./staging/README.md). Here is the list of device backends in **staging**: -- Currently none. +- [Sound](https://github.com/rust-vmm/vhost-device/blob/main/staging/vhost-device-sound/README.md)