Skip to main content

multiboot2/
memory_map.rs

1//! Module for [`MemoryMapTag`], [`EFIMemoryMapTag`] and [`BasicMemoryInfoTag`]
2//! and corresponding helper types.
3
4pub use uefi_raw::table::boot::MemoryAttribute as EFIMemoryAttribute;
5pub use uefi_raw::table::boot::MemoryDescriptor as EFIMemoryDesc;
6pub use uefi_raw::table::boot::MemoryType as EFIMemoryAreaType;
7
8use crate::tag::TagHeader;
9use crate::{TagType, TagTypeRaw};
10use core::fmt::{Debug, Formatter};
11use core::marker::PhantomData;
12use multiboot2_common::{MaybeDynSized, Tag};
13#[cfg(feature = "builder")]
14use {alloc::boxed::Box, core::slice, multiboot2_common::new_boxed};
15
16/// This tag provides an initial host memory map (legacy boot, not UEFI).
17///
18/// The map provided is guaranteed to list all standard RAM that should be
19/// available for normal use. This type however includes the regions occupied
20/// by kernel, mbi, segments and modules. Kernel must take care not to
21/// overwrite these regions.
22///
23/// This tag may not be provided by some bootloaders on EFI platforms if EFI
24/// boot services are enabled and available for the loaded image (The EFI boot
25/// services tag may exist in the Multiboot2 boot information structure).
26#[derive(ptr_meta::Pointee, Debug, PartialEq, Eq)]
27#[repr(C, align(8))]
28pub struct MemoryMapTag {
29    header: TagHeader,
30    entry_size: u32,
31    entry_version: u32,
32    areas: [MemoryArea],
33}
34
35impl MemoryMapTag {
36    /// Constructs a new tag.
37    #[cfg(feature = "builder")]
38    #[must_use]
39    pub fn new(areas: &[MemoryArea]) -> Box<Self> {
40        let header = TagHeader::new(Self::ID, 0);
41        let entry_size = (size_of::<MemoryArea>() as u32).to_ne_bytes();
42        let entry_version = 0_u32.to_ne_bytes();
43        let areas = {
44            let ptr = areas.as_ptr().cast::<u8>();
45            let len = size_of_val(areas);
46            // SAFETY: `areas` is a live slice; we only reinterpret its
47            // initialized bytes.
48            unsafe { slice::from_raw_parts(ptr, len) }
49        };
50        new_boxed(header, &[&entry_size, &entry_version, areas])
51    }
52
53    /// Returns the entry size.
54    #[must_use]
55    pub const fn entry_size(&self) -> u32 {
56        self.entry_size
57    }
58
59    /// Returns the entry version.
60    #[must_use]
61    pub const fn entry_version(&self) -> u32 {
62        self.entry_version
63    }
64
65    /// Return the slice of the provided [`MemoryArea`]s.
66    ///
67    /// Usually, this should already reflect the memory consumed by the
68    /// code running this.
69    #[must_use]
70    pub fn memory_areas(&self) -> &[MemoryArea] {
71        // If this ever fails, we need to model this differently in this crate.
72        assert_eq!(self.entry_size as usize, size_of::<MemoryArea>());
73        &self.areas
74    }
75}
76
77// SAFETY: The tag is repr(C) with the header as first field, any bit
78// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
79unsafe impl MaybeDynSized for MemoryMapTag {
80    type Header = TagHeader;
81
82    const BASE_SIZE: usize = size_of::<TagHeader>() + 2 * size_of::<u32>();
83
84    fn dst_len(header: &TagHeader) -> usize {
85        assert!(header.size as usize >= Self::BASE_SIZE);
86        let size = header.size as usize - Self::BASE_SIZE;
87        assert_eq!(size % size_of::<MemoryArea>(), 0);
88        size / size_of::<MemoryArea>()
89    }
90}
91
92impl Tag for MemoryMapTag {
93    type IDType = TagType;
94
95    const ID: TagType = TagType::Mmap;
96}
97
98/// A descriptor for an available or taken area of physical memory.
99#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
100#[repr(C)]
101pub struct MemoryArea {
102    base_addr: u64,
103    length: u64,
104    typ: MemoryAreaTypeRaw,
105    _reserved: u32,
106}
107
108impl MemoryArea {
109    /// Create a new MemoryArea.
110    pub fn new(base_addr: u64, length: u64, typ: impl Into<MemoryAreaType>) -> Self {
111        Self {
112            base_addr,
113            length,
114            typ: MemoryAreaTypeRaw::new(typ.into().val()),
115            _reserved: 0,
116        }
117    }
118
119    /// The start address of the memory region.
120    #[must_use]
121    pub const fn start_address(&self) -> u64 {
122        self.base_addr
123    }
124
125    /// The end address of the memory region.
126    #[must_use]
127    pub const fn end_address(&self) -> u64 {
128        self.base_addr + self.length
129    }
130
131    /// The size, in bytes, of the memory region.
132    #[must_use]
133    pub const fn size(&self) -> u64 {
134        self.length
135    }
136
137    /// The type of the memory region.
138    #[must_use]
139    pub const fn typ(&self) -> MemoryAreaType {
140        MemoryAreaType::from_val(self.typ.get())
141    }
142}
143
144impl Debug for MemoryArea {
145    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
146        f.debug_struct("MemoryArea")
147            .field("base_addr", &self.base_addr)
148            .field("length", &self.length)
149            .field("typ", &self.typ)
150            .finish()
151    }
152}
153
154multiboot2_common::raw_type! {
155    /// ABI compatible representation of the type of a memory area.
156    ///
157    /// This type matches the binary representation (`u32`).
158    pub struct MemoryAreaTypeRaw(u32);
159
160    /// The type of a memory area of the memory map.
161    ///
162    /// Types 1 to 5 are defined in the Multiboot2 spec and correspond to the
163    /// entry types of e820 memory maps; other values are mapped to
164    /// [`MemoryAreaType::Custom`].
165    ///
166    /// This is a higher level abstraction for [`MemoryAreaTypeRaw`] and not
167    /// binary compatible with the Multiboot2 spec.
168    pub enum MemoryAreaType {
169        /// Available memory free to be used by the OS.
170        Available = 1,
171
172        /// A reserved area that must not be used.
173        Reserved = 2,
174
175        /// Usable memory holding ACPI information.
176        AcpiAvailable = 3,
177
178        /// Reserved memory which needs to be preserved on hibernation.
179        /// Also called NVS in spec, which stands for "Non-Volatile Sleep/Storage",
180        /// which is part of ACPI specification.
181        ReservedHibernate = 4,
182
183        /// Memory which is occupied by defective RAM modules.
184        Defective = 5,
185    }
186}
187
188/// Basic memory info tag.
189///
190/// This tag includes "basic memory information". This means (legacy) lower and
191/// upper memory: In Real Mode (modeled after the 8086), only the first 1MB of
192/// memory is accessible. Typically, the region between 640KB and 1MB is not
193/// freely usable, because it is used for memory-mapped IO, for instance. The
194/// term "lower memory" refers to those first 640KB of memory that are freely
195/// usable for an application in Real Mode. "Upper memory" then refers to the
196/// next freely usable chunk of memory, starting at 1MB up to about 10MB, in
197/// practice. This is the memory an application running on a 286 (which had a
198/// 24-bit address bus) could use, historically.
199///
200/// Nowadays, much bigger chunks of continuous memory are available at higher
201/// addresses, but the Multiboot standard still references those two terms.
202#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
203#[repr(C)]
204pub struct BasicMemoryInfoTag {
205    header: TagHeader,
206    memory_lower: u32,
207    memory_upper: u32,
208}
209
210impl BasicMemoryInfoTag {
211    /// Constructs a new tag.
212    #[must_use]
213    pub fn new(memory_lower: u32, memory_upper: u32) -> Self {
214        Self {
215            header: TagHeader::new(Self::ID, size_of::<Self>().try_into().unwrap()),
216            memory_lower,
217            memory_upper,
218        }
219    }
220
221    #[must_use]
222    /// Returns the lower memory bound.
223    pub const fn memory_lower(&self) -> u32 {
224        self.memory_lower
225    }
226
227    #[must_use]
228    /// Returns the upper memory bound.
229    pub const fn memory_upper(&self) -> u32 {
230        self.memory_upper
231    }
232}
233
234// SAFETY: The tag is repr(C) with the header as first field, any bit
235// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
236unsafe impl MaybeDynSized for BasicMemoryInfoTag {
237    type Header = TagHeader;
238
239    const BASE_SIZE: usize = size_of::<Self>();
240}
241
242impl Tag for BasicMemoryInfoTag {
243    type IDType = TagType;
244
245    const ID: TagType = TagType::BasicMeminfo;
246}
247
248/// EFI memory map tag. The embedded [`EFIMemoryDesc`]s follows the EFI
249/// specification.
250#[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)]
251#[repr(C)]
252pub struct EFIMemoryMapTag {
253    header: TagHeader,
254    /// Most likely a little more than the size of a [`EFIMemoryDesc`].
255    /// This is always the reference, and `size_of` never.
256    /// See <https://github.com/tianocore/edk2/blob/7142e648416ff5d3eac6c6d607874805f5de0ca8/MdeModulePkg/Core/PiSmmCore/Page.c#L1059>.
257    desc_size: u32,
258    /// Version of the tag. The spec leaves it open to extend the memory
259    /// descriptor in the future. However, this never happened so far.
260    /// At the moment, only version "1" is supported.
261    desc_version: u32,
262    /// Contains the UEFI memory map.
263    ///
264    /// To follow the UEFI spec and to allow extendability for future UEFI
265    /// revisions, the length is a multiple of `desc_size` and not a multiple
266    /// of `size_of::<EfiMemoryDescriptor>()`.
267    ///
268    /// This tag is properly `align_of::<EFIMemoryDesc>` aligned, if the tag
269    /// itself is also 8 byte aligned, which every sane MBI guarantees.
270    memory_map: [u8],
271}
272
273impl EFIMemoryMapTag {
274    /// Create a new EFI memory map tag with the given memory descriptors.
275    #[cfg(feature = "builder")]
276    #[must_use]
277    pub fn new_from_descs(descs: &[EFIMemoryDesc]) -> Box<Self> {
278        let efi_mmap = {
279            let ptr = descs.as_ptr().cast::<u8>();
280            let len = size_of_val(descs);
281            // SAFETY: `descs` is a live slice; we only reinterpret its
282            // initialized bytes.
283            unsafe { slice::from_raw_parts(ptr, len) }
284        };
285
286        Self::new_from_map(
287            size_of::<EFIMemoryDesc>() as u32,
288            EFIMemoryDesc::VERSION,
289            efi_mmap,
290        )
291    }
292
293    /// Create a new EFI memory map tag from the given EFI memory map.
294    #[cfg(feature = "builder")]
295    #[must_use]
296    pub fn new_from_map(desc_size: u32, desc_version: u32, efi_mmap: &[u8]) -> Box<Self> {
297        let header = TagHeader::new(Self::ID, 0);
298        assert_ne!(desc_size, 0);
299        let desc_size = desc_size.to_ne_bytes();
300        let desc_version = desc_version.to_ne_bytes();
301        new_boxed(header, &[&desc_size, &desc_version, efi_mmap])
302    }
303
304    /// Returns an iterator over the provided memory areas.
305    ///
306    /// Usually, this should already reflect the memory consumed by the
307    /// code running this.
308    #[must_use]
309    pub fn memory_areas(&self) -> EFIMemoryAreaIter<'_> {
310        // If this ever fails, this needs to be refactored in a joint-effort
311        // with the uefi-rs project to have all corresponding typings.
312        assert_eq!(self.desc_version, EFIMemoryDesc::VERSION);
313        let desc_size = self.desc_size as usize;
314        assert_ne!(desc_size, 0, "EFI descriptor size must not be zero");
315        assert!(
316            desc_size >= size_of::<EFIMemoryDesc>(),
317            "EFI descriptor size must cover an EFI memory descriptor"
318        );
319        assert_eq!(
320            desc_size % align_of::<EFIMemoryDesc>(),
321            0,
322            "EFI descriptor size must preserve EFI memory descriptor alignment"
323        );
324        assert_eq!(
325            self.memory_map
326                .as_ptr()
327                .align_offset(align_of::<EFIMemoryDesc>()),
328            0
329        );
330
331        EFIMemoryAreaIter::new(self)
332    }
333}
334
335impl Debug for EFIMemoryMapTag {
336    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
337        f.debug_struct("EFIMemoryMapTag")
338            .field("typ", &self.header.typ)
339            .field("size", &self.header.size)
340            .field("desc_size", &self.desc_size)
341            .field("desc_version", &self.desc_version)
342            .field("buf", &self.memory_map.as_ptr())
343            .field("buf_len", &self.memory_map.len())
344            .field("entries", &self.memory_areas())
345            .finish()
346    }
347}
348
349// SAFETY: The tag is repr(C) with the header as first field, any bit
350// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
351unsafe impl MaybeDynSized for EFIMemoryMapTag {
352    type Header = TagHeader;
353
354    const BASE_SIZE: usize = size_of::<TagTypeRaw>() + 3 * size_of::<u32>();
355
356    fn dst_len(header: &TagHeader) -> usize {
357        assert!(header.size as usize >= Self::BASE_SIZE);
358        header.size as usize - Self::BASE_SIZE
359    }
360}
361
362impl Tag for EFIMemoryMapTag {
363    type IDType = TagType;
364
365    const ID: TagType = TagType::EfiMmap;
366}
367
368/// An iterator over the EFI memory areas emitting [`EFIMemoryDesc`] items.
369#[derive(Clone)]
370pub struct EFIMemoryAreaIter<'a> {
371    mmap_tag: &'a EFIMemoryMapTag,
372    i: usize,
373    entries: usize,
374    phantom: PhantomData<&'a EFIMemoryDesc>,
375}
376
377impl<'a> EFIMemoryAreaIter<'a> {
378    fn new(mmap_tag: &'a EFIMemoryMapTag) -> Self {
379        let desc_size = mmap_tag.desc_size as usize;
380        let mmap_len = mmap_tag.memory_map.len();
381        assert_eq!(
382            mmap_len % desc_size,
383            0,
384            "memory map length must be a multiple of `desc_size` by definition. The MBI seems to be corrupt."
385        );
386        Self {
387            mmap_tag,
388            i: 0,
389            entries: mmap_len / desc_size,
390            phantom: PhantomData,
391        }
392    }
393}
394
395impl<'a> Iterator for EFIMemoryAreaIter<'a> {
396    type Item = &'a EFIMemoryDesc;
397    fn next(&mut self) -> Option<&'a EFIMemoryDesc> {
398        if self.i >= self.entries {
399            return None;
400        }
401
402        let desc = {
403            let ptr = self
404                .mmap_tag
405                .memory_map
406                .as_ptr()
407                .wrapping_add(self.i * self.mmap_tag.desc_size as usize)
408                .cast::<EFIMemoryDesc>();
409            // SAFETY: The enclosing tag bounds the iterator, and `ptr`
410            // points into it.
411            unsafe { ptr.as_ref().unwrap() }
412        };
413
414        self.i += 1;
415
416        Some(desc)
417    }
418}
419
420impl ExactSizeIterator for EFIMemoryAreaIter<'_> {
421    fn len(&self) -> usize {
422        self.entries
423    }
424}
425
426impl Debug for EFIMemoryAreaIter<'_> {
427    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
428        let mut debug = f.debug_list();
429        let iter = self.clone();
430        for elem in iter {
431            debug.entry(elem);
432        }
433        debug.finish()
434    }
435}
436
437#[cfg(all(test, feature = "builder"))]
438mod tests {
439    use super::*;
440
441    #[test]
442    fn test_create_old_mmap() {
443        let _mmap = MemoryMapTag::new(&[]);
444        let mmap = MemoryMapTag::new(&[
445            MemoryArea::new(0x1000, 0x2000, MemoryAreaType::Available),
446            MemoryArea::new(0x2000, 0x3000, MemoryAreaType::Available),
447        ]);
448        dbg!(mmap);
449    }
450
451    #[test]
452    fn efi_construct_and_parse() {
453        let descs = [
454            EFIMemoryDesc {
455                ty: EFIMemoryAreaType::CONVENTIONAL,
456                padding: 0,
457                phys_start: 0x1000,
458                virt_start: 0x1000,
459                page_count: 1,
460                att: Default::default(),
461            },
462            EFIMemoryDesc {
463                ty: EFIMemoryAreaType::LOADER_DATA,
464                padding: 0,
465                phys_start: 0x2000,
466                virt_start: 0x2000,
467                page_count: 3,
468                att: Default::default(),
469            },
470        ];
471        let efi_mmap_tag = EFIMemoryMapTag::new_from_descs(&descs);
472
473        let mut iter = efi_mmap_tag.memory_areas();
474
475        assert_eq!(iter.next(), Some(&descs[0]));
476        assert_eq!(iter.next(), Some(&descs[1]));
477
478        assert_eq!(iter.next(), None);
479    }
480
481    #[test]
482    #[should_panic(expected = "EFI descriptor size must cover an EFI memory descriptor")]
483    fn efi_rejects_too_small_desc_size() {
484        let map = [0; size_of::<EFIMemoryDesc>()];
485        let tag = EFIMemoryMapTag::new_from_map(1, EFIMemoryDesc::VERSION, &map);
486
487        let _ = tag.memory_areas();
488    }
489
490    #[test]
491    #[should_panic(expected = "EFI descriptor size must preserve EFI memory descriptor alignment")]
492    fn efi_rejects_misaligned_desc_size() {
493        let desc_size = size_of::<EFIMemoryDesc>() + 1;
494        let map = alloc::vec![0; desc_size];
495        let tag = EFIMemoryMapTag::new_from_map(desc_size as u32, EFIMemoryDesc::VERSION, &map);
496
497        let _ = tag.memory_areas();
498    }
499
500    /// Tests the EFI memory map parsing using a real world efi memory map.
501    /// This is taken from the uefi-rs repository. See
502    /// <https://github.com/rust-osdev/uefi-rs/pull/1175> for more info.
503    #[test]
504    fn efi_test_real_data() {
505        const DESC_SIZE: u32 = 48;
506        const DESC_VERSION: u32 = 1;
507        /// Sample with 10 entries of a real UEFI memory map extracted from our
508        /// UEFI test runner.
509        const MMAP_RAW: [u64; 60] = [
510            3, 0, 0, 1, 15, 0, 7, 4096, 0, 134, 15, 0, 4, 552960, 0, 1, 15, 0, 7, 557056, 0, 24,
511            15, 0, 7, 1048576, 0, 1792, 15, 0, 10, 8388608, 0, 8, 15, 0, 7, 8421376, 0, 3, 15, 0,
512            10, 8433664, 0, 1, 15, 0, 7, 8437760, 0, 4, 15, 0, 10, 8454144, 0, 240, 15, 0,
513        ];
514        let buf = MMAP_RAW;
515        // SAFETY: `buf` is a plain array; we only reinterpret its
516        // initialized bytes.
517        let buf = unsafe {
518            slice::from_raw_parts(buf.as_ptr().cast::<u8>(), buf.len() * size_of::<u64>())
519        };
520        let tag = EFIMemoryMapTag::new_from_map(DESC_SIZE, DESC_VERSION, buf);
521        let entries = tag.memory_areas().copied().collect::<alloc::vec::Vec<_>>();
522        let expected = [
523            EFIMemoryDesc {
524                ty: EFIMemoryAreaType::BOOT_SERVICES_CODE,
525                padding: 0,
526                phys_start: 0x0,
527                virt_start: 0x0,
528                page_count: 0x1,
529                att: EFIMemoryAttribute::UNCACHEABLE
530                    | EFIMemoryAttribute::WRITE_COMBINE
531                    | EFIMemoryAttribute::WRITE_THROUGH
532                    | EFIMemoryAttribute::WRITE_BACK,
533            },
534            EFIMemoryDesc {
535                ty: EFIMemoryAreaType::CONVENTIONAL,
536                padding: 0,
537                phys_start: 0x1000,
538                virt_start: 0x0,
539                page_count: 0x86,
540                att: EFIMemoryAttribute::UNCACHEABLE
541                    | EFIMemoryAttribute::WRITE_COMBINE
542                    | EFIMemoryAttribute::WRITE_THROUGH
543                    | EFIMemoryAttribute::WRITE_BACK,
544            },
545            EFIMemoryDesc {
546                ty: EFIMemoryAreaType::BOOT_SERVICES_DATA,
547                padding: 0,
548                phys_start: 0x87000,
549                virt_start: 0x0,
550                page_count: 0x1,
551                att: EFIMemoryAttribute::UNCACHEABLE
552                    | EFIMemoryAttribute::WRITE_COMBINE
553                    | EFIMemoryAttribute::WRITE_THROUGH
554                    | EFIMemoryAttribute::WRITE_BACK,
555            },
556            EFIMemoryDesc {
557                ty: EFIMemoryAreaType::CONVENTIONAL,
558                padding: 0,
559                phys_start: 0x88000,
560                virt_start: 0x0,
561                page_count: 0x18,
562                att: EFIMemoryAttribute::UNCACHEABLE
563                    | EFIMemoryAttribute::WRITE_COMBINE
564                    | EFIMemoryAttribute::WRITE_THROUGH
565                    | EFIMemoryAttribute::WRITE_BACK,
566            },
567            EFIMemoryDesc {
568                ty: EFIMemoryAreaType::CONVENTIONAL,
569                padding: 0,
570                phys_start: 0x100000,
571                virt_start: 0x0,
572                page_count: 0x700,
573                att: EFIMemoryAttribute::UNCACHEABLE
574                    | EFIMemoryAttribute::WRITE_COMBINE
575                    | EFIMemoryAttribute::WRITE_THROUGH
576                    | EFIMemoryAttribute::WRITE_BACK,
577            },
578            EFIMemoryDesc {
579                ty: EFIMemoryAreaType::ACPI_NON_VOLATILE,
580                padding: 0,
581                phys_start: 0x800000,
582                virt_start: 0x0,
583                page_count: 0x8,
584                att: EFIMemoryAttribute::UNCACHEABLE
585                    | EFIMemoryAttribute::WRITE_COMBINE
586                    | EFIMemoryAttribute::WRITE_THROUGH
587                    | EFIMemoryAttribute::WRITE_BACK,
588            },
589            EFIMemoryDesc {
590                ty: EFIMemoryAreaType::CONVENTIONAL,
591                padding: 0,
592                phys_start: 0x808000,
593                virt_start: 0x0,
594                page_count: 0x3,
595                att: EFIMemoryAttribute::UNCACHEABLE
596                    | EFIMemoryAttribute::WRITE_COMBINE
597                    | EFIMemoryAttribute::WRITE_THROUGH
598                    | EFIMemoryAttribute::WRITE_BACK,
599            },
600            EFIMemoryDesc {
601                ty: EFIMemoryAreaType::ACPI_NON_VOLATILE,
602                padding: 0,
603                phys_start: 0x80b000,
604                virt_start: 0x0,
605                page_count: 0x1,
606                att: EFIMemoryAttribute::UNCACHEABLE
607                    | EFIMemoryAttribute::WRITE_COMBINE
608                    | EFIMemoryAttribute::WRITE_THROUGH
609                    | EFIMemoryAttribute::WRITE_BACK,
610            },
611            EFIMemoryDesc {
612                ty: EFIMemoryAreaType::CONVENTIONAL,
613                padding: 0,
614                phys_start: 0x80c000,
615                virt_start: 0x0,
616                page_count: 0x4,
617                att: EFIMemoryAttribute::UNCACHEABLE
618                    | EFIMemoryAttribute::WRITE_COMBINE
619                    | EFIMemoryAttribute::WRITE_THROUGH
620                    | EFIMemoryAttribute::WRITE_BACK,
621            },
622            EFIMemoryDesc {
623                ty: EFIMemoryAreaType::ACPI_NON_VOLATILE,
624                padding: 0,
625                phys_start: 0x810000,
626                virt_start: 0x0,
627                page_count: 0xf0,
628                att: EFIMemoryAttribute::UNCACHEABLE
629                    | EFIMemoryAttribute::WRITE_COMBINE
630                    | EFIMemoryAttribute::WRITE_THROUGH
631                    | EFIMemoryAttribute::WRITE_BACK,
632            },
633        ];
634        assert_eq!(entries.as_slice(), &expected);
635    }
636}