microsoft/openvmm

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e8acaefba35969bbca0ffae96452cd05b47aa621

Branches

Tags

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

Clone

HTTPS

Download ZIP

openhcl/host_fdt_parser/src/lib.rs

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