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/host_fdt_parser/src/lib.rs

1763lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Common parsing code for parsing the device tree provided by the host.
5//! Note that is is not a generic device tree parser, but parses the device tree
6//! for devices and concepts specific to underhill.
7//!
8//! Notably, we search for IGVM specific extensions to nodes, defined here:
9//! [`igvm_defs::dt`].
10
11#![cfg_attr(not(any(test, feature = "std")), no_std)]
12#![forbid(unsafe_code)]
13#![warn(missing_docs)]
14
15use arrayvec::ArrayString;
16use arrayvec::ArrayVec;
17use core::fmt::Display;
18use core::fmt::Write;
19use core::mem::size_of;
20use hvdef::HV_PAGE_SIZE;
21use igvm_defs::MemoryMapEntryType;
22#[cfg(feature = "inspect")]
23use inspect::Inspect;
24use memory_range::MemoryRange;
25
26/// Information about VMBUS.
27#[derive(Clone, Debug, PartialEq, Eq)]
28#[cfg_attr(feature = "inspect", derive(Inspect))]
29pub struct VmbusInfo {
30 /// Parsed sorted mmio ranges from the device tree.
31 #[cfg_attr(feature = "inspect", inspect(with = "inspect_helpers::mmio_internal"))]
32 pub mmio: ArrayVec<MemoryRange, 2>,
33 /// Connection ID for the vmbus root device.
34 #[cfg_attr(feature = "inspect", inspect(hex))]
35 pub connection_id: u32,
36}
37
38/// Information about the GIC.
39#[derive(Clone, Debug, PartialEq, Eq)]
40#[cfg_attr(feature = "inspect", derive(Inspect))]
41pub struct GicInfo {
42 /// GIC distributor base
43 #[cfg_attr(feature = "inspect", inspect(hex))]
44 pub gic_distributor_base: u64,
45 /// GIC distributor size
46 #[cfg_attr(feature = "inspect", inspect(hex))]
47 pub gic_distributor_size: u64,
48 /// GIC redistributors base
49 #[cfg_attr(feature = "inspect", inspect(hex))]
50 pub gic_redistributors_base: u64,
51 /// GIC redistributor block size
52 #[cfg_attr(feature = "inspect", inspect(hex))]
53 pub gic_redistributors_size: u64,
54 /// GIC redistributor size
55 #[cfg_attr(feature = "inspect", inspect(hex))]
56 pub gic_redistributor_stride: u64,
57}
58
59/// Errors returned by parsing.
60#[derive(Debug)]
61pub struct Error<'a>(ErrorKind<'a>);
62
63impl<'a> Display for Error<'a> {
64 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
65 f.write_fmt(format_args!("Parsing failed due to: {}", self.0))
66 }
67}
68
69// TODO: Once core::error::Error is stablized, we can remove this feature gate.
70#[cfg(feature = "std")]
71impl<'a> std::error::Error for Error<'a> {}
72
73#[derive(Debug)]
74enum ErrorKind<'a> {
75 Dt(fdt::parser::Error<'a>),
76 Node {
77 parent_name: &'a str,
78 error: fdt::parser::Error<'a>,
79 },
80 PropMissing {
81 node_name: &'a str,
82 prop_name: &'static str,
83 },
84 Prop(fdt::parser::Error<'a>),
85 TooManyCpus,
86 MemoryRegUnaligned {
87 node_name: &'a str,
88 base: u64,
89 len: u64,
90 },
91 MemoryRegOverlap {
92 lower: MemoryEntry,
93 upper: MemoryEntry,
94 },
95 TooManyMemoryEntries,
96 PropInvalidU32 {
97 node_name: &'a str,
98 prop_name: &'a str,
99 expected: u32,
100 actual: u32,
101 },
102 PropInvalidStr {
103 node_name: &'a str,
104 prop_name: &'a str,
105 expected: &'a str,
106 actual: &'a str,
107 },
108 UnexpectedVmbusVtl {
109 node_name: &'a str,
110 vtl: u32,
111 },
112 MultipleVmbusNode {
113 node_name: &'a str,
114 },
115 VmbusRangesChildParent {
116 node_name: &'a str,
117 child_base: u64,
118 parent_base: u64,
119 },
120 VmbusRangesNotAligned {
121 node_name: &'a str,
122 base: u64,
123 len: u64,
124 },
125 TooManyVmbusMmioRanges {
126 node_name: &'a str,
127 ranges: usize,
128 },
129 VmbusMmioOverlapsRam {
130 mmio: MemoryRange,
131 ram: MemoryEntry,
132 },
133 VmbusMmioOverlapsVmbusMmio {
134 mmio_a: MemoryRange,
135 mmio_b: MemoryRange,
136 },
137 CmdlineSize,
138 UnexpectedMemoryAllocationMode {
139 mode: &'a str,
140 },
141}
142
143impl<'a> Display for ErrorKind<'a> {
144 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
145 match self {
146 ErrorKind::Dt(e) => f.write_fmt(format_args!("invalid device tree: {}", e)),
147 ErrorKind::Node { parent_name, error } => {
148 f.write_fmt(format_args!("invalid device tree node with parent {parent_name}: {error}"))
149 }
150 ErrorKind::PropMissing {
151 node_name,
152 prop_name,
153 } => f.write_fmt(format_args!(
154 "{node_name} did not have the following required property {prop_name}",
155 )),
156 ErrorKind::Prop(e) => f.write_fmt(format_args!("reading node property failed: {e}")),
157 ErrorKind::TooManyCpus => {
158 f.write_str("device tree contained more enabled CPUs than can be parsed")
159 }
160 ErrorKind::MemoryRegUnaligned {
161 node_name,
162 base,
163 len,
164 } => f.write_fmt(format_args!(
165 "memory node {node_name} contains 4K unaligned base {base} or len {len}"
166 )),
167 ErrorKind::MemoryRegOverlap { lower, upper, } => {
168 f.write_fmt(format_args!("ram at {}..{} of type {:?} overlaps ram at {}..{} of type {:?}", lower.range.start(), lower.range.end(), lower.mem_type, upper.range.start(), upper.range.end(), upper.mem_type))
169 }
170 ErrorKind::TooManyMemoryEntries => {
171 f.write_str("device tree contained more memory ranges than can be parsed")
172 }
173 ErrorKind::PropInvalidU32 { node_name, prop_name, expected, actual } => f.write_fmt(format_args!("{node_name} had an invalid u32 value for {prop_name}: expected {expected}, actual {actual}")),
174 ErrorKind::PropInvalidStr { node_name, prop_name, expected, actual } => f.write_fmt(format_args!("{node_name} had an invalid str value for {prop_name}: expected {expected}, actual {actual}")),
175 ErrorKind::UnexpectedVmbusVtl { node_name, vtl } => f.write_fmt(format_args!("{node_name} has an unexpected vtl {vtl}")),
176 ErrorKind::MultipleVmbusNode { node_name } => f.write_fmt(format_args!("{node_name} specifies a duplicate vmbus node")),
177 ErrorKind::VmbusRangesChildParent { node_name, child_base, parent_base } => f.write_fmt(format_args!("vmbus {node_name} ranges child base {child_base} does not match parent {parent_base}")),
178 ErrorKind::VmbusRangesNotAligned { node_name, base, len } => f.write_fmt(format_args!("vmbus {node_name} base {base} or len {len} not aligned to 4K")),
179 ErrorKind::TooManyVmbusMmioRanges { node_name, ranges } => f.write_fmt(format_args!("vmbus {node_name} has more than 2 mmio ranges {ranges}")),
180 ErrorKind::VmbusMmioOverlapsRam { mmio, ram } => {
181 f.write_fmt(format_args!("vmbus mmio at {}..{} overlaps ram at {}..{}", mmio.start(), mmio.end(), ram.range.start(), ram.range.end()))
182 }
183 ErrorKind::VmbusMmioOverlapsVmbusMmio { mmio_a, mmio_b } => {
184 f.write_fmt(format_args!("vmbus mmio at {}..{} overlaps vmbus mmio at {}..{}", mmio_a.start(), mmio_a.end(), mmio_b.start(), mmio_b.end()))
185 }
186 ErrorKind::CmdlineSize => f.write_str("commandline too small to parse /chosen bootargs"),
187 ErrorKind::UnexpectedMemoryAllocationMode { mode } => f.write_fmt(format_args!("unexpected memory allocation mode: {}", mode)),
188 }
189 }
190}
191
192const COM3_REG_BASE: u64 = 0x3E8;
193
194/// Struct containing parsed device tree information.
195#[derive(Debug, PartialEq, Eq)]
196#[cfg_attr(feature = "inspect", derive(Inspect))]
197pub struct ParsedDeviceTree<
198 const MAX_MEMORY_ENTRIES: usize,
199 const MAX_CPU_ENTRIES: usize,
200 const MAX_COMMAND_LINE_SIZE: usize,
201 const MAX_ENTROPY_SIZE: usize,
202> {
203 /// Total size of the parsed device tree, in bytes.
204 pub device_tree_size: usize,
205 /// Parsed sorted memory ranges from the device tree.
206 #[cfg_attr(
207 feature = "inspect",
208 inspect(with = "inspect_helpers::memory_internal")
209 )]
210 pub memory: ArrayVec<MemoryEntry, MAX_MEMORY_ENTRIES>,
211 /// Boot cpu physical id. On X64, this is the APIC id of the BSP.
212 #[cfg_attr(feature = "inspect", inspect(hex))]
213 pub boot_cpuid_phys: u32,
214 /// Information for enabled cpus.
215 #[cfg_attr(feature = "inspect", inspect(iter_by_index))]
216 pub cpus: ArrayVec<CpuEntry, MAX_CPU_ENTRIES>,
217 /// VMBUS info for VTL0.
218 pub vmbus_vtl0: Option<VmbusInfo>,
219 /// VMBUS info for VTL2.
220 pub vmbus_vtl2: Option<VmbusInfo>,
221 /// Command line contained in the `/chosen` node.
222 /// FUTURE: return more information from the chosen node.
223 #[cfg_attr(feature = "inspect", inspect(display))]
224 pub command_line: ArrayString<MAX_COMMAND_LINE_SIZE>,
225 /// Is a com3 device present
226 pub com3_serial: bool,
227 /// GIC information
228 pub gic: Option<GicInfo>,
229 /// The vtl2 memory allocation mode OpenHCL should use for memory.
230 pub memory_allocation_mode: MemoryAllocationMode,
231 /// Entropy from the host to be used by the OpenHCL kernel
232 #[cfg_attr(feature = "inspect", inspect(with = "Option::is_some"))]
233 pub entropy: Option<ArrayVec<u8, MAX_ENTROPY_SIZE>>,
234}
235
236/// The memory allocation mode provided by the host. This determines how OpenHCL
237/// will allocate memory for itself from the partition memory map.
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239#[cfg_attr(feature = "inspect", derive(Inspect))]
240#[cfg_attr(feature = "inspect", inspect(external_tag))]
241pub enum MemoryAllocationMode {
242 /// Use the host provided memory topology, and use VTL2_PROTECTABLE entries
243 /// as VTL2 ram. This is the default if no
244 /// `openhcl/memory-allocation-property` mode is provided by the host.
245 Host,
246 /// Allow VTL2 to select its own ranges from the address space to use for
247 /// memory, with a size provided by the host.
248 Vtl2 {
249 /// The number of bytes VTL2 should allocate for memory for itself.
250 /// Encoded as `openhcl/memory-size` in device tree.
251 memory_size: u64,
252 /// The number of bytes VTL2 should allocate for mmio for itself.
253 /// Encoded as `openhcl/mmio-size` in device tree.
254 mmio_size: u64,
255 },
256}
257
258/// Struct containing parsed memory information.
259#[derive(Copy, Clone, Debug, PartialEq, Eq)]
260#[cfg_attr(feature = "inspect", derive(Inspect))]
261pub struct MemoryEntry {
262 /// The range of addresses covered by this entry.
263 pub range: MemoryRange,
264 /// The type of memory of this entry.
265 #[cfg_attr(
266 feature = "inspect",
267 inspect(with = "inspect_helpers::inspect_memory_map_entry_type")
268 )]
269 pub mem_type: MemoryMapEntryType,
270 /// The numa node id of this entry.
271 pub vnode: u32,
272}
273
274/// Struct containing parsed CPU information.
275#[derive(Copy, Clone, Debug, PartialEq, Eq)]
276#[cfg_attr(feature = "inspect", derive(Inspect))]
277pub struct CpuEntry {
278 /// Architecture specific "reg" value for this CPU.
279 /// For x64, this is the APIC ID.
280 /// For ARM v8 64-bit, this should match the MPIDR_EL1 register affinity bits.
281 #[cfg_attr(feature = "inspect", inspect(hex))]
282 pub reg: u64,
283 /// Numa node id for this CPU.
284 pub vnode: u32,
285}
286
287impl<
288 'a,
289 'b,
290 const MAX_MEMORY_ENTRIES: usize,
291 const MAX_CPU_ENTRIES: usize,
292 const MAX_COMMAND_LINE_SIZE: usize,
293 const MAX_ENTROPY_SIZE: usize,
294 >
295 ParsedDeviceTree<MAX_MEMORY_ENTRIES, MAX_CPU_ENTRIES, MAX_COMMAND_LINE_SIZE, MAX_ENTROPY_SIZE>
296{
297 /// Create an empty parsed device tree structure. This is used to construct
298 /// a valid instance to pass into [`Self::parse`].
299 pub const fn new() -> Self {
300 Self {
301 device_tree_size: 0,
302 memory: ArrayVec::new_const(),
303 boot_cpuid_phys: 0,
304 cpus: ArrayVec::new_const(),
305 vmbus_vtl0: None,
306 vmbus_vtl2: None,
307 command_line: ArrayString::new_const(),
308 com3_serial: false,
309 gic: None,
310 memory_allocation_mode: MemoryAllocationMode::Host,
311 entropy: None,
312 }
313 }
314
315 /// The number of enabled cpus.
316 pub fn cpu_count(&self) -> usize {
317 self.cpus.len()
318 }
319
320 /// Parse the given device tree.
321 pub fn parse(dt: &'a [u8], storage: &'b mut Self) -> Result<&'b Self, Error<'a>> {
322 Self::parse_inner(dt, storage).map_err(Error)
323 }
324
325 fn parse_inner(dt: &'a [u8], storage: &'b mut Self) -> Result<&'b Self, ErrorKind<'a>> {
326 let parser = fdt::parser::Parser::new(dt).map_err(ErrorKind::Dt)?;
327 let root = match parser.root() {
328 Ok(v) => v,
329 Err(e) => {
330 return Err(ErrorKind::Node {
331 parent_name: "",
332 error: e,
333 })
334 }
335 };
336
337 // Insert a memory entry into sorted parsed memory entries.
338 //
339 // TODO: This could be replaced with appending at the end with sort call
340 // after all entries are parsed once sort is stabilized in core.
341 let insert_memory_entry = |memory: &mut ArrayVec<MemoryEntry, MAX_MEMORY_ENTRIES>,
342 entry: MemoryEntry|
343 -> Result<(), ErrorKind<'a>> {
344 let insert_index = match memory.binary_search_by_key(&entry.range, |k| k.range) {
345 Ok(index) => {
346 return Err(ErrorKind::MemoryRegOverlap {
347 lower: memory[index],
348 upper: entry,
349 })
350 }
351 Err(index) => index,
352 };
353
354 memory
355 .try_insert(insert_index, entry)
356 .map_err(|_| ErrorKind::TooManyMemoryEntries)
357 };
358
359 for child in root.children() {
360 let child = child.map_err(|error| ErrorKind::Node {
361 parent_name: root.name,
362 error,
363 })?;
364
365 match child.name {
366 "cpus" => {
367 let address_cells = child
368 .find_property("#address-cells")
369 .map_err(ErrorKind::Prop)?
370 .ok_or(ErrorKind::PropMissing {
371 node_name: child.name,
372 prop_name: "#address-cells",
373 })?
374 .read_u32(0)
375 .map_err(ErrorKind::Prop)?;
376
377 // On ARM v8 64-bit systems, up to 2 address-cells values
378 // can be provided.
379 if address_cells > 2 {
380 return Err(ErrorKind::PropInvalidU32 {
381 node_name: child.name,
382 prop_name: "#address-cells",
383 expected: 2,
384 actual: address_cells,
385 });
386 }
387
388 for cpu in child.children() {
389 let cpu = cpu.map_err(|error| ErrorKind::Node {
390 parent_name: child.name,
391 error,
392 })?;
393
394 if cpu
395 .find_property("status")
396 .map_err(ErrorKind::Prop)?
397 .ok_or(ErrorKind::PropMissing {
398 node_name: cpu.name,
399 prop_name: "status",
400 })?
401 .read_str()
402 .map_err(ErrorKind::Prop)?
403 != "okay"
404 {
405 continue;
406 }
407
408 // NOTE: For x86, Underhill will need to query the hypervisor for
409 // the vp_index to apic_id mapping. There's no
410 // correlation in the device tree about this at all.
411 let reg_property = cpu
412 .find_property("reg")
413 .map_err(ErrorKind::Prop)?
414 .ok_or(ErrorKind::PropMissing {
415 node_name: cpu.name,
416 prop_name: "reg",
417 })?;
418
419 let reg = if address_cells == 1 {
420 reg_property.read_u32(0).map_err(ErrorKind::Prop)? as u64
421 } else {
422 reg_property.read_u64(0).map_err(ErrorKind::Prop)?
423 };
424
425 let vnode = cpu
426 .find_property("numa-node-id")
427 .map_err(ErrorKind::Prop)?
428 .ok_or(ErrorKind::PropMissing {
429 node_name: cpu.name,
430 prop_name: "numa-node-id",
431 })?
432 .read_u32(0)
433 .map_err(ErrorKind::Prop)?;
434
435 storage
436 .cpus
437 .try_push(CpuEntry { reg, vnode })
438 .map_err(|_| ErrorKind::TooManyCpus)?;
439 }
440 }
441 "openhcl" => {
442 let memory_allocation_mode = child
443 .find_property("memory-allocation-mode")
444 .map_err(ErrorKind::Prop)?
445 .ok_or(ErrorKind::PropMissing {
446 node_name: child.name,
447 prop_name: "memory-allocation-mode",
448 })?;
449
450 match memory_allocation_mode.read_str().map_err(ErrorKind::Prop)? {
451 "host" => {
452 storage.memory_allocation_mode = MemoryAllocationMode::Host;
453 }
454 "vtl2" => {
455 let memory_size = child
456 .find_property("memory-size")
457 .map_err(ErrorKind::Prop)?
458 .ok_or(ErrorKind::PropMissing {
459 node_name: child.name,
460 prop_name: "memory-size",
461 })?
462 .read_u64(0)
463 .map_err(ErrorKind::Prop)?;
464
465 let mmio_size = child
466 .find_property("mmio-size")
467 .map_err(ErrorKind::Prop)?
468 .ok_or(ErrorKind::PropMissing {
469 node_name: child.name,
470 prop_name: "mmio-size",
471 })?
472 .read_u64(0)
473 .map_err(ErrorKind::Prop)?;
474
475 storage.memory_allocation_mode = MemoryAllocationMode::Vtl2 {
476 memory_size,
477 mmio_size,
478 };
479 }
480 mode => {
481 return Err(ErrorKind::UnexpectedMemoryAllocationMode { mode });
482 }
483 }
484
485 for openhcl_child in child.children() {
486 let openhcl_child = openhcl_child.map_err(|error| ErrorKind::Node {
487 parent_name: root.name,
488 error,
489 })?;
490
491 #[allow(clippy::single_match)]
492 match openhcl_child.name {
493 "entropy" => {
494 let host_entropy = openhcl_child
495 .find_property("reg")
496 .map_err(ErrorKind::Prop)?
497 .ok_or(ErrorKind::PropMissing {
498 node_name: openhcl_child.name,
499 prop_name: "reg",
500 })?
501 .data;
502
503 if host_entropy.len() > MAX_ENTROPY_SIZE {
504 #[cfg(feature = "std")]
505 tracing::warn!(
506 entropy_len = host_entropy.len(),
507 "Truncating host-provided entropy",
508 );
509 }
510 let use_entropy_bytes =
511 core::cmp::min(host_entropy.len(), MAX_ENTROPY_SIZE);
512 let entropy =
513 ArrayVec::try_from(&host_entropy[..use_entropy_bytes]).unwrap();
514
515 storage.entropy = Some(entropy);
516 }
517 _ => {
518 #[cfg(feature = "std")]
519 tracing::warn!(?openhcl_child.name, "Unrecognized OpenHCL child node");
520 }
521 }
522 }
523 }
524 _ if child.name.starts_with("memory@") => {
525 let igvm_type = if let Some(igvm_type) = child
526 .find_property(igvm_defs::dt::IGVM_DT_IGVM_TYPE_PROPERTY)
527 .map_err(ErrorKind::Prop)?
528 {
529 let typ = igvm_type.read_u32(0).map_err(ErrorKind::Prop)?;
530 MemoryMapEntryType(typ as u16)
531 } else {
532 MemoryMapEntryType::MEMORY
533 };
534
535 let reg = child.find_property("reg").map_err(ErrorKind::Prop)?.ok_or(
536 ErrorKind::PropMissing {
537 node_name: child.name,
538 prop_name: "reg",
539 },
540 )?;
541
542 let vnode = child
543 .find_property("numa-node-id")
544 .map_err(ErrorKind::Prop)?
545 .ok_or(ErrorKind::PropMissing {
546 node_name: child.name,
547 prop_name: "numa-node-id",
548 })?
549 .read_u32(0)
550 .map_err(ErrorKind::Prop)?;
551
552 let len = reg.data.len();
553 let reg_tuple_size = size_of::<u64>() * 2;
554 let number_of_ranges = len / reg_tuple_size;
555
556 for i in 0..number_of_ranges {
557 let base = reg.read_u64(i * 2).map_err(ErrorKind::Prop)?;
558 let len = reg.read_u64(i * 2 + 1).map_err(ErrorKind::Prop)?;
559
560 if base % HV_PAGE_SIZE != 0 || len % HV_PAGE_SIZE != 0 {
561 return Err(ErrorKind::MemoryRegUnaligned {
562 node_name: child.name,
563 base,
564 len,
565 });
566 }
567
568 insert_memory_entry(
569 &mut storage.memory,
570 MemoryEntry {
571 range: MemoryRange::try_new(base..(base + len))
572 .expect("valid range"),
573 mem_type: igvm_type,
574 vnode,
575 },
576 )?;
577 }
578 }
579 "chosen" => {
580 let cmdline = child
581 .find_property("bootargs")
582 .map_err(ErrorKind::Prop)?
583 .map(|prop| prop.read_str().map_err(ErrorKind::Prop))
584 .transpose()?
585 .unwrap_or("");
586
587 write!(storage.command_line, "{}", cmdline)
588 .map_err(|_| ErrorKind::CmdlineSize)?;
589 }
590 _ if child.name.starts_with("intc@") => {
591 validate_property_str(&child, "compatible", "arm,gic-v3")?;
592 validate_property_u32(&child, "#redistributor-regions", 1, 0)?;
593 validate_property_u32(&child, "#address-cells", 2, 0)?;
594 validate_property_u32(&child, "#size-cells", 2, 0)?;
595 validate_property_u32(&child, "#interrupt-cells", 3, 0)?;
596
597 let gic_redistributor_stride = child
598 .find_property("redistributor-stride")
599 .map_err(ErrorKind::Prop)?
600 .ok_or(ErrorKind::PropMissing {
601 node_name: child.name,
602 prop_name: "redistributor-stride",
603 })?
604 .read_u64(0)
605 .map_err(ErrorKind::Prop)?;
606
607 let gic_reg_property = child
608 .find_property("reg")
609 .map_err(ErrorKind::Prop)?
610 .ok_or(ErrorKind::PropMissing {
611 node_name: child.name,
612 prop_name: "reg",
613 })?;
614 let gic_distributor_base =
615 gic_reg_property.read_u64(0).map_err(ErrorKind::Prop)?;
616 let gic_distributor_size =
617 gic_reg_property.read_u64(1).map_err(ErrorKind::Prop)?;
618 let gic_redistributors_base =
619 gic_reg_property.read_u64(2).map_err(ErrorKind::Prop)?;
620 let gic_redistributors_size =
621 gic_reg_property.read_u64(3).map_err(ErrorKind::Prop)?;
622
623 storage.gic = Some(GicInfo {
624 gic_distributor_base,
625 gic_distributor_size,
626 gic_redistributors_base,
627 gic_redistributors_size,
628 gic_redistributor_stride,
629 })
630 }
631 _ => {
632 parse_compatible(
633 &child,
634 &mut storage.vmbus_vtl0,
635 &mut storage.vmbus_vtl2,
636 &mut storage.com3_serial,
637 )?;
638 }
639 }
640 }
641
642 // Validate memory entries do not overlap.
643 for (prev, next) in storage.memory.iter().zip(storage.memory.iter().skip(1)) {
644 if prev.range.overlaps(&next.range) {
645 return Err(ErrorKind::MemoryRegOverlap {
646 lower: *prev,
647 upper: *next,
648 });
649 }
650 }
651
652 // Validate no mmio ranges overlap each other, or memory.
653 let vmbus_vtl0_mmio = storage
654 .vmbus_vtl0
655 .as_ref()
656 .map(|info| info.mmio.as_slice())
657 .unwrap_or(&[]);
658
659 let vmbus_vtl2_mmio = storage
660 .vmbus_vtl2
661 .as_ref()
662 .map(|info| info.mmio.as_slice())
663 .unwrap_or(&[]);
664
665 for ram in storage.memory.iter() {
666 for mmio in vmbus_vtl0_mmio {
667 if mmio.overlaps(&ram.range) {
668 return Err(ErrorKind::VmbusMmioOverlapsRam {
669 mmio: *mmio,
670 ram: *ram,
671 });
672 }
673 }
674
675 for mmio in vmbus_vtl2_mmio {
676 if mmio.overlaps(&ram.range) {
677 return Err(ErrorKind::VmbusMmioOverlapsRam {
678 mmio: *mmio,
679 ram: *ram,
680 });
681 }
682 }
683 }
684
685 for vtl0_mmio in vmbus_vtl0_mmio {
686 for vtl2_mmio in vmbus_vtl2_mmio {
687 if vtl0_mmio.overlaps(vtl2_mmio) {
688 return Err(ErrorKind::VmbusMmioOverlapsVmbusMmio {
689 mmio_a: *vtl0_mmio,
690 mmio_b: *vtl2_mmio,
691 });
692 }
693 }
694 }
695
696 // Set remaining fields that were not already filled out.
697 let Self {
698 device_tree_size,
699 memory: _,
700 boot_cpuid_phys,
701 cpus: _,
702 vmbus_vtl0: _,
703 vmbus_vtl2: _,
704 command_line: _,
705 com3_serial: _,
706 gic: _,
707 memory_allocation_mode: _,
708 entropy: _,
709 } = storage;
710
711 *device_tree_size = parser.total_size;
712 *boot_cpuid_phys = parser.boot_cpuid_phys;
713
714 Ok(storage)
715 }
716}
717
718fn parse_compatible<'a>(
719 node: &fdt::parser::Node<'a>,
720 vmbus_vtl0: &mut Option<VmbusInfo>,
721 vmbus_vtl2: &mut Option<VmbusInfo>,
722 com3_serial: &mut bool,
723) -> Result<(), ErrorKind<'a>> {
724 let compatible = node
725 .find_property("compatible")
726 .map_err(ErrorKind::Prop)?
727 .map(|prop| prop.read_str().map_err(ErrorKind::Prop))
728 .transpose()?
729 .unwrap_or("");
730
731 if compatible == "simple-bus" {
732 parse_simple_bus(node, vmbus_vtl0, vmbus_vtl2)?;
733 } else if compatible == "x86-pio-bus" {
734 parse_io_bus(node, com3_serial)?;
735 } else {
736 #[cfg(feature = "std")]
737 tracing::warn!(?compatible, ?node.name,
738 "Unrecognized compatible field",
739 );
740 }
741
742 Ok(())
743}
744
745fn parse_vmbus<'a>(node: &fdt::parser::Node<'a>) -> Result<VmbusInfo, ErrorKind<'a>> {
746 // Validate address cells and size cells are 2
747 let address_cells = node
748 .find_property("#address-cells")
749 .map_err(ErrorKind::Prop)?
750 .ok_or(ErrorKind::PropMissing {
751 node_name: node.name,
752 prop_name: "#address-cells",
753 })?
754 .read_u32(0)
755 .map_err(ErrorKind::Prop)?;
756
757 if address_cells != 2 {
758 return Err(ErrorKind::PropInvalidU32 {
759 node_name: node.name,
760 prop_name: "#address-cells",
761 expected: 2,
762 actual: address_cells,
763 });
764 }
765
766 let size_cells = node
767 .find_property("#size-cells")
768 .map_err(ErrorKind::Prop)?
769 .ok_or(ErrorKind::PropMissing {
770 node_name: node.name,
771 prop_name: "#size-cells",
772 })?
773 .read_u32(0)
774 .map_err(ErrorKind::Prop)?;
775
776 if size_cells != 2 {
777 return Err(ErrorKind::PropInvalidU32 {
778 node_name: node.name,
779 prop_name: "#size-cells",
780 expected: 2,
781 actual: size_cells,
782 });
783 }
784
785 let mmio: ArrayVec<MemoryRange, 2> =
786 match node.find_property("ranges").map_err(ErrorKind::Prop)? {
787 Some(ranges) => {
788 // Determine how many mmio ranges this describes. Valid numbers are
789 // 0, 1 or 2.
790 let ranges_tuple_size = size_of::<u64>() * 3;
791 let number_of_ranges = ranges.data.len() / ranges_tuple_size;
792 let mut mmio = ArrayVec::new();
793
794 if number_of_ranges > 2 {
795 return Err(ErrorKind::TooManyVmbusMmioRanges {
796 node_name: node.name,
797 ranges: number_of_ranges,
798 });
799 }
800
801 for i in 0..number_of_ranges {
802 let child_base = ranges.read_u64(i * 3).map_err(ErrorKind::Prop)?;
803 let parent_base = ranges.read_u64(i * 3 + 1).map_err(ErrorKind::Prop)?;
804 let len = ranges.read_u64(i * 3 + 2).map_err(ErrorKind::Prop)?;
805
806 if child_base != parent_base {
807 return Err(ErrorKind::VmbusRangesChildParent {
808 node_name: node.name,
809 child_base,
810 parent_base,
811 });
812 }
813
814 if child_base % HV_PAGE_SIZE != 0 || len % HV_PAGE_SIZE != 0 {
815 return Err(ErrorKind::VmbusRangesNotAligned {
816 node_name: node.name,
817 base: child_base,
818 len,
819 });
820 }
821
822 mmio.push(
823 MemoryRange::try_new(child_base..(child_base + len)).expect("valid range"),
824 );
825 }
826
827 // The DT ranges field might not have been sorted. Swap them if the
828 // low gap was described 2nd.
829 if number_of_ranges > 1 && mmio[0].start() > mmio[1].start() {
830 mmio.swap(0, 1);
831 }
832
833 if number_of_ranges > 1 && mmio[0].overlaps(&mmio[1]) {
834 return Err(ErrorKind::VmbusMmioOverlapsVmbusMmio {
835 mmio_a: mmio[0],
836 mmio_b: mmio[1],
837 });
838 }
839
840 mmio
841 }
842 None => {
843 // No mmio is acceptable.
844 ArrayVec::new()
845 }
846 };
847
848 let connection_id = node
849 .find_property("microsoft,message-connection-id")
850 .map_err(ErrorKind::Prop)?
851 .ok_or(ErrorKind::PropMissing {
852 node_name: node.name,
853 prop_name: "microsoft,message-connection-id",
854 })?
855 .read_u32(0)
856 .map_err(ErrorKind::Prop)?;
857
858 Ok(VmbusInfo {
859 mmio,
860 connection_id,
861 })
862}
863
864fn parse_simple_bus<'a>(
865 node: &fdt::parser::Node<'a>,
866 vmbus_vtl0: &mut Option<VmbusInfo>,
867 vmbus_vtl2: &mut Option<VmbusInfo>,
868) -> Result<(), ErrorKind<'a>> {
869 // Vmbus must be under simple-bus node with empty ranges.
870 if !node
871 .find_property("ranges")
872 .map_err(ErrorKind::Prop)?
873 .ok_or(ErrorKind::PropMissing {
874 node_name: node.name,
875 prop_name: "ranges",
876 })?
877 .data
878 .is_empty()
879 {
880 return Ok(());
881 }
882
883 for child in node.children() {
884 let child = child.map_err(|error| ErrorKind::Node {
885 parent_name: node.name,
886 error,
887 })?;
888
889 let compatible = child
890 .find_property("compatible")
891 .map_err(ErrorKind::Prop)?
892 .map(|prop| prop.read_str().map_err(ErrorKind::Prop))
893 .transpose()?
894 .unwrap_or("");
895
896 if compatible == "microsoft,vmbus" {
897 let vtl_name = igvm_defs::dt::IGVM_DT_VTL_PROPERTY;
898 let vtl = child
899 .find_property(vtl_name)
900 .map_err(ErrorKind::Prop)?
901 .ok_or(ErrorKind::PropMissing {
902 node_name: child.name,
903 prop_name: vtl_name,
904 })?
905 .read_u32(0)
906 .map_err(ErrorKind::Prop)?;
907
908 match vtl {
909 0 => {
910 if vmbus_vtl0.replace(parse_vmbus(&child)?).is_some() {
911 return Err(ErrorKind::MultipleVmbusNode {
912 node_name: child.name,
913 });
914 }
915 }
916 2 => {
917 if vmbus_vtl2.replace(parse_vmbus(&child)?).is_some() {
918 return Err(ErrorKind::MultipleVmbusNode {
919 node_name: child.name,
920 });
921 }
922 }
923 _ => {
924 return Err(ErrorKind::UnexpectedVmbusVtl {
925 node_name: child.name,
926 vtl,
927 })
928 }
929 }
930 }
931 }
932
933 Ok(())
934}
935
936fn parse_io_bus<'a>(
937 node: &fdt::parser::Node<'a>,
938 com3_serial: &mut bool,
939) -> Result<(), ErrorKind<'a>> {
940 for io_bus_child in node.children() {
941 let io_bus_child = io_bus_child.map_err(|error| ErrorKind::Node {
942 parent_name: node.name,
943 error,
944 })?;
945
946 let compatible: &str = io_bus_child
947 .find_property("compatible")
948 .map_err(ErrorKind::Prop)?
949 .map(|prop| prop.read_str().map_err(ErrorKind::Prop))
950 .transpose()?
951 .unwrap_or("");
952
953 let _current_speed = io_bus_child
954 .find_property("current-speed")
955 .map_err(ErrorKind::Prop)?
956 .ok_or(ErrorKind::PropMissing {
957 node_name: io_bus_child.name,
958 prop_name: "current-speed",
959 })?
960 .read_u32(0)
961 .map_err(ErrorKind::Prop)?;
962
963 let reg = io_bus_child
964 .find_property("reg")
965 .map_err(ErrorKind::Prop)?
966 .ok_or(ErrorKind::PropMissing {
967 node_name: io_bus_child.name,
968 prop_name: "reg",
969 })?;
970
971 let reg_base = reg.read_u64(0).map_err(ErrorKind::Prop)?;
972 let _reg_len = reg.read_u64(1).map_err(ErrorKind::Prop)?;
973
974 // Linux kernel hard-codes COM3 to COM3_REG_BASE.
975 // If work is ever done in the Linux kernel to instead
976 // parse from DT, the 2nd condition can be removed.
977 if compatible == "ns16550" && reg_base == COM3_REG_BASE {
978 *com3_serial = true
979 } else {
980 #[cfg(feature = "std")]
981 tracing::warn!(?node.name, ?compatible, ?reg_base,
982 "unrecognized io bus child"
983 );
984 }
985 }
986
987 Ok(())
988}
989
990fn validate_property_str<'a>(
991 child: &fdt::parser::Node<'a>,
992 name: &'static str,
993 expected: &'static str,
994) -> Result<(), ErrorKind<'a>> {
995 let actual = child
996 .find_property(name)
997 .map_err(ErrorKind::Prop)?
998 .ok_or(ErrorKind::PropMissing {
999 node_name: child.name,
1000 prop_name: name,
1001 })?
1002 .read_str()
1003 .map_err(ErrorKind::Prop)?;
1004 if actual != expected {
1005 return Err(ErrorKind::PropInvalidStr {
1006 node_name: child.name,
1007 prop_name: name,
1008 expected,
1009 actual,
1010 });
1011 }
1012
1013 Ok(())
1014}
1015
1016fn validate_property_u32<'a>(
1017 child: &fdt::parser::Node<'a>,
1018 name: &'static str,
1019 expected: u32,
1020 index: usize,
1021) -> Result<(), ErrorKind<'a>> {
1022 let actual = child
1023 .find_property(name)
1024 .map_err(ErrorKind::Prop)?
1025 .ok_or(ErrorKind::PropMissing {
1026 node_name: child.name,
1027 prop_name: name,
1028 })?
1029 .read_u32(index)
1030 .map_err(ErrorKind::Prop)?;
1031 if actual != expected {
1032 return Err(ErrorKind::PropInvalidU32 {
1033 node_name: child.name,
1034 prop_name: name,
1035 expected,
1036 actual,
1037 });
1038 }
1039
1040 Ok(())
1041}
1042
1043#[cfg(feature = "inspect")]
1044mod inspect_helpers {
1045 use super::*;
1046
1047 pub(super) fn inspect_memory_map_entry_type(typ: &MemoryMapEntryType) -> impl Inspect + '_ {
1048 // TODO: inspect::AsDebug would work here once
1049 // https://github.com/kupiakos/open-enum/pull/13 is merged.
1050 inspect::adhoc(|req| match *typ {
1051 MemoryMapEntryType::MEMORY => req.value("MEMORY".into()),
1052 MemoryMapEntryType::PERSISTENT => req.value("PERSISTENT".into()),
1053 MemoryMapEntryType::PLATFORM_RESERVED => req.value("PLATFORM_RESERVED".into()),
1054 MemoryMapEntryType::VTL2_PROTECTABLE => req.value("VTL2_PROTECTABLE".into()),
1055 _ => req.value(typ.0.into()),
1056 })
1057 }
1058
1059 pub(super) fn mmio_internal(mmio: &[MemoryRange]) -> impl Inspect + '_ {
1060 inspect::iter_by_key(
1061 mmio.iter()
1062 .map(|range| (range, inspect::AsHex(range.len()))),
1063 )
1064 }
1065
1066 pub(super) fn memory_internal(memory: &[MemoryEntry]) -> impl Inspect + '_ {
1067 inspect::iter_by_key(memory.iter().map(|entry| (entry.range, entry)))
1068 }
1069}
1070
1071#[cfg(test)]
1072mod tests {
1073 use super::*;
1074 use fdt::builder::Builder;
1075 use fdt::builder::BuilderConfig;
1076 use fdt::builder::Nest;
1077
1078 type TestParsedDeviceTree = ParsedDeviceTree<32, 32, 1024, 64>;
1079
1080 fn new_vmbus_mmio(mmio: &[MemoryRange]) -> ArrayVec<MemoryRange, 2> {
1081 let mut vec = ArrayVec::new();
1082 vec.try_extend_from_slice(mmio).unwrap();
1083 vec
1084 }
1085
1086 struct VmbusStringIds {
1087 p_address_cells: fdt::builder::StringId,
1088 p_size_cells: fdt::builder::StringId,
1089 p_compatible: fdt::builder::StringId,
1090 p_ranges: fdt::builder::StringId,
1091 p_vtl: fdt::builder::StringId,
1092 p_vmbus_connection_id: fdt::builder::StringId,
1093 }
1094
1095 fn add_vmbus<'a>(
1096 ids: &VmbusStringIds,
1097 bus: Builder<'a, Nest<Nest<()>>>,
1098 vmbus_info: &VmbusInfo,
1099 vtl: u8,
1100 ) -> Builder<'a, Nest<Nest<()>>> {
1101 let mmio = {
1102 let mut ranges = Vec::new();
1103 for entry in &vmbus_info.mmio {
1104 ranges.push(entry.start());
1105 ranges.push(entry.start());
1106 ranges.push(entry.len());
1107 }
1108 ranges
1109 };
1110 let name = if mmio.is_empty() {
1111 format!("vmbus-vtl{vtl}")
1112 } else {
1113 format!("vmbus-vtl{vtl}@{:x}", mmio[0])
1114 };
1115 bus.start_node(&name)
1116 .unwrap()
1117 .add_u32(ids.p_address_cells, 2)
1118 .unwrap()
1119 .add_u32(ids.p_size_cells, 2)
1120 .unwrap()
1121 .add_str(ids.p_compatible, "microsoft,vmbus")
1122 .unwrap()
1123 .add_u64_array(ids.p_ranges, &mmio)
1124 .unwrap()
1125 .add_u32(ids.p_vtl, vtl as u32)
1126 .unwrap()
1127 .add_u32(ids.p_vmbus_connection_id, vmbus_info.connection_id)
1128 .unwrap()
1129 .end_node()
1130 .unwrap()
1131 }
1132
1133 /// Build a dt from a parsed context.
1134 fn build_dt(context: &TestParsedDeviceTree) -> Vec<u8> {
1135 let mut buf = vec![0; 25600];
1136 let mut builder = Builder::new(BuilderConfig {
1137 blob_buffer: &mut buf,
1138 string_table_cap: 1024,
1139 memory_reservations: &[],
1140 })
1141 .expect("can build the DT builder");
1142 let p_address_cells = builder.add_string("#address-cells").unwrap();
1143 let p_size_cells = builder.add_string("#size-cells").unwrap();
1144 let p_model = builder.add_string("model").unwrap();
1145 let p_reg = builder.add_string("reg").unwrap();
1146 let p_ranges = builder.add_string("ranges").unwrap();
1147 let p_device_type = builder.add_string("device_type").unwrap();
1148 let p_status = builder.add_string("status").unwrap();
1149 let p_igvm_type = builder
1150 .add_string(igvm_defs::dt::IGVM_DT_IGVM_TYPE_PROPERTY)
1151 .unwrap();
1152 let p_numa_node_id = builder.add_string("numa-node-id").unwrap();
1153 let p_compatible = builder.add_string("compatible").unwrap();
1154 let p_vmbus_connection_id = builder
1155 .add_string("microsoft,message-connection-id")
1156 .unwrap();
1157 let p_vtl = builder
1158 .add_string(igvm_defs::dt::IGVM_DT_VTL_PROPERTY)
1159 .unwrap();
1160 let p_bootargs = builder.add_string("bootargs").unwrap();
1161 let p_clock_frequency = builder.add_string("clock-frequency").unwrap();
1162 let p_current_speed = builder.add_string("current-speed").unwrap();
1163 let p_interrupts = builder.add_string("interrupts").unwrap();
1164
1165 let mut cpus = builder
1166 .start_node("")
1167 .unwrap()
1168 .add_u32(p_address_cells, 2)
1169 .unwrap() // 64bit
1170 .add_u32(p_size_cells, 2)
1171 .unwrap() // 64bit
1172 .add_str(p_model, "microsoft,hyperv")
1173 .unwrap()
1174 .start_node("cpus")
1175 .unwrap()
1176 .add_u32(p_address_cells, 1)
1177 .unwrap()
1178 .add_u32(p_size_cells, 0)
1179 .unwrap();
1180
1181 // Add a CPU node for each VP.
1182 for (index, cpu) in context.cpus.iter().enumerate() {
1183 let name = format!("cpu@{:x}", index);
1184 cpus = cpus
1185 .start_node(name.as_ref())
1186 .unwrap()
1187 .add_str(p_device_type, "cpu")
1188 .unwrap()
1189 .add_u32(p_reg, cpu.reg as u32)
1190 .unwrap()
1191 .add_u32(p_numa_node_id, cpu.vnode)
1192 .unwrap()
1193 .add_str(p_status, "okay")
1194 .unwrap()
1195 .end_node()
1196 .unwrap();
1197 }
1198
1199 let mut root = cpus.end_node().unwrap();
1200
1201 // Add memory, but reverse to test parsing sorting.
1202 // TODO: maybe shuffle order even more?
1203 for MemoryEntry {
1204 range,
1205 mem_type,
1206 vnode,
1207 } in context.memory.iter().rev()
1208 {
1209 let name = format!("memory@{:x}", range.start());
1210 root = root
1211 .start_node(name.as_ref())
1212 .unwrap()
1213 .add_str(p_device_type, "memory")
1214 .unwrap()
1215 .add_u64_array(p_reg, &[range.start(), range.len()])
1216 .unwrap()
1217 .add_u32(p_igvm_type, mem_type.0 as u32)
1218 .unwrap()
1219 .add_u32(p_numa_node_id, *vnode)
1220 .unwrap()
1221 .end_node()
1222 .unwrap();
1223 }
1224
1225 // GIC
1226 if let Some(gic) = &context.gic {
1227 let p_interrupt_cells = root.add_string("#interrupt-cells").unwrap();
1228 let p_redist_regions = root.add_string("#redistributor-regions").unwrap();
1229 let p_redist_stride = root.add_string("redistributor-stride").unwrap();
1230 let p_interrupt_controller = root.add_string("interrupt-controller").unwrap();
1231 let p_phandle = root.add_string("phandle").unwrap();
1232 let name = format!("intc@{}", gic.gic_distributor_base);
1233 root = root
1234 .start_node(name.as_ref())
1235 .unwrap()
1236 .add_str(p_compatible, "arm,gic-v3")
1237 .unwrap()
1238 .add_u32(p_redist_regions, 1)
1239 .unwrap()
1240 .add_u64(p_redist_stride, gic.gic_redistributor_stride)
1241 .unwrap()
1242 .add_u64_array(
1243 p_reg,
1244 &[
1245 gic.gic_distributor_base,
1246 gic.gic_distributor_size,
1247 gic.gic_redistributors_base,
1248 gic.gic_redistributors_size,
1249 ],
1250 )
1251 .unwrap()
1252 .add_u32(p_address_cells, 2)
1253 .unwrap()
1254 .add_u32(p_size_cells, 2)
1255 .unwrap()
1256 .add_u32(p_interrupt_cells, 3)
1257 .unwrap()
1258 .add_null(p_interrupt_controller)
1259 .unwrap()
1260 .add_u32(p_phandle, 1)
1261 .unwrap()
1262 .add_null(p_ranges)
1263 .unwrap()
1264 .end_node()
1265 .unwrap();
1266 }
1267
1268 // Linux requires vmbus to be under a simple-bus node.
1269 let mut simple_bus = root
1270 .start_node("bus")
1271 .unwrap()
1272 .add_str(p_compatible, "simple-bus")
1273 .unwrap()
1274 .add_u32(p_address_cells, 2)
1275 .unwrap()
1276 .add_u32(p_size_cells, 2)
1277 .unwrap()
1278 .add_prop_array(p_ranges, &[])
1279 .unwrap();
1280
1281 let vmbus_ids = VmbusStringIds {
1282 p_address_cells,
1283 p_size_cells,
1284 p_compatible,
1285 p_ranges,
1286 p_vtl,
1287 p_vmbus_connection_id,
1288 };
1289
1290 // VTL0 vmbus root device
1291 if let Some(vmbus) = &context.vmbus_vtl0 {
1292 simple_bus = add_vmbus(&vmbus_ids, simple_bus, vmbus, 0);
1293 }
1294
1295 // VTL2 vmbus root device
1296 if let Some(vmbus) = &context.vmbus_vtl2 {
1297 simple_bus = add_vmbus(&vmbus_ids, simple_bus, vmbus, 2);
1298 }
1299
1300 root = simple_bus.end_node().unwrap();
1301
1302 // Com3 serial node
1303 if context.com3_serial {
1304 let mut io_port_bus = root
1305 .start_node("io-bus")
1306 .unwrap()
1307 .add_str(p_compatible, "x86-pio-bus")
1308 .unwrap()
1309 .add_u32(p_address_cells, 1)
1310 .unwrap()
1311 .add_u32(p_size_cells, 0)
1312 .unwrap()
1313 .add_prop_array(p_ranges, &[])
1314 .unwrap();
1315
1316 let serial_name = format!("serial@{:x}", COM3_REG_BASE);
1317 io_port_bus = io_port_bus
1318 .start_node(&serial_name)
1319 .unwrap()
1320 .add_str(p_compatible, "ns16550")
1321 .unwrap()
1322 .add_u32(p_clock_frequency, 0)
1323 .unwrap()
1324 .add_u32(p_current_speed, 115200)
1325 .unwrap()
1326 .add_u64_array(p_reg, &[COM3_REG_BASE, 0x8])
1327 .unwrap()
1328 .add_u64_array(p_interrupts, &[4])
1329 .unwrap()
1330 .end_node()
1331 .unwrap();
1332
1333 root = io_port_bus.end_node().unwrap();
1334 }
1335
1336 // Chosen node - contains cmdline.
1337 root = root
1338 .start_node("chosen")
1339 .unwrap()
1340 .add_str(p_bootargs, context.command_line.as_ref())
1341 .unwrap()
1342 .end_node()
1343 .unwrap();
1344
1345 // openhcl node - contains memory allocation mode.
1346 let p_memory_allocation_mode = root.add_string("memory-allocation-mode").unwrap();
1347 let p_memory_allocation_size = root.add_string("memory-size").unwrap();
1348 let p_mmio_allocation_size = root.add_string("mmio-size").unwrap();
1349 let mut openhcl = root.start_node("openhcl").unwrap();
1350
1351 let memory_alloc_str = match context.memory_allocation_mode {
1352 MemoryAllocationMode::Host => "host",
1353 MemoryAllocationMode::Vtl2 {
1354 memory_size,
1355 mmio_size,
1356 } => {
1357 // Encode the size at the expected property.
1358 openhcl = openhcl
1359 .add_u64(p_memory_allocation_size, memory_size)
1360 .unwrap();
1361 openhcl = openhcl.add_u64(p_mmio_allocation_size, mmio_size).unwrap();
1362 "vtl2"
1363 }
1364 };
1365
1366 root = openhcl
1367 .add_str(p_memory_allocation_mode, memory_alloc_str)
1368 .unwrap()
1369 .end_node()
1370 .unwrap();
1371
1372 let bytes_used = root
1373 .end_node()
1374 .unwrap()
1375 .build(context.boot_cpuid_phys)
1376 .unwrap();
1377 buf.truncate(bytes_used);
1378
1379 buf
1380 }
1381
1382 /// Creates a parsed device tree context. No validation is performed.
1383 fn create_parsed(
1384 dt_size: usize,
1385 memory: &[MemoryEntry],
1386 cpus: &[CpuEntry],
1387 bsp: u32,
1388 vmbus_vtl0: Option<VmbusInfo>,
1389 vmbus_vtl2: Option<VmbusInfo>,
1390 command_line: &str,
1391 com3_serial: bool,
1392 gic: Option<GicInfo>,
1393 memory_allocation_mode: MemoryAllocationMode,
1394 ) -> TestParsedDeviceTree {
1395 let mut context = TestParsedDeviceTree::new();
1396 context.device_tree_size = dt_size;
1397 context.boot_cpuid_phys = bsp;
1398 write!(context.command_line, "{command_line}").unwrap();
1399 context.com3_serial = com3_serial;
1400 context.vmbus_vtl0 = vmbus_vtl0;
1401 context.vmbus_vtl2 = vmbus_vtl2;
1402 context.memory.try_extend_from_slice(memory).unwrap();
1403 context.cpus.try_extend_from_slice(cpus).unwrap();
1404 context.gic = gic;
1405 context.memory_allocation_mode = memory_allocation_mode;
1406 context
1407 }
1408
1409 #[test]
1410 fn test_basic_dt() {
1411 let orig = create_parsed(
1412 2568,
1413 &[
1414 MemoryEntry {
1415 range: MemoryRange::try_new(0..(1024 * HV_PAGE_SIZE)).unwrap(),
1416 mem_type: MemoryMapEntryType::MEMORY,
1417 vnode: 0,
1418 },
1419 MemoryEntry {
1420 range: MemoryRange::try_new((1024 * HV_PAGE_SIZE)..(4024 * HV_PAGE_SIZE))
1421 .unwrap(),
1422 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1423 vnode: 0,
1424 },
1425 MemoryEntry {
1426 range: MemoryRange::try_new((14024 * HV_PAGE_SIZE)..(102400 * HV_PAGE_SIZE))
1427 .unwrap(),
1428 mem_type: MemoryMapEntryType::MEMORY,
1429 vnode: 0,
1430 },
1431 ],
1432 &[
1433 CpuEntry { reg: 12, vnode: 0 },
1434 CpuEntry { reg: 42, vnode: 0 },
1435 CpuEntry { reg: 23, vnode: 0 },
1436 CpuEntry { reg: 24, vnode: 0 },
1437 ],
1438 42,
1439 Some(VmbusInfo {
1440 mmio: new_vmbus_mmio(&[
1441 MemoryRange::try_new((4024 * HV_PAGE_SIZE)..(4096 * HV_PAGE_SIZE)).unwrap(),
1442 MemoryRange::try_new((102400 * HV_PAGE_SIZE)..(102800 * HV_PAGE_SIZE)).unwrap(),
1443 ]),
1444 connection_id: 1,
1445 }),
1446 Some(VmbusInfo {
1447 mmio: new_vmbus_mmio(&[MemoryRange::try_new(
1448 (102800 * HV_PAGE_SIZE)..(102900 * HV_PAGE_SIZE),
1449 )
1450 .unwrap()]),
1451 connection_id: 4,
1452 }),
1453 "THIS_IS_A_BOOT_ARG=1",
1454 false,
1455 Some(GicInfo {
1456 gic_distributor_base: 0x20000,
1457 gic_distributor_size: 0x10000,
1458 gic_redistributors_base: 0x40000,
1459 gic_redistributors_size: 0x60000,
1460 gic_redistributor_stride: 0x20000,
1461 }),
1462 MemoryAllocationMode::Host,
1463 );
1464
1465 let dt = build_dt(&orig);
1466 let mut parsed = TestParsedDeviceTree::new();
1467 let parsed = TestParsedDeviceTree::parse(&dt, &mut parsed).unwrap();
1468 assert_eq!(&orig, parsed);
1469 }
1470
1471 #[test]
1472 fn test_numa_dt() {
1473 let orig = create_parsed(
1474 2352,
1475 &[
1476 MemoryEntry {
1477 range: MemoryRange::try_new(0..(1024 * HV_PAGE_SIZE)).unwrap(),
1478 mem_type: MemoryMapEntryType::MEMORY,
1479 vnode: 0,
1480 },
1481 MemoryEntry {
1482 range: MemoryRange::try_new((1024 * HV_PAGE_SIZE)..(2048 * HV_PAGE_SIZE))
1483 .unwrap(),
1484 mem_type: MemoryMapEntryType::MEMORY,
1485 vnode: 1,
1486 },
1487 MemoryEntry {
1488 range: MemoryRange::try_new((2048 * HV_PAGE_SIZE)..(3072 * HV_PAGE_SIZE))
1489 .unwrap(),
1490 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1491 vnode: 0,
1492 },
1493 MemoryEntry {
1494 range: MemoryRange::try_new((3072 * HV_PAGE_SIZE)..(4096 * HV_PAGE_SIZE))
1495 .unwrap(),
1496 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1497 vnode: 1,
1498 },
1499 MemoryEntry {
1500 range: MemoryRange::try_new((4096 * HV_PAGE_SIZE)..(51200 * HV_PAGE_SIZE))
1501 .unwrap(),
1502 mem_type: MemoryMapEntryType::MEMORY,
1503 vnode: 0,
1504 },
1505 MemoryEntry {
1506 range: MemoryRange::try_new((51200 * HV_PAGE_SIZE)..(102400 * HV_PAGE_SIZE))
1507 .unwrap(),
1508 mem_type: MemoryMapEntryType::MEMORY,
1509 vnode: 1,
1510 },
1511 ],
1512 &[
1513 CpuEntry { reg: 12, vnode: 0 },
1514 CpuEntry { reg: 42, vnode: 1 },
1515 CpuEntry { reg: 23, vnode: 0 },
1516 CpuEntry { reg: 24, vnode: 1 },
1517 ],
1518 23,
1519 None,
1520 None,
1521 "",
1522 false,
1523 None,
1524 MemoryAllocationMode::Vtl2 {
1525 memory_size: 1000 * 1024 * 1024, // 1000 MB
1526 mmio_size: 128 * 1024 * 1024, // 128 MB
1527 },
1528 );
1529
1530 let dt = build_dt(&orig);
1531 let mut parsed = TestParsedDeviceTree::new();
1532 let parsed = TestParsedDeviceTree::parse(&dt, &mut parsed).unwrap();
1533 assert_eq!(&orig, parsed);
1534 }
1535
1536 /// Tests memory ranges that overlap each other, or memory ranges that
1537 /// overlap vmbus mmio.
1538 #[test]
1539 fn test_overlapping_memory() {
1540 // mem overlaps each other
1541 let bad = create_parsed(
1542 0,
1543 &[
1544 MemoryEntry {
1545 range: MemoryRange::try_new(0..(1024 * HV_PAGE_SIZE)).unwrap(),
1546 mem_type: MemoryMapEntryType::MEMORY,
1547 vnode: 0,
1548 },
1549 MemoryEntry {
1550 range: MemoryRange::try_new(4096..(1024 * HV_PAGE_SIZE)).unwrap(),
1551 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1552 vnode: 0,
1553 },
1554 MemoryEntry {
1555 range: MemoryRange::try_new((14024 * HV_PAGE_SIZE)..(102400 * HV_PAGE_SIZE))
1556 .unwrap(),
1557 mem_type: MemoryMapEntryType::MEMORY,
1558 vnode: 0,
1559 },
1560 ],
1561 &[
1562 CpuEntry { reg: 12, vnode: 0 },
1563 CpuEntry { reg: 42, vnode: 0 },
1564 CpuEntry { reg: 23, vnode: 0 },
1565 CpuEntry { reg: 24, vnode: 0 },
1566 ],
1567 42,
1568 None,
1569 None,
1570 "THIS_IS_A_BOOT_ARG=1",
1571 false,
1572 None,
1573 MemoryAllocationMode::Host,
1574 );
1575
1576 let dt = build_dt(&bad);
1577 let mut parsed = TestParsedDeviceTree::new();
1578 assert!(matches!(
1579 TestParsedDeviceTree::parse(&dt, &mut parsed),
1580 Err(Error(ErrorKind::MemoryRegOverlap { .. }))
1581 ));
1582
1583 // mem contained within another
1584 let bad = create_parsed(
1585 0,
1586 &[
1587 MemoryEntry {
1588 range: MemoryRange::try_new(4096..(1024 * HV_PAGE_SIZE)).unwrap(),
1589 mem_type: MemoryMapEntryType::MEMORY,
1590 vnode: 0,
1591 },
1592 MemoryEntry {
1593 range: MemoryRange::try_new(0..(102400 * HV_PAGE_SIZE)).unwrap(),
1594 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1595 vnode: 0,
1596 },
1597 ],
1598 &[
1599 CpuEntry { reg: 12, vnode: 0 },
1600 CpuEntry { reg: 42, vnode: 0 },
1601 CpuEntry { reg: 23, vnode: 0 },
1602 CpuEntry { reg: 24, vnode: 0 },
1603 ],
1604 42,
1605 None,
1606 None,
1607 "THIS_IS_A_BOOT_ARG=1",
1608 false,
1609 None,
1610 MemoryAllocationMode::Host,
1611 );
1612
1613 let dt = build_dt(&bad);
1614 let mut parsed = TestParsedDeviceTree::new();
1615 assert!(matches!(
1616 TestParsedDeviceTree::parse(&dt, &mut parsed),
1617 Err(Error(ErrorKind::MemoryRegOverlap { .. }))
1618 ));
1619
1620 // mem overlaps vmbus
1621 let bad = create_parsed(
1622 0,
1623 &[MemoryEntry {
1624 range: MemoryRange::try_new(0..(202400 * HV_PAGE_SIZE)).unwrap(),
1625 mem_type: MemoryMapEntryType::MEMORY,
1626 vnode: 0,
1627 }],
1628 &[
1629 CpuEntry { reg: 12, vnode: 0 },
1630 CpuEntry { reg: 42, vnode: 0 },
1631 CpuEntry { reg: 23, vnode: 0 },
1632 CpuEntry { reg: 24, vnode: 0 },
1633 ],
1634 42,
1635 Some(VmbusInfo {
1636 mmio: new_vmbus_mmio(&[MemoryRange::try_new(
1637 (4024 * HV_PAGE_SIZE)..(4096 * HV_PAGE_SIZE),
1638 )
1639 .unwrap()]),
1640 connection_id: 1,
1641 }),
1642 Some(VmbusInfo {
1643 mmio: new_vmbus_mmio(&[MemoryRange::try_new(
1644 (102800 * HV_PAGE_SIZE)..(102900 * HV_PAGE_SIZE),
1645 )
1646 .unwrap()]),
1647 connection_id: 4,
1648 }),
1649 "THIS_IS_A_BOOT_ARG=1",
1650 false,
1651 None,
1652 MemoryAllocationMode::Host,
1653 );
1654
1655 let dt = build_dt(&bad);
1656 let mut parsed = TestParsedDeviceTree::new();
1657 assert!(matches!(
1658 TestParsedDeviceTree::parse(&dt, &mut parsed),
1659 Err(Error(ErrorKind::VmbusMmioOverlapsRam { .. }))
1660 ));
1661
1662 // vmbus overlap each other
1663 let bad = create_parsed(
1664 0,
1665 &[MemoryEntry {
1666 range: MemoryRange::try_new(0..(1024 * HV_PAGE_SIZE)).unwrap(),
1667 mem_type: MemoryMapEntryType::MEMORY,
1668 vnode: 0,
1669 }],
1670 &[
1671 CpuEntry { reg: 12, vnode: 0 },
1672 CpuEntry { reg: 42, vnode: 0 },
1673 CpuEntry { reg: 23, vnode: 0 },
1674 CpuEntry { reg: 24, vnode: 0 },
1675 ],
1676 42,
1677 Some(VmbusInfo {
1678 mmio: new_vmbus_mmio(&[
1679 MemoryRange::try_new((4000 * HV_PAGE_SIZE)..(4096 * HV_PAGE_SIZE)).unwrap(),
1680 MemoryRange::EMPTY,
1681 ]),
1682 connection_id: 1,
1683 }),
1684 Some(VmbusInfo {
1685 mmio: new_vmbus_mmio(&[
1686 MemoryRange::try_new((4020 * HV_PAGE_SIZE)..(102900 * HV_PAGE_SIZE)).unwrap(),
1687 MemoryRange::EMPTY,
1688 ]),
1689 connection_id: 4,
1690 }),
1691 "THIS_IS_A_BOOT_ARG=1",
1692 false,
1693 None,
1694 MemoryAllocationMode::Host,
1695 );
1696
1697 let dt = build_dt(&bad);
1698 let mut parsed = TestParsedDeviceTree::new();
1699 assert!(matches!(
1700 TestParsedDeviceTree::parse(&dt, &mut parsed),
1701 Err(Error(ErrorKind::VmbusMmioOverlapsVmbusMmio { .. }))
1702 ));
1703 }
1704
1705 /// tests serial output
1706 #[test]
1707 fn test_com3_serial_output() {
1708 let orig = create_parsed(
1709 2560,
1710 &[
1711 MemoryEntry {
1712 range: MemoryRange::try_new(0..(1024 * HV_PAGE_SIZE)).unwrap(),
1713 mem_type: MemoryMapEntryType::MEMORY,
1714 vnode: 0,
1715 },
1716 MemoryEntry {
1717 range: MemoryRange::try_new((1024 * HV_PAGE_SIZE)..(4024 * HV_PAGE_SIZE))
1718 .unwrap(),
1719 mem_type: MemoryMapEntryType::VTL2_PROTECTABLE,
1720 vnode: 0,
1721 },
1722 MemoryEntry {
1723 range: MemoryRange::try_new((14024 * HV_PAGE_SIZE)..(102400 * HV_PAGE_SIZE))
1724 .unwrap(),
1725 mem_type: MemoryMapEntryType::MEMORY,
1726 vnode: 0,
1727 },
1728 ],
1729 &[
1730 CpuEntry { reg: 12, vnode: 0 },
1731 CpuEntry { reg: 42, vnode: 0 },
1732 CpuEntry { reg: 23, vnode: 0 },
1733 CpuEntry { reg: 24, vnode: 0 },
1734 ],
1735 42,
1736 Some(VmbusInfo {
1737 mmio: new_vmbus_mmio(&[
1738 MemoryRange::try_new((4024 * HV_PAGE_SIZE)..(4096 * HV_PAGE_SIZE)).unwrap(),
1739 MemoryRange::try_new((102400 * HV_PAGE_SIZE)..(102800 * HV_PAGE_SIZE)).unwrap(),
1740 ]),
1741 connection_id: 1,
1742 }),
1743 Some(VmbusInfo {
1744 mmio: new_vmbus_mmio(&[MemoryRange::try_new(
1745 (102800 * HV_PAGE_SIZE)..(102900 * HV_PAGE_SIZE),
1746 )
1747 .unwrap()]),
1748 connection_id: 4,
1749 }),
1750 "THIS_IS_A_BOOT_ARG=1",
1751 true,
1752 None,
1753 MemoryAllocationMode::Host,
1754 );
1755
1756 let dt = build_dt(&orig);
1757 let mut parsed = TestParsedDeviceTree::new();
1758 let parsed = TestParsedDeviceTree::parse(&dt, &mut parsed).unwrap();
1759
1760 assert_eq!(&orig, parsed);
1761 assert!(parsed.com3_serial);
1762 }
1763}