1use anyhow::format_err;
6use derivative::Derivative;
7use fidl_fuchsia_hardware_audio::*;
8use fuchsia_async as fasync;
9use fuchsia_sync::Mutex;
10use futures::{Future, StreamExt};
11use log::warn;
12use std::sync::Arc;
13use vfs::directory::entry_container::Directory;
14use vfs::{pseudo_directory, service};
15
16use crate::DigitalAudioInterface;
17use crate::driver::{ensure_dai_format_is_supported, ensure_pcm_format_is_supported};
18
19#[derive(Derivative, Clone, Debug)]
21#[derivative(Default)]
22pub enum TestStatus {
23 #[derivative(Default)]
24 Idle,
25 Configured {
26 dai_format: DaiFormat,
27 pcm_format: PcmFormat,
28 },
29 Started {
30 dai_format: DaiFormat,
31 pcm_format: PcmFormat,
32 },
33}
34
35impl TestStatus {
36 fn start(&mut self) -> Result<(), ()> {
37 if let Self::Configured { dai_format, pcm_format } = self {
38 *self = Self::Started { dai_format: *dai_format, pcm_format: *pcm_format };
39 Ok(())
40 } else {
41 Err(())
42 }
43 }
44
45 fn stop(&mut self) {
46 if let Self::Started { dai_format, pcm_format } = *self {
47 *self = Self::Configured { dai_format, pcm_format };
48 }
49 }
50}
51
52#[derive(Clone)]
53pub struct TestHandle(Arc<Mutex<TestStatus>>);
54
55impl TestHandle {
56 pub fn new() -> Self {
57 Self(Arc::new(Mutex::new(TestStatus::default())))
58 }
59
60 pub fn status(&self) -> TestStatus {
61 self.0.lock().clone()
62 }
63
64 pub fn is_started(&self) -> bool {
65 let lock = self.0.lock();
66 match *lock {
67 TestStatus::Started { .. } => true,
68 _ => false,
69 }
70 }
71
72 fn set_configured(&self, dai_format: DaiFormat, pcm_format: PcmFormat) {
73 let mut lock = self.0.lock();
74 *lock = TestStatus::Configured { dai_format, pcm_format };
75 }
76
77 fn start(&self) -> Result<(), ()> {
78 self.0.lock().start()
79 }
80
81 fn stop(&self) {
82 self.0.lock().stop()
83 }
84}
85
86macro_rules! log_error {
88 ($result:expr, $tag:expr) => {
89 if let Err(e) = $result {
90 warn!("Error sending {}: {:?}", $tag, e);
91 break;
92 }
93 };
94}
95
96async fn handle_ring_buffer(mut requests: RingBufferRequestStream, handle: TestHandle) {
97 while let Some(req) = requests.next().await {
98 if let Err(e) = req {
99 warn!("Error processing RingBuffer request stream: {:?}", e);
100 break;
101 }
102 match req.unwrap() {
103 RingBufferRequest::Start { responder } => match handle.start() {
104 Ok(()) => log_error!(responder.send(0), "ring buffer start response"),
105 Err(()) => {
106 warn!("Started when we couldn't expect it, shutting down");
107 }
108 },
109 RingBufferRequest::Stop { responder } => {
110 handle.stop();
111 log_error!(responder.send(), "ring buffer stop response");
112 }
113 x => unimplemented!("RingBuffer Request not implemented: {:?}", x),
114 };
115 }
116}
117
118async fn test_handle_dai_requests(
119 dai_formats: DaiSupportedFormats,
120 pcm_formats: SupportedFormats,
121 as_input: bool,
122 mut requests: DaiRequestStream,
123 handle: TestHandle,
124) {
125 use std::slice::from_ref;
126 let properties = DaiProperties {
127 is_input: Some(as_input),
128 manufacturer: Some("Fuchsia".to_string()),
129 ..Default::default()
130 };
131
132 #[allow(clippy::collection_is_never_read)]
133 let mut _rb_task = None;
134 while let Some(req) = requests.next().await {
135 if let Err(e) = req {
136 warn!("Error processing DAI request stream: {:?}", e);
137 break;
138 }
139 match req.unwrap() {
140 DaiRequest::GetProperties { responder } => {
141 log_error!(responder.send(&properties), "properties response");
142 }
143 DaiRequest::GetDaiFormats { responder } => {
144 log_error!(responder.send(Ok(from_ref(&dai_formats))), "formats response");
145 }
146 DaiRequest::GetRingBufferFormats { responder } => {
147 log_error!(responder.send(Ok(from_ref(&pcm_formats))), "pcm response");
148 }
149 DaiRequest::CreateRingBuffer {
150 dai_format, ring_buffer_format, ring_buffer, ..
151 } => {
152 let shutdown_bad_args =
153 |server: fidl::endpoints::ServerEnd<RingBufferMarker>, err: anyhow::Error| {
154 warn!("CreateRingBuffer: {:?}", err);
155 if let Err(e) = server.close_with_epitaph(zx::Status::INVALID_ARGS) {
156 warn!("Couldn't send ring buffer epitaph: {:?}", e);
157 }
158 };
159 if let Err(e) = ensure_dai_format_is_supported(from_ref(&dai_formats), &dai_format)
160 {
161 shutdown_bad_args(ring_buffer, e);
162 continue;
163 }
164 let pcm_format = match ring_buffer_format.pcm_format {
165 Some(f) => f,
166 None => {
167 shutdown_bad_args(ring_buffer, format_err!("Only PCM format supported"));
168 continue;
169 }
170 };
171 if let Err(e) = ensure_pcm_format_is_supported(from_ref(&pcm_formats), &pcm_format)
172 {
173 shutdown_bad_args(ring_buffer, e);
174 continue;
175 }
176 handle.set_configured(dai_format, pcm_format);
177 let requests = ring_buffer.into_stream();
178 _rb_task = Some(fasync::Task::spawn(handle_ring_buffer(requests, handle.clone())));
179 }
180 x => unimplemented!("DAI request not implemented: {:?}", x),
181 };
182 }
183}
184
185fn mock_dai_device(
190 as_input: bool,
191 requests: DaiRequestStream,
192) -> (impl Future<Output = ()>, TestHandle) {
193 let supported_dai_formats = DaiSupportedFormats {
194 number_of_channels: vec![1],
195 sample_formats: vec![DaiSampleFormat::PcmSigned],
196 frame_formats: vec![DaiFrameFormat::FrameFormatStandard(DaiFrameFormatStandard::Tdm1)],
197 frame_rates: vec![8000, 16000, 32000, 48000, 96000],
198 bits_per_slot: vec![16],
199 bits_per_sample: vec![16],
200 };
201
202 let number_of_channels = 1usize;
203 let attributes = vec![ChannelAttributes::default(); number_of_channels];
204 let channel_set = ChannelSet { attributes: Some(attributes), ..Default::default() };
205 let supported_pcm_formats = SupportedFormats {
206 pcm_supported_formats: Some(PcmSupportedFormats {
207 channel_sets: Some(vec![channel_set]),
208 sample_formats: Some(vec![SampleFormat::PcmSigned]),
209 bytes_per_sample: Some(vec![2]),
210 valid_bits_per_sample: Some(vec![16]),
211 frame_rates: Some(vec![8000, 16000, 32000, 48000, 96000]),
212 ..Default::default()
213 }),
214 ..Default::default()
215 };
216
217 let handle = TestHandle::new();
218
219 let handler_fut = test_handle_dai_requests(
220 supported_dai_formats,
221 supported_pcm_formats,
222 as_input,
223 requests,
224 handle.clone(),
225 );
226 (handler_fut, handle)
227}
228
229pub fn test_digital_audio_interface(as_input: bool) -> (DigitalAudioInterface, TestHandle) {
232 let (proxy, requests) = fidl::endpoints::create_proxy_and_stream::<DaiMarker>();
233
234 let (handler, handle) = mock_dai_device(as_input, requests);
235 fasync::Task::spawn(handler).detach();
236
237 (DigitalAudioInterface::from_proxy(proxy), handle)
238}
239
240async fn handle_dai_connect_requests(as_input: bool, mut stream: DaiConnectorRequestStream) {
241 while let Some(request) = stream.next().await {
242 if let Ok(DaiConnectorRequest::Connect { dai_protocol, .. }) = request {
243 let (handler, _test_handle) = mock_dai_device(as_input, dai_protocol.into_stream());
244 fasync::Task::spawn(handler).detach();
245 }
246 }
247}
248
249pub fn mock_dai_dev_with_io_devices(input: String, output: String) -> Arc<dyn Directory> {
251 pseudo_directory! {
252 "class" => pseudo_directory! {
253 "dai" => pseudo_directory! {
254 &input => service::host(
255 move |stream: DaiConnectorRequestStream| handle_dai_connect_requests(true,
256 stream)
257 ),
258 &output => service::host(
259 move |stream: DaiConnectorRequestStream| handle_dai_connect_requests(false,
260 stream)
261 ),
262 }
263 }
264 }
265}