Emmanuel Vadot's Journal

FreeBSD, Electronics and more

Porting FreeBSD to a new ARM Board Part 2 - DTS


DTS stands for Device Tree Source. It is a way to represent the hardware (SoC, CPU, Memory, busses like IIC SPI and more) in a form of a text file.

Those files are then compiled into a DTB, Device Tree Blob that the Kernel can read to identify the different hardware componant and loads/start drivers accordingly.

On FreeBSD/ARM ubldr is reponsible to load the DTB. The name of the DTB is passed from U-Boot via the variable fdtfile. On the previous post we've seen that this variable was set to olinuxino-lime2.dtb by our patched U-Boot, this means that ubldr will attempt to load the file /boot/dtb/ulinuxino-lime2.dtb before booting the kernel.

So, how to create a DTS for our board ? Well, luckily for us Olimex did one (After all who knows best what's in the board beside the creators ?), we usually call these files vendor DTS. A lot of them have been imported in the FreeBSD source tree in sys/gnu/dts/arm while the custom FreeBSD ones are in sys/boot/fdt/dts/arm.

The vendor DTS file is already in the FreeBSD source tree as sun7i-a20-olinuxino-lime2.dts.

To compile DTS file into DTB one we'll use the dtc command (Device Tree Compiler). Let give it a try :

# -I specify the input format (Here dts)
# -o the output file
# -O the output format
# -b the boot CPU, since the A20 have two core we need to specify that the boot cpu is the first core
dtc -I dts -o sun7i-a20-olinuxino-lime2.dtb -O dtb -b 0 sun7i-a20-olinuxino-lime2.dts
Error: sun7i-a20-olinuxino-lime2.dts:49.1-9 syntax error
FATAL ERROR: Unable to parse input tree

Mhm, what's happening ? Let's look at the line #49

#include "sun7i-a20.dtsi"

These #include directive isn't supported by dtc and is supposed to be handle by cpp, the C Pre-Processor.

The dts file are compiled into dtb in the buildkernel target, and the Makefile target responsible for their conversion (bsd.dtb.mk and sys/conf/dtb.mk) use the script sys/tools/fdt/make_dtb.sh. Let's try with this script :

$SRCROOT/sys/tools/fdt/make_dtb.sh $SRCROOT/sys sun7i-a20-olinuxino-lime2.dts .

Now that we have a DTB we will need a kernel :

make `sysctl -n hw.ncpu` -C $SRCROOT buildkernel KERNCONF=A20
sudo -E make -j `sysctl -n hw.ncpu` -C $SRCROOT installkernel KERNCONF=A20 DESTDIR=$DESTDIR

We used the generic kernel for A20 processor.

Copy the DTB file to the SD card and boot up the board:

Type '?' for a list of commands, 'help' for more detailed help.
loader> load -t dtb /boot/dtb/sun7i-a20-olinuxino-lime2.dtb
/boot/dtb/sun7i-a20-olinuxino-lime2.dtb size=0x677e
loader> boot -s
Using DTB from loaded file '/boot/dtb/sun7i-a20-olinuxino-lime2.dtb'.
Kernel entry at 0x42200100...
Kernel args: -s

Since our U-Boot is compile to load the file /boot/dtb/olinuxino-lime2.dtb we load the DTB directy using the load -t dtb command in loader(8).

And we have nothing, the kernel isn't printing anything. This doesn't means that the kernel didn't boot, this just means that it doesn't know how to print stuff in the console.

So the question is how does the kernel knows where to print strings ?

The response is in sys/dev/uart/uart_cpu_fdt.c. In the uart_cpu_getdev function we can see that it will try multiple device:

  • The device in the kernel environment variable hw.fdt.console

  • The property value for either stdout-path, linux,stdout-path, stdout under the /chosen node

  • The device name serial0

Via loader(8) we can inspect the dtb:

loader> fdt prop /chosen
#address-cells = <0x00000001>
#size-cells = <0x00000001>
loader> fdt prop /aliases
ethernet0 = "/soc@01c00000/ethernet@01c50000"

There is no stdout property and no serial0 device, so for the kernel, there is no serial console to be attached.

Let's add a serial0 alias to see if it resolve the issue. The console on the A20-SOM-EVB is at uart0 so we will use the first serial node :

loader> fdt mkprop /aliases/serial0 "/soc@01c00000/serial@01c28000"
loader> fdt prop /aliases
serial0 = "/soc@01c00000/serial@01c28000"
ethernet0 = "/soc@01c00000/ethernet@01c50000"
loader> boot -s
Using DTB from loaded file '/boot/dtb/sun7i-a20-olinuxino-lime2.dtb'.
Kernel entry at 0x42200100...
Kernel args: -s

Still nothing ...

We know by looking at the A20 User Manual that the uart devices are 16550 compatible, the driver in the FreeBSD source tree for theses devices is in sys/dev/uart/uart_dev_ns8250. In this file you'll see an ofw_compat_data structure, and it's saying that it's compatible with a "ns16550" device. Let's look what compatible device is stated in the /soc@01c00000/serial@01c28000 node :

loader> fdt prop /soc@01c00000/serial@01c28000
compatible = "snps,dw-apb-uart"

I have not look yet what does "snps,dw-apb-uart" means, let just change it for now and try to boot the kernel :

loader> fdt prop /soc@01c00000/serial@01c28000/compatible "ns16550"

Still nothing ... While I'm sure that it is possible to bring the board up with using just loader(8), I have not managed to do so, so I decided to make a dts myself that overrides the node/property I need:

cat olinuxino-lime2.dts
#include "sun7i-a20-olinuxino-lime2.dts"

/ {
        aliases {
                serial0 = &uart0;

        chosen {
                bootargs = "-v";
                stdin = "serial0";
                stdout = "serial0";

        soc@01c00000 {
                uart0: serial@01c28000 {
                        compatible = "ns16550";

$SRCROOT/sys/tools/fdt/make_dtb.sh $SRCROOT/sys olinuxino-lime2.dts .

And after copying the dtb into the SD card :

Booting [/boot/kernel/kernel]...               
/boot/dtb/olinuxino-lime2.dtb size=0x67f4
Loaded DTB from file 'olinuxino-lime2.dtb'.
Kernel entry at 0x42200100...
Kernel args: (null)
KDB: debugger backends: ddb
KDB: current backend: ddb
Copyright (c) 1992-2015 The FreeBSD Project.
Copyright (c) 1979, 1980, 1983, 1986, 1988, 1989, 1991, 1992, 1993, 1994
        The Regents of the University of California. All rights reserved.
FreeBSD is a registered trademark of The FreeBSD Foundation.
FreeBSD 11.0-CURRENT #4 155e1e1(olinuxino)-dirty: Fri Nov 27 17:46:51 CET 2015
    elbarto@harlock.staff.bocal.org:/usr/home/elbarto/freebsd-build/arm.armv6/usr/home/elbarto/freebsd.git/sys/A20 arm
FreeBSD clang version 3.7.0 (tags/RELEASE_370/final 246257) 20150906
WARNING: WITNESS option enabled, expect reduced performance.
CPU: Cortex A7 rev 4 (Cortex-A core)
 Supported features: ARM_ISA THUMB2 JAZELLE THUMBEE ARMv4 Security_Ext
 WB disabled EABT branch prediction enabled
LoUU:2 LoC:3 LoUIS:2 
Cache level 1: 
 32KB/64B 4-way data cache WB Read-Alloc Write-Alloc
 32KB/32B 2-way instruction cache Read-Alloc
Cache level 2: 
 256KB/64B 8-way unified cache WB Read-Alloc Write-Alloc
real memory  = 1073741824 (1024 MB)
avail memory = 1037066240 (989 MB)
FreeBSD/SMP: Multiprocessor System Detected: 2 CPUs
random: entropy device external interface
ofwbus0: <Open Firmware Device Tree>
simplebus0: <Flattened device tree simple bus> on ofwbus0
gic0: <ARM Generic Interrupt Controller> mem 0x1c81000-0x1c81fff,0x1c82000-0x1c82fff,0x1c84000-0x1c85fff,0x1c86000-0x1c87fff irq 1 on simplebus0
gic0: pn 0x10, arch 0x2, rev 0x1, implementer 0x43b irqs 160
ahci0: <Allwinner Integrated AHCI controller> mem 0x1c18000-0x1c18fff irq 0 on simplebus0
ahci0: GPIO device not yet present (SATA won't work).
device_attach: ahci0 attach returned 6
uart0: <16750 or compatible> mem 0x1c28000-0x1c283ff irq 0 on simplebus0
uart0: console (8861,n,8,1)
iichb0: <Allwinner I2C Controller> mem 0x1c2ac00-0x1c2afff irq 0 on simplebus0
iicbus0: <OFW I2C bus> on iichb0
iic0: <I2C generic I/O> on iicbus0
iicbus0: <unknown card> at addr 0x68
iichb1: <Allwinner I2C Controller> mem 0x1c2b000-0x1c2b3ff irq 0 on simplebus0
iicbus1: <OFW I2C bus> on iichb1
iic1: <I2C generic I/O> on iicbus1
dwc0: <A20 Gigabit Ethernet Controller> mem 0x1c50000-0x1c5ffff irq 0 on simplebus0
dwc0: could not activate gmac module
device_attach: dwc0 attach returned 6
cryptosoft0: <software crypto>
panic: No usable event timer found!
cpuid = 0
KDB: enter: panic
[ thread pid 0 tid 100000 ]
Stopped at      $d.7:   ldrb    r15, [r15, r15, ror r15]!

Yeah ! We have a console.

Now why does the kernel can't find the timers ? Well we used the vendor DTS file, and when the A20 support was commited in the FreeBSD source tree, the vendor dts files could not be supported by the current code. The DTS were then made by hand and the compatible property is not the same than the vendors ones, here the driver for the timers in the A20 hasn't attached because it couldn't find a node with the right compatible property.

That's what we'll see in Part 3, converting the kernel drivers to vendors DTS.