mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-05 18:20:34 +00:00
Add the VioS audio backend
It only supports playback streams, with capture streams to be added in a different change. BUG=b/171602855 Change-Id: Id9a5a560506f8fd026ef3ed83f8d14b29389e329 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2574813 Tested-by: Jorge Moreira Broche <jemoreira@google.com> Commit-Queue: Jorge Moreira Broche <jemoreira@google.com> Auto-Submit: Jorge Moreira Broche <jemoreira@google.com> Reviewed-by: Dylan Reid <dgreid@chromium.org> Reviewed-by: Chih-Yang Hsia <paulhsia@chromium.org> Reviewed-by: Zach Reizner <zachr@chromium.org>
This commit is contained in:
parent
52e51b6539
commit
359e7de9a1
15 changed files with 950 additions and 10 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -275,8 +275,10 @@ dependencies = [
|
|||
"resources",
|
||||
"rutabaga_gfx",
|
||||
"sync",
|
||||
"sys_util",
|
||||
"syscall_defines",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tpm2",
|
||||
"usb_util",
|
||||
"vfio_sys",
|
||||
|
@ -566,7 +568,6 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"protobuf",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -45,8 +45,10 @@ rand_ish = { path = "../rand_ish" }
|
|||
remain = "*"
|
||||
resources = { path = "../resources" }
|
||||
sync = { path = "../sync" }
|
||||
sys_util = { path = "../sys_util" }
|
||||
base = { path = "../base" }
|
||||
syscall_defines = { path = "../syscall_defines" }
|
||||
thiserror = "1.0.20"
|
||||
tpm2 = { path = "../tpm2", optional = true }
|
||||
usb_util = { path = "../usb_util" }
|
||||
vfio_sys = { path = "../vfio_sys" }
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
use std::default::Default;
|
||||
use std::error;
|
||||
use std::fmt::{self, Display};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use audio_streams::shm_streams::{NullShmStreamSource, ShmStreamSource};
|
||||
|
@ -13,6 +14,10 @@ use libcras::{CrasClient, CrasClientType, CrasSocketType};
|
|||
use resources::{Alloc, MmioType, SystemAllocator};
|
||||
use vm_memory::GuestMemory;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::virtio::snd::vios_backend::VioSShmStreamSource;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
use crate::virtio::snd::vios_backend::Error as VioSError;
|
||||
use crate::pci::ac97_bus_master::Ac97BusMaster;
|
||||
use crate::pci::ac97_mixer::Ac97Mixer;
|
||||
use crate::pci::ac97_regs::*;
|
||||
|
@ -34,6 +39,7 @@ const PCI_DEVICE_ID_INTEL_82801AA_5: u16 = 0x2415;
|
|||
pub enum Ac97Backend {
|
||||
NULL,
|
||||
CRAS,
|
||||
VIOS,
|
||||
}
|
||||
|
||||
impl Default for Ac97Backend {
|
||||
|
@ -46,6 +52,7 @@ impl Default for Ac97Backend {
|
|||
#[derive(Debug)]
|
||||
pub enum Ac97Error {
|
||||
InvalidBackend,
|
||||
MissingServerPath,
|
||||
}
|
||||
|
||||
impl error::Error for Ac97Error {}
|
||||
|
@ -53,7 +60,8 @@ impl error::Error for Ac97Error {}
|
|||
impl Display for Ac97Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Ac97Error::InvalidBackend => write!(f, "Must be cras or null"),
|
||||
Ac97Error::InvalidBackend => write!(f, "Must be cras, vios or null"),
|
||||
Ac97Error::MissingServerPath => write!(f, "server must be provided for vios backend"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,6 +71,7 @@ impl FromStr for Ac97Backend {
|
|||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
match s {
|
||||
"cras" => Ok(Ac97Backend::CRAS),
|
||||
"vios" => Ok(Ac97Backend::VIOS),
|
||||
"null" => Ok(Ac97Backend::NULL),
|
||||
_ => Err(Ac97Error::InvalidBackend),
|
||||
}
|
||||
|
@ -74,6 +83,7 @@ impl FromStr for Ac97Backend {
|
|||
pub struct Ac97Parameters {
|
||||
pub backend: Ac97Backend,
|
||||
pub capture: bool,
|
||||
pub vios_server_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub struct Ac97Dev {
|
||||
|
@ -129,6 +139,7 @@ impl Ac97Dev {
|
|||
);
|
||||
Self::create_null_audio_device(mem)
|
||||
}),
|
||||
Ac97Backend::VIOS => Self::create_vios_audio_device(mem, param),
|
||||
Ac97Backend::NULL => Self::create_null_audio_device(mem),
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +148,7 @@ impl Ac97Dev {
|
|||
pub fn minijail_policy(&self) -> &'static str {
|
||||
match self.backend {
|
||||
Ac97Backend::CRAS => "cras_audio_device",
|
||||
Ac97Backend::VIOS => "vios_audio_device",
|
||||
Ac97Backend::NULL => "null_audio_device",
|
||||
}
|
||||
}
|
||||
|
@ -155,6 +167,21 @@ impl Ac97Dev {
|
|||
Ok(cras_audio)
|
||||
}
|
||||
|
||||
fn create_vios_audio_device(mem: GuestMemory, param: Ac97Parameters) -> Result<Self> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let server = Box::new(
|
||||
// The presence of vios_server_path is checked during argument parsing
|
||||
VioSShmStreamSource::new(param.vios_server_path.expect("Missing server path"))
|
||||
.map_err(|e| pci_device::Error::CreateViosClientFailed(e))?,
|
||||
);
|
||||
let vios_audio = Self::new(mem, Ac97Backend::VIOS, server);
|
||||
return Ok(vios_audio);
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
Err(pci_device::Error::CreateViosClientFailed(VioSError::PlatformNotSupported))
|
||||
}
|
||||
|
||||
fn create_null_audio_device(mem: GuestMemory) -> Result<Self> {
|
||||
let server = Box::new(NullShmStreamSource::new());
|
||||
let null_audio = Self::new(mem, Ac97Backend::NULL, server);
|
||||
|
|
|
@ -11,6 +11,7 @@ use resources::{Error as SystemAllocatorFaliure, SystemAllocator};
|
|||
use crate::pci::pci_configuration;
|
||||
use crate::pci::{PciAddress, PciInterruptPin};
|
||||
use crate::{BusAccessInfo, BusDevice};
|
||||
use crate::virtio::snd::vios_backend::Error as VioSError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
|
@ -23,6 +24,9 @@ pub enum Error {
|
|||
/// Create cras client failed.
|
||||
#[cfg(feature = "audio")]
|
||||
CreateCrasClientFailed(libcras::Error),
|
||||
/// Create VioS client failed.
|
||||
#[cfg(feature = "audio")]
|
||||
CreateViosClientFailed(VioSError),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
|
@ -34,6 +38,8 @@ impl Display for Error {
|
|||
CapabilitiesSetup(e) => write!(f, "failed to add capability {}", e),
|
||||
#[cfg(feature = "audio")]
|
||||
CreateCrasClientFailed(e) => write!(f, "failed to create CRAS Client: {}", e),
|
||||
#[cfg(feature = "audio")]
|
||||
CreateViosClientFailed(e) => write!(f, "failed to create VioS Client: {}", e),
|
||||
IoAllocationFailed(size, e) => write!(
|
||||
f,
|
||||
"failed to allocate space for an IO BAR, size={}: {}",
|
||||
|
|
|
@ -13,6 +13,11 @@ pub const STREAM_STOP: u32 = 0x0100 + 5;
|
|||
|
||||
pub const CHANNEL_MAP_INFO: u32 = 0x0200;
|
||||
|
||||
pub const VIRTIO_SND_S_OK: u32 = 0x8000;
|
||||
pub const VIRTIO_SND_S_BAD_MSG: u32 = 0x8001;
|
||||
pub const VIRTIO_SND_S_NOT_SUPP: u32 = 0x8002;
|
||||
pub const VIRTIO_SND_S_IO_ERR: u32 = 0x8003;
|
||||
|
||||
pub const VIRTIO_SND_D_OUTPUT: u8 = 0;
|
||||
pub const VIRTIO_SND_D_INPUT: u8 = 1;
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use data_model::{DataInit, Le32, Le64};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
#[repr(C)]
|
||||
pub struct virtio_snd_hdr {
|
||||
pub code: Le32,
|
||||
|
@ -23,7 +23,7 @@ pub struct virtio_snd_query_info {
|
|||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl DataInit for virtio_snd_query_info {}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
#[repr(C)]
|
||||
pub struct virtio_snd_info {
|
||||
pub hda_fn_nid: Le32,
|
||||
|
@ -31,7 +31,7 @@ pub struct virtio_snd_info {
|
|||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl DataInit for virtio_snd_info {}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
#[repr(C)]
|
||||
pub struct virtio_snd_pcm_info {
|
||||
pub hdr: virtio_snd_info,
|
||||
|
|
|
@ -5,3 +5,5 @@
|
|||
pub mod constants;
|
||||
|
||||
pub mod layout;
|
||||
|
||||
pub mod vios_backend;
|
||||
|
|
11
devices/src/virtio/snd/vios_backend/mod.rs
Normal file
11
devices/src/virtio/snd/vios_backend/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2020 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
mod shm_streams;
|
||||
mod shm_vios;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use self::shm_streams::*;
|
||||
|
||||
pub use self::shm_vios::*;
|
259
devices/src/virtio/snd/vios_backend/shm_streams.rs
Normal file
259
devices/src/virtio/snd/vios_backend/shm_streams.rs
Normal file
|
@ -0,0 +1,259 @@
|
|||
// Copyright 2020 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Provides an implementation of the audio_streams::shm_streams::ShmStream trait using the VioS
|
||||
//! client.
|
||||
//! Given that the VioS server doesn't emit an event when the next buffer is expected, this
|
||||
//! implementation uses thread::sleep to drive the frame timings.
|
||||
|
||||
use super::shm_vios::{VioSClient, VioSStreamParams};
|
||||
|
||||
use crate::virtio::snd::constants::*;
|
||||
|
||||
use audio_streams::shm_streams::{BufferSet, ServerRequest, ShmStream, ShmStreamSource};
|
||||
use audio_streams::{BoxError, SampleFormat, StreamDirection, StreamEffect};
|
||||
|
||||
use base::{error, SharedMemory, SharedMemoryUnix};
|
||||
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use sync::Mutex;
|
||||
use sys_util::{Error as SysError, SharedMemory as SysSharedMemory};
|
||||
|
||||
use super::shm_vios::{Error, Result};
|
||||
|
||||
// This is the error type used in audio_streams::shm_streams. Unfortunately, it's not declared
|
||||
// public there so it needs to be re-declared here. It also prevents the usage of anyhow::Error.
|
||||
type GenericResult<T> = std::result::Result<T, BoxError>;
|
||||
|
||||
/// Adapter that provides the ShmStreamSource trait around the VioS backend.
|
||||
pub struct VioSShmStreamSource {
|
||||
// Reference counting is needed because the streams also need a reference to the client to push
|
||||
// buffers and release the stream on drop and it has to implement Send because ShmStreamSource
|
||||
// requires it, so the only possibility is Arc.
|
||||
vios_client: Arc<Mutex<VioSClient>>,
|
||||
}
|
||||
|
||||
impl VioSShmStreamSource {
|
||||
/// Creates a new stream source given the path to the audio server's socket.
|
||||
pub fn new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource> {
|
||||
Ok(Self {
|
||||
vios_client: Arc::new(Mutex::new(VioSClient::try_new(server)?)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn from_sample_format(format: SampleFormat) -> u8 {
|
||||
match format {
|
||||
SampleFormat::U8 => VIRTIO_SND_PCM_FMT_U8,
|
||||
SampleFormat::S16LE => VIRTIO_SND_PCM_FMT_U16,
|
||||
SampleFormat::S24LE => VIRTIO_SND_PCM_FMT_U24,
|
||||
SampleFormat::S32LE => VIRTIO_SND_PCM_FMT_U32,
|
||||
}
|
||||
}
|
||||
|
||||
fn virtio_frame_rate(frame_rate: u32) -> GenericResult<u8> {
|
||||
Ok(match frame_rate {
|
||||
5512u32 => VIRTIO_SND_PCM_RATE_5512,
|
||||
8000u32 => VIRTIO_SND_PCM_RATE_8000,
|
||||
11025u32 => VIRTIO_SND_PCM_RATE_11025,
|
||||
16000u32 => VIRTIO_SND_PCM_RATE_16000,
|
||||
22050u32 => VIRTIO_SND_PCM_RATE_22050,
|
||||
32000u32 => VIRTIO_SND_PCM_RATE_32000,
|
||||
44100u32 => VIRTIO_SND_PCM_RATE_44100,
|
||||
48000u32 => VIRTIO_SND_PCM_RATE_48000,
|
||||
64000u32 => VIRTIO_SND_PCM_RATE_64000,
|
||||
88200u32 => VIRTIO_SND_PCM_RATE_88200,
|
||||
96000u32 => VIRTIO_SND_PCM_RATE_96000,
|
||||
176400u32 => VIRTIO_SND_PCM_RATE_176400,
|
||||
192000u32 => VIRTIO_SND_PCM_RATE_192000,
|
||||
384000u32 => VIRTIO_SND_PCM_RATE_384000,
|
||||
_ => {
|
||||
return Err(Box::new(Error::UnsupportedFrameRate(frame_rate)));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl ShmStreamSource for VioSShmStreamSource {
|
||||
/// Creates a new stream
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_stream(
|
||||
&mut self,
|
||||
direction: StreamDirection,
|
||||
num_channels: usize,
|
||||
format: SampleFormat,
|
||||
frame_rate: u32,
|
||||
buffer_size: usize,
|
||||
_effects: &[StreamEffect],
|
||||
client_shm: &SysSharedMemory,
|
||||
_buffer_offsets: [u64; 2],
|
||||
) -> GenericResult<Box<dyn ShmStream>> {
|
||||
let mut vios_client = self.vios_client.lock();
|
||||
match direction {
|
||||
StreamDirection::Playback => {
|
||||
match vios_client.get_unused_stream_id(VIRTIO_SND_D_OUTPUT) {
|
||||
Some(stream_id) => {
|
||||
vios_client.prepare_stream(stream_id)?;
|
||||
let frame_size = num_channels * format.sample_bytes();
|
||||
let period_bytes = (frame_size * buffer_size) as u32;
|
||||
let params = VioSStreamParams {
|
||||
buffer_bytes: 2 * period_bytes,
|
||||
period_bytes,
|
||||
features: 0u32,
|
||||
channels: num_channels as u8,
|
||||
format: from_sample_format(format),
|
||||
rate: virtio_frame_rate(frame_rate)?,
|
||||
};
|
||||
vios_client.set_stream_parameters(stream_id, params)?;
|
||||
vios_client.start_stream(stream_id)?;
|
||||
VioSndShmStream::new(
|
||||
buffer_size,
|
||||
num_channels,
|
||||
format,
|
||||
frame_rate,
|
||||
stream_id,
|
||||
self.vios_client.clone(),
|
||||
client_shm,
|
||||
)
|
||||
}
|
||||
None => Err(Box::new(Error::NoStreamsAvailable)),
|
||||
}
|
||||
}
|
||||
StreamDirection::Capture => panic!("Capture not yet supported"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a list of file descriptors used by the implementation.
|
||||
///
|
||||
/// Returns any open file descriptors needed by the implementation.
|
||||
/// This list helps users of the ShmStreamSource enter Linux jails without
|
||||
/// closing needed file descriptors.
|
||||
fn keep_fds(&self) -> Vec<RawFd> {
|
||||
self.vios_client.lock().keep_fds()
|
||||
}
|
||||
}
|
||||
|
||||
/// Adapter around a VioS stream that implements the ShmStream trait.
|
||||
pub struct VioSndShmStream {
|
||||
num_channels: usize,
|
||||
frame_rate: u32,
|
||||
buffer_size: usize,
|
||||
frame_size: usize,
|
||||
interval: Duration,
|
||||
next_frame: Duration,
|
||||
start_time: Instant,
|
||||
stream_id: u32,
|
||||
vios_client: Arc<Mutex<VioSClient>>,
|
||||
client_shm: SharedMemory,
|
||||
}
|
||||
|
||||
impl VioSndShmStream {
|
||||
/// Creates a new shm stream.
|
||||
fn new(
|
||||
buffer_size: usize,
|
||||
num_channels: usize,
|
||||
format: SampleFormat,
|
||||
frame_rate: u32,
|
||||
stream_id: u32,
|
||||
vios_client: Arc<Mutex<VioSClient>>,
|
||||
client_shm: &SysSharedMemory,
|
||||
) -> GenericResult<Box<dyn ShmStream>> {
|
||||
let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
|
||||
|
||||
let dup_fd = unsafe {
|
||||
// Safe because dup doesn't affect memory and client_shm should wrap a known valid file
|
||||
// descriptor
|
||||
libc::dup(client_shm.as_raw_fd())
|
||||
};
|
||||
if dup_fd < 0 {
|
||||
return Err(Box::new(Error::DupError(SysError::last())));
|
||||
}
|
||||
let file = unsafe {
|
||||
// safe because we checked the result of libc::dup()
|
||||
File::from_raw_fd(dup_fd)
|
||||
};
|
||||
let client_shm_clone =
|
||||
SharedMemory::from_file(file).map_err(|e| Error::BaseMmapError(e))?;
|
||||
|
||||
Ok(Box::new(Self {
|
||||
num_channels,
|
||||
frame_rate,
|
||||
buffer_size,
|
||||
frame_size: format.sample_bytes() * num_channels,
|
||||
interval,
|
||||
next_frame: interval,
|
||||
start_time: Instant::now(),
|
||||
stream_id,
|
||||
vios_client,
|
||||
client_shm: client_shm_clone,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl ShmStream for VioSndShmStream {
|
||||
fn frame_size(&self) -> usize {
|
||||
self.frame_size
|
||||
}
|
||||
|
||||
fn num_channels(&self) -> usize {
|
||||
self.num_channels
|
||||
}
|
||||
|
||||
fn frame_rate(&self) -> u32 {
|
||||
self.frame_rate
|
||||
}
|
||||
|
||||
/// Waits until the next time a frame should be sent to the server. The server may release the
|
||||
/// previous buffer much sooner than it needs the next one, so this function may sleep to wait
|
||||
/// for the right time.
|
||||
fn wait_for_next_action_with_timeout<'b>(
|
||||
&'b mut self,
|
||||
timeout: Duration,
|
||||
) -> GenericResult<Option<ServerRequest<'b>>> {
|
||||
let elapsed = self.start_time.elapsed();
|
||||
if elapsed < self.next_frame {
|
||||
if timeout < self.next_frame - elapsed {
|
||||
std::thread::sleep(timeout);
|
||||
return Ok(None);
|
||||
} else {
|
||||
std::thread::sleep(self.next_frame - elapsed);
|
||||
}
|
||||
}
|
||||
self.next_frame += self.interval;
|
||||
Ok(Some(ServerRequest::new(self.buffer_size, self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferSet for VioSndShmStream {
|
||||
fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()> {
|
||||
self.vios_client.lock().inject_audio_data(
|
||||
self.stream_id,
|
||||
&mut self.client_shm,
|
||||
offset,
|
||||
frames * self.frame_size,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ignore(&mut self) -> GenericResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VioSndShmStream {
|
||||
fn drop(&mut self) {
|
||||
let mut client = self.vios_client.lock();
|
||||
let stream_id = self.stream_id;
|
||||
if let Err(e) = client
|
||||
.stop_stream(stream_id)
|
||||
.and_then(|_| client.release_stream(stream_id))
|
||||
{
|
||||
error!("Failed to stop and release stream {}: {}", stream_id, e);
|
||||
}
|
||||
}
|
||||
}
|
548
devices/src/virtio/snd/vios_backend/shm_vios.rs
Normal file
548
devices/src/virtio/snd/vios_backend/shm_vios.rs
Normal file
|
@ -0,0 +1,548 @@
|
|||
// Copyright 2020 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::virtio::snd::constants::*;
|
||||
use crate::virtio::snd::layout::*;
|
||||
|
||||
use base::{
|
||||
net::UnixSeqpacket, Error as BaseError, FromRawDescriptor, IntoRawDescriptor, MemoryMapping,
|
||||
MemoryMappingBuilder, MmapError, SafeDescriptor, ScmSocket, SharedMemory,
|
||||
};
|
||||
use data_model::{DataInit, VolatileMemory, VolatileMemoryError};
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Error as IOError, ErrorKind as IOErrorKind, Seek, SeekFrom};
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::path::Path;
|
||||
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(ThisError, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Failed to connect to VioS server: {0:?}")]
|
||||
ServerConnectionError(IOError),
|
||||
#[error("Failed to communicate with VioS server: {0:?}")]
|
||||
ServerError(BaseError),
|
||||
#[error("Failed to communicate with VioS server: {0:?}")]
|
||||
ServerIOError(IOError),
|
||||
#[error("Failed to get size of tx shared memory: {0}")]
|
||||
FileSizeError(IOError),
|
||||
#[error("Error duplicating file descriptor: {0}")]
|
||||
DupError(BaseError),
|
||||
#[error("Error accessing VioS server's shared memory: {0}")]
|
||||
ServerMmapError(MmapError),
|
||||
#[error("Error accessing guest's shared memory: {0}")]
|
||||
GuestMmapError(MmapError),
|
||||
#[error("Error memory mapping client_shm: {0}")]
|
||||
BaseMmapError(BaseError),
|
||||
#[error("Error accessing volatile memory: {0}")]
|
||||
VolatileMemoryError(VolatileMemoryError),
|
||||
#[error("{0}")]
|
||||
ProtocolError(ProtocolErrorKind),
|
||||
#[error("No PCM streams available")]
|
||||
NoStreamsAvailable,
|
||||
#[error("No stream with id {0}")]
|
||||
InvalidStreamId(u32),
|
||||
#[error("Stream is unexpected state: {0:?}")]
|
||||
UnexpectedState(StreamState),
|
||||
#[error("Invalid operation for stream direction: {0}")]
|
||||
WrongDirection(u8),
|
||||
#[error("Insuficient space for the new buffer in the queue's buffer area")]
|
||||
OutOfSpace,
|
||||
#[error("Unsupported frame rate: {0}")]
|
||||
UnsupportedFrameRate(u32),
|
||||
#[error("Platform not supported")]
|
||||
PlatformNotSupported,
|
||||
#[error("Command failed with status {0}")]
|
||||
CommandFailed(u32),
|
||||
}
|
||||
|
||||
#[derive(ThisError, Debug)]
|
||||
pub enum ProtocolErrorKind {
|
||||
#[error("The server sent a config of the wrong size: {0}")]
|
||||
UnexpectedConfigSize(usize),
|
||||
#[error("Received {1} file descriptors from the server, expected {0}")]
|
||||
UnexpectedNumberOfFileDescriptors(usize, usize), // expected, received
|
||||
#[error("Server's version ({0}) doesn't match client's")]
|
||||
VersionMismatch(u32),
|
||||
#[error("Received a msg with an unexpected size: expected {0}, received {1}")]
|
||||
UnexpectedMessageSize(usize, usize), // expected, received
|
||||
}
|
||||
|
||||
/// The client for the VioS backend
|
||||
///
|
||||
/// Uses a protocol equivalent to virtio-snd over a shared memory file and a unix socket for
|
||||
/// notifications.
|
||||
pub struct VioSClient {
|
||||
config: VioSConfig,
|
||||
streams: Vec<VioSStreamInfo>,
|
||||
control_socket: UnixSeqpacket,
|
||||
event_socket: UnixSeqpacket,
|
||||
tx: IoBufferQueue,
|
||||
rx: IoBufferQueue,
|
||||
}
|
||||
|
||||
impl VioSClient {
|
||||
/// Create a new client given the path to the audio server's socket.
|
||||
pub fn try_new<P: AsRef<Path>>(server: P) -> Result<VioSClient> {
|
||||
let client_socket =
|
||||
UnixSeqpacket::connect(server).map_err(|e| Error::ServerConnectionError(e))?;
|
||||
let mut config: VioSConfig = Default::default();
|
||||
let mut fds: Vec<RawFd> = Vec::new();
|
||||
const NUM_FDS: usize = 5;
|
||||
fds.resize(NUM_FDS, 0 as RawFd);
|
||||
let (recv_size, fd_count) = client_socket
|
||||
.recv_with_fds(config.as_mut_slice(), &mut fds)
|
||||
.map_err(|e| Error::ServerError(e))?;
|
||||
|
||||
// Resize the vector to the actual number of file descriptors received and wrap them in
|
||||
// SafeDescriptors to prevent leaks
|
||||
fds.resize(fd_count, -1 as RawFd);
|
||||
let mut safe_fds: Vec<SafeDescriptor> = fds
|
||||
.into_iter()
|
||||
.map(|fd| unsafe {
|
||||
// safe because the SafeDescriptor object completely assumes ownership of the fd.
|
||||
SafeDescriptor::from_raw_descriptor(fd)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if recv_size != std::mem::size_of::<VioSConfig>() {
|
||||
return Err(Error::ProtocolError(
|
||||
ProtocolErrorKind::UnexpectedConfigSize(recv_size),
|
||||
));
|
||||
}
|
||||
|
||||
if config.version != VIOS_VERSION {
|
||||
return Err(Error::ProtocolError(ProtocolErrorKind::VersionMismatch(
|
||||
config.version,
|
||||
)));
|
||||
}
|
||||
|
||||
fn pop<T: FromRawFd>(
|
||||
safe_fds: &mut Vec<SafeDescriptor>,
|
||||
expected: usize,
|
||||
received: usize,
|
||||
) -> Result<T> {
|
||||
unsafe {
|
||||
// Safe because we transfer ownership from the SafeDescriptor to T
|
||||
Ok(T::from_raw_fd(
|
||||
safe_fds
|
||||
.pop()
|
||||
.ok_or(Error::ProtocolError(
|
||||
ProtocolErrorKind::UnexpectedNumberOfFileDescriptors(
|
||||
expected, received,
|
||||
),
|
||||
))?
|
||||
.into_raw_descriptor(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let rx_shm_file = pop::<File>(&mut safe_fds, NUM_FDS, fd_count)?;
|
||||
let tx_shm_file = pop::<File>(&mut safe_fds, NUM_FDS, fd_count)?;
|
||||
let rx_socket = pop::<UnixSeqpacket>(&mut safe_fds, NUM_FDS, fd_count)?;
|
||||
let tx_socket = pop::<UnixSeqpacket>(&mut safe_fds, NUM_FDS, fd_count)?;
|
||||
let event_socket = pop::<UnixSeqpacket>(&mut safe_fds, NUM_FDS, fd_count)?;
|
||||
|
||||
if !safe_fds.is_empty() {
|
||||
return Err(Error::ProtocolError(
|
||||
ProtocolErrorKind::UnexpectedNumberOfFileDescriptors(NUM_FDS, fd_count),
|
||||
));
|
||||
}
|
||||
|
||||
let control_socket = client_socket;
|
||||
|
||||
let mut client = VioSClient {
|
||||
config,
|
||||
streams: Vec::new(),
|
||||
control_socket,
|
||||
event_socket,
|
||||
tx: IoBufferQueue::new(tx_socket, tx_shm_file)?,
|
||||
rx: IoBufferQueue::new(rx_socket, rx_shm_file)?,
|
||||
};
|
||||
client.request_and_cache_streams_info()?;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
/// Get a description of the available sound streams.
|
||||
pub fn streams(&self) -> &Vec<VioSStreamInfo> {
|
||||
&self.streams
|
||||
}
|
||||
|
||||
/// Gets an unused stream id of the specified direction. `direction` must be one of
|
||||
/// VIRTIO_SND_D_INPUT OR VIRTIO_SND_D_OUTPUT.
|
||||
pub fn get_unused_stream_id(&self, direction: u8) -> Option<u32> {
|
||||
self.streams
|
||||
.iter()
|
||||
.filter(|s| s.state == StreamState::Available && s.direction == direction as u8)
|
||||
.map(|s| s.id)
|
||||
.next()
|
||||
}
|
||||
|
||||
/// Configures a stream with the given parameters.
|
||||
pub fn set_stream_parameters(
|
||||
&mut self,
|
||||
stream_id: u32,
|
||||
params: VioSStreamParams,
|
||||
) -> Result<()> {
|
||||
self.validate_stream_id(stream_id, &[StreamState::Available, StreamState::Acquired])?;
|
||||
let raw_params: virtio_snd_pcm_set_params = (stream_id, params).into();
|
||||
seq_socket_send(&self.control_socket, raw_params)?;
|
||||
self.recv_cmd_status()?;
|
||||
self.streams[stream_id as usize].state = StreamState::Acquired;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send the PREPARE_STREAM command to the server.
|
||||
pub fn prepare_stream(&mut self, stream_id: u32) -> Result<()> {
|
||||
self.common_stream_op(
|
||||
stream_id,
|
||||
&[StreamState::Available, StreamState::Acquired],
|
||||
StreamState::Acquired,
|
||||
STREAM_PREPARE,
|
||||
)
|
||||
}
|
||||
|
||||
/// Send the RELEASE_STREAM command to the server.
|
||||
pub fn release_stream(&mut self, stream_id: u32) -> Result<()> {
|
||||
self.common_stream_op(
|
||||
stream_id,
|
||||
&[StreamState::Acquired],
|
||||
StreamState::Available,
|
||||
STREAM_RELEASE,
|
||||
)
|
||||
}
|
||||
|
||||
/// Send the START_STREAM command to the server.
|
||||
pub fn start_stream(&mut self, stream_id: u32) -> Result<()> {
|
||||
self.common_stream_op(
|
||||
stream_id,
|
||||
&[StreamState::Acquired],
|
||||
StreamState::Active,
|
||||
STREAM_START,
|
||||
)
|
||||
}
|
||||
|
||||
/// Send the STOP_STREAM command to the server.
|
||||
pub fn stop_stream(&mut self, stream_id: u32) -> Result<()> {
|
||||
self.common_stream_op(
|
||||
stream_id,
|
||||
&[StreamState::Active],
|
||||
StreamState::Acquired,
|
||||
STREAM_STOP,
|
||||
)
|
||||
}
|
||||
|
||||
/// Send audio frames to the server. The audio data is taken from a shared memory resource.
|
||||
pub fn inject_audio_data(
|
||||
&mut self,
|
||||
stream_id: u32,
|
||||
buffer: &mut SharedMemory,
|
||||
src_offset: usize,
|
||||
size: usize,
|
||||
) -> Result<()> {
|
||||
self.validate_stream_id(stream_id, &[StreamState::Active])?;
|
||||
if self.streams[stream_id as usize].direction != VIRTIO_SND_D_OUTPUT {
|
||||
return Err(Error::WrongDirection(
|
||||
self.streams[stream_id as usize].direction,
|
||||
));
|
||||
}
|
||||
let dst_offset = self.tx.push_buffer(buffer, src_offset, size)?;
|
||||
let msg = IoTransferMsg::new(stream_id, dst_offset, size);
|
||||
seq_socket_send(&self.tx.socket, msg)
|
||||
}
|
||||
|
||||
/// Get a list of file descriptors used by the implementation.
|
||||
pub fn keep_fds(&self) -> Vec<RawFd> {
|
||||
vec![
|
||||
self.control_socket.as_raw_fd(),
|
||||
self.event_socket.as_raw_fd(),
|
||||
self.tx.socket.as_raw_fd(),
|
||||
self.rx.socket.as_raw_fd(),
|
||||
self.tx.file.as_raw_fd(),
|
||||
self.rx.file.as_raw_fd(),
|
||||
]
|
||||
}
|
||||
|
||||
fn validate_stream_id(&self, stream_id: u32, permitted_states: &[StreamState]) -> Result<()> {
|
||||
if stream_id >= self.streams.len() as u32 {
|
||||
return Err(Error::InvalidStreamId(stream_id));
|
||||
}
|
||||
if !permitted_states.contains(&self.streams[stream_id as usize].state) {
|
||||
return Err(Error::UnexpectedState(
|
||||
self.streams[stream_id as usize].state,
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn common_stream_op(
|
||||
&mut self,
|
||||
stream_id: u32,
|
||||
expected_states: &[StreamState],
|
||||
new_state: StreamState,
|
||||
op: u32,
|
||||
) -> Result<()> {
|
||||
self.validate_stream_id(stream_id, expected_states)?;
|
||||
let msg = virtio_snd_pcm_hdr {
|
||||
hdr: virtio_snd_hdr { code: op.into() },
|
||||
stream_id: stream_id.into(),
|
||||
};
|
||||
seq_socket_send(&self.control_socket, msg)?;
|
||||
self.recv_cmd_status()?;
|
||||
self.streams[stream_id as usize].state = new_state;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn recv_cmd_status(&mut self) -> Result<()> {
|
||||
let mut status: virtio_snd_hdr = Default::default();
|
||||
self.control_socket
|
||||
.recv(status.as_mut_slice())
|
||||
.map_err(|e| Error::ServerIOError(e))?;
|
||||
if status.code.to_native() == VIRTIO_SND_S_OK {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::CommandFailed(status.code.to_native()))
|
||||
}
|
||||
}
|
||||
|
||||
fn request_and_cache_streams_info(&mut self) -> Result<()> {
|
||||
let num_streams = self.config.streams as usize;
|
||||
let info_size = std::mem::size_of::<virtio_snd_pcm_info>();
|
||||
let req = virtio_snd_query_info {
|
||||
hdr: virtio_snd_hdr {
|
||||
code: STREAM_INFO.into(),
|
||||
},
|
||||
start_id: 0u32.into(),
|
||||
count: (num_streams as u32).into(),
|
||||
size: (std::mem::size_of::<virtio_snd_query_info>() as u32).into(),
|
||||
};
|
||||
seq_socket_send(&self.control_socket, req)?;
|
||||
self.recv_cmd_status()?;
|
||||
let info_vec = self
|
||||
.control_socket
|
||||
.recv_as_vec()
|
||||
.map_err(|e| Error::ServerIOError(e))?;
|
||||
if info_vec.len() != num_streams * info_size {
|
||||
return Err(Error::ProtocolError(
|
||||
ProtocolErrorKind::UnexpectedMessageSize(num_streams * info_size, info_vec.len()),
|
||||
));
|
||||
}
|
||||
self.streams = info_vec
|
||||
.chunks(info_size)
|
||||
.enumerate()
|
||||
.map(|(id, info_buffer)| {
|
||||
// unwrap is safe because we checked the size of the vector above
|
||||
let virtio_stream_info = virtio_snd_pcm_info::from_slice(&info_buffer).unwrap();
|
||||
VioSStreamInfo::new(id as u32, &virtio_stream_info)
|
||||
})
|
||||
.collect();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct IoBufferQueue {
|
||||
socket: UnixSeqpacket,
|
||||
file: File,
|
||||
mmap: MemoryMapping,
|
||||
size: usize,
|
||||
next: usize,
|
||||
}
|
||||
|
||||
impl IoBufferQueue {
|
||||
fn new(socket: UnixSeqpacket, mut file: File) -> Result<IoBufferQueue> {
|
||||
let size = file
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(|e| Error::FileSizeError(e))? as usize;
|
||||
|
||||
let mmap = MemoryMappingBuilder::new(size)
|
||||
.from_descriptor(&file)
|
||||
.build()
|
||||
.map_err(|e| Error::ServerMmapError(e))?;
|
||||
|
||||
Ok(IoBufferQueue {
|
||||
socket,
|
||||
file,
|
||||
mmap,
|
||||
size,
|
||||
next: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn push_buffer(&mut self, src: &mut SharedMemory, offset: usize, size: usize) -> Result<usize> {
|
||||
if size > self.size {
|
||||
return Err(Error::OutOfSpace);
|
||||
}
|
||||
let shm_offset = if size > self.size - self.next {
|
||||
// Can't fit the new buffer at the end of the area, so put it at the beginning
|
||||
0
|
||||
} else {
|
||||
self.next
|
||||
};
|
||||
|
||||
let (src_mmap, mmap_offset) = mmap_buffer(src, offset, size)?;
|
||||
let src_slice = src_mmap
|
||||
.get_slice(mmap_offset, size)
|
||||
.map_err(|e| Error::VolatileMemoryError(e))?;
|
||||
let dst_slice = self
|
||||
.mmap
|
||||
.get_slice(shm_offset, size)
|
||||
.map_err(|e| Error::VolatileMemoryError(e))?;
|
||||
src_slice.copy_to_volatile_slice(dst_slice);
|
||||
self.next = shm_offset + size;
|
||||
Ok(shm_offset)
|
||||
}
|
||||
}
|
||||
|
||||
/// Description of a stream made available by the server.
|
||||
pub struct VioSStreamInfo {
|
||||
pub id: u32,
|
||||
pub hda_fn_nid: u32,
|
||||
pub features: u32,
|
||||
pub formats: u64,
|
||||
pub rates: u64,
|
||||
pub direction: u8,
|
||||
pub channels_min: u8,
|
||||
pub channels_max: u8,
|
||||
state: StreamState,
|
||||
}
|
||||
|
||||
impl VioSStreamInfo {
|
||||
fn new(id: u32, info: &virtio_snd_pcm_info) -> VioSStreamInfo {
|
||||
VioSStreamInfo {
|
||||
id,
|
||||
hda_fn_nid: info.hdr.hda_fn_nid.to_native(),
|
||||
features: info.features.to_native(),
|
||||
formats: info.formats.to_native(),
|
||||
rates: info.rates.to_native(),
|
||||
direction: info.direction,
|
||||
channels_min: info.channels_min,
|
||||
channels_max: info.channels_max,
|
||||
state: StreamState::Available,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub enum StreamState {
|
||||
Available,
|
||||
Acquired,
|
||||
Active,
|
||||
}
|
||||
|
||||
/// Groups the parameters used to configure a stream prior to using it.
|
||||
pub struct VioSStreamParams {
|
||||
pub buffer_bytes: u32,
|
||||
pub period_bytes: u32,
|
||||
pub features: u32,
|
||||
pub channels: u8,
|
||||
pub format: u8,
|
||||
pub rate: u8,
|
||||
}
|
||||
|
||||
impl Into<virtio_snd_pcm_set_params> for (u32, VioSStreamParams) {
|
||||
fn into(self) -> virtio_snd_pcm_set_params {
|
||||
virtio_snd_pcm_set_params {
|
||||
hdr: virtio_snd_pcm_hdr {
|
||||
hdr: virtio_snd_hdr {
|
||||
code: STREAM_SET_PARAMS.into(),
|
||||
},
|
||||
stream_id: self.0.into(),
|
||||
},
|
||||
buffer_bytes: self.1.buffer_bytes.into(),
|
||||
period_bytes: self.1.period_bytes.into(),
|
||||
features: self.1.features.into(),
|
||||
channels: self.1.channels,
|
||||
format: self.1.format,
|
||||
rate: self.1.rate,
|
||||
padding: 0u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory map a shared memory object to access an audio buffer. The buffer may not be located at an
|
||||
/// offset aligned to page size, so the offset within the mapped region is returned along with the
|
||||
/// MemoryMapping struct.
|
||||
fn mmap_buffer(
|
||||
src: &mut SharedMemory,
|
||||
offset: usize,
|
||||
size: usize,
|
||||
) -> Result<(MemoryMapping, usize)> {
|
||||
// If the buffer is not aligned to page size a bigger region needs to be mapped.
|
||||
let aligned_offset = offset & !(base::pagesize() - 1);
|
||||
let offset_from_mapping_start = offset - aligned_offset;
|
||||
let extended_size = size + offset_from_mapping_start;
|
||||
|
||||
let mmap = MemoryMappingBuilder::new(extended_size)
|
||||
.offset(aligned_offset as u64)
|
||||
.from_descriptor(src)
|
||||
.build()
|
||||
.map_err(|e| Error::GuestMmapError(e))?;
|
||||
|
||||
Ok((mmap, offset_from_mapping_start))
|
||||
}
|
||||
|
||||
fn seq_socket_send<T: DataInit>(socket: &UnixSeqpacket, data: T) -> Result<()> {
|
||||
loop {
|
||||
let send_res = socket.send(data.as_slice());
|
||||
if let Err(e) = send_res {
|
||||
match e.kind() {
|
||||
// Retry if interrupted
|
||||
IOErrorKind::Interrupted => continue,
|
||||
_ => return Err(Error::ServerIOError(e)),
|
||||
}
|
||||
}
|
||||
// Success
|
||||
break;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const VIOS_VERSION: u32 = 1;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct VioSConfig {
|
||||
version: u32,
|
||||
jacks: u32,
|
||||
streams: u32,
|
||||
chmaps: u32,
|
||||
}
|
||||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl DataInit for VioSConfig {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct IoTransferMsg {
|
||||
io_xfer: virtio_snd_pcm_xfer,
|
||||
buffer_offset: u32,
|
||||
buffer_len: u32,
|
||||
}
|
||||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl DataInit for IoTransferMsg {}
|
||||
|
||||
impl IoTransferMsg {
|
||||
fn new(stream_id: u32, buffer_offset: usize, buffer_len: usize) -> IoTransferMsg {
|
||||
IoTransferMsg {
|
||||
io_xfer: virtio_snd_pcm_xfer {
|
||||
stream_id: stream_id.into(),
|
||||
},
|
||||
buffer_offset: buffer_offset as u32,
|
||||
buffer_len: buffer_len as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct IoStatusMsg {
|
||||
status: virtio_snd_pcm_status,
|
||||
buffer_offset: u32,
|
||||
consumed_len: u32,
|
||||
}
|
||||
// Safe because it only has data and has no implicit padding.
|
||||
unsafe impl DataInit for IoStatusMsg {}
|
13
seccomp/aarch64/vios_audio_device.policy
Normal file
13
seccomp/aarch64/vios_audio_device.policy
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Copyright 2021 The Chromium OS Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
@include /usr/share/policy/crosvm/common_device.policy
|
||||
|
||||
clock_gettime: 1
|
||||
clock_nanosleep: 1
|
||||
lseek: 1
|
||||
openat: return ENOENT
|
||||
prlimit64: 1
|
||||
sched_setscheduler: 1
|
||||
setrlimit: 1
|
14
seccomp/arm/vios_audio_device.policy
Normal file
14
seccomp/arm/vios_audio_device.policy
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Copyright 2021 The Chromium OS Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
@include /usr/share/policy/crosvm/common_device.policy
|
||||
|
||||
clock_gettime: 1
|
||||
clock_nanosleep: 1
|
||||
lseek: 1
|
||||
open: return ENOENT
|
||||
openat: return ENOENT
|
||||
prlimit64: 1
|
||||
sched_setscheduler: 1
|
||||
setrlimit: 1
|
14
seccomp/x86_64/vios_audio_device.policy
Normal file
14
seccomp/x86_64/vios_audio_device.policy
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Copyright 2021 The Chromium OS Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
@include /usr/share/policy/crosvm/common_device.policy
|
||||
|
||||
clock_gettime: 1
|
||||
clock_nanosleep: 1
|
||||
lseek: 1
|
||||
open: return ENOENT
|
||||
openat: return ENOENT
|
||||
prlimit64: 1
|
||||
sched_setscheduler: 1
|
||||
setrlimit: 1
|
|
@ -51,7 +51,7 @@ use std::result;
|
|||
pub enum Error {
|
||||
/// There was a syntax error with the argument.
|
||||
Syntax(String),
|
||||
/// The argumen's name is unused.
|
||||
/// The argument's name is unused.
|
||||
UnknownArgument(String),
|
||||
/// The argument was required.
|
||||
ExpectedArgument(String),
|
||||
|
|
46
src/main.rs
46
src/main.rs
|
@ -13,6 +13,7 @@ use std::fs::{File, OpenOptions};
|
|||
use std::io::{BufRead, BufReader};
|
||||
use std::num::ParseIntError;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::string::String;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
@ -396,6 +397,16 @@ fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> {
|
|||
argument::Error::Syntax(format!("invalid capture option: {}", e))
|
||||
})?;
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
"server" => {
|
||||
ac97_params.vios_server_path =
|
||||
Some(
|
||||
PathBuf::from_str(v).map_err(|e| argument::Error::InvalidValue {
|
||||
value: v.to_string(),
|
||||
expected: e.to_string(),
|
||||
})?,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
return Err(argument::Error::UnknownArgument(format!(
|
||||
"unknown ac97 parameter {}",
|
||||
|
@ -405,6 +416,25 @@ fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> {
|
|||
}
|
||||
}
|
||||
|
||||
// server is required for and exclusive to vios backend
|
||||
#[cfg(target_os = "linux")]
|
||||
match ac97_params.backend {
|
||||
Ac97Backend::VIOS => {
|
||||
if ac97_params.vios_server_path.is_none() {
|
||||
return Err(argument::Error::ExpectedArgument(String::from(
|
||||
"server argument is required for VIOS backend",
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if ac97_params.vios_server_path.is_some() {
|
||||
return Err(argument::Error::UnexpectedValue(String::from(
|
||||
"server argument is exclusive to the VIOS backend",
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ac97_params)
|
||||
}
|
||||
|
||||
|
@ -1605,13 +1635,14 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
|
|||
Argument::value("net-vq-pairs", "N", "virtio net virtual queue paris. (default: 1)"),
|
||||
#[cfg(feature = "audio")]
|
||||
Argument::value("ac97",
|
||||
"[backend=BACKEND,capture=true,capture_effect=EFFECT]",
|
||||
"[backend=BACKEND,capture=true,capture_effect=EFFECT,shm-fd=FD,client-fd=FD,server-fd=FD]",
|
||||
"Comma separated key=value pairs for setting up Ac97 devices. Can be given more than once .
|
||||
Possible key values:
|
||||
backend=(null, cras) - Where to route the audio device. If not provided, backend will default to null.
|
||||
`null` for /dev/null, and cras for CRAS server.
|
||||
backend=(null, cras, vios) - Where to route the audio device. If not provided, backend will default to null.
|
||||
`null` for /dev/null, cras for CRAS server and vios for VioS server.
|
||||
capture - Enable audio capture
|
||||
capture_effects - | separated effects to be enabled for recording. The only supported effect value now is EchoCancellation or aec."),
|
||||
capture_effects - | separated effects to be enabled for recording. The only supported effect value now is EchoCancellation or aec.
|
||||
server - The to the VIOS server (unix socket)."),
|
||||
Argument::value("serial",
|
||||
"type=TYPE,[hardware=HW,num=NUM,path=PATH,input=PATH,console,earlycon,stdin]",
|
||||
"Comma separated key=value pairs for setting up serial devices. Can be given more than once.
|
||||
|
@ -2401,6 +2432,13 @@ mod tests {
|
|||
parse_ac97_options("backend=cras,capture=true").expect("parse should have succeded");
|
||||
}
|
||||
|
||||
#[cfg(feature = "audio")]
|
||||
#[test]
|
||||
fn parse_ac97_vios_valid() {
|
||||
parse_ac97_options("backend=vios,server=/path/to/server")
|
||||
.expect("parse should have succeded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_serial_vaild() {
|
||||
parse_serial_options("type=syslog,num=1,console=true,stdin=true")
|
||||
|
|
Loading…
Reference in a new issue