You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1309 lines
48 KiB
1309 lines
48 KiB
// Copyright 2019 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 std::convert::TryFrom;
|
|
use std::io;
|
|
use std::mem;
|
|
use std::os::unix::io::{AsRawFd, RawFd};
|
|
use std::ptr;
|
|
use std::ptr::NonNull;
|
|
use std::slice;
|
|
use std::sync::atomic::{self, Ordering};
|
|
use std::thread;
|
|
|
|
use cras_sys::gen::{
|
|
audio_dev_debug_info, audio_stream_debug_info, cras_audio_shm_header, cras_iodev_info,
|
|
cras_ionode_info, cras_server_state, CRAS_MAX_IODEVS, CRAS_MAX_IONODES, CRAS_NUM_SHM_BUFFERS,
|
|
CRAS_SERVER_STATE_VERSION, CRAS_SHM_BUFFERS_MASK, MAX_DEBUG_DEVS, MAX_DEBUG_STREAMS,
|
|
};
|
|
use cras_sys::{
|
|
AudioDebugInfo, AudioDevDebugInfo, AudioStreamDebugInfo, CrasIodevInfo, CrasIonodeInfo,
|
|
};
|
|
use data_model::{VolatileRef, VolatileSlice};
|
|
use sys_util::warn;
|
|
|
|
/// A structure wrapping a fd which contains a shared `cras_audio_shm_header`.
|
|
/// * `shm_fd` - A shared memory fd contains a `cras_audio_shm_header`
|
|
pub struct CrasAudioShmHeaderFd {
|
|
fd: CrasShmFd,
|
|
}
|
|
|
|
impl CrasAudioShmHeaderFd {
|
|
/// Creates a `CrasAudioShmHeaderFd` by shared memory fd
|
|
/// # Arguments
|
|
/// * `fd` - A shared memory file descriptor, which will be owned by the resulting structure and
|
|
/// the fd will be closed on drop.
|
|
///
|
|
/// # Returns
|
|
/// A structure wrapping a `CrasShmFd` with the input fd and `size` which equals to
|
|
/// the size of `cras_audio_shm_header`.
|
|
///
|
|
/// To use this function safely, we need to make sure
|
|
/// - The input fd is a valid shared memory fd.
|
|
/// - The input shared memory fd won't be used by others.
|
|
/// - The shared memory area in the input fd contains a `cras_audio_shm_header`.
|
|
pub unsafe fn new(fd: libc::c_int) -> Self {
|
|
Self {
|
|
fd: CrasShmFd::new(fd, mem::size_of::<cras_audio_shm_header>()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A wrapper for the raw structure `cras_audio_shm_header` with
|
|
/// size information for the separate audio samples shm area and several
|
|
/// `VolatileRef` to sub fields for safe access to the header.
|
|
pub struct CrasAudioHeader<'a> {
|
|
addr: *mut libc::c_void,
|
|
/// Size of the buffer for samples in CrasAudioBuffer
|
|
samples_len: usize,
|
|
used_size: VolatileRef<'a, u32>,
|
|
frame_size: VolatileRef<'a, u32>,
|
|
read_buf_idx: VolatileRef<'a, u32>,
|
|
write_buf_idx: VolatileRef<'a, u32>,
|
|
read_offset: [VolatileRef<'a, u32>; CRAS_NUM_SHM_BUFFERS as usize],
|
|
write_offset: [VolatileRef<'a, u32>; CRAS_NUM_SHM_BUFFERS as usize],
|
|
buffer_offset: [VolatileRef<'a, u64>; CRAS_NUM_SHM_BUFFERS as usize],
|
|
}
|
|
|
|
// It is safe to send audio buffers between threads as this struct has exclusive ownership of the
|
|
// pointers contained in it.
|
|
unsafe impl<'a> Send for CrasAudioHeader<'a> {}
|
|
|
|
/// An unsafe macro for getting `VolatileRef` for a field from a given NonNull pointer.
|
|
/// It Supports
|
|
/// - Nested sub-field
|
|
/// - Element of an array field
|
|
///
|
|
/// To use this macro safely, we need to
|
|
/// - Make sure the pointer address is readable and writable for its structure.
|
|
/// - Make sure all `VolatileRef`s generated from this macro have exclusive ownership for the same
|
|
/// pointer.
|
|
#[macro_export]
|
|
macro_rules! vref_from_addr {
|
|
($addr:ident, $($field:ident).*) => {
|
|
VolatileRef::new(&mut $addr.as_mut().$($field).* as *mut _)
|
|
};
|
|
|
|
($addr:ident, $field:ident[$idx:tt]) => {
|
|
VolatileRef::new(&mut $addr.as_mut().$field[$idx] as *mut _)
|
|
};
|
|
}
|
|
|
|
// Generates error when an index is out of range.
|
|
fn index_out_of_range() -> io::Error {
|
|
io::Error::new(io::ErrorKind::InvalidInput, "Index out of range.")
|
|
}
|
|
|
|
impl<'a> CrasAudioHeader<'a> {
|
|
// Creates a `CrasAudioHeader` with given `CrasAudioShmHeaderFd` and `samples_len`
|
|
fn new(header_fd: CrasAudioShmHeaderFd, samples_len: usize) -> io::Result<Self> {
|
|
// Safe because the creator of CrasAudioShmHeaderFd already
|
|
// ensured that header_fd contains a cras_audio_shm_header.
|
|
let mmap_addr = unsafe {
|
|
cras_mmap(
|
|
header_fd.fd.size,
|
|
libc::PROT_READ | libc::PROT_WRITE,
|
|
header_fd.fd.as_raw_fd(),
|
|
)?
|
|
};
|
|
|
|
let mut addr = NonNull::new(mmap_addr as *mut cras_audio_shm_header)
|
|
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Failed to create header."))?;
|
|
|
|
// Safe because we know that mmap_addr (contained in addr) contains a
|
|
// cras_audio_shm_header, and the mapped area will be exclusively
|
|
// owned by this struct.
|
|
unsafe {
|
|
Ok(CrasAudioHeader {
|
|
addr: addr.as_ptr() as *mut libc::c_void,
|
|
samples_len,
|
|
used_size: vref_from_addr!(addr, config.used_size),
|
|
frame_size: vref_from_addr!(addr, config.frame_bytes),
|
|
read_buf_idx: vref_from_addr!(addr, read_buf_idx),
|
|
write_buf_idx: vref_from_addr!(addr, write_buf_idx),
|
|
read_offset: [
|
|
vref_from_addr!(addr, read_offset[0]),
|
|
vref_from_addr!(addr, read_offset[1]),
|
|
],
|
|
write_offset: [
|
|
vref_from_addr!(addr, write_offset[0]),
|
|
vref_from_addr!(addr, write_offset[1]),
|
|
],
|
|
buffer_offset: [
|
|
vref_from_addr!(addr, buffer_offset[0]),
|
|
vref_from_addr!(addr, buffer_offset[1]),
|
|
],
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Calculates the length of a buffer with the given offset. This length will
|
|
/// be `used_size`, unless the offset is closer than `used_size` to the end
|
|
/// of samples, in which case the length will be as long as possible.
|
|
///
|
|
/// If that buffer length is invalid (too small to hold a frame of audio data),
|
|
/// then returns an error.
|
|
/// The returned buffer length will be rounded down to a multiple of `frame_size`.
|
|
fn buffer_len_from_offset(&self, offset: usize) -> io::Result<usize> {
|
|
if offset > self.samples_len {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::InvalidInput,
|
|
format!(
|
|
"Buffer offset {} exceeds the length of samples area ({}).",
|
|
offset, self.samples_len
|
|
),
|
|
));
|
|
}
|
|
|
|
let used_size = self.get_used_size();
|
|
let frame_size = self.get_frame_size();
|
|
|
|
// We explicitly allow a buffer shorter than used_size, but only
|
|
// at the end of the samples area.
|
|
// This is useful if we're playing a file where the number of samples is
|
|
// not a multiple of used_size (meaning the length of the samples area
|
|
// won't be either). Then, the last buffer played will be smaller than
|
|
// used_size.
|
|
let mut buffer_length = used_size.min(self.samples_len - offset);
|
|
if buffer_length < frame_size {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::InvalidInput,
|
|
format!(
|
|
"Buffer offset {} gives buffer length {} smaller than frame size {}.",
|
|
offset, buffer_length, frame_size
|
|
),
|
|
));
|
|
}
|
|
|
|
// Round buffer_length down to a multiple of frame size
|
|
buffer_length = buffer_length / frame_size * frame_size;
|
|
Ok(buffer_length)
|
|
}
|
|
|
|
/// Gets the base of the write buffer and the writable length (rounded to `frame_size`).
|
|
/// Does not take into account the write offset.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * (`usize`, `usize`) - write buffer base as an offset from the start of
|
|
/// the samples area and buffer length in bytes.
|
|
pub fn get_write_offset_and_len(&self) -> io::Result<(usize, usize)> {
|
|
let idx = self.get_write_buf_idx() as usize;
|
|
let offset = self.get_buffer_offset(idx)?;
|
|
let len = self.buffer_len_from_offset(offset)?;
|
|
|
|
Ok((offset, len))
|
|
}
|
|
|
|
/// Gets the buffer offset of the read buffer.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `usize` - read offset in bytes
|
|
pub fn get_read_buffer_offset(&self) -> io::Result<usize> {
|
|
let idx = self.get_read_buf_idx() as usize;
|
|
self.get_buffer_offset(idx)
|
|
}
|
|
|
|
/// Gets the offset of a buffer from the start of samples.
|
|
///
|
|
/// # Arguments
|
|
/// `index` - 0 <= `index` < `CRAS_NUM_SHM_BUFFERS`. The index of the buffer
|
|
/// for which we want the `buffer_offset`.
|
|
///
|
|
/// # Returns
|
|
/// * `usize` - buffer offset in bytes
|
|
fn get_buffer_offset(&self, idx: usize) -> io::Result<usize> {
|
|
let buffer_offset = self
|
|
.buffer_offset
|
|
.get(idx)
|
|
.ok_or_else(index_out_of_range)?
|
|
.load() as usize;
|
|
self.check_buffer_offset(idx, buffer_offset)?;
|
|
Ok(buffer_offset)
|
|
}
|
|
|
|
/// Gets the number of bytes per frame from the shared memory structure.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `usize` - Number of bytes per frame
|
|
pub fn get_frame_size(&self) -> usize {
|
|
self.frame_size.load() as usize
|
|
}
|
|
|
|
/// Gets the max size in bytes of each shared memory buffer within
|
|
/// the samples area.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `usize` - Value of `used_size` fetched from the shared memory header.
|
|
pub fn get_used_size(&self) -> usize {
|
|
self.used_size.load() as usize
|
|
}
|
|
|
|
/// Gets the index of the current written buffer.
|
|
///
|
|
/// # Returns
|
|
/// `u32` - the returned index is less than `CRAS_NUM_SHM_BUFFERS`.
|
|
fn get_write_buf_idx(&self) -> u32 {
|
|
self.write_buf_idx.load() & CRAS_SHM_BUFFERS_MASK
|
|
}
|
|
|
|
fn get_read_buf_idx(&self) -> u32 {
|
|
self.read_buf_idx.load() & CRAS_SHM_BUFFERS_MASK
|
|
}
|
|
|
|
/// Switches the written buffer.
|
|
fn switch_write_buf_idx(&mut self) {
|
|
self.write_buf_idx
|
|
.store(self.get_write_buf_idx() as u32 ^ 1u32)
|
|
}
|
|
|
|
/// Switches the buffer to read.
|
|
fn switch_read_buf_idx(&mut self) {
|
|
self.read_buf_idx
|
|
.store(self.get_read_buf_idx() as u32 ^ 1u32)
|
|
}
|
|
|
|
/// Checks if the offset value for setting write_offset or read_offset is
|
|
/// out of range or not.
|
|
///
|
|
/// # Arguments
|
|
/// `idx` - The index of the buffer for which we're checking the offset.
|
|
/// `offset` - 0 <= `offset` <= `used_size` && `buffer_offset[idx]` + `offset` <=
|
|
/// `samples_len`. Writable or readable size equals to 0 when offset equals
|
|
/// to `used_size`.
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if `offset` is out of range or if idx is not a valid
|
|
/// buffer idx.
|
|
fn check_rw_offset(&self, idx: usize, offset: u32) -> io::Result<()> {
|
|
let buffer_len = self.buffer_len_from_offset(self.get_buffer_offset(idx)?)?;
|
|
if offset as usize > buffer_len {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::InvalidInput,
|
|
format!(
|
|
"Offset {} is larger than buffer size {}.",
|
|
offset, buffer_len
|
|
),
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Sets `write_offset[idx]` to the count of written bytes.
|
|
///
|
|
/// # Arguments
|
|
/// `idx` - 0 <= `idx` < `CRAS_NUM_SHM_BUFFERS`
|
|
/// `offset` - 0 <= `offset` <= `used_size` && `offset` + `used_size` <=
|
|
/// `samples_len`. Writable size equals to 0 when offset equals to
|
|
/// `used_size`.
|
|
///
|
|
/// # Errors
|
|
/// Returns an error if `offset` is out of range.
|
|
fn set_write_offset(&mut self, idx: usize, offset: u32) -> io::Result<()> {
|
|
self.check_rw_offset(idx, offset)?;
|
|
let write_offset = self.write_offset.get(idx).ok_or_else(index_out_of_range)?;
|
|
write_offset.store(offset);
|
|
Ok(())
|
|
}
|
|
|
|
/// Sets `read_offset[idx]` to count of written bytes.
|
|
///
|
|
/// # Arguments
|
|
/// `idx` - 0 <= `idx` < `CRAS_NUM_SHM_BUFFERS`
|
|
/// `offset` - 0 <= `offset` <= `used_size` && `offset` + `used_size` <=
|
|
/// `samples_len`. Readable size equals to 0 when offset equals to
|
|
/// `used_size`.
|
|
///
|
|
/// # Errors
|
|
/// Returns error if index out of range.
|
|
fn set_read_offset(&mut self, idx: usize, offset: u32) -> io::Result<()> {
|
|
self.check_rw_offset(idx, offset)?;
|
|
let read_offset = self.read_offset.get(idx).ok_or_else(index_out_of_range)?;
|
|
read_offset.store(offset);
|
|
Ok(())
|
|
}
|
|
|
|
/// Check that `offset` is a valid buffer offset for the buffer at `idx`
|
|
/// An offset is not valid if it is
|
|
/// * outside of the samples area
|
|
/// * overlaps some other buffer `[other_offset, other_offset + used_size)`
|
|
/// * is close enough to the end of the samples area that the buffer would
|
|
/// be shorter than `frame_size`.
|
|
fn check_buffer_offset(&self, idx: usize, offset: usize) -> io::Result<()> {
|
|
let start = offset;
|
|
let end = start + self.buffer_len_from_offset(start)?;
|
|
|
|
let other_idx = (idx ^ 1) as usize;
|
|
let other_start = self
|
|
.buffer_offset
|
|
.get(other_idx)
|
|
.ok_or_else(index_out_of_range)?
|
|
.load() as usize;
|
|
let other_end = other_start + self.buffer_len_from_offset(other_start)?;
|
|
if start < other_end && other_start < end {
|
|
// Special case: occasionally we get the same buffer offset twice
|
|
// from the intel8x0 kernel driver in crosvm's AC97 device, and we
|
|
// don't want to crash in that case.
|
|
if start == other_start && end == other_end {
|
|
warn!(
|
|
"Setting buffer {} to same index/offset as buffer {}, [{}, {})",
|
|
idx, other_idx, other_start, other_end
|
|
);
|
|
} else {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::InvalidInput,
|
|
format!(
|
|
"Setting buffer {} to [{}, {}) overlaps buffer {} at [{}, {})",
|
|
idx, start, end, other_idx, other_start, other_end,
|
|
),
|
|
));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Sets the location of the audio buffer `idx` within the samples area to
|
|
/// `offset`, so that CRAS will read/write samples for that buffer from that
|
|
/// offset.
|
|
///
|
|
/// # Arguments
|
|
/// `idx` - 0 <= `idx` < `CRAS_NUM_SHM_BUFFERS`
|
|
/// `offset` - 0 <= `offset` && `offset` + `frame_size` <= `samples_len`
|
|
///
|
|
/// # Errors
|
|
/// If `idx` is out of range
|
|
/// If the offset is invalid, which can happen if `offset` is
|
|
/// * outside of the samples area
|
|
/// * overlaps some other buffer `[other_offset, other_offset + used_size)`
|
|
/// * is close enough to the end of the samples area that the buffer would
|
|
/// be shorter than `frame_size`.
|
|
pub fn set_buffer_offset(&mut self, idx: usize, offset: usize) -> io::Result<()> {
|
|
self.check_buffer_offset(idx, offset)?;
|
|
|
|
let buffer_offset = self.buffer_offset.get(idx).ok_or_else(index_out_of_range)?;
|
|
buffer_offset.store(offset as u64);
|
|
Ok(())
|
|
}
|
|
|
|
/// Commits written frames by switching the current buffer to the other one
|
|
/// after samples are ready and indexes of current buffer are all set.
|
|
/// - Sets `write_offset` of current buffer to `frame_count * frame_size`
|
|
/// - Sets `read_offset` of current buffer to `0`.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `frame_count` - Number of frames written to the current buffer
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// * Returns error if `frame_count` is larger than buffer size
|
|
///
|
|
/// This function is safe because we switch `write_buf_idx` after letting
|
|
/// `write_offset` and `read_offset` ready and we read / write shared memory
|
|
/// variables with volatile operations.
|
|
pub fn commit_written_frames(&mut self, frame_count: u32) -> io::Result<()> {
|
|
// Uses `u64` to prevent possible overflow
|
|
let byte_count = frame_count as u64 * self.get_frame_size() as u64;
|
|
if byte_count > self.get_used_size() as u64 {
|
|
Err(io::Error::new(
|
|
io::ErrorKind::InvalidInput,
|
|
"frame_count * frame_size is larger than used_size",
|
|
))
|
|
} else {
|
|
let idx = self.get_write_buf_idx() as usize;
|
|
// Sets `write_offset` of current buffer to frame_count * frame_size
|
|
self.set_write_offset(idx, byte_count as u32)?;
|
|
// Sets `read_offset` of current buffer to `0`.
|
|
self.set_read_offset(idx, 0)?;
|
|
// Switch to the other buffer
|
|
self.switch_write_buf_idx();
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Get readable frames in current buffer.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `usize` - number of readable frames.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns error if index out of range.
|
|
pub fn get_readable_frames(&self) -> io::Result<usize> {
|
|
let idx = self.get_read_buf_idx() as usize;
|
|
let read_offset = self.read_offset.get(idx).ok_or_else(index_out_of_range)?;
|
|
let write_offset = self.write_offset.get(idx).ok_or_else(index_out_of_range)?;
|
|
let nframes =
|
|
(write_offset.load() as i32 - read_offset.load() as i32) / self.get_frame_size() as i32;
|
|
if nframes < 0 {
|
|
Ok(0)
|
|
} else {
|
|
Ok(nframes as usize)
|
|
}
|
|
}
|
|
|
|
/// Commit read frames from reader, .
|
|
/// - Sets `read_offset` of current buffer to `read_offset + frame_count * frame_size`.
|
|
/// If `read_offset` is larger than or equal to `write_offset`, then
|
|
/// - Sets `read_offset` and `write_offset` to `0` and switch `read_buf_idx`.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `frame_count` - Read frames in current read buffer.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns error if index out of range.
|
|
pub fn commit_read_frames(&mut self, frame_count: u32) -> io::Result<()> {
|
|
let idx = self.get_read_buf_idx() as usize;
|
|
let read_offset = self.read_offset.get(idx).ok_or_else(index_out_of_range)?;
|
|
let write_offset = self.write_offset.get(idx).ok_or_else(index_out_of_range)?;
|
|
read_offset.store(read_offset.load() + frame_count * self.get_frame_size() as u32);
|
|
if read_offset.load() >= write_offset.load() {
|
|
read_offset.store(0);
|
|
write_offset.store(0);
|
|
self.switch_read_buf_idx();
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<'a> Drop for CrasAudioHeader<'a> {
|
|
fn drop(&mut self) {
|
|
// Safe because all references must be gone by the time drop is called.
|
|
unsafe {
|
|
libc::munmap(self.addr as *mut _, mem::size_of::<cras_audio_shm_header>());
|
|
}
|
|
}
|
|
}
|
|
|
|
// To use this safely, we need to make sure
|
|
// - The given fd contains valid space which is larger than `len` + `offset`
|
|
unsafe fn cras_mmap_offset(
|
|
len: usize,
|
|
prot: libc::c_int,
|
|
fd: libc::c_int,
|
|
offset: usize,
|
|
) -> io::Result<*mut libc::c_void> {
|
|
if offset > libc::off_t::max_value() as usize {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::InvalidInput,
|
|
"Requested offset is out of range of `libc::off_t`.",
|
|
));
|
|
}
|
|
// It's safe because we handle its returned results.
|
|
match libc::mmap(
|
|
ptr::null_mut(),
|
|
len,
|
|
prot,
|
|
libc::MAP_SHARED,
|
|
fd,
|
|
offset as libc::off_t,
|
|
) {
|
|
libc::MAP_FAILED => Err(io::Error::last_os_error()),
|
|
shm_ptr => Ok(shm_ptr),
|
|
}
|
|
}
|
|
|
|
// To use this safely, we need to make sure
|
|
// - The given fd contains valid space which is larger than `len`
|
|
unsafe fn cras_mmap(
|
|
len: usize,
|
|
prot: libc::c_int,
|
|
fd: libc::c_int,
|
|
) -> io::Result<*mut libc::c_void> {
|
|
cras_mmap_offset(len, prot, fd, 0)
|
|
}
|
|
|
|
/// An unsafe macro for getting a `VolatileSlice` representing an entire array
|
|
/// field from a given NonNull pointer.
|
|
///
|
|
/// To use this macro safely, we need to
|
|
/// - Make sure the pointer address is readable and writeable for its struct.
|
|
/// - Make sure all `VolatileSlice`s generated from this macro have exclusive ownership for the same
|
|
/// pointer.
|
|
/// - Make sure the length of the array field is non-zero.
|
|
#[macro_export]
|
|
macro_rules! vslice_from_addr {
|
|
($addr:ident, $($field:ident).*) => {{
|
|
let ptr = &mut $addr.as_mut().$($field).* as *mut _ as *mut u8;
|
|
let size = std::mem::size_of_val(&$addr.as_mut().$($field).*);
|
|
VolatileSlice::from_raw_parts(ptr, size)
|
|
}};
|
|
}
|
|
|
|
/// A structure that points to RO shared memory area - `cras_server_state`
|
|
/// The structure is created from a shared memory fd which contains the structure.
|
|
#[derive(Debug)]
|
|
pub struct CrasServerState<'a> {
|
|
addr: *mut libc::c_void,
|
|
volume: VolatileRef<'a, u32>,
|
|
mute: VolatileRef<'a, i32>,
|
|
num_output_devs: VolatileRef<'a, u32>,
|
|
output_devs: VolatileSlice<'a>,
|
|
num_input_devs: VolatileRef<'a, u32>,
|
|
input_devs: VolatileSlice<'a>,
|
|
num_output_nodes: VolatileRef<'a, u32>,
|
|
num_input_nodes: VolatileRef<'a, u32>,
|
|
output_nodes: VolatileSlice<'a>,
|
|
input_nodes: VolatileSlice<'a>,
|
|
update_count: VolatileRef<'a, u32>,
|
|
debug_info_num_devs: VolatileRef<'a, u32>,
|
|
debug_info_devs: VolatileSlice<'a>,
|
|
debug_info_num_streams: VolatileRef<'a, u32>,
|
|
debug_info_streams: VolatileSlice<'a>,
|
|
}
|
|
|
|
// It is safe to send server_state between threads as this struct has exclusive
|
|
// ownership of the shared memory area contained in it.
|
|
unsafe impl<'a> Send for CrasServerState<'a> {}
|
|
|
|
impl<'a> CrasServerState<'a> {
|
|
/// Create a CrasServerState
|
|
pub fn try_new(state_fd: CrasServerStateShmFd) -> io::Result<Self> {
|
|
// Safe because the creator of CrasServerStateShmFd already
|
|
// ensured that state_fd contains a cras_server_state.
|
|
let mmap_addr =
|
|
unsafe { cras_mmap(state_fd.fd.size, libc::PROT_READ, state_fd.fd.as_raw_fd())? };
|
|
|
|
let mut addr = NonNull::new(mmap_addr as *mut cras_server_state).ok_or_else(|| {
|
|
io::Error::new(io::ErrorKind::Other, "Failed to create CrasServerState.")
|
|
})?;
|
|
|
|
// Safe because we know that addr is a non-null pointer to cras_server_state.
|
|
let state_version = unsafe { vref_from_addr!(addr, state_version) };
|
|
if state_version.load() != CRAS_SERVER_STATE_VERSION {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::Other,
|
|
format!(
|
|
"CrasServerState version {} does not match expected version {}",
|
|
state_version.load(),
|
|
CRAS_SERVER_STATE_VERSION
|
|
),
|
|
));
|
|
}
|
|
|
|
// Safe because we know that mmap_addr (contained in addr) contains a
|
|
// cras_server_state, and the mapped area will be exclusively
|
|
// owned by this struct.
|
|
unsafe {
|
|
Ok(CrasServerState {
|
|
addr: addr.as_ptr() as *mut libc::c_void,
|
|
volume: vref_from_addr!(addr, volume),
|
|
mute: vref_from_addr!(addr, mute),
|
|
num_output_devs: vref_from_addr!(addr, num_output_devs),
|
|
num_input_devs: vref_from_addr!(addr, num_input_devs),
|
|
output_devs: vslice_from_addr!(addr, output_devs),
|
|
input_devs: vslice_from_addr!(addr, input_devs),
|
|
num_output_nodes: vref_from_addr!(addr, num_output_nodes),
|
|
num_input_nodes: vref_from_addr!(addr, num_input_nodes),
|
|
output_nodes: vslice_from_addr!(addr, output_nodes),
|
|
input_nodes: vslice_from_addr!(addr, input_nodes),
|
|
update_count: vref_from_addr!(addr, update_count),
|
|
debug_info_num_devs: vref_from_addr!(addr, audio_debug_info.num_devs),
|
|
debug_info_devs: vslice_from_addr!(addr, audio_debug_info.devs),
|
|
debug_info_num_streams: vref_from_addr!(addr, audio_debug_info.num_streams),
|
|
debug_info_streams: vslice_from_addr!(addr, audio_debug_info.streams),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Gets the system volume.
|
|
///
|
|
/// Read the current value for system volume from shared memory.
|
|
pub fn get_system_volume(&self) -> u32 {
|
|
self.volume.load()
|
|
}
|
|
|
|
/// Gets the system mute.
|
|
///
|
|
/// Read the current value for system mute from shared memory.
|
|
pub fn get_system_mute(&self) -> bool {
|
|
self.mute.load() != 0
|
|
}
|
|
|
|
/// Runs a closure safely such that it can be sure that the server state
|
|
/// was not updated during the read.
|
|
/// This can be used for an "atomic" read of non-atomic data from the
|
|
/// state shared memory.
|
|
fn synchronized_state_read<F, T>(&self, mut func: F) -> T
|
|
where
|
|
F: FnMut() -> T,
|
|
{
|
|
// Waits until the server has completed a state update before returning
|
|
// the current update count.
|
|
let begin_server_state_read = || -> u32 {
|
|
loop {
|
|
let update_count = self.update_count.load();
|
|
if update_count % 2 == 0 {
|
|
atomic::fence(Ordering::Acquire);
|
|
return update_count;
|
|
} else {
|
|
thread::yield_now();
|
|
}
|
|
}
|
|
};
|
|
|
|
// Checks that the update count has not changed since the start
|
|
// of the server state read.
|
|
let end_server_state_read = |count: u32| -> bool {
|
|
let result = count == self.update_count.load();
|
|
atomic::fence(Ordering::Release);
|
|
result
|
|
};
|
|
|
|
// Get the state's update count and run the provided closure.
|
|
// If the update count has not changed once the closure is finished,
|
|
// return the result, otherwise repeat the process.
|
|
loop {
|
|
let update_count = begin_server_state_read();
|
|
let result = func();
|
|
if end_server_state_read(update_count) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Gets a list of output devices
|
|
///
|
|
/// Read a list of the currently attached output devices from shared memory.
|
|
pub fn output_devices(&self) -> impl Iterator<Item = CrasIodevInfo> {
|
|
let mut devs: Vec<cras_iodev_info> = vec![Default::default(); CRAS_MAX_IODEVS as usize];
|
|
let num_devs = self.synchronized_state_read(|| {
|
|
self.output_devs.copy_to(&mut devs);
|
|
self.num_output_devs.load()
|
|
});
|
|
devs.into_iter()
|
|
.take(num_devs as usize)
|
|
.map(CrasIodevInfo::from)
|
|
}
|
|
|
|
/// Gets a list of input devices
|
|
///
|
|
/// Read a list of the currently attached input devices from shared memory.
|
|
pub fn input_devices(&self) -> impl Iterator<Item = CrasIodevInfo> {
|
|
let mut devs: Vec<cras_iodev_info> = vec![Default::default(); CRAS_MAX_IODEVS as usize];
|
|
let num_devs = self.synchronized_state_read(|| {
|
|
self.input_devs.copy_to(&mut devs);
|
|
self.num_input_devs.load()
|
|
});
|
|
devs.into_iter()
|
|
.take(num_devs as usize)
|
|
.map(CrasIodevInfo::from)
|
|
}
|
|
|
|
/// Gets a list of output nodes
|
|
///
|
|
/// Read a list of the currently attached output nodes from shared memory.
|
|
pub fn output_nodes(&self) -> impl Iterator<Item = CrasIonodeInfo> {
|
|
let mut nodes: Vec<cras_ionode_info> = vec![Default::default(); CRAS_MAX_IONODES as usize];
|
|
let num_nodes = self.synchronized_state_read(|| {
|
|
self.output_nodes.copy_to(&mut nodes);
|
|
self.num_output_nodes.load()
|
|
});
|
|
nodes
|
|
.into_iter()
|
|
.take(num_nodes as usize)
|
|
.map(CrasIonodeInfo::from)
|
|
}
|
|
|
|
/// Gets a list of input nodes
|
|
///
|
|
/// Read a list of the currently attached input nodes from shared memory.
|
|
pub fn input_nodes(&self) -> impl Iterator<Item = CrasIonodeInfo> {
|
|
let mut nodes: Vec<cras_ionode_info> = vec![Default::default(); CRAS_MAX_IONODES as usize];
|
|
let num_nodes = self.synchronized_state_read(|| {
|
|
self.input_nodes.copy_to(&mut nodes);
|
|
self.num_input_nodes.load()
|
|
});
|
|
nodes
|
|
.into_iter()
|
|
.take(num_nodes as usize)
|
|
.map(CrasIonodeInfo::from)
|
|
}
|
|
|
|
/// Get audio debug info
|
|
///
|
|
/// Loads the server's audio_debug_info struct and converts it into an
|
|
/// idiomatic rust representation.
|
|
///
|
|
/// # Errors
|
|
/// * If any of the stream debug information structs are invalid.
|
|
pub fn get_audio_debug_info(&self) -> Result<AudioDebugInfo, cras_sys::Error> {
|
|
let mut devs: Vec<audio_dev_debug_info> = vec![Default::default(); MAX_DEBUG_DEVS as usize];
|
|
let mut streams: Vec<audio_stream_debug_info> =
|
|
vec![Default::default(); MAX_DEBUG_STREAMS as usize];
|
|
let (num_devs, num_streams) = self.synchronized_state_read(|| {
|
|
self.debug_info_devs.copy_to(&mut devs);
|
|
self.debug_info_streams.copy_to(&mut streams);
|
|
(
|
|
self.debug_info_num_devs.load(),
|
|
self.debug_info_num_streams.load(),
|
|
)
|
|
});
|
|
let dev_info = devs
|
|
.into_iter()
|
|
.take(num_devs as usize)
|
|
.map(AudioDevDebugInfo::from)
|
|
.collect();
|
|
let stream_info = streams
|
|
.into_iter()
|
|
.take(num_streams as usize)
|
|
.map(AudioStreamDebugInfo::try_from)
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
Ok(AudioDebugInfo::new(dev_info, stream_info))
|
|
}
|
|
}
|
|
|
|
impl<'a> Drop for CrasServerState<'a> {
|
|
/// Call `munmap` for `addr`.
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
// Safe because all references must be gone by the time drop is called.
|
|
libc::munmap(self.addr, mem::size_of::<cras_server_state>());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A structure holding the mapped shared memory area used to exchange
|
|
/// samples with CRAS. The shared memory is owned exclusively by this structure,
|
|
/// and will be cleaned up on drop.
|
|
/// * `addr` - The address of the mapped shared memory.
|
|
/// * `len` - Length of the mapped shared memory in bytes.
|
|
pub struct CrasAudioBuffer {
|
|
addr: *mut u8,
|
|
len: usize,
|
|
}
|
|
|
|
// It is safe to send audio buffers between threads as this struct has exclusive ownership of the
|
|
// shared memory area contained in it.
|
|
unsafe impl Send for CrasAudioBuffer {}
|
|
|
|
impl CrasAudioBuffer {
|
|
fn new(samples_fd: CrasShmFd) -> io::Result<Self> {
|
|
// This is safe because we checked that the size of the shm in samples_fd
|
|
// was at least samples_fd.size when it was created.
|
|
let addr = unsafe {
|
|
cras_mmap(
|
|
samples_fd.size,
|
|
libc::PROT_READ | libc::PROT_WRITE,
|
|
samples_fd.as_raw_fd(),
|
|
)? as *mut u8
|
|
};
|
|
Ok(Self {
|
|
addr,
|
|
len: samples_fd.size,
|
|
})
|
|
}
|
|
|
|
/// Provides a mutable slice to be filled with audio samples.
|
|
pub fn get_buffer(&mut self) -> &mut [u8] {
|
|
// This is safe because it takes a mutable reference to self, and there can only be one
|
|
// taken at a time. Although this is shared memory, the reader side must have it mapped as
|
|
// read only.
|
|
unsafe { slice::from_raw_parts_mut(self.addr, self.len) }
|
|
}
|
|
}
|
|
|
|
impl Drop for CrasAudioBuffer {
|
|
fn drop(&mut self) {
|
|
// Safe because all references must be gone by the time drop is called.
|
|
unsafe {
|
|
libc::munmap(self.addr as *mut _, self.len);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Creates header and buffer from given shared memory fds.
|
|
pub fn create_header_and_buffers<'a>(
|
|
header_fd: CrasAudioShmHeaderFd,
|
|
samples_fd: CrasShmFd,
|
|
) -> io::Result<(CrasAudioHeader<'a>, CrasAudioBuffer)> {
|
|
let header = CrasAudioHeader::new(header_fd, samples_fd.size)?;
|
|
let buffer = CrasAudioBuffer::new(samples_fd)?;
|
|
|
|
Ok((header, buffer))
|
|
}
|
|
|
|
/// Creates header from header shared memory fds. Use this function
|
|
/// when mapping the samples shm is not necessary, for instance with a
|
|
/// client-provided shm stream.
|
|
pub fn create_header<'a>(
|
|
header_fd: CrasAudioShmHeaderFd,
|
|
samples_len: usize,
|
|
) -> io::Result<CrasAudioHeader<'a>> {
|
|
Ok(CrasAudioHeader::new(header_fd, samples_len)?)
|
|
}
|
|
|
|
/// A structure wrapping a fd which contains a shared memory area and its size.
|
|
/// * `fd` - The shared memory file descriptor, a `libc::c_int`.
|
|
/// * `size` - Size of the shared memory area.
|
|
pub struct CrasShmFd {
|
|
fd: libc::c_int,
|
|
size: usize,
|
|
}
|
|
|
|
impl CrasShmFd {
|
|
/// Creates a `CrasShmFd` by shared memory fd and size
|
|
/// # Arguments
|
|
/// * `fd` - A shared memory file descriptor, which will be owned by the resulting structure and
|
|
/// the fd will be closed on drop.
|
|
/// * `size` - Size of the shared memory.
|
|
///
|
|
/// # Returns
|
|
/// * `CrasShmFd` - Wrap the input arguments without doing anything.
|
|
///
|
|
/// To use this function safely, we need to make sure
|
|
/// - The input fd is a valid shared memory fd.
|
|
/// - The input shared memory fd won't be used by others.
|
|
/// - The input fd contains memory size larger than `size`.
|
|
pub unsafe fn new(fd: libc::c_int, size: usize) -> CrasShmFd {
|
|
CrasShmFd { fd, size }
|
|
}
|
|
}
|
|
|
|
impl AsRawFd for CrasShmFd {
|
|
fn as_raw_fd(&self) -> RawFd {
|
|
self.fd
|
|
}
|
|
}
|
|
|
|
impl Drop for CrasShmFd {
|
|
fn drop(&mut self) {
|
|
// It's safe here if we make sure
|
|
// - the input fd is valid and
|
|
// - `CrasShmFd` is the only owner
|
|
// in `new` function
|
|
unsafe {
|
|
libc::close(self.fd);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A structure wrapping a fd which contains a shared `cras_server_state`.
|
|
/// * `shm_fd` - A shared memory fd contains a `cras_server_state`
|
|
pub struct CrasServerStateShmFd {
|
|
fd: CrasShmFd,
|
|
}
|
|
|
|
impl CrasServerStateShmFd {
|
|
/// Creates a `CrasServerStateShmFd` by shared memory fd
|
|
/// # Arguments
|
|
/// * `fd` - A shared memory file descriptor, which will be owned by the resulting structure and
|
|
/// the fd will be closed on drop.
|
|
///
|
|
/// # Returns
|
|
/// A structure wrapping a `CrasShmFd` with the input fd and `size` which equals to
|
|
/// the size of `cras_server_sate`.
|
|
///
|
|
/// To use this function safely, we need to make sure
|
|
/// - The input fd is a valid shared memory fd.
|
|
/// - The input shared memory fd won't be used by others.
|
|
/// - The shared memory area in the input fd contains a `cras_server_state`.
|
|
pub unsafe fn new(fd: libc::c_int) -> Self {
|
|
Self {
|
|
fd: CrasShmFd::new(fd, mem::size_of::<cras_server_state>()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::fs::File;
|
|
use std::os::unix::io::IntoRawFd;
|
|
use std::sync::{Arc, Mutex};
|
|
use std::thread;
|
|
use sys_util::{kernel_has_memfd, SharedMemory};
|
|
|
|
#[test]
|
|
fn cras_audio_header_switch_test() {
|
|
if !kernel_has_memfd() {
|
|
return;
|
|
}
|
|
let mut header = create_cras_audio_header(20);
|
|
assert_eq!(0, header.get_write_buf_idx());
|
|
header.switch_write_buf_idx();
|
|
assert_eq!(1, header.get_write_buf_idx());
|
|
}
|
|
|
|
#[test]
|
|
fn cras_audio_header_write_offset_test() {
|
|
if !kernel_has_memfd() {
|
|
return;
|
|
}
|
|
let mut header = create_cras_audio_header(20);
|
|
header.frame_size.store(2);
|
|
header.used_size.store(5);
|
|
header.set_buffer_offset(0, 12).unwrap();
|
|
|
|
assert_eq!(0, header.write_offset[0].load());
|
|
// Index out of bound
|
|
assert!(header.set_write_offset(2, 5).is_err());
|
|
// Offset out of bound
|
|
// Buffer length is 4, since that's the largest multiple of frame_size
|
|
// less than used_size.
|
|
assert!(header.set_write_offset(0, 6).is_err());
|
|
assert_eq!(0, header.write_offset[0].load());
|
|
assert!(header.set_write_offset(0, 5).is_err());
|
|
assert_eq!(0, header.write_offset[0].load());
|
|
assert!(header.set_write_offset(0, 4).is_ok());
|
|
assert_eq!(4, header.write_offset[0].load());
|
|
}
|
|
|
|
#[test]
|
|
fn cras_audio_header_read_offset_test() {
|
|
if !kernel_has_memfd() {
|
|
return;
|
|
}
|
|
let mut header = create_cras_audio_header(20);
|
|
header.frame_size.store(2);
|
|
header.used_size.store(5);
|
|
header.set_buffer_offset(0, 12).unwrap();
|
|
|
|
assert_eq!(0, header.read_offset[0].load());
|
|
// Index out of bound
|
|
assert!(header.set_read_offset(2, 5).is_err());
|
|
// Offset out of bound
|
|
// Buffer length is 4, since that's the largest multiple of frame_size
|
|
// less than used_size.
|
|
assert!(header.set_read_offset(0, 6).is_err());
|
|
assert_eq!(0, header.read_offset[0].load());
|
|
assert!(header.set_read_offset(0, 5).is_err());
|
|
assert_eq!(0, header.read_offset[0].load());
|
|
assert!(header.set_read_offset(0, 4).is_ok());
|
|
assert_eq!(4, header.read_offset[0].load());
|
|
}
|
|
|
|
#[test]
|
|
fn cras_audio_header_commit_written_frame_test() {
|
|
if !kernel_has_memfd() {
|
|
return;
|
|
}
|
|
let mut header = create_cras_audio_header(20);
|
|
header.frame_size.store(2);
|
|
header.used_size.store(10);
|
|
header.read_offset[0].store(10);
|
|
header.set_buffer_offset(0, 10).unwrap();
|
|
|
|
assert!(header.commit_written_frames(5).is_ok());
|
|
assert_eq!(header.write_offset[0].load(), 10);
|
|
assert_eq!(header.read_offset[0].load(), 0);
|
|
assert_eq!(header.write_buf_idx.load(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn cras_audio_header_get_readable_frames_test() {
|
|
if !kernel_has_memfd() {
|
|
return;
|
|
}
|
|
let header = create_cras_audio_header(20);
|
|
header.frame_size.store(2);
|
|
header.used_size.store(10);
|
|
header.read_offset[0].store(2);
|
|
header.write_offset[0].store(10);
|
|
let frames = header
|
|
.get_readable_frames()
|
|
.expect("Failed to get readable frames.");
|
|
assert_eq!(frames, 4);
|
|
}
|
|
|
|
#[test]
|
|
fn cras_audio_header_commit_read_frames_test() {
|
|
if !kernel_has_memfd() {
|
|
return;
|
|
}
|
|
let mut header = create_cras_audio_header(20);
|
|
header.frame_size.store(2);
|
|
header.used_size.store(10);
|
|
header.read_offset[0].store(2);
|
|
header.write_offset[0].store(10);
|
|
header
|
|
.commit_read_frames(3)
|
|
.expect("Failed to commit read frames.");
|
|
assert_eq!(header.get_read_buf_idx(), 0);
|
|
assert_eq!(header.read_offset[0].load(), 8);
|
|
|
|
header
|
|
.commit_read_frames(1)
|
|
.expect("Failed to commit read frames.");
|
|
// Read buffer should be switched
|
|
assert_eq!(header.get_read_buf_idx(), 1);
|
|
assert_eq!(header.read_offset[0].load(), 0);
|
|
assert_eq!(header.read_offset[0].load(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn cras_audio_header_get_write_offset_and_len() {
|
|
if !kernel_has_memfd() {
|
|
return;
|
|
}
|
|
let header = create_cras_audio_header(30);
|
|
header.frame_size.store(2);
|
|
header.used_size.store(10);
|
|
header.write_buf_idx.store(0);
|
|
header.read_offset[0].store(0);
|
|
header.write_offset[0].store(0);
|
|
header.buffer_offset[0].store(0);
|
|
|
|
header.read_buf_idx.store(1);
|
|
header.read_offset[1].store(0);
|
|
header.write_offset[1].store(0);
|
|
header.buffer_offset[1].store(10);
|
|
|
|
// standard offsets and lens
|
|
let (offset, len) = header.get_write_offset_and_len().unwrap();
|
|
assert_eq!(offset, 0);
|
|
assert_eq!(len, 10);
|
|
|
|
header.write_buf_idx.store(1);
|
|
header.read_buf_idx.store(0);
|
|
let (offset, len) = header.get_write_offset_and_len().unwrap();
|
|
assert_eq!(offset, 10);
|
|
assert_eq!(len, 10);
|
|
|
|
// relocate buffer offsets
|
|
header.buffer_offset[1].store(16);
|
|
let (offset, len) = header.get_write_offset_and_len().unwrap();
|
|
assert_eq!(offset, 16);
|
|
assert_eq!(len, 10);
|
|
|
|
header.buffer_offset[0].store(5);
|
|
header.write_buf_idx.store(0);
|
|
let (offset, len) = header.get_write_offset_and_len().unwrap();
|
|
assert_eq!(offset, 5);
|
|
assert_eq!(len, 10);
|
|
|
|
header.write_buf_idx.store(0);
|
|
header.buffer_offset[0].store(2);
|
|
header.read_buf_idx.store(1);
|
|
header.buffer_offset[1].store(10);
|
|
let result = header.get_write_offset_and_len();
|
|
// Should be an error as write buffer would overrun into other buffer.
|
|
assert!(result.is_err());
|
|
|
|
header.buffer_offset[0].store(24);
|
|
header.buffer_offset[1].store(10);
|
|
let (offset, len) = header.get_write_offset_and_len().unwrap();
|
|
// Should be ok since we're only running up against the end of samples.
|
|
assert_eq!(offset, 24);
|
|
assert_eq!(len, 6);
|
|
|
|
header.buffer_offset[0].store(25);
|
|
let (offset, len) = header.get_write_offset_and_len().unwrap();
|
|
// Should be ok, but we'll truncate len to frame_size.
|
|
assert_eq!(offset, 25);
|
|
assert_eq!(len, 4);
|
|
|
|
header.buffer_offset[0].store(29);
|
|
let result = header.get_write_offset_and_len();
|
|
// Should be an error as buffer is smaller than frame_size.
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn cras_audio_header_set_buffer_offset() {
|
|
if !kernel_has_memfd() {
|
|
return;
|
|
}
|
|
let mut header = create_cras_audio_header(30);
|
|
header.frame_size.store(2);
|
|
header.used_size.store(10);
|
|
header.write_buf_idx.store(0);
|
|
header.read_offset[0].store(0);
|
|
header.write_offset[0].store(0);
|
|
header.buffer_offset[0].store(0);
|
|
|
|
header.read_buf_idx.store(1);
|
|
header.read_offset[1].store(0);
|
|
header.write_offset[1].store(0);
|
|
header.buffer_offset[1].store(10);
|
|
|
|
// Setting buffer_offset to exactly overlap with other buffer is okay
|
|
assert!(header.set_buffer_offset(0, 10).is_ok());
|
|
|
|
// Setting buffer_offset to partially overlap other buffer is not okay
|
|
assert!(header.set_buffer_offset(0, 9).is_err());
|
|
|
|
header.buffer_offset[0].store(0);
|
|
header.write_offset[1].store(8);
|
|
// With samples, it's still an error.
|
|
assert!(header.set_buffer_offset(0, 9).is_err());
|
|
|
|
// Setting the offset past the end of the other buffer is okay
|
|
assert!(header.set_buffer_offset(0, 20).is_ok());
|
|
|
|
// Setting buffer offset such that buffer length is less than used_size
|
|
// is okay, but only at the end of the samples area.
|
|
assert!(header.set_buffer_offset(0, 21).is_ok());
|
|
assert!(header.set_buffer_offset(0, 27).is_ok());
|
|
|
|
// It's not okay if we get a buffer with length less than frame_size.
|
|
assert!(header.set_buffer_offset(0, 29).is_err());
|
|
assert!(header.set_buffer_offset(0, 30).is_err());
|
|
|
|
// If we try to overlap another buffer with that other buffer at the end,
|
|
// it's not okay, unless it's the exact same index.
|
|
assert!(header.set_buffer_offset(1, 25).is_err());
|
|
assert!(header.set_buffer_offset(1, 27).is_ok());
|
|
assert!(header.set_buffer_offset(1, 28).is_err());
|
|
|
|
// Setting buffer offset past the end of samples is an error.
|
|
assert!(header.set_buffer_offset(0, 33).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn create_header_and_buffers_test() {
|
|
if !kernel_has_memfd() {
|
|
return;
|
|
}
|
|
let header_fd = cras_audio_header_fd();
|
|
let samples_fd = cras_audio_samples_fd(20);
|
|
let res = create_header_and_buffers(header_fd, samples_fd);
|
|
res.expect("Failed to create header and buffer.");
|
|
}
|
|
|
|
fn create_shm(size: usize) -> File {
|
|
let mut shm = SharedMemory::new(None).expect("failed to create shm");
|
|
shm.set_size(size as u64).expect("failed to set shm size");
|
|
shm.into()
|
|
}
|
|
|
|
fn create_cras_audio_header<'a>(samples_len: usize) -> CrasAudioHeader<'a> {
|
|
CrasAudioHeader::new(cras_audio_header_fd(), samples_len).unwrap()
|
|
}
|
|
|
|
fn cras_audio_header_fd() -> CrasAudioShmHeaderFd {
|
|
let size = mem::size_of::<cras_audio_shm_header>();
|
|
let shm = create_shm(size);
|
|
unsafe { CrasAudioShmHeaderFd::new(shm.into_raw_fd()) }
|
|
}
|
|
|
|
fn cras_audio_samples_fd(size: usize) -> CrasShmFd {
|
|
let shm = create_shm(size);
|
|
unsafe { CrasShmFd::new(shm.into_raw_fd(), size) }
|
|
}
|
|
|
|
#[test]
|
|
fn cras_mmap_pass() {
|
|
if !kernel_has_memfd() {
|
|
return;
|
|
}
|
|
let shm = create_shm(100);
|
|
let rc = unsafe { cras_mmap(10, libc::PROT_READ, shm.as_raw_fd()) };
|
|
assert!(rc.is_ok());
|
|
unsafe { libc::munmap(rc.unwrap(), 10) };
|
|
}
|
|
|
|
#[test]
|
|
fn cras_mmap_failed() {
|
|
if !kernel_has_memfd() {
|
|
return;
|
|
}
|
|
let rc = unsafe { cras_mmap(10, libc::PROT_READ, -1) };
|
|
assert!(rc.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn cras_server_state() {
|
|
let size = mem::size_of::<cras_server_state>();
|
|
let shm = create_shm(size);
|
|
unsafe {
|
|
let addr = cras_mmap(size, libc::PROT_WRITE, shm.as_raw_fd())
|
|
.expect("failed to mmap state shm");
|
|
{
|
|
let state: &mut cras_server_state = &mut *(addr as *mut cras_server_state);
|
|
state.state_version = CRAS_SERVER_STATE_VERSION;
|
|
state.volume = 47;
|
|
state.mute = 1;
|
|
}
|
|
libc::munmap(addr, size);
|
|
};
|
|
let state_fd = unsafe { CrasServerStateShmFd::new(shm.into_raw_fd()) };
|
|
let state =
|
|
CrasServerState::try_new(state_fd).expect("try_new failed for valid server_state fd");
|
|
assert_eq!(state.get_system_volume(), 47);
|
|
assert_eq!(state.get_system_mute(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn cras_server_state_old_version() {
|
|
let size = mem::size_of::<cras_server_state>();
|
|
let shm = create_shm(size);
|
|
unsafe {
|
|
let addr = cras_mmap(size, libc::PROT_WRITE, shm.as_raw_fd())
|
|
.expect("failed to mmap state shm");
|
|
{
|
|
let state: &mut cras_server_state = &mut *(addr as *mut cras_server_state);
|
|
state.state_version = CRAS_SERVER_STATE_VERSION - 1;
|
|
state.volume = 29;
|
|
state.mute = 0;
|
|
}
|
|
libc::munmap(addr, size);
|
|
};
|
|
let state_fd = unsafe { CrasServerStateShmFd::new(shm.into_raw_fd()) };
|
|
CrasServerState::try_new(state_fd)
|
|
.expect_err("try_new succeeded for invalid state version");
|
|
}
|
|
|
|
#[test]
|
|
fn cras_server_sync_state_read() {
|
|
let size = mem::size_of::<cras_server_state>();
|
|
let shm = create_shm(size);
|
|
let addr = unsafe { cras_mmap(size, libc::PROT_WRITE, shm.as_raw_fd()).unwrap() };
|
|
let state: &mut cras_server_state = unsafe { &mut *(addr as *mut cras_server_state) };
|
|
state.state_version = CRAS_SERVER_STATE_VERSION;
|
|
state.update_count = 14;
|
|
state.volume = 12;
|
|
|
|
let state_fd = unsafe { CrasServerStateShmFd::new(shm.into_raw_fd()) };
|
|
let state_struct = CrasServerState::try_new(state_fd).unwrap();
|
|
|
|
// Create a lock so that we can block the reader while we change the
|
|
// update_count;
|
|
let lock = Arc::new(Mutex::new(()));
|
|
let thread_lock = lock.clone();
|
|
let reader_thread = {
|
|
let _guard = lock.lock().unwrap();
|
|
|
|
// Create reader thread that will get the value of volume. Since we
|
|
// hold the lock currently, this will block until we release the lock.
|
|
let reader_thread = thread::spawn(move || {
|
|
state_struct.synchronized_state_read(|| {
|
|
let _guard = thread_lock.lock().unwrap();
|
|
state_struct.volume.load()
|
|
})
|
|
});
|
|
|
|
// Update volume and change update count so that the synchronized read
|
|
// will not return (odd update count means update in progress).
|
|
state.volume = 27;
|
|
state.update_count = 15;
|
|
|
|
reader_thread
|
|
};
|
|
|
|
// The lock has been released, but the reader thread should still not
|
|
// terminate, because of the update in progress.
|
|
|
|
// Yield thread to give reader_thread a chance to get scheduled.
|
|
thread::yield_now();
|
|
{
|
|
let _guard = lock.lock().unwrap();
|
|
|
|
// Update volume and change update count to indicate the write has
|
|
// finished.
|
|
state.volume = 42;
|
|
state.update_count = 16;
|
|
}
|
|
|
|
let read_value = reader_thread.join().unwrap();
|
|
assert_eq!(read_value, 42);
|
|
}
|
|
}
|