microsoft/openvmm

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/review-commit-3347

Branches

Tags

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

Clone

HTTPS

Download ZIP

openhcl/host_fdt_parser/src/lib.rs

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