arch: rewrite FDT writer in native rust

This removes some unsafe code, improving the interface so that it cannot
be misused (e.g. previously, different Vec<u8> instances could be passed
to fdt functions that did not validate the contents).

The new implementation also adds some extra error checking to catch
invalid string values in all API entry points that accept strings, as
well as out-of-order node nesting that would result in DTB data that did
not conform to the spec.

BUG=b:175729255
TEST=cargo test -p arch
TEST=Boot Crostini on kevin (arm)
TEST=diff old and new dts from `dtc -I fs -O dts /proc/device-tree`

Change-Id: I567d562ed1b447aa0d282b68c71758edea13ffc0
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2713569
Reviewed-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
Daniel Verkamp 2021-02-17 18:27:29 -08:00 committed by Commit Bot
parent 413f854564
commit 0a91c96437
12 changed files with 851 additions and 367 deletions

1
Cargo.lock generated
View file

@ -57,6 +57,7 @@ dependencies = [
"power_monitor",
"resources",
"sync",
"thiserror",
"vm_control",
"vm_memory",
]

View file

@ -44,13 +44,13 @@ repo init -g crosvm -u https://chromium.googlesource.com/chromiumos/manifest.git
repo sync
```
A basic crosvm build links against `libcap` and `libfdt`. On a Debian-based system,
you can install `libcap-dev` and `libfdt-dev`.
A basic crosvm build links against `libcap`. On a Debian-based system,
you can install `libcap-dev`.
Handy Debian one-liner for all build and runtime deps, particularly if you're
running Crostini:
```sh
sudo apt install build-essential libcap-dev libfdt-dev libgbm-dev libvirglrenderer-dev libwayland-bin libwayland-dev pkg-config protobuf-compiler python wayland-protocols
sudo apt install build-essential libcap-dev libgbm-dev libvirglrenderer-dev libwayland-bin libwayland-dev pkg-config protobuf-compiler python wayland-protocols
```
Known issues:

View file

@ -2,15 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::ffi::CStr;
use std::fs::File;
use std::io::Read;
use arch::fdt::{
begin_node, end_node, finish_fdt, generate_prop32, generate_prop64, property, property_cstring,
property_null, property_string, property_string_list, property_u32, property_u64, start_fdt,
Error, Result,
};
use arch::fdt::{Error, FdtWriter, Result};
use arch::SERIAL_ADDR;
use devices::{PciAddress, PciInterruptPin};
use hypervisor::PsciVersion;
@ -61,63 +56,62 @@ const IRQ_TYPE_EDGE_RISING: u32 = 0x00000001;
const IRQ_TYPE_LEVEL_HIGH: u32 = 0x00000004;
const IRQ_TYPE_LEVEL_LOW: u32 = 0x00000008;
fn create_memory_node(fdt: &mut Vec<u8>, guest_mem: &GuestMemory) -> Result<()> {
fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemory) -> Result<()> {
let mem_size = guest_mem.memory_size();
let mem_reg_prop = generate_prop64(&[AARCH64_PHYS_MEM_START, mem_size]);
let mem_reg_prop = [AARCH64_PHYS_MEM_START, mem_size];
begin_node(fdt, "memory")?;
property_string(fdt, "device_type", "memory")?;
property(fdt, "reg", &mem_reg_prop)?;
end_node(fdt)?;
let memory_node = fdt.begin_node("memory")?;
fdt.property_string("device_type", "memory")?;
fdt.property_array_u64("reg", &mem_reg_prop)?;
fdt.end_node(memory_node)?;
Ok(())
}
fn create_cpu_nodes(fdt: &mut Vec<u8>, num_cpus: u32) -> Result<()> {
begin_node(fdt, "cpus")?;
property_u32(fdt, "#address-cells", 0x1)?;
property_u32(fdt, "#size-cells", 0x0)?;
fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> {
let cpus_node = fdt.begin_node("cpus")?;
fdt.property_u32("#address-cells", 0x1)?;
fdt.property_u32("#size-cells", 0x0)?;
for cpu_id in 0..num_cpus {
let cpu_name = format!("cpu@{:x}", cpu_id);
begin_node(fdt, &cpu_name)?;
property_string(fdt, "device_type", "cpu")?;
property_string(fdt, "compatible", "arm,arm-v8")?;
let cpu_node = fdt.begin_node(&cpu_name)?;
fdt.property_string("device_type", "cpu")?;
fdt.property_string("compatible", "arm,arm-v8")?;
if num_cpus > 1 {
property_string(fdt, "enable-method", "psci")?;
fdt.property_string("enable-method", "psci")?;
}
property_u32(fdt, "reg", cpu_id)?;
end_node(fdt)?;
fdt.property_u32("reg", cpu_id)?;
fdt.end_node(cpu_node)?;
}
end_node(fdt)?;
fdt.end_node(cpus_node)?;
Ok(())
}
fn create_gic_node(fdt: &mut Vec<u8>, is_gicv3: bool, num_cpus: u64) -> Result<()> {
fn create_gic_node(fdt: &mut FdtWriter, is_gicv3: bool, num_cpus: u64) -> Result<()> {
let mut gic_reg_prop = [AARCH64_GIC_DIST_BASE, AARCH64_GIC_DIST_SIZE, 0, 0];
begin_node(fdt, "intc")?;
let intc_node = fdt.begin_node("intc")?;
if is_gicv3 {
property_string(fdt, "compatible", "arm,gic-v3")?;
fdt.property_string("compatible", "arm,gic-v3")?;
gic_reg_prop[2] = AARCH64_GIC_DIST_BASE - (AARCH64_GIC_REDIST_SIZE * num_cpus);
gic_reg_prop[3] = AARCH64_GIC_REDIST_SIZE * num_cpus;
} else {
property_string(fdt, "compatible", "arm,cortex-a15-gic")?;
fdt.property_string("compatible", "arm,cortex-a15-gic")?;
gic_reg_prop[2] = AARCH64_GIC_CPUI_BASE;
gic_reg_prop[3] = AARCH64_GIC_CPUI_SIZE;
}
let gic_reg_prop = generate_prop64(&gic_reg_prop);
property_u32(fdt, "#interrupt-cells", GIC_FDT_IRQ_NUM_CELLS)?;
property_null(fdt, "interrupt-controller")?;
property(fdt, "reg", &gic_reg_prop)?;
property_u32(fdt, "phandle", PHANDLE_GIC)?;
property_u32(fdt, "#address-cells", 2)?;
property_u32(fdt, "#size-cells", 2)?;
end_node(fdt)?;
fdt.property_u32("#interrupt-cells", GIC_FDT_IRQ_NUM_CELLS)?;
fdt.property_null("interrupt-controller")?;
fdt.property_array_u64("reg", &gic_reg_prop)?;
fdt.property_u32("phandle", PHANDLE_GIC)?;
fdt.property_u32("#address-cells", 2)?;
fdt.property_u32("#size-cells", 2)?;
fdt.end_node(intc_node)?;
Ok(())
}
fn create_timer_node(fdt: &mut Vec<u8>, num_cpus: u32) -> Result<()> {
fn create_timer_node(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> {
// These are fixed interrupt numbers for the timer device.
let irqs = [13, 14, 11, 10];
let compatible = "arm,armv8-timer";
@ -130,49 +124,48 @@ fn create_timer_node(fdt: &mut Vec<u8>, num_cpus: u32) -> Result<()> {
timer_reg_cells.push(irq);
timer_reg_cells.push(cpu_mask | IRQ_TYPE_LEVEL_LOW);
}
let timer_reg_prop = generate_prop32(timer_reg_cells.as_slice());
begin_node(fdt, "timer")?;
property_string(fdt, "compatible", compatible)?;
property(fdt, "interrupts", &timer_reg_prop)?;
property_null(fdt, "always-on")?;
end_node(fdt)?;
let timer_node = fdt.begin_node("timer")?;
fdt.property_string("compatible", compatible)?;
fdt.property_array_u32("interrupts", &timer_reg_cells)?;
fdt.property_null("always-on")?;
fdt.end_node(timer_node)?;
Ok(())
}
fn create_pmu_node(fdt: &mut Vec<u8>, num_cpus: u32) -> Result<()> {
fn create_pmu_node(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> {
let compatible = "arm,armv8-pmuv3";
let cpu_mask: u32 =
(((1 << num_cpus) - 1) << GIC_FDT_IRQ_PPI_CPU_SHIFT) & GIC_FDT_IRQ_PPI_CPU_MASK;
let irq = generate_prop32(&[
let irq = [
GIC_FDT_IRQ_TYPE_PPI,
AARCH64_PMU_IRQ,
cpu_mask | IRQ_TYPE_LEVEL_HIGH,
]);
];
begin_node(fdt, "pmu")?;
property_string(fdt, "compatible", compatible)?;
property(fdt, "interrupts", &irq)?;
end_node(fdt)?;
let pmu_node = fdt.begin_node("pmu")?;
fdt.property_string("compatible", compatible)?;
fdt.property_array_u32("interrupts", &irq)?;
fdt.end_node(pmu_node)?;
Ok(())
}
fn create_serial_node(fdt: &mut Vec<u8>, addr: u64, irq: u32) -> Result<()> {
let serial_reg_prop = generate_prop64(&[addr, AARCH64_SERIAL_SIZE]);
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, irq, IRQ_TYPE_EDGE_RISING]);
fn create_serial_node(fdt: &mut FdtWriter, addr: u64, irq: u32) -> Result<()> {
let serial_reg_prop = [addr, AARCH64_SERIAL_SIZE];
let irq = [GIC_FDT_IRQ_TYPE_SPI, irq, IRQ_TYPE_EDGE_RISING];
begin_node(fdt, &format!("U6_16550A@{:x}", addr))?;
property_string(fdt, "compatible", "ns16550a")?;
property(fdt, "reg", &serial_reg_prop)?;
property_u32(fdt, "clock-frequency", AARCH64_SERIAL_SPEED)?;
property(fdt, "interrupts", &irq)?;
end_node(fdt)?;
let serial_node = fdt.begin_node(&format!("U6_16550A@{:x}", addr))?;
fdt.property_string("compatible", "ns16550a")?;
fdt.property_array_u64("reg", &serial_reg_prop)?;
fdt.property_u32("clock-frequency", AARCH64_SERIAL_SPEED)?;
fdt.property_array_u32("interrupts", &irq)?;
fdt.end_node(serial_node)?;
Ok(())
}
fn create_serial_nodes(fdt: &mut Vec<u8>) -> Result<()> {
fn create_serial_nodes(fdt: &mut FdtWriter) -> Result<()> {
// Note that SERIAL_ADDR contains the I/O port addresses conventionally used
// for serial ports on x86. This uses the same addresses (but on the MMIO bus)
// to simplify the shared serial code.
@ -184,36 +177,32 @@ fn create_serial_nodes(fdt: &mut Vec<u8>) -> Result<()> {
Ok(())
}
fn create_psci_node(fdt: &mut Vec<u8>, version: &PsciVersion) -> Result<()> {
fn create_psci_node(fdt: &mut FdtWriter, version: &PsciVersion) -> Result<()> {
let mut compatible = vec![format!("arm,psci-{}.{}", version.major, version.minor)];
if version.major == 1 {
// Put `psci-0.2` as well because PSCI 1.0 is compatible with PSCI 0.2.
compatible.push(format!("arm,psci-0.2"))
};
begin_node(fdt, "psci")?;
property_string_list(fdt, "compatible", compatible)?;
let psci_node = fdt.begin_node("psci")?;
fdt.property_string_list("compatible", compatible)?;
// Only support aarch64 guest
property_string(fdt, "method", "hvc")?;
end_node(fdt)?;
fdt.property_string("method", "hvc")?;
fdt.end_node(psci_node)?;
Ok(())
}
fn create_chosen_node(
fdt: &mut Vec<u8>,
cmdline: &CStr,
fdt: &mut FdtWriter,
cmdline: &str,
initrd: Option<(GuestAddress, usize)>,
) -> Result<()> {
begin_node(fdt, "chosen")?;
property_u32(fdt, "linux,pci-probe-only", 1)?;
property_cstring(fdt, "bootargs", cmdline)?;
let chosen_node = fdt.begin_node("chosen")?;
fdt.property_u32("linux,pci-probe-only", 1)?;
fdt.property_string("bootargs", cmdline)?;
// Used by android bootloader for boot console output
property_string(
fdt,
"stdout-path",
&format!("/U6_16550A@{:x}", SERIAL_ADDR[0]),
)?;
fdt.property_string("stdout-path", &format!("/U6_16550A@{:x}", SERIAL_ADDR[0]))?;
let mut random_file = File::open("/dev/urandom").map_err(Error::FdtIoError)?;
let mut kaslr_seed_bytes = [0u8; 8];
@ -221,27 +210,27 @@ fn create_chosen_node(
.read_exact(&mut kaslr_seed_bytes)
.map_err(Error::FdtIoError)?;
let kaslr_seed = u64::from_le_bytes(kaslr_seed_bytes);
property_u64(fdt, "kaslr-seed", kaslr_seed)?;
fdt.property_u64("kaslr-seed", kaslr_seed)?;
let mut rng_seed_bytes = [0u8; 256];
random_file
.read_exact(&mut rng_seed_bytes)
.map_err(Error::FdtIoError)?;
property(fdt, "rng-seed", &rng_seed_bytes)?;
fdt.property("rng-seed", &rng_seed_bytes)?;
if let Some((initrd_addr, initrd_size)) = initrd {
let initrd_start = initrd_addr.offset() as u32;
let initrd_end = initrd_start + initrd_size as u32;
property_u32(fdt, "linux,initrd-start", initrd_start)?;
property_u32(fdt, "linux,initrd-end", initrd_end)?;
fdt.property_u32("linux,initrd-start", initrd_start)?;
fdt.property_u32("linux,initrd-end", initrd_end)?;
}
end_node(fdt)?;
fdt.end_node(chosen_node)?;
Ok(())
}
fn create_pci_nodes(
fdt: &mut Vec<u8>,
fdt: &mut FdtWriter,
pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>,
pci_device_base: u64,
pci_device_size: u64,
@ -249,7 +238,7 @@ fn create_pci_nodes(
// Add devicetree nodes describing a PCI generic host controller.
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
// and "PCI Bus Binding to IEEE Std 1275-1994".
let ranges = generate_prop32(&[
let ranges = [
// mmio addresses
0x3000000, // (ss = 11: 64-bit memory space)
(AARCH64_MMIO_BASE >> 32) as u32, // PCI address
@ -266,9 +255,9 @@ fn create_pci_nodes(
pci_device_base as u32,
(pci_device_size >> 32) as u32, // size
pci_device_size as u32,
]);
let bus_range = generate_prop32(&[0, 0]); // Only bus 0
let reg = generate_prop64(&[AARCH64_PCI_CFG_BASE, AARCH64_PCI_CFG_SIZE]);
];
let bus_range = [0, 0]; // Only bus 0
let reg = [AARCH64_PCI_CFG_BASE, AARCH64_PCI_CFG_SIZE];
let mut interrupts: Vec<u32> = Vec::new();
let mut masks: Vec<u32> = Vec::new();
@ -301,51 +290,48 @@ fn create_pci_nodes(
masks.push(0x7); // allow INTA#-INTD# (1 | 2 | 3 | 4)
}
let interrupt_map = generate_prop32(&interrupts);
let interrupt_map_mask = generate_prop32(&masks);
begin_node(fdt, "pci")?;
property_string(fdt, "compatible", "pci-host-cam-generic")?;
property_string(fdt, "device_type", "pci")?;
property(fdt, "ranges", &ranges)?;
property(fdt, "bus-range", &bus_range)?;
property_u32(fdt, "#address-cells", 3)?;
property_u32(fdt, "#size-cells", 2)?;
property(fdt, "reg", &reg)?;
property_u32(fdt, "#interrupt-cells", 1)?;
property(fdt, "interrupt-map", &interrupt_map)?;
property(fdt, "interrupt-map-mask", &interrupt_map_mask)?;
property_null(fdt, "dma-coherent")?;
end_node(fdt)?;
let pci_node = fdt.begin_node("pci")?;
fdt.property_string("compatible", "pci-host-cam-generic")?;
fdt.property_string("device_type", "pci")?;
fdt.property_array_u32("ranges", &ranges)?;
fdt.property_array_u32("bus-range", &bus_range)?;
fdt.property_u32("#address-cells", 3)?;
fdt.property_u32("#size-cells", 2)?;
fdt.property_array_u64("reg", &reg)?;
fdt.property_u32("#interrupt-cells", 1)?;
fdt.property_array_u32("interrupt-map", &interrupts)?;
fdt.property_array_u32("interrupt-map-mask", &masks)?;
fdt.property_null("dma-coherent")?;
fdt.end_node(pci_node)?;
Ok(())
}
fn create_rtc_node(fdt: &mut Vec<u8>) -> Result<()> {
fn create_rtc_node(fdt: &mut FdtWriter) -> Result<()> {
// the kernel driver for pl030 really really wants a clock node
// associated with an AMBA device or it will fail to probe, so we
// need to make up a clock node to associate with the pl030 rtc
// node and an associated handle with a unique phandle value.
const CLK_PHANDLE: u32 = 24;
begin_node(fdt, "pclk@3M")?;
property_u32(fdt, "#clock-cells", 0)?;
property_string(fdt, "compatible", "fixed-clock")?;
property_u32(fdt, "clock-frequency", 3141592)?;
property_u32(fdt, "phandle", CLK_PHANDLE)?;
end_node(fdt)?;
let clock_node = fdt.begin_node("pclk@3M")?;
fdt.property_u32("#clock-cells", 0)?;
fdt.property_string("compatible", "fixed-clock")?;
fdt.property_u32("clock-frequency", 3141592)?;
fdt.property_u32("phandle", CLK_PHANDLE)?;
fdt.end_node(clock_node)?;
let rtc_name = format!("rtc@{:x}", AARCH64_RTC_ADDR);
let reg = generate_prop64(&[AARCH64_RTC_ADDR, AARCH64_RTC_SIZE]);
let irq = generate_prop32(&[GIC_FDT_IRQ_TYPE_SPI, AARCH64_RTC_IRQ, IRQ_TYPE_LEVEL_HIGH]);
let reg = [AARCH64_RTC_ADDR, AARCH64_RTC_SIZE];
let irq = [GIC_FDT_IRQ_TYPE_SPI, AARCH64_RTC_IRQ, IRQ_TYPE_LEVEL_HIGH];
begin_node(fdt, &rtc_name)?;
property_string(fdt, "compatible", "arm,primecell")?;
property_u32(fdt, "arm,primecell-periphid", PL030_AMBA_ID)?;
property(fdt, "reg", &reg)?;
property(fdt, "interrupts", &irq)?;
property_u32(fdt, "clocks", CLK_PHANDLE)?;
property_string(fdt, "clock-names", "apb_pclk")?;
end_node(fdt)?;
let rtc_node = fdt.begin_node(&rtc_name)?;
fdt.property_string("compatible", "arm,primecell")?;
fdt.property_u32("arm,primecell-periphid", PL030_AMBA_ID)?;
fdt.property_array_u64("reg", &reg)?;
fdt.property_array_u32("interrupts", &irq)?;
fdt.property_u32("clocks", CLK_PHANDLE)?;
fdt.property_string("clock-names", "apb_pclk")?;
fdt.end_node(rtc_node)?;
Ok(())
}
@ -374,22 +360,21 @@ pub fn create_fdt(
fdt_load_offset: u64,
pci_device_base: u64,
pci_device_size: u64,
cmdline: &CStr,
cmdline: &str,
initrd: Option<(GuestAddress, usize)>,
android_fstab: Option<File>,
is_gicv3: bool,
use_pmu: bool,
psci_version: PsciVersion,
) -> Result<()> {
let mut fdt = vec![0; fdt_max_size];
start_fdt(&mut fdt, fdt_max_size)?;
let mut fdt = FdtWriter::new(&[]);
// The whole thing is put into one giant node with some top level properties
begin_node(&mut fdt, "")?;
property_u32(&mut fdt, "interrupt-parent", PHANDLE_GIC)?;
property_string(&mut fdt, "compatible", "linux,dummy-virt")?;
property_u32(&mut fdt, "#address-cells", 0x2)?;
property_u32(&mut fdt, "#size-cells", 0x2)?;
let root_node = fdt.begin_node("")?;
fdt.property_u32("interrupt-parent", PHANDLE_GIC)?;
fdt.property_string("compatible", "linux,dummy-virt")?;
fdt.property_u32("#address-cells", 0x2)?;
fdt.property_u32("#size-cells", 0x2)?;
if let Some(android_fstab) = android_fstab {
arch::android::create_android_fdt(&mut fdt, android_fstab)?;
}
@ -406,11 +391,9 @@ pub fn create_fdt(
create_pci_nodes(&mut fdt, pci_irqs, pci_device_base, pci_device_size)?;
create_rtc_node(&mut fdt)?;
// End giant node
end_node(&mut fdt)?;
fdt.end_node(root_node)?;
// Allocate another buffer so we can format and then write fdt to guest
let mut fdt_final = vec![0; fdt_max_size];
finish_fdt(&mut fdt, &mut fdt_final, fdt_max_size)?;
let fdt_final = fdt.finish(fdt_max_size)?;
let fdt_address = GuestAddress(AARCH64_PHYS_MEM_START + fdt_load_offset);
let written = guest_mem

View file

@ -385,7 +385,7 @@ impl arch::LinuxArch for AArch64 {
fdt_offset(components.memory_size, has_bios),
pci_device_base,
pci_device_size,
&CString::new(cmdline).unwrap(),
cmdline.as_str(),
initrd,
components.android_fstab,
irq_chip.get_vgic_version() == DeviceKind::ArmVgicV3,

View file

@ -23,3 +23,4 @@ base = { path = "../base" }
vm_control = { path = "../vm_control" }
vm_memory = { path = "../vm_memory" }
power_monitor = { path = "../power_monitor" }
thiserror = "1.0.20"

View file

@ -6,7 +6,7 @@ use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use crate::fdt::{begin_node, end_node, property_string, Error, Result};
use crate::fdt::{Error, FdtWriter, Result};
fn parse_fstab_line(line: &str) -> Result<Vec<String>> {
let vec: Vec<&str> = line.split_whitespace().collect();
@ -23,36 +23,36 @@ fn parse_fstab_line(line: &str) -> Result<Vec<String>> {
///
/// * `fdt` - The DTB to modify. The top-most node should be open.
/// * `android-fstab` - A text file of Android fstab entries to add to the DTB
pub fn create_android_fdt(fdt: &mut Vec<u8>, fstab: File) -> Result<()> {
pub fn create_android_fdt(fdt: &mut FdtWriter, fstab: File) -> Result<()> {
let vecs = BufReader::new(fstab)
.lines()
.map(|l| parse_fstab_line(&l.map_err(Error::FdtIoError)?))
.collect::<Result<Vec<Vec<String>>>>()?;
begin_node(fdt, "firmware")?;
begin_node(fdt, "android")?;
property_string(fdt, "compatible", "android,firmware")?;
let firmware_node = fdt.begin_node("firmware")?;
let android_node = fdt.begin_node("android")?;
fdt.property_string("compatible", "android,firmware")?;
let (dtprop, fstab): (_, Vec<_>) = vecs.into_iter().partition(|x| x[0] == "#dt-vendor");
begin_node(fdt, "vendor")?;
let vendor_node = fdt.begin_node("vendor")?;
for vec in dtprop {
let content = std::fs::read_to_string(&vec[2]).map_err(Error::FdtIoError)?;
property_string(fdt, &vec[1], &content)?;
fdt.property_string(&vec[1], &content)?;
}
end_node(fdt)?; // vendor
begin_node(fdt, "fstab")?;
property_string(fdt, "compatible", "android,fstab")?;
fdt.end_node(vendor_node)?;
let fstab_node = fdt.begin_node("fstab")?;
fdt.property_string("compatible", "android,fstab")?;
for vec in fstab {
let partition = &vec[1][1..];
begin_node(fdt, partition)?;
property_string(fdt, "compatible", &("android,".to_owned() + partition))?;
property_string(fdt, "dev", &vec[0])?;
property_string(fdt, "type", &vec[2])?;
property_string(fdt, "mnt_flags", &vec[3])?;
property_string(fdt, "fsmgr_flags", &vec[4])?;
end_node(fdt)?;
let partition_node = fdt.begin_node(partition)?;
fdt.property_string("compatible", &("android,".to_owned() + partition))?;
fdt.property_string("dev", &vec[0])?;
fdt.property_string("type", &vec[2])?;
fdt.property_string("mnt_flags", &vec[3])?;
fdt.property_string("fsmgr_flags", &vec[4])?;
fdt.end_node(partition_node)?;
}
end_node(fdt)?; // fstab
end_node(fdt)?; // android
end_node(fdt)?; // firmware
fdt.end_node(fstab_node)?;
fdt.end_node(android_node)?;
fdt.end_node(firmware_node)?;
Ok(())
}

View file

@ -2,240 +2,746 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use libc::{c_char, c_int, c_void};
use std::ffi::{CStr, CString};
use std::fmt::{self, Display};
//! This module writes Flattened Devicetree blobs as defined here:
//! https://devicetree-specification.readthedocs.io/en/stable/flattened-format.html
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::ffi::CString;
use std::io;
use std::ptr::null;
use std::mem::size_of;
// This links to libfdt which handles the creation of the binary blob
// flattened device tree (fdt) that is passed to the kernel and indicates
// the hardware configuration of the machine.
#[link(name = "fdt")]
extern "C" {
fn fdt_create(buf: *mut c_void, bufsize: c_int) -> c_int;
fn fdt_finish_reservemap(fdt: *mut c_void) -> c_int;
fn fdt_begin_node(fdt: *mut c_void, name: *const c_char) -> c_int;
fn fdt_property(fdt: *mut c_void, name: *const c_char, val: *const c_void, len: c_int)
-> c_int;
fn fdt_end_node(fdt: *mut c_void) -> c_int;
fn fdt_open_into(fdt: *const c_void, buf: *mut c_void, bufsize: c_int) -> c_int;
fn fdt_finish(fdt: *const c_void) -> c_int;
fn fdt_pack(fdt: *mut c_void) -> c_int;
}
use thiserror::Error as ThisError;
#[derive(Debug)]
#[derive(ThisError, Debug)]
pub enum Error {
FdtCreateError(c_int),
FdtFinishReservemapError(c_int),
FdtBeginNodeError(c_int),
FdtPropertyError(c_int),
FdtEndNodeError(c_int),
FdtOpenIntoError(c_int),
FdtFinishError(c_int),
FdtPackError(c_int),
#[error("Properties may not be added after a node has been ended")]
PropertyAfterEndNode,
#[error("Property value size must fit in 32 bits")]
PropertyValueTooLarge,
#[error("Total size must fit in 32 bits")]
TotalSizeTooLarge,
#[error("Strings cannot contain NUL")]
InvalidString,
#[error("Attempted to end a node that was not the most recent")]
OutOfOrderEndNode,
#[error("Attempted to call finish without ending all nodes")]
UnclosedNode,
#[error("Error writing FDT to guest memory")]
FdtGuestMemoryWriteError,
#[error("Parse error reading FDT parameters")]
FdtFileParseError,
#[error("I/O error reading FDT parameters code={0}")]
FdtIoError(io::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
write!(f, "libfdt: ")?;
match self {
FdtCreateError(ret) => write!(f, "error creating FDT, code={}", ret),
FdtFinishReservemapError(ret) => write!(f, "error finishing reserve map, code={}", ret),
FdtBeginNodeError(ret) => write!(f, "error beginning FDT node, code={}", ret),
FdtPropertyError(ret) => write!(f, "error adding FDT property, code={}", ret),
FdtEndNodeError(ret) => write!(f, "error ending FDT node, code={}", ret),
FdtOpenIntoError(ret) => write!(f, "error copying FDT to Guest, code={}", ret),
FdtFinishError(ret) => write!(f, "error performing FDT finish, code={}", ret),
FdtPackError(ret) => write!(f, "error packing FDT, code={}", ret),
FdtGuestMemoryWriteError => write!(f, "error writing FDT to guest memory"),
FdtFileParseError => write!(f, "parse error reading FDT parameters"),
FdtIoError(ret) => write!(f, "I/O error reading FDT parameters code={}", ret),
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
impl std::error::Error for Error {}
const FDT_HEADER_SIZE: usize = 40;
const FDT_VERSION: u32 = 17;
const FDT_LAST_COMP_VERSION: u32 = 16;
pub fn begin_node(fdt: &mut Vec<u8>, name: &str) -> Result<()> {
let cstr_name = CString::new(name).unwrap();
const FDT_MAGIC: u32 = 0xd00dfeed;
// Safe because we allocated fdt and converted name to a CString
let fdt_ret = unsafe { fdt_begin_node(fdt.as_mut_ptr() as *mut c_void, cstr_name.as_ptr()) };
if fdt_ret != 0 {
return Err(Error::FdtBeginNodeError(fdt_ret));
}
Ok(())
const FDT_BEGIN_NODE: u32 = 0x00000001;
const FDT_END_NODE: u32 = 0x00000002;
const FDT_PROP: u32 = 0x00000003;
const FDT_END: u32 = 0x00000009;
/// Interface for writing a Flattened Devicetree (FDT) and emitting a Devicetree Blob (FDT).
///
/// # Example
///
/// ```rust
/// use arch::fdt::FdtWriter;
///
/// # fn main() -> arch::fdt::Result<()> {
/// let mut fdt = FdtWriter::new(&[]);
/// let root_node = fdt.begin_node("")?;
/// fdt.property_string("compatible", "linux,dummy-virt")?;
/// fdt.property_u32("#address-cells", 0x2)?;
/// fdt.property_u32("#size-cells", 0x2)?;
/// let chosen_node = fdt.begin_node("chosen")?;
/// fdt.property_u32("linux,pci-probe-only", 1)?;
/// fdt.property_string("bootargs", "panic=-1 console=hvc0 root=/dev/vda")?;
/// fdt.end_node(chosen_node)?;
/// fdt.end_node(root_node)?;
/// let dtb = fdt.finish(0x1000)?;
/// # Ok(())
/// # }
/// ```
pub struct FdtWriter {
data: Vec<u8>,
off_mem_rsvmap: u32,
off_dt_struct: u32,
strings: Vec<u8>,
string_offsets: BTreeMap<CString, u32>,
node_depth: usize,
node_ended: bool,
boot_cpuid_phys: u32,
}
pub fn end_node(fdt: &mut Vec<u8>) -> Result<()> {
// Safe because we allocated fdt
let fdt_ret = unsafe { fdt_end_node(fdt.as_mut_ptr() as *mut c_void) };
if fdt_ret != 0 {
return Err(Error::FdtEndNodeError(fdt_ret));
}
Ok(())
/// Reserved physical memory region.
///
/// This represents an area of physical memory reserved by the firmware and unusable by the OS.
/// For example, this could be used to preserve bootloader code or data used at runtime.
pub struct FdtReserveEntry {
/// Physical address of the beginning of the reserved region.
pub address: u64,
/// Size of the reserved region in bytes.
pub size: u64,
}
pub fn property(fdt: &mut Vec<u8>, name: &str, val: &[u8]) -> Result<()> {
let cstr_name = CString::new(name).unwrap();
let val_ptr = val.as_ptr() as *const c_void;
/// Handle to an open node created by `FdtWriter::begin_node`.
///
/// This must be passed back to `FdtWriter::end_node` to close the nodes.
/// Nodes must be closed in reverse order as they were opened, matching the nesting structure
/// of the devicetree.
#[derive(Debug)]
pub struct FdtWriterNode {
depth: usize,
}
// Safe because we allocated fdt and converted name to a CString
let fdt_ret = unsafe {
fdt_property(
fdt.as_mut_ptr() as *mut c_void,
cstr_name.as_ptr(),
val_ptr,
val.len() as i32,
)
impl FdtWriter {
/// Create a new Flattened Devicetree writer instance.
///
/// # Arguments
///
/// `mem_reservations` - reserved physical memory regions to list in the FDT header.
pub fn new(mem_reservations: &[FdtReserveEntry]) -> Self {
let data = vec![0u8; FDT_HEADER_SIZE]; // Reserve space for header.
let mut fdt = FdtWriter {
data,
off_mem_rsvmap: 0,
off_dt_struct: 0,
strings: Vec::new(),
string_offsets: BTreeMap::new(),
node_depth: 0,
node_ended: false,
boot_cpuid_phys: 0,
};
if fdt_ret != 0 {
return Err(Error::FdtPropertyError(fdt_ret));
}
Ok(())
fdt.align(8);
fdt.off_mem_rsvmap = fdt.data.len() as u32;
fdt.write_mem_rsvmap(mem_reservations);
fdt.align(4);
fdt.off_dt_struct = fdt.data.len() as u32;
fdt
}
fn cpu_to_fdt32(input: u32) -> [u8; 4] {
input.to_be_bytes()
fn write_mem_rsvmap(&mut self, mem_reservations: &[FdtReserveEntry]) {
for rsv in mem_reservations {
self.append_u64(rsv.address);
self.append_u64(rsv.size);
}
fn cpu_to_fdt64(input: u64) -> [u8; 8] {
input.to_be_bytes()
self.append_u64(0);
self.append_u64(0);
}
pub fn property_u32(fdt: &mut Vec<u8>, name: &str, val: u32) -> Result<()> {
property(fdt, name, &cpu_to_fdt32(val))
/// Set the `boot_cpuid_phys` field of the devicetree header.
pub fn set_boot_cpuid_phys(&mut self, boot_cpuid_phys: u32) {
self.boot_cpuid_phys = boot_cpuid_phys;
}
pub fn property_u64(fdt: &mut Vec<u8>, name: &str, val: u64) -> Result<()> {
property(fdt, name, &cpu_to_fdt64(val))
// Append `num_bytes` padding bytes (0x00).
fn pad(&mut self, num_bytes: usize) {
self.data.extend(std::iter::repeat(0).take(num_bytes));
}
// Helper to generate a properly formatted byte vector using 32-bit cells
pub fn generate_prop32(cells: &[u32]) -> Vec<u8> {
let mut ret: Vec<u8> = Vec::new();
for &e in cells {
ret.extend(&cpu_to_fdt32(e));
// Append padding bytes (0x00) until the length of data is a multiple of `alignment`.
fn align(&mut self, alignment: usize) {
let offset = self.data.len() % alignment;
if offset != 0 {
self.pad(alignment - offset);
}
ret
}
// Helper to generate a properly formatted byte vector using 64-bit cells
pub fn generate_prop64(cells: &[u64]) -> Vec<u8> {
let mut ret: Vec<u8> = Vec::new();
for &e in cells {
ret.extend(&cpu_to_fdt64(e));
}
ret
// Rewrite the value of a big-endian u32 within data.
fn update_u32(&mut self, offset: usize, val: u32) {
let data_slice = &mut self.data[offset..offset + 4];
data_slice.copy_from_slice(&val.to_be_bytes());
}
pub fn property_null(fdt: &mut Vec<u8>, name: &str) -> Result<()> {
let cstr_name = CString::new(name).unwrap();
// Safe because we allocated fdt, converted name to a CString
let fdt_ret = unsafe {
fdt_property(
fdt.as_mut_ptr() as *mut c_void,
cstr_name.as_ptr(),
null(),
0,
)
};
if fdt_ret != 0 {
return Err(Error::FdtPropertyError(fdt_ret));
}
Ok(())
fn append_u32(&mut self, val: u32) {
self.data.extend_from_slice(&val.to_be_bytes());
}
pub fn property_bytes(fdt: &mut Vec<u8>, name: &str, bytes: &[u8]) -> Result<()> {
let cstr_name = CString::new(name).unwrap();
// Safe because we allocated fdt, converted name to CStrings
let fdt_ret = unsafe {
fdt_property(
fdt.as_mut_ptr() as *mut c_void,
cstr_name.as_ptr(),
bytes.as_ptr() as *mut c_void,
bytes.len() as i32,
)
};
if fdt_ret != 0 {
return Err(Error::FdtPropertyError(fdt_ret));
}
Ok(())
fn append_u64(&mut self, val: u64) {
self.data.extend_from_slice(&val.to_be_bytes());
}
pub fn property_cstring(fdt: &mut Vec<u8>, name: &str, cstr_value: &CStr) -> Result<()> {
let value_bytes = cstr_value.to_bytes_with_nul();
property_bytes(fdt, name, value_bytes)
}
pub fn property_string(fdt: &mut Vec<u8>, name: &str, value: &str) -> Result<()> {
let cstr_value = CString::new(value).unwrap();
property_cstring(fdt, name, &cstr_value)
}
pub fn property_string_list(fdt: &mut Vec<u8>, name: &str, values: Vec<String>) -> Result<()> {
let bytes = values
.into_iter()
.map(|s| {
let mut bytes = s.into_bytes();
// Each value must be null-terminated.
bytes.push(0);
bytes
/// Open a new FDT node.
///
/// The node must be closed using `end_node`.
///
/// # Arguments
///
/// `name` - name of the node; must not contain any NUL bytes.
pub fn begin_node(&mut self, name: &str) -> Result<FdtWriterNode> {
let name_cstr = CString::new(name).map_err(|_| Error::InvalidString)?;
self.append_u32(FDT_BEGIN_NODE);
self.data.extend(name_cstr.to_bytes_with_nul());
self.align(4);
self.node_depth += 1;
self.node_ended = false;
Ok(FdtWriterNode {
depth: self.node_depth,
})
.flatten()
.collect::<Vec<u8>>();
property_bytes(fdt, name, &bytes)
}
pub fn start_fdt(fdt: &mut Vec<u8>, fdt_max_size: usize) -> Result<()> {
// Safe since we allocated this array with fdt_max_size
let mut fdt_ret = unsafe { fdt_create(fdt.as_mut_ptr() as *mut c_void, fdt_max_size as c_int) };
/// Close a node previously opened with `begin_node`.
pub fn end_node(&mut self, node: FdtWriterNode) -> Result<()> {
if node.depth != self.node_depth {
return Err(Error::OutOfOrderEndNode);
}
if fdt_ret != 0 {
return Err(Error::FdtCreateError(fdt_ret));
}
// Safe since we allocated this array
fdt_ret = unsafe { fdt_finish_reservemap(fdt.as_mut_ptr() as *mut c_void) };
if fdt_ret != 0 {
return Err(Error::FdtFinishReservemapError(fdt_ret));
}
self.append_u32(FDT_END_NODE);
self.node_depth -= 1;
self.node_ended = true;
Ok(())
}
pub fn finish_fdt(fdt: &mut Vec<u8>, fdt_final: &mut Vec<u8>, fdt_max_size: usize) -> Result<()> {
// Safe since we allocated fdt_final and previously passed in it's size
let mut fdt_ret = unsafe { fdt_finish(fdt.as_mut_ptr() as *mut c_void) };
if fdt_ret != 0 {
return Err(Error::FdtFinishError(fdt_ret));
// Find an existing instance of a string `s`, or add it to the strings block.
// Returns the offset into the strings block.
fn intern_string(&mut self, s: CString) -> u32 {
if let Some(off) = self.string_offsets.get(&s) {
*off
} else {
let off = self.strings.len() as u32;
self.strings.extend_from_slice(s.to_bytes_with_nul());
self.string_offsets.insert(s, off);
off
}
}
// Safe because we allocated both arrays with the correct size
fdt_ret = unsafe {
fdt_open_into(
fdt.as_mut_ptr() as *mut c_void,
fdt_final.as_mut_ptr() as *mut c_void,
fdt_max_size as i32,
)
};
if fdt_ret != 0 {
return Err(Error::FdtOpenIntoError(fdt_ret));
/// Write a property.
///
/// # Arguments
///
/// `name` - name of the property; must not contain any NUL bytes.
/// `val` - value of the property (raw byte array).
pub fn property(&mut self, name: &str, val: &[u8]) -> Result<()> {
if self.node_ended {
return Err(Error::PropertyAfterEndNode);
}
// Safe since we allocated fdt_final
fdt_ret = unsafe { fdt_pack(fdt_final.as_mut_ptr() as *mut c_void) };
if fdt_ret != 0 {
return Err(Error::FdtPackError(fdt_ret));
}
let name_cstr = CString::new(name).map_err(|_| Error::InvalidString)?;
let len = val
.len()
.try_into()
.map_err(|_| Error::PropertyValueTooLarge)?;
let nameoff = self.intern_string(name_cstr);
self.append_u32(FDT_PROP);
self.append_u32(len);
self.append_u32(nameoff);
self.data.extend_from_slice(val);
self.align(4);
Ok(())
}
/// Write an empty property.
pub fn property_null(&mut self, name: &str) -> Result<()> {
self.property(name, &[])
}
/// Write a string property.
pub fn property_string(&mut self, name: &str, val: &str) -> Result<()> {
let cstr_value = CString::new(val).map_err(|_| Error::InvalidString)?;
self.property(name, cstr_value.to_bytes_with_nul())
}
/// Write a stringlist property.
pub fn property_string_list(&mut self, name: &str, values: Vec<String>) -> Result<()> {
let mut bytes = Vec::new();
for s in values {
let cstr = CString::new(s).map_err(|_| Error::InvalidString)?;
bytes.extend_from_slice(&cstr.to_bytes_with_nul());
}
self.property(name, &bytes)
}
/// Write a 32-bit unsigned integer property.
pub fn property_u32(&mut self, name: &str, val: u32) -> Result<()> {
self.property(name, &val.to_be_bytes())
}
/// Write a 64-bit unsigned integer property.
pub fn property_u64(&mut self, name: &str, val: u64) -> Result<()> {
self.property(name, &val.to_be_bytes())
}
/// Write a property containing an array of 32-bit unsigned integers.
pub fn property_array_u32(&mut self, name: &str, cells: &[u32]) -> Result<()> {
let mut arr = Vec::with_capacity(cells.len() * size_of::<u32>());
for &c in cells {
arr.extend(&c.to_be_bytes());
}
self.property(name, &arr)
}
/// Write a property containing an array of 64-bit unsigned integers.
pub fn property_array_u64(&mut self, name: &str, cells: &[u64]) -> Result<()> {
let mut arr = Vec::with_capacity(cells.len() * size_of::<u64>());
for &c in cells {
arr.extend(&c.to_be_bytes());
}
self.property(name, &arr)
}
/// Finish writing the Devicetree Blob (DTB).
///
/// Returns the DTB as a vector of bytes, consuming the `FdtWriter`.
/// The DTB is always padded up to `max_size` with zeroes, so the returned
/// value will either be exactly `max_size` bytes long, or an error will
/// be returned if the DTB does not fit in `max_size` bytes.
///
/// # Arguments
///
/// `max_size` - Maximum size of the finished DTB in bytes.
pub fn finish(mut self, max_size: usize) -> Result<Vec<u8>> {
if self.node_depth > 0 {
return Err(Error::UnclosedNode);
}
self.append_u32(FDT_END);
let size_dt_struct = self.data.len() as u32 - self.off_dt_struct;
let totalsize = self.data.len() + self.strings.len();
let totalsize = totalsize.try_into().map_err(|_| Error::TotalSizeTooLarge)?;
let off_dt_strings = self
.data
.len()
.try_into()
.map_err(|_| Error::TotalSizeTooLarge)?;
let size_dt_strings = self
.strings
.len()
.try_into()
.map_err(|_| Error::TotalSizeTooLarge)?;
// Finalize the header.
self.update_u32(0, FDT_MAGIC);
self.update_u32(1 * 4, totalsize);
self.update_u32(2 * 4, self.off_dt_struct);
self.update_u32(3 * 4, off_dt_strings);
self.update_u32(4 * 4, self.off_mem_rsvmap);
self.update_u32(5 * 4, FDT_VERSION);
self.update_u32(6 * 4, FDT_LAST_COMP_VERSION);
self.update_u32(7 * 4, self.boot_cpuid_phys);
self.update_u32(8 * 4, size_dt_strings);
self.update_u32(9 * 4, size_dt_struct);
// Add the strings block.
self.data.append(&mut self.strings);
if self.data.len() > max_size {
Err(Error::TotalSizeTooLarge)
} else {
// Fill remaining data up to `max_size` with zeroes.
self.pad(max_size - self.data.len());
Ok(self.data)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn minimal() {
let mut fdt = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x48).unwrap(),
[
0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed)
0x00, 0x00, 0x00, 0x48, // 0004: totalsize (0x48)
0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38)
0x00, 0x00, 0x00, 0x48, // 000C: off_dt_strings (0x48)
0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28)
0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17)
0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16)
0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0)
0x00, 0x00, 0x00, 0x00, // 0020: size_dt_strings (0)
0x00, 0x00, 0x00, 0x10, // 0024: size_dt_struct (0x10)
0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high)
0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low)
0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high)
0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low)
0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE
0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding
0x00, 0x00, 0x00, 0x02, // 0040: FDT_END_NODE
0x00, 0x00, 0x00, 0x09, // 0044: FDT_END
]
);
}
#[test]
fn reservemap() {
let mut fdt = FdtWriter::new(&[
FdtReserveEntry {
address: 0x12345678AABBCCDD,
size: 0x1234,
},
FdtReserveEntry {
address: 0x1020304050607080,
size: 0x5678,
},
]);
let root_node = fdt.begin_node("").unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x68).unwrap(),
[
0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed)
0x00, 0x00, 0x00, 0x68, // 0004: totalsize (0x68)
0x00, 0x00, 0x00, 0x58, // 0008: off_dt_struct (0x58)
0x00, 0x00, 0x00, 0x68, // 000C: off_dt_strings (0x68)
0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28)
0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17)
0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16)
0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0)
0x00, 0x00, 0x00, 0x00, // 0020: size_dt_strings (0)
0x00, 0x00, 0x00, 0x10, // 0024: size_dt_struct (0x10)
0x12, 0x34, 0x56, 0x78, // 0028: rsvmap entry 0 address high
0xAA, 0xBB, 0xCC, 0xDD, // 002C: rsvmap entry 0 address low
0x00, 0x00, 0x00, 0x00, // 0030: rsvmap entry 0 size high
0x00, 0x00, 0x12, 0x34, // 0034: rsvmap entry 0 size low
0x10, 0x20, 0x30, 0x40, // 0038: rsvmap entry 1 address high
0x50, 0x60, 0x70, 0x80, // 003C: rsvmap entry 1 address low
0x00, 0x00, 0x00, 0x00, // 0040: rsvmap entry 1 size high
0x00, 0x00, 0x56, 0x78, // 0044: rsvmap entry 1 size low
0x00, 0x00, 0x00, 0x00, // 0048: rsvmap terminator (address = 0 high)
0x00, 0x00, 0x00, 0x00, // 004C: rsvmap terminator (address = 0 low)
0x00, 0x00, 0x00, 0x00, // 0050: rsvmap terminator (size = 0 high)
0x00, 0x00, 0x00, 0x00, // 0054: rsvmap terminator (size = 0 low)
0x00, 0x00, 0x00, 0x01, // 0058: FDT_BEGIN_NODE
0x00, 0x00, 0x00, 0x00, // 005C: node name ("") + padding
0x00, 0x00, 0x00, 0x02, // 0060: FDT_END_NODE
0x00, 0x00, 0x00, 0x09, // 0064: FDT_END
]
);
}
#[test]
fn prop_null() {
let mut fdt = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_null("null").unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x59).unwrap(),
[
0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed)
0x00, 0x00, 0x00, 0x59, // 0004: totalsize (0x59)
0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38)
0x00, 0x00, 0x00, 0x54, // 000C: off_dt_strings (0x54)
0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28)
0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17)
0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16)
0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0)
0x00, 0x00, 0x00, 0x05, // 0020: size_dt_strings (0x05)
0x00, 0x00, 0x00, 0x1c, // 0024: size_dt_struct (0x1C)
0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high)
0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low)
0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high)
0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low)
0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE
0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding
0x00, 0x00, 0x00, 0x03, // 0040: FDT_PROP
0x00, 0x00, 0x00, 0x00, // 0044: prop len (0)
0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0)
0x00, 0x00, 0x00, 0x02, // 004C: FDT_END_NODE
0x00, 0x00, 0x00, 0x09, // 0050: FDT_END
b'n', b'u', b'l', b'l', 0x00, // 0054: strings block
]
);
}
#[test]
fn prop_u32() {
let mut fdt = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_u32("u32", 0x12345678).unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x5C).unwrap(),
[
0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed)
0x00, 0x00, 0x00, 0x5c, // 0004: totalsize (0x5C)
0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38)
0x00, 0x00, 0x00, 0x58, // 000C: off_dt_strings (0x58)
0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28)
0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17)
0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16)
0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0)
0x00, 0x00, 0x00, 0x04, // 0020: size_dt_strings (0x04)
0x00, 0x00, 0x00, 0x20, // 0024: size_dt_struct (0x20)
0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high)
0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low)
0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high)
0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low)
0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE
0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding
0x00, 0x00, 0x00, 0x03, // 0040: FDT_PROP
0x00, 0x00, 0x00, 0x04, // 0044: prop len (4)
0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0)
0x12, 0x34, 0x56, 0x78, // 004C: prop u32 value (0x12345678)
0x00, 0x00, 0x00, 0x02, // 0050: FDT_END_NODE
0x00, 0x00, 0x00, 0x09, // 0054: FDT_END
b'u', b'3', b'2', 0x00, // 0058: strings block
]
);
}
#[test]
fn all_props() {
let mut fdt = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_null("null").unwrap();
fdt.property_u32("u32", 0x12345678).unwrap();
fdt.property_u64("u64", 0x1234567887654321).unwrap();
fdt.property_string("str", "hello").unwrap();
fdt.property_string_list("strlst", vec!["hi".into(), "bye".into()])
.unwrap();
fdt.property_array_u32("arru32", &[0x12345678, 0xAABBCCDD])
.unwrap();
fdt.property_array_u64("arru64", &[0x1234567887654321])
.unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0xEE).unwrap(),
[
0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed)
0x00, 0x00, 0x00, 0xee, // 0004: totalsize (0xEE)
0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38)
0x00, 0x00, 0x00, 0xc8, // 000C: off_dt_strings (0xC8)
0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28)
0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17)
0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16)
0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0)
0x00, 0x00, 0x00, 0x26, // 0020: size_dt_strings (0x26)
0x00, 0x00, 0x00, 0x90, // 0024: size_dt_struct (0x90)
0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high)
0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low)
0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high)
0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low)
0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE
0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding
0x00, 0x00, 0x00, 0x03, // 0040: FDT_PROP (null)
0x00, 0x00, 0x00, 0x00, // 0044: prop len (0)
0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0)
0x00, 0x00, 0x00, 0x03, // 004C: FDT_PROP (u32)
0x00, 0x00, 0x00, 0x04, // 0050: prop len (4)
0x00, 0x00, 0x00, 0x05, // 0054: prop nameoff (0x05)
0x12, 0x34, 0x56, 0x78, // 0058: prop u32 value (0x12345678)
0x00, 0x00, 0x00, 0x03, // 005C: FDT_PROP (u64)
0x00, 0x00, 0x00, 0x08, // 0060: prop len (8)
0x00, 0x00, 0x00, 0x09, // 0064: prop nameoff (0x09)
0x12, 0x34, 0x56, 0x78, // 0068: prop u64 value high (0x12345678)
0x87, 0x65, 0x43, 0x21, // 006C: prop u64 value low (0x87654321)
0x00, 0x00, 0x00, 0x03, // 0070: FDT_PROP (string)
0x00, 0x00, 0x00, 0x06, // 0074: prop len (6)
0x00, 0x00, 0x00, 0x0D, // 0078: prop nameoff (0x0D)
b'h', b'e', b'l', b'l', // 007C: prop str value ("hello") + padding
b'o', 0x00, 0x00, 0x00, // 0080: "o\0" + padding
0x00, 0x00, 0x00, 0x03, // 0084: FDT_PROP (string list)
0x00, 0x00, 0x00, 0x07, // 0088: prop len (7)
0x00, 0x00, 0x00, 0x11, // 008C: prop nameoff (0x11)
b'h', b'i', 0x00, b'b', // 0090: prop value ("hi", "bye")
b'y', b'e', 0x00, 0x00, // 0094: "ye\0" + padding
0x00, 0x00, 0x00, 0x03, // 0098: FDT_PROP (u32 array)
0x00, 0x00, 0x00, 0x08, // 009C: prop len (8)
0x00, 0x00, 0x00, 0x18, // 00A0: prop nameoff (0x18)
0x12, 0x34, 0x56, 0x78, // 00A4: prop value 0
0xAA, 0xBB, 0xCC, 0xDD, // 00A8: prop value 1
0x00, 0x00, 0x00, 0x03, // 00AC: FDT_PROP (u64 array)
0x00, 0x00, 0x00, 0x08, // 00B0: prop len (8)
0x00, 0x00, 0x00, 0x1f, // 00B4: prop nameoff (0x1F)
0x12, 0x34, 0x56, 0x78, // 00B8: prop u64 value 0 high
0x87, 0x65, 0x43, 0x21, // 00BC: prop u64 value 0 low
0x00, 0x00, 0x00, 0x02, // 00C0: FDT_END_NODE
0x00, 0x00, 0x00, 0x09, // 00C4: FDT_END
b'n', b'u', b'l', b'l', 0x00, // 00C8: strings + 0x00: "null""
b'u', b'3', b'2', 0x00, // 00CD: strings + 0x05: "u32"
b'u', b'6', b'4', 0x00, // 00D1: strings + 0x09: "u64"
b's', b't', b'r', 0x00, // 00D5: strings + 0x0D: "str"
b's', b't', b'r', b'l', b's', b't', 0x00, // 00D9: strings + 0x11: "strlst"
b'a', b'r', b'r', b'u', b'3', b'2', 0x00, // 00E0: strings + 0x18: "arru32"
b'a', b'r', b'r', b'u', b'6', b'4', 0x00, // 00E7: strings + 0x1F: "arru64"
]
);
}
#[test]
fn nested_nodes() {
let mut fdt = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_u32("abc", 0x13579024).unwrap();
let nested_node = fdt.begin_node("nested").unwrap();
fdt.property_u32("def", 0x12121212).unwrap();
fdt.end_node(nested_node).unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x80).unwrap(),
[
0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed)
0x00, 0x00, 0x00, 0x80, // 0004: totalsize (0x80)
0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38)
0x00, 0x00, 0x00, 0x78, // 000C: off_dt_strings (0x78)
0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28)
0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17)
0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16)
0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0)
0x00, 0x00, 0x00, 0x08, // 0020: size_dt_strings (0x08)
0x00, 0x00, 0x00, 0x40, // 0024: size_dt_struct (0x40)
0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high)
0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low)
0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high)
0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low)
0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE
0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding
0x00, 0x00, 0x00, 0x03, // 0040: FDT_PROP
0x00, 0x00, 0x00, 0x04, // 0044: prop len (4)
0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0x00)
0x13, 0x57, 0x90, 0x24, // 004C: prop u32 value (0x13579024)
0x00, 0x00, 0x00, 0x01, // 0050: FDT_BEGIN_NODE
b'n', b'e', b's', b't', // 0054: Node name ("nested")
b'e', b'd', 0x00, 0x00, // 0058: "ed\0" + pad
0x00, 0x00, 0x00, 0x03, // 005C: FDT_PROP
0x00, 0x00, 0x00, 0x04, // 0060: prop len (4)
0x00, 0x00, 0x00, 0x04, // 0064: prop nameoff (0x04)
0x12, 0x12, 0x12, 0x12, // 0068: prop u32 value (0x12121212)
0x00, 0x00, 0x00, 0x02, // 006C: FDT_END_NODE ("nested")
0x00, 0x00, 0x00, 0x02, // 0070: FDT_END_NODE ("")
0x00, 0x00, 0x00, 0x09, // 0074: FDT_END
b'a', b'b', b'c', 0x00, // 0078: strings + 0x00: "abc"
b'd', b'e', b'f', 0x00, // 007C: strings + 0x04: "def"
]
);
}
#[test]
fn prop_name_string_reuse() {
let mut fdt = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_u32("abc", 0x13579024).unwrap();
let nested_node = fdt.begin_node("nested").unwrap();
fdt.property_u32("def", 0x12121212).unwrap();
fdt.property_u32("abc", 0x12121212).unwrap(); // This should reuse the "abc" string.
fdt.end_node(nested_node).unwrap();
fdt.end_node(root_node).unwrap();
assert_eq!(
fdt.finish(0x90).unwrap(),
[
0xd0, 0x0d, 0xfe, 0xed, // 0000: magic (0xd00dfeed)
0x00, 0x00, 0x00, 0x90, // 0004: totalsize (0x90)
0x00, 0x00, 0x00, 0x38, // 0008: off_dt_struct (0x38)
0x00, 0x00, 0x00, 0x88, // 000C: off_dt_strings (0x88)
0x00, 0x00, 0x00, 0x28, // 0010: off_mem_rsvmap (0x28)
0x00, 0x00, 0x00, 0x11, // 0014: version (0x11 = 17)
0x00, 0x00, 0x00, 0x10, // 0018: last_comp_version (0x10 = 16)
0x00, 0x00, 0x00, 0x00, // 001C: boot_cpuid_phys (0)
0x00, 0x00, 0x00, 0x08, // 0020: size_dt_strings (0x08)
0x00, 0x00, 0x00, 0x50, // 0024: size_dt_struct (0x50)
0x00, 0x00, 0x00, 0x00, // 0028: rsvmap terminator (address = 0 high)
0x00, 0x00, 0x00, 0x00, // 002C: rsvmap terminator (address = 0 low)
0x00, 0x00, 0x00, 0x00, // 0030: rsvmap terminator (size = 0 high)
0x00, 0x00, 0x00, 0x00, // 0034: rsvmap terminator (size = 0 low)
0x00, 0x00, 0x00, 0x01, // 0038: FDT_BEGIN_NODE
0x00, 0x00, 0x00, 0x00, // 003C: node name ("") + padding
0x00, 0x00, 0x00, 0x03, // 0040: FDT_PROP
0x00, 0x00, 0x00, 0x04, // 0044: prop len (4)
0x00, 0x00, 0x00, 0x00, // 0048: prop nameoff (0x00)
0x13, 0x57, 0x90, 0x24, // 004C: prop u32 value (0x13579024)
0x00, 0x00, 0x00, 0x01, // 0050: FDT_BEGIN_NODE
b'n', b'e', b's', b't', // 0054: Node name ("nested")
b'e', b'd', 0x00, 0x00, // 0058: "ed\0" + pad
0x00, 0x00, 0x00, 0x03, // 005C: FDT_PROP
0x00, 0x00, 0x00, 0x04, // 0060: prop len (4)
0x00, 0x00, 0x00, 0x04, // 0064: prop nameoff (0x04)
0x12, 0x12, 0x12, 0x12, // 0068: prop u32 value (0x12121212)
0x00, 0x00, 0x00, 0x03, // 006C: FDT_PROP
0x00, 0x00, 0x00, 0x04, // 0070: prop len (4)
0x00, 0x00, 0x00, 0x00, // 0074: prop nameoff (0x00 - reuse)
0x12, 0x12, 0x12, 0x12, // 0078: prop u32 value (0x12121212)
0x00, 0x00, 0x00, 0x02, // 007C: FDT_END_NODE ("nested")
0x00, 0x00, 0x00, 0x02, // 0080: FDT_END_NODE ("")
0x00, 0x00, 0x00, 0x09, // 0084: FDT_END
b'a', b'b', b'c', 0x00, // 0088: strings + 0x00: "abc"
b'd', b'e', b'f', 0x00, // 008C: strings + 0x04: "def"
]
);
}
#[test]
fn invalid_node_name_nul() {
let mut fdt = FdtWriter::new(&[]);
fdt.begin_node("abc\0def")
.expect_err("node name with embedded NUL");
}
#[test]
fn invalid_prop_name_nul() {
let mut fdt = FdtWriter::new(&[]);
fdt.property_u32("abc\0def", 0)
.expect_err("property name with embedded NUL");
}
#[test]
fn invalid_prop_string_value_nul() {
let mut fdt = FdtWriter::new(&[]);
fdt.property_string("mystr", "abc\0def")
.expect_err("string property value with embedded NUL");
}
#[test]
fn invalid_prop_string_list_value_nul() {
let mut fdt = FdtWriter::new(&[]);
let strs = vec!["test".into(), "abc\0def".into()];
fdt.property_string_list("mystr", strs)
.expect_err("stringlist property value with embedded NUL");
}
#[test]
fn invalid_prop_after_end_node() {
let mut fdt = FdtWriter::new(&[]);
let _root_node = fdt.begin_node("").unwrap();
fdt.property_u32("ok_prop", 1234).unwrap();
let nested_node = fdt.begin_node("mynode").unwrap();
fdt.property_u32("ok_nested_prop", 5678).unwrap();
fdt.end_node(nested_node).unwrap();
fdt.property_u32("bad_prop_after_end_node", 1357)
.expect_err("property after end_node");
}
#[test]
fn invalid_end_node_out_of_order() {
let mut fdt = FdtWriter::new(&[]);
let root_node = fdt.begin_node("").unwrap();
fdt.property_u32("ok_prop", 1234).unwrap();
let _nested_node = fdt.begin_node("mynode").unwrap();
fdt.end_node(root_node)
.expect_err("end node while nested node is open");
}
#[test]
fn invalid_finish_while_node_open() {
let mut fdt = FdtWriter::new(&[]);
let _root_node = fdt.begin_node("").unwrap();
fdt.property_u32("ok_prop", 1234).unwrap();
let _nested_node = fdt.begin_node("mynode").unwrap();
fdt.property_u32("ok_nested_prop", 5678).unwrap();
fdt.finish(0x100)
.expect_err("finish without ending all nodes");
}
}

