microsoft/openvmm

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
release/2411

Branches

Tags

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

Clone

HTTPS

Download ZIP

openhcl/host_fdt_parser/src/lib.rs

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