microsoft/openvmm

Public

mirrored fromhttps://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
104b157372fda313b73d0349f5adf5be4053a680

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

openhcl/bootloader_fdt_parser/src/lib.rs

1041lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Parsing code for the devicetree provided by openhcl_boot used by underhill
5//! usermode. Unlike `host_fdt_parser`, this code requires std as it is intended
6//! to be only used in usermode.
7
8#![warn(missing_docs)]
9#![forbid(unsafe_code)]
10
11use anyhow::bail;
12use anyhow::Context;
13use fdt::parser::Node;
14use fdt::parser::Parser;
15use fdt::parser::Property;
16use igvm_defs::dt::IGVM_DT_IGVM_TYPE_PROPERTY;
17use igvm_defs::MemoryMapEntryType;
18use inspect::Inspect;
19use loader_defs::shim::MemoryVtlType;
20use memory_range::MemoryRange;
21use vm_topology::memory::MemoryRangeWithNode;
22use vm_topology::processor::aarch64::GicInfo;
23
24/// A parsed cpu.
25#[derive(Debug, Inspect, Clone, Copy, PartialEq, Eq)]
26pub struct Cpu {
27 /// Architecture specific "reg" value for this CPU. For x64, this is the
28 /// APIC ID. For ARM v8 64-bit, this should match the MPIDR_EL1 register
29 /// affinity bits.
30 pub reg: u64,
31 /// The vnode field of a cpu dt node, which describes the numa node id.
32 pub vnode: u32,
33}
34
35/// Information about a guest memory range.
36#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
37pub struct Memory {
38 /// The range of memory.
39 pub range: MemoryRangeWithNode,
40 /// The VTL this memory is for.
41 #[inspect(debug)]
42 pub vtl_usage: MemoryVtlType,
43 /// The host provided IGVM type for this memory.
44 #[inspect(debug)]
45 pub igvm_type: MemoryMapEntryType,
46}
47
48/// Vtls for mmio.
49#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
50pub enum Vtl {
51 /// VTL0.
52 Vtl0,
53 /// VTL2.
54 Vtl2,
55}
56
57/// Information about guest mmio.
58#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
59pub struct Mmio {
60 /// The address range of mmio.
61 pub range: MemoryRange,
62 /// The VTL this mmio is for.
63 pub vtl: Vtl,
64}
65
66/// Information about a section of the guest's address space.
67#[derive(Debug, Inspect, Clone, PartialEq, Eq)]
68#[inspect(tag = "type")]
69pub enum AddressRange {
70 /// This range describes memory.
71 Memory(#[inspect(flatten)] Memory),
72 /// This range describes mmio.
73 Mmio(#[inspect(flatten)] Mmio),
74}
75
76impl AddressRange {
77 /// The [`MemoryRange`] for this address range.
78 pub fn range(&self) -> &MemoryRange {
79 match self {
80 AddressRange::Memory(memory) => &memory.range.range,
81 AddressRange::Mmio(mmio) => &mmio.range,
82 }
83 }
84
85 /// The [`MemoryVtlType`] for this address range.
86 pub fn vtl_usage(&self) -> MemoryVtlType {
87 match self {
88 AddressRange::Memory(memory) => memory.vtl_usage,
89 AddressRange::Mmio(Mmio { vtl, .. }) => match vtl {
90 Vtl::Vtl0 => MemoryVtlType::VTL0_MMIO,
91 Vtl::Vtl2 => MemoryVtlType::VTL2_MMIO,
92 },
93 }
94 }
95}
96
97/// The isolation type of the partition.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Inspect)]
99pub enum IsolationType {
100 /// No isolation.
101 None,
102 /// Hyper-V based isolation.
103 Vbs,
104 /// AMD SNP.
105 Snp,
106 /// Intel TDX.
107 Tdx,
108}
109
110/// The memory allocation mode provided by the host. This reports how the
111/// bootloader decided to provide memory for the kernel.
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Inspect)]
113#[inspect(external_tag)]
114pub enum MemoryAllocationMode {
115 /// Use the host provided memory topology, and use VTL2_PROTECTABLE entries
116 /// as VTL2 ram. This is the default if no
117 /// `openhcl/memory-allocation-property` mode is provided by the host.
118 Host,
119 /// Allow VTL2 to select its own ranges from the address space to use for
120 /// memory, with a size provided by the host.
121 Vtl2 {
122 /// The number of bytes VTL2 should allocate for memory for itself.
123 /// Encoded as `openhcl/memory-size` in device tree.
124 #[inspect(hex)]
125 memory_size: Option<u64>,
126 /// The number of bytes VTL2 should allocate for mmio for itself.
127 /// Encoded as `openhcl/mmio-size` in device tree.
128 #[inspect(hex)]
129 mmio_size: Option<u64>,
130 },
131}
132
133/// Information parsed from the device tree provided by openhcl_boot. These
134/// values are trusted, as it's expected that openhcl_boot has already validated
135/// the host provided device tree.
136#[derive(Debug, Inspect, PartialEq, Eq)]
137pub struct ParsedBootDtInfo {
138 /// The cpus in the system. The index in the vector is also the mshv VP
139 /// index.
140 #[inspect(iter_by_index)]
141 pub cpus: Vec<Cpu>,
142 /// The physical address bits of the system. Today, this is only reported on aarch64.
143 /// TODO: Could we also report this on x64, and in CVMs?
144 pub physical_address_bits: Option<u8>,
145 /// The physical address of the VTL0 alias mapping, if one is configured.
146 pub vtl0_alias_map: Option<u64>,
147 /// The memory ranges for VTL2 that were reported to the kernel. This is
148 /// sorted in ascending order.
149 #[inspect(iter_by_index)]
150 pub vtl2_memory: Vec<MemoryRangeWithNode>,
151 /// The unified memory map for the partition, from the bootloader. Sorted in
152 /// ascending order. Note that this includes mmio gaps as well.
153 #[inspect(with = "inspect_helpers::memory_internal")]
154 pub partition_memory_map: Vec<AddressRange>,
155 /// The mmio to report to VTL0.
156 #[inspect(iter_by_index)]
157 pub vtl0_mmio: Vec<MemoryRange>,
158 /// The ranges config regions are stored at.
159 #[inspect(iter_by_index)]
160 pub config_ranges: Vec<MemoryRange>,
161 /// The VTL2 reserved range.
162 pub vtl2_reserved_range: MemoryRange,
163 /// The ranges that were accepted at load time by the host on behalf of the
164 /// guest.
165 #[inspect(iter_by_index)]
166 pub accepted_ranges: Vec<MemoryRange>,
167 /// GIC information
168 pub gic: Option<GicInfo>,
169 /// The memory allocation mode the bootloader decided to use.
170 pub memory_allocation_mode: MemoryAllocationMode,
171 /// The isolation type of the partition.
172 pub isolation: IsolationType,
173 /// VTL2 range for private pool memory.
174 #[inspect(iter_by_index)]
175 pub private_pool_ranges: Vec<MemoryRangeWithNode>,
176}
177
178fn err_to_owned(e: fdt::parser::Error<'_>) -> anyhow::Error {
179 anyhow::Error::msg(e.to_string())
180}
181
182/// Try to find a given property on a node, returning None if not found. As the
183/// bootloader should be producing a well formed device tree, errors are not
184/// expected and flattened into `None`.
185fn try_find_property<'a>(node: &Node<'a>, name: &str) -> Option<Property<'a>> {
186 node.find_property(name).ok().flatten()
187}
188
189fn address_cells(node: &Node<'_>) -> anyhow::Result<u32> {
190 let prop = try_find_property(node, "#address-cells")
191 .context("missing address cells on {node.name}")?;
192 prop.read_u32(0).map_err(err_to_owned)
193}
194
195fn property_to_u64_vec(node: &Node<'_>, name: &str) -> anyhow::Result<Vec<u64>> {
196 let prop = try_find_property(node, name).context("missing prop {name} on {node.name}")?;
197 Ok(prop
198 .as_64_list()
199 .map_err(err_to_owned)
200 .context("prop {name} is not a list of u64s")?
201 .collect())
202}
203
204struct OpenhclInfo {
205 vtl0_mmio: Vec<MemoryRange>,
206 config_ranges: Vec<MemoryRange>,
207 partition_memory_map: Vec<AddressRange>,
208 accepted_memory: Vec<MemoryRange>,
209 vtl2_reserved_range: MemoryRange,
210 vtl0_alias_map: Option<u64>,
211 memory_allocation_mode: MemoryAllocationMode,
212 isolation: IsolationType,
213 private_pool_ranges: Vec<MemoryRangeWithNode>,
214}
215
216fn parse_memory_openhcl(node: &Node<'_>) -> anyhow::Result<AddressRange> {
217 let vtl_usage = {
218 let prop = try_find_property(node, "openhcl,memory-type")
219 .context(format!("missing openhcl,memory-type on node {}", node.name))?;
220
221 MemoryVtlType(prop.read_u32(0).map_err(err_to_owned).context(format!(
222 "openhcl memory node {} openhcl,memory-type invalid",
223 node.name
224 ))?)
225 };
226
227 if vtl_usage.ram() {
228 // Parse this entry as memory.
229 let range = parse_memory(node).context("unable to parse base memory")?;
230
231 let igvm_type = {
232 let prop = try_find_property(node, IGVM_DT_IGVM_TYPE_PROPERTY)
233 .context(format!("missing igvm type on node {}", node.name))?;
234 let value = prop
235 .read_u32(0)
236 .map_err(err_to_owned)
237 .context(format!("memory node {} invalid igvm type", node.name))?;
238 MemoryMapEntryType(value as u16)
239 };
240
241 Ok(AddressRange::Memory(Memory {
242 range,
243 vtl_usage,
244 igvm_type,
245 }))
246 } else {
247 // Parse this type as just mmio.
248 let range = {
249 let reg = property_to_u64_vec(node, "reg")?;
250
251 if reg.len() != 2 {
252 bail!("mmio node {} does not have 2 u64s", node.name);
253 }
254
255 let base = reg[0];
256 let len = reg[1];
257 MemoryRange::try_new(base..(base + len)).context("invalid mmio range")?
258 };
259
260 let vtl = match vtl_usage {
261 MemoryVtlType::VTL0_MMIO => Vtl::Vtl0,
262 MemoryVtlType::VTL2_MMIO => Vtl::Vtl2,
263 _ => bail!(
264 "invalid vtl_usage {vtl_usage:?} type for mmio node {}",
265 node.name
266 ),
267 };
268
269 Ok(AddressRange::Mmio(Mmio { range, vtl }))
270 }
271}
272
273fn parse_accepted_memory(node: &Node<'_>) -> anyhow::Result<MemoryRange> {
274 let reg = property_to_u64_vec(node, "reg")?;
275
276 if reg.len() != 2 {
277 bail!("accepted memory node {} does not have 2 u64s", node.name);
278 }
279
280 let base = reg[0];
281 let len = reg[1];
282 MemoryRange::try_new(base..(base + len)).context("invalid preaccepted memory")
283}
284
285fn parse_openhcl(node: &Node<'_>) -> anyhow::Result<OpenhclInfo> {
286 let mut memory = Vec::new();
287 let mut accepted_memory = Vec::new();
288
289 for child in node.children() {
290 let child = child.map_err(err_to_owned).context("child invalid")?;
291
292 match child.name {
293 name if name.starts_with("memory@") => {
294 memory.push(parse_memory_openhcl(&child)?);
295 }
296
297 name if name.starts_with("accepted-memory@") => {
298 accepted_memory.push(parse_accepted_memory(&child)?);
299 }
300
301 name if name.starts_with("memory-allocation-mode") => {}
302
303 _ => {
304 // Ignore other nodes.
305 }
306 }
307 }
308
309 let isolation = {
310 let prop = try_find_property(node, "isolation-type").context("missing isolation-type")?;
311
312 match prop.read_str().map_err(err_to_owned)? {
313 "none" => IsolationType::None,
314 "vbs" => IsolationType::Vbs,
315 "snp" => IsolationType::Snp,
316 "tdx" => IsolationType::Tdx,
317 ty => bail!("invalid isolation-type {ty}"),
318 }
319 };
320
321 let memory_allocation_mode = {
322 let prop = try_find_property(node, "memory-allocation-mode")
323 .context("missing memory-allocation-mode")?;
324
325 match prop.read_str().map_err(err_to_owned)? {
326 "host" => MemoryAllocationMode::Host,
327 "vtl2" => {
328 let memory_size = try_find_property(node, "memory-size")
329 .map(|p| p.read_u64(0))
330 .transpose()
331 .map_err(err_to_owned)?;
332
333 let mmio_size = try_find_property(node, "mmio-size")
334 .map(|p| p.read_u64(0))
335 .transpose()
336 .map_err(err_to_owned)?;
337
338 MemoryAllocationMode::Vtl2 {
339 memory_size,
340 mmio_size,
341 }
342 }
343 mode => bail!("invalid memory-allocation-mode {mode}"),
344 }
345 };
346
347 memory.sort_by_key(|r| r.range().start());
348 accepted_memory.sort_by_key(|r| r.start());
349
350 // Report config ranges in a separate vec as well, for convenience.
351 let config_ranges = memory
352 .iter()
353 .filter_map(|entry| {
354 if entry.vtl_usage() == MemoryVtlType::VTL2_CONFIG {
355 Some(*entry.range())
356 } else {
357 None
358 }
359 })
360 .collect();
361
362 // Report the reserved range. There should only be one.
363 let vtl2_reserved_range = {
364 let mut reserved_range_iter = memory.iter().filter_map(|entry| {
365 if entry.vtl_usage() == MemoryVtlType::VTL2_RESERVED {
366 Some(*entry.range())
367 } else {
368 None
369 }
370 });
371
372 let reserved_range = reserved_range_iter.next().unwrap_or(MemoryRange::EMPTY);
373
374 if reserved_range_iter.next().is_some() {
375 bail!("multiple VTL2 reserved ranges found");
376 }
377
378 reserved_range
379 };
380
381 // Report private pool ranges in a separate vec, for convenience.
382 let private_pool_ranges = memory
383 .iter()
384 .filter_map(|entry| match entry {
385 AddressRange::Memory(memory) => {
386 if memory.vtl_usage == MemoryVtlType::VTL2_GPA_POOL {
387 Some(memory.range.clone())
388 } else {
389 None
390 }
391 }
392 AddressRange::Mmio(_) => None,
393 })
394 .collect();
395
396 let vtl0_alias_map = try_find_property(node, "vtl0-alias-map")
397 .map(|prop| prop.read_u64(0).map_err(err_to_owned))
398 .transpose()
399 .context("unable to read vtl0-alias-map")?;
400
401 // Extract vmbus mmio information from the overall memory map.
402 let vtl0_mmio = memory
403 .iter()
404 .filter_map(|range| match range {
405 AddressRange::Memory(_) => None,
406 AddressRange::Mmio(mmio) => match mmio.vtl {
407 Vtl::Vtl0 => Some(mmio.range),
408 Vtl::Vtl2 => None,
409 },
410 })
411 .collect();
412
413 Ok(OpenhclInfo {
414 vtl0_mmio,
415 config_ranges,
416 partition_memory_map: memory,
417 accepted_memory,
418 vtl2_reserved_range,
419 vtl0_alias_map,
420 memory_allocation_mode,
421 isolation,
422 private_pool_ranges,
423 })
424}
425
426fn parse_cpus(node: &Node<'_>) -> anyhow::Result<Vec<Cpu>> {
427 let address_cells = address_cells(node)?;
428
429 if address_cells > 2 {
430 bail!("cpus address-cells > 2 unexpected");
431 }
432
433 let mut cpus = Vec::new();
434
435 for cpu in node.children() {
436 let cpu = cpu.map_err(err_to_owned).context("cpu invalid")?;
437 let reg = try_find_property(&cpu, "reg").context("{cpu.name} missing reg")?;
438
439 let reg = match address_cells {
440 1 => reg.read_u32(0).map_err(err_to_owned)? as u64,
441 2 => reg.read_u64(0).map_err(err_to_owned)?,
442 _ => unreachable!(),
443 };
444
445 let vnode = try_find_property(&cpu, "numa-node-id")
446 .context("{cpu.name} missing numa-node-id")?
447 .read_u32(0)
448 .map_err(err_to_owned)?;
449
450 cpus.push(Cpu { reg, vnode });
451 }
452
453 Ok(cpus)
454}
455
456/// Parse a single memory node.
457fn parse_memory(node: &Node<'_>) -> anyhow::Result<MemoryRangeWithNode> {
458 let reg = property_to_u64_vec(node, "reg")?;
459
460 if reg.len() != 2 {
461 bail!("memory node {} does not have 2 u64s", node.name);
462 }
463
464 let base = reg[0];
465 let len = reg[1];
466 let numa_node_id = try_find_property(node, "numa-node-id")
467 .context("{node.name} missing numa-node-id")?
468 .read_u32(0)
469 .map_err(err_to_owned)
470 .context("unable to read numa-node-id")?;
471
472 Ok(MemoryRangeWithNode {
473 range: MemoryRange::try_new(base..base + len).context("invalid memory range")?,
474 vnode: numa_node_id,
475 })
476}
477
478/// Parse GIC config
479fn parse_gic(node: &Node<'_>) -> anyhow::Result<GicInfo> {
480 let reg = property_to_u64_vec(node, "reg")?;
481
482 if reg.len() != 4 {
483 bail!("gic node {} does not have 4 u64s", node.name);
484 }
485
486 Ok(GicInfo {
487 gic_distributor_base: reg[0],
488 gic_redistributors_base: reg[2],
489 })
490}
491
492impl ParsedBootDtInfo {
493 /// Read parameters passed via device tree by openhcl_boot, at
494 /// /sys/firmware/fdt.
495 ///
496 /// The device tree is expected to be well formed from the bootloader, so
497 /// any errors here are not expected.
498 pub fn new() -> anyhow::Result<Self> {
499 let raw = fs_err::read("/sys/firmware/fdt").context("reading fdt")?;
500 Self::new_from_raw(&raw)
501 }
502
503 fn new_from_raw(raw: &[u8]) -> anyhow::Result<Self> {
504 let mut cpus = Vec::new();
505 let mut vtl0_mmio = Vec::new();
506 let mut config_ranges = Vec::new();
507 let mut vtl2_memory = Vec::new();
508 let mut physical_address_bits = None;
509 let mut gic = None;
510 let mut partition_memory_map = Vec::new();
511 let mut accepted_ranges = Vec::new();
512 let mut vtl0_alias_map = None;
513 let mut memory_allocation_mode = MemoryAllocationMode::Host;
514 let mut isolation = IsolationType::None;
515 let mut vtl2_reserved_range = MemoryRange::EMPTY;
516 let mut private_pool_ranges = Vec::new();
517
518 let parser = Parser::new(raw)
519 .map_err(err_to_owned)
520 .context("failed to create fdt parser")?;
521
522 for child in parser
523 .root()
524 .map_err(err_to_owned)
525 .context("root invalid")?
526 .children()
527 {
528 let child = child.map_err(err_to_owned).context("child invalid")?;
529
530 match child.name {
531 "cpus" => {
532 cpus = parse_cpus(&child)?;
533
534 // Read physical address bits if present.
535 if let Some(prop) = try_find_property(&child, "pa_bits") {
536 physical_address_bits = Some(prop.read_u32(0).map_err(err_to_owned)? as u8);
537 }
538 }
539
540 "openhcl" => {
541 let OpenhclInfo {
542 vtl0_mmio: n_vtl0_mmio,
543 config_ranges: n_config_ranges,
544 partition_memory_map: n_partition_memory_map,
545 vtl2_reserved_range: n_vtl2_reserved_range,
546 accepted_memory: n_accepted_memory,
547 vtl0_alias_map: n_vtl0_alias_map,
548 memory_allocation_mode: n_memory_allocation_mode,
549 isolation: n_isolation,
550 private_pool_ranges: n_private_pool_ranges,
551 } = parse_openhcl(&child)?;
552 vtl0_mmio = n_vtl0_mmio;
553 config_ranges = n_config_ranges;
554 partition_memory_map = n_partition_memory_map;
555 accepted_ranges = n_accepted_memory;
556 vtl0_alias_map = n_vtl0_alias_map;
557 memory_allocation_mode = n_memory_allocation_mode;
558 isolation = n_isolation;
559 vtl2_reserved_range = n_vtl2_reserved_range;
560 private_pool_ranges = n_private_pool_ranges;
561 }
562
563 _ if child.name.starts_with("memory@") => {
564 vtl2_memory.push(parse_memory(&child)?);
565 }
566
567 _ if child.name.starts_with("intc@") => {
568 // TODO: make sure we are on aarch64
569 gic = Some(parse_gic(&child)?);
570 }
571
572 _ => {
573 // Ignore other nodes.
574 }
575 }
576 }
577
578 vtl2_memory.sort_by_key(|r| r.range.start());
579
580 Ok(Self {
581 cpus,
582 vtl0_mmio,
583 config_ranges,
584 vtl2_memory,
585 partition_memory_map,
586 physical_address_bits,
587 vtl0_alias_map,
588 accepted_ranges,
589 gic,
590 memory_allocation_mode,
591 isolation,
592 vtl2_reserved_range,
593 private_pool_ranges,
594 })
595 }
596}
597
598/// Boot times reported by the bootloader.
599#[derive(Debug, Clone, Copy, PartialEq, Eq)]
600pub struct BootTimes {
601 /// Kernel start time.
602 pub start: Option<u64>,
603 /// Kernel end time.
604 pub end: Option<u64>,
605 /// Sidecar start time.
606 pub sidecar_start: Option<u64>,
607 /// Sidecar end time.
608 pub sidecar_end: Option<u64>,
609}
610
611impl BootTimes {
612 /// Read the boot times passed via device tree by openhcl_boot, at
613 /// /sys/firmware/fdt.
614 ///
615 /// The device tree is expected to be well formed from the bootloader, so
616 /// any errors here are not expected.
617 pub fn new() -> anyhow::Result<Self> {
618 let raw = fs_err::read("/sys/firmware/fdt").context("reading fdt")?;
619 Self::new_from_raw(&raw)
620 }
621
622 fn new_from_raw(raw: &[u8]) -> anyhow::Result<Self> {
623 let mut start = None;
624 let mut end = None;
625 let mut sidecar_start = None;
626 let mut sidecar_end = None;
627 let parser = Parser::new(raw)
628 .map_err(err_to_owned)
629 .context("failed to create fdt parser")?;
630
631 let root = parser
632 .root()
633 .map_err(err_to_owned)
634 .context("root invalid")?;
635
636 if let Some(prop) = try_find_property(&root, "reftime_boot_start") {
637 start = Some(prop.read_u64(0).map_err(err_to_owned)?);
638 }
639
640 if let Some(prop) = try_find_property(&root, "reftime_boot_end") {
641 end = Some(prop.read_u64(0).map_err(err_to_owned)?);
642 }
643
644 if let Some(prop) = try_find_property(&root, "reftime_sidecar_start") {
645 sidecar_start = Some(prop.read_u64(0).map_err(err_to_owned)?);
646 }
647
648 if let Some(prop) = try_find_property(&root, "reftime_sidecar_end") {
649 sidecar_end = Some(prop.read_u64(0).map_err(err_to_owned)?);
650 }
651
652 Ok(Self {
653 start,
654 end,
655 sidecar_start,
656 sidecar_end,
657 })
658 }
659}
660
661mod inspect_helpers {
662 use super::*;
663
664 pub(super) fn memory_internal(ranges: &[AddressRange]) -> impl Inspect + '_ {
665 inspect::iter_by_key(ranges.iter().map(|entry| (entry.range(), entry)))
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672 use fdt::builder::Builder;
673
674 fn build_dt(info: &ParsedBootDtInfo) -> anyhow::Result<Vec<u8>> {
675 let mut buf = vec![0; 4096];
676
677 let mut builder = Builder::new(fdt::builder::BuilderConfig {
678 blob_buffer: &mut buf,
679 string_table_cap: 1024,
680 memory_reservations: &[],
681 })?;
682 let p_address_cells = builder.add_string("#address-cells")?;
683 let p_size_cells = builder.add_string("#size-cells")?;
684 let p_reg = builder.add_string("reg")?;
685 let p_device_type = builder.add_string("device_type")?;
686 let p_compatible = builder.add_string("compatible")?;
687 let p_ranges = builder.add_string("ranges")?;
688 let p_numa_node_id = builder.add_string("numa-node-id")?;
689 let p_pa_bits = builder.add_string("pa_bits")?;
690 let p_igvm_type = builder.add_string(IGVM_DT_IGVM_TYPE_PROPERTY)?;
691 let p_openhcl_memory = builder.add_string("openhcl,memory-type")?;
692
693 let mut root_builder = builder
694 .start_node("")?
695 .add_u32(p_address_cells, 2)?
696 .add_u32(p_size_cells, 2)?
697 .add_str(p_compatible, "microsoft,openvmm")?;
698
699 let mut cpu_builder = root_builder
700 .start_node("cpus")?
701 .add_u32(p_address_cells, 1)?
702 .add_u32(p_size_cells, 0)?;
703
704 if let Some(pa_bits) = info.physical_address_bits {
705 cpu_builder = cpu_builder.add_u32(p_pa_bits, pa_bits as u32)?;
706 }
707
708 // add cpus
709 for (index, cpu) in info.cpus.iter().enumerate() {
710 let name = format!("cpu@{}", index + 1);
711
712 cpu_builder = cpu_builder
713 .start_node(&name)?
714 .add_str(p_device_type, "cpu")?
715 .add_u32(p_reg, cpu.reg as u32)?
716 .add_u32(p_numa_node_id, cpu.vnode)?
717 .end_node()?;
718 }
719
720 root_builder = cpu_builder.end_node()?;
721
722 // add memory, in reverse order.
723 for memory in info.vtl2_memory.iter().rev() {
724 let name = format!("memory@{:x}", memory.range.start());
725
726 root_builder = root_builder
727 .start_node(&name)?
728 .add_str(p_device_type, "memory")?
729 .add_u64_list(p_reg, [memory.range.start(), memory.range.len()])?
730 .add_u32(p_numa_node_id, memory.vnode)?
731 .end_node()?;
732 }
733
734 // GIC
735 if let Some(gic) = info.gic {
736 let p_interrupt_cells = root_builder.add_string("#interrupt-cells")?;
737 let p_redist_regions = root_builder.add_string("#redistributor-regions")?;
738 let p_redist_stride = root_builder.add_string("redistributor-stride")?;
739 let p_interrupt_controller = root_builder.add_string("interrupt-controller")?;
740 let p_phandle = root_builder.add_string("phandle")?;
741 let name = format!("intc@{}", gic.gic_distributor_base);
742 root_builder = root_builder
743 .start_node(name.as_ref())?
744 .add_str(p_compatible, "arm,gic-v3")?
745 .add_u32(p_redist_regions, 1)?
746 .add_u64(p_redist_stride, 0)?
747 .add_u64_array(
748 p_reg,
749 &[gic.gic_distributor_base, 0, gic.gic_redistributors_base, 0],
750 )?
751 .add_u32(p_address_cells, 2)?
752 .add_u32(p_size_cells, 2)?
753 .add_u32(p_interrupt_cells, 3)?
754 .add_null(p_interrupt_controller)?
755 .add_u32(p_phandle, 1)?
756 .add_null(p_ranges)?
757 .end_node()?;
758 }
759
760 let mut openhcl_builder = root_builder.start_node("openhcl")?;
761 let p_isolation_type = openhcl_builder.add_string("isolation-type")?;
762 openhcl_builder = openhcl_builder.add_str(
763 p_isolation_type,
764 match info.isolation {
765 IsolationType::None => "none",
766 IsolationType::Vbs => "vbs",
767 IsolationType::Snp => "snp",
768 IsolationType::Tdx => "tdx",
769 },
770 )?;
771
772 let p_memory_allocation_mode = openhcl_builder.add_string("memory-allocation-mode")?;
773 match info.memory_allocation_mode {
774 MemoryAllocationMode::Host => {
775 openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "host")?;
776 }
777 MemoryAllocationMode::Vtl2 {
778 memory_size,
779 mmio_size,
780 } => {
781 let p_memory_size = openhcl_builder.add_string("memory-size")?;
782 let p_mmio_size = openhcl_builder.add_string("mmio-size")?;
783 openhcl_builder = openhcl_builder.add_str(p_memory_allocation_mode, "vtl2")?;
784 if let Some(memory_size) = memory_size {
785 openhcl_builder = openhcl_builder.add_u64(p_memory_size, memory_size)?;
786 }
787 if let Some(mmio_size) = mmio_size {
788 openhcl_builder = openhcl_builder.add_u64(p_mmio_size, mmio_size)?;
789 }
790 }
791 }
792
793 if let Some(data) = info.vtl0_alias_map {
794 let p_vtl0_alias_map = openhcl_builder.add_string("vtl0-alias-map")?;
795 openhcl_builder = openhcl_builder.add_u64(p_vtl0_alias_map, data)?;
796 }
797
798 openhcl_builder = openhcl_builder
799 .start_node("vmbus-vtl0")?
800 .add_u32(p_address_cells, 2)?
801 .add_u32(p_size_cells, 2)?
802 .add_str(p_compatible, "microsoft,vmbus")?
803 .add_u64_list(
804 p_ranges,
805 info.vtl0_mmio
806 .iter()
807 .flat_map(|r| [r.start(), r.start(), r.len()]),
808 )?
809 .end_node()?;
810
811 for range in &info.partition_memory_map {
812 let name = format!("memory@{:x}", range.range().start());
813
814 let node_builder = openhcl_builder
815 .start_node(&name)?
816 .add_str(p_device_type, "memory")?
817 .add_u64_list(p_reg, [range.range().start(), range.range().len()])?
818 .add_u32(p_openhcl_memory, range.vtl_usage().0)?;
819
820 openhcl_builder = match range {
821 AddressRange::Memory(memory) => {
822 // Add as a memory node, with numa info and igvm type.
823 node_builder
824 .add_u32(p_numa_node_id, memory.range.vnode)?
825 .add_u32(p_igvm_type, memory.igvm_type.0 as u32)?
826 }
827 AddressRange::Mmio(_) => {
828 // Nothing to do here, mmio already contains the min
829 // required info of range and vtl via vtl_usage.
830 node_builder
831 }
832 }
833 .end_node()?;
834 }
835
836 for range in &info.accepted_ranges {
837 let name = format!("accepted-memory@{:x}", range.start());
838
839 openhcl_builder = openhcl_builder
840 .start_node(&name)?
841 .add_str(p_device_type, "memory")?
842 .add_u64_list(p_reg, [range.start(), range.len()])?
843 .end_node()?;
844 }
845
846 root_builder = openhcl_builder.end_node()?;
847
848 root_builder.end_node()?.build(info.cpus[0].reg as u32)?;
849
850 Ok(buf)
851 }
852
853 #[test]
854 fn test_basic() {
855 let orig_info = ParsedBootDtInfo {
856 cpus: (0..4).map(|i| Cpu { reg: i, vnode: 0 }).collect(),
857 vtl2_memory: vec![
858 MemoryRangeWithNode {
859 range: MemoryRange::new(0x10000..0x20000),
860 vnode: 0,
861 },
862 MemoryRangeWithNode {
863 range: MemoryRange::new(0x20000..0x30000),
864 vnode: 1,
865 },
866 ],
867 partition_memory_map: vec![
868 AddressRange::Memory(Memory {
869 range: MemoryRangeWithNode {
870 range: MemoryRange::new(0..0x1000),
871 vnode: 0,
872 },
873 vtl_usage: MemoryVtlType::VTL0,
874 igvm_type: MemoryMapEntryType::MEMORY,
875 }),
876 AddressRange::Mmio(Mmio {
877 range: MemoryRange::new(0x1000..0x2000),
878 vtl: Vtl::Vtl0,
879 }),
880 AddressRange::Mmio(Mmio {
881 range: MemoryRange::new(0x3000..0x4000),
882 vtl: Vtl::Vtl0,
883 }),
884 AddressRange::Memory(Memory {
885 range: MemoryRangeWithNode {
886 range: MemoryRange::new(0x10000..0x20000),
887 vnode: 0,
888 },
889 vtl_usage: MemoryVtlType::VTL2_RAM,
890 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
891 }),
892 AddressRange::Memory(Memory {
893 range: MemoryRangeWithNode {
894 range: MemoryRange::new(0x20000..0x30000),
895 vnode: 1,
896 },
897 vtl_usage: MemoryVtlType::VTL2_CONFIG,
898 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
899 }),
900 AddressRange::Memory(Memory {
901 range: MemoryRangeWithNode {
902 range: MemoryRange::new(0x30000..0x40000),
903 vnode: 1,
904 },
905 vtl_usage: MemoryVtlType::VTL2_CONFIG,
906 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
907 }),
908 AddressRange::Memory(Memory {
909 range: MemoryRangeWithNode {
910 range: MemoryRange::new(0x40000..0x50000),
911 vnode: 1,
912 },
913 vtl_usage: MemoryVtlType::VTL2_RESERVED,
914 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
915 }),
916 AddressRange::Memory(Memory {
917 range: MemoryRangeWithNode {
918 range: MemoryRange::new(0x60000..0x70000),
919 vnode: 0,
920 },
921 vtl_usage: MemoryVtlType::VTL2_GPA_POOL,
922 igvm_type: MemoryMapEntryType::VTL2_PROTECTABLE,
923 }),
924 AddressRange::Memory(Memory {
925 range: MemoryRangeWithNode {
926 range: MemoryRange::new(0x1000000..0x2000000),
927 vnode: 0,
928 },
929 vtl_usage: MemoryVtlType::VTL0,
930 igvm_type: MemoryMapEntryType::MEMORY,
931 }),
932 AddressRange::Mmio(Mmio {
933 range: MemoryRange::new(0x3000000..0x4000000),
934 vtl: Vtl::Vtl2,
935 }),
936 ],
937 vtl0_mmio: vec![
938 MemoryRange::new(0x1000..0x2000),
939 MemoryRange::new(0x3000..0x4000),
940 ],
941 config_ranges: vec![
942 MemoryRange::new(0x20000..0x30000),
943 MemoryRange::new(0x30000..0x40000),
944 ],
945 physical_address_bits: Some(48),
946 vtl0_alias_map: Some(1 << 48),
947 gic: Some(GicInfo {
948 gic_distributor_base: 0x10000,
949 gic_redistributors_base: 0x20000,
950 }),
951 accepted_ranges: vec![
952 MemoryRange::new(0x10000..0x20000),
953 MemoryRange::new(0x1000000..0x1500000),
954 ],
955 memory_allocation_mode: MemoryAllocationMode::Vtl2 {
956 memory_size: Some(0x1000),
957 mmio_size: Some(0x2000),
958 },
959 isolation: IsolationType::Vbs,
960 vtl2_reserved_range: MemoryRange::new(0x40000..0x50000),
961 private_pool_ranges: vec![MemoryRangeWithNode {
962 range: MemoryRange::new(0x60000..0x70000),
963 vnode: 0,
964 }],
965 };
966
967 let dt = build_dt(&orig_info).unwrap();
968 let parsed = ParsedBootDtInfo::new_from_raw(&dt).unwrap();
969
970 assert_eq!(orig_info, parsed);
971 }
972
973 fn build_boottime_dt(boot_times: BootTimes) -> anyhow::Result<Vec<u8>> {
974 let mut buf = vec![0; 4096];
975
976 let mut builder = Builder::new(fdt::builder::BuilderConfig {
977 blob_buffer: &mut buf,
978 string_table_cap: 1024,
979 memory_reservations: &[],
980 })?;
981 let p_address_cells = builder.add_string("#address-cells")?;
982 let p_size_cells = builder.add_string("#size-cells")?;
983 let p_reftime_boot_start = builder.add_string("reftime_boot_start")?;
984 let p_reftime_boot_end = builder.add_string("reftime_boot_end")?;
985 let p_reftime_sidecar_start = builder.add_string("reftime_sidecar_start")?;
986 let p_reftime_sidecar_end = builder.add_string("reftime_sidecar_end")?;
987
988 let mut root_builder = builder
989 .start_node("")?
990 .add_u32(p_address_cells, 2)?
991 .add_u32(p_size_cells, 2)?;
992
993 if let Some(start) = boot_times.start {
994 root_builder = root_builder.add_u64(p_reftime_boot_start, start)?;
995 }
996
997 if let Some(end) = boot_times.end {
998 root_builder = root_builder.add_u64(p_reftime_boot_end, end)?;
999 }
1000
1001 if let Some(start) = boot_times.sidecar_start {
1002 root_builder = root_builder.add_u64(p_reftime_sidecar_start, start)?;
1003 }
1004
1005 if let Some(end) = boot_times.sidecar_end {
1006 root_builder = root_builder.add_u64(p_reftime_sidecar_end, end)?;
1007 }
1008
1009 root_builder.end_node()?.build(0)?;
1010
1011 Ok(buf)
1012 }
1013
1014 #[test]
1015 fn test_basic_boottime() {
1016 let orig_info = BootTimes {
1017 start: Some(0x1000),
1018 end: Some(0x2000),
1019 sidecar_start: Some(0x3000),
1020 sidecar_end: Some(0x4000),
1021 };
1022
1023 let dt = build_boottime_dt(orig_info).unwrap();
1024 let parsed = BootTimes::new_from_raw(&dt).unwrap();
1025
1026 assert_eq!(orig_info, parsed);
1027
1028 // test no boot times.
1029 let orig_info = BootTimes {
1030 start: None,
1031 end: None,
1032 sidecar_start: None,
1033 sidecar_end: None,
1034 };
1035
1036 let dt = build_boottime_dt(orig_info).unwrap();
1037 let parsed = BootTimes::new_from_raw(&dt).unwrap();
1038
1039 assert_eq!(orig_info, parsed);
1040 }
1041}
1042