mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2025-12-27 07:29:24 +00:00
sound: add Stream, ControlMessage and other types
Signed-off-by: Emmanouil Pitsidianakis <manos.pitsidianakis@linaro.org>
This commit is contained in:
parent
91a5259cce
commit
d385dcd1b2
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1182,6 +1182,7 @@ dependencies = [
|
||||
"vhost",
|
||||
"vhost-user-backend",
|
||||
"virtio-bindings 0.2.1",
|
||||
"virtio-queue",
|
||||
"vm-memory",
|
||||
"vmm-sys-util",
|
||||
]
|
||||
|
||||
@ -27,6 +27,7 @@ thiserror = "1.0"
|
||||
vhost = { version = "0.6", features = ["vhost-user-slave"] }
|
||||
vhost-user-backend = "0.8"
|
||||
virtio-bindings = "0.2.1"
|
||||
virtio-queue = "0.7"
|
||||
vm-memory = "0.10"
|
||||
vmm-sys-util = "0.11"
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ pub trait AudioBackend {
|
||||
}
|
||||
|
||||
pub fn alloc_audio_backend(name: String) -> Result<Box<dyn AudioBackend + Send + Sync>> {
|
||||
log::trace!("allocating audio backend {}", name);
|
||||
match name.as_str() {
|
||||
#[cfg(feature = "null-backend")]
|
||||
"null" => Ok(Box::new(NullBackend::new())),
|
||||
|
||||
@ -13,10 +13,12 @@ impl NullBackend {
|
||||
|
||||
impl AudioBackend for NullBackend {
|
||||
fn write(&self, _req: &SoundRequest) -> Result<()> {
|
||||
log::trace!("NullBackend writing {:?}", _req);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read(&self, req: &mut SoundRequest) -> Result<()> {
|
||||
log::trace!("NullBackend reading {:?}", req);
|
||||
let buf = req.data_slice().ok_or(Error::SoundReqMissingData)?;
|
||||
let zero_mem = vec![0u8; buf.len()];
|
||||
|
||||
|
||||
@ -5,12 +5,13 @@
|
||||
use std::{io::Result as IoResult, sync::RwLock, u16, u32, u64, u8};
|
||||
|
||||
use vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
|
||||
use vhost_user_backend::{VhostUserBackend, VringRwLock};
|
||||
use vhost_user_backend::{VhostUserBackend, VringRwLock, VringT};
|
||||
use virtio_bindings::bindings::{
|
||||
virtio_config::{VIRTIO_F_NOTIFY_ON_EMPTY, VIRTIO_F_VERSION_1},
|
||||
virtio_ring::{VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC},
|
||||
};
|
||||
use vm_memory::{ByteValued, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use virtio_queue::QueueOwnedT;
|
||||
use vm_memory::{ByteValued, GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
|
||||
use vmm_sys_util::{
|
||||
epoll::EventSet,
|
||||
eventfd::{EventFd, EFD_NONBLOCK},
|
||||
@ -28,6 +29,17 @@ struct VhostUserSoundThread {
|
||||
queue_indexes: Vec<u16>,
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
const fn queue_idx_as_str(q: u16) -> &'static str {
|
||||
match q {
|
||||
CONTROL_QUEUE_IDX => stringify!(CONTROL_QUEUE_IDX),
|
||||
EVENT_QUEUE_IDX => stringify!(EVENT_QUEUE_IDX),
|
||||
TX_QUEUE_IDX => stringify!(TX_QUEUE_IDX),
|
||||
RX_QUEUE_IDX => stringify!(RX_QUEUE_IDX),
|
||||
_ => "unknown queue idx",
|
||||
}
|
||||
}
|
||||
|
||||
impl VhostUserSoundThread {
|
||||
pub fn new(mut queue_indexes: Vec<u16>) -> Result<Self> {
|
||||
queue_indexes.sort();
|
||||
@ -59,8 +71,15 @@ impl VhostUserSoundThread {
|
||||
}
|
||||
|
||||
fn handle_event(&self, device_event: u16, vrings: &[VringRwLock]) -> IoResult<bool> {
|
||||
log::trace!("handle_event device_event {}", device_event);
|
||||
|
||||
let vring = &vrings[device_event as usize];
|
||||
let queue_idx = self.queue_indexes[device_event as usize];
|
||||
log::trace!(
|
||||
"handle_event queue_idx {} == {}",
|
||||
queue_idx,
|
||||
queue_idx_as_str(queue_idx)
|
||||
);
|
||||
|
||||
match queue_idx {
|
||||
CONTROL_QUEUE_IDX => self.process_control(vring),
|
||||
@ -72,18 +91,29 @@ impl VhostUserSoundThread {
|
||||
}
|
||||
|
||||
fn process_control(&self, _vring: &VringRwLock) -> IoResult<bool> {
|
||||
Ok(false)
|
||||
let requests: Vec<_> = _vring
|
||||
.get_mut()
|
||||
.get_queue_mut()
|
||||
.iter(self.mem.as_ref().unwrap().memory())
|
||||
.map_err(|_| Error::DescriptorNotFound)?
|
||||
.collect();
|
||||
dbg!(&requests);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn process_event(&self, _vring: &VringRwLock) -> IoResult<bool> {
|
||||
log::trace!("process_event");
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn process_tx(&self, _vring: &VringRwLock) -> IoResult<bool> {
|
||||
log::trace!("process_tx");
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn process_rx(&self, _vring: &VringRwLock) -> IoResult<bool> {
|
||||
log::trace!("process_rx");
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
@ -97,6 +127,7 @@ pub struct VhostUserSoundBackend {
|
||||
|
||||
impl VhostUserSoundBackend {
|
||||
pub fn new(config: SoundConfig) -> Result<Self> {
|
||||
log::trace!("VhostUserSoundBackend::new config {:?}", &config);
|
||||
let threads = if config.multi_thread {
|
||||
vec![
|
||||
RwLock::new(VhostUserSoundThread::new(vec![
|
||||
@ -152,16 +183,18 @@ impl VhostUserBackend<VringRwLock, ()> for VhostUserSoundBackend {
|
||||
}
|
||||
|
||||
fn protocol_features(&self) -> VhostUserProtocolFeatures {
|
||||
VhostUserProtocolFeatures::CONFIG
|
||||
VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ
|
||||
}
|
||||
|
||||
fn set_event_idx(&self, enabled: bool) {
|
||||
log::trace!("set_event_idx enabled {:?}", enabled);
|
||||
for thread in self.threads.iter() {
|
||||
thread.write().unwrap().set_event_idx(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_memory(&self, mem: GuestMemoryAtomic<GuestMemoryMmap>) -> IoResult<()> {
|
||||
log::trace!("update_memory");
|
||||
for thread in self.threads.iter() {
|
||||
thread.write().unwrap().update_memory(mem.clone())?;
|
||||
}
|
||||
@ -176,6 +209,7 @@ impl VhostUserBackend<VringRwLock, ()> for VhostUserSoundBackend {
|
||||
vrings: &[VringRwLock],
|
||||
thread_id: usize,
|
||||
) -> IoResult<bool> {
|
||||
log::trace!("handle_event device_event {}", device_event);
|
||||
if evset != EventSet::IN {
|
||||
return Err(Error::HandleEventNotEpollIn.into());
|
||||
}
|
||||
@ -187,6 +221,7 @@ impl VhostUserBackend<VringRwLock, ()> for VhostUserSoundBackend {
|
||||
}
|
||||
|
||||
fn get_config(&self, offset: u32, size: u32) -> Vec<u8> {
|
||||
log::trace!("get_config offset {} size {}", offset, size);
|
||||
let offset = offset as usize;
|
||||
let size = size as usize;
|
||||
|
||||
@ -210,6 +245,7 @@ impl VhostUserBackend<VringRwLock, ()> for VhostUserSoundBackend {
|
||||
}
|
||||
|
||||
fn exit_event(&self, _thread_index: usize) -> Option<EventFd> {
|
||||
log::trace!("exit_event");
|
||||
self.exit_event.try_clone().ok()
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,44 +3,199 @@
|
||||
|
||||
pub mod audio_backends;
|
||||
pub mod device;
|
||||
pub mod stream;
|
||||
pub mod virtio_sound;
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
io::{Error as IoError, ErrorKind},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use log::{info, warn};
|
||||
pub use stream::Stream;
|
||||
use thiserror::Error as ThisError;
|
||||
use vhost::{vhost_user, vhost_user::Listener};
|
||||
use vhost_user_backend::VhostUserDaemon;
|
||||
use vm_memory::{GuestMemoryAtomic, GuestMemoryMmap, VolatileSlice};
|
||||
use vhost_user_backend::{VhostUserDaemon, VringRwLock, VringT};
|
||||
use virtio_sound::*;
|
||||
use vm_memory::{
|
||||
ByteValued, Bytes, GuestMemoryAtomic, GuestMemoryLoadGuard, GuestMemoryMmap, Le32,
|
||||
VolatileSlice,
|
||||
};
|
||||
|
||||
use crate::device::VhostUserSoundBackend;
|
||||
|
||||
pub const SUPPORTED_FORMATS: u64 = 1 << VIRTIO_SND_PCM_FMT_U8
|
||||
| 1 << VIRTIO_SND_PCM_FMT_S16
|
||||
| 1 << VIRTIO_SND_PCM_FMT_S24
|
||||
| 1 << VIRTIO_SND_PCM_FMT_S32;
|
||||
|
||||
pub const SUPPORTED_RATES: u64 = 1 << VIRTIO_SND_PCM_RATE_8000
|
||||
| 1 << VIRTIO_SND_PCM_RATE_11025
|
||||
| 1 << VIRTIO_SND_PCM_RATE_16000
|
||||
| 1 << VIRTIO_SND_PCM_RATE_22050
|
||||
| 1 << VIRTIO_SND_PCM_RATE_32000
|
||||
| 1 << VIRTIO_SND_PCM_RATE_44100
|
||||
| 1 << VIRTIO_SND_PCM_RATE_48000;
|
||||
|
||||
use virtio_queue::DescriptorChain;
|
||||
pub type SoundDescriptorChain = DescriptorChain<GuestMemoryLoadGuard<GuestMemoryMmap<()>>>;
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Custom error types
|
||||
#[derive(Debug, ThisError)]
|
||||
pub enum Error {
|
||||
#[error("Notification send failed")]
|
||||
SendNotificationFailed,
|
||||
#[error("Descriptor not found")]
|
||||
DescriptorNotFound,
|
||||
#[error("Descriptor read failed")]
|
||||
DescriptorReadFailed,
|
||||
#[error("Descriptor write failed")]
|
||||
DescriptorWriteFailed,
|
||||
#[error("Failed to handle event other than EPOLLIN event")]
|
||||
HandleEventNotEpollIn,
|
||||
#[error("Failed to handle unknown event")]
|
||||
HandleUnknownEvent,
|
||||
#[error("Invalid control message code {0}")]
|
||||
InvalidControlMessage(u32),
|
||||
#[error("Failed to create a new EventFd")]
|
||||
EventFdCreate(IoError),
|
||||
#[error("Request missing data buffer")]
|
||||
SoundReqMissingData,
|
||||
#[error("Audio backend not supported")]
|
||||
AudioBackendNotSupported,
|
||||
#[error("Invalid virtio_snd_hdr size, expected: {0}, found: {1}")]
|
||||
UnexpectedSoundHeaderSize(usize, u32),
|
||||
#[error("Received unexpected write only descriptor at index {0}")]
|
||||
UnexpectedWriteOnlyDescriptor(usize),
|
||||
#[error("Received unexpected readable descriptor at index {0}")]
|
||||
UnexpectedReadableDescriptor(usize),
|
||||
#[error("Invalid descriptor count {0}")]
|
||||
UnexpectedDescriptorCount(usize),
|
||||
#[error("Invalid descriptor size, expected: {0}, found: {1}")]
|
||||
UnexpectedDescriptorSize(usize, u32),
|
||||
#[error("Protocol or device error: {0}")]
|
||||
Stream(stream::Error),
|
||||
}
|
||||
|
||||
impl std::convert::From<Error> for IoError {
|
||||
impl From<Error> for IoError {
|
||||
fn from(e: Error) -> Self {
|
||||
Self::new(ErrorKind::Other, e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<stream::Error> for Error {
|
||||
fn from(val: stream::Error) -> Self {
|
||||
Self::Stream(val)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidControlMessage(u32);
|
||||
|
||||
impl std::fmt::Display for InvalidControlMessage {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(fmt, "Invalid control message code {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InvalidControlMessage> for crate::Error {
|
||||
fn from(val: InvalidControlMessage) -> Self {
|
||||
Self::InvalidControlMessage(val.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InvalidControlMessage {}
|
||||
|
||||
#[derive(Copy, Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(u32)]
|
||||
pub enum ControlMessageKind {
|
||||
JackInfo = 1,
|
||||
JackRemap = 2,
|
||||
PcmInfo = 0x0100,
|
||||
PcmSetParams = 0x0101,
|
||||
PcmPrepare = 0x0102,
|
||||
PcmRelease = 0x0103,
|
||||
PcmStart = 0x0104,
|
||||
PcmStop = 0x0105,
|
||||
ChmapInfo = 0x0200,
|
||||
}
|
||||
|
||||
impl TryFrom<Le32> for ControlMessageKind {
|
||||
type Error = InvalidControlMessage;
|
||||
|
||||
fn try_from(val: Le32) -> std::result::Result<Self, Self::Error> {
|
||||
Ok(match u32::from(val) {
|
||||
VIRTIO_SND_R_JACK_INFO => Self::JackInfo,
|
||||
VIRTIO_SND_R_JACK_REMAP => Self::JackRemap,
|
||||
VIRTIO_SND_R_PCM_INFO => Self::PcmInfo,
|
||||
VIRTIO_SND_R_PCM_SET_PARAMS => Self::PcmSetParams,
|
||||
VIRTIO_SND_R_PCM_PREPARE => Self::PcmPrepare,
|
||||
VIRTIO_SND_R_PCM_RELEASE => Self::PcmRelease,
|
||||
VIRTIO_SND_R_PCM_START => Self::PcmStart,
|
||||
VIRTIO_SND_R_PCM_STOP => Self::PcmStop,
|
||||
VIRTIO_SND_R_CHMAP_INFO => Self::ChmapInfo,
|
||||
other => return Err(InvalidControlMessage(other)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ControlMessage {
|
||||
pub kind: ControlMessageKind,
|
||||
pub code: u32,
|
||||
pub desc_chain: SoundDescriptorChain,
|
||||
pub descriptor: virtio_queue::Descriptor,
|
||||
pub vring: VringRwLock,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ControlMessage {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmt.debug_struct(stringify!(ControlMessage))
|
||||
.field("kind", &self.kind)
|
||||
.field("code", &self.code)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ControlMessage {
|
||||
fn drop(&mut self) {
|
||||
log::trace!(
|
||||
"dropping ControlMessage {:?} reply = {}",
|
||||
self.kind,
|
||||
match self.code {
|
||||
virtio_sound::VIRTIO_SND_S_OK => "VIRTIO_SND_S_OK",
|
||||
virtio_sound::VIRTIO_SND_S_BAD_MSG => "VIRTIO_SND_S_BAD_MSG",
|
||||
virtio_sound::VIRTIO_SND_S_NOT_SUPP => "VIRTIO_SND_S_NOT_SUPP",
|
||||
virtio_sound::VIRTIO_SND_S_IO_ERR => "VIRTIO_SND_S_IO_ERR",
|
||||
_ => "other",
|
||||
}
|
||||
);
|
||||
let resp = VirtioSoundHeader {
|
||||
code: self.code.into(),
|
||||
};
|
||||
|
||||
if let Err(err) = self
|
||||
.desc_chain
|
||||
.memory()
|
||||
.write_obj(resp, self.descriptor.addr())
|
||||
{
|
||||
log::error!("Error::DescriptorWriteFailed: {}", err);
|
||||
return;
|
||||
}
|
||||
if self
|
||||
.vring
|
||||
.add_used(self.desc_chain.head_index(), resp.as_slice().len() as u32)
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Couldn't add used");
|
||||
}
|
||||
if self.vring.signal_used_queue().is_err() {
|
||||
log::error!("Couldn't signal used queue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// This structure is the public API through which an external program
|
||||
/// is allowed to configure the backend.
|
||||
@ -87,6 +242,7 @@ impl<'a> SoundRequest<'a> {
|
||||
/// This is the public API through which an external program starts the
|
||||
/// vhost-user-sound backend server.
|
||||
pub fn start_backend_server(config: SoundConfig) {
|
||||
log::trace!("Using config {:?}", &config);
|
||||
let listener = Listener::new(config.get_socket_path(), true).unwrap();
|
||||
let backend = Arc::new(VhostUserSoundBackend::new(config).unwrap());
|
||||
|
||||
@ -97,6 +253,7 @@ pub fn start_backend_server(config: SoundConfig) {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
log::trace!("Starting daemon");
|
||||
daemon.start(listener).unwrap();
|
||||
|
||||
match daemon.wait() {
|
||||
|
||||
230
crates/sound/src/stream.rs
Normal file
230
crates/sound/src/stream.rs
Normal file
@ -0,0 +1,230 @@
|
||||
// Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
|
||||
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
||||
|
||||
use thiserror::Error as ThisError;
|
||||
use vm_memory::{Le32, Le64};
|
||||
|
||||
use crate::{virtio_sound::*, SUPPORTED_FORMATS, SUPPORTED_RATES};
|
||||
|
||||
/// Stream errors.
|
||||
#[derive(Debug, ThisError)]
|
||||
pub enum Error {
|
||||
#[error("Guest driver request an invalid stream state transition from {0} to {1}.")]
|
||||
InvalidStateTransition(PCMState, PCMState),
|
||||
#[error("Guest requested an invalid stream id: {0}")]
|
||||
InvalidStreamId(u32),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// PCM stream state machine.
|
||||
///
|
||||
/// ## 5.14.6.6.1 PCM Command Lifecycle
|
||||
///
|
||||
/// A PCM stream has the following command lifecycle:
|
||||
///
|
||||
/// - `SET PARAMETERS`
|
||||
///
|
||||
/// The driver negotiates the stream parameters (format, transport, etc) with
|
||||
/// the device.
|
||||
///
|
||||
/// Possible valid transitions: `SET PARAMETERS`, `PREPARE`.
|
||||
///
|
||||
/// - `PREPARE`
|
||||
///
|
||||
/// The device prepares the stream (allocates resources, etc).
|
||||
///
|
||||
/// Possible valid transitions: `SET PARAMETERS`, `PREPARE`, `START`,
|
||||
/// `RELEASE`. Output only: the driver transfers data for pre-buffing.
|
||||
///
|
||||
/// - `START`
|
||||
///
|
||||
/// The device starts the stream (unmute, putting into running state, etc).
|
||||
///
|
||||
/// Possible valid transitions: `STOP`.
|
||||
/// The driver transfers data to/from the stream.
|
||||
///
|
||||
/// - `STOP`
|
||||
///
|
||||
/// The device stops the stream (mute, putting into non-running state, etc).
|
||||
///
|
||||
/// Possible valid transitions: `START`, `RELEASE`.
|
||||
///
|
||||
/// - `RELEASE`
|
||||
///
|
||||
/// The device releases the stream (frees resources, etc).
|
||||
///
|
||||
/// Possible valid transitions: `SET PARAMETERS`, `PREPARE`.
|
||||
///
|
||||
/// ```text
|
||||
/// +---------------+ +---------+ +---------+ +-------+ +-------+
|
||||
/// | SetParameters | | Prepare | | Release | | Start | | Stop |
|
||||
/// +---------------+ +---------+ +---------+ +-------+ +-------+
|
||||
/// | | | | |
|
||||
/// |- | | | |
|
||||
/// || | | | |
|
||||
/// |< | | | |
|
||||
/// | | | | |
|
||||
/// |------------->| | | |
|
||||
/// | | | | |
|
||||
/// |<-------------| | | |
|
||||
/// | | | | |
|
||||
/// | |- | | |
|
||||
/// | || | | |
|
||||
/// | |< | | |
|
||||
/// | | | | |
|
||||
/// | |--------------------->| |
|
||||
/// | | | | |
|
||||
/// | |---------->| | |
|
||||
/// | | | | |
|
||||
/// | | | |-------->|
|
||||
/// | | | | |
|
||||
/// | | | |<--------|
|
||||
/// | | | | |
|
||||
/// | | |<-------------------|
|
||||
/// | | | | |
|
||||
/// |<-------------------------| | |
|
||||
/// | | | | |
|
||||
/// | |<----------| | |
|
||||
/// ```
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub enum PCMState {
|
||||
#[default]
|
||||
#[doc(alias = "VIRTIO_SND_R_PCM_SET_PARAMS")]
|
||||
SetParameters,
|
||||
#[doc(alias = "VIRTIO_SND_R_PCM_PREPARE")]
|
||||
Prepare,
|
||||
#[doc(alias = "VIRTIO_SND_R_PCM_RELEASE")]
|
||||
Release,
|
||||
#[doc(alias = "VIRTIO_SND_R_PCM_START")]
|
||||
Start,
|
||||
#[doc(alias = "VIRTIO_SND_R_PCM_STOP")]
|
||||
Stop,
|
||||
}
|
||||
|
||||
macro_rules! set_new_state {
|
||||
($new_state_fn:ident, $new_state:expr, $($valid_source_states:tt)*) => {
|
||||
pub fn $new_state_fn(&mut self) -> Result<()> {
|
||||
if !matches!(self, $($valid_source_states)*) {
|
||||
return Err(Error::InvalidStateTransition(*self, $new_state));
|
||||
}
|
||||
*self = $new_state;
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl PCMState {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
set_new_state!(
|
||||
set_parameters,
|
||||
Self::SetParameters,
|
||||
Self::SetParameters | Self::Prepare | Self::Release
|
||||
);
|
||||
|
||||
set_new_state!(
|
||||
prepare,
|
||||
Self::Prepare,
|
||||
Self::SetParameters | Self::Prepare | Self::Release
|
||||
);
|
||||
|
||||
set_new_state!(start, Self::Start, Self::Prepare | Self::Stop);
|
||||
|
||||
set_new_state!(stop, Self::Stop, Self::Start);
|
||||
|
||||
set_new_state!(release, Self::Release, Self::Prepare | Self::Stop);
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PCMState {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use PCMState::*;
|
||||
match *self {
|
||||
SetParameters => {
|
||||
write!(fmt, "VIRTIO_SND_R_PCM_SET_PARAMS")
|
||||
}
|
||||
Prepare => {
|
||||
write!(fmt, "VIRTIO_SND_R_PCM_PREPARE")
|
||||
}
|
||||
Release => {
|
||||
write!(fmt, "VIRTIO_SND_R_PCM_RELEASE")
|
||||
}
|
||||
Start => {
|
||||
write!(fmt, "VIRTIO_SND_R_PCM_START")
|
||||
}
|
||||
Stop => {
|
||||
write!(fmt, "VIRTIO_SND_R_PCM_STOP")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal state of a PCM stream of the VIRTIO Sound device.
|
||||
#[derive(Debug)]
|
||||
pub struct Stream {
|
||||
pub id: usize,
|
||||
pub params: PcmParams,
|
||||
pub formats: Le64,
|
||||
pub rates: Le64,
|
||||
pub direction: u8,
|
||||
pub channels_min: u8,
|
||||
pub channels_max: u8,
|
||||
pub state: PCMState,
|
||||
}
|
||||
|
||||
impl Default for Stream {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: 0,
|
||||
direction: VIRTIO_SND_D_OUTPUT,
|
||||
formats: SUPPORTED_FORMATS.into(),
|
||||
rates: SUPPORTED_RATES.into(),
|
||||
params: PcmParams::default(),
|
||||
channels_min: 1,
|
||||
channels_max: 6,
|
||||
state: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
#[inline]
|
||||
pub fn supports_format(&self, format: u8) -> bool {
|
||||
let formats: u64 = self.formats.into();
|
||||
(formats & (1_u64 << format)) != 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn supports_rate(&self, rate: u8) -> bool {
|
||||
let rates: u64 = self.rates.into();
|
||||
(rates & (1_u64 << rate)) != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Stream params
|
||||
#[derive(Debug)]
|
||||
pub struct PcmParams {
|
||||
/// size of hardware buffer in bytes
|
||||
pub buffer_bytes: Le32,
|
||||
/// size of hardware period in bytes
|
||||
pub period_bytes: Le32,
|
||||
pub features: Le32,
|
||||
pub channels: u8,
|
||||
pub format: u8,
|
||||
pub rate: u8,
|
||||
}
|
||||
|
||||
impl Default for PcmParams {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buffer_bytes: 8192.into(),
|
||||
period_bytes: 4096.into(),
|
||||
features: 0.into(),
|
||||
channels: 1,
|
||||
format: VIRTIO_SND_PCM_FMT_S16,
|
||||
rate: VIRTIO_SND_PCM_RATE_44100,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -46,8 +46,8 @@ pub const VIRTIO_SND_S_IO_ERR: u32 = 0x8003;
|
||||
|
||||
// device data flow directions
|
||||
|
||||
pub const VIRTIO_SND_D_OUTPUT: u32 = 0;
|
||||
pub const VIRTIO_SND_D_INPUT: u32 = 1;
|
||||
pub const VIRTIO_SND_D_OUTPUT: u8 = 0;
|
||||
pub const VIRTIO_SND_D_INPUT: u8 = 1;
|
||||
|
||||
// supported jack features
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user