#[cfg(test)]
mod fake;
use super::common::Feature;
use crate::{
queue::{owning::OwningQueue, VirtQueue},
transport::Transport,
volatile::{volread, ReadOnly},
Error, Hal, Result, PAGE_SIZE,
};
use alloc::{boxed::Box, collections::BTreeMap, vec, vec::Vec};
use bitflags::bitflags;
use core::{
array,
fmt::{self, Debug, Display, Formatter},
hint::spin_loop,
mem::size_of,
ops::RangeInclusive,
};
use enumn::N;
use log::{error, info, warn};
use zerocopy::{AsBytes, FromBytes, FromZeroes};
pub struct VirtIOSound<H: Hal, T: Transport> {
transport: T,
control_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
event_queue: OwningQueue<H, { QUEUE_SIZE as usize }, { size_of::<VirtIOSndEvent>() }>,
tx_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
rx_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
negotiated_features: Feature,
jacks: u32,
streams: u32,
chmaps: u32,
pcm_infos: Option<Vec<VirtIOSndPcmInfo>>,
jack_infos: Option<Vec<VirtIOSndJackInfo>>,
chmap_infos: Option<Vec<VirtIOSndChmapInfo>>,
pcm_parameters: Vec<PcmParameters>,
queue_buf_send: Box<[u8]>,
queue_buf_recv: Box<[u8]>,
set_up: bool,
token_rsp: BTreeMap<u16, Box<VirtIOSndPcmStatus>>, pcm_states: Vec<PCMState>,
token_buf: BTreeMap<u16, Vec<u8>>, }
impl<H: Hal, T: Transport> VirtIOSound<H, T> {
pub fn new(mut transport: T) -> Result<Self> {
let negotiated_features = transport.begin_init(SUPPORTED_FEATURES);
info!(
"[sound device] negotiated_features: {:?}",
negotiated_features
);
let control_queue = VirtQueue::new(
&mut transport,
CONTROL_QUEUE_IDX,
negotiated_features.contains(Feature::RING_INDIRECT_DESC),
negotiated_features.contains(Feature::RING_EVENT_IDX),
)?;
let event_queue = OwningQueue::new(VirtQueue::new(
&mut transport,
EVENT_QUEUE_IDX,
negotiated_features.contains(Feature::RING_INDIRECT_DESC),
negotiated_features.contains(Feature::RING_EVENT_IDX),
)?)?;
let tx_queue = VirtQueue::new(
&mut transport,
TX_QUEUE_IDX,
negotiated_features.contains(Feature::RING_INDIRECT_DESC),
negotiated_features.contains(Feature::RING_EVENT_IDX),
)?;
let rx_queue = VirtQueue::new(
&mut transport,
RX_QUEUE_IDX,
negotiated_features.contains(Feature::RING_INDIRECT_DESC),
negotiated_features.contains(Feature::RING_EVENT_IDX),
)?;
let config_ptr = transport.config_space::<VirtIOSoundConfig>()?;
let (jacks, streams, chmaps) = unsafe {
(
volread!(config_ptr, jacks),
volread!(config_ptr, streams),
volread!(config_ptr, chmaps),
)
};
info!(
"[sound device] config: jacks: {}, streams: {}, chmaps: {}",
jacks, streams, chmaps
);
let queue_buf_send = FromZeroes::new_box_slice_zeroed(PAGE_SIZE);
let queue_buf_recv = FromZeroes::new_box_slice_zeroed(PAGE_SIZE);
let mut pcm_parameters = vec![];
for _ in 0..streams {
pcm_parameters.push(PcmParameters::default());
}
transport.finish_init();
if event_queue.should_notify() {
transport.notify(EVENT_QUEUE_IDX);
}
Ok(VirtIOSound {
transport,
control_queue,
event_queue,
tx_queue,
rx_queue,
negotiated_features,
jacks,
streams,
chmaps,
pcm_infos: None,
jack_infos: None,
chmap_infos: None,
queue_buf_send,
queue_buf_recv,
pcm_parameters,
set_up: false,
token_rsp: BTreeMap::new(),
pcm_states: vec![],
token_buf: BTreeMap::new(),
})
}
pub fn jacks(&self) -> u32 {
self.jacks
}
pub fn streams(&self) -> u32 {
self.streams
}
pub fn chmaps(&self) -> u32 {
self.chmaps
}
pub fn ack_interrupt(&mut self) -> bool {
self.transport.ack_interrupt()
}
fn request<Req: AsBytes>(&mut self, req: Req) -> Result<VirtIOSndHdr> {
self.control_queue.add_notify_wait_pop(
&[req.as_bytes()],
&mut [self.queue_buf_recv.as_bytes_mut()],
&mut self.transport,
)?;
Ok(VirtIOSndHdr::read_from_prefix(&self.queue_buf_recv).unwrap())
}
fn set_up(&mut self) -> Result<()> {
if let Ok(jack_infos) = self.jack_info(0, self.jacks) {
for jack_info in &jack_infos {
info!("[sound device] jack_info: {}", jack_info);
}
self.jack_infos = Some(jack_infos);
} else {
self.jack_infos = Some(vec![]);
warn!("[sound device] Error getting jack infos");
}
let pcm_infos = self.pcm_info(0, self.streams)?;
for pcm_info in &pcm_infos {
info!("[sound device] pcm_info: {}", pcm_info);
}
self.pcm_infos = Some(pcm_infos);
if let Ok(chmap_infos) = self.chmap_info(0, self.chmaps) {
for chmap_info in &chmap_infos {
info!("[sound device] chmap_info: {}", chmap_info);
}
self.chmap_infos = Some(chmap_infos);
} else {
self.chmap_infos = Some(vec![]);
warn!("[sound device] Error getting chmap infos");
}
for _ in 0..self.streams {
self.pcm_states.push(PCMState::default());
}
Ok(())
}
pub fn enable_interrupts(&mut self, enable: bool) {
self.event_queue.set_dev_notify(enable);
}
fn jack_info(&mut self, jack_start_id: u32, jack_count: u32) -> Result<Vec<VirtIOSndJackInfo>> {
if jack_start_id + jack_count > self.jacks {
error!("jack_start_id + jack_count > jacks! There are not enough jacks to be queried!");
return Err(Error::IoError);
}
let hdr = self.request(VirtIOSndQueryInfo {
hdr: ItemInformationRequestType::RJackInfo.into(),
start_id: jack_start_id,
count: jack_count,
size: size_of::<VirtIOSndJackInfo>() as u32,
})?;
if hdr != RequestStatusCode::Ok.into() {
return Err(Error::IoError);
}
let mut jack_infos = vec![];
for i in 0..jack_count as usize {
const HDR_SIZE: usize = size_of::<VirtIOSndHdr>();
const JACK_INFO_SIZE: usize = size_of::<VirtIOSndJackInfo>();
let start_byte_idx = HDR_SIZE + i * JACK_INFO_SIZE;
let end_byte_idx = HDR_SIZE + (i + 1) * JACK_INFO_SIZE;
let jack_info =
VirtIOSndJackInfo::read_from(&self.queue_buf_recv[start_byte_idx..end_byte_idx])
.unwrap();
jack_infos.push(jack_info)
}
Ok(jack_infos)
}
fn pcm_info(
&mut self,
stream_start_id: u32,
stream_count: u32,
) -> Result<Vec<VirtIOSndPcmInfo>> {
if stream_start_id + stream_count > self.streams {
error!("stream_start_id + stream_count > streams! There are not enough streams to be queried!");
return Err(Error::IoError);
}
let request_hdr = VirtIOSndHdr::from(ItemInformationRequestType::RPcmInfo);
let hdr = self.request(VirtIOSndQueryInfo {
hdr: request_hdr,
start_id: stream_start_id,
count: stream_count,
size: size_of::<VirtIOSndPcmInfo>() as u32,
})?;
if hdr != RequestStatusCode::Ok.into() {
return Err(Error::IoError);
}
let mut pcm_infos = vec![];
for i in 0..stream_count as usize {
const HDR_SIZE: usize = size_of::<VirtIOSndHdr>();
const PCM_INFO_SIZE: usize = size_of::<VirtIOSndPcmInfo>();
let start_byte_idx = HDR_SIZE + i * PCM_INFO_SIZE;
let end_byte_idx = HDR_SIZE + (i + 1) * PCM_INFO_SIZE;
let pcm_info =
VirtIOSndPcmInfo::read_from(&self.queue_buf_recv[start_byte_idx..end_byte_idx])
.unwrap();
pcm_infos.push(pcm_info);
}
Ok(pcm_infos)
}
fn chmap_info(
&mut self,
chmaps_start_id: u32,
chmaps_count: u32,
) -> Result<Vec<VirtIOSndChmapInfo>> {
if chmaps_start_id + chmaps_count > self.chmaps {
error!("chmaps_start_id + chmaps_count > self.chmaps");
return Err(Error::IoError);
}
let hdr = self.request(VirtIOSndQueryInfo {
hdr: ItemInformationRequestType::RChmapInfo.into(),
start_id: chmaps_start_id,
count: chmaps_count,
size: size_of::<VirtIOSndChmapInfo>() as u32,
})?;
if hdr != RequestStatusCode::Ok.into() {
return Err(Error::IoError);
}
let mut chmap_infos = vec![];
for i in 0..chmaps_count as usize {
const OFFSET: usize = size_of::<VirtIOSndHdr>();
let start_byte = OFFSET + i * size_of::<VirtIOSndChmapInfo>();
let end_byte = OFFSET + (i + 1) * size_of::<VirtIOSndChmapInfo>();
let chmap_info =
VirtIOSndChmapInfo::read_from(&self.queue_buf_recv[start_byte..end_byte]).unwrap();
chmap_infos.push(chmap_info);
}
Ok(chmap_infos)
}
pub fn jack_remap(&mut self, jack_id: u32, association: u32, sequence: u32) -> Result {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
if self.jacks == 0 {
error!("[sound device] There is no available jacks!");
return Err(Error::InvalidParam);
}
if jack_id >= self.jacks {
error!("jack_id >= self.jacks! Make sure jack_id is in the range of [0, jacks - 1)!");
return Err(Error::InvalidParam);
}
let jack_features = JackFeatures::from_bits_retain(
self.jack_infos
.as_ref()
.unwrap()
.get(jack_id as usize)
.unwrap()
.features,
);
if !jack_features.contains(JackFeatures::REMAP) {
error!("The jack selected does not support VIRTIO_SND_JACK_F_REMAP!");
return Err(Error::Unsupported);
}
let hdr = self.request(VirtIOSndJackRemap {
hdr: VirtIOSndJackHdr {
hdr: CommandCode::RJackRemap.into(),
jack_id,
},
association,
sequence,
})?;
if hdr == RequestStatusCode::Ok.into() {
Ok(())
} else {
Err(Error::Unsupported)
}
}
pub fn pcm_set_params(
&mut self,
stream_id: u32,
buffer_bytes: u32,
period_bytes: u32,
features: PcmFeatures,
channels: u8,
format: PcmFormat,
rate: PcmRate,
) -> Result {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
if period_bytes == 0 || period_bytes > buffer_bytes || buffer_bytes % period_bytes != 0 {
return Err(Error::InvalidParam);
}
let request_hdr = VirtIOSndHdr::from(CommandCode::RPcmSetParams);
let rsp = self.request(VirtIOSndPcmSetParams {
hdr: VirtIOSndPcmHdr {
hdr: request_hdr,
stream_id,
},
buffer_bytes,
period_bytes,
features: features.bits(),
channels,
format: format.into(),
rate: rate.into(),
_padding: 0,
})?;
if rsp == VirtIOSndHdr::from(RequestStatusCode::Ok) {
self.pcm_parameters[stream_id as usize] = PcmParameters {
setup: true,
buffer_bytes,
period_bytes,
features,
channels,
format,
rate,
};
Ok(())
} else {
Err(Error::IoError)
}
}
pub fn pcm_prepare(&mut self, stream_id: u32) -> Result {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
let request_hdr = VirtIOSndHdr::from(CommandCode::RPcmPrepare);
let rsp = self.request(VirtIOSndPcmHdr {
hdr: request_hdr,
stream_id,
})?;
if rsp == VirtIOSndHdr::from(RequestStatusCode::Ok) {
Ok(())
} else {
Err(Error::IoError)
}
}
pub fn pcm_release(&mut self, stream_id: u32) -> Result {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
let request_hdr = VirtIOSndHdr::from(CommandCode::RPcmRelease);
let rsp = self.request(VirtIOSndPcmHdr {
hdr: request_hdr,
stream_id,
})?;
if rsp == VirtIOSndHdr::from(RequestStatusCode::Ok) {
Ok(())
} else {
Err(Error::IoError)
}
}
pub fn pcm_start(&mut self, stream_id: u32) -> Result {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
let request_hdr = VirtIOSndHdr::from(CommandCode::RPcmStart);
let rsp = self.request(VirtIOSndPcmHdr {
hdr: request_hdr,
stream_id,
})?;
if rsp == VirtIOSndHdr::from(RequestStatusCode::Ok) {
Ok(())
} else {
Err(Error::IoError)
}
}
pub fn pcm_stop(&mut self, stream_id: u32) -> Result {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
let request_hdr = VirtIOSndHdr::from(CommandCode::RPcmStop);
let rsp = self.request(VirtIOSndPcmHdr {
hdr: request_hdr,
stream_id,
})?;
if rsp == VirtIOSndHdr::from(RequestStatusCode::Ok) {
Ok(())
} else {
Err(Error::IoError)
}
}
pub fn pcm_xfer(&mut self, stream_id: u32, frames: &[u8]) -> Result {
const U32_SIZE: usize = size_of::<u32>();
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
if !self.pcm_parameters[stream_id as usize].setup {
warn!("Please set parameters for a stream before using it!");
return Err(Error::IoError);
}
let stream_id_bytes = stream_id.to_le_bytes();
let period_size = self.pcm_parameters[stream_id as usize].period_bytes as usize;
let mut remaining_buffers = frames.chunks(period_size);
let mut buffers: [Option<&[u8]>; QUEUE_SIZE as usize] = [None; QUEUE_SIZE as usize];
let mut statuses: [VirtIOSndPcmStatus; QUEUE_SIZE as usize] =
array::from_fn(|_| Default::default());
let mut tokens = [0; QUEUE_SIZE as usize];
let mut head = 0;
let mut tail = 0;
loop {
if self.tx_queue.available_desc() >= 3 {
if let Some(buffer) = remaining_buffers.next() {
tokens[head] = unsafe {
self.tx_queue.add(
&[&stream_id_bytes, buffer],
&mut [statuses[head].as_bytes_mut()],
)?
};
if self.tx_queue.should_notify() {
self.transport.notify(TX_QUEUE_IDX);
}
buffers[head] = Some(buffer);
head += 1;
if head >= usize::from(QUEUE_SIZE) {
head = 0;
}
} else if head == tail {
break;
}
}
if self.tx_queue.can_pop() {
unsafe {
self.tx_queue.pop_used(
tokens[tail],
&[&stream_id_bytes, buffers[tail].unwrap()],
&mut [statuses[tail].as_bytes_mut()],
)?;
}
if statuses[tail].status != CommandCode::SOk.into() {
return Err(Error::IoError);
}
tail += 1;
if tail >= usize::from(QUEUE_SIZE) {
tail = 0;
}
}
spin_loop();
}
Ok(())
}
pub fn pcm_xfer_nb(&mut self, stream_id: u32, frames: &[u8]) -> Result<u16> {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
if !self.pcm_parameters[stream_id as usize].setup {
warn!("Please set parameters for a stream before using it!");
return Err(Error::IoError);
}
const U32_SIZE: usize = size_of::<u32>();
let period_size: usize = self.pcm_parameters[stream_id as usize].period_bytes as usize;
assert_eq!(period_size, frames.len());
let mut buf = vec![0; U32_SIZE + period_size];
buf[..U32_SIZE].copy_from_slice(&stream_id.to_le_bytes());
buf[U32_SIZE..U32_SIZE + period_size].copy_from_slice(frames);
let mut rsp = VirtIOSndPcmStatus::new_box_zeroed();
let token = unsafe { self.tx_queue.add(&[&buf], &mut [rsp.as_bytes_mut()])? };
if self.tx_queue.should_notify() {
self.transport.notify(TX_QUEUE_IDX);
}
self.token_buf.insert(token, buf);
self.token_rsp.insert(token, rsp);
Ok(token)
}
pub fn pcm_xfer_ok(&mut self, token: u16) -> Result {
assert!(self.token_buf.contains_key(&token));
assert!(self.token_rsp.contains_key(&token));
unsafe {
self.tx_queue.pop_used(
token,
&[&self.token_buf[&token]],
&mut [self.token_rsp.get_mut(&token).unwrap().as_bytes_mut()],
)?;
}
self.token_buf.remove(&token);
self.token_rsp.remove(&token);
Ok(())
}
pub fn output_streams(&mut self) -> Result<Vec<u32>> {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
Ok(self
.pcm_infos
.as_ref()
.unwrap()
.iter()
.enumerate()
.filter(|(_, info)| info.direction == VIRTIO_SND_D_OUTPUT)
.map(|(idx, _)| idx as u32)
.collect())
}
pub fn input_streams(&mut self) -> Result<Vec<u32>> {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
Ok(self
.pcm_infos
.as_ref()
.unwrap()
.iter()
.enumerate()
.filter(|(_, info)| info.direction == VIRTIO_SND_D_INPUT)
.map(|(idx, _)| idx as u32)
.collect())
}
pub fn rates_supported(&mut self, stream_id: u32) -> Result<PcmRates> {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
if stream_id >= self.pcm_infos.as_ref().unwrap().len() as u32 {
return Err(Error::InvalidParam);
}
Ok(PcmRates::from_bits_retain(
self.pcm_infos.as_ref().unwrap()[stream_id as usize].rates,
))
}
pub fn formats_supported(&mut self, stream_id: u32) -> Result<PcmFormats> {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
if stream_id >= self.pcm_infos.as_ref().unwrap().len() as u32 {
return Err(Error::InvalidParam);
}
Ok(PcmFormats::from_bits_retain(
self.pcm_infos.as_ref().unwrap()[stream_id as usize].formats,
))
}
pub fn channel_range_supported(&mut self, stream_id: u32) -> Result<RangeInclusive<u8>> {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
if stream_id >= self.pcm_infos.as_ref().unwrap().len() as u32 {
return Err(Error::InvalidParam);
}
let pcm_info = &self.pcm_infos.as_ref().unwrap()[stream_id as usize];
Ok(pcm_info.channels_min..=pcm_info.channels_max)
}
pub fn features_supported(&mut self, stream_id: u32) -> Result<PcmFeatures> {
if !self.set_up {
self.set_up()?;
self.set_up = true;
}
if stream_id >= self.pcm_infos.as_ref().unwrap().len() as u32 {
return Err(Error::InvalidParam);
}
let pcm_info = &self.pcm_infos.as_ref().unwrap()[stream_id as usize];
Ok(PcmFeatures::from_bits_retain(pcm_info.features))
}
pub fn latest_notification(&mut self) -> Result<Option<Notification>> {
self.event_queue.poll(&mut self.transport, |buffer| {
if let Some(event) = VirtIOSndEvent::read_from(buffer) {
Ok(Some(Notification {
notification_type: NotificationType::n(event.hdr.command_code)
.ok_or(Error::IoError)?,
data: event.data,
}))
} else {
Ok(None)
}
})
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
enum PCMState {
#[default]
SetParams,
Prepare,
Release,
Start,
Stop,
}
const QUEUE_SIZE: u16 = 32;
const CONTROL_QUEUE_IDX: u16 = 0;
const EVENT_QUEUE_IDX: u16 = 1;
const TX_QUEUE_IDX: u16 = 2;
const RX_QUEUE_IDX: u16 = 3;
const SUPPORTED_FEATURES: Feature = Feature::RING_INDIRECT_DESC.union(Feature::RING_EVENT_IDX);
bitflags! {
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[repr(transparent)]
struct JackFeatures: u32 {
const REMAP = 1 << 0;
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[repr(transparent)]
pub struct PcmFeatures: u32 {
const SHMEM_HOST = 1 << 0;
const SHMEM_GUEST = 1 << 1;
const MSG_POLLING = 1 << 2;
const EVT_SHMEM_PERIODS = 1 << 3;
const EVT_XRUNS = 1 << 4;
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[repr(transparent)]
pub struct PcmFormats: u64 {
const IMA_ADPCM = 1 << 0;
const MU_LAW = 1 << 1;
const A_LAW = 1 << 2;
const S8 = 1 << 3;
const U8 = 1 << 4;
const S16 = 1 << 5;
const U16 = 1 << 6;
const S18_3 = 1 << 7;
const U18_3 = 1 << 8;
const S20_3 = 1 << 9;
const U20_3 = 1 << 10;
const S24_3 = 1 << 11;
const U24_3 = 1 << 12;
const S20 = 1 << 13;
const U20 = 1 << 14;
const S24 = 1 << 15;
const U24 = 1 << 16;
const S32 = 1 << 17;
const U32 = 1 << 18;
const FLOAT = 1 << 19;
const FLOAT64 = 1 << 20;
const DSD_U8 = 1 << 21;
const DSD_U16 = 1 << 22;
const DSD_U32 = 1 << 23;
const IEC958_SUBFRAME = 1 << 24;
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[repr(u8)]
pub enum PcmFormat {
#[default]
ImaAdpcm = 0,
MuLaw = 1,
ALaw = 2,
S8 = 3,
U8 = 4,
S16 = 5,
U16 = 6,
S18_3 = 7,
U18_3 = 8,
S20_3 = 9,
U20_3 = 10,
S24_3 = 11,
U24_3 = 12,
S20 = 13,
U20 = 14,
S24 = 15,
U24 = 16,
S32 = 17,
U32 = 18,
FLOAT = 19,
FLOAT64 = 20,
DsdU8 = 21,
DsdU16 = 22,
DsdU32 = 23,
Iec958Subframe = 24,
}
impl From<PcmFormat> for PcmFormats {
fn from(format: PcmFormat) -> Self {
match format {
PcmFormat::ImaAdpcm => PcmFormats::IMA_ADPCM,
PcmFormat::MuLaw => PcmFormats::MU_LAW,
PcmFormat::ALaw => PcmFormats::A_LAW,
PcmFormat::S8 => PcmFormats::S8,
PcmFormat::U8 => PcmFormats::U8,
PcmFormat::S16 => PcmFormats::S16,
PcmFormat::U16 => PcmFormats::U16,
PcmFormat::S18_3 => PcmFormats::S18_3,
PcmFormat::U18_3 => PcmFormats::U18_3,
PcmFormat::S20_3 => PcmFormats::S20_3,
PcmFormat::U20_3 => PcmFormats::U20_3,
PcmFormat::S24_3 => PcmFormats::S24_3,
PcmFormat::U24_3 => PcmFormats::U24_3,
PcmFormat::S20 => PcmFormats::S20,
PcmFormat::U20 => PcmFormats::U20,
PcmFormat::S24 => PcmFormats::S24,
PcmFormat::U24 => PcmFormats::U24,
PcmFormat::S32 => PcmFormats::S32,
PcmFormat::U32 => PcmFormats::U32,
PcmFormat::FLOAT => PcmFormats::FLOAT,
PcmFormat::FLOAT64 => PcmFormats::FLOAT64,
PcmFormat::DsdU8 => PcmFormats::DSD_U8,
PcmFormat::DsdU16 => PcmFormats::DSD_U16,
PcmFormat::DsdU32 => PcmFormats::DSD_U32,
PcmFormat::Iec958Subframe => PcmFormats::IEC958_SUBFRAME,
}
}
}
impl From<PcmFormat> for u8 {
fn from(format: PcmFormat) -> u8 {
format as _
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[repr(transparent)]
pub struct PcmRates: u64 {
const RATE_5512 = 1 << 0;
const RATE_8000 = 1 << 1;
const RATE_11025 = 1 << 2;
const RATE_16000 = 1 << 3;
const RATE_22050 = 1 << 4;
const RATE_32000 = 1 << 5;
const RATE_44100 = 1 << 6;
const RATE_48000 = 1 << 7;
const RATE_64000 = 1 << 8;
const RATE_88200 = 1 << 9;
const RATE_96000 = 1 << 10;
const RATE_176400 = 1 << 11;
const RATE_192000 = 1 << 12;
const RATE_384000 = 1 << 13;
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[repr(u8)]
pub enum PcmRate {
#[default]
Rate5512 = 0,
Rate8000 = 1,
Rate11025 = 2,
Rate16000 = 3,
Rate22050 = 4,
Rate32000 = 5,
Rate44100 = 6,
Rate48000 = 7,
Rate64000 = 8,
Rate88200 = 9,
Rate96000 = 10,
Rate176400 = 11,
Rate192000 = 12,
Rate384000 = 13,
}
impl From<PcmRate> for PcmRates {
fn from(rate: PcmRate) -> Self {
match rate {
PcmRate::Rate5512 => Self::RATE_5512,
PcmRate::Rate8000 => Self::RATE_8000,
PcmRate::Rate11025 => Self::RATE_11025,
PcmRate::Rate16000 => Self::RATE_16000,
PcmRate::Rate22050 => Self::RATE_22050,
PcmRate::Rate32000 => Self::RATE_32000,
PcmRate::Rate44100 => Self::RATE_44100,
PcmRate::Rate48000 => Self::RATE_48000,
PcmRate::Rate64000 => Self::RATE_64000,
PcmRate::Rate88200 => Self::RATE_88200,
PcmRate::Rate96000 => Self::RATE_96000,
PcmRate::Rate176400 => Self::RATE_176400,
PcmRate::Rate192000 => Self::RATE_192000,
PcmRate::Rate384000 => Self::RATE_384000,
}
}
}
impl From<PcmRate> for u8 {
fn from(rate: PcmRate) -> Self {
rate as _
}
}
#[repr(C)]
struct VirtIOSoundConfig {
jacks: ReadOnly<u32>,
streams: ReadOnly<u32>,
chmaps: ReadOnly<u32>,
}
#[repr(u32)]
#[derive(Copy, Clone, Debug, Eq, N, PartialEq)]
enum CommandCode {
RJackInfo = 1,
RJackRemap,
RPcmInfo = 0x0100,
RPcmSetParams,
RPcmPrepare,
RPcmRelease,
RPcmStart,
RPcmStop,
RChmapInfo = 0x0200,
EvtJackConnected = 0x1000,
EvtJackDisconnected,
EvtPcmPeriodElapsed = 0x1100,
EvtPcmXrun,
SOk = 0x8000,
SBadMsg,
SNotSupp,
SIoErr,
}
impl From<CommandCode> for u32 {
fn from(code: CommandCode) -> u32 {
code as u32
}
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ItemInformationRequestType {
RJackInfo = 1,
RPcmInfo = 0x0100,
RChmapInfo = 0x0200,
}
impl From<ItemInformationRequestType> for VirtIOSndHdr {
fn from(value: ItemInformationRequestType) -> Self {
VirtIOSndHdr {
command_code: value.into(),
}
}
}
impl From<ItemInformationRequestType> for u32 {
fn from(request_type: ItemInformationRequestType) -> u32 {
request_type as _
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
#[repr(u32)]
enum RequestStatusCode {
Ok = 0x8000,
BadMsg,
NotSupp,
IoErr,
}
impl From<RequestStatusCode> for VirtIOSndHdr {
fn from(value: RequestStatusCode) -> Self {
VirtIOSndHdr {
command_code: value as _,
}
}
}
#[repr(C)]
#[derive(AsBytes, Clone, Debug, Eq, FromBytes, FromZeroes, PartialEq)]
struct VirtIOSndHdr {
command_code: u32,
}
impl From<CommandCode> for VirtIOSndHdr {
fn from(value: CommandCode) -> Self {
VirtIOSndHdr {
command_code: value.into(),
}
}
}
#[repr(C)]
#[derive(FromBytes, FromZeroes)]
struct VirtIOSndEvent {
hdr: VirtIOSndHdr,
data: u32,
}
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum NotificationType {
JackConnected = 0x1000,
JackDisconnected,
PcmPeriodElapsed = 0x1100,
PcmXrun,
}
impl NotificationType {
fn n(value: u32) -> Option<Self> {
match value {
0x1100 => Some(Self::PcmPeriodElapsed),
0x1101 => Some(Self::PcmXrun),
0x1000 => Some(Self::JackConnected),
0x1001 => Some(Self::JackDisconnected),
_ => None,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Notification {
notification_type: NotificationType,
data: u32,
}
impl Notification {
pub fn data(&self) -> u32 {
self.data
}
pub fn notification_type(&self) -> NotificationType {
self.notification_type
}
}
const VIRTIO_SND_D_OUTPUT: u8 = 0;
const VIRTIO_SND_D_INPUT: u8 = 1;
#[repr(C)]
#[derive(AsBytes, Debug, FromBytes, FromZeroes)]
struct VirtIOSndQueryInfo {
hdr: VirtIOSndHdr,
start_id: u32,
count: u32,
size: u32,
}
#[repr(C)]
#[derive(AsBytes, Debug, FromBytes, FromZeroes)]
struct VirtIOSndQueryInfoRsp {
hdr: VirtIOSndHdr,
info: VirtIOSndInfo,
}
#[repr(C)]
#[derive(AsBytes, Clone, Debug, Eq, FromBytes, FromZeroes, PartialEq)]
pub struct VirtIOSndInfo {
hda_fn_nid: u32,
}
#[repr(C)]
#[derive(AsBytes, Clone, Debug, FromBytes, FromZeroes)]
struct VirtIOSndJackHdr {
hdr: VirtIOSndHdr,
jack_id: u32,
}
#[repr(C)]
#[derive(AsBytes, Clone, Eq, FromBytes, FromZeroes, PartialEq)]
pub struct VirtIOSndJackInfo {
hdr: VirtIOSndInfo,
features: u32,
hda_reg_defconf: u32,
hda_reg_caps: u32,
connected: u8,
_padding: [u8; 7],
}
impl Debug for VirtIOSndJackInfo {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("VirtIOSndJackInfo")
.field("hdr", &self.hdr)
.field("features", &JackFeatures::from_bits_retain(self.features))
.field("hda_reg_defconf", &self.hda_reg_defconf)
.field("hda_reg_caps", &self.hda_reg_caps)
.field("connected", &self.connected)
.field("_padding", &self._padding)
.finish()
}
}
impl Display for VirtIOSndJackInfo {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let connected_status = if self.connected == 1 {
"CONNECTED"
} else {
"DISCONNECTED"
};
write!(
f,
"features: {:?}, hda_reg_defconf: {}, hda_reg_caps: {}, connected: {}",
JackFeatures::from_bits_retain(self.features),
self.hda_reg_defconf,
self.hda_reg_caps,
connected_status
)
}
}
#[repr(C)]
#[derive(AsBytes, FromBytes, FromZeroes)]
struct VirtIOSndJackInfoRsp {
hdr: VirtIOSndHdr,
body: VirtIOSndJackInfo,
}
#[repr(C)]
#[derive(AsBytes, FromBytes, FromZeroes)]
struct VirtIOSndJackRemap {
hdr: VirtIOSndJackHdr,
association: u32,
sequence: u32,
}
#[repr(C)]
#[derive(AsBytes, Debug, Eq, FromBytes, FromZeroes, PartialEq)]
struct VirtIOSndPcmHdr {
hdr: VirtIOSndHdr,
stream_id: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum PcmStreamFeatures {
ShmemHost = 0,
ShmemGuest,
MsgPolling,
EvtShmemPeriods,
EvtXruns,
}
impl From<PcmStreamFeatures> for u32 {
fn from(value: PcmStreamFeatures) -> Self {
value as _
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u64)]
enum PcmSampleFormat {
ImaAdpcm = 0, MuLaw, ALaw, S8, U8, S16, U16, S18_3, U18_3, S20_3, U20_3, S24_3, U24_3, S20, U20, S24, U24, S32, U32, Float, Float64, DsdU8, DsdU16, DsdU32, Iec958Subframe, }
impl From<PcmSampleFormat> for u64 {
fn from(value: PcmSampleFormat) -> Self {
value as _
}
}
#[repr(C)]
#[derive(AsBytes, Clone, Eq, FromBytes, FromZeroes, PartialEq)]
pub struct VirtIOSndPcmInfo {
hdr: VirtIOSndInfo,
features: u32, formats: u64, rates: u64, direction: u8,
channels_min: u8,
channels_max: u8,
_padding: [u8; 5],
}
impl Debug for VirtIOSndPcmInfo {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("VirtIOSndPcmInfo")
.field("hdr", &self.hdr)
.field("features", &PcmFeatures::from_bits_retain(self.features))
.field("formats", &PcmFormats::from_bits_retain(self.formats))
.field("rates", &PcmRates::from_bits_retain(self.rates))
.field("direction", &self.direction)
.field("channels_min", &self.channels_min)
.field("channels_max", &self.channels_max)
.field("_padding", &self._padding)
.finish()
}
}
impl Display for VirtIOSndPcmInfo {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let direction = if self.direction == VIRTIO_SND_D_INPUT {
"INPUT"
} else {
"OUTPUT"
};
write!(
f,
"features: {:?}, rates: {:?}, formats: {:?}, direction: {}",
PcmFeatures::from_bits_retain(self.features),
PcmRates::from_bits_retain(self.rates),
PcmFormats::from_bits_retain(self.formats),
direction
)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct PcmParameters {
setup: bool,
buffer_bytes: u32,
period_bytes: u32,
features: PcmFeatures,
channels: u8,
format: PcmFormat,
rate: PcmRate,
}
#[repr(C)]
#[derive(AsBytes, Debug, Eq, FromBytes, FromZeroes, PartialEq)]
struct VirtIOSndPcmSetParams {
hdr: VirtIOSndPcmHdr, buffer_bytes: u32,
period_bytes: u32,
features: u32, channels: u8,
format: u8,
rate: u8,
_padding: u8,
}
#[repr(C)]
#[derive(AsBytes, FromBytes, FromZeroes)]
struct VirtIOSndPcmXfer {
stream_id: u32,
}
#[repr(C)]
#[derive(AsBytes, Default, FromBytes, FromZeroes)]
struct VirtIOSndPcmStatus {
status: u32,
latency_bytes: u32,
}
#[derive(Copy, Clone, Debug, Eq, N, PartialEq)]
#[repr(u8)]
enum ChannelPosition {
None = 0,
Na,
Mono,
Fl,
Fr,
Rl,
Rr,
Fc,
Lfe,
Sl,
Sr,
Rc,
Flc,
Frc,
Rlc,
Rrc,
Flw,
Frw,
Flh,
Fch,
Frh,
Tc,
Tfl,
Tfr,
Tfc,
Trl,
Trr,
Trc,
Tflc,
Tfrc,
Tsl,
Tsr,
Llfe,
Rlfe,
Bc,
Blc,
Brc,
}
const VIRTIO_SND_CHMAP_MAX_SIZE: usize = 18;
#[repr(C)]
#[derive(AsBytes, Clone, Debug, FromBytes, FromZeroes)]
struct VirtIOSndChmapInfo {
hdr: VirtIOSndInfo,
direction: u8,
channels: u8,
positions: [u8; VIRTIO_SND_CHMAP_MAX_SIZE],
}
impl Display for VirtIOSndChmapInfo {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let direction = if self.direction == VIRTIO_SND_D_INPUT {
"INPUT"
} else {
"OUTPUT"
};
write!(
f,
"direction: {}, channels: {}, postions: [",
direction, self.channels
)?;
for i in 0..usize::from(self.channels) {
if i != 0 {
write!(f, ", ")?;
}
if let Some(position) = ChannelPosition::n(self.positions[i]) {
write!(f, "{:?}", position)?;
} else {
write!(f, "{}", self.positions[i])?;
}
}
write!(f, "]")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
hal::fake::FakeHal,
transport::{
fake::{FakeTransport, QueueStatus, State},
DeviceType,
},
volatile::ReadOnly,
};
use alloc::{sync::Arc, vec};
use core::ptr::NonNull;
use fake::FakeSoundDevice;
use std::sync::Mutex;
#[test]
fn config() {
let mut config_space = VirtIOSoundConfig {
jacks: ReadOnly::new(3),
streams: ReadOnly::new(4),
chmaps: ReadOnly::new(2),
};
let state = Arc::new(Mutex::new(State {
queues: vec![
QueueStatus::default(),
QueueStatus::default(),
QueueStatus::default(),
QueueStatus::default(),
],
..Default::default()
}));
let transport = FakeTransport {
device_type: DeviceType::Sound,
max_queue_size: 32,
device_features: 0,
config_space: NonNull::from(&mut config_space),
state: state.clone(),
};
let sound =
VirtIOSound::<FakeHal, FakeTransport<VirtIOSoundConfig>>::new(transport).unwrap();
assert_eq!(sound.jacks(), 3);
assert_eq!(sound.streams(), 4);
assert_eq!(sound.chmaps(), 2);
}
#[test]
fn empty_info() {
let (fake, transport) = FakeSoundDevice::new(vec![], vec![], vec![]);
let mut sound =
VirtIOSound::<FakeHal, FakeTransport<VirtIOSoundConfig>>::new(transport).unwrap();
let handle = fake.spawn();
assert_eq!(sound.jacks(), 0);
assert_eq!(sound.streams(), 0);
assert_eq!(sound.chmaps(), 0);
assert_eq!(sound.output_streams().unwrap(), vec![]);
assert_eq!(sound.input_streams().unwrap(), vec![]);
fake.terminate();
handle.join().unwrap();
}
#[test]
fn stream_info() {
let (fake, transport) = FakeSoundDevice::new(
vec![VirtIOSndJackInfo {
hdr: VirtIOSndInfo { hda_fn_nid: 0 },
features: 0,
hda_reg_defconf: 0,
hda_reg_caps: 0,
connected: 0,
_padding: Default::default(),
}],
vec![
VirtIOSndPcmInfo {
hdr: VirtIOSndInfo { hda_fn_nid: 0 },
features: 0,
formats: (PcmFormats::U8 | PcmFormats::U32).bits(),
rates: (PcmRates::RATE_44100 | PcmRates::RATE_32000).bits(),
direction: VIRTIO_SND_D_OUTPUT,
channels_min: 1,
channels_max: 2,
_padding: Default::default(),
},
VirtIOSndPcmInfo {
hdr: VirtIOSndInfo { hda_fn_nid: 0 },
features: 0,
formats: 0,
rates: 0,
direction: VIRTIO_SND_D_INPUT,
channels_min: 0,
channels_max: 0,
_padding: Default::default(),
},
],
vec![VirtIOSndChmapInfo {
hdr: VirtIOSndInfo { hda_fn_nid: 0 },
direction: 0,
channels: 0,
positions: [0; 18],
}],
);
let mut sound =
VirtIOSound::<FakeHal, FakeTransport<VirtIOSoundConfig>>::new(transport).unwrap();
let handle = fake.spawn();
assert_eq!(sound.output_streams().unwrap(), vec![0]);
assert_eq!(
sound.rates_supported(0).unwrap(),
PcmRates::RATE_44100 | PcmRates::RATE_32000
);
assert_eq!(
sound.formats_supported(0).unwrap(),
PcmFormats::U8 | PcmFormats::U32
);
assert_eq!(sound.channel_range_supported(0).unwrap(), 1..=2);
assert_eq!(sound.features_supported(0).unwrap(), PcmFeatures::empty());
assert_eq!(sound.input_streams().unwrap(), vec![1]);
assert_eq!(sound.rates_supported(1).unwrap(), PcmRates::empty());
assert_eq!(sound.formats_supported(1).unwrap(), PcmFormats::empty());
assert_eq!(sound.channel_range_supported(1).unwrap(), 0..=0);
assert_eq!(sound.features_supported(1).unwrap(), PcmFeatures::empty());
fake.terminate();
handle.join().unwrap();
}
#[test]
fn play() {
let (fake, transport) = FakeSoundDevice::new(
vec![],
vec![VirtIOSndPcmInfo {
hdr: VirtIOSndInfo { hda_fn_nid: 0 },
features: 0,
formats: (PcmFormats::U8 | PcmFormats::U32).bits(),
rates: (PcmRates::RATE_44100 | PcmRates::RATE_32000).bits(),
direction: VIRTIO_SND_D_OUTPUT,
channels_min: 1,
channels_max: 2,
_padding: Default::default(),
}],
vec![],
);
let mut sound =
VirtIOSound::<FakeHal, FakeTransport<VirtIOSoundConfig>>::new(transport).unwrap();
let handle = fake.spawn();
assert_eq!(sound.output_streams().unwrap(), vec![0]);
assert_eq!(sound.input_streams().unwrap(), vec![]);
sound
.pcm_set_params(
0,
100,
100,
PcmFeatures::empty(),
1,
PcmFormat::U8,
PcmRate::Rate8000,
)
.unwrap();
assert_eq!(
fake.params.lock().unwrap()[0],
Some(VirtIOSndPcmSetParams {
hdr: VirtIOSndPcmHdr {
hdr: VirtIOSndHdr {
command_code: CommandCode::RPcmSetParams.into(),
},
stream_id: 0,
},
buffer_bytes: 100,
period_bytes: 100,
features: 0,
channels: 1,
format: PcmFormat::U8.into(),
rate: PcmRate::Rate8000.into(),
_padding: Default::default(),
})
);
sound.pcm_prepare(0).unwrap();
sound.pcm_start(0).unwrap();
let mut expected_sound = vec![];
println!("Playing empty");
sound.pcm_xfer(0, &[]).unwrap();
assert_eq!(fake.played_bytes.lock().unwrap()[0], expected_sound);
println!("Playing 100");
sound.pcm_xfer(0, &[42; 100]).unwrap();
expected_sound.extend([42; 100]);
assert_eq!(fake.played_bytes.lock().unwrap()[0], expected_sound);
println!("Playing 200");
sound.pcm_xfer(0, &[66; 200]).unwrap();
expected_sound.extend([66; 200]);
assert_eq!(fake.played_bytes.lock().unwrap()[0], expected_sound);
println!("Playing 50");
sound.pcm_xfer(0, &[55; 50]).unwrap();
expected_sound.extend([55; 50]);
assert_eq!(fake.played_bytes.lock().unwrap()[0], expected_sound);
println!("Playing 5000");
sound.pcm_xfer(0, &[12; 5000]).unwrap();
expected_sound.extend([12; 5000]);
assert_eq!(fake.played_bytes.lock().unwrap()[0], expected_sound);
sound.pcm_stop(0).unwrap();
sound.pcm_release(0).unwrap();
fake.terminate();
handle.join().unwrap();
}
}