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