Skip to content

Commit

Permalink
Add fake sound device, and test getting stream info.
Browse files Browse the repository at this point in the history
  • Loading branch information
qwandor committed Jul 26, 2024
1 parent f7be927 commit 00095f8
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 7 deletions.
61 changes: 58 additions & 3 deletions src/device/sound.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Driver for VirtIO Sound devices.

#[cfg(test)]
mod fake;

use alloc::vec;
use alloc::{boxed::Box, collections::BTreeMap, vec::Vec};
use bitflags::bitflags;
Expand Down Expand Up @@ -1344,7 +1347,7 @@ struct VirtIOSndJackHdr {

/// Jack infomation.
#[repr(C)]
#[derive(AsBytes, Eq, FromBytes, FromZeroes, PartialEq)]
#[derive(AsBytes, Clone, Eq, FromBytes, FromZeroes, PartialEq)]
pub struct VirtIOSndJackInfo {
hdr: VirtIOSndInfo,
features: u32,
Expand Down Expand Up @@ -1640,7 +1643,7 @@ enum ChannelPosition {
const VIRTIO_SND_CHMAP_MAX_SIZE: usize = 18;

#[repr(C)]
#[derive(Debug, FromBytes, FromZeroes)]
#[derive(AsBytes, Clone, Debug, FromBytes, FromZeroes)]
struct VirtIOSndChmapInfo {
hdr: VirtIOSndInfo,
direction: u8,
Expand Down Expand Up @@ -1684,6 +1687,7 @@ mod tests {
};
use alloc::{sync::Arc, vec};
use core::ptr::NonNull;
use fake::FakeSoundDevice;
use std::sync::Mutex;

#[test]
Expand All @@ -1703,7 +1707,7 @@ mod tests {
..Default::default()
}));
let transport = FakeTransport {
device_type: DeviceType::Socket,
device_type: DeviceType::Sound,
max_queue_size: 32,
device_features: 0,
config_space: NonNull::from(&mut config_space),
Expand All @@ -1715,4 +1719,55 @@ mod tests {
assert_eq!(sound.streams(), 4);
assert_eq!(sound.chmaps(), 2);
}

#[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: 0,
rates: 0,
direction: VIRTIO_SND_D_OUTPUT,
channels_min: 0,
channels_max: 0,
_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(), vec![0]);
assert_eq!(sound.input_streams(), vec![1]);

fake.terminate();
handle.join().unwrap();
}
}
180 changes: 180 additions & 0 deletions src/device/sound/fake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//! Fake VirtIO sound device for tests.

use super::{
CommandCode, VirtIOSndChmapInfo, VirtIOSndHdr, VirtIOSndJackInfo, VirtIOSndPcmInfo,
VirtIOSndQueryInfo, VirtIOSoundConfig, CONTROL_QUEUE_IDX, QUEUE_SIZE,
};
use crate::{
transport::{
fake::{FakeTransport, QueueStatus, State},
DeviceType,
},
volatile::ReadOnly,
};
use alloc::{sync::Arc, vec};
use core::{
convert::{TryFrom, TryInto},
mem::size_of,
ptr::NonNull,
time::Duration,
};
use std::{
sync::{
atomic::{AtomicBool, Ordering},
Mutex,
},
thread::{self, JoinHandle},
};
use zerocopy::{AsBytes, FromBytes};

#[derive(Clone, Debug)]
pub struct FakeSoundDevice {
pub state: Arc<Mutex<State>>,
pub terminate: Arc<AtomicBool>,
pub jack_infos: Vec<VirtIOSndJackInfo>,
pub pcm_infos: Vec<VirtIOSndPcmInfo>,
pub chmap_infos: Vec<VirtIOSndChmapInfo>,
}

impl FakeSoundDevice {
pub fn new(
jack_infos: Vec<VirtIOSndJackInfo>,
pcm_infos: Vec<VirtIOSndPcmInfo>,
chmap_infos: Vec<VirtIOSndChmapInfo>,
) -> (Self, FakeTransport<VirtIOSoundConfig>) {
let mut config_space = VirtIOSoundConfig {
jacks: ReadOnly::new(jack_infos.len().try_into().unwrap()),
streams: ReadOnly::new(pcm_infos.len().try_into().unwrap()),
chmaps: ReadOnly::new(chmap_infos.len().try_into().unwrap()),
};
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::Socket,
max_queue_size: 32,
device_features: 0,
config_space: NonNull::from(&mut config_space),
state: state.clone(),
};

(
Self {
state,
terminate: Arc::new(AtomicBool::new(false)),
jack_infos,
pcm_infos,
chmap_infos,
},
transport,
)
}

