1#![forbid(unsafe_code)]
9
10use anyhow::Context;
11use anyhow::bail;
12use fdt::parser::Node;
13use fdt::parser::Parser;
14use fdt::parser::Property;
15use igvm_defs::MemoryMapEntryType;
16use igvm_defs::dt::IGVM_DT_IGVM_TYPE_PROPERTY;
17use inspect::Inspect;
18use loader_defs::shim::MemoryVtlType;
19use memory_range::MemoryRange;
20use vm_topology::memory::MemoryRangeWithNode;
21use vm_topology::processor::aarch64::GicInfo;
22
23#[derive(Debug, Inspect, Clone, Copy, PartialEq, Eq)]
25pub struct Cpu {
26 pub reg: u64,
30 pub vnode: u32,
32}
33
34#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
36pub struct Memory {
37 pub range: MemoryRangeWithNode,
39 #[inspect(debug)]
41 pub vtl_usage: MemoryVtlType,
42 #[inspect(debug)]
44 pub igvm_type: MemoryMapEntryType,
45}
46
47#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
49pub enum Vtl {
50 Vtl0,
52 Vtl2,
54}
55
56#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
58pub struct Mmio {
59 pub range: MemoryRange,
61 pub vtl: Vtl,
63}
64
65#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
67#[inspect(tag = "type")]
68pub enum AddressRange {
69 Memory(#[inspect(flatten)] Memory),
71 Mmio(#[inspect(flatten)] Mmio),
73}
74
75impl AddressRange {
76 pub fn range(&self) -> &MemoryRange {
78 match self {
79 AddressRange::Memory(memory) => &memory.range.range,
80 AddressRange::Mmio(mmio) => &mmio.range,
81 }
82 }
83
84 pub fn vtl_usage(&self) -> MemoryVtlType {
86 match self {
87 AddressRange::Memory(memory) => memory.vtl_usage,
88 AddressRange::Mmio(Mmio { vtl, .. }) => match vtl {
89 Vtl::Vtl0 => MemoryVtlType::VTL0_MMIO,
90 Vtl::Vtl2 => MemoryVtlType::VTL2_MMIO,
91 },
92 }
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Inspect)]
98pub enum IsolationType {
99 None,
101 Vbs,
103 Snp,
105 Tdx,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Inspect)]
112#[inspect(external_tag)]
113pub enum MemoryAllocationMode {
114 Host,
118 Vtl2 {
121 #[inspect(hex)]
124 memory_size: Option<u64>,
125 #[inspect(hex)]
128 mmio_size: Option<u64>,
129 },
130}
131
132#[derive(Debug, Inspect, PartialEq, Eq)]
136pub struct ParsedBootDtInfo {
137 #[inspect(iter_by_index)]
140 pub cpus: Vec<Cpu>,
141 pub vtl0_alias_map: Option<u64>,
143 #[inspect(iter_by_index)]
146 pub vtl2_memory: Vec<MemoryRangeWithNode>,
147 #[inspect(with = "inspect_helpers::memory_internal")]
150 pub partition_memory_map: Vec<AddressRange>,
151 #[inspect(iter_by_index)]
153 pub vtl0_mmio: Vec<MemoryRange>,
154 #[inspect(iter_by_index)]
156 pub config_ranges: Vec<MemoryRange>,
157 pub vtl2_reserved_range: MemoryRange,
159 pub vtl2_persisted_header: MemoryRange,
162 pub vtl2_persisted_protobuf_region: MemoryRange,
164 #[inspect(iter_by_index)]
167 pub accepted_ranges: Vec<MemoryRange>,
168 pub memory_allocation_mode: MemoryAllocationMode,
170 pub isolation: IsolationType,
172 #[inspect(iter_by_index)]
174 pub private_pool_ranges: Vec<MemoryRangeWithNode>,
175
176 pub gic: Option<GicInfo>,
178 pub pmu_gsiv: Option<u32>,
180}
181
182fn err_to_owned(e: fdt::parser::Error<'_>) -> anyhow::Error {
183 anyhow::Error::msg(e.to_string())
184}
185
186fn try_find_property<'a>(node: &Node<'a>, name: &str) -> Option<Property<'a>> {
190 node.find_property(name).ok().flatten()
191}
192
193fn address_cells(node: &Node<'_>) -> anyhow::Result<u32> {
194 let prop = try_find_property(node, "#address-cells")
195 .context("missing address cells on {node.name}")?;
196 prop.read_u32(0).map_err(err_to_owned)
197}
198
199fn property_to_u64_vec(node: &Node<'_>, name: &str) -> anyhow::Result<Vec<u64>> {
200 let prop = try_find_property(node, name).context("missing prop {name} on {node.name}")?;
201 Ok(prop
202 .as_64_list()
203 .map_err(err_to_owned)
204 .context("prop {name} is not a list of u64s")?
205 .collect())
206}
207
208struct OpenhclInfo {
209 vtl0_mmio: Vec<MemoryRange>,
210 config_ranges: Vec<MemoryRange>,
211 partition_memory_map: Vec<AddressRange>,
212 accepted_memory: Vec<MemoryRange>,
213 vtl2_reserved_range: MemoryRange,
214 vtl0_alias_map: Option<u64>,
215 memory_allocation_mode: MemoryAllocationMode,
216 isolation: IsolationType,
217 private_pool_ranges: Vec<MemoryRangeWithNode>,
218 vtl2_persisted_header: MemoryRange,
219 vtl2_persisted_protobuf_region: MemoryRange,
220}
221
222fn parse_memory_openhcl(node: &Node<'_>) -> anyhow::Result<AddressRange> {
223 let vtl_usage = {
224 let prop = try_find_property(node, "openhcl,memory-type")
225 .context(format!("missing openhcl,memory-type on node {}", node.name))?;
226
227 MemoryVtlType(prop.read_u32(0).map_err(err_to_owned).context(format!(
228 "openhcl memory node {} openhcl,memory-type invalid",
229 node.name
230 ))?)
231 };
232
233 if vtl_usage.ram() {
234 let range = parse_memory(node).context("unable to parse base memory")?;
236
237 let igvm_type = {
238 let prop = try_find_property(node, IGVM_DT_IGVM_TYPE_PROPERTY)
239 .context(format!("missing igvm type on node {}", node.name))?;
240 let value = prop
241 .read_u32(0)
242 .map_err(err_to_owned)
243 .context(format!("memory node {} invalid igvm type", node.name))?;
244 MemoryMapEntryType(value as u16)
245 };
246
247 Ok(AddressRange::Memory(Memory {
248 range,
249 vtl_usage,
250 igvm_type,
251 }))
252 } else {
253 let range = {
255 let reg = property_to_u64_vec(node, "reg")?;
256
257 if reg.len() != 2 {
258 bail!("mmio node {} does not have 2 u64s", node.name);
259 }
260
261 let base = reg[0];
262 let len = reg[1];
263 MemoryRange::try_new(base..(base + len)).context("invalid mmio range")?
264 };
265
266 let vtl = match vtl_usage {
267 MemoryVtlType::VTL0_MMIO => Vtl::Vtl0,
268 MemoryVtlType::VTL2_MMIO => Vtl::Vtl2,
269 _ => bail!(
270 "invalid vtl_usage {vtl_usage:?} type for mmio node {}",
271 node.name
272 ),
273 };
274
275 Ok(AddressRange::Mmio(Mmio { range, vtl }))
276 }
277}
278
279fn parse_accepted_memory(node: &Node<'_>) -> anyhow::Result<MemoryRange> {
280 let reg = property_to_u64_vec(node, "reg")?;
281
282 if reg.len() != 2 {
283 bail!("accepted memory node {} does not have 2 u64s", node.name);
284 }
285
286 let base = reg[0];
287 let len = reg[1];
288 MemoryRange::try_new(base..(base + len)).context("invalid preaccepted memory")
289}
290
291fn parse_openhcl(node: &Node<'_>) -> anyhow::Result<OpenhclInfo> {
292 let mut memory = Vec::new();
293 let mut accepted_memory = Vec::new();
294
295 for child in node.children() {
296 let child = child.map_err(err_to_owned).context("child invalid")?;
297
298 match child.name {
299 name if name.starts_with("memory@") => {
300 memory.push(parse_memory_openhcl(&child)?);
301 }
302
303 name if name.starts_with("accepted-memory@") => {
304 accepted_memory.push(parse_accepted_memory(&child)?);
305 }
306
307 name if name.starts_with("memory-allocation-mode") => {}
308
309 _ => {
310 }
312 }
313 }
314
315 let isolation = {
316 let prop = try_find_property(node, "isolation-type").context("missing isolation-type")?;
317
318 match prop.read_str().map_err(err_to_owned)? {
319 "none" => IsolationType::None,
320 "vbs" => IsolationType::Vbs,
321 "snp" => IsolationType::Snp,
322 "tdx" => IsolationType::Tdx,
323 ty => bail!("invalid isolation-type {ty}"),
324 }
325 };
326
327 let memory_allocation_mode = {
328 let prop = try_find_property(node, "memory-allocation-mode")
329 .context("missing memory-allocation-mode")?;
330
331 match prop.read_str().map_err(err_to_owned)? {
332 "host" => MemoryAllocationMode::Host,
333 "vtl2" => {
334 let memory_size = try_find_property(node, "memory-size")
335 .map(|p| p.read_u64(0))
336 .transpose()
337 .map_err(err_to_owned)?;
338
339 let mmio_size = try_find_property(node, "mmio-size")
340 .map(|p| p.read_u64(0))
341 .transpose()
342 .map_err(err_to_owned)?;
343
344 MemoryAllocationMode::Vtl2 {
345 memory_size,
346 mmio_size,
347 }
348 }
349 mode => bail!("invalid memory-allocation-mode {mode}"),
350 }
351 };
352
353 memory.sort_by_key(|r| r.range().start());
354 accepted_memory.sort_by_key(|r| r.start());
355
356 let config_ranges = memory
358 .iter()
359 .filter_map(|entry| {
360 if entry.vtl_usage() == MemoryVtlType::VTL2_CONFIG {
361 Some(*entry.range())
362 } else {
363 None
364 }
365 })
366 .collect();
367
368 let vtl2_reserved_range = {
370 let mut reserved_range_iter = memory.iter().filter_map(|entry| {
371 if entry.vtl_usage() == MemoryVtlType::VTL2_RESERVED {
372 Some(*entry.range())
373 } else {
374 None
375 }
376 });
377
378 let reserved_range = reserved_range_iter.next().unwrap_or(MemoryRange::EMPTY);
379
380 if reserved_range_iter.next().is_some() {
381 bail!("multiple VTL2 reserved ranges found");
382 }
383
384 reserved_range
385 };
386
387 let private_pool_ranges = memory
389 .iter()
390 .filter_map(|entry| match entry {
391 AddressRange::Memory(memory) => {
392 if memory.vtl_usage == MemoryVtlType::VTL2_GPA_POOL {
393 Some(memory.range.clone())
394 } else {
395 None
396 }
397 }
398 AddressRange::Mmio(_) => None,
399 })
400 .collect();
401
402 let vtl2_persisted_header = {
404 let mut header_iter = memory.iter().filter_map(|entry| {
405 if entry.vtl_usage() == MemoryVtlType::VTL2_PERSISTED_STATE_HEADER {
406 Some(*entry.range())
407 } else {
408 None
409 }
410 });
411
412 let header = header_iter
413 .next()
414 .ok_or(anyhow::anyhow!("no VTL2 persisted header range found"))?;
415
416 if header_iter.next().is_some() {
417 bail!("multiple VTL2 persisted header ranges found");
418 }
419
420 header
421 };
422
423 let vtl2_persisted_protobuf_region = {
425 let mut region_iter = memory.iter().filter_map(|entry| {
426 if entry.vtl_usage() == MemoryVtlType::VTL2_PERSISTED_STATE_PROTOBUF {
427 Some(*entry.range())
428 } else {
429 None
430 }
431 });
432
433 let region = region_iter.next().ok_or(anyhow::anyhow!(
434 "no VTL2 persisted protobuf region range found"
435 ))?;
436
437 if region_iter.next().is_some() {
438 bail!("multiple VTL2 persisted protobuf region ranges found");
439 }
440
441 region
442 };
443
444 let vtl0_alias_map = try_find_property(node, "vtl0-alias-map")
445 .map(|prop| prop.read_u64(0).map_err(err_to_owned))
446 .transpose()
447 .context("unable to read vtl0-alias-map")?;
448
449 let vtl0_mmio = memory
451 .iter()
452 .filter_map(|range| match range {
453 AddressRange::Memory(_) => None,
454 AddressRange::Mmio(mmio) => match mmio.vtl {
455 Vtl::Vtl0 => Some(mmio.range),
456 Vtl::Vtl2 => None,
457 },
458 })
459 .collect();
460
461 Ok(OpenhclInfo {
462 vtl0_mmio,
463 config_ranges,
464 partition_memory_map: memory,
465 accepted_memory,
466 vtl2_reserved_range,
467 vtl0_alias_map,
468 memory_allocation_mode,
469 isolation,
470 private_pool_ranges,
471 vtl2_persisted_header,
472 vtl2_persisted_protobuf_region,
473 })
474}
475
476fn parse_cpus(node: &Node<'_>) -> anyhow::Result<Vec<Cpu>> {
477 let address_cells = address_cells(node)?;
478
479 if address_cells > 2 {
480 bail!("cpus address-cells > 2 unexpected");
481 }
482
483 let mut cpus = Vec::new();
484
485 for cpu in node.children() {
486 let cpu = cpu.map_err(err_to_owned).context("cpu invalid")?;
487 let reg = try_find_property(&cpu, "reg").context("{cpu.name} missing reg")?;
488
489 let reg = match address_cells {
490 1 => reg.read_u32(0).map_err(err_to_owned)? as u64,
491 2 => reg.read_u64(0).map_err(err_to_owned)?,
492 _ => unreachable!(),
493 };
494
495 let vnode = try_find_property(&cpu, "numa-node-id")
496 .context("{cpu.name} missing numa-node-id")?
497 .read_u32(0)
498 .map_err(err_to_owned)?;
499
500 cpus.push(Cpu { reg, vnode });
501 }
502
503 Ok(cpus)
504}
505
506fn parse_memory(node: &Node<'_>) -> anyhow::Result<MemoryRangeWithNode> {
508 let reg = property_to_u64_vec(node, "reg")?;
509
510 if reg.len() != 2 {
511 bail!("memory node {} does not have 2 u64s", node.name);
512 }
513
514 let base = reg[0];
515 let len = reg[1];
516 let numa_node_id = try_find_property(node, "numa-node-id")
517 .context(format!("{} missing numa-node-id", node.name))?
518 .read_u32(0)
519 .map_err(err_to_owned)
520 .context("unable to read numa-node-id")?;
521
522 Ok(MemoryRangeWithNode {
523 range: MemoryRange::try_new(base..base + len).context("invalid memory range")?,
524 vnode: numa_node_id,
525 })
526}
527
528fn parse_gic(node: &Node<'_>) -> anyhow::Result<GicInfo> {
530 let reg = property_to_u64_vec(node, "reg")?;
531
532 if reg.len() != 4 {
533 bail!("gic node {} does not have 4 u64s", node.name);
534 }
535
536 Ok(GicInfo {
537 gic_distributor_base: reg[0],
538 gic_redistributors_base: reg[2],
539 })
540}
541
542fn parse_pmu(node: &Node<'_>) -> anyhow::Result<u32> {
543 let interrupts =
544 try_find_property(node, "interrupts").context("pmu node missing interrupts")?;
545 let interrupts_typ = interrupts
546 .read_u32(0)
547 .map_err(err_to_owned)
548 .context("missing pmu interrupts typ")?;
549 let interrupts_ppi_index = interrupts
550 .read_u32(1)
551 .map_err(err_to_owned)
552 .context("missing pmu interrupts index")?;
553
554 const GIC_PPI: u32 = 1;
556 if interrupts_typ != GIC_PPI {
557 bail!("pmu node has unexpected interrupt type {interrupts_typ}");
558 }
559
560 if interrupts_ppi_index >= 16 {
563 bail!("pmu node has unexpected interrupt index {interrupts_ppi_index}");
564 }
565
566 const PPI_BASE: u32 = 16;
567 Ok(PPI_BASE + interrupts_ppi_index)
568}
569
570impl ParsedBootDtInfo {
571 pub fn new() -> anyhow::Result<Self> {
577 let raw = fs_err::read("/sys/firmware/fdt").context("reading fdt")?;
578 Self::new_from_raw(&raw)
579 }
580
581 fn new_from_raw(raw: &[u8]) -> anyhow::Result<Self> {
582 let mut cpus = Vec::new();
583 let mut vtl0_mmio = Vec::new();
584 let mut config_ranges = Vec::new();
585 let mut vtl2_memory = Vec::new();
586 let mut gic = None;
587 let mut pmu_gsiv = None;
588 let mut partition_memory_map = Vec::new();
589 let mut accepted_ranges = Vec::new();
590 let mut vtl0_alias_map = None;
591 let mut memory_allocation_mode = MemoryAllocationMode::Host;
592 let mut isolation = IsolationType::None;
593 let mut vtl2_reserved_range = MemoryRange::EMPTY;
594 let mut private_pool_ranges = Vec::new();
595 let mut vtl2_persisted_header = MemoryRange::EMPTY;
596 let mut vtl2_persisted_protobuf_region = MemoryRange::EMPTY;
597
598 let parser = Parser::new(raw)
599 .map_err(err_to_owned)
600 .context("failed to create fdt parser")?;
601
602 for child in parser
603 .root()
604 .map_err(err_to_owned)
605 .context("root invalid")?
606 .children()
607 {
608 let child = child.map_err(err_to_owned).context("child invalid")?;
609
610 match child.name {
611 "cpus" => {
612 cpus = parse_cpus(&child)?;
613 }
614
615 "openhcl" => {
616 let OpenhclInfo {
617 vtl0_mmio: n_vtl0_mmio,
618 config_ranges: n_config_ranges,
619 partition_memory_map: n_partition_memory_map,
620 vtl2_reserved_range: n_vtl2_reserved_range,
621 accepted_memory: n_accepted_memory,
622 vtl0_alias_map: n_vtl0_alias_map,
623 memory_allocation_mode: n_memory_allocation_mode,
624 isolation: n_isolation,
625 private_pool_ranges: n_private_pool_ranges,
626 vtl2_persisted_header: n_vtl2_persisted_header,
627 vtl2_persisted_protobuf_region: n_vtl2_persisted_protobuf_region,
628 } = parse_openhcl(&child)?;
629 vtl0_mmio = n_vtl0_mmio;
630 config_ranges = n_config_ranges;
631 partition_memory_map = n_partition_memory_map;
632 accepted_ranges = n_accepted_memory;
633 vtl0_alias_map = n_vtl0_alias_map;
634 memory_allocation_mode = n_memory_allocation_mode;
635 isolation = n_isolation;
636 vtl2_reserved_range = n_vtl2_reserved_range;
637 private_pool_ranges = n_private_pool_ranges;
638 vtl2_persisted_header = n_vtl2_persisted_header;
639 vtl2_persisted_protobuf_region = n_vtl2_persisted_protobuf_region;
640 }
641
642 _ if child.name.starts_with("memory@") => {
643 vtl2_memory.push(parse_memory(&child)?);
644 }
645
646 _ if child.name.starts_with("intc@") => {
647 gic = Some(parse_gic(&child)?);
649 }
650
651 _ if child.name.starts_with("pmu") => {
652 pmu_gsiv = Some(parse_pmu(&child)?);
654 }
655
656 _ => {
657 }
659 }
660 }
661
662 vtl2_memory.sort_by_key(|r| r.range.start());
663
664 Ok(Self {
665 cpus,
666 vtl0_mmio,
667 config_ranges,
668 vtl2_memory,
669 partition_memory_map,
670 vtl0_alias_map,
671 accepted_ranges,
672 gic,
673 pmu_gsiv,
674 memory_allocation_mode,
675 isolation,
676 vtl2_reserved_range,
677 private_pool_ranges,
678 vtl2_persisted_header,
679 vtl2_persisted_protobuf_region,
680 })
681 }
682}
683
684#[derive(Debug, Clone, Copy, PartialEq, Eq)]
686pub struct BootTimes {
687 pub start: Option<u64>,
689 pub end: Option<u64>,
691 pub sidecar_start: Option<u64>,
693 pub sidecar_end: Option<u64>,
695}
696
697impl BootTimes {
698 pub fn new() -> anyhow::Result<Self> {
704 let raw = fs_err::read("/sys/firmware/fdt").context("reading fdt")?;
705 Self::new_from_raw(&raw)
706 }
707
708 fn new_from_raw(raw: &[u8]) -> anyhow::Result<Self> {
709 let mut start = None;
710 let mut end = None;
711 let mut sidecar_start = None;
712 let mut sidecar_end = None;
713 let parser = Parser::new(raw)
714 .map_err(err_to_owned)
715 .context("failed to create fdt parser")?;
716
717 let root = parser
718 .root()
719 .map_err(err_to_owned)
720 .context("root invalid")?;
721
722 if let Some(prop) = try_find_property(&root, "reftime_boot_start") {
723 start = Some(prop.read_u64(0).map_err(err_to_owned)?);
724 }
725
726 if let Some(prop) = try_find_property(&root, "reftime_boot_end") {
727 end = Some(prop.read_u64(0).map_err(err_to_owned)?);
728 }
729
730 if let Some(prop) = try_find_property(&root, "reftime_sidecar_start") {
731 sidecar_start = Some(prop.read_u64(0).map_err(err_to_owned)?);
732 }
733
734 if let Some(prop) = try_find_property(&root, "reftime_sidecar_end") {
735 sidecar_end = Some(prop.read_u64(0).map_err(err_to_owned)?);
736 }
737
738 Ok(Self {
739 start,
740 end,
741 sidecar_start,
742 sidecar_end,
743 })
744 }
745}
746
747mod inspect_helpers {
748 use super::*;
749
750 pub(super) fn memory_internal(ranges: &[AddressRange]) -> impl Inspect + '_ {
751 inspect::iter_by_key(ranges.iter().map(|entry| (entry.range(), entry)))
752 }
753}
754
755#[cfg(test)]
756mod tests {
757 use super::*;
758 use fdt::builder::Builder;
759
760 fn build_dt(info: &ParsedBootDtInfo) -> anyhow::Result<Vec<u8>> {
761 let mut buf = vec![0; 4096];
762
763 let mut builder = Builder::new(fdt::builder::BuilderConfig {
764 blob_buffer: &mut buf,
765 string_table_cap: 1024,
766 memory_reservations: &[],
767 })?;
768 let p_address_cells = builder.add_string("#address-cells")?;
769 let p_size_cells = builder.add_string("#size-cells")?;
770 let p_reg = builder.add_string("reg")?;
771 let p_device_type = builder.add_string("device_type")?;
772 let p_compatible = builder.add_string("compatible")?;
773 let p_ranges = builder.add_string("ranges")?;
774 let p_numa_node_id = builder.add_string("numa-node-id")?;
775 let p_igvm_type = builder.add_string(IGVM_DT_IGVM_TYPE_PROPERTY)?;
776 let p_openhcl_memory = builder.add_string("openhcl,memory-type")?;
777 let p_interrupts = builder.add_string("interrupts")?;
778
779 let mut root_builder = builder
780 .start_node("")?
781 .add_u32(p_address_cells, 2)?
782 .add_u32(p_size_cells, 2)?
783 .add_str(p_compatible, "microsoft,openvmm")?;
784
785 let mut cpu_builder = root_builder
786 .start_node("cpus")?
787 .add_u32(p_address_cells, 1)?
788 .add_u32(p_size_cells, 0)?;
789
790 for (index, cpu) in info.cpus.iter().enumerate() {
792 let name = format!("cpu@{}", index + 1);
793
794 cpu_builder = cpu_builder
795 .start_node(&name)?
796 .add_str(p_device_type, "cpu")?
797 .add_u32(p_reg, cpu.reg as u32)?
798 .add_u32(p_numa_node_id, cpu.vnode)?
799 .end_node()?;
800 }
801
802 root_builder = cpu_builder.end_node()?;
803
804 for memory in info.vtl2_memory.iter().rev() {
806 let name = format!("memory@{:x}", memory.range.start());
807
808 root_builder = root_builder
809 .start_node(&name)?
810 .add_str(p_device_type, "memory")?
811 .add_u64_list(p_reg, [memory.range.start(), memory.range.len()])?
812 .add_u32(p_numa_node_id, memory.vnode)?
813 .end_node()?;
814 }
815
816 if let Some(gic) = info.gic {
818 let p_interrupt_cells = root_builder.add_string("#interrupt-cells")?;
819 let p_redist_regions = root_builder.add_string("#redistributor-regions")?;
820 let p_redist_stride = root_builder.add_string("redistributor-stride")?;
821 let p_interrupt_controller = root_builder.add_string("interrupt-controller")?;
822 let p_phandle = root_builder.add_string("phandle")?;
823 let name = format!("intc@{}", gic.gic_distributor_base);
824 root_builder = root_builder
825 .start_node(name.as_ref())?
826 .add_str(p_compatible, "arm,gic-v3")?
827 .add_u32(p_redist_regions, 1)?
828 .add_u64(p_redist_stride, 0)?
829 .add_u64_array(
830 p_reg,
831 &[gic.gic_distributor_base, 0, gic.gic_redistributors_base, 0],
832 )?
833 .add_u32(p_address_cells, 2)?
834 .add_u32(p_size_cells, 2)?
835 .add_u32(p_interrupt_cells, 3)?
836 .add_null(p_interrupt_controller)?
837 .add_u32(p_phandle, 1)?
838 .add_null(p_ranges)?
839 .end_node()?;
840 }
841
842 if let Some(pmu_gsiv) = info.pmu_gsiv {
844 assert!((16..32).contains(&pmu_gsiv));
845 const GIC_PPI: u32 = 1;
846 const IRQ_TYPE_LEVEL_HIGH: u32 = 4;
847 root_builder = root_builder
848 .start_node("pmu")?
849 .add_str(p_compatible, "arm,armv8-pmuv3")?
850 .add_u32_array(p_interrupts, &[GIC_PPI, pmu_gsiv - 16, IRQ_TYPE_LEVEL_HIGH])?
851 .end_node()?;
852 }
853
854 let mut openhcl_builder = root_builder.start_node("openhcl")?;
855 let p_isolation_type = openhcl_builder.add_string("isolation-type")?;
856 openhcl_builder = openhcl_builder.add_str(
857 p_isolation_type,
858 match info.isolation {
859 IsolationType::None => "none",
860 IsolationType::Vbs => "vbs",
861 IsolationType::Snp => "snp",
862 IsolationType::Tdx => "tdx",
863 },
864 )?;
865
866 let p_memory_allocation_mode = openhcl_builder.add_string("memory-allocation-mode")?;
867 match info.memory_allocation_mode {
868 MemoryAllocationMode::Host => {
869 openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "host")?;
870 }
871 MemoryAllocationMode::Vtl2 {
872 memory_size,
873 mmio_size,
874 } => {
875 let p_memory_size = openhcl_builder.add_string("memory-size")?;
876 let p_mmio_size = openhcl_builder.add_string("mmio-size")?;
877 openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "vtl2")?;
878 if let Some(memory_size) = memory_size {
879 openhcl_builder = openhcl_builder.add_u64(p_memory_size, memory_size)?;
880 }
881 if let Some(mmio_size) = mmio_size {
882 openhcl_builder = openhcl_builder.add_u64(p_mmio_size, mmio_size)?;
883 }
884 }
885 }
886
887 if let Some(data) = info.vtl0_alias_map {
888 let p_vtl0_alias_map = openhcl_builder.add_string("vtl0-alias-map")?;
889 openhcl_builder = openhcl_builder.add_u64(p_vtl0_alias_map, data)?;
890 }
891
892 openhcl_builder = openhcl_builder
893 .start_node("vmbus-vtl0")?
894 .add_u32(p_address_cells, 2)?
895 .add_u32(p_size_cells, 2)?
896 .add_str(p_compatible, "microsoft,vmbus")?
897 .add_u64_list(
898 p_ranges,
899 info.vtl0_mmio
900 .iter()
901 .flat_map(|r| [r.start(), r.start(), r.len()]),
902 )?
903 .end_node()?;
904
905 for range in &info.partition_memory_map {
906 let name = format!("memory@{:x}", range.range().start());
907
908 let node_builder = openhcl_builder
909 .start_node(&name)?
910 .add_str(p_device_type, "memory-openhcl")?
911 .add_u64_list(p_reg, [range.range().start(), range.range().len()])?
912 .add_u32(p_openhcl_memory, range.vtl_usage().0)?;
913
914 openhcl_builder = match range {
915 AddressRange::Memory(memory) => {
916 node_builder
918 .add_u32(p_numa_node_id, memory.range.vnode)?
919 .add_u32(p_igvm_type, memory.igvm_type.0 as u32)?
920 }
921 AddressRange::Mmio(_) => {
922 node_builder
925 }
926 }
927 .end_node()?;
928 }
929
930 for range in &info.accepted_ranges {
931 let name = format!("accepted-memory@{:x}", range.start());
932
933 openhcl_builder = openhcl_builder
934 .start_node(&name)?
935 .add_str(p_device_type, "memory-openhcl")?
936 .add_u64_list(p_reg, [range.start(), range.len()])?
937 .end_node()?;
938 }
939
940 root_builder = openhcl_builder.end_node()?;
941
942 root_builder.end_node()?.build(info.cpus[0].reg as u32)?;
943
944 Ok(buf)
945 }
946
947 #[test]
948 fn test_basic() {
949 let orig_info = ParsedBootDtInfo {
950 cpus: (0..4).map(|i| Cpu { reg: i, vnode: 0 }).collect(),
951 vtl2_memory: vec![
952 MemoryRangeWithNode {
953 range: MemoryRange::new(0x10000..0x20000),
954 vnode: 0,
955 },
956 MemoryRangeWithNode {
957 range: MemoryRange::new(0x20000..0x30000),
958 vnode: 1,
959 },
960 ],
961 partition_memory_map: vec![
962 AddressRange::Memory(Memory {
963 range: MemoryRangeWithNode {
964 range: MemoryRange::new(0..0x1000),
965 vnode: 0,
966 },
967 vtl_usage: MemoryVtlType::VTL0,
968 igvm_type: MemoryMapEntryType::MEMORY,
969 }),
970 AddressRange::Mmio(Mmio {
971 range: MemoryRange::new(0x1000..0x2000),
972 vtl: Vtl::Vtl0,
973 }),
974 AddressRange::Mmio(Mmio {
975 range: MemoryRange::new(0x3000..0x4000),
976 vtl: Vtl::Vtl0,
977 }),
978 AddressRange::Memory(Memory {
979 range: MemoryRangeWithNode {
980 range: MemoryRange::new(0x10000..0x20000),
981 vnode: 0,
982 },
983 vtl_usage: MemoryVtlType::VTL2_RAM,
984 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
985 }),
986 AddressRange::Memory(Memory {
987 range: MemoryRangeWithNode {
988 range: MemoryRange::new(0x20000..0x30000),
989 vnode: 1,
990 },
991 vtl_usage: MemoryVtlType::VTL2_CONFIG,
992 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
993 }),
994 AddressRange::Memory(Memory {
995 range: MemoryRangeWithNode {
996 range: MemoryRange::new(0x30000..0x40000),
997 vnode: 1,
998 },
999 vtl_usage: MemoryVtlType::VTL2_CONFIG,
1000 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1001 }),
1002 AddressRange::Memory(Memory {
1003 range: MemoryRangeWithNode {
1004 range: MemoryRange::new(0x40000..0x50000),
1005 vnode: 1,
1006 },
1007 vtl_usage: MemoryVtlType::VTL2_RESERVED,
1008 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1009 }),
1010 AddressRange::Memory(Memory {
1011 range: MemoryRangeWithNode {
1012 range: MemoryRange::new(0x50000..0x51000),
1013 vnode: 0,
1014 },
1015 vtl_usage: MemoryVtlType::VTL2_PERSISTED_STATE_HEADER,
1016 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1017 }),
1018 AddressRange::Memory(Memory {
1019 range: MemoryRangeWithNode {
1020 range: MemoryRange::new(0x51000..0x52000),
1021 vnode: 0,
1022 },
1023 vtl_usage: MemoryVtlType::VTL2_PERSISTED_STATE_PROTOBUF,
1024 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1025 }),
1026 AddressRange::Memory(Memory {
1027 range: MemoryRangeWithNode {
1028 range: MemoryRange::new(0x60000..0x70000),
1029 vnode: 0,
1030 },
1031 vtl_usage: MemoryVtlType::VTL2_GPA_POOL,
1032 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1033 }),
1034 AddressRange::Memory(Memory {
1035 range: MemoryRangeWithNode {
1036 range: MemoryRange::new(0x1000000..0x2000000),
1037 vnode: 0,
1038 },
1039 vtl_usage: MemoryVtlType::VTL0,
1040 igvm_type: MemoryMapEntryType::MEMORY,
1041 }),
1042 AddressRange::Mmio(Mmio {
1043 range: MemoryRange::new(0x3000000..0x4000000),
1044 vtl: Vtl::Vtl2,
1045 }),
1046 ],
1047 vtl0_mmio: vec![
1048 MemoryRange::new(0x1000..0x2000),
1049 MemoryRange::new(0x3000..0x4000),
1050 ],
1051 config_ranges: vec![
1052 MemoryRange::new(0x20000..0x30000),
1053 MemoryRange::new(0x30000..0x40000),
1054 ],
1055 vtl0_alias_map: Some(1 << 48),
1056 gic: Some(GicInfo {
1057 gic_distributor_base: 0x10000,
1058 gic_redistributors_base: 0x20000,
1059 }),
1060 pmu_gsiv: Some(0x17),
1061 accepted_ranges: vec![
1062 MemoryRange::new(0x10000..0x20000),
1063 MemoryRange::new(0x1000000..0x1500000),
1064 ],
1065 memory_allocation_mode: MemoryAllocationMode::Vtl2 {
1066 memory_size: Some(0x1000),
1067 mmio_size: Some(0x2000),
1068 },
1069 isolation: IsolationType::Vbs,
1070 vtl2_reserved_range: MemoryRange::new(0x40000..0x50000),
1071 private_pool_ranges: vec![MemoryRangeWithNode {
1072 range: MemoryRange::new(0x60000..0x70000),
1073 vnode: 0,
1074 }],
1075 vtl2_persisted_header: MemoryRange::new(0x50000..0x51000),
1076 vtl2_persisted_protobuf_region: MemoryRange::new(0x51000..0x52000),
1077 };
1078
1079 let dt = build_dt(&orig_info).unwrap();
1080 let parsed = ParsedBootDtInfo::new_from_raw(&dt).unwrap();
1081
1082 assert_eq!(orig_info, parsed);
1083 }
1084
1085 fn build_boottime_dt(boot_times: BootTimes) -> anyhow::Result<Vec<u8>> {
1086 let mut buf = vec![0; 4096];
1087
1088 let mut builder = Builder::new(fdt::builder::BuilderConfig {
1089 blob_buffer: &mut buf,
1090 string_table_cap: 1024,
1091 memory_reservations: &[],
1092 })?;
1093 let p_address_cells = builder.add_string("#address-cells")?;
1094 let p_size_cells = builder.add_string("#size-cells")?;
1095 let p_reftime_boot_start = builder.add_string("reftime_boot_start")?;
1096 let p_reftime_boot_end = builder.add_string("reftime_boot_end")?;
1097 let p_reftime_sidecar_start = builder.add_string("reftime_sidecar_start")?;
1098 let p_reftime_sidecar_end = builder.add_string("reftime_sidecar_end")?;
1099
1100 let mut root_builder = builder
1101 .start_node("")?
1102 .add_u32(p_address_cells, 2)?
1103 .add_u32(p_size_cells, 2)?;
1104
1105 if let Some(start) = boot_times.start {
1106 root_builder = root_builder.add_u64(p_reftime_boot_start, start)?;
1107 }
1108
1109 if let Some(end) = boot_times.end {
1110 root_builder = root_builder.add_u64(p_reftime_boot_end, end)?;
1111 }
1112
1113 if let Some(start) = boot_times.sidecar_start {
1114 root_builder = root_builder.add_u64(p_reftime_sidecar_start, start)?;
1115 }
1116
1117 if let Some(end) = boot_times.sidecar_end {
1118 root_builder = root_builder.add_u64(p_reftime_sidecar_end, end)?;
1119 }
1120
1121 root_builder.end_node()?.build(0)?;
1122
1123 Ok(buf)
1124 }
1125
1126 #[test]
1127 fn test_basic_boottime() {
1128 let orig_info = BootTimes {
1129 start: Some(0x1000),
1130 end: Some(0x2000),
1131 sidecar_start: Some(0x3000),
1132 sidecar_end: Some(0x4000),
1133 };
1134
1135 let dt = build_boottime_dt(orig_info).unwrap();
1136 let parsed = BootTimes::new_from_raw(&dt).unwrap();
1137
1138 assert_eq!(orig_info, parsed);
1139
1140 let orig_info = BootTimes {
1142 start: None,
1143 end: None,
1144 sidecar_start: None,
1145 sidecar_end: None,
1146 };
1147
1148 let dt = build_boottime_dt(orig_info).unwrap();
1149 let parsed = BootTimes::new_from_raw(&dt).unwrap();
1150
1151 assert_eq!(orig_info, parsed);
1152 }
1153}