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(b/356700720): Don't try to 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 `frame`.
69    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        // Check whether the buffer can be written to contiguously or if the write needs to be
82        // split into two: one until the end of the buffer and one starting from the beginning.
83        if (frame_offset + num_frames_in_buf) <= self.num_frames {
84            self.vmo.write(&buf[..], byte_offset as u64).map_err(VmoBufferError::VmoWrite)?;
85            // Flush cache so that hardware reads most recent write.
86            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            // Flush cache so that hardware reads most recent write.
102            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            // Write what remains to the beginning of the buffer.
110            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    /// Reads frames from the ring buffer into `buf` starting at position `frame`.
125    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; // 96
134        let mili_elapsed = frame / frames_per_mili as u64;
135        let seconds = mili_elapsed as f64 / 1000.0;
136
137        // Check whether the buffer can be read from contiguously or if the read needs to be
138        // split into two: one until the end of the buffer and one starting from the beginning.
139        if (frame_offset + num_frames_in_buf) <= self.num_frames {
140            // Flush and invalidate cache so we read the hardware's most recent write.
141            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            // Flush and invalidate cache so we read the hardware's most recent write.
162            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    /// Flush the cache for a portion of the memory-mapped VMO.
182    // TODO(https://fxbug.dev/328478694): Remove these methods once VMOs are created without caching
183    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            // SAFETY: The range was asserted above to be within the mapped region of the VMO.
187            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    /// Flush and invalidate cache for a portion of the memory-mapped VMO.
197    // TODO(https://fxbug.dev/328478694): Remove these methods once VMOs are created without caching
198    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            // SAFETY: The range was asserted above to be within the mapped region of the VMO.
206            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        // SAFETY: `base_address` and `vmo_size_bytes` are private to self,
219        // so no other code can observe that this mapping has been removed.
220        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        // VMO size is rounded up to the system page size.
262        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        // Buffer used to read from the VmoBuffer.
286        let mut in_buf = vec![0; buf_size as usize];
287        // Buffer used to write to from the VmoBuffer.
288        let out_buf = vec![SAMPLE; buf_size as usize];
289
290        let vmo_buffer = VmoBuffer::new(vmo, NUM_FRAMES_VMO, format).unwrap();
291
292        // Write the buffer to the VmoBuffer, starting on the second frame (zero based).
293        vmo_buffer.write_to_frame(1, &out_buf).unwrap();
294
295        // Read back from the VmoBuffer.
296        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        // Buffer used to read from the VmoBuffer.
316        let mut in_buf = vec![0; buf_size as usize];
317        // Buffer used to write to from the VmoBuffer.
318        let out_buf = vec![SAMPLE; buf_size as usize];
319
320        let vmo_buffer = VmoBuffer::new(vmo, num_frames_vmo, format).unwrap();
321
322        // Write and read at the last frame to ensure the operations wrap to the beginning.
323        let frame = num_frames_vmo - 1;
324        assert!(frame + num_frames_buf > num_frames_vmo);
325
326        // Write the buffer to the VmoBuffer, starting on the last frame.
327        vmo_buffer.write_to_frame(frame, &out_buf).unwrap();
328
329        // Read back from the VmoBuffer.
330        vmo_buffer.read_from_frame(frame, &mut in_buf).unwrap();
331
332        assert_eq!(in_buf, out_buf);
333    }
334}