Add vhost-user-sound crate

Signed-off-by: Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org>
This commit is contained in:
Manos Pitsidianakis 2023-07-04 17:53:38 +03:00
parent 583d15433b
commit 91a5259cce
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0
15 changed files with 612 additions and 1011 deletions

543
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
# Upcoming Release
- First initial daemon implementation.

View File

@ -1,11 +1,11 @@
[package]
name = "vhost-user-sound"
version = "0.1.0"
authors = ["Stefano Garzarella <sgarzare@redhat.com>"]
authors = ["Stefano Garzarella <sgarzare@redhat.com>", "Manos Pitsidianakis <manos.pitsidianakis@linaro.org>"]
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"

1
crates/sound/LICENSE-APACHE Symbolic link
View File

@ -0,0 +1 @@
../../LICENSE-APACHE

View File

@ -0,0 +1 @@
../../LICENSE-BSD-3-Clause

View File

@ -1,10 +1,44 @@
# vhost-user-sound
## Design
<!--
generated with help2man target/debug/vhost-user-sound |mandoc
-->
## Synopsis
vhost-user-sound --socket <SOCKET> --backend <BACKEND>
## Usage
## Description
A virtio-sound device using the vhost-user protocol.
## Working example
## Options
```text
--socket <SOCKET>
vhost-user Unix domain socket path
--backend <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

View File

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

View File

@ -1,41 +1,22 @@
// Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// 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<Box<dyn AudioBackend + Send + Sync>> {

View File

@ -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(())
}
}

View File

@ -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(())
}
}

215
crates/sound/src/device.rs Normal file
View File

@ -0,0 +1,215 @@
// Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// Stefano Garzarella <sgarzare@redhat.com>
// 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<GuestMemoryAtomic<GuestMemoryMmap>>,
event_idx: bool,
queue_indexes: Vec<u16>,
}
impl VhostUserSoundThread {
pub fn new(mut queue_indexes: Vec<u16>) -> Result<Self> {
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<GuestMemoryMmap>) -> IoResult<()> {
self.mem = Some(mem);
Ok(())
}
fn handle_event(&self, device_event: u16, vrings: &[VringRwLock]) -> IoResult<bool> {
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<bool> {
Ok(false)
}
fn process_event(&self, _vring: &VringRwLock) -> IoResult<bool> {
Ok(false)
}
fn process_tx(&self, _vring: &VringRwLock) -> IoResult<bool> {
Ok(false)
}
fn process_rx(&self, _vring: &VringRwLock) -> IoResult<bool> {
Ok(false)
}
}
pub struct VhostUserSoundBackend {
threads: Vec<RwLock<VhostUserSoundThread>>,
virtio_cfg: VirtioSoundConfig,
exit_event: EventFd,
_audio_backend: RwLock<Box<dyn AudioBackend + Send + Sync>>,
}
impl VhostUserSoundBackend {
pub fn new(config: SoundConfig) -> Result<Self> {
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<VringRwLock, ()> 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<GuestMemoryMmap>) -> 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<bool> {
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<u8> {
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<u64> {
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<EventFd> {
self.exit_event.try_clone().ok()
}
}

View File

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

View File

@ -1,9 +1,9 @@
// Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// Stefano Garzarella <sgarzare@redhat.com>
// 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 {

View File

@ -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<GuestMemoryAtomic<GuestMemoryMmap>>,
event_idx: bool,
queue_indexes: Vec<u16>,
audio_backend: Arc<Box<dyn AudioBackend + Send + Sync>>,
}
impl VhostUserSoundThread {
pub fn new(
mut queue_indexes: Vec<u16>,
audio_backend: Arc<Box<dyn AudioBackend + Send + Sync>>,
) -> Result<Self> {
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<GuestMemoryMmap>) -> IoResult<()> {
debug!("update memory");
self.mem = Some(mem);
Ok(())
}
fn handle_event(
&self,
device_event: u16,
vrings: &[VringRwLock],
stream_info: &[StreamInfo],
) -> IoResult<bool> {
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<bool> {
let requests: Vec<SndDescriptorChain> = 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::<VirtioSoundHeader>();
if read_desc_len < header_size {
return Err(Error::UnexpectedMinimumDescriptorSize(
header_size,
read_desc_len,
));
}
let hdr_request = desc_chain
.memory()
.read_obj::<VirtioSoundHeader>(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::<VirtioSoundQueryInfo>(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::<VirtioSndPcmSetParams>(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::<VirtioSoundPcmHeader>(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<bool> {
Ok(false)
}
fn process_tx(&self, vring: &VringRwLock) -> Result<bool> {
let requests: Vec<SndDescriptorChain> = 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::<VirtioSoundPcmStatus>() {
return Err(Error::UnexpectedDescriptorSize(
size_of::<VirtioSoundPcmStatus>(),
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::<VirtioSoundPcmXfer>() {
return Err(Error::UnexpectedDescriptorSize(
size_of::<VirtioSoundPcmXfer>(),
desc_request.len() as usize,
));
}
if desc_request.is_write_only() {
return Err(Error::UnexpectedWriteOnlyDescriptor(1));
}
let mut all_bufs = Vec::<u8>::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::<VirtioSoundPcmXfer>(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<bool> {
Ok(false)
}
}
pub struct VhostUserSoundBackend {
threads: Vec<RwLock<VhostUserSoundThread>>,
virtio_cfg: VirtioSoundConfig,
exit_event: EventFd,
streams_info: Vec<StreamInfo>,
}
type SndDescriptorChain = DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap<()>>>;
impl VhostUserSoundBackend {
pub fn new(config: SoundConfig) -> Result<Self> {
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::<StreamInfo>::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<VringRwLock, ()> 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<GuestMemoryMmap>) -> 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<bool> {
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<u8> {
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<u64> {
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<EventFd> {
self.exit_event.try_clone().ok()
}
}

View File

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