Skip to main content

memalloc/
range.rs

1// Copyright 2026 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7//! A memory range type used in the representation of the internal memory map managed by Memalloc.
8//!
9//! This module introduces the [`Range`] type, which represents a contiguous chunk of physical
10//! memory.  Each range has a [`Type`] associated with it, which describes what the memory is used
11//! for.
12//!
13//! The [`NormalizedRangesIterator`] subtrait is introduced to indicate that an iterator over a
14//! collection of ranges behaves nicely.
15
16use core::cmp::Ordering;
17use core::iter::Peekable;
18use core::mem::offset_of;
19use core::slice;
20
21use zr::static_assert;
22
23/// Helper macro for defining the [`Type`] enum to assume the discriminants from C++ bindgen.
24macro_rules! define_type_enum {
25    (
26        $(#[$enum_attr:meta])*
27        $vis:vis enum $name:ident {
28            $(
29                $(#[$variant_attr:meta])*
30                $variant:ident
31            ),* $(,)?
32        }
33    ) => {
34        paste::paste! {
35            $(#[$enum_attr])*
36            $vis enum $name {
37                $(
38                    $(#[$variant_attr])*
39                    $variant = bindings::memalloc_Type::[<k $variant>] as u64,
40                )*
41            }
42        }
43    };
44}
45
46define_type_enum! {
47    /// A description of a physical memory range.
48    ///
49    /// A few important sub-categories of the `Type` enum:
50    ///
51    ///     * Base types. These are the three types specified by the ZBI: `Type::FreeRam`,
52    ///     `Type::Peripheral`, and `Type::Reserved`.
53    ///
54    ///     * Allocated types.  These are all of the types which are specific to Memalloc's use
55    ///     case, and are not present in the ZBI spec.  For example, `Type::PoolBookkeeping`,
56    ///     `Type::PhysKernel`, etc.  These types are all carved out from `Type::FreeRam`.
57    ///
58    ///     * Ram types.  This encompasses all of the allocated types plus `Type::FreeRam`
59    ///
60    /// `Type` is represented by 64 bits.
61    ///
62    /// The lower 2^32 values in the space are reserved for the base types; the upper values are
63    /// allocated types.
64    ///
65    /// The C++ definition of this enum serves as the source of truth for all of the discriminant
66    /// values.  We enforce this in Rust indirectly by assigning the variants of the enum to the
67    /// C++ values, with bindgen.  The [`define_type_enum!`] macro makes this assignment automatic.
68    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
69    #[repr(u64)]
70    pub enum Type {
71        FreeRam,
72        Peripheral,
73        Reserved,
74        /// Reserved for internal bookkeeping.
75        PoolBookkeeping,
76        /// The phys ZBI kernel memory image.
77        PhysKernel,
78        /// A phys ELF memory image.
79        PhysElf,
80        /// A phys log file.
81        PhysLog,
82        /// The kernel memory image.
83        Kernel,
84        /// A (decompressed) STORAGE_KERNEL ZBI payload.
85        KernelStorage,
86        /// The data ZBI, as placed by the bootloader.
87        DataZbi,
88        /// Memory intended to remain allocated until the end of the phys hand-off phase.
89        TemporaryPhysHandoff,
90        /// Memory intended to remain allocated for the lifetime of the kernel.
91        PermanentPhysHandoff,
92        /// A vDSO memory image.
93        Vdso,
94        /// A userboot memory image.
95        Userboot,
96        /// The kernel's boot machine stack.
97        BootMachineStack,
98        /// The kernel's boot shadow call stack, if supported.
99        BootShadowCallStack,
100        /// The kernel's boot unsafe stack, if safe stack is supported.
101        BootUnsafeStack,
102        /// The intermediate kernel memory image used to trampoline into the same image loaded at a
103        /// fixed address (i.e., as used by FixedAddressBootZbi).
104        FixedAddressStagingKernel,
105        /// The intermediate data ZBI used to trampoline into the same image loaded at a fixed
106        /// address (i.e., as used by FixedAddressBootZbi).
107        FixedAddressStagingDataZbi,
108        /// Data structures related to legacy boot protocols.
109        LegacyBootData,
110        /// Identity-mapping page tables intended only for the lifetime of the phys program in
111        /// execution.
112        TemporaryIdentityPageTables,
113        /// Page tables that describe mappings intended to exist into the kernel's proper lifetime.
114        KernelPageTables,
115        /// A debug data blob of phys origin (e.g., related to instrumentation).
116        PhysDebugdata,
117        /// A firmware-provided devicetree blob.
118        DevicetreeBlob,
119        /// General scratch space used by the phys kernel, but that which is free for the next
120        /// kernel as of hand-off.
121        PhysScratch,
122        /// A generic allocated type for Pool tests.
123        PoolTestPayload,
124        /// A generic allocated type for ZBI tests.
125        ZbiTestPayload,
126        /// Memory carved out for the kernel.test.ram.reserve boot option.
127        TestRamReserve,
128        /// Memory carved out for the ZBI_TYPE_NVRAM region.
129        Nvram,
130        /// Low memory, in an architecture-specific context, that is deemed to be unsafe for
131        /// general-purpose allocation and use (e.g., BIOS-related bits below 1MiB in the case of
132        /// PCs).
133        ReservedLow,
134        /// RAM 'discarded' from a truncation of the physical address space when simulating booting
135        /// contexts with less physical memory available.
136        TruncatedRam,
137    }
138}
139
140// Ensure `Type` has the same layout as the C++ version, using bindgen.
141static_assert!(size_of::<Type>() == size_of::<bindings::memalloc_Type>());
142static_assert!(align_of::<Type>() == align_of::<bindings::memalloc_Type>());
143
144impl Type {
145    /// Memalloc reserves the discriminant values above 2^32 to enumerate its own types.
146    pub const MIN_ALLOCATED_VALUE: u64 = bindings::memalloc_kMinAllocatedTypeValue;
147
148    /// Convert a `Type` to a string slice.
149    pub const fn as_str(&self) -> &'static str {
150        match self {
151            Type::FreeRam => "free RAM",
152            Type::Reserved => "reserved",
153            Type::Peripheral => "peripheral",
154            Type::PoolBookkeeping => "bookkeeping",
155            Type::PhysKernel => "phys ZBI kernel image",
156            Type::PhysElf => "phys ELF image",
157            Type::PhysLog => "phys log file",
158            Type::Kernel => "kernel image",
159            Type::KernelStorage => "decompressed kernel payload",
160            Type::DataZbi => "data ZBI",
161            Type::TemporaryPhysHandoff => "phys hand-off data (temporary)",
162            Type::PermanentPhysHandoff => "phys hand-off data (permanent)",
163            Type::Vdso => "vDSO",
164            Type::Userboot => "userboot",
165            Type::BootMachineStack => "boot machine stack",
166            Type::BootShadowCallStack => "boot shadow call stack",
167            Type::BootUnsafeStack => "boot unsafe stack",
168            Type::FixedAddressStagingKernel => "trampoline staging kernel image",
169            Type::FixedAddressStagingDataZbi => "trampoline staging data ZBI",
170            Type::LegacyBootData => "legacy boot data",
171            Type::TemporaryIdentityPageTables => "temporary identity page tables",
172            Type::KernelPageTables => "kernel page tables",
173            Type::PhysDebugdata => "phys debugdata",
174            Type::DevicetreeBlob => "devicetree blob",
175            Type::PhysScratch => "phys scratch",
176            Type::PoolTestPayload => "memalloc::Pool test payload",
177            Type::ZbiTestPayload => "ZBI test payload",
178            Type::TestRamReserve => "kernel.test.ram.reserve",
179            Type::Nvram => "ZBI_TYPE_NVRAM",
180            Type::ReservedLow => "reserved low memory",
181            Type::TruncatedRam => "truncated RAM",
182        }
183    }
184
185    /// Determine if a `Type` is an allocated type.
186    pub const fn is_allocated(&self) -> bool {
187        (*self as u64) >= Self::MIN_ALLOCATED_VALUE
188    }
189
190    /// Determine if a `Type` is a ram type.
191    pub const fn is_ram(&self) -> bool {
192        matches!(*self, Type::FreeRam) || self.is_allocated()
193    }
194}
195
196/// A physical memory range type.
197///
198/// `Range` specifies a half open iterval of physical memory `[addr, addr + size)` with `ty`
199/// describing the usage of this memory.  It layout-compatible to `zbi_mem_range_t`, but with the
200/// benefit of being able to use allocated types.
201///
202/// `addr + size` never overflows.
203#[repr(C)]
204#[derive(Debug, Clone, Copy, Eq, PartialEq)]
205pub struct Range {
206    addr: u64,
207    size: u64,
208    ty: Type,
209}
210
211// Ensure `Range` has the same layout as the C++ version.
212static_assert!(size_of::<Range>() == size_of::<bindings::memalloc_Range>());
213static_assert!(align_of::<Range>() == align_of::<bindings::memalloc_Range>());
214static_assert!(offset_of!(Range, addr) == offset_of!(bindings::memalloc_Range, addr));
215static_assert!(offset_of!(Range, size) == offset_of!(bindings::memalloc_Range, size));
216static_assert!(offset_of!(Range, ty) == offset_of!(bindings::memalloc_Range, type_));
217
218// Ensure `Range` has the same layout as `zbi::MemRange`.
219static_assert!(size_of::<Range>() == size_of::<zbi::MemRange>());
220static_assert!(align_of::<Range>() == align_of::<zbi::MemRange>());
221static_assert!(offset_of!(Range, addr) == offset_of!(zbi::MemRange, paddr));
222static_assert!(offset_of!(Range, size) == offset_of!(zbi::MemRange, length));
223static_assert!(offset_of!(Range, ty) == offset_of!(zbi::MemRange, r#type));
224
225impl Range {
226    /// Construct a new range.
227    ///
228    /// If `addr + size > u64::MAX`, the size is reduced so there is no overflow.
229    pub const fn new(addr: u64, size: u64, ty: Type) -> Self {
230        let end = addr.saturating_add(size);
231        Self { addr, size: end - addr, ty }
232    }
233
234    /// Sanitize a mutable slice of [`zbi::MemRange`] in-place and view it as `&mut [Range]`.
235    pub fn slice_from_zbi_mem_ranges(ranges: &mut [zbi::MemRange]) -> &mut [Self] {
236        for range in ranges.iter_mut() {
237            range.length = range.paddr.saturating_add(range.length) - range.paddr;
238            range.reserved = 0;
239        }
240        // Safety:
241        //
242        // `Range` and `zbi::MemRange` have identical size, alignment, and field offsets
243        //  (verified by `static_assert!` above).
244        //
245        // Every `zbi::MemType` discriminant with `reserved == 0` in the upper 32 bits is a
246        // valid [`Type`].
247        //
248        // `paddr + length` does not overflow.
249        unsafe { slice::from_raw_parts_mut(ranges.as_mut_ptr().cast::<Range>(), ranges.len()) }
250    }
251
252    /// The start address of the range.
253    pub const fn addr(&self) -> u64 {
254        self.addr
255    }
256
257    /// The size of the range.
258    pub const fn size(&self) -> u64 {
259        self.size
260    }
261
262    /// The type of the range.
263    pub const fn ty(&self) -> Type {
264        self.ty
265    }
266
267    /// The exclusive endpoint of the range.
268    pub const fn end(&self) -> u64 {
269        self.addr + self.size
270    }
271
272    /// Align the ends of a range outward to be multiples of `alignment`.
273    ///
274    /// The start address is rounded down and the end address is rounded up.  If the end address
275    /// would be larger than 2^64, we saturate it.
276    ///
277    /// # Panics
278    ///
279    /// Panics if `alignment` is not a power of two.
280    pub const fn align_out(&self, alignment: u64) -> Self {
281        assert!(alignment.is_power_of_two());
282        let aligned_addr = self.addr & alignment.wrapping_neg();
283        let aligned_end = match self.end().checked_add(alignment - 1) {
284            Some(end) => end & alignment.wrapping_neg(),
285            None => u64::MAX,
286        };
287        Self { addr: aligned_addr, size: aligned_end - aligned_addr, ty: self.ty }
288    }
289
290    /// Determine if `other` intersects with `self`.
291    ///
292    /// Intersection is type-agnostic.
293    pub const fn intersects(&self, other: &Range) -> bool {
294        self.addr < other.end() && other.addr < self.end()
295    }
296}
297
298impl PartialOrd for Range {
299    /// Order ranges lexicographically.
300    ///
301    /// Comparisons are not type sensitive unless both the start address and size are equal to each
302    /// other, in which case different types yields a `None`.
303    fn partial_cmp(&self, other: &Range) -> Option<Ordering> {
304        match (self.addr.cmp(&other.addr), self.size.cmp(&other.size)) {
305            (Ordering::Equal, Ordering::Equal) => {
306                if self.ty == other.ty {
307                    Some(Ordering::Equal)
308                } else {
309                    None
310                }
311            }
312            (Ordering::Equal, size_ord) => Some(size_ord),
313            (addr_ord, _) => Some(addr_ord),
314        }
315    }
316}
317
318/// An iterator over a normalized collection of ranges.
319///
320/// A normalized collection of ranges is one in which the ranges are:
321///
322/// * Lexicographically sorted on endpoint
323/// * Mutually disjoint
324/// * Maximally contiguous (adjacent ranges never share the same type)
325///
326/// # Safety
327///
328/// Implementors must guarantee that the items in iteration order are indeed normalized
329pub unsafe trait NormalizedRangeIterator: Iterator<Item = Range> {
330    /// Creates a normalized range iterator which maps and filters out the range type.
331    ///
332    /// `f` serves as the map that determines which types maps to which, and which are discarded
333    /// or filtered out.
334    fn filter_map_ranges<F>(self, f: F) -> FilterMapRanges<Self, F>
335    where
336        Self: Sized,
337        F: Fn(Type) -> Option<Type>,
338    {
339        FilterMapRanges::new(self, f)
340    }
341
342    /// Creates a normalized range iterator which forgets about non-ram types, and sees allocated
343    /// types as free ram.
344    fn filter_map_ram(self) -> impl NormalizedRangeIterator
345    where
346        Self: Sized,
347    {
348        self.filter_map_ranges(|t| if t.is_ram() { Some(Type::FreeRam) } else { None })
349    }
350}
351
352/// An iterator over a normalized collection of range which uses `f` to filter map the ranges.
353///
354/// The normalized invariant of the underylying collection is preserved.
355#[derive(Clone)]
356pub struct FilterMapRanges<Ranges, F> {
357    ranges: Ranges,
358    f: F,
359    pending: Option<Range>,
360}
361
362impl<Ranges, F> FilterMapRanges<Ranges, F>
363where
364    Ranges: NormalizedRangeIterator,
365    F: Fn(Type) -> Option<Type>,
366{
367    fn new(ranges: impl IntoIterator<IntoIter = Ranges>, f: F) -> Self {
368        Self { ranges: ranges.into_iter(), f, pending: None }
369    }
370}
371
372impl<Ranges, F> Iterator for FilterMapRanges<Ranges, F>
373where
374    Ranges: NormalizedRangeIterator,
375    F: Fn(Type) -> Option<Type>,
376{
377    type Item = Range;
378
379    fn next(&mut self) -> Option<Range> {
380        // The strategy here is to run a sliding window [prev, cur] across the underlying ranges,
381        // using `self.pending` to both store `cur` and collect adjacent ranges of the same type.
382        // A tail-recursive call indicates we slide the window by one without yielding anything.
383
384        let Some(cur) = self.ranges.next() else {
385            // Our sliding window has reached the end.
386            return self.pending.take();
387        };
388
389        // Send cur to f(cur).
390        let Some(ty) = (self.f)(cur.ty) else {
391            // The `f` map discards this range.
392            return self.next();
393        };
394
395        let cur = Range { addr: cur.addr, size: cur.size, ty };
396
397        let Some(prev) = self.pending.take() else {
398            // This is the first call to `next()`.
399            self.pending = Some(cur);
400            return self.next();
401        };
402
403        if prev.end() == cur.addr && prev.ty == cur.ty {
404            self.pending = Some(Range::new(prev.addr, prev.size + cur.size, prev.ty));
405            return self.next();
406        }
407
408        self.pending = Some(cur);
409        Some(prev)
410    }
411}
412
413/// Safety: Merging of neighboring ranges while tweaking types preserves normalization.
414unsafe impl<I, F> NormalizedRangeIterator for FilterMapRanges<I, F>
415where
416    I: NormalizedRangeIterator,
417    F: Fn(Type) -> Option<Type>,
418{
419}
420
421/// Safety:
422///
423/// `Peekable` yields the exact same items in the exact same order as the underlying iterator.
424unsafe impl<I> NormalizedRangeIterator for Peekable<I> where I: NormalizedRangeIterator {}
425
426#[cfg(test)]
427mod tests {
428    use super::super::*;
429    use super::*;
430
431    // [ Book ][ Zbi ][ Test ][Peri ]     [ Test ][Kern ]
432    const TEST_RANGES: [Range; 6] = [
433        Range::new(0, 0x1000, Type::PoolBookkeeping),
434        Range::new(0x1000, 0x1000, Type::DataZbi),
435        Range::new(0x2000, 0x1000, Type::PoolTestPayload),
436        Range::new(0x3000, 0x1000, Type::Peripheral),
437        Range::new(0x5000, 0x1000, Type::PoolTestPayload),
438        Range::new(0x6000, 0x1000, Type::PhysKernel),
439    ];
440
441    // Non-overflowing range preserves addr and size.
442    #[test]
443    fn range_test_new() {
444        assert_eq!(
445            Range::new(0, 0x1000, Type::FreeRam),
446            Range { addr: 0, size: 0x1000, ty: Type::FreeRam },
447        );
448    }
449
450    // Overflowing `addr + size` saturates at `u64::MAX`.
451    #[test]
452    fn range_test_new_saturates() {
453        assert_eq!(
454            Range::new(u64::MAX - 1, 0x1000, Type::FreeRam),
455            Range::new(u64::MAX - 1, 1, Type::FreeRam),
456        );
457        assert_eq!(Range::new(u64::MAX, 1, Type::FreeRam), Range::new(u64::MAX, 0, Type::FreeRam));
458    }
459
460    // Unaligned start rounds down to alignment boundary.
461    #[test]
462    fn range_test_align_out_start_rounds_down() {
463        let r = Range::new(1, 0x1000 - 1, Type::FreeRam);
464        assert_eq!(r.align_out(0x1000), Range::new(0, 0x1000, Type::FreeRam));
465    }
466
467    // Unaligned end rounds up to alignment boundary.
468    #[test]
469    fn range_test_align_out_end_rounds_up() {
470        let r = Range::new(0, 0x1000 - 1, Type::FreeRam);
471        assert_eq!(r.align_out(0x1000), Range::new(0, 0x1000, Type::FreeRam));
472    }
473
474    // Sub-chunk range expands to a full aligned chunk.
475    #[test]
476    fn range_test_align_out_range_smaller_than_alignment() {
477        let r = Range::new(1, 1, Type::FreeRam);
478        assert_eq!(r.align_out(0x1000), Range::new(0, 0x1000, Type::FreeRam));
479    }
480
481    // Unaligned start and end round outward in both directions.
482    #[test]
483    fn range_test_align_out_both_ends_change() {
484        let r = Range::new(1, 0x1000, Type::FreeRam);
485        assert_eq!(r.align_out(0x1000), Range::new(0, 2 * 0x1000, Type::FreeRam));
486    }
487
488    // Upward end alignment saturates at `u64::MAX` on overflow.
489    #[test]
490    fn range_test_align_out_saturates() {
491        let r = Range::new(u64::MAX - 1, 1, Type::FreeRam);
492        assert_eq!(
493            r.align_out(0x1000),
494            Range::new(u64::MAX - 0x1000 + 1, 0x1000 - 1, Type::FreeRam),
495        );
496    }
497
498    // Empty ranges are still aligned outward to have a size of `alignment`.
499    #[test]
500    fn range_test_align_out_empty_range() {
501        let r = Range::new(0x3000 + 1, 0, Type::FreeRam);
502        assert_eq!(r.align_out(0x1000), Range::new(0x3000, 0x1000, Type::FreeRam));
503    }
504
505    // Adjacent ranges do not intersect.
506    //
507    // Input:
508    // [          )
509    //            [          )
510    // Output: false
511    #[test]
512    fn range_test_intersects_disjoint() {
513        let a = Range::new(0, 0x1000, Type::FreeRam);
514        let b = Range::new(0x1000, 0x1000, Type::FreeRam);
515        assert!(!a.intersects(&b));
516        assert!(!b.intersects(&a));
517    }
518
519    // A non-empty range intersects with itself.
520    //
521    // Input:
522    // [          )
523    // [          )
524    // Output: true
525    #[test]
526    fn range_test_intersects_identity() {
527        let r = Range::new(0, 0x1000, Type::FreeRam);
528        assert!(r.intersects(&r));
529    }
530
531    // A range strictly containing another intersects with it.
532    //
533    // Input:
534    // [                                    )
535    //            [            )
536    // Output: true
537    #[test]
538    fn range_test_intersects_eclipse() {
539        let outer = Range::new(0, 0x3000, Type::FreeRam);
540        let inner = Range::new(0x1000, 0x1000, Type::FreeRam);
541        assert!(outer.intersects(&inner));
542        assert!(inner.intersects(&outer));
543    }
544
545    // Ranges overlapping on the right side intersect.
546    //
547    // Input:
548    //        [                       )
549    //                 [                         )
550    // Output: true
551    #[test]
552    fn range_test_intersects_clip_right() {
553        let r = Range::new(0, 0x2000, Type::FreeRam);
554        let other = Range::new(0x1000, 0x2000, Type::FreeRam);
555        assert!(r.intersects(&other));
556    }
557
558    // Ranges overlapping on the left side intersect.
559    //
560    // Input:
561    //                   [                         )
562    //       [                       )
563    // Output: true
564    #[test]
565    fn range_test_intersects_clip_left() {
566        let r = Range::new(0x1000, 0x2000, Type::FreeRam);
567        let other = Range::new(0, 0x2000, Type::FreeRam);
568        assert!(r.intersects(&other));
569    }
570
571    // Overlapping ranges of different types still intersect.
572    //
573    // Input:
574    //              [          FreeRam         )
575    //                         [      Peripheral        )
576    // Output: true
577    #[test]
578    fn range_test_intersects_type_agnostic() {
579        let a = Range::new(0, 0x2000, Type::FreeRam);
580        let b = Range::new(0x1000, 0x2000, Type::Peripheral);
581        assert!(a.intersects(&b));
582    }
583
584    // Empty ranges will not intersect with other empty ranges at the same address.
585    //
586    // Input:
587    //              [)
588    //              [)
589    // Output: false
590    #[test]
591    fn range_test_intersects_empty_range_with_other_zero_sized_range() {
592        let a = Range::new(0x3000, 0, Type::FreeRam);
593        let b = Range::new(0x3000, 0, Type::Peripheral);
594        assert!(!a.intersects(&b));
595    }
596
597    // An empty range will not intersect with non-empty ranges at the same address.
598    //
599    // Input:
600    //              [)
601    //              [      Peripheral        )
602    // Output: false
603    #[test]
604    fn range_test_intersects_empty_range_at_same_start_address() {
605        let a = Range::new(0x1000, 0, Type::FreeRam);
606        let b = Range::new(0x1000, 0x2000, Type::Peripheral);
607        assert!(!a.intersects(&b));
608    }
609
610    // An empty range will intersect with an eclipsing range.
611    //
612    // Input:
613    //                      [)
614    //          [      Peripheral        )
615    // Output: true
616    #[test]
617    fn range_test_intersects_type_empty_range_eclipsed() {
618        let a = Range::new(0x3000, 0, Type::FreeRam);
619        let b = Range::new(0x1000, 0x10000, Type::Peripheral);
620        assert!(a.intersects(&b));
621    }
622
623    // Lower start address orders before higher start address regardless of type.
624    //
625    // a: [ FreeRam  )
626    // b:            [Peripheral)
627    //
628    // a < b
629    #[test]
630    fn range_test_partial_cmp_addr_less_than() {
631        let a = Range::new(0, 0x1000, Type::FreeRam);
632        let b = Range::new(0x1000, 0x1000, Type::Peripheral);
633        assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
634        assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
635    }
636
637    // Equal start addresses order by size regardless of type.
638    //
639    // a: [ FreeRam  )
640    // b: [     Peripheral      )
641    //
642    // a < b
643    #[test]
644    fn range_test_partial_cmp_smaller() {
645        let a = Range::new(0, 0x1000, Type::FreeRam);
646        let b = Range::new(0, 0x2000, Type::Peripheral);
647        assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
648        assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
649    }
650
651    // Identical bounds compare `Equal` if types match, or `None` if types differ.
652    //
653    // a: [   FreeRam   )
654    // b: [   FreeRam   )
655    // c: [  Peripheral )
656    //
657    // a == b
658    // a == c is undefined.
659    #[test]
660    fn range_test_partial_cmp_eq() {
661        let a = Range::new(0, 0x1000, Type::FreeRam);
662        let b = Range::new(0, 0x1000, Type::FreeRam);
663        let c = Range::new(0, 0x1000, Type::Peripheral);
664        assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
665        assert_eq!(a.partial_cmp(&c), None);
666    }
667
668    // [`Range::slice_from_zbi_mem_ranges`] zeroes `reserved`, saturates overflowing lengths, and
669    // casts in-place.
670    #[test]
671    fn range_test_from_zbi_ranges() {
672        fn check_ranges(ranges: &[Range]) {
673            assert_eq!(
674                ranges,
675                &[
676                    Range::new(0, 0x1000, Type::FreeRam),
677                    Range::new(0x1000, 0x1000, Type::Peripheral),
678                    Range::new(u64::MAX - 1, 1, Type::Reserved),
679                ]
680            );
681        }
682
683        let mut zbi_ranges = [
684            zbi::MemRange {
685                paddr: 0,
686                length: 0x1000,
687                r#type: zbi::MemType::Ram,
688                reserved: 0xdeadbeef,
689            },
690            zbi::MemRange {
691                paddr: 0x1000,
692                length: 0x1000,
693                r#type: zbi::MemType::Peripheral,
694                reserved: 0x12345678,
695            },
696            zbi::MemRange {
697                paddr: u64::MAX - 1,
698                length: 0x1000,
699                r#type: zbi::MemType::Reserved,
700                reserved: u32::MAX,
701            },
702        ];
703
704        check_ranges(Range::slice_from_zbi_mem_ranges(&mut zbi_ranges));
705    }
706
707    // Coalesces contiguous RAM ranges into `FreeRam` and drops non-ram ranges.
708    //
709    // Input:  [ Book ][ Zbi ][ Test ][Peri ]     [ Test ][Kern ]
710    // Output: [      FreeRam        ]            [   FreeRam   ]
711    #[test]
712    fn normalized_range_iterator_test_filter_map_ram() {
713        let mut actual =
714            testing::NormalizedRangeCheckingIterator::new(TEST_RANGES).filter_map_ram();
715        assert_eq!(actual.next(), Some(Range::new(0, 0x3000, Type::FreeRam)));
716        assert_eq!(actual.next(), Some(Range::new(0x5000, 0x2000, Type::FreeRam)));
717        assert_eq!(actual.next(), None);
718    }
719
720    // Mapping all types to `None` yields an empty iterator.
721    //
722    // Input:  [ Book ][ Zbi ][ Test ][Peri ]     [ Test ][Kern ]
723    // Output:
724    #[test]
725    fn normalized_range_iterator_test_filter_map_ranges_discard_all() {
726        let mut actual =
727            testing::NormalizedRangeCheckingIterator::new(TEST_RANGES).filter_map_ranges(|_| None);
728        assert_eq!(actual.next(), None);
729    }
730
731    // Discarding RAM ranges leaves only `Peripheral`.
732    //
733    // Input:  [ Book ][ Zbi ][ Test ][Peri ]     [ Test ][Kern ]
734    // Output:                        [Peri ]
735    #[test]
736    fn normalized_range_iterator_test_filter_map_ranges_discard_ram() {
737        let mut actual = testing::NormalizedRangeCheckingIterator::new(TEST_RANGES)
738            .filter_map_ranges(|r| if r.is_ram() { None } else { Some(r) });
739        assert_eq!(actual.next(), Some(Range::new(0x3000, 0x1000, Type::Peripheral)));
740        assert_eq!(actual.next(), None);
741    }
742
743    // Retains only `PoolBookkeeping` and `PoolTestPayload` ranges.
744    //
745    // Input:  [ Book ][ Zbi ][ Test ][Peri ]     [ Test ][Kern ]
746    // Output: [ Book ]       [ Test ]            [ Test ]
747    #[test]
748    fn normalized_range_iterator_test_filter_map_ranges_keep_pool_test_payloads_and_bookkeeping() {
749        let mut actual = testing::NormalizedRangeCheckingIterator::new(TEST_RANGES)
750            .filter_map_ranges(|r| {
751                if r == Type::PoolBookkeeping || r == Type::PoolTestPayload {
752                    Some(r)
753                } else {
754                    None
755                }
756            });
757        assert_eq!(actual.next(), Some(Range::new(0, 0x1000, Type::PoolBookkeeping)));
758        assert_eq!(actual.next(), Some(Range::new(0x2000, 0x1000, Type::PoolTestPayload)));
759        assert_eq!(actual.next(), Some(Range::new(0x5000, 0x1000, Type::PoolTestPayload)));
760        assert_eq!(actual.next(), None);
761    }
762}