devices/virtio/snd/vios_backend/
mod.rs

1// Copyright 2020 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5mod shm_streams;
6mod shm_vios;
7
8#[cfg(any(target_os = "linux", target_os = "android"))]
9pub use self::shm_streams::*;
10pub use self::shm_vios::*;
11
12pub mod streams;
13mod worker;
14
15use std::collections::BTreeMap;
16use std::io::Error as IoError;
17use std::path::Path;
18use std::sync::mpsc::RecvError;
19use std::sync::mpsc::SendError;
20use std::sync::Arc;
21
22use anyhow::anyhow;
23use anyhow::Context;
24use base::error;
25use base::Error as BaseError;
26use base::RawDescriptor;
27use base::WorkerThread;
28use data_model::Le32;
29use remain::sorted;
30use serde::Deserialize;
31use serde::Serialize;
32use snapshot::AnySnapshot;
33use streams::StreamMsg;
34use streams::StreamSnapshot;
35use sync::Mutex;
36use thiserror::Error as ThisError;
37use vm_memory::GuestMemory;
38use worker::*;
39use zerocopy::IntoBytes;
40
41use crate::virtio::copy_config;
42use crate::virtio::device_constants::snd::virtio_snd_config;
43use crate::virtio::DeviceType;
44use crate::virtio::Interrupt;
45use crate::virtio::Queue;
46use crate::virtio::VirtioDevice;
47
48const QUEUE_SIZES: &[u16] = &[64, 64, 64, 64];
49
50#[sorted]
51#[derive(ThisError, Debug)]
52pub enum SoundError {
53    #[error("The driver sent an invalid message")]
54    BadDriverMsg,
55    #[error("Failed to get event notifier from VioS client: {0}")]
56    ClientEventNotifier(Error),
57    #[error("Failed to create VioS client: {0}")]
58    ClientNew(Error),
59    #[error("Failed to create event pair: {0}")]
60    CreateEvent(BaseError),
61    #[error("Failed to create thread: {0}")]
62    CreateThread(IoError),
63    #[error("Attempted a {0} operation while on the wrong state: {1}, this is a bug")]
64    ImpossibleState(&'static str, &'static str),
65    #[error("Error consuming queue event: {0}")]
66    QueueEvt(BaseError),
67    #[error("Failed to read/write from/to queue: {0}")]
68    QueueIO(IoError),
69    #[error("Failed to receive message: {0}")]
70    StreamThreadRecv(RecvError),
71    #[error("Failed to send message: {0}")]
72    StreamThreadSend(SendError<Box<StreamMsg>>),
73    #[error("Error creating WaitContext: {0}")]
74    WaitCtx(BaseError),
75}
76
77pub type Result<T> = std::result::Result<T, SoundError>;
78
79pub struct Sound {
80    config: virtio_snd_config,
81    virtio_features: u64,
82    worker_thread: Option<WorkerThread<anyhow::Result<Worker>>>,
83    vios_client: Arc<Mutex<VioSClient>>,
84    saved_stream_state: Vec<StreamSnapshot>,
85}
86
87#[derive(Serialize, Deserialize)]
88struct SoundSnapshot {
89    config: virtio_snd_config,
90    virtio_features: u64,
91    vios_client: VioSClientSnapshot,
92    saved_stream_state: Vec<StreamSnapshot>,
93}
94
95impl VirtioDevice for Sound {
96    fn keep_rds(&self) -> Vec<RawDescriptor> {
97        self.vios_client.lock().keep_rds()
98    }
99
100    fn device_type(&self) -> DeviceType {
101        DeviceType::Sound
102    }
103
104    fn queue_max_sizes(&self) -> &[u16] {
105        QUEUE_SIZES
106    }
107
108    fn read_config(&self, offset: u64, data: &mut [u8]) {
109        copy_config(data, 0, self.config.as_bytes(), offset);
110    }
111
112    fn write_config(&mut self, _offset: u64, _data: &[u8]) {
113        error!("virtio-snd: driver attempted a config write which is not allowed by the spec");
114    }
115
116    fn features(&self) -> u64 {
117        self.virtio_features
118    }
119
120    fn activate(
121        &mut self,
122        _mem: GuestMemory,
123        _interrupt: Interrupt,
124        mut queues: BTreeMap<usize, Queue>,
125    ) -> anyhow::Result<()> {
126        if self.worker_thread.is_some() {
127            return Err(anyhow!("virtio-snd: Device is already active"));
128        }
129        if queues.len() != 4 {
130            return Err(anyhow!(
131                "virtio-snd: device activated with wrong number of queues: {}",
132                queues.len(),
133            ));
134        }
135        let control_queue = queues.remove(&0).unwrap();
136        let event_queue = queues.remove(&1).unwrap();
137        let tx_queue = queues.remove(&2).unwrap();
138        let rx_queue = queues.remove(&3).unwrap();
139
140        let vios_client = self.vios_client.clone();
141        vios_client
142            .lock()
143            .start_bg_thread()
144            .context("Failed to start vios background thread")?;
145
146        let saved_stream_state: Vec<StreamSnapshot> = self.saved_stream_state.drain(..).collect();
147        self.worker_thread =
148            Some(WorkerThread::start(
149                "v_snd_vios",
150                move |kill_evt| match Worker::try_new(
151                    vios_client,
152                    Arc::new(Mutex::new(control_queue)),
153                    event_queue,
154                    Arc::new(Mutex::new(tx_queue)),
155                    Arc::new(Mutex::new(rx_queue)),
156                    saved_stream_state,
157                ) {
158                    Ok(mut worker) => match worker.control_loop(kill_evt) {
159                        Ok(_) => Ok(worker),
160                        Err(e) => {
161                            error!("virtio-snd: Error in worker loop: {}", e);
162                            Err(anyhow!("virtio-snd: Error in worker loop: {}", e))
163                        }
164                    },
165                    Err(e) => {
166                        error!("virtio-snd: Failed to create worker: {}", e);
167                        Err(anyhow!("virtio-snd: Failed to create worker: {}", e))
168                    }
169                },
170            ));
171
172        Ok(())
173    }
174
175    fn reset(&mut self) -> anyhow::Result<()> {
176        if let Some(worker_thread) = self.worker_thread.take() {
177            let worker = worker_thread.stop();
178            self.vios_client
179                .lock()
180                .stop_bg_thread()
181                .context("failed to stop VioS Client background thread")?;
182            let _worker = worker.context("failed to stop worker_thread")?;
183        }
184        Ok(())
185    }
186
187    fn virtio_sleep(&mut self) -> anyhow::Result<Option<BTreeMap<usize, Queue>>> {
188        if let Some(worker_thread) = self.worker_thread.take() {
189            // The worker is stopped first but not unwrapped until after the VioSClient is stopped.
190            // If the worker fails to stop and returns an error, but that error is unwrapped, the
191            // vios_client background thread could remain running. Instead, by delaying the unwrap,
192            // we can ensure the signal to both threads to stop is sent.
193            let worker = worker_thread.stop();
194            self.vios_client
195                .lock()
196                .stop_bg_thread()
197                .context("failed to stop VioS Client background thread")?;
198            let mut worker = worker.context("failed to stop worker_thread")?;
199            self.saved_stream_state = worker.saved_stream_state.drain(..).collect();
200            let ctrl_queue = worker.control_queue.clone();
201            let event_queue = worker.event_queue.take().unwrap();
202            let tx_queue = worker.tx_queue.clone();
203            let rx_queue = worker.rx_queue.clone();
204
205            // Must drop worker to drop all references to queues.
206            // This also drops the io_thread
207            drop(worker);
208
209            let ctrl_queue = match Arc::try_unwrap(ctrl_queue) {
210                Ok(q) => q.into_inner(),
211                Err(_) => panic!("too many refs to snd control queue"),
212            };
213            let tx_queue = match Arc::try_unwrap(tx_queue) {
214                Ok(q) => q.into_inner(),
215                Err(_) => panic!("too many refs to snd tx queue"),
216            };
217            let rx_queue = match Arc::try_unwrap(rx_queue) {
218                Ok(q) => q.into_inner(),
219                Err(_) => panic!("too many refs to snd rx queue"),
220            };
221            let queues = vec![ctrl_queue, event_queue, tx_queue, rx_queue];
222            return Ok(Some(BTreeMap::from_iter(queues.into_iter().enumerate())));
223        }
224        Ok(None)
225    }
226
227    fn virtio_wake(
228        &mut self,
229        device_state: Option<(GuestMemory, Interrupt, BTreeMap<usize, Queue>)>,
230    ) -> anyhow::Result<()> {
231        match device_state {
232            None => Ok(()),
233            Some((mem, interrupt, queues)) => {
234                // TODO: activate is just what we want at the moment, but we should probably move
235                // it into a "start workers" function to make it obvious that it isn't strictly
236                // used for activate events.
237                self.activate(mem, interrupt, queues)?;
238                Ok(())
239            }
240        }
241    }
242
243    fn virtio_snapshot(&mut self) -> anyhow::Result<AnySnapshot> {
244        AnySnapshot::to_any(SoundSnapshot {
245            config: self.config,
246            virtio_features: self.virtio_features,
247            vios_client: self.vios_client.lock().snapshot(),
248            saved_stream_state: self.saved_stream_state.clone(),
249        })
250        .context("failed to serialize VioS Client")
251    }
252
253    fn virtio_restore(&mut self, data: AnySnapshot) -> anyhow::Result<()> {
254        let data: SoundSnapshot =
255            AnySnapshot::from_any(data).context("failed to deserialize VioS Client")?;
256        anyhow::ensure!(
257            data.config == self.config,
258            "config doesn't match on restore: expected: {:?}, got: {:?}",
259            data.config,
260            self.config
261        );
262        anyhow::ensure!(
263            data.virtio_features == self.virtio_features,
264            "virtio_features doesn't match on restore: expected: {}, got: {}",
265            data.virtio_features,
266            self.virtio_features
267        );
268        self.saved_stream_state = data.saved_stream_state;
269        self.vios_client.lock().restore(data.vios_client)
270    }
271}
272
273/// Creates a new virtio sound device connected to a VioS backend
274pub fn new_sound<P: AsRef<Path>>(path: P, virtio_features: u64) -> Result<Sound> {
275    let vios_client = VioSClient::try_new(path).map_err(SoundError::ClientNew)?;
276    let jacks = Le32::from(vios_client.num_jacks());
277    let streams = Le32::from(vios_client.num_streams());
278    let chmaps = Le32::from(vios_client.num_chmaps());
279    Ok(Sound {
280        config: virtio_snd_config {
281            jacks,
282            streams,
283            chmaps,
284        },
285        virtio_features,
286        worker_thread: None,
287        vios_client: Arc::new(Mutex::new(vios_client)),
288        saved_stream_state: Vec::new(),
289    })
290}