microsoft/openvmm

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
93af13fed5d5fc7a8a08fbf37c0ea1e155c4160a

Branches

Tags

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

Clone

HTTPS

Download ZIP

openhcl/host_fdt_parser/src/lib.rs

1766lines · modecode

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