Device Trees

This section will detail how to modify a hardware device tree.
It will not tell you how to modify a device tree for your specific hardware and peripherals, but it will provide the tools to do so.



Overview

A device tree is a way to represent hardware.  It is comprised of many device tree source (DTS) files and DTS include (DTSI) files.
When the source files are compiled, a flattened device tree (FDT), also known as a device tree blob (DTB), is created.
QEMU and Linux use the DTB to understand the structure of the hardware without any hard coding involved.

Acquiring the Tools

Before proceeding, the following programs and files should be installed on your computer

  • PetaLinux
  • BSPs, on this page we will use a ZCU102 BSP
  • Device tree compiler (DTC)

These are available with PetaLinux, Yocto, or Vitis tools.

Modifying a Device Tree

It is strongly recommended to read the device tree specification before modifying a device tree.
The specification will cover the structure, syntax, and good practices of device tree modification.

Device Tree Properties and QEMU

QEMU uses device trees to represent hardware.  This device tree is not passed to the guest and is sometimes referred to as the hardware device tree.

Some devices contain device-specific properties that should exist in the hardware device tree.

For example, an SI57X may have a device node that looks like:

clock-generator@5e {
	compatible = "silabs,si57x";
	reg = <0x5d>;
	temperature-stability = <0x32>;
};

The temperature-stability property is one that the device should have present.

If you want to add another device and you're not sure what properties need to be present, you can pass the -device <device-name>,help argument along with your other QEMU arguments when starting QEMU.

In this case, the argument will look like:

-device si57x,help

An alternative is to look at its properties list in the device's .c file in the QEMU source.

For example, the SI57X has the properties list:

qemu/hw/misc/si57x.c
static Property si57x_properties[] = {
    DEFINE_PROP_UINT16("temperature-stability", Si57xState, temp_stab,
                       TEMP_STAB_50PPM),
    DEFINE_PROP_END_OF_LIST(),
};

As we can see, it only looks for the temperature-stability property.

If a property is not defined in a device node, QEMU will use a default value.  In this case, 50PPM.

Device Tree Properties and the Guest

QEMU guests, such as Linux, can use device trees to understand the hardware it has access to.  For instance, a guest device tree may list partitions for a SPI flash.
So for this example, we will modify the device tree to add a partition to SPI flash on a ZCU102 platform using PetaLinux.

Create your ZCU102 project if you haven't already:

petalinux-create -t project -s "/path/to/bsp/xilinx-zcu102.bsp" -n "project-name"

Boot it once to make sure everything is working properly.  Take note of the DTBs used when booting.

$petalinux-boot --qemu --prebuilt 3

