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