bootloader_fdt_parser/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Parsing code for the devicetree provided by openhcl_boot used by underhill
5//! usermode. Unlike `host_fdt_parser`, this code requires std as it is intended
6//! to be only used in usermode.
7
8#![forbid(unsafe_code)]
9
10use anyhow::Context;
11use anyhow::bail;
12use fdt::parser::Node;
13use fdt::parser::Parser;
14use fdt::parser::Property;
15use igvm_defs::MemoryMapEntryType;
16use igvm_defs::dt::IGVM_DT_IGVM_TYPE_PROPERTY;
17use inspect::Inspect;
18use loader_defs::shim::MemoryVtlType;
19use memory_range::MemoryRange;
20use vm_topology::memory::MemoryRangeWithNode;
21use vm_topology::processor::aarch64::GicInfo;
22
23/// A parsed cpu.
24#[derive(Debug, Inspect, Clone, Copy, PartialEq, Eq)]
25pub struct Cpu {
26    /// Architecture specific "reg" value for this CPU. For x64, this is the
27    /// APIC ID. For ARM v8 64-bit, this should match the MPIDR_EL1 register
28    /// affinity bits.
29    pub reg: u64,
30    /// The vnode field of a cpu dt node, which describes the numa node id.
31    pub vnode: u32,
32}
33
34/// Information about a guest memory range.
35#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
36pub struct Memory {
37    /// The range of memory.
38    pub range: MemoryRangeWithNode,
39    /// The VTL this memory is for.
40    #[inspect(debug)]
41    pub vtl_usage: MemoryVtlType,
42    /// The host provided IGVM type for this memory.
43    #[inspect(debug)]
44    pub igvm_type: MemoryMapEntryType,
45}
46
47/// Vtls for mmio.
48#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
49pub enum Vtl {
50    /// VTL0.
51    Vtl0,
52    /// VTL2.
53    Vtl2,
54}
55
56/// Information about guest mmio.
57#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
58pub struct Mmio {
59    /// The address range of mmio.
60    pub range: MemoryRange,
61    /// The VTL this mmio is for.
62    pub vtl: Vtl,
63}
64
65/// Information about a section of the guest's address space.
66#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
67#[inspect(tag = "type")]
68pub enum AddressRange {
69    /// This range describes memory.
70    Memory(#[inspect(flatten)] Memory),
71    /// This range describes mmio.
72    Mmio(#[inspect(flatten)] Mmio),
73}
74
75impl AddressRange {
76    /// The [`MemoryRange`] for this address range.
77    pub fn range(&self) -> &MemoryRange {
78        match self {
79            AddressRange::Memory(memory) => &memory.range.range,
80            AddressRange::Mmio(mmio) => &mmio.range,
81        }
82    }
83
84    /// The [`MemoryVtlType`] for this address range.
85    pub fn vtl_usage(&self) -> MemoryVtlType {
86        match self {
87            AddressRange::Memory(memory) => memory.vtl_usage,
88            AddressRange::Mmio(Mmio { vtl, .. }) => match vtl {
89                Vtl::Vtl0 => MemoryVtlType::VTL0_MMIO,
90                Vtl::Vtl2 => MemoryVtlType::VTL2_MMIO,
91            },
92        }
93    }
94}
95
96/// The isolation type of the partition.
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Inspect)]
98pub enum IsolationType {
99    /// No isolation.
100    None,
101    /// Hyper-V based isolation.
102    Vbs,
103    /// AMD SNP.
104    Snp,
105    /// Intel TDX.
106    Tdx,
107}
108
109/// The memory allocation mode provided by the host. This reports how the
110/// bootloader decided to provide memory for the kernel.
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Inspect)]
112#[inspect(external_tag)]
113pub enum MemoryAllocationMode {
114    /// Use the host provided memory topology, and use VTL2_PROTECTABLE entries
115    /// as VTL2 ram. This is the default if no
116    /// `openhcl/memory-allocation-property` mode is provided by the host.
117    Host,
118    /// Allow VTL2 to select its own ranges from the address space to use for
119    /// memory, with a size provided by the host.
120    Vtl2 {
121        /// The number of bytes VTL2 should allocate for memory for itself.
122        /// Encoded as `openhcl/memory-size` in device tree.
123        #[inspect(hex)]
124        memory_size: Option<u64>,
125        /// The number of bytes VTL2 should allocate for mmio for itself.
126        /// Encoded as `openhcl/mmio-size` in device tree.
127        #[inspect(hex)]
128        mmio_size: Option<u64>,
129    },
130}
131
132/// Information parsed from the device tree provided by openhcl_boot. These
133/// values are trusted, as it's expected that openhcl_boot has already validated
134/// the host provided device tree.
135#[derive(Debug, Inspect, PartialEq, Eq)]
136pub struct ParsedBootDtInfo {
137    /// The cpus in the system. The index in the vector is also the mshv VP
138    /// index.
139    #[inspect(iter_by_index)]
140    pub cpus: Vec<Cpu>,
141    /// The physical address of the VTL0 alias mapping, if one is configured.
142    pub vtl0_alias_map: Option<u64>,
143    /// The memory ranges for VTL2 that were reported to the kernel. This is
144    /// sorted in ascending order.
145    #[inspect(iter_by_index)]
146    pub vtl2_memory: Vec<MemoryRangeWithNode>,
147    /// The unified memory map for the partition, from the bootloader. Sorted in
148    /// ascending order. Note that this includes mmio gaps as well.
149    #[inspect(with = "inspect_helpers::memory_internal")]
150    pub partition_memory_map: Vec<AddressRange>,
151    /// The mmio to report to VTL0.
152    #[inspect(iter_by_index)]
153    pub vtl0_mmio: Vec<MemoryRange>,
154    /// The ranges config regions are stored at.
155    #[inspect(iter_by_index)]
156    pub config_ranges: Vec<MemoryRange>,
157    /// The VTL2 reserved range.
158    pub vtl2_reserved_range: MemoryRange,
159    /// The ranges that were accepted at load time by the host on behalf of the
160    /// guest.
161    #[inspect(iter_by_index)]
162    pub accepted_ranges: Vec<MemoryRange>,
163    /// The memory allocation mode the bootloader decided to use.
164    pub memory_allocation_mode: MemoryAllocationMode,
165    /// The isolation type of the partition.
166    pub isolation: IsolationType,
167    /// VTL2 range for private pool memory.
168    #[inspect(iter_by_index)]
169    pub private_pool_ranges: Vec<MemoryRangeWithNode>,
170
171    /// GIC information, on AArch64.
172    pub gic: Option<GicInfo>,
173    /// PMU GSIV, on AArch64.
174    pub pmu_gsiv: Option<u32>,
175}
176
177fn err_to_owned(e: fdt::parser::Error<'_>) -> anyhow::Error {
178    anyhow::Error::msg(e.to_string())
179}
180
181/// Try to find a given property on a node, returning None if not found. As the
182/// bootloader should be producing a well formed device tree, errors are not
183/// expected and flattened into `None`.
184fn try_find_property<'a>(node: &Node<'a>, name: &str) -> Option<Property<'a>> {
185    node.find_property(name).ok().flatten()
186}
187
188fn address_cells(node: &Node<'_>) -> anyhow::Result<u32> {
189    let prop = try_find_property(node, "#address-cells")
190        .context("missing address cells on {node.name}")?;
191    prop.read_u32(0).map_err(err_to_owned)
192}
193
194fn property_to_u64_vec(node: &Node<'_>, name: &str) -> anyhow::Result<Vec<u64>> {
195    let prop = try_find_property(node, name).context("missing prop {name} on {node.name}")?;
196    Ok(prop
197        .as_64_list()
198        .map_err(err_to_owned)
199        .context("prop {name} is not a list of u64s")?
200        .collect())
201}
202
203struct OpenhclInfo {
204    vtl0_mmio: Vec<MemoryRange>,
205    config_ranges: Vec<MemoryRange>,
206    partition_memory_map: Vec<AddressRange>,
207    accepted_memory: Vec<MemoryRange>,
208    vtl2_reserved_range: MemoryRange,
209    vtl0_alias_map: Option<u64>,
210    memory_allocation_mode: MemoryAllocationMode,
211    isolation: IsolationType,
212    private_pool_ranges: Vec<MemoryRangeWithNode>,
213}
214
215fn parse_memory_openhcl(node: &Node<'_>) -> anyhow::Result<AddressRange> {
216    let vtl_usage = {
217        let prop = try_find_property(node, "openhcl,memory-type")
218            .context(format!("missing openhcl,memory-type on node {}", node.name))?;
219
220        MemoryVtlType(prop.read_u32(0).map_err(err_to_owned).context(format!(
221            "openhcl memory node {} openhcl,memory-type invalid",
222            node.name
223        ))?)
224    };
225
226    if vtl_usage.ram() {
227        // Parse this entry as memory.
228        let range = parse_memory(node).context("unable to parse base memory")?;
229
230        let igvm_type = {
231            let prop = try_find_property(node, IGVM_DT_IGVM_TYPE_PROPERTY)
232                .context(format!("missing igvm type on node {}", node.name))?;
233            let value = prop
234                .read_u32(0)
235                .map_err(err_to_owned)
236                .context(format!("memory node {} invalid igvm type", node.name))?;
237            MemoryMapEntryType(value as u16)
238        };
239
240        Ok(AddressRange::Memory(Memory {
241            range,
242            vtl_usage,
243            igvm_type,
244        }))
245    } else {
246        // Parse this type as just mmio.
247        let range = {
248            let reg = property_to_u64_vec(node, "reg")?;
249
250            if reg.len() != 2 {
251                bail!("mmio node {} does not have 2 u64s", node.name);
252            }
253
254            let base = reg[0];
255            let len = reg[1];
256            MemoryRange::try_new(base..(base + len)).context("invalid mmio range")?
257        };
258
259        let vtl = match vtl_usage {
260            MemoryVtlType::VTL0_MMIO => Vtl::Vtl0,
261            MemoryVtlType::VTL2_MMIO => Vtl::Vtl2,
262            _ => bail!(
263                "invalid vtl_usage {vtl_usage:?} type for mmio node {}",
264                node.name
265            ),
266        };
267
268        Ok(AddressRange::Mmio(Mmio { range, vtl }))
269    }
270}
271
272fn parse_accepted_memory(node: &Node<'_>) -> anyhow::Result<MemoryRange> {
273    let reg = property_to_u64_vec(node, "reg")?;
274
275    if reg.len() != 2 {
276        bail!("accepted memory node {} does not have 2 u64s", node.name);
277    }
278
279    let base = reg[0];
280    let len = reg[1];
281    MemoryRange::try_new(base..(base + len)).context("invalid preaccepted memory")
282}
283
284fn parse_openhcl(node: &Node<'_>) -> anyhow::Result<OpenhclInfo> {
285    let mut memory = Vec::new();
286    let mut accepted_memory = Vec::new();
287
288    for child in node.children() {
289        let child = child.map_err(err_to_owned).context("child invalid")?;
290
291        match child.name {
292            name if name.starts_with("memory@") => {
293                memory.push(parse_memory_openhcl(&child)?);
294            }
295
296            name if name.starts_with("accepted-memory@") => {
297                accepted_memory.push(parse_accepted_memory(&child)?);
298            }
299
300            name if name.starts_with("memory-allocation-mode") => {}
301
302            _ => {
303                // Ignore other nodes.
304            }
305        }
306    }
307
308    let isolation = {
309        let prop = try_find_property(node, "isolation-type").context("missing isolation-type")?;
310
311        match prop.read_str().map_err(err_to_owned)? {
312            "none" => IsolationType::None,
313            "vbs" => IsolationType::Vbs,
314            "snp" => IsolationType::Snp,
315            "tdx" => IsolationType::Tdx,
316            ty => bail!("invalid isolation-type {ty}"),
317        }
318    };
319
320    let memory_allocation_mode = {
321        let prop = try_find_property(node, "memory-allocation-mode")
322            .context("missing memory-allocation-mode")?;
323
324        match prop.read_str().map_err(err_to_owned)? {
325            "host" => MemoryAllocationMode::Host,
326            "vtl2" => {
327                let memory_size = try_find_property(node, "memory-size")
328                    .map(|p| p.read_u64(0))
329                    .transpose()
330                    .map_err(err_to_owned)?;
331
332                let mmio_size = try_find_property(node, "mmio-size")
333                    .map(|p| p.read_u64(0))
334                    .transpose()
335                    .map_err(err_to_owned)?;
336
337                MemoryAllocationMode::Vtl2 {
338                    memory_size,
339                    mmio_size,
340                }
341            }
342            mode => bail!("invalid memory-allocation-mode {mode}"),
343        }
344    };
345
346    memory.sort_by_key(|r| r.range().start());
347    accepted_memory.sort_by_key(|r| r.start());
348
349    // Report config ranges in a separate vec as well, for convenience.
350    let config_ranges = memory
351        .iter()
352        .filter_map(|entry| {
353            if entry.vtl_usage() == MemoryVtlType::VTL2_CONFIG {
354                Some(*entry.range())
355            } else {
356                None
357            }
358        })
359        .collect();
360
361    // Report the reserved range. There should only be one.
362    let vtl2_reserved_range = {
363        let mut reserved_range_iter = memory.iter().filter_map(|entry| {
364            if entry.vtl_usage() == MemoryVtlType::VTL2_RESERVED {
365                Some(*entry.range())
366            } else {
367                None
368            }
369        });
370
371        let reserved_range = reserved_range_iter.next().unwrap_or(MemoryRange::EMPTY);
372
373        if reserved_range_iter.next().is_some() {
374            bail!("multiple VTL2 reserved ranges found");
375        }
376
377        reserved_range
378    };
379
380    // Report private pool ranges in a separate vec, for convenience.
381    let private_pool_ranges = memory
382        .iter()
383        .filter_map(|entry| match entry {
384            AddressRange::Memory(memory) => {
385                if memory.vtl_usage == MemoryVtlType::VTL2_GPA_POOL {
386                    Some(memory.range.clone())
387                } else {
388                    None
389                }
390            }
391            AddressRange::Mmio(_) => None,
392        })
393        .collect();
394
395    let vtl0_alias_map = try_find_property(node, "vtl0-alias-map")
396        .map(|prop| prop.read_u64(0).map_err(err_to_owned))
397        .transpose()
398        .context("unable to read vtl0-alias-map")?;
399
400    // Extract vmbus mmio information from the overall memory map.
401    let vtl0_mmio = memory
402        .iter()
403        .filter_map(|range| match range {
404            AddressRange::Memory(_) => None,
405            AddressRange::Mmio(mmio) => match mmio.vtl {
406                Vtl::Vtl0 => Some(mmio.range),
407                Vtl::Vtl2 => None,
408            },
409        })
410        .collect();
411
412    Ok(OpenhclInfo {
413        vtl0_mmio,
414        config_ranges,
415        partition_memory_map: memory,
416        accepted_memory,
417        vtl2_reserved_range,
418        vtl0_alias_map,
419        memory_allocation_mode,
420        isolation,
421        private_pool_ranges,
422    })
423}
424
425fn parse_cpus(node: &Node<'_>) -> anyhow::Result<Vec<Cpu>> {
426    let address_cells = address_cells(node)?;
427
428    if address_cells > 2 {
429        bail!("cpus address-cells > 2 unexpected");
430    }
431
432    let mut cpus = Vec::new();
433
434    for cpu in node.children() {
435        let cpu = cpu.map_err(err_to_owned).context("cpu invalid")?;
436        let reg = try_find_property(&cpu, "reg").context("{cpu.name} missing reg")?;
437
438        let reg = match address_cells {
439            1 => reg.read_u32(0).map_err(err_to_owned)? as u64,
440            2 => reg.read_u64(0).map_err(err_to_owned)?,
441            _ => unreachable!(),
442        };
443
444        let vnode = try_find_property(&cpu, "numa-node-id")
445            .context("{cpu.name} missing numa-node-id")?
446            .read_u32(0)
447            .map_err(err_to_owned)?;
448
449        cpus.push(Cpu { reg, vnode });
450    }
451
452    Ok(cpus)
453}
454
455/// Parse a single memory node.
456fn parse_memory(node: &Node<'_>) -> anyhow::Result<MemoryRangeWithNode> {
457    let reg = property_to_u64_vec(node, "reg")?;
458
459    if reg.len() != 2 {
460        bail!("memory node {} does not have 2 u64s", node.name);
461    }
462
463    let base = reg[0];
464    let len = reg[1];
465    let numa_node_id = try_find_property(node, "numa-node-id")
466        .context("{node.name} missing numa-node-id")?
467        .read_u32(0)
468        .map_err(err_to_owned)
469        .context("unable to read numa-node-id")?;
470
471    Ok(MemoryRangeWithNode {
472        range: MemoryRange::try_new(base..base + len).context("invalid memory range")?,
473        vnode: numa_node_id,
474    })
475}
476
477/// Parse GIC config
478fn parse_gic(node: &Node<'_>) -> anyhow::Result<GicInfo> {
479    let reg = property_to_u64_vec(node, "reg")?;
480
481    if reg.len() != 4 {
482        bail!("gic node {} does not have 4 u64s", node.name);
483    }
484
485    Ok(GicInfo {
486        gic_distributor_base: reg[0],
487        gic_redistributors_base: reg[2],
488    })
489}
490
491fn parse_pmu(node: &Node<'_>) -> anyhow::Result<u32> {
492    let interrupts =
493        try_find_property(node, "interrupts").context("pmu node missing interrupts")?;
494    let interrupts_typ = interrupts
495        .read_u32(0)
496        .map_err(err_to_owned)
497        .context("missing pmu interrupts typ")?;
498    let interrupts_ppi_index = interrupts
499        .read_u32(1)
500        .map_err(err_to_owned)
501        .context("missing pmu interrupts index")?;
502
503    // The interrupt is expected to be a PPI.
504    const GIC_PPI: u32 = 1;
505    if interrupts_typ != GIC_PPI {
506        bail!("pmu node has unexpected interrupt type {interrupts_typ}");
507    }
508
509    // The index is the index off of the PPI base of 16. PPIs only exist from 16
510    // to 31, so the index should be < 16.
511    if interrupts_ppi_index >= 16 {
512        bail!("pmu node has unexpected interrupt index {interrupts_ppi_index}");
513    }
514
515    const PPI_BASE: u32 = 16;
516    Ok(PPI_BASE + interrupts_ppi_index)
517}
518
519impl ParsedBootDtInfo {
520    /// Read parameters passed via device tree by openhcl_boot, at
521    /// /sys/firmware/fdt.
522    ///
523    /// The device tree is expected to be well formed from the bootloader, so
524    /// any errors here are not expected.
525    pub fn new() -> anyhow::Result<Self> {
526        let raw = fs_err::read("/sys/firmware/fdt").context("reading fdt")?;
527        Self::new_from_raw(&raw)
528    }
529
530    fn new_from_raw(raw: &[u8]) -> anyhow::Result<Self> {
531        let mut cpus = Vec::new();
532        let mut vtl0_mmio = Vec::new();
533        let mut config_ranges = Vec::new();
534        let mut vtl2_memory = Vec::new();
535        let mut gic = None;
536        let mut pmu_gsiv = None;
537        let mut partition_memory_map = Vec::new();
538        let mut accepted_ranges = Vec::new();
539        let mut vtl0_alias_map = None;
540        let mut memory_allocation_mode = MemoryAllocationMode::Host;
541        let mut isolation = IsolationType::None;
542        let mut vtl2_reserved_range = MemoryRange::EMPTY;
543        let mut private_pool_ranges = Vec::new();
544
545        let parser = Parser::new(raw)
546            .map_err(err_to_owned)
547            .context("failed to create fdt parser")?;
548
549        for child in parser
550            .root()
551            .map_err(err_to_owned)
552            .context("root invalid")?
553            .children()
554        {
555            let child = child.map_err(err_to_owned).context("child invalid")?;
556
557            match child.name {
558                "cpus" => {
559                    cpus = parse_cpus(&child)?;
560                }
561
562                "openhcl" => {
563                    let OpenhclInfo {
564                        vtl0_mmio: n_vtl0_mmio,
565                        config_ranges: n_config_ranges,
566                        partition_memory_map: n_partition_memory_map,
567                        vtl2_reserved_range: n_vtl2_reserved_range,
568                        accepted_memory: n_accepted_memory,
569                        vtl0_alias_map: n_vtl0_alias_map,
570                        memory_allocation_mode: n_memory_allocation_mode,
571                        isolation: n_isolation,
572                        private_pool_ranges: n_private_pool_ranges,
573                    } = parse_openhcl(&child)?;
574                    vtl0_mmio = n_vtl0_mmio;
575                    config_ranges = n_config_ranges;
576                    partition_memory_map = n_partition_memory_map;
577                    accepted_ranges = n_accepted_memory;
578                    vtl0_alias_map = n_vtl0_alias_map;
579                    memory_allocation_mode = n_memory_allocation_mode;
580                    isolation = n_isolation;
581                    vtl2_reserved_range = n_vtl2_reserved_range;
582                    private_pool_ranges = n_private_pool_ranges;
583                }
584
585                _ if child.name.starts_with("memory@") => {
586                    vtl2_memory.push(parse_memory(&child)?);
587                }
588
589                _ if child.name.starts_with("intc@") => {
590                    // TODO: make sure we are on aarch64
591                    gic = Some(parse_gic(&child)?);
592                }
593
594                _ if child.name.starts_with("pmu") => {
595                    // TODO: make sure we are on aarch64
596                    pmu_gsiv = Some(parse_pmu(&child)?);
597                }
598
599                _ => {
600                    // Ignore other nodes.
601                }
602            }
603        }
604
605        vtl2_memory.sort_by_key(|r| r.range.start());
606
607        Ok(Self {
608            cpus,
609            vtl0_mmio,
610            config_ranges,
611            vtl2_memory,
612            partition_memory_map,
613            vtl0_alias_map,
614            accepted_ranges,
615            gic,
616            pmu_gsiv,
617            memory_allocation_mode,
618            isolation,
619            vtl2_reserved_range,
620            private_pool_ranges,
621        })
622    }
623}
624
625/// Boot times reported by the bootloader.
626#[derive(Debug, Clone, Copy, PartialEq, Eq)]
627pub struct BootTimes {
628    /// Kernel start time.
629    pub start: Option<u64>,
630    /// Kernel end time.
631    pub end: Option<u64>,
632    /// Sidecar start time.
633    pub sidecar_start: Option<u64>,
634    /// Sidecar end time.
635    pub sidecar_end: Option<u64>,
636}
637
638impl BootTimes {
639    /// Read the boot times passed via device tree by openhcl_boot, at
640    /// /sys/firmware/fdt.
641    ///
642    /// The device tree is expected to be well formed from the bootloader, so
643    /// any errors here are not expected.
644    pub fn new() -> anyhow::Result<Self> {
645        let raw = fs_err::read("/sys/firmware/fdt").context("reading fdt")?;
646        Self::new_from_raw(&raw)
647    }
648
649    fn new_from_raw(raw: &[u8]) -> anyhow::Result<Self> {
650        let mut start = None;
651        let mut end = None;
652        let mut sidecar_start = None;
653        let mut sidecar_end = None;
654        let parser = Parser::new(raw)
655            .map_err(err_to_owned)
656            .context("failed to create fdt parser")?;
657
658        let root = parser
659            .root()
660            .map_err(err_to_owned)
661            .context("root invalid")?;
662
663        if let Some(prop) = try_find_property(&root, "reftime_boot_start") {
664            start = Some(prop.read_u64(0).map_err(err_to_owned)?);
665        }
666
667        if let Some(prop) = try_find_property(&root, "reftime_boot_end") {
668            end = Some(prop.read_u64(0).map_err(err_to_owned)?);
669        }
670
671        if let Some(prop) = try_find_property(&root, "reftime_sidecar_start") {
672            sidecar_start = Some(prop.read_u64(0).map_err(err_to_owned)?);
673        }
674
675        if let Some(prop) = try_find_property(&root, "reftime_sidecar_end") {
676            sidecar_end = Some(prop.read_u64(0).map_err(err_to_owned)?);
677        }
678
679        Ok(Self {
680            start,
681            end,
682            sidecar_start,
683            sidecar_end,
684        })
685    }
686}
687
688mod inspect_helpers {
689    use super::*;
690
691    pub(super) fn memory_internal(ranges: &[AddressRange]) -> impl Inspect + '_ {
692        inspect::iter_by_key(ranges.iter().map(|entry| (entry.range(), entry)))
693    }
694}
695
696#[cfg(test)]
697mod tests {
698    use super::*;
699    use fdt::builder::Builder;
700
701    fn build_dt(info: &ParsedBootDtInfo) -> anyhow::Result<Vec<u8>> {
702        let mut buf = vec![0; 4096];
703
704        let mut builder = Builder::new(fdt::builder::BuilderConfig {
705            blob_buffer: &mut buf,
706            string_table_cap: 1024,
707            memory_reservations: &[],
708        })?;
709        let p_address_cells = builder.add_string("#address-cells")?;
710        let p_size_cells = builder.add_string("#size-cells")?;
711        let p_reg = builder.add_string("reg")?;
712        let p_device_type = builder.add_string("device_type")?;
713        let p_compatible = builder.add_string("compatible")?;
714        let p_ranges = builder.add_string("ranges")?;
715        let p_numa_node_id = builder.add_string("numa-node-id")?;
716        let p_igvm_type = builder.add_string(IGVM_DT_IGVM_TYPE_PROPERTY)?;
717        let p_openhcl_memory = builder.add_string("openhcl,memory-type")?;
718        let p_interrupts = builder.add_string("interrupts")?;
719
720        let mut root_builder = builder
721            .start_node("")?
722            .add_u32(p_address_cells, 2)?
723            .add_u32(p_size_cells, 2)?
724            .add_str(p_compatible, "microsoft,openvmm")?;
725
726        let mut cpu_builder = root_builder
727            .start_node("cpus")?
728            .add_u32(p_address_cells, 1)?
729            .add_u32(p_size_cells, 0)?;
730
731        // add cpus
732        for (index, cpu) in info.cpus.iter().enumerate() {
733            let name = format!("cpu@{}", index + 1);
734
735            cpu_builder = cpu_builder
736                .start_node(&name)?
737                .add_str(p_device_type, "cpu")?
738                .add_u32(p_reg, cpu.reg as u32)?
739                .add_u32(p_numa_node_id, cpu.vnode)?
740                .end_node()?;
741        }
742
743        root_builder = cpu_builder.end_node()?;
744
745        // add memory, in reverse order.
746        for memory in info.vtl2_memory.iter().rev() {
747            let name = format!("memory@{:x}", memory.range.start());
748
749            root_builder = root_builder
750                .start_node(&name)?
751                .add_str(p_device_type, "memory")?
752                .add_u64_list(p_reg, [memory.range.start(), memory.range.len()])?
753                .add_u32(p_numa_node_id, memory.vnode)?
754                .end_node()?;
755        }
756
757        // GIC
758        if let Some(gic) = info.gic {
759            let p_interrupt_cells = root_builder.add_string("#interrupt-cells")?;
760            let p_redist_regions = root_builder.add_string("#redistributor-regions")?;
761            let p_redist_stride = root_builder.add_string("redistributor-stride")?;
762            let p_interrupt_controller = root_builder.add_string("interrupt-controller")?;
763            let p_phandle = root_builder.add_string("phandle")?;
764            let name = format!("intc@{}", gic.gic_distributor_base);
765            root_builder = root_builder
766                .start_node(name.as_ref())?
767                .add_str(p_compatible, "arm,gic-v3")?
768                .add_u32(p_redist_regions, 1)?
769                .add_u64(p_redist_stride, 0)?
770                .add_u64_array(
771                    p_reg,
772                    &[gic.gic_distributor_base, 0, gic.gic_redistributors_base, 0],
773                )?
774                .add_u32(p_address_cells, 2)?
775                .add_u32(p_size_cells, 2)?
776                .add_u32(p_interrupt_cells, 3)?
777                .add_null(p_interrupt_controller)?
778                .add_u32(p_phandle, 1)?
779                .add_null(p_ranges)?
780                .end_node()?;
781        }
782
783        // PMU
784        if let Some(pmu_gsiv) = info.pmu_gsiv {
785            assert!((16..32).contains(&pmu_gsiv));
786            const GIC_PPI: u32 = 1;
787            const IRQ_TYPE_LEVEL_HIGH: u32 = 4;
788            root_builder = root_builder
789                .start_node("pmu")?
790                .add_str(p_compatible, "arm,armv8-pmuv3")?
791                .add_u32_array(p_interrupts, &[GIC_PPI, pmu_gsiv - 16, IRQ_TYPE_LEVEL_HIGH])?
792                .end_node()?;
793        }
794
795        let mut openhcl_builder = root_builder.start_node("openhcl")?;
796        let p_isolation_type = openhcl_builder.add_string("isolation-type")?;
797        openhcl_builder = openhcl_builder.add_str(
798            p_isolation_type,
799            match info.isolation {
800                IsolationType::None => "none",
801                IsolationType::Vbs => "vbs",
802                IsolationType::Snp => "snp",
803                IsolationType::Tdx => "tdx",
804            },
805        )?;
806
807        let p_memory_allocation_mode = openhcl_builder.add_string("memory-allocation-mode")?;
808        match info.memory_allocation_mode {
809            MemoryAllocationMode::Host => {
810                openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "host")?;
811            }
812            MemoryAllocationMode::Vtl2 {
813                memory_size,
814                mmio_size,
815            } => {
816                let p_memory_size = openhcl_builder.add_string("memory-size")?;
817                let p_mmio_size = openhcl_builder.add_string("mmio-size")?;
818                openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "vtl2")?;
819                if let Some(memory_size) = memory_size {
820                    openhcl_builder = openhcl_builder.add_u64(p_memory_size, memory_size)?;
821                }
822                if let Some(mmio_size) = mmio_size {
823                    openhcl_builder = openhcl_builder.add_u64(p_mmio_size, mmio_size)?;
824                }
825            }
826        }
827
828        if let Some(data) = info.vtl0_alias_map {
829            let p_vtl0_alias_map = openhcl_builder.add_string("vtl0-alias-map")?;
830            openhcl_builder = openhcl_builder.add_u64(p_vtl0_alias_map, data)?;
831        }
832
833        openhcl_builder = openhcl_builder
834            .start_node("vmbus-vtl0")?
835            .add_u32(p_address_cells, 2)?
836            .add_u32(p_size_cells, 2)?
837            .add_str(p_compatible, "microsoft,vmbus")?
838            .add_u64_list(
839                p_ranges,
840                info.vtl0_mmio
841                    .iter()
842                    .flat_map(|r| [r.start(), r.start(), r.len()]),
843            )?
844            .end_node()?;
845
846        for range in &info.partition_memory_map {
847            let name = format!("memory@{:x}", range.range().start());
848
849            let node_builder = openhcl_builder
850                .start_node(&name)?
851                .add_str(p_device_type, "memory-openhcl")?
852                .add_u64_list(p_reg, [range.range().start(), range.range().len()])?
853                .add_u32(p_openhcl_memory, range.vtl_usage().0)?;
854
855            openhcl_builder = match range {
856                AddressRange::Memory(memory) => {
857                    // Add as a memory node, with numa info and igvm type.
858                    node_builder
859                        .add_u32(p_numa_node_id, memory.range.vnode)?
860                        .add_u32(p_igvm_type, memory.igvm_type.0 as u32)?
861                }
862                AddressRange::Mmio(_) => {
863                    // Nothing to do here, mmio already contains the min
864                    // required info of range and vtl via vtl_usage.
865                    node_builder
866                }
867            }
868            .end_node()?;
869        }
870
871        for range in &info.accepted_ranges {
872            let name = format!("accepted-memory@{:x}", range.start());
873
874            openhcl_builder = openhcl_builder
875                .start_node(&name)?
876                .add_str(p_device_type, "memory-openhcl")?
877                .add_u64_list(p_reg, [range.start(), range.len()])?
878                .end_node()?;
879        }
880
881        root_builder = openhcl_builder.end_node()?;
882
883        root_builder.end_node()?.build(info.cpus[0].reg as u32)?;
884
885        Ok(buf)
886    }
887
888    #[test]
889    fn test_basic() {
890        let orig_info = ParsedBootDtInfo {
891            cpus: (0..4).map(|i| Cpu { reg: i, vnode: 0 }).collect(),
892            vtl2_memory: vec![
893                MemoryRangeWithNode {
894                    range: MemoryRange::new(0x10000..0x20000),
895                    vnode: 0,
896                },
897                MemoryRangeWithNode {
898                    range: MemoryRange::new(0x20000..0x30000),
899                    vnode: 1,
900                },
901            ],
902            partition_memory_map: vec![
903                AddressRange::Memory(Memory {
904                    range: MemoryRangeWithNode {
905                        range: MemoryRange::new(0..0x1000),
906                        vnode: 0,
907                    },
908                    vtl_usage: MemoryVtlType::VTL0,
909                    igvm_type: MemoryMapEntryType::MEMORY,
910                }),
911                AddressRange::Mmio(Mmio {
912                    range: MemoryRange::new(0x1000..0x2000),
913                    vtl: Vtl::Vtl0,
914                }),
915                AddressRange::Mmio(Mmio {
916                    range: MemoryRange::new(0x3000..0x4000),
917                    vtl: Vtl::Vtl0,
918                }),
919                AddressRange::Memory(Memory {
920                    range: MemoryRangeWithNode {
921                        range: MemoryRange::new(0x10000..0x20000),
922                        vnode: 0,
923                    },
924                    vtl_usage: MemoryVtlType::VTL2_RAM,
925                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
926                }),
927                AddressRange::Memory(Memory {
928                    range: MemoryRangeWithNode {
929                        range: MemoryRange::new(0x20000..0x30000),
930                        vnode: 1,
931                    },
932                    vtl_usage: MemoryVtlType::VTL2_CONFIG,
933                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
934                }),
935                AddressRange::Memory(Memory {
936                    range: MemoryRangeWithNode {
937                        range: MemoryRange::new(0x30000..0x40000),
938                        vnode: 1,
939                    },
940                    vtl_usage: MemoryVtlType::VTL2_CONFIG,
941                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
942                }),
943                AddressRange::Memory(Memory {
944                    range: MemoryRangeWithNode {
945                        range: MemoryRange::new(0x40000..0x50000),
946                        vnode: 1,
947                    },
948                    vtl_usage: MemoryVtlType::VTL2_RESERVED,
949                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
950                }),
951                AddressRange::Memory(Memory {
952                    range: MemoryRangeWithNode {
953                        range: MemoryRange::new(0x60000..0x70000),
954                        vnode: 0,
955                    },
956                    vtl_usage: MemoryVtlType::VTL2_GPA_POOL,
957                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
958                }),
959                AddressRange::Memory(Memory {
960                    range: MemoryRangeWithNode {
961                        range: MemoryRange::new(0x1000000..0x2000000),
962                        vnode: 0,
963                    },
964                    vtl_usage: MemoryVtlType::VTL0,
965                    igvm_type: MemoryMapEntryType::MEMORY,
966                }),
967                AddressRange::Mmio(Mmio {
968                    range: MemoryRange::new(0x3000000..0x4000000),
969                    vtl: Vtl::Vtl2,
970                }),
971            ],
972            vtl0_mmio: vec![
973                MemoryRange::new(0x1000..0x2000),
974                MemoryRange::new(0x3000..0x4000),
975            ],
976            config_ranges: vec![
977                MemoryRange::new(0x20000..0x30000),
978                MemoryRange::new(0x30000..0x40000),
979            ],
980            vtl0_alias_map: Some(1 << 48),
981            gic: Some(GicInfo {
982                gic_distributor_base: 0x10000,
983                gic_redistributors_base: 0x20000,
984            }),
985            pmu_gsiv: Some(0x17),
986            accepted_ranges: vec![
987                MemoryRange::new(0x10000..0x20000),
988                MemoryRange::new(0x1000000..0x1500000),
989            ],
990            memory_allocation_mode: MemoryAllocationMode::Vtl2 {
991                memory_size: Some(0x1000),
992                mmio_size: Some(0x2000),
993            },
994            isolation: IsolationType::Vbs,
995            vtl2_reserved_range: MemoryRange::new(0x40000..0x50000),
996            private_pool_ranges: vec![MemoryRangeWithNode {
997                range: MemoryRange::new(0x60000..0x70000),
998                vnode: 0,
999            }],
1000        };
1001
1002        let dt = build_dt(&orig_info).unwrap();
1003        let parsed = ParsedBootDtInfo::new_from_raw(&dt).unwrap();
1004
1005        assert_eq!(orig_info, parsed);
1006    }
1007
1008    fn build_boottime_dt(boot_times: BootTimes) -> anyhow::Result<Vec<u8>> {
1009        let mut buf = vec![0; 4096];
1010
1011        let mut builder = Builder::new(fdt::builder::BuilderConfig {
1012            blob_buffer: &mut buf,
1013            string_table_cap: 1024,
1014            memory_reservations: &[],
1015        })?;
1016        let p_address_cells = builder.add_string("#address-cells")?;
1017        let p_size_cells = builder.add_string("#size-cells")?;
1018        let p_reftime_boot_start = builder.add_string("reftime_boot_start")?;
1019        let p_reftime_boot_end = builder.add_string("reftime_boot_end")?;
1020        let p_reftime_sidecar_start = builder.add_string("reftime_sidecar_start")?;
1021        let p_reftime_sidecar_end = builder.add_string("reftime_sidecar_end")?;
1022
1023        let mut root_builder = builder
1024            .start_node("")?
1025            .add_u32(p_address_cells, 2)?
1026            .add_u32(p_size_cells, 2)?;
1027
1028        if let Some(start) = boot_times.start {
1029            root_builder = root_builder.add_u64(p_reftime_boot_start, start)?;
1030        }
1031
1032        if let Some(end) = boot_times.end {
1033            root_builder = root_builder.add_u64(p_reftime_boot_end, end)?;
1034        }
1035
1036        if let Some(start) = boot_times.sidecar_start {
1037            root_builder = root_builder.add_u64(p_reftime_sidecar_start, start)?;
1038        }
1039
1040        if let Some(end) = boot_times.sidecar_end {
1041            root_builder = root_builder.add_u64(p_reftime_sidecar_end, end)?;
1042        }
1043
1044        root_builder.end_node()?.build(0)?;
1045
1046        Ok(buf)
1047    }
1048
1049    #[test]
1050    fn test_basic_boottime() {
1051        let orig_info = BootTimes {
1052            start: Some(0x1000),
1053            end: Some(0x2000),
1054            sidecar_start: Some(0x3000),
1055            sidecar_end: Some(0x4000),
1056        };
1057
1058        let dt = build_boottime_dt(orig_info).unwrap();
1059        let parsed = BootTimes::new_from_raw(&dt).unwrap();
1060
1061        assert_eq!(orig_info, parsed);
1062
1063        // test no boot times.
1064        let orig_info = BootTimes {
1065            start: None,
1066            end: None,
1067            sidecar_start: None,
1068            sidecar_end: None,
1069        };
1070
1071        let dt = build_boottime_dt(orig_info).unwrap();
1072        let parsed = BootTimes::new_from_raw(&dt).unwrap();
1073
1074        assert_eq!(orig_info, parsed);
1075    }
1076}