1use 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
32mod aarch64 {
34 pub const DEFAULT_GIC_DISTRIBUTOR_BASE: u64 = 0xFFFF_0000;
37 pub const DEFAULT_GIC_REDISTRIBUTORS_BASE: u64 = 0xEFFE_E000;
38
39 pub const VMBUS_INTID: u32 = 2; pub const TIMER_INTID: u32 = 4; pub const PMU_GSIV: u32 = 0x17;
51 pub const PMU_GSIV_INT_INDEX: u32 = PMU_GSIV - 16;
52
53 pub const GIC_PHANDLE: u32 = 1;
54 pub const GIC_PPI: u32 = 1;
55 pub const IRQ_TYPE_EDGE_FALLING: u32 = 2;
56 pub const IRQ_TYPE_LEVEL_LOW: u32 = 8;
57 pub const IRQ_TYPE_LEVEL_HIGH: u32 = 4;
58}
59
60#[derive(Debug)]
61pub enum DtError {
62 Fdt(#[expect(dead_code)] fdt::builder::Error),
64}
65
66impl From<fdt::builder::Error> for DtError {
67 fn from(err: fdt::builder::Error) -> Self {
68 DtError::Fdt(err)
69 }
70}
71
72macro_rules! format_fixed {
73 ($n:expr, $($arg:tt)*) => {
74 {
75 let mut buf = ArrayString::<$n>::new();
76 fmt::write(&mut buf, format_args!($($arg)*)).unwrap();
77 buf
78 }
79 };
80}
81
82pub struct BootTimes {
83 pub start: u64,
84 pub end: u64,
85}
86
87#[derive(Clone, Copy)]
89pub struct VmbusDeviceTreeInfo {
90 p_address_cells: StringId,
91 p_size_cells: StringId,
92 p_compatible: StringId,
93 p_ranges: StringId,
94 p_vtl: StringId,
95 p_vmbus_connection_id: StringId,
96 p_dma_coherent: StringId,
97 p_interrupt_parent: StringId,
98 p_interrupts: StringId,
99 interrupt_cell_value: Option<u32>,
100}
101
102fn write_vmbus<'a, T>(
104 parent: Builder<'a, T>,
105 name: &str,
106 vtl: Vtl,
107 vmbus: &VmbusInfo,
108 dt: VmbusDeviceTreeInfo,
109) -> Result<Builder<'a, T>, DtError> {
110 let VmbusDeviceTreeInfo {
111 p_address_cells,
112 p_size_cells,
113 p_compatible,
114 p_ranges,
115 p_vtl,
116 p_vmbus_connection_id,
117 p_dma_coherent,
118 p_interrupt_parent,
119 p_interrupts,
120 interrupt_cell_value,
121 } = dt;
122
123 let mut vmbus_builder = parent
124 .start_node(name)?
125 .add_u32(p_address_cells, 2)?
126 .add_u32(p_size_cells, 2)?
127 .add_null(p_dma_coherent)?
128 .add_str(p_compatible, "microsoft,vmbus")?
129 .add_u32(p_vtl, u8::from(vtl).into())?
130 .add_u32(p_vmbus_connection_id, vmbus.connection_id)?;
131
132 let mut mmio_ranges = ArrayVec::<u64, 6>::new();
133 for entry in vmbus.mmio.iter() {
134 mmio_ranges
135 .try_extend_from_slice(&[entry.start(), entry.start(), entry.len()])
136 .expect("should always fit");
137 }
138 vmbus_builder = vmbus_builder.add_u64_array(p_ranges, mmio_ranges.as_slice())?;
139
140 if cfg!(target_arch = "aarch64") {
141 vmbus_builder = vmbus_builder
142 .add_u32(p_interrupt_parent, aarch64::GIC_PHANDLE)?
143 .add_u32_array(
144 p_interrupts,
145 &[
148 aarch64::GIC_PPI,
149 aarch64::VMBUS_INTID,
150 interrupt_cell_value.expect("must be set on aarch64"),
151 ],
152 )?;
153 }
154
155 Ok(vmbus_builder.end_node()?)
156}
157
158pub fn write_dt(
160 buffer: &mut [u8],
161 partition_info: &PartitionInfo,
162 address_space: &AddressSpaceManager,
163 accepted_ranges: impl IntoIterator<Item = MemoryRange>,
164 initrd: Range<u64>,
165 cmdline: &ArrayString<COMMAND_LINE_SIZE>,
166 sidecar: Option<&SidecarConfig<'_>>,
167 boot_times: Option<BootTimes>,
168 #[cfg_attr(target_arch = "aarch64", expect(unused_variables))]
169 isolation_type: IsolationType,
171) -> Result<(), DtError> {
172 let mut memory_reservations =
178 off_stack!(ArrayVec<fdt::ReserveEntry, MAX_RESERVED_MEM_RANGES>, ArrayVec::new_const());
179
180 memory_reservations.extend(address_space.reserved_vtl2_ranges().map(|(r, _)| {
181 fdt::ReserveEntry {
182 address: r.start().into(),
183 size: r.len().into(),
184 }
185 }));
186
187 let builder_config = fdt::builder::BuilderConfig {
189 blob_buffer: buffer,
190 string_table_cap: 1024,
191 memory_reservations: &memory_reservations,
192 };
193 let mut builder = Builder::new(builder_config)?;
194
195 let p_address_cells = builder.add_string("#address-cells")?;
197 let p_size_cells = builder.add_string("#size-cells")?;
198 let p_reg = builder.add_string("reg")?;
199 let p_reg_names = builder.add_string("reg-names")?;
200 let p_device_type = builder.add_string("device_type")?;
201 let p_status = builder.add_string("status")?;
202 let p_compatible = builder.add_string("compatible")?;
203 let p_ranges = builder.add_string("ranges")?;
204 let p_numa_node_id = builder.add_string("numa-node-id")?;
205 let p_reftime_boot_start = builder.add_string("reftime_boot_start")?;
206 let p_reftime_boot_end = builder.add_string("reftime_boot_end")?;
207 let p_reftime_sidecar_start = builder.add_string("reftime_sidecar_start")?;
208 let p_reftime_sidecar_end = builder.add_string("reftime_sidecar_end")?;
209 let p_vtl = builder.add_string(igvm_defs::dt::IGVM_DT_VTL_PROPERTY)?;
210 let p_vmbus_connection_id = builder.add_string("microsoft,message-connection-id")?;
211 let p_dma_coherent = builder.add_string("dma-coherent")?;
212 let p_igvm_type = builder.add_string(IGVM_DT_IGVM_TYPE_PROPERTY)?;
213 let p_openhcl_memory = builder.add_string("openhcl,memory-type")?;
214
215 let p_interrupt_parent = builder.add_string("interrupt-parent")?;
222 let p_interrupts = builder.add_string("interrupts")?;
223 let p_enable_method = builder.add_string("enable-method")?;
224
225 let num_cpus = partition_info.cpus.len();
226
227 let mut root_builder = builder
228 .start_node("")?
229 .add_u32(p_address_cells, 2)?
230 .add_u32(p_size_cells, 2)?
231 .add_str(p_compatible, "microsoft,openvmm")?;
232
233 if let Some(boot_times) = boot_times {
234 let BootTimes { start, end } = boot_times;
235 root_builder = root_builder
236 .add_u64(p_reftime_boot_start, start)?
237 .add_u64(p_reftime_boot_end, end)?;
238 }
239
240 if let Some(sidecar) = sidecar {
241 root_builder = root_builder
242 .add_u64(p_reftime_sidecar_start, sidecar.start_reftime)?
243 .add_u64(p_reftime_sidecar_end, sidecar.end_reftime)?;
244 }
245
246 let hypervisor_builder = root_builder
247 .start_node("hypervisor")?
248 .add_str(p_compatible, "microsoft,hyperv")?;
249 root_builder = hypervisor_builder.end_node()?;
250
251 #[cfg(target_arch = "x86_64")]
252 if isolation_type == IsolationType::Tdx {
253 let mut mailbox_builder = root_builder
254 .start_node("reserved-memory")?
255 .add_u32(p_address_cells, 2)?
256 .add_u32(p_size_cells, 1)?
257 .add_null(p_ranges)?;
258
259 let name = format_fixed!(32, "wakeup_table@{:x}", RESET_VECTOR_PAGE);
260 let mailbox_addr_builder = mailbox_builder
261 .start_node(name.as_ref())?
262 .add_str(p_compatible, "intel,wakeup-mailbox")?
263 .add_u32_array(p_reg, &[0x0, RESET_VECTOR_PAGE.try_into().unwrap(), 0x1000])?;
264
265 mailbox_builder = mailbox_addr_builder.end_node()?;
266
267 root_builder = mailbox_builder.end_node()?;
268 }
269
270 let address_cells = if cfg!(target_arch = "aarch64") { 2 } else { 1 };
273 let mut cpu_builder = root_builder
274 .start_node("cpus")?
275 .add_u32(p_address_cells, address_cells)?
276 .add_u32(p_size_cells, 0)?;
277
278 for (vp_index, cpu_entry) in partition_info.cpus.iter().enumerate() {
280 let name = format_fixed!(32, "cpu@{}", vp_index + 1);
281
282 let mut cpu = cpu_builder
283 .start_node(name.as_ref())?
284 .add_str(p_device_type, "cpu")?
285 .add_u32(p_numa_node_id, cpu_entry.vnode)?;
286
287 if cfg!(target_arch = "aarch64") {
288 cpu = cpu
289 .add_u64(p_reg, cpu_entry.reg)?
290 .add_str(p_compatible, "arm,arm-v8")?;
291
292 if num_cpus > 1 {
293 cpu = cpu.add_str(p_enable_method, "psci")?;
294 }
295
296 if vp_index == 0 {
297 cpu = cpu.add_str(p_status, "okay")?;
298 } else {
299 cpu = cpu.add_str(p_status, "disabled")?;
300 }
301 } else {
302 cpu = cpu
303 .add_u32(p_reg, cpu_entry.reg as u32)?
304 .add_str(p_status, "okay")?;
305 }
306
307 cpu_builder = cpu.end_node()?;
308 }
309 root_builder = cpu_builder.end_node()?;
310
311 if cfg!(target_arch = "aarch64") {
312 let p_method = root_builder.add_string("method")?;
313 let p_cpu_off = root_builder.add_string("cpu_off")?;
314 let p_cpu_on = root_builder.add_string("cpu_on")?;
315 let psci = root_builder
316 .start_node("psci")?
317 .add_str(p_compatible, "arm,psci-0.2")?
318 .add_str(p_method, "hvc")?
319 .add_u32(p_cpu_off, 1)?
320 .add_u32(p_cpu_on, 2)?;
321 root_builder = psci.end_node()?;
322 }
323
324 for mem_entry in partition_info.vtl2_ram.iter() {
326 let name = format_fixed!(32, "memory@{:x}", mem_entry.range.start());
327 let mut mem = root_builder.start_node(&name)?;
328 mem = mem.add_str(p_device_type, "memory")?;
329 mem = mem.add_u64_array(p_reg, &[mem_entry.range.start(), mem_entry.range.len()])?;
330 mem = mem.add_u32(p_numa_node_id, mem_entry.vnode)?;
331 root_builder = mem.end_node()?;
332 }
333
334 if cfg!(target_arch = "aarch64") {
335 let default = GicInfo {
339 gic_distributor_base: aarch64::DEFAULT_GIC_DISTRIBUTOR_BASE,
340 gic_distributor_size: aarch64defs::GIC_DISTRIBUTOR_SIZE,
341 gic_redistributors_base: aarch64::DEFAULT_GIC_REDISTRIBUTORS_BASE,
342 gic_redistributors_size: aarch64defs::GIC_REDISTRIBUTOR_SIZE * num_cpus as u64,
343 gic_redistributor_stride: aarch64defs::GIC_REDISTRIBUTOR_SIZE,
344 };
345 let gic = partition_info.gic.as_ref().unwrap_or(&default);
346
347 assert_eq!(gic.gic_distributor_size, default.gic_distributor_size);
349 assert_eq!(gic.gic_redistributors_size, default.gic_redistributors_size);
350 assert_eq!(
351 gic.gic_redistributor_stride,
352 default.gic_redistributor_stride
353 );
354
355 let p_interrupt_cells = root_builder.add_string("#interrupt-cells")?;
356 let p_redist_regions = root_builder.add_string("#redistributor-regions")?;
357 let p_redist_stride = root_builder.add_string("redistributor-stride")?;
358 let p_interrupt_controller = root_builder.add_string("interrupt-controller")?;
359 let p_phandle = root_builder.add_string("phandle")?;
360 let p_interrupt_names = root_builder.add_string("interrupt-names")?;
361 let p_always_on = root_builder.add_string("always-on")?;
362 let name = format_fixed!(32, "intc@{}", gic.gic_distributor_base);
363 let gicv3 = root_builder
364 .start_node(name.as_ref())?
365 .add_str(p_compatible, "arm,gic-v3")?
366 .add_u32(p_redist_regions, 1)?
367 .add_u64(p_redist_stride, gic.gic_redistributor_stride)?
368 .add_u64_array(
369 p_reg,
370 &[
371 gic.gic_distributor_base,
372 gic.gic_distributor_size,
373 gic.gic_redistributors_base,
374 gic.gic_redistributors_size,
375 ],
376 )?
377 .add_u32(p_address_cells, 2)?
378 .add_u32(p_size_cells, 2)?
379 .add_u32(p_interrupt_cells, 3)?
380 .add_null(p_interrupt_controller)?
381 .add_u32(p_phandle, aarch64::GIC_PHANDLE)?
382 .add_null(p_ranges)?;
383 root_builder = gicv3.end_node()?;
384
385 let timer = root_builder
387 .start_node("timer")?
388 .add_str(p_compatible, "arm,armv8-timer")?
389 .add_u32(p_interrupt_parent, aarch64::GIC_PHANDLE)?
390 .add_str(p_interrupt_names, "virt")?
391 .add_u32_array(
392 p_interrupts,
393 &[
398 aarch64::GIC_PPI,
399 aarch64::TIMER_INTID,
400 aarch64::IRQ_TYPE_LEVEL_LOW,
401 ],
402 )?
403 .add_null(p_always_on)?;
404 root_builder = timer.end_node()?;
405
406 let pmu_gsiv_index = partition_info
417 .pmu_gsiv
418 .map(|gsiv| {
419 assert!(
420 (16..32).contains(&gsiv),
421 "PMU GSIV must be a PPI in [16, 32) range"
422 );
423 gsiv - 16
424 })
425 .unwrap_or(aarch64::PMU_GSIV_INT_INDEX);
426 let pmu = root_builder
427 .start_node("pmu")?
428 .add_str(p_compatible, "arm,armv8-pmuv3")?
429 .add_u32_array(
430 p_interrupts,
431 &[
432 aarch64::GIC_PPI,
433 pmu_gsiv_index,
434 aarch64::IRQ_TYPE_LEVEL_HIGH,
435 ],
436 )?;
437 root_builder = pmu.end_node()?;
438 }
439
440 let mut simple_bus_builder = root_builder
442 .start_node("bus")?
443 .add_str(p_compatible, "simple-bus")?
444 .add_u32(p_address_cells, 2)?
445 .add_u32(p_size_cells, 2)?;
446 simple_bus_builder = simple_bus_builder.add_prop_array(p_ranges, &[])?;
447
448 let vmbus_info = VmbusDeviceTreeInfo {
449 p_address_cells,
450 p_size_cells,
451 p_compatible,
452 p_ranges,
453 p_vtl,
454 p_vmbus_connection_id,
455 p_dma_coherent,
456 p_interrupt_parent,
457 p_interrupts,
458 interrupt_cell_value: if cfg!(target_arch = "aarch64") {
459 Some(aarch64::IRQ_TYPE_EDGE_FALLING)
460 } else {
461 None
462 },
463 };
464
465 simple_bus_builder = write_vmbus(
466 simple_bus_builder,
467 "vmbus",
468 Vtl::Vtl2,
469 &partition_info.vmbus_vtl2,
470 vmbus_info,
471 )?;
472
473 if let Some(sidecar) = sidecar {
474 for node in sidecar.nodes {
475 let name = format_fixed!(64, "sidecar@{:x}", node.control_page);
476 simple_bus_builder = simple_bus_builder
477 .start_node(&name)?
478 .add_str(p_compatible, "microsoft,openhcl-sidecar")?
479 .add_u64_array(
480 p_reg,
481 &[
482 node.control_page,
483 sidecar_defs::PAGE_SIZE as u64,
484 node.shmem_pages_base,
485 node.shmem_pages_size,
486 ],
487 )?
488 .add_str_array(p_reg_names, &["ctrl", "shmem"])?
489 .end_node()?;
490 }
491 }
492
493 root_builder = simple_bus_builder.end_node()?;
494
495 if cfg!(target_arch = "aarch64") {
496 let p_bootargs = root_builder.add_string("bootargs")?;
497 let p_initrd_start = root_builder.add_string("linux,initrd-start")?;
498 let p_initrd_end = root_builder.add_string("linux,initrd-end")?;
499
500 let chosen = root_builder
501 .start_node("chosen")?
502 .add_str(p_bootargs, cmdline.as_str())?
503 .add_u64(p_initrd_start, initrd.start)?
504 .add_u64(p_initrd_end, initrd.end)?;
505 root_builder = chosen.end_node()?;
506 }
507
508 let mut openhcl_builder = root_builder.start_node("openhcl")?;
510
511 let p_isolation_type = openhcl_builder.add_string("isolation-type")?;
512 let isolation_type = match partition_info.isolation {
513 IsolationType::None => "none",
514 IsolationType::Vbs => "vbs",
515 IsolationType::Snp => "snp",
516 IsolationType::Tdx => "tdx",
517 };
518 openhcl_builder = openhcl_builder.add_str(p_isolation_type, isolation_type)?;
519
520 let p_memory_allocation_mode = openhcl_builder.add_string("memory-allocation-mode")?;
523 match partition_info.memory_allocation_mode {
524 MemoryAllocationMode::Host => {
525 openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "host")?;
526 }
527 MemoryAllocationMode::Vtl2 {
528 memory_size,
529 mmio_size,
530 } => {
531 let p_memory_size = openhcl_builder.add_string("memory-size")?;
532 let p_mmio_size = openhcl_builder.add_string("mmio-size")?;
533 openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "vtl2")?;
534 if let Some(memory_size) = memory_size {
535 openhcl_builder = openhcl_builder.add_u64(p_memory_size, memory_size)?;
536 }
537 if let Some(mmio_size) = mmio_size {
538 openhcl_builder = openhcl_builder.add_u64(p_mmio_size, mmio_size)?;
539 }
540 }
541 }
542
543 if let Some(data) = partition_info.vtl0_alias_map {
544 let p_vtl0_alias_map = openhcl_builder.add_string("vtl0-alias-map")?;
545 openhcl_builder = openhcl_builder.add_u64(p_vtl0_alias_map, data)?;
546 }
547
548 let memory_openhcl_type = "memory-openhcl";
555 for (range, result) in walk_ranges(
556 partition_info.partition_ram.iter().map(|r| (r.range, r)),
557 address_space.vtl2_ranges(),
558 ) {
559 match result {
560 RangeWalkResult::Left(entry) => {
561 let name = format_fixed!(64, "memory@{:x}", range.start());
563 openhcl_builder = openhcl_builder
564 .start_node(&name)?
565 .add_str(p_device_type, memory_openhcl_type)?
566 .add_u64_array(p_reg, &[range.start(), range.len()])?
567 .add_u32(p_numa_node_id, entry.vnode)?
568 .add_u32(p_igvm_type, entry.mem_type.0.into())?
569 .add_u32(p_openhcl_memory, MemoryVtlType::VTL0.0)?
570 .end_node()?;
571 }
572 RangeWalkResult::Both(partition_entry, vtl2_type) => {
573 let name = format_fixed!(64, "memory@{:x}", range.start());
575 openhcl_builder = openhcl_builder
576 .start_node(&name)?
577 .add_str(p_device_type, memory_openhcl_type)?
578 .add_u64_array(p_reg, &[range.start(), range.len()])?
579 .add_u32(p_numa_node_id, partition_entry.vnode)?
580 .add_u32(p_igvm_type, partition_entry.mem_type.0.into())?
581 .add_u32(p_openhcl_memory, vtl2_type.0)?
582 .end_node()?;
583 }
584 RangeWalkResult::Right(..) => {
585 panic!("vtl2 range {:?} not contained in partition ram", range)
586 }
587 RangeWalkResult::Neither => {}
589 }
590 }
591
592 for entry in &partition_info.vmbus_vtl0.mmio {
594 let name = format_fixed!(64, "memory@{:x}", entry.start());
595 openhcl_builder = openhcl_builder
596 .start_node(&name)?
597 .add_str(p_device_type, memory_openhcl_type)?
598 .add_u64_array(p_reg, &[entry.start(), entry.len()])?
599 .add_u32(p_openhcl_memory, MemoryVtlType::VTL0_MMIO.0)?
600 .end_node()?;
601 }
602
603 for entry in &partition_info.vmbus_vtl2.mmio {
604 let name = format_fixed!(64, "memory@{:x}", entry.start());
605 openhcl_builder = openhcl_builder
606 .start_node(&name)?
607 .add_str(p_device_type, memory_openhcl_type)?
608 .add_u64_array(p_reg, &[entry.start(), entry.len()])?
609 .add_u32(p_openhcl_memory, MemoryVtlType::VTL2_MMIO.0)?
610 .end_node()?;
611 }
612
613 for range in accepted_ranges {
615 let name = format_fixed!(64, "accepted-memory@{:x}", range.start());
616 openhcl_builder = openhcl_builder
617 .start_node(&name)?
618 .add_u64_array(p_reg, &[range.start(), range.len()])?
619 .end_node()?;
620 }
621
622 if let Some(entropy) = &partition_info.entropy {
625 openhcl_builder = openhcl_builder
626 .start_node("entropy")?
627 .add_prop_array(p_reg, &[entropy])?
628 .end_node()?;
629 }
630
631 let root_builder = openhcl_builder.end_node()?;
632
633 root_builder.end_node()?.build(partition_info.bsp_reg)?;
634 Ok(())
635}