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