use crate::Format;
use fuchsia_runtime::vmar_root_self;
use thiserror::Error;
pub struct VmoBuffer {
vmo: zx::Vmo,
vmo_size_bytes: u64,
num_frames: u64,
format: Format,
base_address: usize,
}
impl VmoBuffer {
pub fn new(vmo: zx::Vmo, num_frames: u64, format: Format) -> Result<Self, VmoBufferError> {
let data_size_bytes = num_frames * format.bytes_per_frame() as u64;
let vmo_size_bytes = vmo
.get_size()
.map_err(|status| VmoBufferError::VmoGetSize(zx::Status::from(status)))?;
if data_size_bytes > vmo_size_bytes {
return Err(VmoBufferError::VmoTooSmall { data_size_bytes, vmo_size_bytes });
}
let base_address = vmar_root_self()
.map(
0,
&vmo,
0,
vmo_size_bytes as usize,
zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE,
)
.map_err(|status| VmoBufferError::VmoMap(zx::Status::from(status)))?;
Ok(Self { vmo, vmo_size_bytes, base_address, num_frames, format })
}
pub fn data_size_bytes(&self) -> u64 {
self.num_frames * self.format.bytes_per_frame() as u64
}
pub fn write_to_frame(&self, frame: u64, buf: &[u8]) -> Result<(), VmoBufferError> {
if buf.len() % self.format.bytes_per_frame() as usize != 0 {
return Err(VmoBufferError::BufferIncompleteFrames);
}
let frame_offset = frame % self.num_frames;
let byte_offset = frame_offset as usize * self.format.bytes_per_frame() as usize;
let num_frames_in_buf = buf.len() as u64 / self.format.bytes_per_frame() as u64;
if (frame_offset + num_frames_in_buf) <= self.num_frames {
self.vmo.write(&buf[..], byte_offset as u64).map_err(VmoBufferError::VmoWrite)?;
self.flush_cache(byte_offset, buf.len()).map_err(VmoBufferError::VmoFlushCache)?;
} else {
let frames_to_write_until_end = self.num_frames - frame_offset;
let bytes_until_buffer_end =
frames_to_write_until_end as usize * self.format.bytes_per_frame() as usize;
self.vmo
.write(&buf[..bytes_until_buffer_end], byte_offset as u64)
.map_err(VmoBufferError::VmoWrite)?;
self.flush_cache(byte_offset, bytes_until_buffer_end)
.map_err(VmoBufferError::VmoFlushCache)?;
self.vmo.write(&buf[bytes_until_buffer_end..], 0).map_err(VmoBufferError::VmoWrite)?;
self.flush_cache(0, buf.len() - bytes_until_buffer_end)
.map_err(VmoBufferError::VmoFlushCache)?;
}
Ok(())
}
pub fn read_from_frame(&self, frame: u64, buf: &mut [u8]) -> Result<(), VmoBufferError> {
if buf.len() % self.format.bytes_per_frame() as usize != 0 {
return Err(VmoBufferError::BufferIncompleteFrames);
}
let frame_offset = frame % self.num_frames;
let byte_offset = frame_offset as usize * self.format.bytes_per_frame() as usize;
let num_frames_in_buf = buf.len() as u64 / self.format.bytes_per_frame() as u64;
if (frame_offset + num_frames_in_buf) <= self.num_frames {
self.flush_invalidate_cache(byte_offset as usize, buf.len())
.map_err(VmoBufferError::VmoFlushCache)?;
self.vmo.read(buf, byte_offset as u64).map_err(VmoBufferError::VmoRead)?;
} else {
let frames_to_write_until_end = self.num_frames - frame_offset;
let bytes_until_buffer_end =
frames_to_write_until_end as usize * self.format.bytes_per_frame() as usize;
self.flush_invalidate_cache(byte_offset, bytes_until_buffer_end)
.map_err(VmoBufferError::VmoFlushCache)?;
self.vmo
.read(&mut buf[..bytes_until_buffer_end], byte_offset as u64)
.map_err(VmoBufferError::VmoRead)?;
self.flush_invalidate_cache(0, buf.len() - bytes_until_buffer_end)
.map_err(VmoBufferError::VmoFlushCache)?;
self.vmo
.read(&mut buf[bytes_until_buffer_end..], 0)
.map_err(VmoBufferError::VmoRead)?;
}
Ok(())
}
fn flush_cache(&self, offset_bytes: usize, size_bytes: usize) -> Result<(), zx::Status> {
assert!(offset_bytes + size_bytes <= self.vmo_size_bytes as usize);
let status = unsafe {
zx::sys::zx_cache_flush(
(self.base_address + offset_bytes) as *mut u8,
size_bytes,
zx::sys::ZX_CACHE_FLUSH_DATA,
)
};
zx::Status::ok(status)
}
fn flush_invalidate_cache(
&self,
offset_bytes: usize,
size_bytes: usize,
) -> Result<(), zx::Status> {
assert!(offset_bytes + size_bytes <= self.vmo_size_bytes as usize);
let status = unsafe {
zx::sys::zx_cache_flush(
(self.base_address + offset_bytes) as *mut u8,
size_bytes,
zx::sys::ZX_CACHE_FLUSH_DATA | zx::sys::ZX_CACHE_FLUSH_INVALIDATE,
)
};
zx::Status::ok(status)
}
}
impl Drop for VmoBuffer {
fn drop(&mut self) {
unsafe {
vmar_root_self().unmap(self.base_address, self.vmo_size_bytes as usize).unwrap();
}
}
}
#[derive(Error, Debug)]
pub enum VmoBufferError {
#[error("VMO is too small ({vmo_size_bytes} bytes) to hold ring buffer data ({data_size_bytes} bytes)")]
VmoTooSmall { data_size_bytes: u64, vmo_size_bytes: u64 },
#[error("Buffer size is invalid; contains incomplete frames")]
BufferIncompleteFrames,
#[error("Failed to memory map VMO: {}", .0)]
VmoMap(#[source] zx::Status),
#[error("Failed to get VMO size: {}", .0)]
VmoGetSize(#[source] zx::Status),
#[error("Failed to flush VMO memory cache: {}", .0)]
VmoFlushCache(#[source] zx::Status),
#[error("Failed to read from VMO: {}", .0)]
VmoRead(#[source] zx::Status),
#[error("Failed to write to VMO: {}", .0)]
VmoWrite(#[source] zx::Status),
}
#[cfg(test)]
mod test {
use super::*;
use crate::format::SampleType;
use assert_matches::assert_matches;
#[test]
fn vmobuffer_vmo_too_small() {
let format =
Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
let page_size = zx::system_get_page_size() as u64;
let num_frames = page_size + 1;
let vmo = zx::Vmo::create(page_size).unwrap();
assert_matches!(
VmoBuffer::new(vmo, num_frames, format).err(),
Some(VmoBufferError::VmoTooSmall { .. })
)
}
#[test]
fn vmobuffer_read_write() {
let format =
Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
const NUM_FRAMES_VMO: u64 = 10;
const NUM_FRAMES_BUF: u64 = 5;
const SAMPLE: u8 = 42;
let vmo_size = format.bytes_per_frame() as u64 * NUM_FRAMES_VMO;
let buf_size = format.bytes_per_frame() as u64 * NUM_FRAMES_BUF;
let vmo = zx::Vmo::create(vmo_size).unwrap();
let mut in_buf = vec![0; buf_size as usize];
let out_buf = vec![SAMPLE; buf_size as usize];
let vmo_buffer = VmoBuffer::new(vmo, NUM_FRAMES_VMO, format).unwrap();
vmo_buffer.write_to_frame(1, &out_buf).unwrap();
vmo_buffer.read_from_frame(1, &mut in_buf).unwrap();
assert_eq!(in_buf, out_buf);
}
#[test]
fn vmobuffer_read_write_wrapping() {
let format =
Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
let page_size = zx::system_get_page_size() as u64;
let num_frames_vmo: u64 = page_size;
let num_frames_buf: u64 = page_size / 2;
const SAMPLE: u8 = 42;
let vmo_size = format.bytes_per_frame() as u64 * num_frames_vmo;
let buf_size = format.bytes_per_frame() as u64 * num_frames_buf;
let vmo = zx::Vmo::create(vmo_size).unwrap();
let mut in_buf = vec![0; buf_size as usize];
let out_buf = vec![SAMPLE; buf_size as usize];
let vmo_buffer = VmoBuffer::new(vmo, num_frames_vmo, format).unwrap();
let frame = num_frames_vmo - 1;
assert!(frame + num_frames_buf > num_frames_vmo);
vmo_buffer.write_to_frame(frame, &out_buf).unwrap();
vmo_buffer.read_from_frame(frame, &mut in_buf).unwrap();
assert_eq!(in_buf, out_buf);
}
}