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