microsoft/openvmm

Public

mirrored from https://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
b8910f97c258ca39c8f2e535fbc241ea1e1be3ff

Branches

Tags

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

Clone

HTTPS

Download ZIP

openhcl/bootloader_fdt_parser/src/lib.rs

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