fuchsia_audio/
vmo_buffer.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::Format;
6use fuchsia_runtime::vmar_root_self;
7
8use thiserror::Error;
9
10/// A VMO-backed ring buffer that contains frames of audio.
11pub struct VmoBuffer {
12    /// VMO that contains `num_frames` of audio in `format`.
13    vmo: zx::Vmo,
14
15    /// Size of the VMO, in bytes.
16    vmo_size_bytes: u64,
17
18    /// Number of frames in the VMO.
19    num_frames: u64,
20
21    /// Format of each frame.
22    format: Format,
23
24    /// Base address of the memory-mapped `vmo`.
25    base_address: usize,
26}
27
28impl VmoBuffer {
29    pub fn new(vmo: zx::Vmo, num_frames: u64, format: Format) -> Result<Self, VmoBufferError> {
30        // Ensure that the VMO is big enough to hold `num_frames` of audio in the given `format`.
31        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                // TODO(https://fxbug.dev/356700720): Don't map read-only VMOs with `PERM_WRITE`.
47                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    /// Returns the size of the buffer in bytes.
62    ///
63    /// This may be less than the size of the backing VMO.
64    pub fn data_size_bytes(&self) -> u64 {
65        self.num_frames * self.format.bytes_per_frame() as u64
66    }
67
68    /// Writes all frames from `buf` to the ring buffer at position `running_frame`.
69    pub fn write_to_frame(&self, running_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 = running_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        // Check whether the buffer can be written to contiguously or if the write needs to be
78        // split into two: one until the end of the buffer and one starting from the beginning.
79        if (frame_offset + num_frames_in_buf) <= self.num_frames {
80            self.vmo.write(&buf[..], byte_offset as u64).map_err(VmoBufferError::VmoWrite)?;
81            // Flush cache so that hardware reads most recent write.
82            self.flush_cache(byte_offset, buf.len()).map_err(VmoBufferError::VmoFlushCache)?;
83            log::debug!(
84                "Wrote {} bytes ({} frames) from buf, to ring_buf frame {} (running frame {})",
85                buf.len(),
86                num_frames_in_buf,
87                frame_offset,
88                running_frame
89            );
90            fuchsia_trace::instant!(
91                c"audio-streaming",
92                c"AudioLib::VmoBuffer::write_to_frame",
93                fuchsia_trace::Scope::Process,
94                "source bytes to write" => buf.len(),
95                "source frames to write" => num_frames_in_buf,
96                "frame size" => self.format.bytes_per_frame(),
97                "dest RB size (frames)" => self.num_frames,
98                "dest RB running frame" => running_frame,
99                "dest RB frame position" => frame_offset,
100                "vmo write start" => byte_offset,
101                "vmo write len (bytes)" => buf.len()
102            );
103        } else {
104            let frames_to_write_until_end = self.num_frames - frame_offset;
105            let bytes_until_buffer_end =
106                frames_to_write_until_end as usize * self.format.bytes_per_frame() as usize;
107
108            self.vmo
109                .write(&buf[..bytes_until_buffer_end], byte_offset as u64)
110                .map_err(VmoBufferError::VmoWrite)?;
111            // Flush cache so that hardware reads most recent write.
112            self.flush_cache(byte_offset, bytes_until_buffer_end)
113                .map_err(VmoBufferError::VmoFlushCache)?;
114            log::debug!(
115                "First wrote {} bytes ({} frames) from buf, to ring_buf frame {} (running frame {})",
116                bytes_until_buffer_end, frames_to_write_until_end,
117                frame_offset, running_frame
118            );
119
120            if buf[bytes_until_buffer_end..].len() > self.vmo_size_bytes as usize {
121                log::error!("Remainder of write buffer is too big for the vmo.");
122            }
123
124            // Write what remains to the beginning of the buffer.
125            self.vmo.write(&buf[bytes_until_buffer_end..], 0).map_err(VmoBufferError::VmoWrite)?;
126            self.flush_cache(0, buf.len() - bytes_until_buffer_end)
127                .map_err(VmoBufferError::VmoFlushCache)?;
128            fuchsia_trace::instant!(
129                c"audio-streaming",
130                c"AudioLib::VmoBuffer::write_to_frame",
131                fuchsia_trace::Scope::Process,
132                "source bytes to write" => buf.len(),
133                "source frames to write" => num_frames_in_buf,
134                "frame size" => self.format.bytes_per_frame(),
135                "dest RB size (frames)" => self.num_frames,
136                "dest RB running frame" => running_frame,
137                "dest RB frame position" => frame_offset,
138                "first vmo write start" => byte_offset,
139                "first vmo write len (bytes)" => bytes_until_buffer_end,
140                "second vmo write start" => 0,
141                "second vmo write len (bytes)" => buf.len() - bytes_until_buffer_end
142            );
143            log::debug!(
144                "Then wrote {} bytes ({} frames) from buf, to ring_buf frame 0 (running frame {})",
145                buf.len() - bytes_until_buffer_end,
146                num_frames_in_buf - frames_to_write_until_end,
147                running_frame + frames_to_write_until_end
148            );
149        }
150
151        Ok(())
152    }
153
154    /// Reads frames from the ring buffer into `buf` starting at position `running_frame`.
155    pub fn read_from_frame(
156        &self,
157        running_frame: u64,
158        buf: &mut [u8],
159    ) -> Result<(), VmoBufferError> {
160        if buf.len() % self.format.bytes_per_frame() as usize != 0 {
161            return Err(VmoBufferError::BufferIncompleteFrames);
162        }
163        let frame_offset = running_frame % self.num_frames;
164        let byte_offset = frame_offset as usize * self.format.bytes_per_frame() as usize;
165        let num_frames_in_buf = buf.len() as u64 / self.format.bytes_per_frame() as u64;
166
167        // Check whether the buffer can be read from contiguously or if the read needs to be
168        // split into two: one until the end of the buffer and one starting from the beginning.
169        if (frame_offset + num_frames_in_buf) <= self.num_frames {
170            // Flush and invalidate cache so we read the hardware's most recent write.
171            log::debug!("frame {} reading starting from position {}", running_frame, byte_offset);
172            self.flush_invalidate_cache(byte_offset as usize, buf.len())
173                .map_err(VmoBufferError::VmoFlushCache)?;
174            self.vmo.read(buf, byte_offset as u64).map_err(VmoBufferError::VmoRead)?;
175        } else {
176            let frames_to_write_until_end = self.num_frames - frame_offset;
177            let bytes_until_buffer_end =
178                frames_to_write_until_end as usize * self.format.bytes_per_frame() as usize;
179
180            log::debug!(
181                "frame {} reading starting from position {}  (with looparound)",
182                running_frame,
183                byte_offset
184            );
185            // Flush and invalidate cache so we read the hardware's most recent write.
186            self.flush_invalidate_cache(byte_offset, bytes_until_buffer_end)
187                .map_err(VmoBufferError::VmoFlushCache)?;
188            self.vmo
189                .read(&mut buf[..bytes_until_buffer_end], byte_offset as u64)
190                .map_err(VmoBufferError::VmoRead)?;
191
192            if buf[bytes_until_buffer_end..].len() > self.vmo_size_bytes as usize {
193                log::error!("Remainder of read buffer is too big for the vmo.");
194            }
195
196            self.flush_invalidate_cache(0, buf.len() - bytes_until_buffer_end)
197                .map_err(VmoBufferError::VmoFlushCache)?;
198            self.vmo
199                .read(&mut buf[bytes_until_buffer_end..], 0)
200                .map_err(VmoBufferError::VmoRead)?;
201        }
202        Ok(())
203    }
204
205    /// Flush the cache for a portion of the memory-mapped VMO.
206    // TODO(https://fxbug.dev/328478694): Remove these methods once VMOs are created without caching
207    fn flush_cache(&self, offset_bytes: usize, size_bytes: usize) -> Result<(), zx::Status> {
208        assert!(offset_bytes + size_bytes <= self.vmo_size_bytes as usize);
209        let status = unsafe {
210            // SAFETY: The range was asserted above to be within the mapped region of the VMO.
211            zx::sys::zx_cache_flush(
212                (self.base_address + offset_bytes) as *mut u8,
213                size_bytes,
214                zx::sys::ZX_CACHE_FLUSH_DATA,
215            )
216        };
217        zx::Status::ok(status)
218    }
219
220    /// Flush and invalidate cache for a portion of the memory-mapped VMO.
221    // TODO(https://fxbug.dev/328478694): Remove these methods once VMOs are created without caching
222    fn flush_invalidate_cache(
223        &self,
224        offset_bytes: usize,
225        size_bytes: usize,
226    ) -> Result<(), zx::Status> {
227        assert!(offset_bytes + size_bytes <= self.vmo_size_bytes as usize);
228        let status = unsafe {
229            // SAFETY: The range was asserted above to be within the mapped region of the VMO.
230            zx::sys::zx_cache_flush(
231                (self.base_address + offset_bytes) as *mut u8,
232                size_bytes,
233                zx::sys::ZX_CACHE_FLUSH_DATA | zx::sys::ZX_CACHE_FLUSH_INVALIDATE,
234            )
235        };
236        zx::Status::ok(status)
237    }
238}
239
240impl Drop for VmoBuffer {
241    fn drop(&mut self) {
242        // SAFETY: `base_address` and `vmo_size_bytes` are private to self,
243        // so no other code can observe that this mapping has been removed.
244        unsafe {
245            vmar_root_self().unmap(self.base_address, self.vmo_size_bytes as usize).unwrap();
246        }
247    }
248}
249
250#[derive(Error, Debug)]
251pub enum VmoBufferError {
252    #[error("VMO is too small ({vmo_size_bytes} bytes) to hold ring buffer data ({data_size_bytes} bytes)")]
253    VmoTooSmall { data_size_bytes: u64, vmo_size_bytes: u64 },
254
255    #[error("Buffer size is invalid; contains incomplete frames")]
256    BufferIncompleteFrames,
257
258    #[error("Failed to memory map VMO: {}", .0)]
259    VmoMap(#[source] zx::Status),
260
261    #[error("Failed to get VMO size: {}", .0)]
262    VmoGetSize(#[source] zx::Status),
263
264    #[error("Failed to flush VMO memory cache: {}", .0)]
265    VmoFlushCache(#[source] zx::Status),
266
267    #[error("Failed to read from VMO: {}", .0)]
268    VmoRead(#[source] zx::Status),
269
270    #[error("Failed to write to VMO: {}", .0)]
271    VmoWrite(#[source] zx::Status),
272}
273
274#[cfg(test)]
275mod test {
276    use super::*;
277    use crate::format::SampleType;
278    use assert_matches::assert_matches;
279
280    #[test]
281    fn vmobuffer_vmo_too_small() {
282        let format =
283            Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
284
285        // VMO size is rounded up to the system page size.
286        let page_size = zx::system_get_page_size() as u64;
287        let num_frames = page_size + 1;
288        let vmo = zx::Vmo::create(page_size).unwrap();
289
290        assert_matches!(
291            VmoBuffer::new(vmo, num_frames, format).err(),
292            Some(VmoBufferError::VmoTooSmall { .. })
293        )
294    }
295
296    #[test]
297    fn vmobuffer_read_write() {
298        let format =
299            Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
300        const NUM_FRAMES_VMO: u64 = 10;
301        const NUM_FRAMES_BUF: u64 = 5;
302        const SAMPLE: u8 = 42;
303
304        let vmo_size = format.bytes_per_frame() as u64 * NUM_FRAMES_VMO;
305        let buf_size = format.bytes_per_frame() as u64 * NUM_FRAMES_BUF;
306
307        let vmo = zx::Vmo::create(vmo_size).unwrap();
308
309        // Buffer used to read from the VmoBuffer.
310        let mut in_buf = vec![0; buf_size as usize];
311        // Buffer used to write to from the VmoBuffer.
312        let out_buf = vec![SAMPLE; buf_size as usize];
313
314        let vmo_buffer = VmoBuffer::new(vmo, NUM_FRAMES_VMO, format).unwrap();
315
316        // Write the buffer to the VmoBuffer, starting on the second frame (zero based).
317        vmo_buffer.write_to_frame(1, &out_buf).unwrap();
318
319        // Read back from the VmoBuffer.
320        vmo_buffer.read_from_frame(1, &mut in_buf).unwrap();
321
322        assert_eq!(in_buf, out_buf);
323    }
324
325    #[test]
326    fn vmobuffer_read_write_wrapping() {
327        let format =
328            Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
329        let page_size = zx::system_get_page_size() as u64;
330        let num_frames_vmo: u64 = page_size;
331        let num_frames_buf: u64 = page_size / 2;
332        const SAMPLE: u8 = 42;
333
334        let vmo_size = format.bytes_per_frame() as u64 * num_frames_vmo;
335        let buf_size = format.bytes_per_frame() as u64 * num_frames_buf;
336
337        let vmo = zx::Vmo::create(vmo_size).unwrap();
338
339        // Buffer used to read from the VmoBuffer.
340        let mut in_buf = vec![0; buf_size as usize];
341        // Buffer used to write to from the VmoBuffer.
342        let out_buf = vec![SAMPLE; buf_size as usize];
343
344        let vmo_buffer = VmoBuffer::new(vmo, num_frames_vmo, format).unwrap();
345
346        // Write and read at the last frame to ensure the operations wrap to the beginning.
347        let frame = num_frames_vmo - 1;
348        assert!(frame + num_frames_buf > num_frames_vmo);
349
350        // Write the buffer to the VmoBuffer, starting on the last frame.
351        vmo_buffer.write_to_frame(frame, &out_buf).unwrap();
352
353        // Read back from the VmoBuffer.
354        vmo_buffer.read_from_frame(frame, &mut in_buf).unwrap();
355
356        assert_eq!(in_buf, out_buf);
357    }
358}