openhcl_boot/
dt.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Module used to write the device tree used by the OpenHCL kernel and
5//! usermode.
6
7use crate::host_params::COMMAND_LINE_SIZE;
8use crate::host_params::PartitionInfo;
9use crate::host_params::shim_params::IsolationType;
10use crate::memory::AddressSpaceManager;
11use crate::memory::MAX_RESERVED_MEM_RANGES;
12use crate::sidecar::SidecarConfig;
13use crate::single_threaded::off_stack;
14use arrayvec::ArrayString;
15use arrayvec::ArrayVec;
16use core::fmt;
17use core::ops::Range;
18use fdt::builder::Builder;
19use fdt::builder::StringId;
20use host_fdt_parser::GicInfo;
21use host_fdt_parser::MemoryAllocationMode;
22use host_fdt_parser::VmbusInfo;
23use hvdef::Vtl;
24use igvm_defs::dt::IGVM_DT_IGVM_TYPE_PROPERTY;
25use loader_defs::shim::MemoryVtlType;
26use memory_range::MemoryRange;
27use memory_range::RangeWalkResult;
28use memory_range::walk_ranges;
29#[cfg(target_arch = "x86_64")]
30use x86defs::tdx::RESET_VECTOR_PAGE;
31
32/// AArch64 defines
33mod aarch64 {
34    // For compatibility with older hosts, use these legacy Hyper-V defaults if
35    // GIC addresses aren't passed in via the host device tree
36    pub const DEFAULT_GIC_DISTRIBUTOR_BASE: u64 = 0xFFFF_0000;
37    pub const DEFAULT_GIC_REDISTRIBUTORS_BASE: u64 = 0xEFFE_E000;
38
39    // The interrupt IDs (INTID's) in the ARM64 DeviceTree must be _relative_
40    // to its base. See `gic_irq_domain_translate` in the Linux kernel, could not
41    // find a specification for that.
42    //
43    // Architecturally, PPIs occupy INTID's in the [16..32) range. In DeviceTree,
44    // the type of the interrupt is specified first (PPI) and then the _relative_ INTID:
45    // for PPI INTID `27` `[GIC_PPI, 27-16, flags]` goes into the DT description.
46    /// VMBus PPI offset for the DT `interrupts` property.
47    /// Canonical INTID is DEFAULT_VMBUS_PPI (18) in openvmm_defs.
48    pub const VMBUS_PPI_OFFSET: u32 = 2;
49    pub const TIMER_INTID: u32 = 4; // Note: the hardware INTID will be 16 + 4
50
51    /// The Hyper-V default PMU_GSIV value.
52    pub const PMU_GSIV: u32 = 0x17;
53    pub const PMU_GSIV_INT_INDEX: u32 = PMU_GSIV - 16;
54
55    pub const GIC_PHANDLE: u32 = 1;
56    pub const GIC_PPI: u32 = 1;
57    pub const IRQ_TYPE_EDGE_RISING: u32 = 1;
58    pub const IRQ_TYPE_LEVEL_LOW: u32 = 8;
59    pub const IRQ_TYPE_LEVEL_HIGH: u32 = 4;
60}
61
62#[derive(Debug)]
63pub enum DtError {
64    // Field is stored solely for logging via debug, not actually dead.
65    Fdt(#[expect(dead_code)] fdt::builder::Error),
66}
67
68impl From<fdt::builder::Error> for DtError {
69    fn from(err: fdt::builder::Error) -> Self {
70        DtError::Fdt(err)
71    }
72}
73
74macro_rules! format_fixed {
75    ($n:expr, $($arg:tt)*) => {
76        {
77            let mut buf = ArrayString::<$n>::new();
78            fmt::write(&mut buf, format_args!($($arg)*)).unwrap();
79            buf
80        }
81    };
82}
83
84pub struct BootTimes {
85    pub start: u64,
86    pub end: u64,
87}
88
89/// Info needed about the current device tree being built to add the vmbus node.
90#[derive(Clone, Copy)]
91pub struct VmbusDeviceTreeInfo {
92    p_address_cells: StringId,
93    p_size_cells: StringId,
94    p_compatible: StringId,
95    p_ranges: StringId,
96    p_vtl: StringId,
97    p_vmbus_connection_id: StringId,
98    p_dma_coherent: StringId,
99    p_interrupt_parent: StringId,
100    p_interrupts: StringId,
101    interrupt_cell_value: Option<u32>,
102}
103
104/// Write a vmbus node to the device tree.
105fn write_vmbus<'a, T>(
106    parent: Builder<'a, T>,
107    name: &str,
108    vtl: Vtl,
109    vmbus: &VmbusInfo,
110    dt: VmbusDeviceTreeInfo,
111) -> Result<Builder<'a, T>, DtError> {
112    let VmbusDeviceTreeInfo {
113        p_address_cells,
114        p_size_cells,
115        p_compatible,
116        p_ranges,
117        p_vtl,
118        p_vmbus_connection_id,
119        p_dma_coherent,
120        p_interrupt_parent,
121        p_interrupts,
122        interrupt_cell_value,
123    } = dt;
124
125    let mut vmbus_builder = parent
126        .start_node(name)?
127        .add_u32(p_address_cells, 2)?
128        .add_u32(p_size_cells, 2)?
129        .add_null(p_dma_coherent)?
130        .add_str(p_compatible, "microsoft,vmbus")?
131        .add_u32(p_vtl, u8::from(vtl).into())?
132        .add_u32(p_vmbus_connection_id, vmbus.connection_id)?;
133
134    let mut mmio_ranges = ArrayVec::<u64, 6>::new();
135    for entry in vmbus.mmio.iter() {
136        mmio_ranges
137            .try_extend_from_slice(&[entry.start(), entry.start(), entry.len()])
138            .expect("should always fit");
139    }
140    vmbus_builder = vmbus_builder.add_u64_array(p_ranges, mmio_ranges.as_slice())?;
141
142    if cfg!(target_arch = "aarch64") {
143        vmbus_builder = vmbus_builder
144            .add_u32(p_interrupt_parent, aarch64::GIC_PHANDLE)?
145            .add_u32_array(
146                p_interrupts,
147                // Here 3 parameters are used as the "#interrupt-cells"
148                // above specifies.
149                &[
150                    aarch64::GIC_PPI,
151                    aarch64::VMBUS_PPI_OFFSET,
152                    interrupt_cell_value.expect("must be set on aarch64"),
153                ],
154            )?;
155    }
156
157    Ok(vmbus_builder.end_node()?)
158}
159
160/// Writes the device tree blob into `buffer`.
161pub fn write_dt(
162    buffer: &mut [u8],
163    partition_info: &PartitionInfo,
164    address_space: &AddressSpaceManager,
165    accepted_ranges: impl IntoIterator<Item = MemoryRange>,
166    initrd: Range<u64>,
167    cmdline: &ArrayString<COMMAND_LINE_SIZE>,
168    sidecar: Option<&SidecarConfig<'_>>,
169    boot_times: Option<BootTimes>,
170    #[cfg_attr(target_arch = "aarch64", expect(unused_variables))]
171    // isolation_type is unused on aarch64
172    isolation_type: IsolationType,
173) -> Result<(), DtError> {
174    // First, the reservation map is built. That keyes off of the x86 E820 memory map.
175    // The `/memreserve/` is used to tell the kernel that the reserved memory is RAM
176    // but it is reserved. That way the kernel allows mapping it via `/dev/mem` without
177    // inhibiting the cache thus disabling the unaligned access on some architectures.
178
179    let mut memory_reservations =
180        off_stack!(ArrayVec<fdt::ReserveEntry, MAX_RESERVED_MEM_RANGES>, ArrayVec::new_const());
181
182    memory_reservations.extend(address_space.reserved_vtl2_ranges().map(|(r, _)| {
183        fdt::ReserveEntry {
184            address: r.start().into(),
185            size: r.len().into(),
186        }
187    }));
188
189    // Build the actual device tree.
190    let builder_config = fdt::builder::BuilderConfig {
191        blob_buffer: buffer,
192        string_table_cap: 1024,
193        memory_reservations: &memory_reservations,
194    };
195    let mut builder = Builder::new(builder_config)?;
196
197    // These StringIds are common across many nodes.
198    let p_address_cells = builder.add_string("#address-cells")?;
199    let p_size_cells = builder.add_string("#size-cells")?;
200    let p_reg = builder.add_string("reg")?;
201    let p_reg_names = builder.add_string("reg-names")?;
202    let p_device_type = builder.add_string("device_type")?;
203    let p_status = builder.add_string("status")?;
204    let p_compatible = builder.add_string("compatible")?;
205    let p_ranges = builder.add_string("ranges")?;
206    let p_numa_node_id = builder.add_string("numa-node-id")?;
207    let p_reftime_boot_start = builder.add_string("reftime_boot_start")?;
208    let p_reftime_boot_end = builder.add_string("reftime_boot_end")?;
209    let p_reftime_sidecar_start = builder.add_string("reftime_sidecar_start")?;
210    let p_reftime_sidecar_end = builder.add_string("reftime_sidecar_end")?;
211    let p_vtl = builder.add_string(igvm_defs::dt::IGVM_DT_VTL_PROPERTY)?;
212    let p_vmbus_connection_id = builder.add_string("microsoft,message-connection-id")?;
213    let p_dma_coherent = builder.add_string("dma-coherent")?;
214    let p_igvm_type = builder.add_string(IGVM_DT_IGVM_TYPE_PROPERTY)?;
215    let p_openhcl_memory = builder.add_string("openhcl,memory-type")?;
216
217    // These StringIds are used across multiple AArch64 nodes.
218    //
219    // TODO: If we add support for an associative map based add_string/add_prop
220    // interface to the fdt builder, these explicit definitions would go away.
221    // That would require either alloc support, or an alloc-free associative
222    // datastructure.
223    let p_interrupt_parent = builder.add_string("interrupt-parent")?;
224    let p_interrupts = builder.add_string("interrupts")?;
225    let p_enable_method = builder.add_string("enable-method")?;
226
227    let num_cpus = partition_info.cpus.len();
228
229    let mut root_builder = builder
230        .start_node("")?
231        .add_u32(p_address_cells, 2)?
232        .add_u32(p_size_cells, 2)?
233        .add_str(p_compatible, "microsoft,openvmm")?;
234
235    if let Some(boot_times) = boot_times {
236        let BootTimes { start, end } = boot_times;
237        root_builder = root_builder
238            .add_u64(p_reftime_boot_start, start)?
239            .add_u64(p_reftime_boot_end, end)?;
240    }
241
242    if let Some(sidecar) = sidecar {
243        root_builder = root_builder
244            .add_u64(p_reftime_sidecar_start, sidecar.start_reftime)?
245            .add_u64(p_reftime_sidecar_end, sidecar.end_reftime)?;
246    }
247
248    let hypervisor_builder = root_builder
249        .start_node("hypervisor")?
250        .add_str(p_compatible, "microsoft,hyperv")?;
251    root_builder = hypervisor_builder.end_node()?;
252
253    #[cfg(target_arch = "x86_64")]
254    if isolation_type == IsolationType::Tdx {
255        let mut mailbox_builder = root_builder
256            .start_node("reserved-memory")?
257            .add_u32(p_address_cells, 2)?
258            .add_u32(p_size_cells, 1)?
259            .add_null(p_ranges)?;
260
261        let name = format_fixed!(32, "wakeup_table@{:x}", RESET_VECTOR_PAGE);
262        let mailbox_addr_builder = mailbox_builder
263            .start_node(name.as_ref())?
264            .add_str(p_compatible, "intel,wakeup-mailbox")?
265            .add_u32_array(p_reg, &[0x0, RESET_VECTOR_PAGE.try_into().unwrap(), 0x1000])?;
266
267        mailbox_builder = mailbox_addr_builder.end_node()?;
268
269        root_builder = mailbox_builder.end_node()?;
270    }
271
272    // For ARM v8, always specify two register cells, which can accommodate
273    // higher number of VPs.
274    let address_cells = if cfg!(target_arch = "aarch64") { 2 } else { 1 };
275    let mut cpu_builder = root_builder
276        .start_node("cpus")?
277        .add_u32(p_address_cells, address_cells)?
278        .add_u32(p_size_cells, 0)?;
279
280    // Add a CPU node for each cpu.
281    for (vp_index, cpu_entry) in partition_info.cpus.iter().enumerate() {
282        let name = format_fixed!(32, "cpu@{}", vp_index + 1);
283
284        let mut cpu = cpu_builder
285            .start_node(name.as_ref())?
286            .add_str(p_device_type, "cpu")?
287            .add_u32(p_numa_node_id, cpu_entry.vnode)?;
288
289        if cfg!(target_arch = "aarch64") {
290            cpu = cpu
291                .add_u64(p_reg, cpu_entry.reg)?
292                .add_str(p_compatible, "arm,arm-v8")?;
293
294            if num_cpus > 1 {
295                cpu = cpu.add_str(p_enable_method, "psci")?;
296            }
297
298            if vp_index == 0 {
299                cpu = cpu.add_str(p_status, "okay")?;
300            } else {
301                cpu = cpu.add_str(p_status, "disabled")?;
302            }
303        } else {
304            cpu = cpu
305                .add_u32(p_reg, cpu_entry.reg as u32)?
306                .add_str(p_status, "okay")?;
307        }
308
309        cpu_builder = cpu.end_node()?;
310    }
311    root_builder = cpu_builder.end_node()?;
312
313    if cfg!(target_arch = "aarch64") {
314        let p_method = root_builder.add_string("method")?;
315        let p_cpu_off = root_builder.add_string("cpu_off")?;
316        let p_cpu_on = root_builder.add_string("cpu_on")?;
317        let psci = root_builder
318            .start_node("psci")?
319            .add_str(p_compatible, "arm,psci-0.2")?
320            .add_str(p_method, "hvc")?
321            .add_u32(p_cpu_off, 1)?
322            .add_u32(p_cpu_on, 2)?;
323        root_builder = psci.end_node()?;
324    }
325
326    // Add a memory node for each VTL2 range.
327    for mem_entry in partition_info.vtl2_ram.iter() {
328        let name = format_fixed!(32, "memory@{:x}", mem_entry.range.start());
329        let mut mem = root_builder.start_node(&name)?;
330        mem = mem.add_str(p_device_type, "memory")?;
331        mem = mem.add_u64_array(p_reg, &[mem_entry.range.start(), mem_entry.range.len()])?;
332        mem = mem.add_u32(p_numa_node_id, mem_entry.vnode)?;
333        root_builder = mem.end_node()?;
334    }
335
336    if cfg!(target_arch = "aarch64") {
337        // ARM64 Generic Interrupt Controller aka GIC, v3.
338
339        // Use legacy Hyper-V defaults if not specified in the host device tree.
340        let default = GicInfo {
341            gic_distributor_base: aarch64::DEFAULT_GIC_DISTRIBUTOR_BASE,
342            gic_distributor_size: aarch64defs::GIC_DISTRIBUTOR_SIZE,
343            gic_redistributors_base: aarch64::DEFAULT_GIC_REDISTRIBUTORS_BASE,
344            gic_redistributors_size: aarch64defs::GIC_REDISTRIBUTOR_SIZE * num_cpus as u64,
345            gic_redistributor_stride: aarch64defs::GIC_REDISTRIBUTOR_SIZE,
346        };
347        let gic = partition_info.gic.as_ref().unwrap_or(&default);
348
349        // Validate sizes
350        assert_eq!(gic.gic_distributor_size, default.gic_distributor_size);
351        assert_eq!(gic.gic_redistributors_size, default.gic_redistributors_size);
352        assert_eq!(
353            gic.gic_redistributor_stride,
354            default.gic_redistributor_stride
355        );
356
357        let p_interrupt_cells = root_builder.add_string("#interrupt-cells")?;
358        let p_redist_regions = root_builder.add_string("#redistributor-regions")?;
359        let p_redist_stride = root_builder.add_string("redistributor-stride")?;
360        let p_interrupt_controller = root_builder.add_string("interrupt-controller")?;
361        let p_phandle = root_builder.add_string("phandle")?;
362        let p_interrupt_names = root_builder.add_string("interrupt-names")?;
363        let p_always_on = root_builder.add_string("always-on")?;
364        let name = format_fixed!(32, "intc@{}", gic.gic_distributor_base);
365        let gicv3 = root_builder
366            .start_node(name.as_ref())?
367            .add_str(p_compatible, "arm,gic-v3")?
368            .add_u32(p_redist_regions, 1)?
369            .add_u64(p_redist_stride, gic.gic_redistributor_stride)?
370            .add_u64_array(
371                p_reg,
372                &[
373                    gic.gic_distributor_base,
374                    gic.gic_distributor_size,
375                    gic.gic_redistributors_base,
376                    gic.gic_redistributors_size,
377                ],
378            )?
379            .add_u32(p_address_cells, 2)?
380            .add_u32(p_size_cells, 2)?
381            .add_u32(p_interrupt_cells, 3)?
382            .add_null(p_interrupt_controller)?
383            .add_u32(p_phandle, aarch64::GIC_PHANDLE)?
384            .add_null(p_ranges)?;
385        root_builder = gicv3.end_node()?;
386
387        // ARM64 Architectural Timer.
388        let timer = root_builder
389            .start_node("timer")?
390            .add_str(p_compatible, "arm,armv8-timer")?
391            .add_u32(p_interrupt_parent, aarch64::GIC_PHANDLE)?
392            .add_str(p_interrupt_names, "virt")?
393            .add_u32_array(
394                p_interrupts,
395                // Here 3 parameters are used as the "#interrupt-cells"
396                // above specifies. The only interrupt employed is
397                // the one for the virtualized environment, it is a
398                // Private Peripheral Interrupt.
399                &[
400                    aarch64::GIC_PPI,
401                    aarch64::TIMER_INTID,
402                    aarch64::IRQ_TYPE_LEVEL_LOW,
403                ],
404            )?
405            .add_null(p_always_on)?;
406        root_builder = timer.end_node()?;
407
408        // Add PMU.
409        //
410        // TODO: This may be insufficient to get perf to work as the kernel
411        // prints:
412        // `armv8-pmu pmu: hw perfevents: no irqs for PMU, sampling events not supported`
413        //
414        // Tracked by issue 1808.
415        //
416        // NOTE: The host may not provide this value in device tree, so use the
417        // Hyper-V platform default if not provided.
418        let pmu_gsiv_index = partition_info
419            .pmu_gsiv
420            .map(|gsiv| {
421                assert!(
422                    (16..32).contains(&gsiv),
423                    "PMU GSIV must be a PPI in [16, 32) range"
424                );
425                gsiv - 16
426            })
427            .unwrap_or(aarch64::PMU_GSIV_INT_INDEX);
428        let pmu = root_builder
429            .start_node("pmu")?
430            .add_str(p_compatible, "arm,armv8-pmuv3")?
431            .add_u32_array(
432                p_interrupts,
433                &[
434                    aarch64::GIC_PPI,
435                    pmu_gsiv_index,
436                    aarch64::IRQ_TYPE_LEVEL_HIGH,
437                ],
438            )?;
439        root_builder = pmu.end_node()?;
440    }
441
442    // Linux requires vmbus to be under a simple-bus node.
443    let mut simple_bus_builder = root_builder
444        .start_node("bus")?
445        .add_str(p_compatible, "simple-bus")?
446        .add_u32(p_address_cells, 2)?
447        .add_u32(p_size_cells, 2)?;
448    simple_bus_builder = simple_bus_builder.add_prop_array(p_ranges, &[])?;
449
450    let vmbus_info = VmbusDeviceTreeInfo {
451        p_address_cells,
452        p_size_cells,
453        p_compatible,
454        p_ranges,
455        p_vtl,
456        p_vmbus_connection_id,
457        p_dma_coherent,
458        p_interrupt_parent,
459        p_interrupts,
460        interrupt_cell_value: if cfg!(target_arch = "aarch64") {
461            Some(aarch64::IRQ_TYPE_EDGE_RISING)
462        } else {
463            None
464        },
465    };
466
467    simple_bus_builder = write_vmbus(
468        simple_bus_builder,
469        "vmbus",
470        Vtl::Vtl2,
471        &partition_info.vmbus_vtl2,
472        vmbus_info,
473    )?;
474
475    if let Some(sidecar) = sidecar {
476        for node in sidecar.nodes {
477            let name = format_fixed!(64, "sidecar@{:x}", node.control_page);
478            simple_bus_builder = simple_bus_builder
479                .start_node(&name)?
480                .add_str(p_compatible, "microsoft,openhcl-sidecar")?
481                .add_u64_array(
482                    p_reg,
483                    &[
484                        node.control_page,
485                        sidecar_defs::PAGE_SIZE as u64,
486                        node.shmem_pages_base,
487                        node.shmem_pages_size,
488                    ],
489                )?
490                .add_str_array(p_reg_names, &["ctrl", "shmem"])?
491                .end_node()?;
492        }
493    }
494
495    root_builder = simple_bus_builder.end_node()?;
496
497    if cfg!(target_arch = "aarch64") {
498        let p_bootargs = root_builder.add_string("bootargs")?;
499        let p_initrd_start = root_builder.add_string("linux,initrd-start")?;
500        let p_initrd_end = root_builder.add_string("linux,initrd-end")?;
501
502        let chosen = root_builder
503            .start_node("chosen")?
504            .add_str(p_bootargs, cmdline.as_str())?
505            .add_u64(p_initrd_start, initrd.start)?
506            .add_u64(p_initrd_end, initrd.end)?;
507        root_builder = chosen.end_node()?;
508    }
509
510    // Add information used by openhcl usermode.
511    let mut openhcl_builder = root_builder.start_node("openhcl")?;
512
513    let p_isolation_type = openhcl_builder.add_string("isolation-type")?;
514    let isolation_type = match partition_info.isolation {
515        IsolationType::None => "none",
516        IsolationType::Vbs => "vbs",
517        IsolationType::Snp => "snp",
518        IsolationType::Tdx => "tdx",
519    };
520    openhcl_builder = openhcl_builder.add_str(p_isolation_type, isolation_type)?;
521
522    // Indicate what kind of memory allocation mode was done by the bootloader
523    // to usermode.
524    let p_memory_allocation_mode = openhcl_builder.add_string("memory-allocation-mode")?;
525    match partition_info.memory_allocation_mode {
526        MemoryAllocationMode::Host => {
527            openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "host")?;
528        }
529        MemoryAllocationMode::Vtl2 {
530            memory_size,
531            mmio_size,
532        } => {
533            let p_memory_size = openhcl_builder.add_string("memory-size")?;
534            let p_mmio_size = openhcl_builder.add_string("mmio-size")?;
535            openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "vtl2")?;
536            if let Some(memory_size) = memory_size {
537                openhcl_builder = openhcl_builder.add_u64(p_memory_size, memory_size)?;
538            }
539            if let Some(mmio_size) = mmio_size {
540                openhcl_builder = openhcl_builder.add_u64(p_mmio_size, mmio_size)?;
541            }
542        }
543    }
544
545    if let Some(data) = partition_info.vtl0_alias_map {
546        let p_vtl0_alias_map = openhcl_builder.add_string("vtl0-alias-map")?;
547        openhcl_builder = openhcl_builder.add_u64(p_vtl0_alias_map, data)?;
548    }
549
550    // Report the unified memory map to usermode describing which memory is
551    // used by what.
552    //
553    // NOTE: Use a different device type for memory ranges, as the Linux kernel
554    // will treat every device tree node with device type as memory, and attempt
555    // to parse numa information from it.
556    let memory_openhcl_type = "memory-openhcl";
557    for (range, result) in walk_ranges(
558        partition_info.partition_ram.iter().map(|r| (r.range, r)),
559        address_space.vtl2_ranges(),
560    ) {
561        match result {
562            RangeWalkResult::Left(entry) => {
563                // This range is usable by VTL0.
564                let name = format_fixed!(64, "memory@{:x}", range.start());
565                openhcl_builder = openhcl_builder
566                    .start_node(&name)?
567                    .add_str(p_device_type, memory_openhcl_type)?
568                    .add_u64_array(p_reg, &[range.start(), range.len()])?
569                    .add_u32(p_numa_node_id, entry.vnode)?
570                    .add_u32(p_igvm_type, entry.mem_type.0.into())?
571                    .add_u32(p_openhcl_memory, MemoryVtlType::VTL0.0)?
572                    .end_node()?;
573            }
574            RangeWalkResult::Both(partition_entry, vtl2_type) => {
575                // This range is in use by VTL2. Indicate that.
576                let name = format_fixed!(64, "memory@{:x}", range.start());
577                openhcl_builder = openhcl_builder
578                    .start_node(&name)?
579                    .add_str(p_device_type, memory_openhcl_type)?
580                    .add_u64_array(p_reg, &[range.start(), range.len()])?
581                    .add_u32(p_numa_node_id, partition_entry.vnode)?
582                    .add_u32(p_igvm_type, partition_entry.mem_type.0.into())?
583                    .add_u32(p_openhcl_memory, vtl2_type.0)?
584                    .end_node()?;
585            }
586            RangeWalkResult::Right(..) => {
587                panic!("vtl2 range {:?} not contained in partition ram", range)
588            }
589            // Ignore ranges not described in either.
590            RangeWalkResult::Neither => {}
591        }
592    }
593
594    // Add mmio ranges for both VTL0 and VTL2.
595    for entry in &partition_info.vmbus_vtl0.mmio {
596        let name = format_fixed!(64, "memory@{:x}", entry.start());
597        openhcl_builder = openhcl_builder
598            .start_node(&name)?
599            .add_str(p_device_type, memory_openhcl_type)?
600            .add_u64_array(p_reg, &[entry.start(), entry.len()])?
601            .add_u32(p_openhcl_memory, MemoryVtlType::VTL0_MMIO.0)?
602            .end_node()?;
603    }
604
605    for entry in &partition_info.vmbus_vtl2.mmio {
606        let name = format_fixed!(64, "memory@{:x}", entry.start());
607        openhcl_builder = openhcl_builder
608            .start_node(&name)?
609            .add_str(p_device_type, memory_openhcl_type)?
610            .add_u64_array(p_reg, &[entry.start(), entry.len()])?
611            .add_u32(p_openhcl_memory, MemoryVtlType::VTL2_MMIO.0)?
612            .end_node()?;
613    }
614
615    // Report accepted ranges underhil openhcl node.
616    for range in accepted_ranges {
617        let name = format_fixed!(64, "accepted-memory@{:x}", range.start());
618        openhcl_builder = openhcl_builder
619            .start_node(&name)?
620            .add_u64_array(p_reg, &[range.start(), range.len()])?
621            .end_node()?;
622    }
623
624    // Pass through host-provided entropy to the init process for seeding
625    // the OpenHCL kernel random number generator
626    if let Some(entropy) = &partition_info.entropy {
627        openhcl_builder = openhcl_builder
628            .start_node("entropy")?
629            .add_prop_array(p_reg, &[entropy])?
630            .end_node()?;
631    }
632
633    let root_builder = openhcl_builder.end_node()?;
634
635    root_builder.end_node()?.build(partition_info.bsp_reg)?;
636    Ok(())
637}