1use 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
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 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 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#[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
97fn 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 &[
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
153pub 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: IsolationType,
166) -> Result<(), DtError> {
167 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 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 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 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 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 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 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 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 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 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 &[
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 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 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 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 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 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 RangeWalkResult::Neither => {}
553 }
554 }
555
556 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 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 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 RangeWalkResult::Neither => {}
597 }
598 }
599
600 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 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 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}