Skip to main content

dma_buffer/
phys_iter.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//! Physical address and size segment iterator for pinned DMA buffers.
6
7use zx::sys::zx_paddr_t;
8use zx::system_get_page_size;
9
10/// An iterator yielding `(zx_paddr_t, usize)` physical address and byte length pairs for a pinned
11/// DMA buffer.
12///
13/// The yielded pairs are suitable for hardware DMA transactions that consume a physical address and
14/// byte length. The iterator ensures chunk boundaries are not crossed unless the buffer is backed
15/// by a single contiguous chunk (`chunk_list.len() == 1`) or contiguous chunk merging (`merge`) is
16/// enabled.
17#[derive(Debug, Clone)]
18pub struct PhysIter<'a> {
19    chunk_list: &'a [zx_paddr_t],
20    buf_length: usize,
21    max_length: usize,
22    chunk_size: usize,
23    merge: bool,
24    chunk_index: usize,
25    offset_in_chunk: usize,
26    current: Option<(zx_paddr_t, usize)>,
27    iterated_bytes: usize,
28}
29
30impl<'a> PhysIter<'a> {
31    /// Creates a new `PhysIter` with page-sized chunks, unlimited segment length, and chunk merging
32    /// disabled.
33    ///
34    /// # Panics
35    ///
36    /// Panics if `chunk_list` is empty, `buf_length == 0`, or
37    /// `chunk_list.len() > 1 && vmo_offset >= system_get_page_size() as usize`.
38    pub fn new(chunk_list: &'a [zx_paddr_t], vmo_offset: usize, buf_length: usize) -> Self {
39        Self::new_with_options(
40            chunk_list,
41            system_get_page_size() as usize,
42            vmo_offset,
43            buf_length,
44            usize::MAX,
45            false,
46        )
47    }
48
49    /// Creates a new `PhysIter` with explicit chunk size, maximum segment length, and chunk
50    /// merging options.
51    ///
52    /// # Panics
53    ///
54    /// Panics if `chunk_list` is empty, `buf_length == 0`, `chunk_size` is not a non-zero power
55    /// of two, or `chunk_list.len() > 1 && vmo_offset >= chunk_size`.
56    pub fn new_with_options(
57        chunk_list: &'a [zx_paddr_t],
58        chunk_size: usize,
59        vmo_offset: usize,
60        buf_length: usize,
61        max_length: usize,
62        merge: bool,
63    ) -> Self {
64        assert!(!chunk_list.is_empty(), "chunk_list must not be empty");
65        assert!(buf_length > 0, "buf_length must be greater than 0");
66        assert!(chunk_size.is_power_of_two(), "chunk_size must be a power of 2");
67        assert!(
68            chunk_list.len() == 1 || vmo_offset < chunk_size,
69            "vmo_offset must be less than chunk_size when chunk_list has multiple entries"
70        );
71
72        let max_length = if max_length == 0 { usize::MAX } else { max_length };
73
74        let mut iter = Self {
75            chunk_list,
76            buf_length,
77            max_length,
78            chunk_size,
79            merge,
80            chunk_index: 0,
81            offset_in_chunk: vmo_offset,
82            current: None,
83            iterated_bytes: 0,
84        };
85        let first_addr = chunk_list[0] + vmo_offset;
86        let first_size = iter.boundary_truncate();
87        iter.current = Some((first_addr, first_size));
88        iter
89    }
90
91    fn boundary_truncate(&self) -> usize {
92        let end_of_buffer = self.buf_length - self.iterated_bytes;
93        if self.chunk_list.len() == 1 {
94            return self.max_length.min(end_of_buffer);
95        }
96
97        let mut end_of_chunk = self.chunk_size - self.offset_in_chunk;
98
99        if self.merge {
100            let mut idx = self.chunk_index;
101            while end_of_chunk < self.max_length
102                && end_of_chunk < end_of_buffer
103                && idx + 1 < self.chunk_list.len()
104                && self.chunk_list[idx] + self.chunk_size == self.chunk_list[idx + 1]
105            {
106                end_of_chunk += self.chunk_size;
107                idx += 1;
108            }
109        }
110
111        self.max_length.min(end_of_chunk.min(end_of_buffer))
112    }
113}
114
115impl Iterator for PhysIter<'_> {
116    type Item = (zx_paddr_t, usize);
117
118    fn next(&mut self) -> Option<Self::Item> {
119        let (cur_addr, cur_size) = self.current?;
120        let item = (cur_addr, cur_size);
121
122        self.iterated_bytes += cur_size;
123        if self.iterated_bytes == self.buf_length {
124            self.current = None;
125            return Some(item);
126        }
127
128        let next_addr = if self.chunk_list.len() == 1 {
129            cur_addr + cur_size
130        } else {
131            let next_offset = self.offset_in_chunk + cur_size;
132            self.chunk_index += next_offset / self.chunk_size;
133            self.offset_in_chunk = next_offset % self.chunk_size;
134            self.chunk_list[self.chunk_index] + self.offset_in_chunk
135        };
136
137        let next_size = self.boundary_truncate();
138        self.current = Some((next_addr, next_size));
139        Some(item)
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use test_case::test_case;
147
148    const PAGE_SIZE: usize = 4096;
149    const HALF_PAGE: usize = 2048;
150
151    #[test_case(
152        &[PAGE_SIZE, 2 * PAGE_SIZE],
153        PAGE_SIZE,
154        0,
155        2 * PAGE_SIZE,
156        usize::MAX,
157        false,
158        &[(PAGE_SIZE, PAGE_SIZE), (2 * PAGE_SIZE, PAGE_SIZE)];
159        "simple_page_boundary"
160    )]
161    #[test_case(
162        &[PAGE_SIZE, 3 * PAGE_SIZE, 5 * PAGE_SIZE],
163        PAGE_SIZE,
164        0,
165        3 * PAGE_SIZE,
166        usize::MAX,
167        false,
168        &[(PAGE_SIZE, PAGE_SIZE), (3 * PAGE_SIZE, PAGE_SIZE), (5 * PAGE_SIZE, PAGE_SIZE)];
169        "non_contiguous_pages"
170    )]
171    #[test_case(
172        &[PAGE_SIZE, 2 * PAGE_SIZE, 3 * PAGE_SIZE],
173        PAGE_SIZE,
174        PAGE_SIZE - 5,
175        2 * PAGE_SIZE + 5,
176        usize::MAX,
177        false,
178        &[(2 * PAGE_SIZE - 5, 5), (2 * PAGE_SIZE, PAGE_SIZE), (3 * PAGE_SIZE, PAGE_SIZE)];
179        "partial_first_page_contiguous"
180    )]
181    #[test_case(
182        &[PAGE_SIZE, 3 * PAGE_SIZE, 5 * PAGE_SIZE],
183        PAGE_SIZE,
184        PAGE_SIZE - 5,
185        2 * PAGE_SIZE + 5,
186        usize::MAX,
187        false,
188        &[(2 * PAGE_SIZE - 5, 5), (3 * PAGE_SIZE, PAGE_SIZE), (5 * PAGE_SIZE, PAGE_SIZE)];
189        "partial_first_page_non_contiguous"
190    )]
191    #[test_case(
192        &[PAGE_SIZE, 2 * PAGE_SIZE, 3 * PAGE_SIZE],
193        PAGE_SIZE,
194        0,
195        2 * PAGE_SIZE + 5,
196        usize::MAX,
197        false,
198        &[(PAGE_SIZE, PAGE_SIZE), (2 * PAGE_SIZE, PAGE_SIZE), (3 * PAGE_SIZE, 5)];
199        "partial_last_page_contiguous"
200    )]
201    #[test_case(
202        &[PAGE_SIZE, 3 * PAGE_SIZE, 5 * PAGE_SIZE],
203        PAGE_SIZE,
204        0,
205        2 * PAGE_SIZE + 5,
206        usize::MAX,
207        false,
208        &[(PAGE_SIZE, PAGE_SIZE), (3 * PAGE_SIZE, PAGE_SIZE), (5 * PAGE_SIZE, 5)];
209        "partial_last_page_non_contiguous"
210    )]
211    #[test_case(
212        &[PAGE_SIZE, 2 * PAGE_SIZE],
213        PAGE_SIZE,
214        0,
215        2 * PAGE_SIZE,
216        HALF_PAGE,
217        false,
218        &[
219            (2 * HALF_PAGE, HALF_PAGE),
220            (3 * HALF_PAGE, HALF_PAGE),
221            (4 * HALF_PAGE, HALF_PAGE),
222            (5 * HALF_PAGE, HALF_PAGE),
223        ];
224        "sub_chunk_max_length"
225    )]
226    #[test_case(
227        &[PAGE_SIZE],
228        PAGE_SIZE,
229        0,
230        1 << 20,
231        usize::MAX,
232        false,
233        &[(PAGE_SIZE, 1 << 20)];
234        "zx_bti_contiguous_large_buffer"
235    )]
236    #[test_case(
237        &[HALF_PAGE, 2 * HALF_PAGE, 3 * HALF_PAGE],
238        HALF_PAGE,
239        0,
240        3 * HALF_PAGE,
241        usize::MAX,
242        false,
243        &[(HALF_PAGE, HALF_PAGE), (2 * HALF_PAGE, HALF_PAGE), (3 * HALF_PAGE, HALF_PAGE)];
244        "zx_bti_compress_contiguous"
245    )]
246    #[test_case(
247        &[HALF_PAGE, 3 * HALF_PAGE, 5 * HALF_PAGE],
248        HALF_PAGE,
249        0,
250        3 * HALF_PAGE,
251        usize::MAX,
252        false,
253        &[(HALF_PAGE, HALF_PAGE), (3 * HALF_PAGE, HALF_PAGE), (5 * HALF_PAGE, HALF_PAGE)];
254        "zx_bti_compress_non_contiguous"
255    )]
256    #[test_case(
257        &[PAGE_SIZE],
258        PAGE_SIZE,
259        0,
260        2 * PAGE_SIZE,
261        HALF_PAGE,
262        false,
263        &[
264            (2 * HALF_PAGE, HALF_PAGE),
265            (3 * HALF_PAGE, HALF_PAGE),
266            (4 * HALF_PAGE, HALF_PAGE),
267            (5 * HALF_PAGE, HALF_PAGE),
268        ];
269        "zx_bti_contiguous_sub_chunk_max_length"
270    )]
271    #[test_case(
272        &[PAGE_SIZE, 3 * PAGE_SIZE],
273        PAGE_SIZE,
274        0,
275        2 * PAGE_SIZE,
276        HALF_PAGE,
277        false,
278        &[
279            (2 * HALF_PAGE, HALF_PAGE),
280            (3 * HALF_PAGE, HALF_PAGE),
281            (6 * HALF_PAGE, HALF_PAGE),
282            (7 * HALF_PAGE, HALF_PAGE),
283        ];
284        "zx_bti_non_contiguous_sub_chunk_max_length"
285    )]
286    #[test_case(
287        &[PAGE_SIZE, 2 * PAGE_SIZE],
288        PAGE_SIZE,
289        0,
290        2 * PAGE_SIZE,
291        usize::MAX,
292        true,
293        &[(PAGE_SIZE, 2 * PAGE_SIZE)];
294        "simple_page_boundary_merge"
295    )]
296    #[test_case(
297        &[PAGE_SIZE, 3 * PAGE_SIZE, 5 * PAGE_SIZE],
298        PAGE_SIZE,
299        0,
300        3 * PAGE_SIZE,
301        usize::MAX,
302        true,
303        &[(PAGE_SIZE, PAGE_SIZE), (3 * PAGE_SIZE, PAGE_SIZE), (5 * PAGE_SIZE, PAGE_SIZE)];
304        "non_contiguous_pages_merge"
305    )]
306    #[test_case(
307        &[PAGE_SIZE, 2 * PAGE_SIZE, 4 * PAGE_SIZE, 5 * PAGE_SIZE, 6 * PAGE_SIZE],
308        PAGE_SIZE,
309        0,
310        5 * PAGE_SIZE,
311        usize::MAX,
312        true,
313        &[(PAGE_SIZE, 2 * PAGE_SIZE), (4 * PAGE_SIZE, 3 * PAGE_SIZE)];
314        "mixed_contiguous_and_non_contiguous_merge"
315    )]
316    #[test_case(
317        &[PAGE_SIZE, 2 * PAGE_SIZE, 3 * PAGE_SIZE],
318        PAGE_SIZE,
319        PAGE_SIZE - 5,
320        2 * PAGE_SIZE + 5,
321        usize::MAX,
322        true,
323        &[(2 * PAGE_SIZE - 5, 2 * PAGE_SIZE + 5)];
324        "partial_first_page_contiguous_merge"
325    )]
326    #[test_case(
327        &[PAGE_SIZE, 2 * PAGE_SIZE, 3 * PAGE_SIZE],
328        PAGE_SIZE,
329        0,
330        2 * PAGE_SIZE + 5,
331        usize::MAX,
332        true,
333        &[(PAGE_SIZE, 2 * PAGE_SIZE + 5)];
334        "partial_last_page_contiguous_merge"
335    )]
336    #[test_case(
337        &[PAGE_SIZE, 2 * PAGE_SIZE, 3 * PAGE_SIZE, 4 * PAGE_SIZE],
338        PAGE_SIZE,
339        0,
340        4 * PAGE_SIZE,
341        2 * PAGE_SIZE,
342        true,
343        &[(PAGE_SIZE, 2 * PAGE_SIZE), (3 * PAGE_SIZE, 2 * PAGE_SIZE)];
344        "merge_respects_chunk_aligned_max_length"
345    )]
346    #[test_case(
347        &[PAGE_SIZE, 2 * PAGE_SIZE, 3 * PAGE_SIZE, 5 * PAGE_SIZE],
348        PAGE_SIZE,
349        0,
350        4 * PAGE_SIZE,
351        PAGE_SIZE + HALF_PAGE,
352        true,
353        &[
354            (PAGE_SIZE, PAGE_SIZE + HALF_PAGE),
355            (2 * PAGE_SIZE + HALF_PAGE, PAGE_SIZE + HALF_PAGE),
356            (5 * PAGE_SIZE, PAGE_SIZE),
357        ];
358        "merge_respects_unaligned_max_length"
359    )]
360    #[test_case(
361        &[HALF_PAGE, 2 * HALF_PAGE, 3 * HALF_PAGE],
362        HALF_PAGE,
363        0,
364        3 * HALF_PAGE,
365        usize::MAX,
366        true,
367        &[(HALF_PAGE, 3 * HALF_PAGE)];
368        "zx_bti_compress_contiguous_merge"
369    )]
370    #[test_case(
371        &[0x22_0000, 0x35_0000],
372        0x10_0000,
373        0xf_8000,
374        0x1_0000,
375        0x1_0000,
376        true,
377        &[(0x31_8000, 0x8000), (0x35_0000, 0x8000)];
378        "page_aligned_large_chunks_non_contiguous"
379    )]
380    #[test_case(
381        &[0x22_0000, 0x32_0000],
382        0x10_0000,
383        0xf_8000,
384        0x1_0000,
385        0x1_0000,
386        true,
387        &[(0x31_8000, 0x1_0000)];
388        "page_aligned_large_chunks_contiguous_merge"
389    )]
390    #[test_case(
391        &[0x22_0000, 0x35_0000],
392        0x10_0000,
393        0xd_8000,
394        0x3_0000,
395        0x1_0000,
396        true,
397        &[(0x2f_8000, 0x1_0000), (0x30_8000, 0x1_0000), (0x31_8000, 0x8000), (0x35_0000, 0x8000)];
398        "page_aligned_large_chunks_multi_step_across_boundary"
399    )]
400    fn test_phys_iter(
401        chunk_list: &[zx_paddr_t],
402        chunk_size: usize,
403        vmo_offset: usize,
404        buf_length: usize,
405        max_length: usize,
406        merge: bool,
407        expected: &[(zx_paddr_t, usize)],
408    ) {
409        let iter = PhysIter::new_with_options(
410            chunk_list, chunk_size, vmo_offset, buf_length, max_length, merge,
411        );
412        let actual: Vec<_> = iter.collect();
413        assert_eq!(actual, expected);
414    }
415}