1use crate::Format;
6use fuchsia_runtime::vmar_root_self;
7
8use thiserror::Error;
9
10pub struct VmoBuffer {
12 vmo: zx::Vmo,
14
15 vmo_size_bytes: u64,
17
18 num_frames: u64,
20
21 format: Format,
23
24 base_address: usize,
26}
27
28impl VmoBuffer {
29 pub fn new(vmo: zx::Vmo, num_frames: u64, format: Format) -> Result<Self, VmoBufferError> {
30 let data_size_bytes = num_frames * format.bytes_per_frame() as u64;
32 let vmo_size_bytes = vmo
33 .get_size()
34 .map_err(|status| VmoBufferError::VmoGetSize(zx::Status::from(status)))?;
35
36 if data_size_bytes > vmo_size_bytes {
37 return Err(VmoBufferError::VmoTooSmall { data_size_bytes, vmo_size_bytes });
38 }
39
40 let base_address = vmar_root_self()
41 .map(
42 0,
43 &vmo,
44 0,
45 vmo_size_bytes as usize,
46 zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE,
48 )
49 .map_err(|status| VmoBufferError::VmoMap(zx::Status::from(status)))?;
50
51 log::debug!(
52 "format {:?} num frames {} data_size_bytes {}",
53 format,
54 num_frames,
55 data_size_bytes
56 );
57
58 Ok(Self { vmo, vmo_size_bytes, base_address, num_frames, format })
59 }
60
61 pub fn data_size_bytes(&self) -> u64 {
65 self.num_frames * self.format.bytes_per_frame() as u64
66 }
67
68 pub fn write_to_frame(&self, frame: u64, buf: &[u8]) -> Result<(), VmoBufferError> {
70 if buf.len() % self.format.bytes_per_frame() as usize != 0 {
71 return Err(VmoBufferError::BufferIncompleteFrames);
72 }
73 let frame_offset = frame % self.num_frames;
74 let byte_offset = frame_offset as usize * self.format.bytes_per_frame() as usize;
75 let num_frames_in_buf = buf.len() as u64 / self.format.bytes_per_frame() as u64;
76
77 let frames_per_mili = self.format.frames_per_second / 1000;
78 let mili_elapsed = frame / frames_per_mili as u64;
79 let seconds = mili_elapsed as f64 / 1000.0;
80
81 if (frame_offset + num_frames_in_buf) <= self.num_frames {
84 self.vmo.write(&buf[..], byte_offset as u64).map_err(VmoBufferError::VmoWrite)?;
85 self.flush_cache(byte_offset, buf.len()).map_err(VmoBufferError::VmoFlushCache)?;
87 log::debug!(
88 "frame {} wrote starting from position {} Time {}s",
89 frame,
90 byte_offset,
91 seconds
92 );
93 } else {
94 let frames_to_write_until_end = self.num_frames - frame_offset;
95 let bytes_until_buffer_end =
96 frames_to_write_until_end as usize * self.format.bytes_per_frame() as usize;
97
98 self.vmo
99 .write(&buf[..bytes_until_buffer_end], byte_offset as u64)
100 .map_err(VmoBufferError::VmoWrite)?;
101 self.flush_cache(byte_offset, bytes_until_buffer_end)
103 .map_err(VmoBufferError::VmoFlushCache)?;
104
105 if buf[bytes_until_buffer_end..].len() > self.vmo_size_bytes as usize {
106 log::error!("Remainder of write buffer is too big for the vmo.");
107 }
108
109 self.vmo.write(&buf[bytes_until_buffer_end..], 0).map_err(VmoBufferError::VmoWrite)?;
111 self.flush_cache(0, buf.len() - bytes_until_buffer_end)
112 .map_err(VmoBufferError::VmoFlushCache)?;
113
114 log::debug!(
115 "frame {} wrote starting from position {} (with looparound) Time {}s",
116 frame,
117 byte_offset,
118 seconds
119 );
120 }
121 Ok(())
122 }
123
124 pub fn read_from_frame(&self, frame: u64, buf: &mut [u8]) -> Result<(), VmoBufferError> {
126 if buf.len() % self.format.bytes_per_frame() as usize != 0 {
127 return Err(VmoBufferError::BufferIncompleteFrames);
128 }
129 let frame_offset = frame % self.num_frames;
130 let byte_offset = frame_offset as usize * self.format.bytes_per_frame() as usize;
131 let num_frames_in_buf = buf.len() as u64 / self.format.bytes_per_frame() as u64;
132
133 let frames_per_mili = self.format.frames_per_second / 1000; let mili_elapsed = frame / frames_per_mili as u64;
135 let seconds = mili_elapsed as f64 / 1000.0;
136
137 if (frame_offset + num_frames_in_buf) <= self.num_frames {
140 log::debug!(
142 "frame {} reading starting from position {} Time {}s",
143 frame,
144 byte_offset,
145 seconds
146 );
147 self.flush_invalidate_cache(byte_offset as usize, buf.len())
148 .map_err(VmoBufferError::VmoFlushCache)?;
149 self.vmo.read(buf, byte_offset as u64).map_err(VmoBufferError::VmoRead)?;
150 } else {
151 let frames_to_write_until_end = self.num_frames - frame_offset;
152 let bytes_until_buffer_end =
153 frames_to_write_until_end as usize * self.format.bytes_per_frame() as usize;
154
155 log::debug!(
156 "frame {} reading starting from position {} (with looparound) Time {}s",
157 frame,
158 byte_offset,
159 seconds
160 );
161 self.flush_invalidate_cache(byte_offset, bytes_until_buffer_end)
163 .map_err(VmoBufferError::VmoFlushCache)?;
164 self.vmo
165 .read(&mut buf[..bytes_until_buffer_end], byte_offset as u64)
166 .map_err(VmoBufferError::VmoRead)?;
167
168 if buf[bytes_until_buffer_end..].len() > self.vmo_size_bytes as usize {
169 log::error!("Remainder of read buffer is too big for the vmo.");
170 }
171
172 self.flush_invalidate_cache(0, buf.len() - bytes_until_buffer_end)
173 .map_err(VmoBufferError::VmoFlushCache)?;
174 self.vmo
175 .read(&mut buf[bytes_until_buffer_end..], 0)
176 .map_err(VmoBufferError::VmoRead)?;
177 }
178 Ok(())
179 }
180
181 fn flush_cache(&self, offset_bytes: usize, size_bytes: usize) -> Result<(), zx::Status> {
184 assert!(offset_bytes + size_bytes <= self.vmo_size_bytes as usize);
185 let status = unsafe {
186 zx::sys::zx_cache_flush(
188 (self.base_address + offset_bytes) as *mut u8,
189 size_bytes,
190 zx::sys::ZX_CACHE_FLUSH_DATA,
191 )
192 };
193 zx::Status::ok(status)
194 }
195
196 fn flush_invalidate_cache(
199 &self,
200 offset_bytes: usize,
201 size_bytes: usize,
202 ) -> Result<(), zx::Status> {
203 assert!(offset_bytes + size_bytes <= self.vmo_size_bytes as usize);
204 let status = unsafe {
205 zx::sys::zx_cache_flush(
207 (self.base_address + offset_bytes) as *mut u8,
208 size_bytes,
209 zx::sys::ZX_CACHE_FLUSH_DATA | zx::sys::ZX_CACHE_FLUSH_INVALIDATE,
210 )
211 };
212 zx::Status::ok(status)
213 }
214}
215
216impl Drop for VmoBuffer {
217 fn drop(&mut self) {
218 unsafe {
221 vmar_root_self().unmap(self.base_address, self.vmo_size_bytes as usize).unwrap();
222 }
223 }
224}
225
226#[derive(Error, Debug)]
227pub enum VmoBufferError {
228 #[error("VMO is too small ({vmo_size_bytes} bytes) to hold ring buffer data ({data_size_bytes} bytes)")]
229 VmoTooSmall { data_size_bytes: u64, vmo_size_bytes: u64 },
230
231 #[error("Buffer size is invalid; contains incomplete frames")]
232 BufferIncompleteFrames,
233
234 #[error("Failed to memory map VMO: {}", .0)]
235 VmoMap(#[source] zx::Status),
236
237 #[error("Failed to get VMO size: {}", .0)]
238 VmoGetSize(#[source] zx::Status),
239
240 #[error("Failed to flush VMO memory cache: {}", .0)]
241 VmoFlushCache(#[source] zx::Status),
242
243 #[error("Failed to read from VMO: {}", .0)]
244 VmoRead(#[source] zx::Status),
245
246 #[error("Failed to write to VMO: {}", .0)]
247 VmoWrite(#[source] zx::Status),
248}
249
250#[cfg(test)]
251mod test {
252 use super::*;
253 use crate::format::SampleType;
254 use assert_matches::assert_matches;
255
256 #[test]
257 fn vmobuffer_vmo_too_small() {
258 let format =
259 Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
260
261 let page_size = zx::system_get_page_size() as u64;
263 let num_frames = page_size + 1;
264 let vmo = zx::Vmo::create(page_size).unwrap();
265
266 assert_matches!(
267 VmoBuffer::new(vmo, num_frames, format).err(),
268 Some(VmoBufferError::VmoTooSmall { .. })
269 )
270 }
271
272 #[test]
273 fn vmobuffer_read_write() {
274 let format =
275 Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
276 const NUM_FRAMES_VMO: u64 = 10;
277 const NUM_FRAMES_BUF: u64 = 5;
278 const SAMPLE: u8 = 42;
279
280 let vmo_size = format.bytes_per_frame() as u64 * NUM_FRAMES_VMO;
281 let buf_size = format.bytes_per_frame() as u64 * NUM_FRAMES_BUF;
282
283 let vmo = zx::Vmo::create(vmo_size).unwrap();
284
285 let mut in_buf = vec![0; buf_size as usize];
287 let out_buf = vec![SAMPLE; buf_size as usize];
289
290 let vmo_buffer = VmoBuffer::new(vmo, NUM_FRAMES_VMO, format).unwrap();
291
292 vmo_buffer.write_to_frame(1, &out_buf).unwrap();
294
295 vmo_buffer.read_from_frame(1, &mut in_buf).unwrap();
297
298 assert_eq!(in_buf, out_buf);
299 }
300
301 #[test]
302 fn vmobuffer_read_write_wrapping() {
303 let format =
304 Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
305 let page_size = zx::system_get_page_size() as u64;
306 let num_frames_vmo: u64 = page_size;
307 let num_frames_buf: u64 = page_size / 2;
308 const SAMPLE: u8 = 42;
309
310 let vmo_size = format.bytes_per_frame() as u64 * num_frames_vmo;
311 let buf_size = format.bytes_per_frame() as u64 * num_frames_buf;
312
313 let vmo = zx::Vmo::create(vmo_size).unwrap();
314
315 let mut in_buf = vec![0; buf_size as usize];
317 let out_buf = vec![SAMPLE; buf_size as usize];
319
320 let vmo_buffer = VmoBuffer::new(vmo, num_frames_vmo, format).unwrap();
321
322 let frame = num_frames_vmo - 1;
324 assert!(frame + num_frames_buf > num_frames_vmo);
325
326 vmo_buffer.write_to_frame(frame, &out_buf).unwrap();
328
329 vmo_buffer.read_from_frame(frame, &mut in_buf).unwrap();
331
332 assert_eq!(in_buf, out_buf);
333 }
334}