View file

@ -33,7 +33,6 @@ RUN apt-get install --yes --no-install-recommends -o APT::Immediate-Configure=fa
libdbus-1-dev:arm64 \
libdrm-dev:arm64 \
libepoxy-dev:arm64 \
libfdt-dev:arm64 \
libssl-dev:arm64 \
libwayland-dev:arm64

View file

@ -21,7 +21,6 @@ RUN apt-get install --yes --no-install-recommends \
libdbus-1-dev \
libdrm-dev \
libepoxy-dev \
libfdt-dev \
libssl-dev \
libwayland-dev \
qemu-system-x86

View file

@ -30,7 +30,6 @@ packages:
- libdbus-1-3
- libdrm2
- libepoxy0
- libfdt1
- libssl1.1
- libwayland-client0
- libx11-6

View file

@ -17,7 +17,6 @@ RUN apt-get update && apt-get install -y \
libcap-dev \
libdbus-1-dev \
libegl1-mesa-dev \
libfdt-dev \
libgl1-mesa-dev \
libgles2-mesa-dev \
libpciaccess-dev \

View file

@ -3,7 +3,7 @@
// found in the LICENSE file.
use arch::android::create_android_fdt;
use arch::fdt::{begin_node, end_node, finish_fdt, start_fdt, Error};
use arch::fdt::{Error, FdtWriter};
use data_model::DataInit;
use std::fs::File;
use std::mem;
@ -42,17 +42,14 @@ pub fn create_fdt(
// Reserve space for the setup_data
let fdt_data_size = fdt_max_size - mem::size_of::<setup_data>();
let mut fdt = vec![0; fdt_data_size];
start_fdt(&mut fdt, fdt_data_size)?;
let mut fdt = FdtWriter::new(&[]);
// The whole thing is put into one giant node with some top level properties
begin_node(&mut fdt, "")?;
let root_node = fdt.begin_node("")?;
create_android_fdt(&mut fdt, android_fstab)?;
end_node(&mut fdt)?;
fdt.end_node(root_node)?;
// Allocate another buffer so we can format and then write fdt to guest
let mut fdt_final = vec![0; fdt_data_size];
finish_fdt(&mut fdt, &mut fdt_final, fdt_data_size)?;
let fdt_final = fdt.finish(fdt_data_size)?;
assert_eq!(
mem::size_of::<setup_data>(),