host_fdt_parser/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Common parsing code for parsing the device tree provided by the host.
5//! Note that is is not a generic device tree parser, but parses the device tree
6//! for devices and concepts specific to underhill.
7//!
8//! Notably, we search for IGVM specific extensions to nodes, defined here:
9//! [`igvm_defs::dt`].
10
11#![no_std]
12#![forbid(unsafe_code)]
13
14use arrayvec::ArrayString;
15use arrayvec::ArrayVec;
16use core::fmt::Display;
17use core::fmt::Write;
18use core::mem::size_of;
19use hvdef::HV_PAGE_SIZE;
20use igvm_defs::MemoryMapEntryType;
21#[cfg(feature = "inspect")]
22use inspect::Inspect;
23use memory_range::MemoryRange;
24
25/// Information about VMBUS.
26#[derive(Clone, Debug, PartialEq, Eq)]
27#[cfg_attr(feature = "inspect", derive(Inspect))]
28pub struct VmbusInfo {
29    /// Parsed sorted mmio ranges from the device tree.
30    #[cfg_attr(feature = "inspect", inspect(with = "inspect_helpers::mmio_internal"))]
31    pub mmio: ArrayVec<MemoryRange, 2>,
32    /// Connection ID for the vmbus root device.
33    #[cfg_attr(feature = "inspect", inspect(hex))]
34    pub connection_id: u32,
35}
36
37/// Information about the GIC.
38#[derive(Clone, Debug, PartialEq, Eq)]
39#[cfg_attr(feature = "inspect", derive(Inspect))]
40pub struct GicInfo {
41    /// GIC distributor base
42    #[cfg_attr(feature = "inspect", inspect(hex))]
43    pub gic_distributor_base: u64,
44    /// GIC distributor size
45    #[cfg_attr(feature = "inspect", inspect(hex))]
46    pub gic_distributor_size: u64,
47    /// GIC redistributors base
48    #[cfg_attr(feature = "inspect", inspect(hex))]
49    pub gic_redistributors_base: u64,
50    /// GIC redistributor block size
51    #[cfg_attr(feature = "inspect", inspect(hex))]
52    pub gic_redistributors_size: u64,
53    /// GIC redistributor size
54    #[cfg_attr(feature = "inspect", inspect(hex))]
55    pub gic_redistributor_stride: u64,
56}
57
58/// Errors returned by parsing.
59#[derive(Debug)]
60pub struct Error<'a>(ErrorKind<'a>);
61
62impl Display for Error<'_> {
63    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
64        f.write_fmt(format_args!("Parsing failed due to: {}", self.0))
65    }
66}
67
68impl core::error::Error for Error<'_> {}
69
70#[derive(Debug)]
71enum ErrorKind<'a> {
72    Dt(fdt::parser::Error<'a>),
73    Node {
74        parent_name: &'a str,
75        error: fdt::parser::Error<'a>,
76    },
77    PropMissing {
78        node_name: &'a str,
79        prop_name: &'static str,
80    },
81    Prop(fdt::parser::Error<'a>),
82    TooManyCpus,
83    MemoryRegUnaligned {
84        node_name: &'a str,
85        base: u64,
86        len: u64,
87    },
88    MemoryRegOverlap {
89        lower: MemoryEntry,
90        upper: MemoryEntry,
91    },
92    TooManyMemoryEntries,
93    PropInvalidU32 {
94        node_name: &'a str,
95        prop_name: &'a str,
96        expected: u32,
97        actual: u32,
98    },
99    PropInvalidStr {
100        node_name: &'a str,
101        prop_name: &'a str,
102        expected: &'a str,
103        actual: &'a str,
104    },
105    UnexpectedVmbusVtl {
106        node_name: &'a str,
107        vtl: u32,
108    },
109    MultipleVmbusNode {
110        node_name: &'a str,
111    },
112    VmbusRangesChildParent {
113        node_name: &'a str,
114        child_base: u64,
115        parent_base: u64,
116    },
117    VmbusRangesNotAligned {
118        node_name: &'a str,
119        base: u64,
120        len: u64,
121    },
122    TooManyVmbusMmioRanges {
123        node_name: &'a str,
124        ranges: usize,
125    },
126    VmbusMmioOverlapsRam {
127        mmio: MemoryRange,
128        ram: MemoryEntry,
129    },
130    VmbusMmioOverlapsVmbusMmio {
131        mmio_a: MemoryRange,
132        mmio_b: MemoryRange,
133    },
134    CmdlineSize,
135    UnexpectedMemoryAllocationMode {
136        mode: &'a str,
137    },
138}
139
140impl Display for ErrorKind<'_> {
141    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
142        match self {
143            ErrorKind::Dt(e) => f.write_fmt(format_args!("invalid device tree: {}", e)),
144            ErrorKind::Node { parent_name, error } => {
145                f.write_fmt(format_args!("invalid device tree node with parent {parent_name}: {error}"))
146            }
147            ErrorKind::PropMissing {
148                node_name,
149                prop_name,
150            } => f.write_fmt(format_args!(
151                "{node_name} did not have the following required property {prop_name}",
152            )),
153            ErrorKind::Prop(e) => f.write_fmt(format_args!("reading node property failed: {e}")),
154            ErrorKind::TooManyCpus => {
155                f.write_str("device tree contained more enabled CPUs than can be parsed")
156            }
157            ErrorKind::MemoryRegUnaligned {
158                node_name,
159                base,
160                len,
161            } => f.write_fmt(format_args!(
162                "memory node {node_name} contains 4K unaligned base {base} or len {len}"
163            )),
164            ErrorKind::MemoryRegOverlap { lower, upper,  } => {
165                f.write_fmt(format_args!("ram at {}..{} of type {:?} overlaps ram at {}..{} of type {:?}", lower.range.start(), lower.range.end(), lower.mem_type, upper.range.start(), upper.range.end(), upper.mem_type))
166            }
167            ErrorKind::TooManyMemoryEntries => {
168                f.write_str("device tree contained more memory ranges than can be parsed")
169            }
170            ErrorKind::PropInvalidU32 { node_name, prop_name, expected, actual } => f.write_fmt(format_args!("{node_name} had an invalid u32 value for {prop_name}: expected {expected}, actual {actual}")),
171            ErrorKind::PropInvalidStr { node_name, prop_name, expected, actual } => f.write_fmt(format_args!("{node_name} had an invalid str value for {prop_name}: expected {expected}, actual {actual}")),
172            ErrorKind::UnexpectedVmbusVtl { node_name, vtl } => f.write_fmt(format_args!("{node_name} has an unexpected vtl {vtl}")),
173            ErrorKind::MultipleVmbusNode { node_name } => f.write_fmt(format_args!("{node_name} specifies a duplicate vmbus node")),
174            ErrorKind::VmbusRangesChildParent { node_name, child_base, parent_base } => f.write_fmt(format_args!("vmbus {node_name} ranges child base {child_base} does not match parent {parent_base}")),
175            ErrorKind::VmbusRangesNotAligned { node_name, base, len } => f.write_fmt(format_args!("vmbus {node_name} base {base} or len {len} not aligned to 4K")),
176            ErrorKind::TooManyVmbusMmioRanges { node_name, ranges } => f.write_fmt(format_args!("vmbus {node_name} has more than 2 mmio ranges {ranges}")),
177            ErrorKind::VmbusMmioOverlapsRam { mmio, ram } => {
178                f.write_fmt(format_args!("vmbus mmio at {}..{} overlaps ram at {}..{}", mmio.start(), mmio.end(), ram.range.start(), ram.range.end()))
179            }
180            ErrorKind::VmbusMmioOverlapsVmbusMmio { mmio_a, mmio_b } => {
181                f.write_fmt(format_args!("vmbus mmio at {}..{} overlaps vmbus mmio at {}..{}", mmio_a.start(), mmio_a.end(), mmio_b.start(), mmio_b.end()))
182            }
183            ErrorKind::CmdlineSize => f.write_str("commandline too small to parse /chosen bootargs"),
184            ErrorKind::UnexpectedMemoryAllocationMode { mode } => f.write_fmt(format_args!("unexpected memory allocation mode: {}", mode)),
185        }
186    }
187}
188
189const COM3_REG_BASE: u64 = 0x3E8;
190
191/// Struct containing parsed device tree information.
192#[derive(Debug, PartialEq, Eq)]
193#[cfg_attr(feature = "inspect", derive(Inspect))]
194pub struct ParsedDeviceTree<
195    const MAX_MEMORY_ENTRIES: usize,
196    const MAX_CPU_ENTRIES: usize,
197    const MAX_COMMAND_LINE_SIZE: usize,
198    const MAX_ENTROPY_SIZE: usize,
199> {
200    /// Total size of the parsed device tree, in bytes.
201    pub device_tree_size: usize,
202    /// Parsed sorted memory ranges from the device tree.
203    #[cfg_attr(
204        feature = "inspect",
205        inspect(with = "inspect_helpers::memory_internal")
206    )]
207    pub memory: ArrayVec<MemoryEntry, MAX_MEMORY_ENTRIES>,
208    /// Boot cpu physical id. On X64, this is the APIC id of the BSP.
209    #[cfg_attr(feature = "inspect", inspect(hex))]
210    pub boot_cpuid_phys: u32,
211    /// Information for enabled cpus.
212    #[cfg_attr(feature = "inspect", inspect(iter_by_index))]
213    pub cpus: ArrayVec<CpuEntry, MAX_CPU_ENTRIES>,
214    /// VMBUS info for VTL0.
215    pub vmbus_vtl0: Option<VmbusInfo>,
216    /// VMBUS info for VTL2.
217    pub vmbus_vtl2: Option<VmbusInfo>,
218    /// Command line contained in the `/chosen` node.
219    /// FUTURE: return more information from the chosen node.
220    #[cfg_attr(feature = "inspect", inspect(display))]
221    pub command_line: ArrayString<MAX_COMMAND_LINE_SIZE>,
222    /// Is a com3 device present
223    pub com3_serial: bool,
224    /// GIC information
225    pub gic: Option<GicInfo>,
226    /// The vtl2 memory allocation mode OpenHCL should use for memory.
227    pub memory_allocation_mode: MemoryAllocationMode,
228    /// Entropy from the host to be used by the OpenHCL kernel
229    #[cfg_attr(feature = "inspect", inspect(with = "Option::is_some"))]
230    pub entropy: Option<ArrayVec<u8, MAX_ENTROPY_SIZE>>,
231    /// The number of pages the host has provided as a hint for device dma.
232    ///
233    /// This is used to allocate a persistent VTL2 pool on non-isolated guests,
234    /// to allow devices to stay alive during a servicing operation.
235    pub device_dma_page_count: Option<u64>,
236    /// Indicates that Host does support NVMe keep-alive.
237    pub nvme_keepalive: bool,
238    /// The physical address of the VTL0 alias mapping, if one is configured.
239    pub vtl0_alias_map: Option<u64>,
240}
241
242/// The memory allocation mode provided by the host. This determines how OpenHCL
243/// will allocate memory for itself from the partition memory map.
244#[derive(Debug, Clone, Copy, PartialEq, Eq)]
245#[cfg_attr(feature = "inspect", derive(Inspect))]
246#[cfg_attr(feature = "inspect", inspect(external_tag))]
247pub enum MemoryAllocationMode {
248    /// Use the host provided memory topology, and use VTL2_PROTECTABLE entries
249    /// as VTL2 ram. This is the default if no
250    /// `openhcl/memory-allocation-property` mode is provided by the host.
251    Host,
252    /// Allow VTL2 to select its own ranges from the address space to use for
253    /// memory, with a size provided by the host.
254    Vtl2 {
255        /// The number of bytes VTL2 should allocate for memory for itself.
256        /// Encoded as `openhcl/memory-size` in device tree.
257        memory_size: Option<u64>,
258        /// The number of bytes VTL2 should allocate for mmio for itself.
259        /// Encoded as `openhcl/mmio-size` in device tree.
260        mmio_size: Option<u64>,
261    },
262}
263
264/// Struct containing parsed memory information.
265#[derive(Copy, Clone, Debug, PartialEq, Eq)]
266#[cfg_attr(feature = "inspect", derive(Inspect))]
267pub struct MemoryEntry {
268    /// The range of addresses covered by this entry.
269    pub range: MemoryRange,
270    /// The type of memory of this entry.
271    #[cfg_attr(
272        feature = "inspect",
273        inspect(with = "inspect_helpers::inspect_memory_map_entry_type")
274    )]
275    pub mem_type: MemoryMapEntryType,
276    /// The numa node id of this entry.
277    pub vnode: u32,
278}
279
280/// Struct containing parsed CPU information.
281#[derive(Copy, Clone, Debug, PartialEq, Eq)]
282#[cfg_attr(feature = "inspect", derive(Inspect))]
283pub struct CpuEntry {
284    /// Architecture specific "reg" value for this CPU.
285    /// For x64, this is the APIC ID.
286    /// For ARM v8 64-bit, this should match the MPIDR_EL1 register affinity bits.
287    #[cfg_attr(feature = "inspect", inspect(hex))]
288    pub reg: u64,
289    /// Numa node id for this CPU.
290    pub vnode: u32,
291}
292
293impl<
294    'a,
295    'b,
296    const MAX_MEMORY_ENTRIES: usize,
297    const MAX_CPU_ENTRIES: usize,
298    const MAX_COMMAND_LINE_SIZE: usize,
299    const MAX_ENTROPY_SIZE: usize,
300> ParsedDeviceTree<MAX_MEMORY_ENTRIES, MAX_CPU_ENTRIES, MAX_COMMAND_LINE_SIZE, MAX_ENTROPY_SIZE>
301{
302    /// Create an empty parsed device tree structure. This is used to construct
303    /// a valid instance to pass into [`Self::parse`].
304    pub const fn new() -> Self {
305        Self {
306            device_tree_size: 0,
307            memory: ArrayVec::new_const(),
308            boot_cpuid_phys: 0,
309            cpus: ArrayVec::new_const(),
310            vmbus_vtl0: None,
311            vmbus_vtl2: None,
312            command_line: ArrayString::new_const(),
313            com3_serial: false,
314            gic: None,
315            memory_allocation_mode: MemoryAllocationMode::Host,
316            entropy: None,
317            device_dma_page_count: None,
318            nvme_keepalive: false,
319            vtl0_alias_map: None,
320        }
321    }
322
323    /// The number of enabled cpus.
324    pub fn cpu_count(&self) -> usize {
325        self.cpus.len()
326    }
327
328    /// Parse the given device tree.
329    pub fn parse(dt: &'a [u8], storage: &'b mut Self) -> Result<&'b Self, Error<'a>> {
330        Self::parse_inner(dt, storage).map_err(Error)
331    }
332
333    fn parse_inner(dt: &'a [u8], storage: &'b mut Self) -> Result<&'b Self, ErrorKind<'a>> {
334        let parser = fdt::parser::Parser::new(dt).map_err(ErrorKind::Dt)?;
335        let root = match parser.root() {
336            Ok(v) => v,
337            Err(e) => {
338                return Err(ErrorKind::Node {
339                    parent_name: "",
340                    error: e,
341                });
342            }
343        };
344
345        // Insert a memory entry into sorted parsed memory entries.
346        //
347        // TODO: This could be replaced with appending at the end with sort call
348        // after all entries are parsed once sort is stabilized in core.
349        let insert_memory_entry = |memory: &mut ArrayVec<MemoryEntry, MAX_MEMORY_ENTRIES>,
350                                   entry: MemoryEntry|
351         -> Result<(), ErrorKind<'a>> {
352            let insert_index = match memory.binary_search_by_key(&entry.range, |k| k.range) {
353                Ok(index) => {
354                    return Err(ErrorKind::MemoryRegOverlap {
355                        lower: memory[index],
356                        upper: entry,
357                    });
358                }
359                Err(index) => index,
360            };
361
362            memory
363                .try_insert(insert_index, entry)
364                .map_err(|_| ErrorKind::TooManyMemoryEntries)
365        };
366
367        for child in root.children() {
368            let child = child.map_err(|error| ErrorKind::Node {
369                parent_name: root.name,
370                error,
371            })?;
372
373            match child.name {
374                "cpus" => {
375                    let address_cells = child
376                        .find_property("#address-cells")
377                        .map_err(ErrorKind::Prop)?
378                        .ok_or(ErrorKind::PropMissing {
379                            node_name: child.name,
380                            prop_name: "#address-cells",
381                        })?
382                        .read_u32(0)
383                        .map_err(ErrorKind::Prop)?;
384
385                    // On ARM v8 64-bit systems, up to 2 address-cells values
386                    // can be provided.
387                    if address_cells > 2 {
388                        return Err(ErrorKind::PropInvalidU32 {
389                            node_name: child.name,
390                            prop_name: "#address-cells",
391                            expected: 2,
392                            actual: address_cells,
393                        });
394                    }
395
396                    for cpu in child.children() {
397                        let cpu = cpu.map_err(|error| ErrorKind::Node {
398                            parent_name: child.name,
399                            error,
400                        })?;
401
402                        if cpu
403                            .find_property("status")
404                            .map_err(ErrorKind::Prop)?
405                            .ok_or(ErrorKind::PropMissing {
406                                node_name: cpu.name,
407                                prop_name: "status",
408                            })?
409                            .read_str()
410                            .map_err(ErrorKind::Prop)?
411                            != "okay"
412                        {
413                            continue;
414                        }
415
416                        // NOTE: For x86, Underhill will need to query the hypervisor for
417                        // the vp_index to apic_id mapping. There's no
418                        // correlation in the device tree about this at all.
419                        let reg_property = cpu
420                            .find_property("reg")
421                            .map_err(ErrorKind::Prop)?
422                            .ok_or(ErrorKind::PropMissing {
423                                node_name: cpu.name,
424                                prop_name: "reg",
425                            })?;
426
427                        let reg = if address_cells == 1 {
428                            reg_property.read_u32(0).map_err(ErrorKind::Prop)? as u64
429                        } else {
430                            reg_property.read_u64(0).map_err(ErrorKind::Prop)?
431                        };
432
433                        let vnode = cpu
434                            .find_property("numa-node-id")
435                            .map_err(ErrorKind::Prop)?
436                            .ok_or(ErrorKind::PropMissing {
437                                node_name: cpu.name,
438                                prop_name: "numa-node-id",
439                            })?
440                            .read_u32(0)
441                            .map_err(ErrorKind::Prop)?;
442
443                        storage
444                            .cpus
445                            .try_push(CpuEntry { reg, vnode })
446                            .map_err(|_| ErrorKind::TooManyCpus)?;
447                    }
448                }
449                "openhcl" => {
450                    let memory_allocation_mode = child
451                        .find_property("memory-allocation-mode")
452                        .map_err(ErrorKind::Prop)?
453                        .ok_or(ErrorKind::PropMissing {
454                            node_name: child.name,
455                            prop_name: "memory-allocation-mode",
456                        })?;
457
458                    match memory_allocation_mode.read_str().map_err(ErrorKind::Prop)? {
459                        "host" => {
460                            storage.memory_allocation_mode = MemoryAllocationMode::Host;
461                        }
462                        "vtl2" => {
463                            let memory_size = child
464                                .find_property("memory-size")
465                                .map_err(ErrorKind::Prop)?
466                                .map(|p| p.read_u64(0))
467                                .transpose()
468                                .map_err(ErrorKind::Prop)?;
469
470                            let mmio_size = child
471                                .find_property("mmio-size")
472                                .map_err(ErrorKind::Prop)?
473                                .map(|p| p.read_u64(0))
474                                .transpose()
475                                .map_err(ErrorKind::Prop)?;
476
477                            storage.memory_allocation_mode = MemoryAllocationMode::Vtl2 {
478                                memory_size,
479                                mmio_size,
480                            };
481                        }
482                        mode => {
483                            return Err(ErrorKind::UnexpectedMemoryAllocationMode { mode });
484                        }
485                    }
486
487                    storage.vtl0_alias_map = child
488                        .find_property("vtl0-alias-map")
489                        .map_err(ErrorKind::Prop)?
490                        .map(|p| p.read_u64(0))
491                        .transpose()
492                        .map_err(ErrorKind::Prop)?;
493
494                    for openhcl_child in child.children() {
495                        let openhcl_child = openhcl_child.map_err(|error| ErrorKind::Node {
496                            parent_name: root.name,
497                            error,
498                        })?;
499
500                        match openhcl_child.name {
501                            "entropy" => {
502                                let host_entropy = openhcl_child
503                                    .find_property("reg")
504                                    .map_err(ErrorKind::Prop)?
505                                    .ok_or(ErrorKind::PropMissing {
506                                        node_name: openhcl_child.name,
507                                        prop_name: "reg",
508                                    })?
509                                    .data;
510
511                                if host_entropy.len() > MAX_ENTROPY_SIZE {
512                                    #[cfg(feature = "tracing")]
513                                    tracing::warn!(
514                                        entropy_len = host_entropy.len(),
515                                        "Truncating host-provided entropy",
516                                    );
517                                }
518                                let use_entropy_bytes =
519                                    core::cmp::min(host_entropy.len(), MAX_ENTROPY_SIZE);
520                                let entropy =
521                                    ArrayVec::try_from(&host_entropy[..use_entropy_bytes]).unwrap();
522
523                                storage.entropy = Some(entropy);
524                            }
525                            // These parameters may not be present so it is not an error if they are missing.
526                            "keep-alive" => {
527                                storage.nvme_keepalive = openhcl_child
528                                    .find_property("device-types")
529                                    .ok()
530                                    .flatten()
531                                    .and_then(|p| p.read_str().ok())
532                                    == Some("nvme");
533                            }
534                            "device-dma" => {
535                                // DMA reserved page count hint.
536                                storage.device_dma_page_count = openhcl_child
537                                    .find_property("total-pages")
538                                    .ok()
539                                    .flatten()
540                                    .and_then(|p| p.read_u64(0).ok());
541                            }
542                            _ => {
543                                #[cfg(feature = "tracing")]
544                                tracing::warn!(?openhcl_child.name, "Unrecognized OpenHCL child node");
545                            }
546                        }
547                    }
548                }
549
550                _ if child.name.starts_with("memory@") => {
551                    let igvm_type = if let Some(igvm_type) = child
552                        .find_property(igvm_defs::dt::IGVM_DT_IGVM_TYPE_PROPERTY)
553                        .map_err(ErrorKind::Prop)?
554                    {
555                        let typ = igvm_type.read_u32(0).map_err(ErrorKind::Prop)?;
556                        MemoryMapEntryType(typ as u16)
557                    } else {
558                        MemoryMapEntryType::MEMORY
559                    };
560
561                    let reg = child.find_property("reg").map_err(ErrorKind::Prop)?.ok_or(
562                        ErrorKind::PropMissing {
563                            node_name: child.name,
564                            prop_name: "reg",
565                        },
566                    )?;
567
568                    let vnode = child
569                        .find_property("numa-node-id")
570                        .map_err(ErrorKind::Prop)?
571                        .ok_or(ErrorKind::PropMissing {
572                            node_name: child.name,
573                            prop_name: "numa-node-id",
574                        })?
575                        .read_u32(0)
576                        .map_err(ErrorKind::Prop)?;
577
578                    let len = reg.data.len();
579                    let reg_tuple_size = size_of::<u64>() * 2;
580                    let number_of_ranges = len / reg_tuple_size;
581
582                    for i in 0..number_of_ranges {
583                        let base = reg.read_u64(i * 2).map_err(ErrorKind::Prop)?;
584                        let len = reg.read_u64(i * 2 + 1).map_err(ErrorKind::Prop)?;
585
586                        if base % HV_PAGE_SIZE != 0 || len % HV_PAGE_SIZE != 0 {
587                            return Err(ErrorKind::MemoryRegUnaligned {
588                                node_name: child.name,
589                                base,
590                                len,
591                            });
592                        }
593
594                        insert_memory_entry(
595                            &mut storage.memory,
596                            MemoryEntry {
597                                range: MemoryRange::try_new(base..(base + len))
598                                    .expect("valid range"),
599                                mem_type: igvm_type,
600                                vnode,
601                            },
602                        )?;
603                    }
604                }
605                "chosen" => {
606                    let cmdline = child
607                        .find_property("bootargs")
608                        .map_err(ErrorKind::Prop)?
609                        .map(|prop| prop.read_str().map_err(ErrorKind::Prop))
610                        .transpose()?
611                        .unwrap_or("");
612
613                    write!(storage.command_line, "{}", cmdline)
614                        .map_err(|_| ErrorKind::CmdlineSize)?;
615                }
616                _ if child.name.starts_with("intc@") => {
617                    validate_property_str(&child, "compatible", "arm,gic-v3")?;
618                    validate_property_u32(&child, "#redistributor-regions", 1, 0)?;
619                    validate_property_u32(&child, "#address-cells", 2, 0)?;
620                    validate_property_u32(&child, "#size-cells", 2, 0)?;
621                    validate_property_u32(&child, "#interrupt-cells", 3, 0)?;
622
623                    let gic_redistributor_stride = child
624                        .find_property("redistributor-stride")
625                        .map_err(ErrorKind::Prop)?
626                        .ok_or(ErrorKind::PropMissing {
627                            node_name: child.name,
628                            prop_name: "redistributor-stride",
629                        })?
630                        .read_u64(0)
631                        .map_err(ErrorKind::Prop)?;
632
633                    let gic_reg_property = child
634                        .find_property("reg")
635                        .map_err(ErrorKind::Prop)?
636                        .ok_or(ErrorKind::PropMissing {
637                            node_name: child.name,
638                            prop_name: "reg",
639                        })?;
640                    let gic_distributor_base =
641                        gic_reg_property.read_u64(0).map_err(ErrorKind::Prop)?;
642                    let gic_distributor_size =
643                        gic_reg_property.read_u64(1).map_err(ErrorKind::Prop)?;
644                    let gic_redistributors_base =
645                        gic_reg_property.read_u64(2).map_err(ErrorKind::Prop)?;
646                    let gic_redistributors_size =
647                        gic_reg_property.read_u64(3).map_err(ErrorKind::Prop)?;
648
649                    storage.gic = Some(GicInfo {
650                        gic_distributor_base,
651                        gic_distributor_size,
652                        gic_redistributors_base,
653                        gic_redistributors_size,
654                        gic_redistributor_stride,
655                    })
656                }
657                _ => {
658                    parse_compatible(
659                        &child,
660                        &mut storage.vmbus_vtl0,
661                        &mut storage.vmbus_vtl2,
662                        &mut storage.com3_serial,
663                    )?;
664                }
665            }
666        }
667
668        // Validate memory entries do not overlap.
669        for (prev, next) in storage.memory.iter().zip(storage.memory.iter().skip(1)) {
670            if prev.range.overlaps(&next.range) {
671                return Err(ErrorKind::MemoryRegOverlap {
672                    lower: *prev,
673                    upper: *next,
674                });
675            }
676        }
677
678        // Validate no mmio ranges overlap each other, or memory.
679        let vmbus_vtl0_mmio = storage
680            .vmbus_vtl0
681            .as_ref()
682            .map(|info| info.mmio.as_slice())
683            .unwrap_or(&[]);
684
685        let vmbus_vtl2_mmio = storage
686            .vmbus_vtl2
687            .as_ref()
688            .map(|info| info.mmio.as_slice())
689            .unwrap_or(&[]);
690
691        for ram in storage.memory.iter() {
692            for mmio in vmbus_vtl0_mmio {
693                if mmio.overlaps(&ram.range) {
694                    return Err(ErrorKind::VmbusMmioOverlapsRam {
695                        mmio: *mmio,
696                        ram: *ram,
697                    });
698                }
699            }
700
701            for mmio in vmbus_vtl2_mmio {
702                if mmio.overlaps(&ram.range) {
703                    return Err(ErrorKind::VmbusMmioOverlapsRam {
704                        mmio: *mmio,
705                        ram: *ram,
706                    });
707                }
708            }
709        }
710
711        for vtl0_mmio in vmbus_vtl0_mmio {
712            for vtl2_mmio in vmbus_vtl2_mmio {
713                if vtl0_mmio.overlaps(vtl2_mmio) {
714                    return Err(ErrorKind::VmbusMmioOverlapsVmbusMmio {
715                        mmio_a: *vtl0_mmio,
716                        mmio_b: *vtl2_mmio,
717                    });
718                }
719            }
720        }
721
722        // Set remaining fields that were not already filled out.
723        let Self {
724            device_tree_size,
725            memory: _,
726            boot_cpuid_phys,
727            cpus: _,
728            vmbus_vtl0: _,
729            vmbus_vtl2: _,
730            command_line: _,
731            com3_serial: _,
732            gic: _,
733            memory_allocation_mode: _,
734            entropy: _,
735            device_dma_page_count: _,
736            nvme_keepalive: _,
737            vtl0_alias_map: _,
738        } = storage;
739
740        *device_tree_size = parser.total_size;
741        *boot_cpuid_phys = parser.boot_cpuid_phys;
742
743        Ok(storage)
744    }
745}
746
747fn parse_compatible<'a>(
748    node: &fdt::parser::Node<'a>,
749    vmbus_vtl0: &mut Option<VmbusInfo>,
750    vmbus_vtl2: &mut Option<VmbusInfo>,
751    com3_serial: &mut bool,
752) -> Result<(), ErrorKind<'a>> {
753    let compatible = node
754        .find_property("compatible")
755        .map_err(ErrorKind::Prop)?
756        .map(|prop| prop.read_str().map_err(ErrorKind::Prop))
757        .transpose()?
758        .unwrap_or("");
759
760    if compatible == "simple-bus" {
761        parse_simple_bus(node, vmbus_vtl0, vmbus_vtl2)?;
762    } else if compatible == "x86-pio-bus" {
763        parse_io_bus(node, com3_serial)?;
764    } else {
765        #[cfg(feature = "tracing")]
766        tracing::warn!(?compatible, ?node.name,
767            "Unrecognized compatible field",
768        );
769    }
770
771    Ok(())
772}
773
774fn parse_vmbus<'a>(node: &fdt::parser::Node<'a>) -> Result<VmbusInfo, ErrorKind<'a>> {
775    // Validate address cells and size cells are 2
776    let address_cells = node
777        .find_property("#address-cells")
778        .map_err(ErrorKind::Prop)?
779        .ok_or(ErrorKind::PropMissing {
780            node_name: node.name,
781            prop_name: "#address-cells",
782        })?
783        .read_u32(0)
784        .map_err(ErrorKind::Prop)?;
785
786    if address_cells != 2 {
787        return Err(ErrorKind::PropInvalidU32 {
788            node_name: node.name,
789            prop_name: "#address-cells",
790            expected: 2,
791            actual: address_cells,
792        });
793    }
794
795    let size_cells = node
796        .find_property("#size-cells")
797        .map_err(ErrorKind::Prop)?
798        .ok_or(ErrorKind::PropMissing {
799            node_name: node.name,
800            prop_name: "#size-cells",
801        })?
802        .read_u32(0)
803        .map_err(ErrorKind::Prop)?;
804
805    if size_cells != 2 {
806        return Err(ErrorKind::PropInvalidU32 {
807            node_name: node.name,
808            prop_name: "#size-cells",
809            expected: 2,
810            actual: size_cells,
811        });
812    }
813
814    let mmio: ArrayVec<MemoryRange, 2> =
815        match node.find_property("ranges").map_err(ErrorKind::Prop)? {
816            Some(ranges) => {
817                // Determine how many mmio ranges this describes. Valid numbers are
818                // 0, 1 or 2.
819                let ranges_tuple_size = size_of::<u64>() * 3;
820                let number_of_ranges = ranges.data.len() / ranges_tuple_size;
821                let mut mmio = ArrayVec::new();
822
823                if number_of_ranges > 2 {
824                    return Err(ErrorKind::TooManyVmbusMmioRanges {
825                        node_name: node.name,
826                        ranges: number_of_ranges,
827                    });
828                }
829
830                for i in 0..number_of_ranges {
831                    let child_base = ranges.read_u64(i * 3).map_err(ErrorKind::Prop)?;
832                    let parent_base = ranges.read_u64(i * 3 + 1).map_err(ErrorKind::Prop)?;
833                    let len = ranges.read_u64(i * 3 + 2).map_err(ErrorKind::Prop)?;
834
835                    if child_base != parent_base {
836                        return Err(ErrorKind::VmbusRangesChildParent {
837                            node_name: node.name,
838                            child_base,
839                            parent_base,
840                        });
841                    }
842
843                    if child_base % HV_PAGE_SIZE != 0 || len % HV_PAGE_SIZE != 0 {
844                        return Err(ErrorKind::VmbusRangesNotAligned {
845                            node_name: node.name,
846                            base: child_base,
847                            len,
848                        });
849                    }
850
851                    mmio.push(
852                        MemoryRange::try_new(child_base..(child_base + len)).expect("valid range"),
853                    );
854                }
855
856                // The DT ranges field might not have been sorted. Swap them if the
857                // low gap was described 2nd.
858                if number_of_ranges > 1 && mmio[0].start() > mmio[1].start() {
859                    mmio.swap(0, 1);
860                }
861
862                if number_of_ranges > 1 && mmio[0].overlaps(&mmio[1]) {
863                    return Err(ErrorKind::VmbusMmioOverlapsVmbusMmio {
864                        mmio_a: mmio[0],
865                        mmio_b: mmio[1],
866                    });
867                }
868
869                mmio
870            }
871            None => {
872                // No mmio is acceptable.
873                ArrayVec::new()
874            }
875        };
876
877    let connection_id = node
878        .find_property("microsoft,message-connection-id")
879        .map_err(ErrorKind::Prop)?
880        .ok_or(ErrorKind::PropMissing {
881            node_name: node.name,
882            prop_name: "microsoft,message-connection-id",
883        })?
884        .read_u32(0)
885        .map_err(ErrorKind::Prop)?;
886
887    Ok(VmbusInfo {
888        mmio,
889        connection_id,
890    })
891}
892
893fn parse_simple_bus<'a>(
894    node: &fdt::parser::Node<'a>,
895    vmbus_vtl0: &mut Option<VmbusInfo>,
896    vmbus_vtl2: &mut Option<VmbusInfo>,
897) -> Result<(), ErrorKind<'a>> {
898    // Vmbus must be under simple-bus node with empty ranges.
899    if !node
900        .find_property("ranges")
901        .map_err(ErrorKind::Prop)?
902        .ok_or(ErrorKind::PropMissing {
903            node_name: node.name,
904            prop_name: "ranges",
905        })?
906        .data
907        .is_empty()
908    {
909        return Ok(());
910    }
911
912    for child in node.children() {
913        let child = child.map_err(|error| ErrorKind::Node {
914            parent_name: node.name,
915            error,
916        })?;
917
918        let compatible = child
919            .find_property("compatible")
920            .map_err(ErrorKind::Prop)?
921            .map(|prop| prop.read_str().map_err(ErrorKind::Prop))
922            .transpose()?
923            .unwrap_or("");
924
925        if compatible == "microsoft,vmbus" {
926            let vtl_name = igvm_defs::dt::IGVM_DT_VTL_PROPERTY;
927            let vtl = child
928                .find_property(vtl_name)
929                .map_err(ErrorKind::Prop)?
930                .ok_or(ErrorKind::PropMissing {
931                    node_name: child.name,
932                    prop_name: vtl_name,
933                })?
934                .read_u32(0)
935                .map_err(ErrorKind::Prop)?;
936
937            match vtl {
938                0 => {
939                    if vmbus_vtl0.replace(parse_vmbus(&child)?).is_some() {
940                        return Err(ErrorKind::MultipleVmbusNode {
941                            node_name: child.name,
942                        });
943                    }
944                }
945                2 => {
946                    if vmbus_vtl2.replace(parse_vmbus(&child)?).is_some() {
947                        return Err(ErrorKind::MultipleVmbusNode {
948                            node_name: child.name,
949                        });
950                    }
951                }
952                _ => {
953                    return Err(ErrorKind::UnexpectedVmbusVtl {
954                        node_name: child.name,
955                        vtl,
956                    });
957                }
958            }
959        }
960    }
961
962    Ok(())
963}
964
965fn parse_io_bus<'a>(
966    node: &fdt::parser::Node<'a>,
967    com3_serial: &mut bool,
968) -> Result<(), ErrorKind<'a>> {
969    for io_bus_child in node.children() {
970        let io_bus_child = io_bus_child.map_err(|error| ErrorKind::Node {
971            parent_name: node.name,
972            error,
973        })?;
974
975        let compatible: &str = io_bus_child
976            .find_property("compatible")
977            .map_err(ErrorKind::Prop)?
978            .map(|prop| prop.read_str().map_err(ErrorKind::Prop))
979            .transpose()?
980            .unwrap_or("");
981
982        let _current_speed = io_bus_child
983            .find_property("current-speed")
984            .map_err(ErrorKind::Prop)?
985            .ok_or(ErrorKind::PropMissing {
986                node_name: io_bus_child.name,
987                prop_name: "current-speed",
988            })?
989            .read_u32(0)
990            .map_err(ErrorKind::Prop)?;
991
992        let reg = io_bus_child
993            .find_property("reg")
994            .map_err(ErrorKind::Prop)?
995            .ok_or(ErrorKind::PropMissing {
996                node_name: io_bus_child.name,
997                prop_name: "reg",
998            })?;
999
1000        let reg_base = reg.read_u64(0).map_err(ErrorKind::Prop)?;
1001        let _reg_len = reg.read_u64(1).map_err(ErrorKind::Prop)?;
1002
1003        // Linux kernel hard-codes COM3 to COM3_REG_BASE.
1004        // If work is ever done in the Linux kernel to instead
1005        // parse from DT, the 2nd condition can be removed.
1006        if compatible == "ns16550" && reg_base == COM3_REG_BASE {
1007            *com3_serial = true
1008        } else {
1009            #[cfg(feature = "tracing")]
1010            tracing::warn!(?node.name, ?compatible, ?reg_base,
1011                "unrecognized io bus child"
1012            );
1013        }
1014    }
1015
1016    Ok(())
1017}
1018
1019fn validate_property_str<'a>(
1020    child: &fdt::parser::Node<'a>,
1021    name: &'static str,
1022    expected: &'static str,
1023) -> Result<(), ErrorKind<'a>> {
1024    let actual = child
1025        .find_property(name)
1026        .map_err(ErrorKind::Prop)?
1027        .ok_or(ErrorKind::PropMissing {
1028            node_name: child.name,
1029            prop_name: name,
1030        })?
1031        .read_str()
1032        .map_err(ErrorKind::Prop)?;
1033    if actual != expected {
1034        return Err(ErrorKind::PropInvalidStr {
1035            node_name: child.name,
1036            prop_name: name,
1037            expected,
1038            actual,
1039        });
1040    }
1041
1042    Ok(())
1043}
1044
1045fn validate_property_u32<'a>(
1046    child: &fdt::parser::Node<'a>,
1047    name: &'static str,
1048    expected: u32,
1049    index: usize,
1050) -> Result<(), ErrorKind<'a>> {
1051    let actual = child
1052        .find_property(name)
1053        .map_err(ErrorKind::Prop)?
1054        .ok_or(ErrorKind::PropMissing {
1055            node_name: child.name,
1056            prop_name: name,
1057        })?
1058        .read_u32(index)
1059        .map_err(ErrorKind::Prop)?;
1060    if actual != expected {
1061        return Err(ErrorKind::PropInvalidU32 {
1062            node_name: child.name,
1063            prop_name: name,
1064            expected,
1065            actual,
1066        });
1067    }
1068
1069    Ok(())
1070}
1071
1072#[cfg(feature = "inspect")]
1073mod inspect_helpers {
1074    use super::*;
1075
1076    pub(super) fn inspect_memory_map_entry_type(typ: &MemoryMapEntryType) -> impl Inspect + '_ {
1077        // TODO: inspect::AsDebug would work here once
1078        // https://github.com/kupiakos/open-enum/pull/13 is merged.
1079        inspect::adhoc(|req| match *typ {
1080            MemoryMapEntryType::MEMORY => req.value("MEMORY"),
1081            MemoryMapEntryType::PERSISTENT => req.value("PERSISTENT"),
1082            MemoryMapEntryType::PLATFORM_RESERVED => req.value("PLATFORM_RESERVED"),
1083            MemoryMapEntryType::VTL2_PROTECTABLE => req.value("VTL2_PROTECTABLE"),
1084            _ => req.value(typ.0),
1085        })
1086    }
1087
1088    pub(super) fn mmio_internal(mmio: &[MemoryRange]) -> impl Inspect + '_ {
1089        inspect::iter_by_key(
1090            mmio.iter()
1091                .map(|range| (range, inspect::AsHex(range.len()))),
1092        )
1093    }
1094
1095    pub(super) fn memory_internal(memory: &[MemoryEntry]) -> impl Inspect + '_ {
1096        inspect::iter_by_key(memory.iter().map(|entry| (entry.range, entry)))
1097    }
1098}
1099
1100#[cfg(test)]
1101mod tests {
1102    extern crate alloc;
1103
1104    use super::*;
1105    use alloc::format;
1106    use alloc::vec;
1107    use alloc::vec::Vec;
1108    use fdt::builder::Builder;
1109    use fdt::builder::BuilderConfig;
1110    use fdt::builder::Nest;
1111
1112    type TestParsedDeviceTree = ParsedDeviceTree<32, 32, 1024, 64>;
1113
1114    fn new_vmbus_mmio(mmio: &[MemoryRange]) -> ArrayVec<MemoryRange, 2> {
1115        let mut vec = ArrayVec::new();
1116        vec.try_extend_from_slice(mmio).unwrap();
1117        vec
1118    }
1119
1120    struct VmbusStringIds {
1121        p_address_cells: fdt::builder::StringId,
1122        p_size_cells: fdt::builder::StringId,
1123        p_compatible: fdt::builder::StringId,
1124        p_ranges: fdt::builder::StringId,
1125        p_vtl: fdt::builder::StringId,
1126        p_vmbus_connection_id: fdt::builder::StringId,
1127    }
1128
1129    fn add_vmbus<'a>(
1130        ids: &VmbusStringIds,
1131        bus: Builder<'a, Nest<Nest<()>>>,
1132        vmbus_info: &VmbusInfo,
1133        vtl: u8,
1134    ) -> Builder<'a, Nest<Nest<()>>> {
1135        let mmio = {
1136            let mut ranges = Vec::new();
1137            for entry in &vmbus_info.mmio {
1138                ranges.push(entry.start());
1139                ranges.push(entry.start());
1140                ranges.push(entry.len());
1141            }
1142            ranges
1143        };
1144        let name = if mmio.is_empty() {
1145            format!("vmbus-vtl{vtl}")
1146        } else {
1147            format!("vmbus-vtl{vtl}@{:x}", mmio[0])
1148        };
1149        bus.start_node(&name)
1150            .unwrap()
1151            .add_u32(ids.p_address_cells, 2)
1152            .unwrap()
1153            .add_u32(ids.p_size_cells, 2)
1154            .unwrap()
1155            .add_str(ids.p_compatible, "microsoft,vmbus")
1156            .unwrap()
1157            .add_u64_array(ids.p_ranges, &mmio)
1158            .unwrap()
1159            .add_u32(ids.p_vtl, vtl as u32)
1160            .unwrap()
1161            .add_u32(ids.p_vmbus_connection_id, vmbus_info.connection_id)
1162            .unwrap()
1163            .end_node()
1164            .unwrap()
1165    }
1166
1167    /// Build a dt from a parsed context.
1168    fn build_dt(context: &TestParsedDeviceTree) -> Vec<u8> {
1169        let mut buf = vec![0; 25600];
1170        let mut builder = Builder::new(BuilderConfig {
1171            blob_buffer: &mut buf,
1172            string_table_cap: 1024,
1173            memory_reservations: &[],
1174        })
1175        .expect("can build the DT builder");
1176        let p_address_cells = builder.add_string("#address-cells").unwrap();
1177        let p_size_cells = builder.add_string("#size-cells").unwrap();
1178        let p_model = builder.add_string("model").unwrap();
1179        let p_reg = builder.add_string("reg").unwrap();
1180        let p_ranges = builder.add_string("ranges").unwrap();
1181        let p_device_type = builder.add_string("device_type").unwrap();
1182        let p_status = builder.add_string("status").unwrap();
1183        let p_igvm_type = builder
1184            .add_string(igvm_defs::dt::IGVM_DT_IGVM_TYPE_PROPERTY)
1185            .unwrap();
1186        let p_numa_node_id = builder.add_string("numa-node-id").unwrap();
1187        let p_compatible = builder.add_string("compatible").unwrap();
1188        let p_vmbus_connection_id = builder
1189            .add_string("microsoft,message-connection-id")
1190            .unwrap();
1191        let p_vtl = builder
1192            .add_string(igvm_defs::dt::IGVM_DT_VTL_PROPERTY)
1193            .unwrap();
1194        let p_bootargs = builder.add_string("bootargs").unwrap();
1195        let p_clock_frequency = builder.add_string("clock-frequency").unwrap();
1196        let p_current_speed = builder.add_string("current-speed").unwrap();
1197        let p_interrupts = builder.add_string("interrupts").unwrap();
1198
1199        let mut cpus = builder
1200            .start_node("")
1201            .unwrap()
1202            .add_u32(p_address_cells, 2)
1203            .unwrap() // 64bit
1204            .add_u32(p_size_cells, 2)
1205            .unwrap() // 64bit
1206            .add_str(p_model, "microsoft,hyperv")
1207            .unwrap()
1208            .start_node("cpus")
1209            .unwrap()
1210            .add_u32(p_address_cells, 1)
1211            .unwrap()
1212            .add_u32(p_size_cells, 0)
1213            .unwrap();
1214
1215        // Add a CPU node for each VP.
1216        for (index, cpu) in context.cpus.iter().enumerate() {
1217            let name = format!("cpu@{:x}", index);
1218            cpus = cpus
1219                .start_node(name.as_ref())
1220                .unwrap()
1221                .add_str(p_device_type, "cpu")
1222                .unwrap()
1223                .add_u32(p_reg, cpu.reg as u32)
1224                .unwrap()
1225                .add_u32(p_numa_node_id, cpu.vnode)
1226                .unwrap()
1227                .add_str(p_status, "okay")
1228                .unwrap()
1229                .end_node()
1230                .unwrap();
1231        }
1232
1233        let mut root = cpus.end_node().unwrap();
1234
1235        // Add memory, but reverse to test parsing sorting.
1236        // TODO: maybe shuffle order even more?
1237        for MemoryEntry {
1238            range,
1239            mem_type,
1240            vnode,
1241        } in context.memory.iter().rev()
1242        {
1243            let name = format!("memory@{:x}", range.start());
1244            root = root
1245                .start_node(name.as_ref())
1246                .unwrap()
1247                .add_str(p_device_type, "memory")
1248                .unwrap()
1249                .add_u64_array(p_reg, &[range.start(), range.len()])
1250                .unwrap()
1251                .add_u32(p_igvm_type, mem_type.0 as u32)
1252                .unwrap()
1253                .add_u32(p_numa_node_id, *vnode)
1254                .unwrap()
1255                .end_node()
1256                .unwrap();
1257        }
1258
1259        // GIC
1260        if let Some(gic) = &context.gic {
1261            let p_interrupt_cells = root.add_string("#interrupt-cells").unwrap();
1262            let p_redist_regions = root.add_string("#redistributor-regions").unwrap();
1263            let p_redist_stride = root.add_string("redistributor-stride").unwrap();
1264            let p_interrupt_controller = root.add_string("interrupt-controller").unwrap();
1265            let p_phandle = root.add_string("phandle").unwrap();
1266            let name = format!("intc@{}", gic.gic_distributor_base);
1267            root = root
1268                .start_node(name.as_ref())
1269                .unwrap()
1270                .add_str(p_compatible, "arm,gic-v3")
1271                .unwrap()
1272                .add_u32(p_redist_regions, 1)
1273                .unwrap()
1274                .add_u64(p_redist_stride, gic.gic_redistributor_stride)
1275                .unwrap()
1276                .add_u64_array(
1277                    p_reg,
1278                    &[
1279                        gic.gic_distributor_base,
1280                        gic.gic_distributor_size,
1281                        gic.gic_redistributors_base,
1282                        gic.gic_redistributors_size,
1283                    ],
1284                )
1285                .unwrap()
1286                .add_u32(p_address_cells, 2)
1287                .unwrap()
1288                .add_u32(p_size_cells, 2)
1289                .unwrap()
1290                .add_u32(p_interrupt_cells, 3)
1291                .unwrap()
1292                .add_null(p_interrupt_controller)
1293                .unwrap()
1294                .add_u32(p_phandle, 1)
1295                .unwrap()
1296                .add_null(p_ranges)
1297                .unwrap()
1298                .end_node()
1299                .unwrap();
1300        }
1301
1302        // Linux requires vmbus to be under a simple-bus node.
1303        let mut simple_bus = root
1304            .start_node("bus")
1305            .unwrap()
1306            .add_str(p_compatible, "simple-bus")
1307            .unwrap()
1308            .add_u32(p_address_cells, 2)
1309            .unwrap()
1310            .add_u32(p_size_cells, 2)
1311            .unwrap()
1312            .add_prop_array(p_ranges, &[])
1313            .unwrap();
1314
1315        let vmbus_ids = VmbusStringIds {
1316            p_address_cells,
1317            p_size_cells,
1318            p_compatible,
1319            p_ranges,
1320            p_vtl,
1321            p_vmbus_connection_id,
1322        };
1323
1324        // VTL0 vmbus root device
1325        if let Some(vmbus) = &context.vmbus_vtl0 {
1326            simple_bus = add_vmbus(&vmbus_ids, simple_bus, vmbus, 0);
1327        }
1328
1329        // VTL2 vmbus root device
1330        if let Some(vmbus) = &context.vmbus_vtl2 {
1331            simple_bus = add_vmbus(&vmbus_ids, simple_bus, vmbus, 2);
1332        }
1333
1334        root = simple_bus.end_node().unwrap();
1335
1336        // Com3 serial node
1337        if context.com3_serial {
1338            let mut io_port_bus = root
1339                .start_node("io-bus")
1340                .unwrap()
1341                .add_str(p_compatible, "x86-pio-bus")
1342                .unwrap()
1343                .add_u32(p_address_cells, 1)
1344                .unwrap()
1345                .add_u32(p_size_cells, 0)
1346                .unwrap()
1347                .add_prop_array(p_ranges, &[])
1348                .unwrap();
1349
1350            let serial_name = format!("serial@{:x}", COM3_REG_BASE);
1351            io_port_bus = io_port_bus
1352                .start_node(&serial_name)
1353                .unwrap()
1354                .add_str(p_compatible, "ns16550")
1355                .unwrap()
1356                .add_u32(p_clock_frequency, 0)
1357                .unwrap()
1358                .add_u32(p_current_speed, 115200)
1359                .unwrap()
1360                .add_u64_array(p_reg, &[COM3_REG_BASE, 0x8])
1361                .unwrap()
1362                .add_u64_array(p_interrupts, &[4])
1363                .unwrap()
1364                .end_node()
1365                .unwrap();
1366
1367            root = io_port_bus.end_node().unwrap();
1368        }
1369
1370        // Chosen node - contains cmdline.
1371        root = root
1372            .start_node("chosen")
1373            .unwrap()
1374            .add_str(p_bootargs, context.command_line.as_ref())
1375            .unwrap()
1376            .end_node()
1377            .unwrap();
1378
1379        // openhcl node - contains openhcl specific information.
1380        let p_memory_allocation_mode = root.add_string("memory-allocation-mode").unwrap();
1381        let p_memory_allocation_size = root.add_string("memory-size").unwrap();
1382        let p_mmio_allocation_size = root.add_string("mmio-size").unwrap();
1383        let p_device_dma_page_count = root.add_string("total-pages").unwrap();
1384        let mut openhcl = root.start_node("openhcl").unwrap();
1385
1386        let memory_alloc_str = match context.memory_allocation_mode {
1387            MemoryAllocationMode::Host => "host",
1388            MemoryAllocationMode::Vtl2 {
1389                memory_size,
1390                mmio_size,
1391            } => {
1392                // Encode the size at the expected property.
1393                if let Some(memory_size) = memory_size {
1394                    openhcl = openhcl
1395                        .add_u64(p_memory_allocation_size, memory_size)
1396                        .unwrap();
1397                }
1398                if let Some(mmio_size) = mmio_size {
1399                    openhcl = openhcl.add_u64(p_mmio_allocation_size, mmio_size).unwrap();
1400                }
1401                "vtl2"
1402            }
1403        };
1404
1405        openhcl = openhcl
1406            .add_str(p_memory_allocation_mode, memory_alloc_str)
1407            .unwrap();
1408
1409        // add device_dma_page_count
1410        if let Some(device_dma_page_count) = context.device_dma_page_count {
1411            openhcl = openhcl
1412                .start_node("device-dma")
1413                .unwrap()
1414                .add_u64(p_device_dma_page_count, device_dma_page_count)
1415                .unwrap()
1416                .end_node()
1417                .unwrap();
1418        }
1419
1420        root = openhcl.end_node().unwrap();
1421
1422        let bytes_used = root
1423            .end_node()
1424            .unwrap()
1425            .build(context.boot_cpuid_phys)
1426            .unwrap();
1427        buf.truncate(bytes_used);
1428
1429        buf
1430    }
1431
1432    /// Creates a parsed device tree context. No validation is performed.
1433    fn create_parsed(
1434        dt_size: usize,
1435        memory: &[MemoryEntry],
1436        cpus: &[CpuEntry],
1437        bsp: u32,
1438        vmbus_vtl0: Option<VmbusInfo>,
1439        vmbus_vtl2: Option<VmbusInfo>,
1440        command_line: &str,
1441        com3_serial: bool,
1442        gic: Option<GicInfo>,
1443        memory_allocation_mode: MemoryAllocationMode,
1444        device_dma_page_count: Option<u64>,
1445    ) -> TestParsedDeviceTree {
1446        let mut context = TestParsedDeviceTree::new();
1447        context.device_tree_size = dt_size;
1448        context.boot_cpuid_phys = bsp;
1449        write!(context.command_line, "{command_line}").unwrap();
1450        context.com3_serial = com3_serial;
1451        context.vmbus_vtl0 = vmbus_vtl0;
1452        context.vmbus_vtl2 = vmbus_vtl2;
1453        context.memory.try_extend_from_slice(memory).unwrap();
1454        context.cpus.try_extend_from_slice(cpus).unwrap();
1455        context.gic = gic;
1456        context.memory_allocation_mode = memory_allocation_mode;
1457        context.device_dma_page_count = device_dma_page_count;
1458        context
1459    }
1460
1461    #[test]
1462    fn test_basic_dt() {
1463        let orig = create_parsed(
1464            2608,
1465            &[
1466                MemoryEntry {
1467                    range: MemoryRange::try_new(0..(1024 * HV_PAGE_SIZE)).unwrap(),
1468                    mem_type: MemoryMapEntryType::MEMORY,
1469                    vnode: 0,
1470                },
1471                MemoryEntry {
1472                    range: MemoryRange::try_new((1024 * HV_PAGE_SIZE)..(4024 * HV_PAGE_SIZE))
1473                        .unwrap(),
1474                    mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1475                    vnode: 0,
1476                },
1477                MemoryEntry {
1478                    range: MemoryRange::try_new((14024 * HV_PAGE_SIZE)..(102400 * HV_PAGE_SIZE))
1479                        .unwrap(),
1480                    mem_type: MemoryMapEntryType::MEMORY,
1481                    vnode: 0,
1482                },
1483            ],
1484            &[
1485                CpuEntry { reg: 12, vnode: 0 },
1486                CpuEntry { reg: 42, vnode: 0 },
1487                CpuEntry { reg: 23, vnode: 0 },
1488                CpuEntry { reg: 24, vnode: 0 },
1489            ],
1490            42,
1491            Some(VmbusInfo {
1492                mmio: new_vmbus_mmio(&[
1493                    MemoryRange::try_new((4024 * HV_PAGE_SIZE)..(4096 * HV_PAGE_SIZE)).unwrap(),
1494                    MemoryRange::try_new((102400 * HV_PAGE_SIZE)..(102800 * HV_PAGE_SIZE)).unwrap(),
1495                ]),
1496                connection_id: 1,
1497            }),
1498            Some(VmbusInfo {
1499                mmio: new_vmbus_mmio(&[MemoryRange::try_new(
1500                    (102800 * HV_PAGE_SIZE)..(102900 * HV_PAGE_SIZE),
1501                )
1502                .unwrap()]),
1503                connection_id: 4,
1504            }),
1505            "THIS_IS_A_BOOT_ARG=1",
1506            false,
1507            Some(GicInfo {
1508                gic_distributor_base: 0x20000,
1509                gic_distributor_size: 0x10000,
1510                gic_redistributors_base: 0x40000,
1511                gic_redistributors_size: 0x60000,
1512                gic_redistributor_stride: 0x20000,
1513            }),
1514            MemoryAllocationMode::Host,
1515            Some(1234),
1516        );
1517
1518        let dt = build_dt(&orig);
1519        let mut parsed = TestParsedDeviceTree::new();
1520        let parsed = TestParsedDeviceTree::parse(&dt, &mut parsed).unwrap();
1521        assert_eq!(&orig, parsed);
1522    }
1523
1524    #[test]
1525    fn test_numa_dt() {
1526        let orig = create_parsed(
1527            2352,
1528            &[
1529                MemoryEntry {
1530                    range: MemoryRange::try_new(0..(1024 * HV_PAGE_SIZE)).unwrap(),
1531                    mem_type: MemoryMapEntryType::MEMORY,
1532                    vnode: 0,
1533                },
1534                MemoryEntry {
1535                    range: MemoryRange::try_new((1024 * HV_PAGE_SIZE)..(2048 * HV_PAGE_SIZE))
1536                        .unwrap(),
1537                    mem_type: MemoryMapEntryType::MEMORY,
1538                    vnode: 1,
1539                },
1540                MemoryEntry {
1541                    range: MemoryRange::try_new((2048 * HV_PAGE_SIZE)..(3072 * HV_PAGE_SIZE))
1542                        .unwrap(),
1543                    mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1544                    vnode: 0,
1545                },
1546                MemoryEntry {
1547                    range: MemoryRange::try_new((3072 * HV_PAGE_SIZE)..(4096 * HV_PAGE_SIZE))
1548                        .unwrap(),
1549                    mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1550                    vnode: 1,
1551                },
1552                MemoryEntry {
1553                    range: MemoryRange::try_new((4096 * HV_PAGE_SIZE)..(51200 * HV_PAGE_SIZE))
1554                        .unwrap(),
1555                    mem_type: MemoryMapEntryType::MEMORY,
1556                    vnode: 0,
1557                },
1558                MemoryEntry {
1559                    range: MemoryRange::try_new((51200 * HV_PAGE_SIZE)..(102400 * HV_PAGE_SIZE))
1560                        .unwrap(),
1561                    mem_type: MemoryMapEntryType::MEMORY,
1562                    vnode: 1,
1563                },
1564            ],
1565            &[
1566                CpuEntry { reg: 12, vnode: 0 },
1567                CpuEntry { reg: 42, vnode: 1 },
1568                CpuEntry { reg: 23, vnode: 0 },
1569                CpuEntry { reg: 24, vnode: 1 },
1570            ],
1571            23,
1572            None,
1573            None,
1574            "",
1575            false,
1576            None,
1577            MemoryAllocationMode::Vtl2 {
1578                memory_size: Some(1000 * 1024 * 1024), // 1000 MB
1579                mmio_size: Some(128 * 1024 * 1024),    // 128 MB
1580            },
1581            None,
1582        );
1583
1584        let dt = build_dt(&orig);
1585        let mut parsed = TestParsedDeviceTree::new();
1586        let parsed = TestParsedDeviceTree::parse(&dt, &mut parsed).unwrap();
1587        assert_eq!(&orig, parsed);
1588    }
1589
1590    /// Tests memory ranges that overlap each other, or memory ranges that
1591    /// overlap vmbus mmio.
1592    #[test]
1593    fn test_overlapping_memory() {
1594        // mem overlaps each other
1595        let bad = create_parsed(
1596            0,
1597            &[
1598                MemoryEntry {
1599                    range: MemoryRange::try_new(0..(1024 * HV_PAGE_SIZE)).unwrap(),
1600                    mem_type: MemoryMapEntryType::MEMORY,
1601                    vnode: 0,
1602                },
1603                MemoryEntry {
1604                    range: MemoryRange::try_new(4096..(1024 * HV_PAGE_SIZE)).unwrap(),
1605                    mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1606                    vnode: 0,
1607                },
1608                MemoryEntry {
1609                    range: MemoryRange::try_new((14024 * HV_PAGE_SIZE)..(102400 * HV_PAGE_SIZE))
1610                        .unwrap(),
1611                    mem_type: MemoryMapEntryType::MEMORY,
1612                    vnode: 0,
1613                },
1614            ],
1615            &[
1616                CpuEntry { reg: 12, vnode: 0 },
1617                CpuEntry { reg: 42, vnode: 0 },
1618                CpuEntry { reg: 23, vnode: 0 },
1619                CpuEntry { reg: 24, vnode: 0 },
1620            ],
1621            42,
1622            None,
1623            None,
1624            "THIS_IS_A_BOOT_ARG=1",
1625            false,
1626            None,
1627            MemoryAllocationMode::Host,
1628            None,
1629        );
1630
1631        let dt = build_dt(&bad);
1632        let mut parsed = TestParsedDeviceTree::new();
1633        assert!(matches!(
1634            TestParsedDeviceTree::parse(&dt, &mut parsed),
1635            Err(Error(ErrorKind::MemoryRegOverlap { .. }))
1636        ));
1637
1638        // mem contained within another
1639        let bad = create_parsed(
1640            0,
1641            &[
1642                MemoryEntry {
1643                    range: MemoryRange::try_new(4096..(1024 * HV_PAGE_SIZE)).unwrap(),
1644                    mem_type: MemoryMapEntryType::MEMORY,
1645                    vnode: 0,
1646                },
1647                MemoryEntry {
1648                    range: MemoryRange::try_new(0..(102400 * HV_PAGE_SIZE)).unwrap(),
1649                    mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1650                    vnode: 0,
1651                },
1652            ],
1653            &[
1654                CpuEntry { reg: 12, vnode: 0 },
1655                CpuEntry { reg: 42, vnode: 0 },
1656                CpuEntry { reg: 23, vnode: 0 },
1657                CpuEntry { reg: 24, vnode: 0 },
1658            ],
1659            42,
1660            None,
1661            None,
1662            "THIS_IS_A_BOOT_ARG=1",
1663            false,
1664            None,
1665            MemoryAllocationMode::Host,
1666            None,
1667        );
1668
1669        let dt = build_dt(&bad);
1670        let mut parsed = TestParsedDeviceTree::new();
1671        assert!(matches!(
1672            TestParsedDeviceTree::parse(&dt, &mut parsed),
1673            Err(Error(ErrorKind::MemoryRegOverlap { .. }))
1674        ));
1675
1676        // mem overlaps vmbus
1677        let bad = create_parsed(
1678            0,
1679            &[MemoryEntry {
1680                range: MemoryRange::try_new(0..(202400 * HV_PAGE_SIZE)).unwrap(),
1681                mem_type: MemoryMapEntryType::MEMORY,
1682                vnode: 0,
1683            }],
1684            &[
1685                CpuEntry { reg: 12, vnode: 0 },
1686                CpuEntry { reg: 42, vnode: 0 },
1687                CpuEntry { reg: 23, vnode: 0 },
1688                CpuEntry { reg: 24, vnode: 0 },
1689            ],
1690            42,
1691            Some(VmbusInfo {
1692                mmio: new_vmbus_mmio(&[MemoryRange::try_new(
1693                    (4024 * HV_PAGE_SIZE)..(4096 * HV_PAGE_SIZE),
1694                )
1695                .unwrap()]),
1696                connection_id: 1,
1697            }),
1698            Some(VmbusInfo {
1699                mmio: new_vmbus_mmio(&[MemoryRange::try_new(
1700                    (102800 * HV_PAGE_SIZE)..(102900 * HV_PAGE_SIZE),
1701                )
1702                .unwrap()]),
1703                connection_id: 4,
1704            }),
1705            "THIS_IS_A_BOOT_ARG=1",
1706            false,
1707            None,
1708            MemoryAllocationMode::Host,
1709            None,
1710        );
1711
1712        let dt = build_dt(&bad);
1713        let mut parsed = TestParsedDeviceTree::new();
1714        assert!(matches!(
1715            TestParsedDeviceTree::parse(&dt, &mut parsed),
1716            Err(Error(ErrorKind::VmbusMmioOverlapsRam { .. }))
1717        ));
1718
1719        // vmbus overlap each other
1720        let bad = create_parsed(
1721            0,
1722            &[MemoryEntry {
1723                range: MemoryRange::try_new(0..(1024 * HV_PAGE_SIZE)).unwrap(),
1724                mem_type: MemoryMapEntryType::MEMORY,
1725                vnode: 0,
1726            }],
1727            &[
1728                CpuEntry { reg: 12, vnode: 0 },
1729                CpuEntry { reg: 42, vnode: 0 },
1730                CpuEntry { reg: 23, vnode: 0 },
1731                CpuEntry { reg: 24, vnode: 0 },
1732            ],
1733            42,
1734            Some(VmbusInfo {
1735                mmio: new_vmbus_mmio(&[
1736                    MemoryRange::try_new((4000 * HV_PAGE_SIZE)..(4096 * HV_PAGE_SIZE)).unwrap(),
1737                    MemoryRange::EMPTY,
1738                ]),
1739                connection_id: 1,
1740            }),
1741            Some(VmbusInfo {
1742                mmio: new_vmbus_mmio(&[
1743                    MemoryRange::try_new((4020 * HV_PAGE_SIZE)..(102900 * HV_PAGE_SIZE)).unwrap(),
1744                    MemoryRange::EMPTY,
1745                ]),
1746                connection_id: 4,
1747            }),
1748            "THIS_IS_A_BOOT_ARG=1",
1749            false,
1750            None,
1751            MemoryAllocationMode::Host,
1752            None,
1753        );
1754
1755        let dt = build_dt(&bad);
1756        let mut parsed = TestParsedDeviceTree::new();
1757        assert!(matches!(
1758            TestParsedDeviceTree::parse(&dt, &mut parsed),
1759            Err(Error(ErrorKind::VmbusMmioOverlapsVmbusMmio { .. }))
1760        ));
1761    }
1762
1763    /// tests serial output
1764    #[test]
1765    fn test_com3_serial_output() {
1766        let orig = create_parsed(
1767            2560,
1768            &[
1769                MemoryEntry {
1770                    range: MemoryRange::try_new(0..(1024 * HV_PAGE_SIZE)).unwrap(),
1771                    mem_type: MemoryMapEntryType::MEMORY,
1772                    vnode: 0,
1773                },
1774                MemoryEntry {
1775                    range: MemoryRange::try_new((1024 * HV_PAGE_SIZE)..(4024 * HV_PAGE_SIZE))
1776                        .unwrap(),
1777                    mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1778                    vnode: 0,
1779                },
1780                MemoryEntry {
1781                    range: MemoryRange::try_new((14024 * HV_PAGE_SIZE)..(102400 * HV_PAGE_SIZE))
1782                        .unwrap(),
1783                    mem_type: MemoryMapEntryType::MEMORY,
1784                    vnode: 0,
1785                },
1786            ],
1787            &[
1788                CpuEntry { reg: 12, vnode: 0 },
1789                CpuEntry { reg: 42, vnode: 0 },
1790                CpuEntry { reg: 23, vnode: 0 },
1791                CpuEntry { reg: 24, vnode: 0 },
1792            ],
1793            42,
1794            Some(VmbusInfo {
1795                mmio: new_vmbus_mmio(&[
1796                    MemoryRange::try_new((4024 * HV_PAGE_SIZE)..(4096 * HV_PAGE_SIZE)).unwrap(),
1797                    MemoryRange::try_new((102400 * HV_PAGE_SIZE)..(102800 * HV_PAGE_SIZE)).unwrap(),
1798                ]),
1799                connection_id: 1,
1800            }),
1801            Some(VmbusInfo {
1802                mmio: new_vmbus_mmio(&[MemoryRange::try_new(
1803                    (102800 * HV_PAGE_SIZE)..(102900 * HV_PAGE_SIZE),
1804                )
1805                .unwrap()]),
1806                connection_id: 4,
1807            }),
1808            "THIS_IS_A_BOOT_ARG=1",
1809            true,
1810            None,
1811            MemoryAllocationMode::Host,
1812            None,
1813        );
1814
1815        let dt = build_dt(&orig);
1816        let mut parsed = TestParsedDeviceTree::new();
1817        let parsed = TestParsedDeviceTree::parse(&dt, &mut parsed).unwrap();
1818
1819        assert_eq!(&orig, parsed);
1820        assert!(parsed.com3_serial);
1821    }
1822}