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