xref: /cloud-hypervisor/arch/src/aarch64/fdt.rs (revision f7f2f25a574b1b2dba22c094fc8226d404157d15)
1 // Copyright 2020 Arm Limited (or its affiliates). All rights reserved.
2 // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 // Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
6 // Use of this source code is governed by a BSD-style license that can be
7 // found in the THIRD-PARTY file.
8 
9 use byteorder::{BigEndian, ByteOrder};
10 use std::cmp;
11 use std::collections::HashMap;
12 use std::ffi::CStr;
13 use std::fmt::Debug;
14 use std::result;
15 use std::str;
16 
17 use super::super::DeviceType;
18 use super::super::GuestMemoryMmap;
19 use super::super::InitramfsConfig;
20 use super::get_fdt_addr;
21 use super::gic::GicDevice;
22 use super::layout::{
23     IRQ_BASE, MEM_32BIT_DEVICES_SIZE, MEM_32BIT_DEVICES_START, MEM_PCI_IO_SIZE, MEM_PCI_IO_START,
24     PCI_HIGH_BASE, PCI_MMCONFIG_SIZE, PCI_MMCONFIG_START,
25 };
26 use vm_fdt::{FdtWriter, FdtWriterResult};
27 use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryError};
28 
29 // This is a value for uniquely identifying the FDT node declaring the interrupt controller.
30 const GIC_PHANDLE: u32 = 1;
31 // This is a value for uniquely identifying the FDT node declaring the MSI controller.
32 const MSI_PHANDLE: u32 = 2;
33 // This is a value for uniquely identifying the FDT node containing the clock definition.
34 const CLOCK_PHANDLE: u32 = 3;
35 // This is a value for uniquely identifying the FDT node containing the gpio controller.
36 const GPIO_PHANDLE: u32 = 4;
37 // This is a value for uniquely identifying the FDT node containing the first vCPU.
38 // The last number of vCPU phandle depends on the number of vCPUs.
39 const FIRST_VCPU_PHANDLE: u32 = 5;
40 
41 // Read the documentation specified when appending the root node to the FDT.
42 const ADDRESS_CELLS: u32 = 0x2;
43 const SIZE_CELLS: u32 = 0x2;
44 
45 // As per kvm tool and
46 // https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.txt
47 // Look for "The 1st cell..."
48 const GIC_FDT_IRQ_TYPE_SPI: u32 = 0;
49 const GIC_FDT_IRQ_TYPE_PPI: u32 = 1;
50 
51 // From https://elixir.bootlin.com/linux/v4.9.62/source/include/dt-bindings/interrupt-controller/irq.h#L17
52 const IRQ_TYPE_EDGE_RISING: u32 = 1;
53 const IRQ_TYPE_LEVEL_HI: u32 = 4;
54 
55 // Keys and Buttons
56 // System Power Down
57 const KEY_POWER: u32 = 116;
58 
59 /// Trait for devices to be added to the Flattened Device Tree.
60 pub trait DeviceInfoForFdt {
61     /// Returns the address where this device will be loaded.
62     fn addr(&self) -> u64;
63     /// Returns the associated interrupt for this device.
64     fn irq(&self) -> u32;
65     /// Returns the amount of memory that needs to be reserved for this device.
66     fn length(&self) -> u64;
67 }
68 
69 /// Errors thrown while configuring the Flattened Device Tree for aarch64.
70 #[derive(Debug)]
71 pub enum Error {
72     /// Failure in writing FDT in memory.
73     WriteFdtToMemory(GuestMemoryError),
74 }
75 type Result<T> = result::Result<T, Error>;
76 
77 /// Creates the flattened device tree for this aarch64 VM.
78 #[allow(clippy::too_many_arguments)]
79 pub fn create_fdt<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
80     guest_mem: &GuestMemoryMmap,
81     cmdline: &CStr,
82     vcpu_mpidr: Vec<u64>,
83     vcpu_topology: Option<(u8, u8, u8)>,
84     device_info: &HashMap<(DeviceType, String), T, S>,
85     gic_device: &dyn GicDevice,
86     initrd: &Option<InitramfsConfig>,
87     pci_space_address: &(u64, u64),
88 ) -> FdtWriterResult<Vec<u8>> {
89     // Allocate stuff necessary for the holding the blob.
90     let mut fdt = FdtWriter::new().unwrap();
91 
92     // For an explanation why these nodes were introduced in the blob take a look at
93     // https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L845
94     // Look for "Required nodes and properties".
95 
96     // Header or the root node as per above mentioned documentation.
97     let root_node = fdt.begin_node("")?;
98     fdt.property_string("compatible", "linux,dummy-virt")?;
99     // For info on #address-cells and size-cells read "Note about cells and address representation"
100     // from the above mentioned txt file.
101     fdt.property_u32("#address-cells", ADDRESS_CELLS)?;
102     fdt.property_u32("#size-cells", SIZE_CELLS)?;
103     // This is not mandatory but we use it to point the root node to the node
104     // containing description of the interrupt controller for this VM.
105     fdt.property_u32("interrupt-parent", GIC_PHANDLE)?;
106     create_cpu_nodes(&mut fdt, &vcpu_mpidr, vcpu_topology)?;
107     create_memory_node(&mut fdt, guest_mem)?;
108     create_chosen_node(&mut fdt, cmdline.to_str().unwrap(), initrd)?;
109     create_gic_node(&mut fdt, gic_device)?;
110     create_timer_node(&mut fdt)?;
111     create_clock_node(&mut fdt)?;
112     create_psci_node(&mut fdt)?;
113     create_devices_node(&mut fdt, device_info)?;
114     create_pci_nodes(&mut fdt, pci_space_address.0, pci_space_address.1)?;
115 
116     // End Header node.
117     fdt.end_node(root_node)?;
118 
119     let fdt_final = fdt.finish()?;
120 
121     Ok(fdt_final)
122 }
123 
124 pub fn write_fdt_to_memory(fdt_final: Vec<u8>, guest_mem: &GuestMemoryMmap) -> Result<()> {
125     // Write FDT to memory.
126     let fdt_address = GuestAddress(get_fdt_addr());
127     guest_mem
128         .write_slice(fdt_final.as_slice(), fdt_address)
129         .map_err(Error::WriteFdtToMemory)?;
130     Ok(())
131 }
132 
133 // Following are the auxiliary function for creating the different nodes that we append to our FDT.
134 fn create_cpu_nodes(
135     fdt: &mut FdtWriter,
136     vcpu_mpidr: &[u64],
137     vcpu_topology: Option<(u8, u8, u8)>,
138 ) -> FdtWriterResult<()> {
139     // See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/arm/cpus.yaml.
140     let cpus_node = fdt.begin_node("cpus")?;
141     fdt.property_u32("#address-cells", 0x1)?;
142     fdt.property_u32("#size-cells", 0x0)?;
143 
144     let num_cpus = vcpu_mpidr.len();
145     let threads_per_core = vcpu_topology.unwrap_or_default().0 as u8;
146     let cores_per_package = vcpu_topology.unwrap_or_default().1 as u8;
147     let packages = vcpu_topology.unwrap_or_default().2 as u8;
148 
149     for (cpu_id, mpidr) in vcpu_mpidr.iter().enumerate().take(num_cpus) {
150         let cpu_name = format!("cpu@{:x}", cpu_id);
151         let cpu_node = fdt.begin_node(&cpu_name)?;
152         fdt.property_string("device_type", "cpu")?;
153         fdt.property_string("compatible", "arm,arm-v8")?;
154         if num_cpus > 1 {
155             // This is required on armv8 64-bit. See aforementioned documentation.
156             fdt.property_string("enable-method", "psci")?;
157         }
158         // Set the field to first 24 bits of the MPIDR - Multiprocessor Affinity Register.
159         // See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0488c/BABHBJCI.html.
160         fdt.property_u32("reg", (mpidr & 0x7FFFFF) as u32)?;
161         fdt.property_u32("phandle", cpu_id as u32 + FIRST_VCPU_PHANDLE)?;
162         fdt.end_node(cpu_node)?;
163     }
164 
165     // If there is a valid cpu topology config, create the cpu-map node.
166     if (threads_per_core > 0)
167         && (cores_per_package > 0)
168         && (packages > 0)
169         && (num_cpus as u8 == threads_per_core * cores_per_package * packages)
170     {
171         let cpu_map_node = fdt.begin_node("cpu-map")?;
172         // Create mappings between CPU index and cluster,core, and thread.
173         let mut cluster_core_thread_to_cpuidx = HashMap::new();
174         for cpu_idx in 0..num_cpus as u8 {
175             if threads_per_core > 1 {
176                 cluster_core_thread_to_cpuidx.insert(
177                     (
178                         cpu_idx / (cores_per_package * threads_per_core),
179                         (cpu_idx / threads_per_core) % cores_per_package,
180                         cpu_idx % threads_per_core,
181                     ),
182                     cpu_idx,
183                 );
184             } else {
185                 cluster_core_thread_to_cpuidx.insert(
186                     (cpu_idx / cores_per_package, cpu_idx % cores_per_package, 0),
187                     cpu_idx,
188                 );
189             }
190         }
191 
192         // Create device tree nodes with regard of above mapping.
193         for cluster_idx in 0..packages {
194             let cluster_name = format!("cluster{:x}", cluster_idx);
195             let cluster_node = fdt.begin_node(&cluster_name)?;
196 
197             for core_idx in 0..cores_per_package {
198                 let core_name = format!("core{:x}", core_idx);
199                 let core_node = fdt.begin_node(&core_name)?;
200 
201                 if threads_per_core > 1 {
202                     for thread_idx in 0..threads_per_core {
203                         let thread_name = format!("thread{:x}", thread_idx);
204                         let thread_node = fdt.begin_node(&thread_name)?;
205                         let cpu_idx = cluster_core_thread_to_cpuidx
206                             .get(&(cluster_idx, core_idx, thread_idx))
207                             .unwrap();
208 
209                         fdt.property_u32("cpu", *cpu_idx as u32 + FIRST_VCPU_PHANDLE)?;
210                         fdt.end_node(thread_node)?;
211                     }
212                 } else {
213                     let cpu_idx = cluster_core_thread_to_cpuidx
214                         .get(&(cluster_idx, core_idx, 0))
215                         .unwrap();
216 
217                     fdt.property_u32("cpu", *cpu_idx as u32 + FIRST_VCPU_PHANDLE)?;
218                 }
219                 fdt.end_node(core_node)?;
220             }
221             fdt.end_node(cluster_node)?;
222         }
223         fdt.end_node(cpu_map_node)?;
224     } else {
225         debug!("Boot using device tree, CPU topology is not (correctly) specified");
226     }
227 
228     fdt.end_node(cpus_node)?;
229 
230     Ok(())
231 }
232 
233 fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemoryMmap) -> FdtWriterResult<()> {
234     let mem_size = guest_mem.last_addr().raw_value() - super::layout::RAM_64BIT_START + 1;
235     // See https://github.com/torvalds/linux/blob/master/Documentation/devicetree/booting-without-of.txt#L960
236     // for an explanation of this.
237     let mem_reg_prop = [super::layout::RAM_64BIT_START as u64, mem_size as u64];
238 
239     let memory_node = fdt.begin_node("memory")?;
240     fdt.property_string("device_type", "memory")?;
241     fdt.property_array_u64("reg", &mem_reg_prop)?;
242     fdt.end_node(memory_node)?;
243 
244     Ok(())
245 }
246 
247 fn create_chosen_node(
248     fdt: &mut FdtWriter,
249     cmdline: &str,
250     initrd: &Option<InitramfsConfig>,
251 ) -> FdtWriterResult<()> {
252     let chosen_node = fdt.begin_node("chosen")?;
253     fdt.property_string("bootargs", cmdline)?;
254 
255     if let Some(initrd_config) = initrd {
256         let initrd_start = initrd_config.address.raw_value() as u64;
257         let initrd_end = initrd_config.address.raw_value() + initrd_config.size as u64;
258         fdt.property_u64("linux,initrd-start", initrd_start)?;
259         fdt.property_u64("linux,initrd-end", initrd_end)?;
260     }
261 
262     fdt.end_node(chosen_node)?;
263 
264     Ok(())
265 }
266 
267 fn create_gic_node(fdt: &mut FdtWriter, gic_device: &dyn GicDevice) -> FdtWriterResult<()> {
268     let gic_reg_prop = gic_device.device_properties();
269 
270     let intc_node = fdt.begin_node("intc")?;
271 
272     fdt.property_string("compatible", gic_device.fdt_compatibility())?;
273     fdt.property_null("interrupt-controller")?;
274     // "interrupt-cells" field specifies the number of cells needed to encode an
275     // interrupt source. The type shall be a <u32> and the value shall be 3 if no PPI affinity description
276     // is required.
277     fdt.property_u32("#interrupt-cells", 3)?;
278     fdt.property_array_u64("reg", gic_reg_prop)?;
279     fdt.property_u32("phandle", GIC_PHANDLE)?;
280     fdt.property_u32("#address-cells", 2)?;
281     fdt.property_u32("#size-cells", 2)?;
282     fdt.property_null("ranges")?;
283 
284     let gic_intr_prop = [
285         GIC_FDT_IRQ_TYPE_PPI,
286         gic_device.fdt_maint_irq(),
287         IRQ_TYPE_LEVEL_HI,
288     ];
289     fdt.property_array_u32("interrupts", &gic_intr_prop)?;
290 
291     if gic_device.msi_compatible() {
292         let msic_node = fdt.begin_node("msic")?;
293         fdt.property_string("compatible", gic_device.msi_compatibility())?;
294         fdt.property_null("msi-controller")?;
295         fdt.property_u32("phandle", MSI_PHANDLE)?;
296         let msi_reg_prop = gic_device.msi_properties();
297         fdt.property_array_u64("reg", msi_reg_prop)?;
298         fdt.end_node(msic_node)?;
299     }
300 
301     fdt.end_node(intc_node)?;
302 
303     Ok(())
304 }
305 
306 fn create_clock_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
307     // The Advanced Peripheral Bus (APB) is part of the Advanced Microcontroller Bus Architecture
308     // (AMBA) protocol family. It defines a low-cost interface that is optimized for minimal power
309     // consumption and reduced interface complexity.
310     // PCLK is the clock source and this node defines exactly the clock for the APB.
311     let clock_node = fdt.begin_node("apb-pclk")?;
312     fdt.property_string("compatible", "fixed-clock")?;
313     fdt.property_u32("#clock-cells", 0x0)?;
314     fdt.property_u32("clock-frequency", 24000000)?;
315     fdt.property_string("clock-output-names", "clk24mhz")?;
316     fdt.property_u32("phandle", CLOCK_PHANDLE)?;
317     fdt.end_node(clock_node)?;
318 
319     Ok(())
320 }
321 
322 fn create_timer_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
323     // See
324     // https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/interrupt-controller/arch_timer.txt
325     // These are fixed interrupt numbers for the timer device.
326     let irqs = [13, 14, 11, 10];
327     let compatible = "arm,armv8-timer";
328 
329     let mut timer_reg_cells: Vec<u32> = Vec::new();
330     for &irq in irqs.iter() {
331         timer_reg_cells.push(GIC_FDT_IRQ_TYPE_PPI);
332         timer_reg_cells.push(irq);
333         timer_reg_cells.push(IRQ_TYPE_LEVEL_HI);
334     }
335 
336     let timer_node = fdt.begin_node("timer")?;
337     fdt.property_string("compatible", compatible)?;
338     fdt.property_null("always-on")?;
339     fdt.property_array_u32("interrupts", &timer_reg_cells)?;
340     fdt.end_node(timer_node)?;
341 
342     Ok(())
343 }
344 
345 fn create_psci_node(fdt: &mut FdtWriter) -> FdtWriterResult<()> {
346     let compatible = "arm,psci-0.2";
347     let psci_node = fdt.begin_node("psci")?;
348     fdt.property_string("compatible", compatible)?;
349     // Two methods available: hvc and smc.
350     // As per documentation, PSCI calls between a guest and hypervisor may use the HVC conduit instead of SMC.
351     // So, since we are using kvm, we need to use hvc.
352     fdt.property_string("method", "hvc")?;
353     fdt.end_node(psci_node)?;
354 
355     Ok(())
356 }
357 
358 fn create_virtio_node<T: DeviceInfoForFdt + Clone + Debug>(
359     fdt: &mut FdtWriter,
360     dev_info: &T,
361 ) -> FdtWriterResult<()> {
362     let device_reg_prop = [dev_info.addr(), dev_info.length()];
363     let irq = [GIC_FDT_IRQ_TYPE_SPI, dev_info.irq(), IRQ_TYPE_EDGE_RISING];
364 
365     let virtio_node = fdt.begin_node(&format!("virtio_mmio@{:x}", dev_info.addr()))?;
366     fdt.property_string("compatible", "virtio,mmio")?;
367     fdt.property_array_u64("reg", &device_reg_prop)?;
368     fdt.property_array_u32("interrupts", &irq)?;
369     fdt.property_u32("interrupt-parent", GIC_PHANDLE)?;
370     fdt.end_node(virtio_node)?;
371 
372     Ok(())
373 }
374 
375 fn create_serial_node<T: DeviceInfoForFdt + Clone + Debug>(
376     fdt: &mut FdtWriter,
377     dev_info: &T,
378 ) -> FdtWriterResult<()> {
379     let compatible = b"arm,pl011\0arm,primecell\0";
380     let serial_reg_prop = [dev_info.addr(), dev_info.length()];
381     let irq = [
382         GIC_FDT_IRQ_TYPE_SPI,
383         dev_info.irq() - IRQ_BASE,
384         IRQ_TYPE_EDGE_RISING,
385     ];
386 
387     let serial_node = fdt.begin_node(&format!("pl011@{:x}", dev_info.addr()))?;
388     fdt.property("compatible", compatible)?;
389     fdt.property_array_u64("reg", &serial_reg_prop)?;
390     fdt.property_u32("clocks", CLOCK_PHANDLE)?;
391     fdt.property_string("clock-names", "apb_pclk")?;
392     fdt.property_array_u32("interrupts", &irq)?;
393     fdt.end_node(serial_node)?;
394 
395     Ok(())
396 }
397 
398 fn create_rtc_node<T: DeviceInfoForFdt + Clone + Debug>(
399     fdt: &mut FdtWriter,
400     dev_info: &T,
401 ) -> FdtWriterResult<()> {
402     let compatible = b"arm,pl031\0arm,primecell\0";
403     let rtc_reg_prop = [dev_info.addr(), dev_info.length()];
404     let irq = [
405         GIC_FDT_IRQ_TYPE_SPI,
406         dev_info.irq() - IRQ_BASE,
407         IRQ_TYPE_LEVEL_HI,
408     ];
409 
410     let rtc_node = fdt.begin_node(&format!("rtc@{:x}", dev_info.addr()))?;
411     fdt.property("compatible", compatible)?;
412     fdt.property_array_u64("reg", &rtc_reg_prop)?;
413     fdt.property_array_u32("interrupts", &irq)?;
414     fdt.property_u32("clocks", CLOCK_PHANDLE)?;
415     fdt.property_string("clock-names", "apb_pclk")?;
416     fdt.end_node(rtc_node)?;
417 
418     Ok(())
419 }
420 
421 fn create_gpio_node<T: DeviceInfoForFdt + Clone + Debug>(
422     fdt: &mut FdtWriter,
423     dev_info: &T,
424 ) -> FdtWriterResult<()> {
425     // PL061 GPIO controller node
426     let compatible = b"arm,pl061\0arm,primecell\0";
427     let gpio_reg_prop = [dev_info.addr(), dev_info.length()];
428     let irq = [
429         GIC_FDT_IRQ_TYPE_SPI,
430         dev_info.irq() - IRQ_BASE,
431         IRQ_TYPE_EDGE_RISING,
432     ];
433 
434     let gpio_node = fdt.begin_node(&format!("pl061@{:x}", dev_info.addr()))?;
435     fdt.property("compatible", compatible)?;
436     fdt.property_array_u64("reg", &gpio_reg_prop)?;
437     fdt.property_array_u32("interrupts", &irq)?;
438     fdt.property_null("gpio-controller")?;
439     fdt.property_u32("#gpio-cells", 2)?;
440     fdt.property_u32("clocks", CLOCK_PHANDLE)?;
441     fdt.property_string("clock-names", "apb_pclk")?;
442     fdt.property_u32("phandle", GPIO_PHANDLE)?;
443     fdt.end_node(gpio_node)?;
444 
445     // gpio-keys node
446     let gpio_keys_node = fdt.begin_node("gpio-keys")?;
447     fdt.property_string("compatible", "gpio-keys")?;
448     fdt.property_u32("#size-cells", 0)?;
449     fdt.property_u32("#address-cells", 1)?;
450     let gpio_keys_poweroff_node = fdt.begin_node("button@1")?;
451     fdt.property_string("label", "GPIO Key Poweroff")?;
452     fdt.property_u32("linux,code", KEY_POWER)?;
453     let gpios = [GPIO_PHANDLE, 3, 0];
454     fdt.property_array_u32("gpios", &gpios)?;
455     fdt.end_node(gpio_keys_poweroff_node)?;
456     fdt.end_node(gpio_keys_node)?;
457 
458     Ok(())
459 }
460 
461 fn create_devices_node<T: DeviceInfoForFdt + Clone + Debug, S: ::std::hash::BuildHasher>(
462     fdt: &mut FdtWriter,
463     dev_info: &HashMap<(DeviceType, String), T, S>,
464 ) -> FdtWriterResult<()> {
465     // Create one temp Vec to store all virtio devices
466     let mut ordered_virtio_device: Vec<&T> = Vec::new();
467 
468     for ((device_type, _device_id), info) in dev_info {
469         match device_type {
470             DeviceType::Gpio => create_gpio_node(fdt, info)?,
471             DeviceType::Rtc => create_rtc_node(fdt, info)?,
472             DeviceType::Serial => create_serial_node(fdt, info)?,
473             DeviceType::Virtio(_) => {
474                 ordered_virtio_device.push(info);
475             }
476         }
477     }
478 
479     // Sort out virtio devices by address from low to high and insert them into fdt table.
480     ordered_virtio_device.sort_by_key(|&a| a.addr());
481     // Current address allocation strategy in cloud-hypervisor is: the first created device
482     // will be allocated to higher address. Here we reverse the vector to make sure that
483     // the older created device will appear in front of the newer created device in FDT.
484     ordered_virtio_device.reverse();
485     for ordered_device_info in ordered_virtio_device.drain(..) {
486         create_virtio_node(fdt, ordered_device_info)?;
487     }
488 
489     Ok(())
490 }
491 
492 fn create_pci_nodes(
493     fdt: &mut FdtWriter,
494     pci_device_base: u64,
495     pci_device_size: u64,
496 ) -> FdtWriterResult<()> {
497     // Add node for PCIe controller.
498     // See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
499     // and https://elinux.org/Device_Tree_Usage.
500 
501     // EDK2 requires the PCIe high space above 4G address.
502     // The actual space in CLH follows the RAM. If the RAM space is small, the PCIe high space
503     // could fall bellow 4G.
504     // Here we put it above 512G in FDT to workaround the EDK2 check.
505     // But the address written in ACPI is not impacted.
506     let pci_device_base_64bit: u64 = if cfg!(feature = "acpi") {
507         pci_device_base + PCI_HIGH_BASE
508     } else {
509         pci_device_base
510     };
511     let pci_device_size_64bit: u64 = if cfg!(feature = "acpi") {
512         pci_device_size - PCI_HIGH_BASE
513     } else {
514         pci_device_size
515     };
516 
517     let ranges = [
518         // io addresses
519         0x1000000,
520         0_u32,
521         0_u32,
522         (MEM_PCI_IO_START.0 >> 32) as u32,
523         MEM_PCI_IO_START.0 as u32,
524         (MEM_PCI_IO_SIZE >> 32) as u32,
525         MEM_PCI_IO_SIZE as u32,
526         // mmio addresses
527         0x2000000,                                // (ss = 10: 32-bit memory space)
528         (MEM_32BIT_DEVICES_START.0 >> 32) as u32, // PCI address
529         MEM_32BIT_DEVICES_START.0 as u32,
530         (MEM_32BIT_DEVICES_START.0 >> 32) as u32, // CPU address
531         MEM_32BIT_DEVICES_START.0 as u32,
532         (MEM_32BIT_DEVICES_SIZE >> 32) as u32, // size
533         MEM_32BIT_DEVICES_SIZE as u32,
534         // device addresses
535         0x3000000,                            // (ss = 11: 64-bit memory space)
536         (pci_device_base_64bit >> 32) as u32, // PCI address
537         pci_device_base_64bit as u32,
538         (pci_device_base_64bit >> 32) as u32, // CPU address
539         pci_device_base_64bit as u32,
540         (pci_device_size_64bit >> 32) as u32, // size
541         pci_device_size_64bit as u32,
542     ];
543     let bus_range = [0, 0]; // Only bus 0
544     let reg = [PCI_MMCONFIG_START.0, PCI_MMCONFIG_SIZE];
545 
546     let pci_node = fdt.begin_node("pci")?;
547     fdt.property_string("compatible", "pci-host-ecam-generic")?;
548     fdt.property_string("device_type", "pci")?;
549     fdt.property_array_u32("ranges", &ranges)?;
550     fdt.property_array_u32("bus-range", &bus_range)?;
551     fdt.property_u32("#address-cells", 3)?;
552     fdt.property_u32("#size-cells", 2)?;
553     fdt.property_array_u64("reg", &reg)?;
554     fdt.property_u32("#interrupt-cells", 1)?;
555     fdt.property_null("interrupt-map")?;
556     fdt.property_null("interrupt-map-mask")?;
557     fdt.property_null("dma-coherent")?;
558     fdt.property_u32("msi-parent", MSI_PHANDLE)?;
559     fdt.end_node(pci_node)?;
560 
561     Ok(())
562 }
563 
564 // Parse the DTB binary and print for debugging
565 pub fn print_fdt(dtb: &[u8]) {
566     match fdt_parser::Fdt::new(dtb) {
567         Ok(fdt) => {
568             if let Some(root) = fdt.find_node("/") {
569                 debug!("Printing the FDT:");
570                 print_node(root, 0);
571             } else {
572                 debug!("Failed to find root node in FDT for debugging.");
573             }
574         }
575         Err(_) => debug!("Failed to parse FDT for debugging."),
576     }
577 }
578 
579 fn print_node(node: fdt_parser::node::FdtNode<'_, '_>, n_spaces: usize) {
580     debug!("{:indent$}{}/", "", node.name, indent = n_spaces);
581     for property in node.properties() {
582         let name = property.name;
583 
584         // If the property is 'compatible', its value requires special handling.
585         // The u8 array could contain multiple null-terminated strings.
586         // We copy the original array and simply replace all 'null' characters with spaces.
587         let value = if name == "compatible" {
588             let mut compatible = vec![0u8; 256];
589             let handled_value = property
590                 .value
591                 .iter()
592                 .map(|&c| if c == 0 { b' ' } else { c })
593                 .collect::<Vec<_>>();
594             let len = cmp::min(255, handled_value.len());
595             compatible[..len].copy_from_slice(&handled_value[..len]);
596             compatible[..(len + 1)].to_vec()
597         } else {
598             property.value.to_vec()
599         };
600         let value = &value;
601 
602         // Now the value can be either:
603         //   - A null-terminated C string, or
604         //   - Binary data
605         // We follow a very simple logic to present the value:
606         //   - At first, try to convert it to CStr and print,
607         //   - If failed, print it as u32 array.
608         let value_result = match CStr::from_bytes_with_nul(value) {
609             Ok(value_cstr) => match value_cstr.to_str() {
610                 Ok(value_str) => Some(value_str),
611                 Err(_e) => None,
612             },
613             Err(_e) => None,
614         };
615 
616         if let Some(value_str) = value_result {
617             debug!(
618                 "{:indent$}{} : {:#?}",
619                 "",
620                 name,
621                 value_str,
622                 indent = (n_spaces + 2)
623             );
624         } else {
625             let mut array = Vec::with_capacity(256);
626             array.resize(value.len() / 4, 0u32);
627             BigEndian::read_u32_into(value, &mut array);
628             debug!(
629                 "{:indent$}{} : {:X?}",
630                 "",
631                 name,
632                 array,
633                 indent = (n_spaces + 2)
634             );
635         };
636     }
637 
638     // Print children nodes if there is any
639     for child in node.children() {
640         print_node(child, n_spaces + 2);
641     }
642 }
643