/// Start a background thread simulating the device.
pub fn spawn(&self) -> JoinHandle<()> {
let clone = self.clone();
thread::spawn(move || clone.run())
}

/// Terminate the background thread for the device.
pub fn terminate(&self) {
self.terminate.store(true, Ordering::Release);
}

fn run(&self) {
while !self.terminate.load(Ordering::Acquire) {
if State::poll_queue_notified(&self.state, CONTROL_QUEUE_IDX) {
println!("Control queue was notified.");

self.state
.lock()
.unwrap()
.read_write_queue::<{ QUEUE_SIZE as usize }>(CONTROL_QUEUE_IDX, |request| {
self.handle_control_request(&request)
})
} else {
thread::sleep(Duration::from_millis(10));
}
}
}

fn handle_control_request(&self, request: &[u8]) -> Vec<u8> {
{
let header =
VirtIOSndHdr::read_from_prefix(&request).expect("Control request too short");
let mut response = Vec::new();
const RJACKINFO: u32 = CommandCode::RJackInfo as u32;
const RPCMINFO: u32 = CommandCode::RPcmInfo as u32;
const RCHMAPINFO: u32 = CommandCode::RChmapInfo as u32;
match header.command_code {
RJACKINFO => {
let request = VirtIOSndQueryInfo::read_from(&request)
.expect("RJackInfo control request wrong length");
assert_eq!(
request.size,
u32::try_from(size_of::<VirtIOSndJackInfo>()).unwrap()
);
response.extend_from_slice(
VirtIOSndHdr {
command_code: CommandCode::SOk.into(),
}
.as_bytes(),
);
for jack_info in &self.jack_infos[request.start_id as usize
..request.start_id as usize + request.count as usize]
{
response.extend_from_slice(jack_info.as_bytes());
}
}
RPCMINFO => {
let request = VirtIOSndQueryInfo::read_from(&request)
.expect("RPcmInfo control request wrong length");
assert_eq!(
request.size,
u32::try_from(size_of::<VirtIOSndPcmInfo>()).unwrap()
);
response.extend_from_slice(
VirtIOSndHdr {
command_code: CommandCode::SOk.into(),
}
.as_bytes(),
);
for pcm_info in &self.pcm_infos[request.start_id as usize
..request.start_id as usize + request.count as usize]
{
response.extend_from_slice(pcm_info.as_bytes());
}
}
RCHMAPINFO => {
let request = VirtIOSndQueryInfo::read_from(&request)
.expect("RJackInfo control request wrong length");
assert_eq!(
request.size,
u32::try_from(size_of::<VirtIOSndChmapInfo>()).unwrap()
);
response.extend_from_slice(
VirtIOSndHdr {
command_code: CommandCode::SOk.into(),
}
.as_bytes(),
);
for chmap_info in &self.chmap_infos[request.start_id as usize
..request.start_id as usize + request.count as usize]
{
response.extend_from_slice(chmap_info.as_bytes());
}
}
_ => {
panic!("Unexpected control request, header {:?}", header);
}
}
response
}
}
}
15 changes: 11 additions & 4 deletions src/transport/fake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,20 @@ impl State {

/// Waits until the given queue is notified.
pub fn wait_until_queue_notified(state: &Mutex<Self>, queue_index: u16) {
while !state.lock().unwrap().queues[usize::from(queue_index)]
.notified
.swap(false, Ordering::SeqCst)
{
while !Self::poll_queue_notified(state, queue_index) {
thread::sleep(Duration::from_millis(10));
}
}

/// Checks if the given queue has been notified.
///
/// If it has, returns true and resets the status so this will return false until it is notified
/// again.
pub fn poll_queue_notified(state: &Mutex<Self>, queue_index: u16) -> bool {
state.lock().unwrap().queues[usize::from(queue_index)]
.notified
.swap(false, Ordering::SeqCst)
}
}

#[derive(Debug, Default)]
Expand Down

0 comments on commit 00095f8

Please sign in to comment.