Merge pull request #475 from epilys/virtio-sound-pr-2

Add vhost-user-sound crate
This commit is contained in:
Viresh Kumar 2023-10-16 18:20:15 +05:30 committed by GitHub
commit 114cbe456b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 4217 additions and 17 deletions

29
Cargo.lock generated
View File

@ -79,7 +79,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.38",
]
[[package]]
@ -112,9 +112,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "byteorder"
@ -188,7 +188,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.38",
]
[[package]]
@ -242,7 +242,7 @@ version = "4.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79"
dependencies = [
"bitflags 2.4.0",
"bitflags 2.4.1",
"libc",
]
@ -265,11 +265,10 @@ dependencies = [
[[package]]
name = "errno"
version = "0.3.2"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
@ -500,7 +499,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.38",
]
[[package]]
@ -641,8 +640,8 @@ version = "0.38.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed"
dependencies = [
"bitflags 2.4.0",
"errno 0.3.2",
"bitflags 2.4.1",
"errno 0.3.5",
"libc",
"linux-raw-sys",
"windows-sys",
@ -671,7 +670,7 @@ checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.38",
]
[[package]]
@ -730,9 +729,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.29"
version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [
"proc-macro2",
"quote",
@ -793,7 +792,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
"syn 2.0.38",
]
[[package]]

View File

@ -22,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)
<!--
Template:

1174
staging/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
[workspace]
resolver = "2"
members = []
members = [
"vhost-device-sound",
]

View File

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

View File

@ -0,0 +1,33 @@
[package]
name = "vhost-device-sound"
version = "0.1.0"
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", "virtio-snd", "virtio"]
license = "Apache-2.0 OR BSD-3-Clause"
edition = "2018"
[features]
default = ["alsa-backend", "pw-backend"]
alsa-backend = ["dep:alsa"]
pw-backend = ["pw"]
[dependencies]
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 = "5fe090b3ac8f6fed756c4871ac18f26edda3ac89", optional = true }
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"
[dev-dependencies]
serial_test = "1.0"
rstest = "0.18.2"

View File

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

View File

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

View File