INFO: sourcing build tools
INFO: No DTB has been specified, use the default one "/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/system.dtb".
INFO: Starting microblaze QEMU
INFO: Starting the above QEMU command in the background
INFO:  qemu-system-microblazeel -M microblaze-fdt   -serial mon:stdio -serial /dev/null -display none -kernel /scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/pmu_rom_qemu_sha3.elf -device loader,file=/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/pmufw.elf      -hw-dtb /scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/zynqmp-qemu-multiarch-pmu.dtb -machine-path /tmp/tmp.geJUZ51a7f -device loader,addr=0xfd1a0074,data=0x1011003,data-len=4 -device loader,addr=0xfd1a007C,data=0x1010f03,data-len=4 
INFO: Set QEMU tftp to /scratch/petalinux-images/xilinx-zcu102-2019.2/images/linux 
qemu-system-microblazeel: Failed to connect socket /tmp/tmp.geJUZ51a7f/qemu-rport-_pmu@0: No such file or directory
qemu-system-microblazeel: info: QEMU waiting for connection on: disconnected:unix:/tmp/tmp.geJUZ51a7f/qemu-rport-_pmu@0,server
INFO: TCP PORT is free 
INFO: Starting aarch64 QEMU
INFO:  qemu-system-aarch64 -M arm-generic-fdt   -serial mon:stdio -serial /dev/null -display none -device loader,file=/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/bl31.elf,cpu-num=0 -device loader,file=/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/Image,addr=0x00080000 -device loader,file=/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/system.dtb,addr=0x15e80000 -device loader,file=/scratch/petalinux-images/xilinx-zcu102-2019.2/build/misc/linux-boot/linux-boot.elf -gdb tcp::9000 -dtb /scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/system.dtb  -net nic -net nic -net nic -net nic,netdev=eth0 -netdev user,id=eth0,tftp=/scratch/petalinux-images/xilinx-zcu102-2019.2/images/linux   -hw-dtb /scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/zynqmp-qemu-multiarch-arm.dtb -machine-path /tmp/tmp.geJUZ51a7f -global xlnx,zynqmp-boot.cpu-num=0 -global xlnx,zynqmp-boot.use-pmufw=true   -m 4G
QEMU 2.11.1 monitor - type 'help' for more information
# 8<----------------------------------------------------------------------------------------------
[    7.456218] m25p80 spi0.0: n25q512a (131072 Kbytes)
[    7.457506] 3 fixed-partitions partitions found on MTD device spi0.0
[    7.457958] Creating 3 MTD partitions on "spi0.0":
[    7.458617] 0x000000000000-0x000001e00000 : "boot"
[    7.464772] 0x000001e00000-0x000001e40000 : "bootenv"
[    7.468541] 0x000001e40000-0x000004240000 : "kernel"
# ...

For this example, we will be modifying system.dtb.

Change directory to where system.dtb is and un-flatten system.dtb by using DTC.

cd pre-built/linux/images
dtc -I dtb -O dts system.dtb -o system.dts

Open system.dts with your preferred text editor and navigate to the line that says:

