microsoft/openvmm

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
f3b136c6d8bf49f0874bbaf9c6b330c325ec6c79

Branches

Tags

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

Clone

HTTPS

Download ZIP

openhcl/host_fdt_parser/src/lib.rs

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