mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2025-12-27 07:29:24 +00:00
Add vhost-user-sound crate
Signed-off-by: Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org>
This commit is contained in:
parent
583d15433b
commit
91a5259cce
543
Cargo.lock
generated
543
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
3
crates/sound/CHANGELOG.md
Normal file
3
crates/sound/CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Upcoming Release
|
||||
|
||||
- First initial daemon implementation.
|
||||
@ -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
1
crates/sound/LICENSE-APACHE
Symbolic link
@ -0,0 +1 @@
|
||||
../../LICENSE-APACHE
|
||||
1
crates/sound/LICENSE-BSD-3-Clause
Symbolic link
1
crates/sound/LICENSE-BSD-3-Clause
Symbolic link
@ -0,0 +1 @@
|
||||
../../LICENSE-BSD-3-Clause
|
||||
@ -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
|
||||
|
||||
|
||||
7
crates/sound/rustfmt.toml
Normal file
7
crates/sound/rustfmt.toml
Normal 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
|
||||
@ -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>> {
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
28
crates/sound/src/audio_backends/pipewire.rs
Normal file
28
crates/sound/src/audio_backends/pipewire.rs
Normal 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
215
crates/sound/src/device.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user