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