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