sound: add Stream, ControlMessage and other types

Signed-off-by: Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org>
This commit is contained in:
Manos Pitsidianakis 2023-07-04 17:52:01 +03:00
parent 91a5259cce
commit d385dcd1b2
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0
8 changed files with 437 additions and 9 deletions

1
Cargo.lock generated
View File

@ -1182,6 +1182,7 @@ dependencies = [
"vhost",
"vhost-user-backend",
"virtio-bindings 0.2.1",
"virtio-queue",
"vm-memory",
"vmm-sys-util",
]

View File

@ -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"

View File

@ -20,6 +20,7 @@ pub trait AudioBackend {
}
pub fn alloc_audio_backend(name: String) -> Result<Box<dyn AudioBackend + Send + Sync>> {
log::trace!("allocating audio backend {}", name);
match name.as_str() {
#[cfg(feature = "null-backend")]
"null" => Ok(Box::new(NullBackend::new())),

View File

@ -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()];

View File

@ -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<u16>,
}
#[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<u16>) -> Result<Self> {
queue_indexes.sort();
@ -59,8 +71,15 @@ impl VhostUserSoundThread {
}
fn handle_event(&self, device_event: u16, vrings: &[VringRwLock]) -> IoResult<bool> {
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<bool> {
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<bool> {
log::trace!("process_event");
Ok(false)
}
fn process_tx(&self, _vring: &VringRwLock) -> IoResult<bool> {
log::trace!("process_tx");
Ok(false)
}
fn process_rx(&self, _vring: &VringRwLock) -> IoResult<bool> {
log::trace!("process_rx");
Ok(false)
}
}
@ -97,6 +127,7 @@ pub struct VhostUserSoundBackend {
impl VhostUserSoundBackend {
pub fn new(config: SoundConfig) -> Result<Self> {
log::trace!("VhostUserSoundBackend::new config {:?}", &config);
let threads = if config.multi_thread {
vec![
RwLock::new(VhostUserSoundThread::new(vec![
@ -152,16 +183,18 @@ impl VhostUserBackend<VringRwLock, ()> 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<GuestMemoryMmap>) -> IoResult<()> {
log::trace!("update_memory");
for thread in self.threads.iter() {
thread.write().unwrap().update_memory(mem.clone())?;
}
@ -176,6 +209,7 @@ impl VhostUserBackend<VringRwLock, ()> for VhostUserSoundBackend {
vrings: &[VringRwLock],
thread_id: usize,
) -> IoResult<bool> {
log::trace!("handle_event device_event {}", device_event);
if evset != EventSet::IN {
return Err(Error::HandleEventNotEpollIn.into());
}
@ -187,6 +221,7 @@ impl VhostUserBackend<VringRwLock, ()> for VhostUserSoundBackend {
}
fn get_config(&self, offset: u32, size: u32) -> Vec<u8> {
log::trace!("get_config offset {} size {}", offset, size);
let offset = offset as usize;
let size = size as usize;
@ -210,6 +245,7 @@ impl VhostUserBackend<VringRwLock, ()> for VhostUserSoundBackend {
}
fn exit_event(&self, _thread_index: usize) -> Option<EventFd> {
log::trace!("exit_event");
self.exit_event.try_clone().ok()
}
}

View File

@ -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<GuestMemoryLoadGuard<GuestMemoryMmap<()>>>;
pub type Result<T> = std::result::Result<T, Error>;
/// 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<Error> for IoError {
impl From<Error> for IoError {
fn from(e: Error) -> Self {
Self::new(ErrorKind::Other, e)
}
}
impl From<stream::Error> 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<InvalidControlMessage> 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<Le32> for ControlMessageKind {
type Error = InvalidControlMessage;
fn try_from(val: Le32) -> std::result::Result<Self, Self::Error> {
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() {

230
crates/sound/src/stream.rs Normal file
View File

@ -0,0 +1,230 @@
// Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// 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<T> = std::result::Result<T, Error>;
/// 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,
}
}
}

View File

@ -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