The Device Tree for embedded Linux and Xilinx FPGAs

This post was written by eli on August 1, 2011
Posted Under: FPGA,Linux kernel,Microblaze

Spoiler

It’s very likely that you don’t need to read this. If all you want is to get a Linux kernel to detect a Microblaze processor on an Xilinx FPGA, the relevant information is in another post of mine. This post goes into the details which are necessary to understand, if you want to write a kernel driver for a device tree mapped peripheral.

Why a device tree is necessary

The main issue with running Linux on an FPGA is that the Linux kernel needs to know what peripherals it has and where it can find them. On PC computers this problem was solved many years ago with the PCI bus: The BIOS detects the peripherals, allocates their addresses and interrupts and tells the operating system what it has and where it can be found. In the embedded world, this information was hardcoded into pieces of the kernel sources, which were written specifically for every board. With many boards out there, the kernel source grew way too fast. This far-from-optimal solution is not feasible with a soft processor, whose peripherals are configured per case. Hacking the kernel sources to match the FPGA is a recipe for bugs, crashes and being stuck with a certain kernel forever.

The elegant solution for this is the Flattened Device Tree. The idea is to create some binary data structure, which is either linked into the kernel image or given to it during boot. This binary blob contains the information about the processor itself and its peripherals, including the addresses, interrupts and several application-specific parameters. So the drivers for these peripherals are written very similar to PCI drivers: They declare what peripherals they support, and obtain their resources from a standard kernel API.

The code for Flattened Device Tree and Open Firmware resides in drivers/of in the kernel tree. The relevant include file is include/linux/of.h.

Generation

Note that at least for Xilinx FPGAs, there is no need to generate the device tree manually. Rather, get a copy of the device tree generator with

bash> git clone git://git.xilinx.com/device-tree.git

which basically consists of a TCL script run by libgen and a configuration file. The device tree generator’s page explains how to make SDK recognize the script, but there’s no reason to play around with SDK for that.

Instead, go

libgen -hw /path/to/system.xml -lp /path/to/device-tree -pe microblaze_0 -log libgen.log system.mss

Which generates a system.dts in microblaze_0/libsrc/device-tree_v0_00_x

The system.mss file is generated as a byproduct when compiling just any a project within SDK, and is found under the directory with the _bsp_n suffix. I still need to find out how to create the file from the command line.

It needs to be modified, so that the BEGIN OS to END part reads

BEGIN OS
 PARAMETER OS_NAME = device-tree
 PARAMETER OS_VER = 0.00.x
 PARAMETER PROC_INSTANCE = microblaze_0
END

and not “standalone” for OS.

To get the system.xml file (which was necessary to create the system.mss), go Project > Export Hardware to SDK in the EDK platform studio. Or

make -f system.make exporttosdk

from the project’s home directory.

The correct setup of the device tree entry can be found in the Documentation/devicetree/bindings directory of the kernel sources. The xilinx.txt file describes the bindings for Xilinx peripherals, and explains how information in the system.mhs file is translated into a xilinx.dts.

As part of a full kernel compilation, the .dts is compiled into a .dtb file (the Device Tree binary blob), which can be found in the same directory as the generated kernel image. The Device Tree Compiler (dtc) comes with the kernel sources, and can be found in scripts/dtc.

A sample entry

The following example is given there for a Uartlite (which we’ll follow on below):

opb_uartlite_0: serial@ec100000 {
 device_type = "serial";
 compatible = "xlnx,opb-uartlite-1.00.b";
 reg = <ec100000 10000>;
 interrupt-parent = <&opb_intc_0>;
 interrupts = <1 0>; // got this from the opb_intc parameters
 current-speed = <d#115200>;     // standard serial device prop
 clock-frequency = <d#50000000>; // standard serial device prop
 xlnx,data-bits = <8>;
 xlnx,odd-parity = <0>;
 xlnx,use-parity = <0>;
 };

It’s recommended to have a look at arch/microblaze/platform/generic/system.dts in the kernel sources for a fullblown file. Or one you’ve generated yourself, for that matter.

Declarations in a kernel module driver

Device tree mapped instances are treated by the kernel very much like PCI devices, only the source of information is the DTB (Device Tree Binary) rather than from the BIOS.

The parallel to PCI’s Vendor/Product IDs is an entry looking like this (taken from uartlite.c):

static struct of_device_id ulite_of_match[] __devinitdata = {
 { .compatible = "xlnx,opb-uartlite-1.00.b", },
 { .compatible = "xlnx,xps-uartlite-1.00.a", },
 {}
};

MODULE_DEVICE_TABLE(of, ulite_of_match)

Which is then bound to a driver with

static struct of_platform_driver ulite_of_driver = {
 .probe = ulite_of_probe,
 .remove = __devexit_p(ulite_of_remove),
 .driver = {
 .name = "uartlite",
 .owner = THIS_MODULE,
 .of_match_table = ulite_of_match,
 },
}

and then, finally, exposed to the kernel with

static inline int __init ulite_of_register(void)
{
 pr_debug("uartlite: calling of_register_platform_driver()\n");
 return of_register_platform_driver(&ulite_of_driver);
}

somewhere at the end of the driver’s code. This format is very similar to the declaration of PCI devices, so if this is unclear, I’d suggest learning how to do it the PCI way, which is by far more documented.

And by the way, when the kernel is configured to support it, the device tree can be viewed in human-readable format in /proc/device-tree.

The of_device_id structure

The structure is defined in include/linux/mod_devicetable.h as

struct of_device_id
{
 char    name[32];
 char    type[32];
 char    compatible[128];
#ifdef __KERNEL__
 void    *data;
#else
 kernel_ulong_t data;
#endif
};

Surprisingly enough, the lengths of the entries are fixed and limited.The three strings, name, type and compatible are compared as strings (with strcmp(), see of/base.c) with the device tree’s node’s data. Everything declared (that is, non-NULL) in the structure must be equal with the node’s info for a match. In other words, NULLs are wildcards.

In the declaration example above, only the “compatible” part was declared, so any device matching the string exactly triggers off a probe on the driver.

 

Reader Comments

Hello Eli, I am now working on the Xilinx SOPC. I am trying to run Linux on PowerPC440 of ML507. But when I follow the step on the Xilinx websit, the device tree confuse me. I can not find the device tree menu on the BSP option. I do not know how to go on with my project. Thank you.

#1 
Written By Kiwis on August 28th, 2014 @ 04:37

Add a Comment

required, use real name
required, will not be published
optional, your blog address