spi@ff0f0000 {

This SPI peripheral has a flash chip as a child.  This is the flash chip we will be adding another partition to.

In the flash chip inside the SPI peripheral, there should be three partitions:

partition@0x00000000 {
    label = "boot";
    reg = <0x0 0x1e00000>;
};

partition@0x01e00000 {
    label = "bootenv";
    reg = <0x1e00000 0x40000>;
};

partition@0x01e40000 {
    label = "kernel";
    reg = <0x1e40000 0x2400000>;
};

Add a fourth partition so it looks like this:

partition@0x00000000 {
    label = "boot";
    reg = <0x0 0x1e00000>;
};

partition@0x01e00000 {
    label = "bootenv";
    reg = <0x1e00000 0x40000>;
};

partition@0x01e40000 {
    label = "kernel";
    reg = <0x1e40000 0x2400000>;
};
    
partition@0x04240000 {
    label = "new-partition";
    reg = <0x4240000 0xc0000>;
};

Build the DTB.

dtc -I dts -O dtb system.dts -o system.dtb

Re-run QEMU.

$petalinux-boot --qemu --prebuilt 3

INFO: sourcing build tools
INFO: No DTB has been specified, use the default one "/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/system.dtb".
INFO: Starting microblaze QEMU
INFO: Starting the above QEMU command in the background
INFO:  qemu-system-microblazeel -M microblaze-fdt   -serial mon:stdio -serial /dev/null -display none -kernel /scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/pmu_rom_qemu_sha3.elf -device loader,file=/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/pmufw.elf      -hw-dtb /scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/zynqmp-qemu-multiarch-pmu.dtb -machine-path /tmp/tmp.geJUZ51a7f -device loader,addr=0xfd1a0074,data=0x1011003,data-len=4 -device loader,addr=0xfd1a007C,data=0x1010f03,data-len=4 
INFO: Set QEMU tftp to /scratch/petalinux-images/xilinx-zcu102-2019.2/images/linux 
qemu-system-microblazeel: Failed to connect socket /tmp/tmp.geJUZ51a7f/qemu-rport-_pmu@0: No such file or directory
qemu-system-microblazeel: info: QEMU waiting for connection on: disconnected:unix:/tmp/tmp.geJUZ51a7f/qemu-rport-_pmu@0,server
INFO: TCP PORT is free 
INFO: Starting aarch64 QEMU
INFO:  qemu-system-aarch64 -M arm-generic-fdt   -serial mon:stdio -serial /dev/null -display none -device loader,file=/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/bl31.elf,cpu-num=0 -device loader,file=/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/Image,addr=0x00080000 -device loader,file=/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/system.dtb,addr=0x15e80000 -device loader,file=/scratch/petalinux-images/xilinx-zcu102-2019.2/build/misc/linux-boot/linux-boot.elf -gdb tcp::9000 -dtb /scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/system.dtb  -net nic -net nic -net nic -net nic,netdev=eth0 -netdev user,id=eth0,tftp=/scratch/petalinux-images/xilinx-zcu102-2019.2/images/linux   -hw-dtb /scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images/zynqmp-qemu-multiarch-arm.dtb -machine-path /tmp/tmp.geJUZ51a7f -global xlnx,zynqmp-boot.cpu-num=0 -global xlnx,zynqmp-boot.use-pmufw=true   -m 4G
QEMU 2.11.1 monitor - type 'help' for more information
# 8<----------------------------------------------------------------------------------------------
[    7.666932] m25p80 spi0.0: n25q512a (131072 Kbytes)
[    7.668616] 4 fixed-partitions partitions found on MTD device spi0.0
[    7.669350] Creating 4 MTD partitions on "spi0.0":
[    7.670832] 0x000000000000-0x000001e00000 : "boot"
[    7.677346] 0x000001e00000-0x000001e40000 : "bootenv"
[    7.681945] 0x000001e40000-0x000004240000 : "kernel"
[    7.685878] 0x000004240000-0x000004300000 : "new-partition"
# ...

Verify we can read and write to the new partition

root@xilinx-zcu102-2020_2:~# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 01e00000 00002000 "boot"
mtd1: 00040000 00002000 "bootenv"
mtd2: 02400000 00002000 "kernel"
mtd3: 000c0000 00002000 "new-partition"
root@xilinx-zcu102-2020_2:~# dd if=/dev/urandom of=./sample.bin bs=1024 count=64
64+0 records in
64+0 records out
root@xilinx-zcu102-2020_2:~# flashcp -v sample.bin /dev/mtd3
Erasing blocks: 8/8 (100%)
Writing data: 64k/64k (100%)
Verifying data: 64k/64k (100%)

Words of Caution

When modifying device trees, your changes must make sense as if you were representing physical hardware.
Just because the device tree will build properly doesn't mean it will work properly with hardware, virtualized or otherwise.

For example, if we made our partitions look like:

partition@0x00000000 {
    label = "boot";
    reg = <0x0 0x1e00000>;
};

partition@0x01e00000 {
    label = "bootenv";
    reg = <0x1e00000 0x40000>;
};

partition@0x01e40000 {
    label = "kernel";
    reg = <0x1e40000 0x2400000>;
};
    
partition@0x04240000 {
    label = "new-partition";
    reg = <0x4240000 0x80000000>;
};

The flash does not have enough space to have a partition that large.

[    7.514727] m25p80 spi0.0: n25q512a (131072 Kbytes)
[    7.516235] 4 fixed-partitions partitions found on MTD device spi0.0
[    7.516899] Creating 4 MTD partitions on "spi0.0":
[    7.518107] 0x000000000000-0x000001e00000 : "boot"
[    7.525290] 0x000001e00000-0x000001e40000 : "bootenv"
[    7.530586] 0x000001e40000-0x000004240000 : "kernel"
[    7.535273] 0x000004240000-0x000084240000 : "new-partition"
[    7.535825] mtd: partition "new-partition" extends beyond the end of device "spi0.0" -- size truncated to 0x3dc0000

In this case, we were warned, but other device tree modifications may have unwanted behavior that won't have warnings.

© Copyright 2019 - 2022 Xilinx Inc. Privacy Policy