sound: initial commit
This commit is contained in:
parent
a3427f5c34
commit
0a78103b41
7 changed files with 350 additions and 0 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
|
@ -864,6 +864,22 @@ dependencies = [
|
|||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vhost-user-sound"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"env_logger",
|
||||
"log",
|
||||
"serial_test",
|
||||
"thiserror",
|
||||
"vhost",
|
||||
"vhost-user-backend",
|
||||
"virtio-bindings 0.2.0",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vhost-user-vsock"
|
||||
version = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@ members = [
|
|||
"crates/gpio",
|
||||
"crates/i2c",
|
||||
"crates/rng",
|
||||
"crates/sound",
|
||||
"crates/vsock",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ Here is the list of device backends that we support:
|
|||
- [GPIO](https://github.com/rust-vmm/vhost-device/blob/main/crates/gpio/README.md)
|
||||
- [I2C](https://github.com/rust-vmm/vhost-device/blob/main/crates/i2c/README.md)
|
||||
- [RNG](https://github.com/rust-vmm/vhost-device/blob/main/crates/rng/README.md)
|
||||
- [Sound](https://github.com/rust-vmm/vhost-device/blob/main/crates/sound/README.md)
|
||||
- [VSOCK](https://github.com/rust-vmm/vhost-device/blob/main/crates/vsock/README.md)
|
||||
|
||||
## Testing and Code Coverage
|
||||
|
|
|
|||
24
crates/sound/Cargo.toml
Normal file
24
crates/sound/Cargo.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "vhost-user-sound"
|
||||
version = "0.1.0"
|
||||
authors = ["Stefano Garzarella <sgarzare@redhat.com>"]
|
||||
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"]
|
||||
license = "Apache-2.0 OR BSD-3-Clause"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.1", features = ["derive"] }
|
||||
env_logger = "0.10"
|
||||
log = "0.4"
|
||||
thiserror = "1.0"
|
||||
vhost = { version = "0.6", features = ["vhost-user-slave"] }
|
||||
vhost-user-backend = "0.8"
|
||||
virtio-bindings = "0.2"
|
||||
vm-memory = "0.10"
|
||||
vmm-sys-util = "0.11"
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "1.0"
|
||||
14
crates/sound/README.md
Normal file
14
crates/sound/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# vhost-user-sound
|
||||
|
||||
## Design
|
||||
|
||||
## Usage
|
||||
|
||||
## Working example
|
||||
|
||||
## 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)
|
||||
103
crates/sound/src/main.rs
Normal file
103
crates/sound/src/main.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
mod vhu_sound;
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use log::{info, warn};
|
||||
use vhost::{vhost_user, vhost_user::Listener};
|
||||
use vhost_user_backend::VhostUserDaemon;
|
||||
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap};
|
||||
|
||||
use crate::vhu_sound::{Error, Result, SoundConfig, VhostUserSoundBackend};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version, about, long_about = None)]
|
||||
struct SoundArgs {
|
||||
/// vhost-user Unix domain socket path.
|
||||
#[clap(long)]
|
||||
socket: String,
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the public API through which an external program starts the
|
||||
/// vhost-user-sound backend server.
|
||||
pub(crate) fn start_backend_server(config: SoundConfig) {
|
||||
loop {
|
||||
let backend = Arc::new(RwLock::new(
|
||||
VhostUserSoundBackend::new(config.clone()).unwrap(),
|
||||
));
|
||||
|
||||
let listener = Listener::new(config.get_socket_path(), true).unwrap();
|
||||
|
||||
let mut daemon = VhostUserDaemon::new(
|
||||
String::from("vhost-user-sound"),
|
||||
backend.clone(),
|
||||
GuestMemoryAtomic::new(GuestMemoryMmap::new()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
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.read().unwrap().exit_event.write(1).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let config = SoundConfig::try_from(SoundArgs::parse()).unwrap();
|
||||
start_backend_server(config);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serial_test::serial;
|
||||
|
||||
impl SoundArgs {
|
||||
fn from_args(socket: &str) -> Self {
|
||||
SoundArgs {
|
||||
socket: socket.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_vsock_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");
|
||||
}
|
||||
}
|
||||
191
crates/sound/src/vhu_sound.rs
Normal file
191
crates/sound/src/vhu_sound.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
use std::{
|
||||
io::{self, Result as IoResult},
|
||||
u16, u32, u64, u8,
|
||||
};
|
||||
use thiserror::Error as ThisError;
|
||||
use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
|
||||
use vhost_user_backend::{VhostUserBackendMut, VringRwLock};
|
||||
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 vm_memory::{ByteValued, GuestMemoryAtomic, GuestMemoryMmap, Le32};
|
||||
use vmm_sys_util::{
|
||||
epoll::EventSet,
|
||||
eventfd::{EventFd, EFD_NONBLOCK},
|
||||
};
|
||||
|
||||
const CONTROL_Q: u16 = 0;
|
||||
const EVENT_Q: u16 = 1;
|
||||
const TX_Q: u16 = 2;
|
||||
const RX_Q: u16 = 3;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Custom error types
|
||||
#[derive(Debug, ThisError)]
|
||||
pub(crate) enum Error {
|
||||
#[error("Failed to handle event other than EPOLLIN event")]
|
||||
HandleEventNotEpollIn,
|
||||
#[error("Failed to handle unknown event")]
|
||||
HandleUnknownEvent,
|
||||
#[error("Failed to create a new EventFd")]
|
||||
EventFdCreate(std::io::Error),
|
||||
}
|
||||
|
||||
impl std::convert::From<Error> for std::io::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
std::io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// This structure is the public API through which an external program
|
||||
/// is allowed to configure the backend.
|
||||
pub(crate) struct SoundConfig {
|
||||
socket: String,
|
||||
}
|
||||
|
||||
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) -> Self {
|
||||
Self { socket }
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Virtio Sound Configuration
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
#[repr(C)]
|
||||
struct VirtioSoundConfig {
|
||||
/// total number of all available jacks
|
||||
jacks: Le32,
|
||||
/// total number of all available PCM streams
|
||||
streams: Le32,
|
||||
/// total number of all available channel maps
|
||||
chmpas: 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)]
|
||||
#[repr(C)]
|
||||
struct VirtioSoundHeader {
|
||||
/// request type / response status
|
||||
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 {}
|
||||
|
||||
pub(crate) struct VhostUserSoundBackend {
|
||||
config: VirtioSoundConfig,
|
||||
queues_per_thread: Vec<u64>,
|
||||
event_idx: bool,
|
||||
pub(crate) exit_event: EventFd,
|
||||
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
|
||||
}
|
||||
|
||||
impl VhostUserSoundBackend {
|
||||
pub fn new(_config: SoundConfig) -> Result<Self> {
|
||||
let queues_per_thread = vec![0b1111];
|
||||
|
||||
Ok(Self {
|
||||
config: VirtioSoundConfig {
|
||||
jacks: 0.into(),
|
||||
streams: 1.into(),
|
||||
chmpas: 0.into(),
|
||||
},
|
||||
queues_per_thread,
|
||||
event_idx: false,
|
||||
exit_event: EventFd::new(EFD_NONBLOCK).map_err(Error::EventFdCreate)?,
|
||||
mem: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserBackendMut<VringRwLock, ()> for VhostUserSoundBackend {
|
||||
fn num_queues(&self) -> usize {
|
||||
4
|
||||
}
|
||||
|
||||
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(&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(
|
||||
&mut self,
|
||||
device_event: u16,
|
||||
evset: EventSet,
|
||||
_vrings: &[VringRwLock],
|
||||
_thread_id: usize,
|
||||
) -> IoResult<bool> {
|
||||
if evset != EventSet::IN {
|
||||
return Err(Error::HandleEventNotEpollIn.into());
|
||||
}
|
||||
|
||||
match device_event {
|
||||
CONTROL_Q => {}
|
||||
EVENT_Q => {}
|
||||
TX_Q => {}
|
||||
RX_Q => {}
|
||||
_ => {
|
||||
return Err(Error::HandleUnknownEvent.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn get_config(&self, offset: u32, size: u32) -> Vec<u8> {
|
||||
let offset = offset as usize;
|
||||
let size = size as usize;
|
||||
|
||||
let buf = self.config.as_slice();
|
||||
|
||||
if offset + size > buf.len() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
buf[offset..offset + size].to_vec()
|
||||
}
|
||||
|
||||
fn queues_per_thread(&self) -> Vec<u64> {
|
||||
self.queues_per_thread.clone()
|
||||
}
|
||||
|
||||
fn exit_event(&self, _thread_index: usize) -> Option<EventFd> {
|
||||
self.exit_event.try_clone().ok()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue