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 memory range that contains the persisted header describing the
160    /// persisted protobuf region.
161    pub vtl2_persisted_header: MemoryRange,
162    /// The memory range that contains the persisted protobuf region.
163    pub vtl2_persisted_protobuf_region: MemoryRange,
164    /// The ranges that were accepted at load time by the host on behalf of the
165    /// guest.
166    #[inspect(iter_by_index)]
167    pub accepted_ranges: Vec<MemoryRange>,
168    /// The memory allocation mode the bootloader decided to use.
169    pub memory_allocation_mode: MemoryAllocationMode,
170    /// The isolation type of the partition.
171    pub isolation: IsolationType,
172    /// VTL2 range for private pool memory.
173    #[inspect(iter_by_index)]
174    pub private_pool_ranges: Vec<MemoryRangeWithNode>,
175
176    /// GIC information, on AArch64.
177    pub gic: Option<GicInfo>,
178    /// PMU GSIV, on AArch64.
179    pub pmu_gsiv: Option<u32>,
180}
181
182fn err_to_owned(e: fdt::parser::Error<'_>) -> anyhow::Error {
183    anyhow::Error::msg(e.to_string())
184}
185
186/// Try to find a given property on a node, returning None if not found. As the
187/// bootloader should be producing a well formed device tree, errors are not
188/// expected and flattened into `None`.
189fn try_find_property<'a>(node: &Node<'a>, name: &str) -> Option<Property<'a>> {
190    node.find_property(name).ok().flatten()
191}
192
193fn address_cells(node: &Node<'_>) -> anyhow::Result<u32> {
194    let prop = try_find_property(node, "#address-cells")
195        .context("missing address cells on {node.name}")?;
196    prop.read_u32(0).map_err(err_to_owned)
197}
198
199fn property_to_u64_vec(node: &Node<'_>, name: &str) -> anyhow::Result<Vec<u64>> {
200    let prop = try_find_property(node, name).context("missing prop {name} on {node.name}")?;
201    Ok(prop
202        .as_64_list()
203        .map_err(err_to_owned)
204        .context("prop {name} is not a list of u64s")?
205        .collect())
206}
207
208struct OpenhclInfo {
209    vtl0_mmio: Vec<MemoryRange>,
210    config_ranges: Vec<MemoryRange>,
211    partition_memory_map: Vec<AddressRange>,
212    accepted_memory: Vec<MemoryRange>,
213    vtl2_reserved_range: MemoryRange,
214    vtl0_alias_map: Option<u64>,
215    memory_allocation_mode: MemoryAllocationMode,
216    isolation: IsolationType,
217    private_pool_ranges: Vec<MemoryRangeWithNode>,
218    vtl2_persisted_header: MemoryRange,
219    vtl2_persisted_protobuf_region: MemoryRange,
220}
221
222fn parse_memory_openhcl(node: &Node<'_>) -> anyhow::Result<AddressRange> {
223    let vtl_usage = {
224        let prop = try_find_property(node, "openhcl,memory-type")
225            .context(format!("missing openhcl,memory-type on node {}", node.name))?;
226
227        MemoryVtlType(prop.read_u32(0).map_err(err_to_owned).context(format!(
228            "openhcl memory node {} openhcl,memory-type invalid",
229            node.name
230        ))?)
231    };
232
233    if vtl_usage.ram() {
234        // Parse this entry as memory.
235        let range = parse_memory(node).context("unable to parse base memory")?;
236
237        let igvm_type = {
238            let prop = try_find_property(node, IGVM_DT_IGVM_TYPE_PROPERTY)
239                .context(format!("missing igvm type on node {}", node.name))?;
240            let value = prop
241                .read_u32(0)
242                .map_err(err_to_owned)
243                .context(format!("memory node {} invalid igvm type", node.name))?;
244            MemoryMapEntryType(value as u16)
245        };
246
247        Ok(AddressRange::Memory(Memory {
248            range,
249            vtl_usage,
250            igvm_type,
251        }))
252    } else {
253        // Parse this type as just mmio.
254        let range = {
255            let reg = property_to_u64_vec(node, "reg")?;
256
257            if reg.len() != 2 {
258                bail!("mmio node {} does not have 2 u64s", node.name);
259            }
260
261            let base = reg[0];
262            let len = reg[1];
263            MemoryRange::try_new(base..(base + len)).context("invalid mmio range")?
264        };
265
266        let vtl = match vtl_usage {
267            MemoryVtlType::VTL0_MMIO => Vtl::Vtl0,
268            MemoryVtlType::VTL2_MMIO => Vtl::Vtl2,
269            _ => bail!(
270                "invalid vtl_usage {vtl_usage:?} type for mmio node {}",
271                node.name
272            ),
273        };
274
275        Ok(AddressRange::Mmio(Mmio { range, vtl }))
276    }
277}
278
279fn parse_accepted_memory(node: &Node<'_>) -> anyhow::Result<MemoryRange> {
280    let reg = property_to_u64_vec(node, "reg")?;
281
282    if reg.len() != 2 {
283        bail!("accepted memory node {} does not have 2 u64s", node.name);
284    }
285
286    let base = reg[0];
287    let len = reg[1];
288    MemoryRange::try_new(base..(base + len)).context("invalid preaccepted memory")
289}
290
291fn parse_openhcl(node: &Node<'_>) -> anyhow::Result<OpenhclInfo> {
292    let mut memory = Vec::new();
293    let mut accepted_memory = Vec::new();
294
295    for child in node.children() {
296        let child = child.map_err(err_to_owned).context("child invalid")?;
297
298        match child.name {
299            name if name.starts_with("memory@") => {
300                memory.push(parse_memory_openhcl(&child)?);
301            }
302
303            name if name.starts_with("accepted-memory@") => {
304                accepted_memory.push(parse_accepted_memory(&child)?);
305            }
306
307            name if name.starts_with("memory-allocation-mode") => {}
308
309            _ => {
310                // Ignore other nodes.
311            }
312        }
313    }
314
315    let isolation = {
316        let prop = try_find_property(node, "isolation-type").context("missing isolation-type")?;
317
318        match prop.read_str().map_err(err_to_owned)? {
319            "none" => IsolationType::None,
320            "vbs" => IsolationType::Vbs,
321            "snp" => IsolationType::Snp,
322            "tdx" => IsolationType::Tdx,
323            ty => bail!("invalid isolation-type {ty}"),
324        }
325    };
326
327    let memory_allocation_mode = {
328        let prop = try_find_property(node, "memory-allocation-mode")
329            .context("missing memory-allocation-mode")?;
330
331        match prop.read_str().map_err(err_to_owned)? {
332            "host" => MemoryAllocationMode::Host,
333            "vtl2" => {
334                let memory_size = try_find_property(node, "memory-size")
335                    .map(|p| p.read_u64(0))
336                    .transpose()
337                    .map_err(err_to_owned)?;
338
339                let mmio_size = try_find_property(node, "mmio-size")
340                    .map(|p| p.read_u64(0))
341                    .transpose()
342                    .map_err(err_to_owned)?;
343
344                MemoryAllocationMode::Vtl2 {
345                    memory_size,
346                    mmio_size,
347                }
348            }
349            mode => bail!("invalid memory-allocation-mode {mode}"),
350        }
351    };
352
353    memory.sort_by_key(|r| r.range().start());
354    accepted_memory.sort_by_key(|r| r.start());
355
356    // Report config ranges in a separate vec as well, for convenience.
357    let config_ranges = memory
358        .iter()
359        .filter_map(|entry| {
360            if entry.vtl_usage() == MemoryVtlType::VTL2_CONFIG {
361                Some(*entry.range())
362            } else {
363                None
364            }
365        })
366        .collect();
367
368    // Report the reserved range. There should only be one.
369    let vtl2_reserved_range = {
370        let mut reserved_range_iter = memory.iter().filter_map(|entry| {
371            if entry.vtl_usage() == MemoryVtlType::VTL2_RESERVED {
372                Some(*entry.range())
373            } else {
374                None
375            }
376        });
377
378        let reserved_range = reserved_range_iter.next().unwrap_or(MemoryRange::EMPTY);
379
380        if reserved_range_iter.next().is_some() {
381            bail!("multiple VTL2 reserved ranges found");
382        }
383
384        reserved_range
385    };
386
387    // Report private pool ranges in a separate vec, for convenience.
388    let private_pool_ranges = memory
389        .iter()
390        .filter_map(|entry| match entry {
391            AddressRange::Memory(memory) => {
392                if memory.vtl_usage == MemoryVtlType::VTL2_GPA_POOL {
393                    Some(memory.range.clone())
394                } else {
395                    None
396                }
397            }
398            AddressRange::Mmio(_) => None,
399        })
400        .collect();
401
402    // Report the persisted header range.
403    let vtl2_persisted_header = {
404        let mut header_iter = memory.iter().filter_map(|entry| {
405            if entry.vtl_usage() == MemoryVtlType::VTL2_PERSISTED_STATE_HEADER {
406                Some(*entry.range())
407            } else {
408                None
409            }
410        });
411
412        let header = header_iter
413            .next()
414            .ok_or(anyhow::anyhow!("no VTL2 persisted header range found"))?;
415
416        if header_iter.next().is_some() {
417            bail!("multiple VTL2 persisted header ranges found");
418        }
419
420        header
421    };
422
423    // Report the persisted protobuf region range.
424    let vtl2_persisted_protobuf_region = {
425        let mut region_iter = memory.iter().filter_map(|entry| {
426            if entry.vtl_usage() == MemoryVtlType::VTL2_PERSISTED_STATE_PROTOBUF {
427                Some(*entry.range())
428            } else {
429                None
430            }
431        });
432
433        let region = region_iter.next().ok_or(anyhow::anyhow!(
434            "no VTL2 persisted protobuf region range found"
435        ))?;
436
437        if region_iter.next().is_some() {
438            bail!("multiple VTL2 persisted protobuf region ranges found");
439        }
440
441        region
442    };
443
444    let vtl0_alias_map = try_find_property(node, "vtl0-alias-map")
445        .map(|prop| prop.read_u64(0).map_err(err_to_owned))
446        .transpose()
447        .context("unable to read vtl0-alias-map")?;
448
449    // Extract vmbus mmio information from the overall memory map.
450    let vtl0_mmio = memory
451        .iter()
452        .filter_map(|range| match range {
453            AddressRange::Memory(_) => None,
454            AddressRange::Mmio(mmio) => match mmio.vtl {
455                Vtl::Vtl0 => Some(mmio.range),
456                Vtl::Vtl2 => None,
457            },
458        })
459        .collect();
460
461    Ok(OpenhclInfo {
462        vtl0_mmio,
463        config_ranges,
464        partition_memory_map: memory,
465        accepted_memory,
466        vtl2_reserved_range,
467        vtl0_alias_map,
468        memory_allocation_mode,
469        isolation,
470        private_pool_ranges,
471        vtl2_persisted_header,
472        vtl2_persisted_protobuf_region,
473    })
474}
475
476fn parse_cpus(node: &Node<'_>) -> anyhow::Result<Vec<Cpu>> {
477    let address_cells = address_cells(node)?;
478
479    if address_cells > 2 {
480        bail!("cpus address-cells > 2 unexpected");
481    }
482
483    let mut cpus = Vec::new();
484
485    for cpu in node.children() {
486        let cpu = cpu.map_err(err_to_owned).context("cpu invalid")?;
487        let reg = try_find_property(&cpu, "reg").context("{cpu.name} missing reg")?;
488
489        let reg = match address_cells {
490            1 => reg.read_u32(0).map_err(err_to_owned)? as u64,
491            2 => reg.read_u64(0).map_err(err_to_owned)?,
492            _ => unreachable!(),
493        };
494
495        let vnode = try_find_property(&cpu, "numa-node-id")
496            .context("{cpu.name} missing numa-node-id")?
497            .read_u32(0)
498            .map_err(err_to_owned)?;
499
500        cpus.push(Cpu { reg, vnode });
501    }
502
503    Ok(cpus)
504}
505
506/// Parse a single memory node.
507fn parse_memory(node: &Node<'_>) -> anyhow::Result<MemoryRangeWithNode> {
508    let reg = property_to_u64_vec(node, "reg")?;
509
510    if reg.len() != 2 {
511        bail!("memory node {} does not have 2 u64s", node.name);
512    }
513
514    let base = reg[0];
515    let len = reg[1];
516    let numa_node_id = try_find_property(node, "numa-node-id")
517        .context(format!("{} missing numa-node-id", node.name))?
518        .read_u32(0)
519        .map_err(err_to_owned)
520        .context("unable to read numa-node-id")?;
521
522    Ok(MemoryRangeWithNode {
523        range: MemoryRange::try_new(base..base + len).context("invalid memory range")?,
524        vnode: numa_node_id,
525    })
526}
527
528/// Parse GIC config
529fn parse_gic(node: &Node<'_>) -> anyhow::Result<GicInfo> {
530    let reg = property_to_u64_vec(node, "reg")?;
531
532    if reg.len() != 4 {
533        bail!("gic node {} does not have 4 u64s", node.name);
534    }
535
536    Ok(GicInfo {
537        gic_distributor_base: reg[0],
538        gic_redistributors_base: reg[2],
539    })
540}
541
542fn parse_pmu(node: &Node<'_>) -> anyhow::Result<u32> {
543    let interrupts =
544        try_find_property(node, "interrupts").context("pmu node missing interrupts")?;
545    let interrupts_typ = interrupts
546        .read_u32(0)
547        .map_err(err_to_owned)
548        .context("missing pmu interrupts typ")?;
549    let interrupts_ppi_index = interrupts
550        .read_u32(1)
551        .map_err(err_to_owned)
552        .context("missing pmu interrupts index")?;
553
554    // The interrupt is expected to be a PPI.
555    const GIC_PPI: u32 = 1;
556    if interrupts_typ != GIC_PPI {
557        bail!("pmu node has unexpected interrupt type {interrupts_typ}");
558    }
559
560    // The index is the index off of the PPI base of 16. PPIs only exist from 16
561    // to 31, so the index should be < 16.
562    if interrupts_ppi_index >= 16 {
563        bail!("pmu node has unexpected interrupt index {interrupts_ppi_index}");
564    }
565
566    const PPI_BASE: u32 = 16;
567    Ok(PPI_BASE + interrupts_ppi_index)
568}
569
570impl ParsedBootDtInfo {
571    /// Read parameters passed via device tree by openhcl_boot, at
572    /// /sys/firmware/fdt.
573    ///
574    /// The device tree is expected to be well formed from the bootloader, so
575    /// any errors here are not expected.
576    pub fn new() -> anyhow::Result<Self> {
577        let raw = fs_err::read("/sys/firmware/fdt").context("reading fdt")?;
578        Self::new_from_raw(&raw)
579    }
580
581    fn new_from_raw(raw: &[u8]) -> anyhow::Result<Self> {
582        let mut cpus = Vec::new();
583        let mut vtl0_mmio = Vec::new();
584        let mut config_ranges = Vec::new();
585        let mut vtl2_memory = Vec::new();
586        let mut gic = None;
587        let mut pmu_gsiv = None;
588        let mut partition_memory_map = Vec::new();
589        let mut accepted_ranges = Vec::new();
590        let mut vtl0_alias_map = None;
591        let mut memory_allocation_mode = MemoryAllocationMode::Host;
592        let mut isolation = IsolationType::None;
593        let mut vtl2_reserved_range = MemoryRange::EMPTY;
594        let mut private_pool_ranges = Vec::new();
595        let mut vtl2_persisted_header = MemoryRange::EMPTY;
596        let mut vtl2_persisted_protobuf_region = MemoryRange::EMPTY;
597
598        let parser = Parser::new(raw)
599            .map_err(err_to_owned)
600            .context("failed to create fdt parser")?;
601
602        for child in parser
603            .root()
604            .map_err(err_to_owned)
605            .context("root invalid")?
606            .children()
607        {
608            let child = child.map_err(err_to_owned).context("child invalid")?;
609
610            match child.name {
611                "cpus" => {
612                    cpus = parse_cpus(&child)?;
613                }
614
615                "openhcl" => {
616                    let OpenhclInfo {
617                        vtl0_mmio: n_vtl0_mmio,
618                        config_ranges: n_config_ranges,
619                        partition_memory_map: n_partition_memory_map,
620                        vtl2_reserved_range: n_vtl2_reserved_range,
621                        accepted_memory: n_accepted_memory,
622                        vtl0_alias_map: n_vtl0_alias_map,
623                        memory_allocation_mode: n_memory_allocation_mode,
624                        isolation: n_isolation,
625                        private_pool_ranges: n_private_pool_ranges,
626                        vtl2_persisted_header: n_vtl2_persisted_header,
627                        vtl2_persisted_protobuf_region: n_vtl2_persisted_protobuf_region,
628                    } = parse_openhcl(&child)?;
629                    vtl0_mmio = n_vtl0_mmio;
630                    config_ranges = n_config_ranges;
631                    partition_memory_map = n_partition_memory_map;
632                    accepted_ranges = n_accepted_memory;
633                    vtl0_alias_map = n_vtl0_alias_map;
634                    memory_allocation_mode = n_memory_allocation_mode;
635                    isolation = n_isolation;
636                    vtl2_reserved_range = n_vtl2_reserved_range;
637                    private_pool_ranges = n_private_pool_ranges;
638                    vtl2_persisted_header = n_vtl2_persisted_header;
639                    vtl2_persisted_protobuf_region = n_vtl2_persisted_protobuf_region;
640                }
641
642                _ if child.name.starts_with("memory@") => {
643                    vtl2_memory.push(parse_memory(&child)?);
644                }
645
646                _ if child.name.starts_with("intc@") => {
647                    // TODO: make sure we are on aarch64
648                    gic = Some(parse_gic(&child)?);
649                }
650
651                _ if child.name.starts_with("pmu") => {
652                    // TODO: make sure we are on aarch64
653                    pmu_gsiv = Some(parse_pmu(&child)?);
654                }
655
656                _ => {
657                    // Ignore other nodes.
658                }
659            }
660        }
661
662        vtl2_memory.sort_by_key(|r| r.range.start());
663
664        Ok(Self {
665            cpus,
666            vtl0_mmio,
667            config_ranges,
668            vtl2_memory,
669            partition_memory_map,
670            vtl0_alias_map,
671            accepted_ranges,
672            gic,
673            pmu_gsiv,
674            memory_allocation_mode,
675            isolation,
676            vtl2_reserved_range,
677            private_pool_ranges,
678            vtl2_persisted_header,
679            vtl2_persisted_protobuf_region,
680        })
681    }
682}
683
684/// Boot times reported by the bootloader.
685#[derive(Debug, Clone, Copy, PartialEq, Eq)]
686pub struct BootTimes {
687    /// Kernel start time.
688    pub start: Option<u64>,
689    /// Kernel end time.
690    pub end: Option<u64>,
691    /// Sidecar start time.
692    pub sidecar_start: Option<u64>,
693    /// Sidecar end time.
694    pub sidecar_end: Option<u64>,
695}
696
697impl BootTimes {
698    /// Read the boot times passed via device tree by openhcl_boot, at
699    /// /sys/firmware/fdt.
700    ///
701    /// The device tree is expected to be well formed from the bootloader, so
702    /// any errors here are not expected.
703    pub fn new() -> anyhow::Result<Self> {
704        let raw = fs_err::read("/sys/firmware/fdt").context("reading fdt")?;
705        Self::new_from_raw(&raw)
706    }
707
708    fn new_from_raw(raw: &[u8]) -> anyhow::Result<Self> {
709        let mut start = None;
710        let mut end = None;
711        let mut sidecar_start = None;
712        let mut sidecar_end = None;
713        let parser = Parser::new(raw)
714            .map_err(err_to_owned)
715            .context("failed to create fdt parser")?;
716
717        let root = parser
718            .root()
719            .map_err(err_to_owned)
720            .context("root invalid")?;
721
722        if let Some(prop) = try_find_property(&root, "reftime_boot_start") {
723            start = Some(prop.read_u64(0).map_err(err_to_owned)?);
724        }
725
726        if let Some(prop) = try_find_property(&root, "reftime_boot_end") {
727            end = Some(prop.read_u64(0).map_err(err_to_owned)?);
728        }
729
730        if let Some(prop) = try_find_property(&root, "reftime_sidecar_start") {
731            sidecar_start = Some(prop.read_u64(0).map_err(err_to_owned)?);
732        }
733
734        if let Some(prop) = try_find_property(&root, "reftime_sidecar_end") {
735            sidecar_end = Some(prop.read_u64(0).map_err(err_to_owned)?);
736        }
737
738        Ok(Self {
739            start,
740            end,
741            sidecar_start,
742            sidecar_end,
743        })
744    }
745}
746
747mod inspect_helpers {
748    use super::*;
749
750    pub(super) fn memory_internal(ranges: &[AddressRange]) -> impl Inspect + '_ {
751        inspect::iter_by_key(ranges.iter().map(|entry| (entry.range(), entry)))
752    }
753}
754
755#[cfg(test)]
756mod tests {
757    use super::*;
758    use fdt::builder::Builder;
759
760    fn build_dt(info: &ParsedBootDtInfo) -> anyhow::Result<Vec<u8>> {
761        let mut buf = vec![0; 4096];
762
763        let mut builder = Builder::new(fdt::builder::BuilderConfig {
764            blob_buffer: &mut buf,
765            string_table_cap: 1024,
766            memory_reservations: &[],
767        })?;
768        let p_address_cells = builder.add_string("#address-cells")?;
769        let p_size_cells = builder.add_string("#size-cells")?;
770        let p_reg = builder.add_string("reg")?;
771        let p_device_type = builder.add_string("device_type")?;
772        let p_compatible = builder.add_string("compatible")?;
773        let p_ranges = builder.add_string("ranges")?;
774        let p_numa_node_id = builder.add_string("numa-node-id")?;
775        let p_igvm_type = builder.add_string(IGVM_DT_IGVM_TYPE_PROPERTY)?;
776        let p_openhcl_memory = builder.add_string("openhcl,memory-type")?;
777        let p_interrupts = builder.add_string("interrupts")?;
778
779        let mut root_builder = builder
780            .start_node("")?
781            .add_u32(p_address_cells, 2)?
782            .add_u32(p_size_cells, 2)?
783            .add_str(p_compatible, "microsoft,openvmm")?;
784
785        let mut cpu_builder = root_builder
786            .start_node("cpus")?
787            .add_u32(p_address_cells, 1)?
788            .add_u32(p_size_cells, 0)?;
789
790        // add cpus
791        for (index, cpu) in info.cpus.iter().enumerate() {
792            let name = format!("cpu@{}", index + 1);
793
794            cpu_builder = cpu_builder
795                .start_node(&name)?
796                .add_str(p_device_type, "cpu")?
797                .add_u32(p_reg, cpu.reg as u32)?
798                .add_u32(p_numa_node_id, cpu.vnode)?
799                .end_node()?;
800        }
801
802        root_builder = cpu_builder.end_node()?;
803
804        // add memory, in reverse order.
805        for memory in info.vtl2_memory.iter().rev() {
806            let name = format!("memory@{:x}", memory.range.start());
807
808            root_builder = root_builder
809                .start_node(&name)?
810                .add_str(p_device_type, "memory")?
811                .add_u64_list(p_reg, [memory.range.start(), memory.range.len()])?
812                .add_u32(p_numa_node_id, memory.vnode)?
813                .end_node()?;
814        }
815
816        // GIC
817        if let Some(gic) = info.gic {
818            let p_interrupt_cells = root_builder.add_string("#interrupt-cells")?;
819            let p_redist_regions = root_builder.add_string("#redistributor-regions")?;
820            let p_redist_stride = root_builder.add_string("redistributor-stride")?;
821            let p_interrupt_controller = root_builder.add_string("interrupt-controller")?;
822            let p_phandle = root_builder.add_string("phandle")?;
823            let name = format!("intc@{}", gic.gic_distributor_base);
824            root_builder = root_builder
825                .start_node(name.as_ref())?
826                .add_str(p_compatible, "arm,gic-v3")?
827                .add_u32(p_redist_regions, 1)?
828                .add_u64(p_redist_stride, 0)?
829                .add_u64_array(
830                    p_reg,
831                    &[gic.gic_distributor_base, 0, gic.gic_redistributors_base, 0],
832                )?
833                .add_u32(p_address_cells, 2)?
834                .add_u32(p_size_cells, 2)?
835                .add_u32(p_interrupt_cells, 3)?
836                .add_null(p_interrupt_controller)?
837                .add_u32(p_phandle, 1)?
838                .add_null(p_ranges)?
839                .end_node()?;
840        }
841
842        // PMU
843        if let Some(pmu_gsiv) = info.pmu_gsiv {
844            assert!((16..32).contains(&pmu_gsiv));
845            const GIC_PPI: u32 = 1;
846            const IRQ_TYPE_LEVEL_HIGH: u32 = 4;
847            root_builder = root_builder
848                .start_node("pmu")?
849                .add_str(p_compatible, "arm,armv8-pmuv3")?
850                .add_u32_array(p_interrupts, &[GIC_PPI, pmu_gsiv - 16, IRQ_TYPE_LEVEL_HIGH])?
851                .end_node()?;
852        }
853
854        let mut openhcl_builder = root_builder.start_node("openhcl")?;
855        let p_isolation_type = openhcl_builder.add_string("isolation-type")?;
856        openhcl_builder = openhcl_builder.add_str(
857            p_isolation_type,
858            match info.isolation {
859                IsolationType::None => "none",
860                IsolationType::Vbs => "vbs",
861                IsolationType::Snp => "snp",
862                IsolationType::Tdx => "tdx",
863            },
864        )?;
865
866        let p_memory_allocation_mode = openhcl_builder.add_string("memory-allocation-mode")?;
867        match info.memory_allocation_mode {
868            MemoryAllocationMode::Host => {
869                openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "host")?;
870            }
871            MemoryAllocationMode::Vtl2 {
872                memory_size,
873                mmio_size,
874            } => {
875                let p_memory_size = openhcl_builder.add_string("memory-size")?;
876                let p_mmio_size = openhcl_builder.add_string("mmio-size")?;
877                openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "vtl2")?;
878                if let Some(memory_size) = memory_size {
879                    openhcl_builder = openhcl_builder.add_u64(p_memory_size, memory_size)?;
880                }
881                if let Some(mmio_size) = mmio_size {
882                    openhcl_builder = openhcl_builder.add_u64(p_mmio_size, mmio_size)?;
883                }
884            }
885        }
886
887        if let Some(data) = info.vtl0_alias_map {
888            let p_vtl0_alias_map = openhcl_builder.add_string("vtl0-alias-map")?;
889            openhcl_builder = openhcl_builder.add_u64(p_vtl0_alias_map, data)?;
890        }
891
892        openhcl_builder = openhcl_builder
893            .start_node("vmbus-vtl0")?
894            .add_u32(p_address_cells, 2)?
895            .add_u32(p_size_cells, 2)?
896            .add_str(p_compatible, "microsoft,vmbus")?
897            .add_u64_list(
898                p_ranges,
899                info.vtl0_mmio
900                    .iter()
901                    .flat_map(|r| [r.start(), r.start(), r.len()]),
902            )?
903            .end_node()?;
904
905        for range in &info.partition_memory_map {
906            let name = format!("memory@{:x}", range.range().start());
907
908            let node_builder = openhcl_builder
909                .start_node(&name)?
910                .add_str(p_device_type, "memory-openhcl")?
911                .add_u64_list(p_reg, [range.range().start(), range.range().len()])?
912                .add_u32(p_openhcl_memory, range.vtl_usage().0)?;
913
914            openhcl_builder = match range {
915                AddressRange::Memory(memory) => {
916                    // Add as a memory node, with numa info and igvm type.
917                    node_builder
918                        .add_u32(p_numa_node_id, memory.range.vnode)?
919                        .add_u32(p_igvm_type, memory.igvm_type.0 as u32)?
920                }
921                AddressRange::Mmio(_) => {
922                    // Nothing to do here, mmio already contains the min
923                    // required info of range and vtl via vtl_usage.
924                    node_builder
925                }
926            }
927            .end_node()?;
928        }
929
930        for range in &info.accepted_ranges {
931            let name = format!("accepted-memory@{:x}", range.start());
932
933            openhcl_builder = openhcl_builder
934                .start_node(&name)?
935                .add_str(p_device_type, "memory-openhcl")?
936                .add_u64_list(p_reg, [range.start(), range.len()])?
937                .end_node()?;
938        }
939
940        root_builder = openhcl_builder.end_node()?;
941
942        root_builder.end_node()?.build(info.cpus[0].reg as u32)?;
943
944        Ok(buf)
945    }
946
947    #[test]
948    fn test_basic() {
949        let orig_info = ParsedBootDtInfo {
950            cpus: (0..4).map(|i| Cpu { reg: i, vnode: 0 }).collect(),
951            vtl2_memory: vec![
952                MemoryRangeWithNode {
953                    range: MemoryRange::new(0x10000..0x20000),
954                    vnode: 0,
955                },
956                MemoryRangeWithNode {
957                    range: MemoryRange::new(0x20000..0x30000),
958                    vnode: 1,
959                },
960            ],
961            partition_memory_map: vec![
962                AddressRange::Memory(Memory {
963                    range: MemoryRangeWithNode {
964                        range: MemoryRange::new(0..0x1000),
965                        vnode: 0,
966                    },
967                    vtl_usage: MemoryVtlType::VTL0,
968                    igvm_type: MemoryMapEntryType::MEMORY,
969                }),
970                AddressRange::Mmio(Mmio {
971                    range: MemoryRange::new(0x1000..0x2000),
972                    vtl: Vtl::Vtl0,
973                }),
974                AddressRange::Mmio(Mmio {
975                    range: MemoryRange::new(0x3000..0x4000),
976                    vtl: Vtl::Vtl0,
977                }),
978                AddressRange::Memory(Memory {
979                    range: MemoryRangeWithNode {
980                        range: MemoryRange::new(0x10000..0x20000),
981                        vnode: 0,
982                    },
983                    vtl_usage: MemoryVtlType::VTL2_RAM,
984                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
985                }),
986                AddressRange::Memory(Memory {
987                    range: MemoryRangeWithNode {
988                        range: MemoryRange::new(0x20000..0x30000),
989                        vnode: 1,
990                    },
991                    vtl_usage: MemoryVtlType::VTL2_CONFIG,
992                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
993                }),
994                AddressRange::Memory(Memory {
995                    range: MemoryRangeWithNode {
996                        range: MemoryRange::new(0x30000..0x40000),
997                        vnode: 1,
998                    },
999                    vtl_usage: MemoryVtlType::VTL2_CONFIG,
1000                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1001                }),
1002                AddressRange::Memory(Memory {
1003                    range: MemoryRangeWithNode {
1004                        range: MemoryRange::new(0x40000..0x50000),
1005                        vnode: 1,
1006                    },
1007                    vtl_usage: MemoryVtlType::VTL2_RESERVED,
1008                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1009                }),
1010                AddressRange::Memory(Memory {
1011                    range: MemoryRangeWithNode {
1012                        range: MemoryRange::new(0x50000..0x51000),
1013                        vnode: 0,
1014                    },
1015                    vtl_usage: MemoryVtlType::VTL2_PERSISTED_STATE_HEADER,
1016                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1017                }),
1018                AddressRange::Memory(Memory {
1019                    range: MemoryRangeWithNode {
1020                        range: MemoryRange::new(0x51000..0x52000),
1021                        vnode: 0,
1022                    },
1023                    vtl_usage: MemoryVtlType::VTL2_PERSISTED_STATE_PROTOBUF,
1024                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1025                }),
1026                AddressRange::Memory(Memory {
1027                    range: MemoryRangeWithNode {
1028                        range: MemoryRange::new(0x60000..0x70000),
1029                        vnode: 0,
1030                    },
1031                    vtl_usage: MemoryVtlType::VTL2_GPA_POOL,
1032                    igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1033                }),
1034                AddressRange::Memory(Memory {
1035                    range: MemoryRangeWithNode {
1036                        range: MemoryRange::new(0x1000000..0x2000000),
1037                        vnode: 0,
1038                    },
1039                    vtl_usage: MemoryVtlType::VTL0,
1040                    igvm_type: MemoryMapEntryType::MEMORY,
1041                }),
1042                AddressRange::Mmio(Mmio {
1043                    range: MemoryRange::new(0x3000000..0x4000000),
1044                    vtl: Vtl::Vtl2,
1045                }),
1046            ],
1047            vtl0_mmio: vec![
1048                MemoryRange::new(0x1000..0x2000),
1049                MemoryRange::new(0x3000..0x4000),
1050            ],
1051            config_ranges: vec![
1052                MemoryRange::new(0x20000..0x30000),
1053                MemoryRange::new(0x30000..0x40000),
1054            ],
1055            vtl0_alias_map: Some(1 << 48),
1056            gic: Some(GicInfo {
1057                gic_distributor_base: 0x10000,
1058                gic_redistributors_base: 0x20000,
1059            }),
1060            pmu_gsiv: Some(0x17),
1061            accepted_ranges: vec![
1062                MemoryRange::new(0x10000..0x20000),
1063                MemoryRange::new(0x1000000..0x1500000),
1064            ],
1065            memory_allocation_mode: MemoryAllocationMode::Vtl2 {
1066                memory_size: Some(0x1000),
1067                mmio_size: Some(0x2000),
1068            },
1069            isolation: IsolationType::Vbs,
1070            vtl2_reserved_range: MemoryRange::new(0x40000..0x50000),
1071            private_pool_ranges: vec![MemoryRangeWithNode {
1072                range: MemoryRange::new(0x60000..0x70000),
1073                vnode: 0,
1074            }],
1075            vtl2_persisted_header: MemoryRange::new(0x50000..0x51000),
1076            vtl2_persisted_protobuf_region: MemoryRange::new(0x51000..0x52000),
1077        };
1078
1079        let dt = build_dt(&orig_info).unwrap();
1080        let parsed = ParsedBootDtInfo::new_from_raw(&dt).unwrap();
1081
1082        assert_eq!(orig_info, parsed);
1083    }
1084
1085    fn build_boottime_dt(boot_times: BootTimes) -> anyhow::Result<Vec<u8>> {
1086        let mut buf = vec![0; 4096];
1087
1088        let mut builder = Builder::new(fdt::builder::BuilderConfig {
1089            blob_buffer: &mut buf,
1090            string_table_cap: 1024,
1091            memory_reservations: &[],
1092        })?;
1093        let p_address_cells = builder.add_string("#address-cells")?;
1094        let p_size_cells = builder.add_string("#size-cells")?;
1095        let p_reftime_boot_start = builder.add_string("reftime_boot_start")?;
1096        let p_reftime_boot_end = builder.add_string("reftime_boot_end")?;
1097        let p_reftime_sidecar_start = builder.add_string("reftime_sidecar_start")?;
1098        let p_reftime_sidecar_end = builder.add_string("reftime_sidecar_end")?;
1099
1100        let mut root_builder = builder
1101            .start_node("")?
1102            .add_u32(p_address_cells, 2)?
1103            .add_u32(p_size_cells, 2)?;
1104
1105        if let Some(start) = boot_times.start {
1106            root_builder = root_builder.add_u64(p_reftime_boot_start, start)?;
1107        }
1108
1109        if let Some(end) = boot_times.end {
1110            root_builder = root_builder.add_u64(p_reftime_boot_end, end)?;
1111        }
1112
1113        if let Some(start) = boot_times.sidecar_start {
1114            root_builder = root_builder.add_u64(p_reftime_sidecar_start, start)?;
1115        }
1116
1117        if let Some(end) = boot_times.sidecar_end {
1118            root_builder = root_builder.add_u64(p_reftime_sidecar_end, end)?;
1119        }
1120
1121        root_builder.end_node()?.build(0)?;
1122
1123        Ok(buf)
1124    }
1125
1126    #[test]
1127    fn test_basic_boottime() {
1128        let orig_info = BootTimes {
1129            start: Some(0x1000),
1130            end: Some(0x2000),
1131            sidecar_start: Some(0x3000),
1132            sidecar_end: Some(0x4000),
1133        };
1134
1135        let dt = build_boottime_dt(orig_info).unwrap();
1136        let parsed = BootTimes::new_from_raw(&dt).unwrap();
1137
1138        assert_eq!(orig_info, parsed);
1139
1140        // test no boot times.
1141        let orig_info = BootTimes {
1142            start: None,
1143            end: None,
1144            sidecar_start: None,
1145            sidecar_end: None,
1146        };
1147
1148        let dt = build_boottime_dt(orig_info).unwrap();
1149        let parsed = BootTimes::new_from_raw(&dt).unwrap();
1150
1151        assert_eq!(orig_info, parsed);
1152    }
1153}