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:
Jorge E. Moreira 2020-12-02 18:25:53 -08:00 committed by Commit Bot
parent 52e51b6539
commit 359e7de9a1
15 changed files with 950 additions and 10 deletions

3
Cargo.lock generated
View file

@ -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]]

View file

@ -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" }

View file

@ -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);

View file

@ -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={}: {}",

View file

@ -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;

View file

@ -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,

View file

@ -5,3 +5,5 @@
pub mod constants;
pub mod layout;
pub mod vios_backend;

View 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::*;

View 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);
}
}
}

View 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 {}

View 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

View 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

View 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

View file

@ -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),

View file

@ -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")