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