@ -0,0 +1,48 @@
# vhost-device-sound
<!--
generated with help2man target/debug/vhost-device-sound |mandoc
-->
## Synopsis
vhost-device-sound --socket <SOCKET> --backend <BACKEND>
## Description
A virtio-sound device using the vhost-user protocol.
## Options
```text
--socket <SOCKET>
vhost-user Unix domain socket path
--backend <BACKEND>
audio backend to be used [possible values: null, pipewire, alsa]
-h, --help
Print help
-V, --version
Print version
```
## Examples
Launch the backend on the host machine:
```shell
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:
```text
-chardev socket,id=vsnd,path=/tmp/snd.sock \
-device vhost-user-snd-pci,chardev=vsnd,id=snd
```
## 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)

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

@ -0,0 +1,58 @@
// Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
#[cfg(feature = "alsa-backend")]
mod alsa;
mod null;
#[cfg(feature = "pw-backend")]
mod pipewire;
use std::sync::{Arc, RwLock};
#[cfg(feature = "alsa-backend")]
use self::alsa::AlsaBackend;
use self::null::NullBackend;
#[cfg(feature = "pw-backend")]
use self::pipewire::PwBackend;
use crate::{device::ControlMessage, stream::Stream, BackendType, Result};
pub trait AudioBackend {
fn write(&self, stream_id: u32) -> 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(
backend: BackendType,
streams: Arc<RwLock<Vec<Stream>>>,
) -> Result<Box<dyn AudioBackend + Send + Sync>> {
log::trace!("allocating audio backend {:?}", backend);
match 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))),
}
}

View File

@ -0,0 +1,527 @@
/// Alsa backend
//
// Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// 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<T> = std::result::Result<T, alsa::Error>;
#[derive(Clone, Debug)]
pub struct AlsaBackend {
sender: Arc<Mutex<Sender<AlsaAction>>>,
}
#[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<Mutex<PCM>>,
stream_id: usize,
streams: &RwLock<Vec<Stream>>,
) -> 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<u8>,
) -> AResult<bool> {
while mmap.avail() > 0 {
// Write samples to DMA area from iterator
let Some(buffer) = stream.buffers.front_mut() else {
return Ok(false);
};
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.data_descriptor.len() as usize {
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<u8>,
) -> AResult<bool> {
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 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) {
*sample = byte;
written_bytes += 1;
}
buffer.pos += written_bytes as usize;
if buffer.pos >= buffer.data_descriptor.len() as usize {
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<Mutex<PCM>>,
streams: Arc<RwLock<Vec<Stream>>>,
receiver: &Receiver<bool>,
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::<u8>().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<RwLock<Vec<Stream>>>) -> 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<RwLock<Vec<Stream>>>,
receiver: Receiver<AlsaAction>,
) -> Result<(), Box<dyn std::error::Error>> {
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<Descriptor> = msg.desc_chain.clone().collect();
let desc_request = &descriptors[0];
let request = msg
.desc_chain
.memory()
.read_obj::<VirtioSndPcmSetParams>(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,
}
}

View File

@ -0,0 +1,29 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::sync::{Arc, RwLock};
use super::AudioBackend;
use crate::{Result, Stream};
pub struct NullBackend {
streams: Arc<RwLock<Vec<Stream>>>,
}
impl NullBackend {
pub fn new(streams: Arc<RwLock<Vec<Stream>>>) -> Self {
Self { streams }
}
}
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(())
}
fn read(&self, _id: u32) -> Result<()> {
log::trace!("NullBackend read stream_id {}", _id);
Ok(())
}
}

View File

@ -0,0 +1,465 @@
// Pipewire backend device
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::{
collections::HashMap,
convert::TryInto,
mem::size_of,
ptr,
sync::{Arc, RwLock},
};
use log::debug;
use pw::{properties, spa, sys::PW_ID_CORE, Context, Core, ThreadLoop};
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,
};
// 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 {
pub stream_params: Arc<RwLock<Vec<Stream>>>,
thread_loop: ThreadLoop,
pub core: Core,
#[allow(dead_code)]
context: Context,
pub stream_hash: RwLock<HashMap<u32, pw::stream::Stream>>,
pub stream_listener: RwLock<HashMap<u32, pw::stream::StreamListener<i32>>>,
}
impl PwBackend {
pub fn new(stream_params: Arc<RwLock<Vec<Stream>>>) -> Self {
pw::init();
let thread_loop = unsafe { ThreadLoop::new(Some("Pipewire thread loop")).unwrap() };
let lock_guard = thread_loop.lock();
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");
// 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(false);
}
})
.register();
thread_loop.wait();
lock_guard.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<()> {
Ok(())
}
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<Descriptor> = msg.desc_chain.clone().collect();
let desc_request = &descriptors[0];
let request = msg
.desc_chain
.memory()
.read_obj::<VirtioSndPcmSetParams>(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();
let lock_guard = 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<u8> = 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| match stream.dequeue_buffer() {
None => debug!("No buffer recieved"),
Some(mut buf) => {
let datas = buf.datas_mut();
let frame_size = info.channels * size_of::<i16>() as u32;
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 start = buffer.pos;
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[0..n_bytes];
if avail <= 0 {
// pad with silence
unsafe {
ptr::write_bytes(p.as_mut_ptr(), 0, n_bytes);
}
} else {
// 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.data_descriptor.len() as usize {
streams.buffers.pop_front();
}
}
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()
.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");
// insert created stream in a hash table
stream_hash.insert(stream_id, stream);
lock_guard.unlock();
}
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 {
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();
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);
lock_guard.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 {
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");
lock_guard.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 {
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");
lock_guard.unlock();
}
Ok(())
}
}

View File

@ -0,0 +1,816 @@
// Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// Stefano Garzarella <sgarzare@redhat.com>
// 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},
};
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},
virtio_ring::{VIRTIO_RING_F_EVENT_IDX, 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},
stream::{Buffer, Error as StreamError, Stream},
virtio_sound::{self, *},
ControlMessageKind, Error, IOMessage, Result, SoundConfig,
};
struct VhostUserSoundThread {
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
event_idx: bool,
chmaps: Arc<RwLock<Vec<VirtioSoundChmapInfo>>>,
jacks: Arc<RwLock<Vec<VirtioSoundJackInfo>>>,
queue_indexes: Vec<u16>,
streams: Arc<RwLock<Vec<Stream>>>,
streams_no: usize,
}
type SoundDescriptorChain = DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap<()>>>;
impl VhostUserSoundThread {
pub fn new(
chmaps: Arc<RwLock<Vec<VirtioSoundChmapInfo>>>,
jacks: Arc<RwLock<Vec<VirtioSoundJackInfo>>>,
mut queue_indexes: Vec<u16>,
streams: Arc<RwLock<Vec<Stream>>>,
streams_no: usize,
) -> Result<Self> {
queue_indexes.sort();
Ok(Self {
event_idx: false,
mem: None,
chmaps,
jacks,
queue_indexes,
streams,
streams_no,
})
}
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],
audio_backend: &RwLock<Box<dyn AudioBackend + Send + Sync>>,
) -> IoResult<bool> {
let vring = &vrings[device_event as usize];
let queue_idx = self.queue_indexes[device_event as usize];
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,
audio_backend: &RwLock<Box<dyn AudioBackend + Send + Sync>>,
) -> IoResult<bool> {
let requests: Vec<SoundDescriptorChain> = 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);
}
// 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::<VirtioSoundHeader>(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::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::<VirtioSoundQueryInfo>(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::<VirtioSoundQueryInfo>(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 => {
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::<VirtioSoundQueryInfo>(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::<VirtioSndPcmSetParams>(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 {
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.
continue;
}
}
ControlMessageKind::PcmPrepare => {
let request = desc_chain
.memory()
.read_obj::<VirtioSoundPcmHeader>(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 {
audio_backend.write().unwrap().prepare(stream_id).unwrap();
}
}
ControlMessageKind::PcmRelease => {
let request = desc_chain
.memory()
.read_obj::<VirtioSoundPcmHeader>(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 {
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.
continue;
}
}
ControlMessageKind::PcmStart => {
let request = desc_chain
.memory()
.read_obj::<VirtioSoundPcmHeader>(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 {
audio_backend.write().unwrap().start(stream_id).unwrap();
}
}
ControlMessageKind::PcmStop => {
let request = desc_chain
.memory()
.read_obj::<VirtioSoundPcmHeader>(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 {
audio_backend.write().unwrap().stop(stream_id).unwrap();
}
}
}
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<bool> {
log::trace!("process_event");
Ok(false)
}
fn process_tx(
&self,
vring: &VringRwLock,
audio_backend: &RwLock<Box<dyn AudioBackend + Send + Sync>>,
) -> IoResult<bool> {
let requests: Vec<SoundDescriptorChain> = 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::<VirtioSoundPcmStatus>() {
return Err(Error::UnexpectedDescriptorSize(
size_of::<VirtioSoundPcmStatus>(),
descriptor.len(),
)
.into());
}
state = TxState::Done;
}
TxState::WaitingBufferForStreamId(stream_id) if descriptor.is_write_only() => {
if descriptor.len() as usize != size_of::<VirtioSoundPcmStatus>() {
return Err(Error::UnexpectedDescriptorSize(
size_of::<VirtioSoundPcmStatus>(),
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::<VirtioSoundPcmXfer>() =>
{
return Err(Error::UnexpectedDescriptorSize(
size_of::<VirtioSoundPcmXfer>(),
descriptor.len(),
)
.into());
}
TxState::Ready => {
let xfer = desc_chain
.memory()
.read_obj::<VirtioSoundPcmXfer>(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::<VirtioSoundPcmXfer>() =>
{
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) => {
/*
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)));
}
}
}
}
if !stream_ids.is_empty() {
let b = audio_backend.write().unwrap();
for id in stream_ids {
b.write(id).unwrap();
}
}
Ok(false)
}
fn process_rx(
&self,
_vring: &VringRwLock,
_audio_backend: &RwLock<Box<dyn AudioBackend + Send + Sync>>,
) -> IoResult<bool> {
log::trace!("process_rx");
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 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));
let jacks: Arc<RwLock<Vec<VirtioSoundJackInfo>>> = 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<VirtioSoundChmapInfo> = 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<RwLock<Vec<VirtioSoundChmapInfo>>> = 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,
)?),
]
} else {
vec![RwLock::new(VhostUserSoundThread::new(
chmaps.clone(),
jacks.clone(),
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, streams)?;
Ok(Self {
threads,
virtio_cfg: VirtioSoundConfig {
jacks: 0.into(),
streams: 1.into(),
chmaps: 1.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 {
// TODO: Investigate if an alternative value makes any difference.
64
}
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 | VhostUserProtocolFeatures::MQ
}
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.audio_backend,
)
}
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()
}
}
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");
}
}
}

View File

@ -0,0 +1,317 @@
// Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
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 clap::ValueEnum;
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, VringRwLock, VringT};
use virtio_sound::*;
use vm_memory::{
ByteValued, Bytes, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, Le32,
};
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),
#[error("Stream with id {0} not found")]
StreamWithIdNotFound(u32),
#[error("Channel number not supported: {0}")]
ChannelNotSupported(u8),
}
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(ValueEnum, Clone, Copy, Default, Debug, Eq, PartialEq)]
pub enum BackendType {
#[default]
Null,
#[cfg(feature = "pw-backend")]
Pipewire,
#[cfg(feature = "alsa-backend")]
Alsa,
}
#[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.
pub struct SoundConfig {
/// vhost-user Unix domain socket
socket: String,
/// use multiple threads to hanlde the virtqueues
multi_thread: bool,
/// 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: BackendType) -> Self {
Self {
socket,
multi_thread,
audio_backend,
}
}
/// 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)
}
pub fn get_audio_backend(&self) -> BackendType {
self.audio_backend
}
}
pub struct IOMessage {
status: std::sync::atomic::AtomicU32,
desc_chain: SoundDescriptorChain,
descriptor: virtio_queue::Descriptor,
vring: VringRwLock,
}
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");
}
}
}
/// This is the public API through which an external program starts the
/// 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-device-sound"),
backend.clone(),
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
)
.unwrap();
log::trace!("Starting daemon");
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();
}

View File

@ -0,0 +1,92 @@
// 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_device_sound::{start_backend_server, BackendType, Error, Result, SoundConfig};
#[derive(Parser, Debug)]
#[clap(version, about, long_about = None)]
struct SoundArgs {
/// vhost-user Unix domain socket path.
#[clap(long)]
socket: String,
/// audio backend to be used
#[clap(long)]
#[clap(value_enum)]
backend: BackendType,
}
impl TryFrom<SoundArgs> for SoundConfig {
type Error = Error;
fn try_from(cmd_args: SoundArgs) -> Result<Self> {
let socket = cmd_args.socket.trim().to_string();
Ok(SoundConfig::new(socket, false, cmd_args.backend))
}
}
fn main() {
env_logger::init();
let config = SoundConfig::try_from(SoundArgs::parse()).unwrap();
loop {
start_backend_server(config.clone());
}
}
#[cfg(test)]
mod tests {
use rstest::*;
use serial_test::serial;
use super::*;
impl SoundArgs {
fn from_args(socket: &str) -> Self {
SoundArgs {
socket: socket.to_string(),
backend: BackendType::default(),
}
}
}
#[test]
#[serial]
fn test_sound_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");
}
#[rstest]
#[serial]
#[case::null_backend("null", BackendType::Null)]
#[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([
"",
"--socket",
"/tmp/vhost-sound.socket ",
"--backend",
backend_name,
]);
let config = SoundConfig::try_from(args);
assert!(config.is_ok());
let config = config.unwrap();
assert_eq!(config.get_audio_backend(), backend);
}
}

View File

@ -0,0 +1,284 @@
// Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use std::{collections::VecDeque, sync::Arc};
use thiserror::Error as ThisError;
use vm_memory::{Address, Bytes, Le32, Le64};
use crate::{virtio_sound::*, IOMessage, 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),
#[error("Descriptor read failed")]
DescriptorReadFailed,
}
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,
pub buffers: VecDeque<Buffer>,
}
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(),
buffers: VecDeque::new(),
}
}
}
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,
}
}
}
pub struct Buffer {
// TODO: to make private and add len usize
pub data_descriptor: virtio_queue::Descriptor,
pub pos: usize,
pub message: Arc<IOMessage>,
}
impl std::fmt::Debug for Buffer {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct(stringify!(Buffer))
.field("pos", &self.pos)
.field("message", &Arc::as_ptr(&self.message))
.finish()
}
}
impl Buffer {
pub fn new(data_descriptor: virtio_queue::Descriptor, message: Arc<IOMessage>) -> Self {
Self {
pos: 0,
data_descriptor,
message,
}
}
pub fn consume(&self, buf: &mut [u8]) -> Result<u32> {
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 {
fn drop(&mut self) {
log::trace!("dropping buffer {:?}", self);
}
}

View File

@ -0,0 +1,344 @@
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
use vm_memory::{ByteValued, Le32, Le64};
// 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: u8 = 0;
pub const VIRTIO_SND_D_INPUT: u8 = 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, Eq)]
#[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 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 {}
/// Virtio Sound Request / Response common header
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[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, Eq)]
#[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, Eq)]
#[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 count: 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, Eq)]
#[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 {}
/// Jack control request / Jack common header
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[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, Eq)]
#[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, Eq)]
#[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, Eq)]
#[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, Eq)]
#[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, Eq)]
#[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, Eq)]
#[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, Eq)]
#[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, Eq)]
#[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 {}