microsoft/openvmm

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dab9fd2b38f9332db59020cce4acb488e6febb8d

Branches

Tags

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

Clone

HTTPS

Download ZIP

openhcl/bootloader_fdt_parser/src/lib.rs

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