diff options
Diffstat (limited to 'cras/client/libcras/src/cras_shm.rs')
-rw-r--r-- | cras/client/libcras/src/cras_shm.rs | 1308 |
1 files changed, 0 insertions, 1308 deletions
diff --git a/cras/client/libcras/src/cras_shm.rs b/cras/client/libcras/src/cras_shm.rs deleted file mode 100644 index 05533753..00000000 --- a/cras/client/libcras/src/cras_shm.rs +++ /dev/null @@ -1,1308 +0,0 @@ -// 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); - } -} |