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_PPI_OFFSET: u32 = 2;
49 pub const TIMER_INTID: u32 = 4; 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 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#[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
104fn 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 &[
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
160pub 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: IsolationType,
173) -> Result<(), DtError> {
174 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 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 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 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 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 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 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 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 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 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 &[
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 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 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 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 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 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 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 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 RangeWalkResult::Neither => {}
591 }
592 }
593
594 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 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 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}