Skip to main content

dma_buffer/
lib.rs

1// Copyright 2026 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
5//! Safe wrappers around pinned and mapped DMA buffers for Fuchsia drivers.
6
7#![deny(missing_docs)]
8
9use fuchsia_runtime::vmar_root_self;
10use mapped_vmo::Mapping;
11use std::ops::{Deref, DerefMut};
12use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
13use zx::sys::zx_paddr_t;
14use zx::{
15    Bti, BtiOptions, CachePolicy, Pmt, Status, Vmar, VmarFlags, Vmo, VmoOp, system_get_page_size,
16};
17
18mod phys_iter;
19pub use phys_iter::PhysIter;
20
21/// Cache options for DMA buffers.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub enum CacheOptions {
24    /// Disable CPU caching for the DMA buffer (`ZX_CACHE_POLICY_UNCACHED_DEVICE`).
25    Disabled,
26    /// Enable CPU caching for the DMA buffer.
27    #[default]
28    Enabled,
29}
30
31/// Helper for writing to uncached DMA buffer memory without triggering alignment faults.
32///
33/// Using volatile pointer writes prevents the compiler from coalescing or vectorizing stores into
34/// unaligned instructions or `memcpy` calls that fault on `ZX_CACHE_POLICY_UNCACHED_DEVICE`
35/// mappings (e.g. on ARM64).
36///
37/// # Safety
38///
39/// `dest_ptr..dest_ptr.add(src.len())` must be valid for writes within the mapped DMA buffer.
40unsafe fn uncached_write(mut dest_ptr: *mut u8, mut src: &[u8]) {
41    while (dest_ptr as usize) & (size_of::<u64>() - 1) != 0 && !src.is_empty() {
42        // SAFETY: `dest_ptr` is within the mapped buffer bounds.
43        unsafe {
44            dest_ptr.write_volatile(src[0]);
45            dest_ptr = dest_ptr.add(1);
46        }
47        src = &src[1..];
48    }
49    let (chunks, remainder) = src.as_chunks::<{ size_of::<u64>() }>();
50    for chunk in chunks {
51        let val = u64::from_ne_bytes(*chunk);
52        // SAFETY: `dest_ptr` is 8-byte aligned and at least 8 bytes remain in the mapped buffer.
53        unsafe {
54            dest_ptr.cast::<u64>().write_volatile(val);
55            dest_ptr = dest_ptr.add(size_of::<u64>());
56        }
57    }
58    for &byte in remainder {
59        // SAFETY: `dest_ptr` is within the mapped buffer bounds.
60        unsafe {
61            dest_ptr.write_volatile(byte);
62            dest_ptr = dest_ptr.add(1);
63        }
64    }
65}
66
67/// Helper for reading from uncached DMA buffer memory without triggering alignment faults.
68///
69/// # Safety
70///
71/// `src_ptr..src_ptr.add(dst.len())` must be valid for reads within the mapped DMA buffer.
72unsafe fn uncached_read(mut src_ptr: *const u8, mut dst: &mut [u8]) {
73    while (src_ptr as usize) & (size_of::<u64>() - 1) != 0 && !dst.is_empty() {
74        // SAFETY: `src_ptr` is within the mapped buffer bounds.
75        unsafe {
76            dst[0] = src_ptr.read_volatile();
77            src_ptr = src_ptr.add(1);
78        }
79        dst = &mut dst[1..];
80    }
81    let (chunks, remainder) = dst.as_chunks_mut::<{ size_of::<u64>() }>();
82    for chunk in chunks {
83        // SAFETY: `src_ptr` is 8-byte aligned and at least 8 bytes remain in the mapped buffer.
84        let val = unsafe {
85            let v = src_ptr.cast::<u64>().read_volatile();
86            src_ptr = src_ptr.add(size_of::<u64>());
87            v
88        };
89        *chunk = val.to_ne_bytes();
90    }
91    for byte in remainder {
92        // SAFETY: `src_ptr` is within the mapped buffer bounds.
93        unsafe {
94            *byte = src_ptr.read_volatile();
95            src_ptr = src_ptr.add(1);
96        }
97    }
98}
99
100/// Concrete type for DMA buffers providing cache management, pinned memory token lifecycle, and
101/// safe memory access backed by a [`mapped_vmo::Mapping`].
102#[derive(Debug)]
103pub struct Buffer {
104    mapping: Mapping,
105    ptr: *mut u8,
106    vmo: Vmo,
107    pmt: Pmt,
108    cache_options: CacheOptions,
109}
110
111// SAFETY: `Buffer` owns `mapping` (which is `Send` and `Sync`), and `ptr` points to the base of
112// `mapping` and is only accessed via bounds-checked volatile reads and writes while `mapping` is
113// alive.
114unsafe impl Send for Buffer {}
115unsafe impl Sync for Buffer {}
116
117impl Deref for Buffer {
118    type Target = Mapping;
119
120    fn deref(&self) -> &Self::Target {
121        &self.mapping
122    }
123}
124
125impl DerefMut for Buffer {
126    fn deref_mut(&mut self) -> &mut Self::Target {
127        &mut self.mapping
128    }
129}
130
131impl Buffer {
132    fn new(mut mapping: Mapping, vmo: Vmo, pmt: Pmt, cache_options: CacheOptions) -> Self {
133        let (ptr, _) = mapping.as_ptr_len();
134        Self { mapping, ptr, vmo, pmt, cache_options }
135    }
136
137    /// Returns the size of the mapped DMA buffer in bytes.
138    pub fn size(&self) -> usize {
139        self.mapping.len()
140    }
141
142    /// Returns a reference to the underlying VMO.
143    pub fn vmo(&self) -> &Vmo {
144        &self.vmo
145    }
146
147    /// Returns a reference to the underlying [`Mapping`].
148    pub fn mapping(&self) -> &Mapping {
149        &self.mapping
150    }
151
152    /// Returns a mutable reference to the underlying [`Mapping`].
153    pub fn mapping_mut(&mut self) -> &mut Mapping {
154        &mut self.mapping
155    }
156
157    /// Returns whether CPU caching is enabled for this DMA buffer.
158    pub fn is_cache_enabled(&self) -> bool {
159        self.cache_options == CacheOptions::Enabled
160    }
161
162    /// Returns the [`CacheOptions`] configured for this DMA buffer.
163    pub fn cache_options(&self) -> CacheOptions {
164        self.cache_options
165    }
166
167    /// Unpins the DMA buffer's physical memory token, consuming the buffer.
168    ///
169    /// Note that `Drop` does not automatically unpin the buffer (instead allowing the kernel to
170    /// quarantine the pages on the BTI if dropped while still pinned). Call this method after
171    /// ensuring hardware DMA has ceased.
172    ///
173    /// # Safety
174    ///
175    /// The caller must ensure that hardware is no longer accessing the pinned memory (e.g. after
176    /// resetting or quiescing the device).
177    pub unsafe fn unpin(self) -> Result<(), Status> {
178        // SAFETY: The caller guarantees hardware DMA to this buffer has ceased.
179        unsafe { self.pmt.unpin() }
180    }
181
182    /// Flushes CPU data cache lines (clean to memory) for the given byte range.
183    ///
184    /// Use after CPU writes to the buffer so that DMA hardware can read from physical RAM
185    /// (`DMA_TO_DEVICE`).
186    pub fn cache_flush(&self, offset: usize, length: usize) -> Result<(), Status> {
187        if length == 0 || !self.is_cache_enabled() {
188            return Ok(());
189        }
190        if offset.checked_add(length).is_none_or(|end| end > self.size()) {
191            return Err(Status::OUT_OF_RANGE);
192        }
193        self.vmo.op_range(VmoOp::CACHE_CLEAN, offset as u64, length as u64)
194    }
195
196    /// Flushes and invalidates CPU data cache lines for the given byte range.
197    ///
198    /// Use before CPU reads from the buffer after DMA hardware writes to physical RAM
199    /// (`DMA_FROM_DEVICE`).
200    pub fn cache_flush_invalidate(&self, offset: usize, length: usize) -> Result<(), Status> {
201        if length == 0 || !self.is_cache_enabled() {
202            return Ok(());
203        }
204        if offset.checked_add(length).is_none_or(|end| end > self.size()) {
205            return Err(Status::OUT_OF_RANGE);
206        }
207        self.vmo.op_range(VmoOp::CACHE_CLEAN_INVALIDATE, offset as u64, length as u64)
208    }
209
210    /// Writes `src` into the DMA buffer at `offset` and flushes the CPU cache if caching is
211    /// enabled.
212    pub fn write(&self, offset: usize, src: &[u8]) -> Result<(), Status> {
213        if src.is_empty() {
214            return Ok(());
215        }
216        if offset.checked_add(src.len()).is_none_or(|end| end > self.size()) {
217            return Err(Status::OUT_OF_RANGE);
218        }
219        if self.is_cache_enabled() {
220            assert_eq!(self.mapping.write_at(offset, src), src.len());
221            self.cache_flush(offset, src.len())
222        } else {
223            // SAFETY: `offset + src.len() <= self.size()` was verified above.
224            unsafe { uncached_write(self.ptr.add(offset), src) };
225            Ok(())
226        }
227    }
228
229    /// Invalidates the CPU cache if caching is enabled and reads from the DMA buffer at `offset`
230    /// into `dst`.
231    pub fn read(&self, offset: usize, dst: &mut [u8]) -> Result<(), Status> {
232        if dst.is_empty() {
233            return Ok(());
234        }
235        if offset.checked_add(dst.len()).is_none_or(|end| end > self.size()) {
236            return Err(Status::OUT_OF_RANGE);
237        }
238        if self.is_cache_enabled() {
239            self.cache_flush_invalidate(offset, dst.len())?;
240            assert_eq!(self.mapping.read_at(offset, dst), dst.len());
241        } else {
242            // SAFETY: `offset + dst.len() <= self.size()` was verified above.
243            unsafe { uncached_read(self.ptr.add(offset), dst) };
244        }
245        Ok(())
246    }
247
248    /// Writes a value of type `T` at byte `offset` with automatic cache flush.
249    pub fn write_struct<T: IntoBytes + Immutable>(
250        &self,
251        offset: usize,
252        val: &T,
253    ) -> Result<(), Status> {
254        self.write(offset, val.as_bytes())
255    }
256
257    /// Reads a value of type `T` from byte `offset` with automatic cache invalidation.
258    pub fn read_struct<T: FromBytes + IntoBytes + KnownLayout + Immutable>(
259        &self,
260        offset: usize,
261    ) -> Result<T, Status> {
262        let mut val = T::new_zeroed();
263        self.read(offset, val.as_mut_bytes())?;
264        Ok(val)
265    }
266
267    /// Reads the `index`-th element of type `T` (at byte offset `index * size_of::<T>()`) from the
268    /// mapped buffer.
269    ///
270    /// # Panics
271    ///
272    /// Panics if `index * size_of::<T>() + size_of::<T>()` overflows or exceeds `self.size()`.
273    pub fn read_element<T: FromBytes + IntoBytes + KnownLayout + Immutable>(
274        &self,
275        index: usize,
276    ) -> T {
277        let mut val = T::new_zeroed();
278        let dst = val.as_mut_bytes();
279        let offset = index.checked_mul(dst.len()).expect("DMA read index overflow");
280        assert!(
281            offset.checked_add(dst.len()).is_some_and(|end| end <= self.size()),
282            "DMA read out of bounds"
283        );
284        assert_eq!(self.mapping.read_at(offset, dst), dst.len());
285        val
286    }
287
288    /// Writes `value` as the `index`-th element of type `T` (at byte offset
289    /// `index * size_of::<T>()`) in the mapped buffer.
290    ///
291    /// # Panics
292    ///
293    /// Panics if `index * size_of::<T>() + size_of::<T>()` overflows or exceeds `self.size()`.
294    pub fn write_element<T: IntoBytes + Immutable>(&self, index: usize, value: &T) {
295        let src = value.as_bytes();
296        let offset = index.checked_mul(src.len()).expect("DMA write index overflow");
297        assert!(
298            offset.checked_add(src.len()).is_some_and(|end| end <= self.size()),
299            "DMA write out of bounds"
300        );
301        assert_eq!(self.mapping.write_at(offset, src), src.len());
302    }
303}
304
305/// A physically contiguous DMA buffer pinned to a [`Bti`] and mapped into a [`Vmar`].
306#[derive(Debug)]
307pub struct ContiguousBuffer {
308    buffer: Buffer,
309    phys: zx_paddr_t,
310}
311
312impl Deref for ContiguousBuffer {
313    type Target = Buffer;
314
315    fn deref(&self) -> &Self::Target {
316        &self.buffer
317    }
318}
319
320impl DerefMut for ContiguousBuffer {
321    fn deref_mut(&mut self) -> &mut Self::Target {
322        &mut self.buffer
323    }
324}
325
326impl ContiguousBuffer {
327    /// Creates a new contiguous DMA buffer of `size` bytes (rounded up to the system page size)
328    /// mapped in the root VMAR with default alignment and CPU caching enabled.
329    pub fn new(bti: &Bti, size: usize) -> Result<Self, Status> {
330        Self::new_with_vmar(bti, &vmar_root_self(), size)
331    }
332
333    /// Creates a new contiguous DMA buffer of `size` bytes (rounded up to the system page size)
334    /// mapped in `vmar` with default alignment and CPU caching enabled.
335    pub fn new_with_vmar(bti: &Bti, vmar: &Vmar, size: usize) -> Result<Self, Status> {
336        Self::new_with_options(bti, vmar, size, 0, CacheOptions::Enabled)
337    }
338
339    /// Creates a new contiguous DMA buffer of `size` bytes (rounded up to the system page size)
340    /// mapped in `vmar` with the specified `alignment_log2` and `cache_options`.
341    pub fn new_with_options(
342        bti: &Bti,
343        vmar: &Vmar,
344        size: usize,
345        alignment_log2: u32,
346        cache_options: CacheOptions,
347    ) -> Result<Self, Status> {
348        if size == 0 {
349            return Err(Status::INVALID_ARGS);
350        }
351        let page_size = system_get_page_size() as usize;
352        let mapped_size = size.checked_next_multiple_of(page_size).ok_or(Status::OUT_OF_RANGE)?;
353
354        let vmo = Vmo::create_contiguous(bti, mapped_size, alignment_log2)?;
355        if cache_options == CacheOptions::Disabled {
356            vmo.set_cache_policy(CachePolicy::UnCachedDevice)?;
357        }
358        let mapping = Mapping::create_from_vmo_with_vmar(
359            &vmo,
360            vmar,
361            mapped_size,
362            VmarFlags::PERM_READ | VmarFlags::PERM_WRITE,
363        )?;
364        let mut phys = [0 as zx_paddr_t; 1];
365        let pmt = bti.pin(
366            BtiOptions::PERM_READ | BtiOptions::PERM_WRITE | BtiOptions::CONTIGUOUS,
367            &vmo,
368            0,
369            mapped_size as u64,
370            &mut phys,
371        )?;
372
373        Ok(Self { buffer: Buffer::new(mapping, vmo, pmt, cache_options), phys: phys[0] })
374    }
375
376    /// Returns a reference to the underlying [`Buffer`].
377    pub fn buffer(&self) -> &Buffer {
378        &self.buffer
379    }
380
381    /// Returns the base physical address of the contiguous DMA buffer.
382    pub fn phys(&self) -> zx_paddr_t {
383        self.phys
384    }
385
386    /// Unpins the DMA buffer's physical memory token, consuming the buffer so its physical
387    /// address and mapping cannot be accessed after unpinning.
388    ///
389    /// # Safety
390    ///
391    /// The caller must ensure that hardware is no longer accessing the pinned memory (e.g. after
392    /// resetting or quiescing the device).
393    pub unsafe fn unpin(self) -> Result<(), Status> {
394        // SAFETY: The caller guarantees hardware DMA to this buffer has ceased.
395        unsafe { self.buffer.unpin() }
396    }
397}
398
399/// A paged DMA buffer consisting of one or more pinned pages that are contiguous in virtual memory
400/// (`Mapping`), but may be discontiguous in physical memory.
401#[derive(Debug)]
402pub struct PagedBuffer {
403    buffer: Buffer,
404    phys: Vec<zx_paddr_t>,
405}
406
407impl Deref for PagedBuffer {
408    type Target = Buffer;
409
410    fn deref(&self) -> &Self::Target {
411        &self.buffer
412    }
413}
414
415impl DerefMut for PagedBuffer {
416    fn deref_mut(&mut self) -> &mut Self::Target {
417        &mut self.buffer
418    }
419}
420
421impl PagedBuffer {
422    /// Creates a new paged DMA buffer of `size` bytes (rounded up to the system page size) mapped
423    /// in the root VMAR with CPU caching enabled.
424    pub fn new(bti: &Bti, size: usize) -> Result<Self, Status> {
425        Self::new_with_vmar(bti, &vmar_root_self(), size)
426    }
427
428    /// Creates a new paged DMA buffer of `size` bytes (rounded up to the system page size) mapped
429    /// in `vmar` with CPU caching enabled.
430    pub fn new_with_vmar(bti: &Bti, vmar: &Vmar, size: usize) -> Result<Self, Status> {
431        Self::new_with_options(bti, vmar, size, CacheOptions::Enabled)
432    }
433
434    /// Creates a new paged DMA buffer of `size` bytes (rounded up to the system page size) mapped
435    /// in `vmar` with the specified `cache_options`.
436    pub fn new_with_options(
437        bti: &Bti,
438        vmar: &Vmar,
439        size: usize,
440        cache_options: CacheOptions,
441    ) -> Result<Self, Status> {
442        if size == 0 {
443            return Err(Status::INVALID_ARGS);
444        }
445        let page_size = system_get_page_size() as usize;
446        let mapped_size = size.checked_next_multiple_of(page_size).ok_or(Status::OUT_OF_RANGE)?;
447
448        let vmo = Vmo::create(mapped_size as u64)?;
449        if cache_options == CacheOptions::Disabled {
450            vmo.set_cache_policy(CachePolicy::UnCachedDevice)?;
451        }
452        let mapping = Mapping::create_from_vmo_with_vmar(
453            &vmo,
454            vmar,
455            mapped_size,
456            VmarFlags::PERM_READ | VmarFlags::PERM_WRITE,
457        )?;
458        let mut phys = vec![0 as zx_paddr_t; mapped_size / page_size];
459        let pmt = bti.pin(
460            BtiOptions::PERM_READ | BtiOptions::PERM_WRITE,
461            &vmo,
462            0,
463            mapped_size as u64,
464            &mut phys,
465        )?;
466
467        Ok(Self { buffer: Buffer::new(mapping, vmo, pmt, cache_options), phys })
468    }
469
470    /// Returns a reference to the underlying [`Buffer`].
471    pub fn buffer(&self) -> &Buffer {
472        &self.buffer
473    }
474
475    /// Returns the slice of pinned page physical addresses backing the buffer.
476    pub fn phys(&self) -> &[zx_paddr_t] {
477        &self.phys
478    }
479
480    /// Unpins the DMA buffer's physical memory token, consuming the buffer so its physical
481    /// addresses and mapping cannot be accessed after unpinning.
482    ///
483    /// # Safety
484    ///
485    /// The caller must ensure that hardware is no longer accessing the pinned memory (e.g. after
486    /// resetting or quiescing the device).
487    pub unsafe fn unpin(self) -> Result<(), Status> {
488        // SAFETY: The caller guarantees hardware DMA to this buffer has ceased.
489        unsafe { self.buffer.unpin() }
490    }
491}
492
493#[cfg(test)]
494mod tests {
495    use super::*;
496    use fake_bti::FakeBti;
497
498    #[fuchsia::test]
499    fn test_contiguous_with_cache_enabled() {
500        let bti = FakeBti::create().unwrap();
501        let page_size = system_get_page_size() as usize;
502        let size = page_size * 4;
503        let buffer = ContiguousBuffer::new_with_options(
504            &bti,
505            &vmar_root_self(),
506            size,
507            12,
508            CacheOptions::Enabled,
509        )
510        .unwrap();
511
512        assert_eq!(buffer.size(), size);
513        assert_eq!(buffer.phys(), FakeBti::phys_addr());
514        assert!(buffer.is_cache_enabled());
515        assert_eq!(buffer.cache_options(), CacheOptions::Enabled);
516        assert_eq!(buffer.vmo().info().unwrap().cache_policy(), CachePolicy::Cached);
517        assert_eq!(bti.info().unwrap().pmo_count, 1);
518
519        unsafe { buffer.unpin().unwrap() };
520        assert_eq!(bti.info().unwrap().pmo_count, 0);
521    }
522
523    #[fuchsia::test]
524    fn test_contiguous_with_cache_disabled() {
525        let bti = FakeBti::create().unwrap();
526        let page_size = system_get_page_size() as usize;
527        let size = page_size * 4;
528        let buffer = ContiguousBuffer::new_with_options(
529            &bti,
530            &vmar_root_self(),
531            size,
532            12,
533            CacheOptions::Disabled,
534        )
535        .unwrap();
536
537        assert_eq!(buffer.size(), size);
538        assert_eq!(buffer.phys(), FakeBti::phys_addr());
539        assert!(!buffer.is_cache_enabled());
540        assert_eq!(buffer.cache_options(), CacheOptions::Disabled);
541        assert_eq!(buffer.vmo().info().unwrap().cache_policy(), CachePolicy::UnCachedDevice);
542
543        unsafe { buffer.unpin().unwrap() };
544        assert_eq!(bti.info().unwrap().pmo_count, 0);
545    }
546
547    #[fuchsia::test]
548    fn test_paged_buffer() {
549        let bti = FakeBti::create().unwrap();
550        bti.set_paddrs(&[0x1000, 0x3000, 0x5000]);
551        let page_size = system_get_page_size() as usize;
552        // Request unaligned size (2 pages + 100 bytes) to verify rounding up to 3 pages.
553        let buffer = PagedBuffer::new(&bti, page_size * 2 + 100).unwrap();
554
555        assert_eq!(buffer.size(), page_size * 3);
556        assert_eq!(buffer.phys(), &[0x1000, 0x3000, 0x5000]);
557        assert!(buffer.is_cache_enabled());
558        assert_eq!(bti.info().unwrap().pmo_count, 1);
559
560        unsafe { buffer.unpin().unwrap() };
561        assert_eq!(bti.info().unwrap().pmo_count, 0);
562    }
563
564    #[fuchsia::test]
565    fn test_read_write_and_cache_operations() {
566        let bti = FakeBti::create().unwrap();
567        let size = system_get_page_size() as usize;
568        let buffer = ContiguousBuffer::new(&bti, size).unwrap();
569
570        let write_data = [0xDE, 0xAD, 0xBE, 0xEF];
571        let mut read_data = [0u8; 4];
572        buffer.write(16, &write_data).unwrap();
573        buffer.read(16, &mut read_data).unwrap();
574        assert_eq!(write_data, read_data);
575
576        #[repr(C)]
577        #[derive(
578            Debug, Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable,
579        )]
580        struct TestPacket {
581            magic: u32,
582            length: u16,
583            flags: u8,
584            pad: u8,
585        }
586
587        let out_pkt = TestPacket { magic: 0x12345678, length: 64, flags: 0xFF, pad: 0 };
588        buffer.write_struct(64, &out_pkt).unwrap();
589        let in_pkt: TestPacket = buffer.read_struct(64).unwrap();
590        assert_eq!(out_pkt, in_pkt);
591
592        buffer.write_element(3, &out_pkt);
593        assert_eq!(buffer.read_element::<TestPacket>(3), out_pkt);
594
595        assert_eq!(buffer.write(size - 2, &write_data), Err(Status::OUT_OF_RANGE));
596        assert_eq!(buffer.read(size - 2, &mut read_data), Err(Status::OUT_OF_RANGE));
597
598        assert_eq!(buffer.cache_flush(0, 0), Ok(()));
599        assert_eq!(buffer.cache_flush(0, 32), Ok(()));
600        assert_eq!(buffer.cache_flush_invalidate(0, 0), Ok(()));
601        assert_eq!(buffer.cache_flush_invalidate(0, 32), Ok(()));
602        assert_eq!(buffer.cache_flush(size - 10, 20), Err(Status::OUT_OF_RANGE));
603        assert_eq!(buffer.cache_flush_invalidate(size - 10, 20), Err(Status::OUT_OF_RANGE));
604
605        unsafe { buffer.unpin().unwrap() };
606    }
607
608    #[fuchsia::test]
609    fn test_uncached_memory_helper_all_cases() {
610        let bti = FakeBti::create().unwrap();
611        let size = system_get_page_size() as usize;
612        let buffer = ContiguousBuffer::new_with_options(
613            &bti,
614            &vmar_root_self(),
615            size,
616            0,
617            CacheOptions::Disabled,
618        )
619        .unwrap();
620        assert!(!buffer.is_cache_enabled());
621
622        // 1. Zero-length transfer.
623        let mut empty = [];
624        assert_eq!(buffer.write(0, &empty), Ok(()));
625        assert_eq!(buffer.read(0, &mut empty), Ok(()));
626
627        // 2. Background pattern verification helper.
628        let bg_pattern = vec![0xAAu8; size];
629        buffer.write(0, &bg_pattern).unwrap();
630
631        let verify_slice = |offset: usize, length: usize, val: u8| {
632            let mut read_back = vec![0u8; size];
633            buffer.read(0, &mut read_back).unwrap();
634            for (i, &b) in read_back.iter().enumerate() {
635                if i >= offset && i < offset + length {
636                    assert_eq!(b, val, "mismatch at modified index {i}");
637                } else {
638                    assert_eq!(b, 0xAA, "corruption at background index {i}");
639                }
640            }
641        };
642
643        // 3. Small unaligned transfer (< 8 bytes, at offset 1).
644        buffer.write(1, &[0x11; 3]).unwrap();
645        verify_slice(1, 3, 0x11);
646        buffer.write(0, &bg_pattern).unwrap();
647
648        // 4. Unaligned head alignment (offset 3, length 24).
649        buffer.write(3, &[0x22; 24]).unwrap();
650        verify_slice(3, 24, 0x22);
651        buffer.write(0, &bg_pattern).unwrap();
652
653        // 5. Unaligned tail alignment (offset 8, length 13).
654        buffer.write(8, &[0x33; 13]).unwrap();
655        verify_slice(8, 13, 0x33);
656        buffer.write(0, &bg_pattern).unwrap();
657
658        // 6. Both unaligned head and unaligned tail (offset 5, length 19).
659        buffer.write(5, &[0x44; 19]).unwrap();
660        verify_slice(5, 19, 0x44);
661        buffer.write(0, &bg_pattern).unwrap();
662
663        // 7. Exact word boundary transfer (offset 16, length 32).
664        buffer.write(16, &[0x66; 32]).unwrap();
665        verify_slice(16, 32, 0x66);
666
667        unsafe { buffer.unpin().unwrap() };
668    }
669}