A week ago Adrian Chadd asked me to take a loot at FreeBSD/MIPS emulation. So last week I’ve been busy tidying up stuff in that department and looking up bits of information on various emulators. This morning I finally committed last changeset so now is the time to write up summary.

Emulators

There are two widely used MIPS emulatoes that FreeBSD supports: QEMU and GXemul. Both of them support numerous MIPS devices but we’re interested in only two.

MALTA is more or less standard for MIPS emulation and supported by both emulators. QEMU supports 32 and 64 bit variants with both big and little-endian byte order. So four modes in total. Also for MALTA machine QEMU provides PCNet NIC emulation.

GXemul supports 32 and 64 bit modes of MALTA but only for little-endian byte order. Big-endian byte order is not supported due to incomplete PCI controller implementation. No NIC support for MALTA machine. Also Gxemul provides so-called oldtestmips emulation mode: generic implementation of abstract MIPS machine with very simplified NIC/disk devices. In theory it should be faster then actual hardware emulation but I haven’t got around to benchmarking yet. oldtestmips can be run in 32 and 64 bit mode, but only big-endian byte order is supported.

Disk images

I used raw disk images created by makefs(8) utility. The advantage of raw disk image is that they can be used with both emulators. qemu provides more options in this area but they’re out of scope of this article. I created four images for my tests: disk.img, disk64.img, diskel.img, and disk64el.img. 32-bit big-endian, 64-bit big-endian, 32-bit little-endian, 64-bit big-endian. The process looks somewhat like this script:

#!/bin/sh
set -e

export TARGET=mips
export TARGET_ARCH=mips
export SRCCONF=/dev/null
export SRCROOT=/src/FreeBSD/head
export MAKEOBJDIRPREFIX=/src/FreeBSD/obj/head
export DESTDIR=/src/FreeBSD/tftproot/$TARGET_ARCH
make -C $SRCROOT buildworld

sudo -E mkdir -p /src/FreeBSD/tftproot/$TARGET_ARCH
sudo -E make -C $SRCROOT KERNCONF=$KERNCONF DESTDIR=$DESTDIR installworld
sudo -E make -C $SRCROOT KERNCONF=$KERNCONF DESTDIR=$DESTDIR distribution

# modify /etc/fstab and /etc/rc.conf in $DESTDIR

# Create 512Mb disk image with big-endian UFS
# For TARGET_ARCH set to mipsel or mips64el use "-B le" switch 
sudo -E makefs -M 538968064 -B be /src/FreeBSD/disk.img $DESTDIR

Change TARGET_ARCH and disk image name accordingly for mipsel/mip64/mips64el targets.

QEMU

As of QEMU 1.6.0 there is one known problem with it: NIC emulation does not work in big-endian mode. See this patch for fix and details. Also, amount of memory limited to 128Mb. Use MALTA kernel config for 32-bit mode and MALTA64 for 64-bit mode.

Command lines for various modes/byte orders are:

qemu-system-mips -M malta -kernel /path/to/mips.mips/MALTA/.../kernel -hda /src/FreeBSD/disk.img -nographic
qemu-system-mips64 -M malta -kernel /path/to/mips.mips64/.../MALTA64/kernel -hda /src/FreeBSD/disk64.img -nographic
qemu-system-mipsel -M malta -kernel /path/to/mips.mipsel/.../MALTA/kernel -hda /src/FreeBSD/diskel.img -nographic
qemu-system-mips64el -M malta -kernel /path/to/mips.mips64el/.../MALTA64/kernel -hda /src/FreeBSD/disk64el.img -nographic

GXemul

GXemul requires two patches for proper FreeBSD/MIPS emulation. First one implements rdhwr op for reg 29 required by TLS support. The second one fixes UDP packet checksum calculation and required for proper functioning of emulated DHCP server. Both of these patches have been committed upstream but there were no GXemul release after that so they’re not available as part of emulators/gxemul port.

As with QEMU kernel configs for MALTA are MALTA and MALTA64. And since only little-endian byte order is supported command lines for this emulation mode are:

gxemul -e malta -C 4Kc -d /home/gonzo/FreeBSD/diskel.img /path/to/mips.mipsel/.../MALTA/kernel
gxemul -e malta -d /home/gonzo/FreeBSD/disk64el.img /path/to/mips.mips64el/.../MALTA64/kernel

For oldtestmips emulation mode is other way around, only big-endian. And to make things more complicated it’s GXEMUL kernel config for 64-bit and GXEMUL32 for 32-bit. So here you are:

gxemul -M 256 -E oldtestmips -d /home/gonzo/FreeBSD/disk64.img /path/to/mips.mips64/.../GXEMUL/kernel
gxemul -M 256 -C 4Kc -E oldtestmips -d /home/gonzo/FreeBSD/disk.img /path/to/mips.mips/.../GXEMUL32/kernel

Maximum amount of memory set could be set by -M command-line switch for oldtestmips emulation is 256M. Otherwise physical memory would overlap with memory-mapped regions reserved by devices. There are some preliminary work to work around this limitation but no results yet.

QEMU support in FreeBSD/armv6 regressed since I tried it last time few months back. Changes in FreeBSD kernel and in QEMU itself revealed bugs that were masked by previous behaviour.

In FreeBSD it was r248467: the way memory/IO resources are activated on FDT bus has been changed and it triggered bug in versatile_pci.c

The other issue is more complex. It seems that PCI IRQ routing in QEMU was out of sync with real hardware. So after commit 66a96d7018b9cbabb73c9b87b62a37e4cc46580a IRQ numbers assigned to PCI devices by FreeBSD kernel by default were invalid. Authors of QEMU eventually added compatibility knob to fall back to previous logic. So if you’re using QEMU 1.5 or later add this option to your command line:

-global versatile_pci.broken-irq-mapping=1

It took me two months but I finally got back to hacking on musb driver for FreeBSD (the one that is used in TI AM335x-based devices like Beaglebone or Beaglebone Black). Previous revision turned out not to be ready for production. Here is the new one: beaglebone-usb-20130626.diff. I adopted it to latest HEAD, fixed numerous bugs, added support for SPLIT transactions and USB suspend/resume signalling. There is some cleaning-up to do but unless something major comes up the plan is to commit it over next few days.

Quick hint. If you did not disable “device sc” in kernel config all the message from kernel go to video console. But if something bad happened after kernel started and before framebuffer driver is activated all you’ll see would be “Kernel args: (null)” message on serial console which is not very helpful. So in order to debug this problem and have kernel boot messages on both monitor and serial port without recompiling kernel just add following line to /boot/loader.rc on SD card:

set boot_multicons="YES"

I finally got around to finishing the PWM and LCDC driver for AM335x. Everything was committed today. Here is demo on AM335x EVM (I apologize for quality):

IMG_0049

I’m trying to wrap up some project I started working on quite some time ago and this is first chunk of clean-up.

Patch contains:

  • Kernel config for AM335x EVM
  • dts file for AM335x EVM with TFT panel info
  • LCD controller driver with some functionality missing: only 24/32 bit depth and only TFT mode is supported
  • Really simple PWM driver. LCD backlight is controlled through eCAS submodule of PWMSS0 module.

I tested it only on evaluation module, although I think with proper panel/pinmux configuration it should work with BeagleBone’s LCD caps too.
Parts missing: adjusting clock to proper pixel frequency, proper allocation of framebuffer memory.

Writing new driver for FDT-based device always involves several simple steps:

  • writing generic newbus driver skeleton
  • Checking for compatibility of node in probe routine
  • Allocate memory/IRQ resources in attach routine

I can’t say for other developers but I just copy existing driver, remove all device-specific stuff and rewrite generic stuff. Which is less time-consuming then writing it from scratch but time-consuming it is. Being huge fan of automation of any kind I decided to let computer do all this dumb work and leave creative part (copy-pasting registers definition from spec to code) to myself. the result is this script.

Developer feeds driver description in YAML format to the script and gets driver skeleton that requires minimal amount of editing to get it compiled. Driver description includes author name, prefix for macroses, prefix for newbus method-functions, FDT compatibility string, driver name and number of IRQ/MEMORY resources. A minute saved is a minute earned.

YAML example:

AUTHOR: Oleksandr Tymoshenko <gonzo@freebsd.org>
PREFIX: am335x_pwm
MACRO_PREFIX: PWM
DRIVER: am335x_pwm
FDT_COMPATIBLE: ti,am335x-pwm
IRQ_RESOURCES: 0
MEM_RESOURCES: 4

As it was mentioned in previous post U-Boot can boot FreeBSD kernel directly but this approach doesn’t allow a great deal of control over boot process: there is no way to set tunables’ values or pre-load module. Controlling this stuff requires more knowledge of FreeBSD internal data structures and its boot process then U-Boot holds.

On i386 and other Tier1 architectures this task is handled by the loader(8) program. It’s last stage boot loader (e.g. it’s supposed to pass control to FreeBSD kernel only), highly customizable and scriptable. loader(8) relies on one of the previous stages boot loader to access resources like disks, console, network. For i386 it’s BTX and BIOS.

ubldr is implementation of loader(8) for ARM on top of U-Boot. Original code was developed by Semihalf back in 2008 and has been being improved by them and FreeBSD Community since then. Despite it has been around for almost 5 years amount of documentation is shockingly low. I found only these slides from BSDCan 2008.

One of the nice feature U-Boot provides is API for stand-alone process. If you don’t need full-blown operating system running on your hardware but still want access to SD card/network/console you can request them from U-Boot via syscall-like API that turns boot loader into quasi-OS. Some bits of information on this topic can be found in api/README file in U-Boot sources: here.

ubldr uses U-Boot API to enumerate devices that might be used as a boot source: block (e.g. SD card) or network. For network device it will use BOOTP to try to obtain network/boot data and then mount directory over NFS. For block device it will inspect partition table and try to find suitable partition to use as a root device. Once root is mounted ubldr will perform standard loader(8) magic: get loader-related config from /boot/ directory and act on it.

There is no dedicated top-level build target for ubldr so getting it compiled is a little bit tricky. You need to perform whole buildworld cycle before compiling ubldr. Build script would look something like this:

export SRCROOT=/src/FreeBSD/head
export MAKESYSPATH=$SRCROOT/share/mk
export TARGET=arm
export TARGET_ARCH=armv6
export MAKEOBJDIRPREFIX=/src/FreeBSD/obj

make -C $SRCROOT buildworld

buildenv=`make -C $SRCROOT buildenvvars`

eval $buildenv make -C $SRCROOT/sys/boot clean
eval $buildenv make -C $SRCROOT/sys/boot obj
eval $buildenv make -C $SRCROOT/sys/boot UBLDR_LOADADDR=0x2000000 all

Meaning of UBLDR_LOADADDR is the same as KERNPHYSADDR in previous post.

ubldr is ELF executable and can be used with bootelf command. Typical boot log is something like this:

## Starting application at 0x02000054 ...
Consoles: U-Boot console  
Compatible API signature found @7b662a8
Number of U-Boot devices: 2

FreeBSD/armv6 U-Boot loader, Revision 1.2
(gonzo@bsdbox, Fri Apr 19 18:52:33 PDT 2013)
DRAM:    128MB

Device: disk

Device: net

/boot/kernel/kernel data=0x3ae624+0x2128c syms=[0x4+0x71ca0+0x4+0x44075]
Hit [Enter] to boot immediately, or any other key for command prompt.
Booting [/boot/kernel/kernel]...               
Waiting for Ethernet connection... done.
Using DTB provided by U-Boot.
Kernel entry at 0x100100...
Kernel args: (null)
KDB: debugger backends: ddb
KDB: current backend: ddb
Copyright (c) 1992-2013 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.
...

U-Boot is a boot loader. Its task is to get kernel into memory and pass control to it. I will cover only parts of it related to netboot.

kernel or kernel.bin

But before we start loading something we need to know what to load. In previous post I mentioned that there are kernel, kernel.bin, and ubldr files. Let’s get into details. First of all: ubldr requires its own post. So there will be one more covering just ubldr. Now kernel and kernel.bin.

kernel is ELF executable. It means that it’s a self-contained file with all the information required to layout its bits in memory. e.g.: this data in file should be copied to address A, and N bytes at address B should be set to zero, code intry point is address X. All this auxiliary information is stored alongside to raw code and data. U-boot (or any other bootloader) reads it, lays out data/code accordingly and passes control to entry point. U-Boot’s command for it is bootelf.

Now, bootelf or ELF support in general is not always available in boot loaders. In this case we load ELF on host machine. Technically it’s called “convert to binary format” but essentially what objcopy utility does is it simulates loading of ELF file into memory and dumps memory region from the lowest address that belongs to loaded executable to the highest one into the kernel.bin file. No auxiliary information is saved – only raw code and data. Without this information it’s users responsibility to point which address this memory dump should be loaded at and where to start execution.

That’s theory in a nutshell. Back to practice.

U-Boot

Network initialization routine depends on the board you’re working with. If the ethernet card connected to board over USB (like on Raspberry Pi or Pandaboard) you might need to initialize USB first:

U-Boot> usb start
(Re)start USB...
USB0:   Core Release: 2.80a
scanning bus 0 for devices... 3 USB Device(s) found
       scanning usb for storage devices... 0 Storage Device(s) found
       scanning usb for ethernet devices... 1 Ethernet Device(s) found

At this point you can either get network settings via DHCP or set them manually.
Manual control over network is performed by setting U-Boot environment variables:

U-Boot> setenv ipaddr 192.168.10.21
U-Boot> setenv netmask 255.255.255.0
U-Boot> setenv gatewayip 192.168.10.1

DHCP also provides information about TFTP server and boot file, we can set them manually too:

U-Boot> setenv bootfile kernel
U-Boot> setenv serverip 192.168.10.1

And now load it

U-Boot> tftpboot 0x8000

and boot

U-Boot> bootelf 0x8000

By default tftpboot and bootelf would use loadaddr env variable if it’s set so you can combine last two commands to

U-Boot> setenv loadaddr 0x8000
U-Boot> tftpboot
U-Boot> bootelf

With DHCP everything above is combined into three commands:

U-Boot> setenv loadaddr 0x8000
U-Boot> dhcp
U-Boot> bootelf

If you’re booting ELF loadaddr can be any valid address because bootelf will relocate kernel to proper location. Valid range for addresses depends on the board in use.

With kernel.bin though you have to specify specific value as a loadaddr. Usually it’s KERNPHYSADDR option in kernel config file for ARM and KERNLOADADDR value for MIPS. U-Boot commands sequence would look like:

U-Boot> setenv bootfile kernel.bin
...
U-Boot> setenv loadaddr 0x00100000
U-Boot> dhcp
U-Boot> go 0x00100000

uImage, ubldr

This is basic stuff I’ve been using for several years in my development environment. There are more options though: u-boot application images and bootm command and ubldr. Former is well-documented on Internet and about latter I’ll post some information soon.

I was asked to share details about my root-over-NFS setup so here they are. I decided to split how-to in two posts: server/kernel part and u-boot part.

Usual components in the setup are:

  • DHCP server
  • TFTP server
  • NFS server
  • NAT (optional)

DHCP server

I use net/isc-dhcp42-server as a server. Sample dhcpd.conf:

option root-opts code 130 = string; # NFS / mount options
log-facility local7;

subnet 192.168.10.0 netmask 255.255.255.0 {
        server-name "cinderella.bluezbox.com";
        server-identifier 192.168.10.1;
        option subnet-mask 255.255.255.0;
        option broadcast-address 192.168.10.255;
        option domain-name-servers 8.8.8.8;
        option domain-name "bluezbox.com";
        next-server 192.168.10.1;
        option routers 192.168.10.1;
}

group {
        host pandaboard {
                hardware ethernet 0E:60:33:B1:46:01;
                fixed-address 192.168.10.90;
                filename "kernel.PANDA.bin";
                option root-path "/src/FreeBSD/nfs/armv6";
                option root-opts "nolockd";
        }

        host rpi {
                hardware ethernet b8:27:eb:f6:08:83;
                fixed-address 192.168.10.91;
                filename "ubldr";
                option root-path "/src/FreeBSD/nfs/rpi";
                option root-opts "nolockd";
        }
}

Config is pretty self-explanatory. I use google’s 8.8.8.8 nameserver but you can change it to your very own DNS server. Difference between various filename “…” will be explained later.

dhcpd should be enabled in rc.conf(5)

dhcpd_enable="YES"

TFTP server

TFTP server provides access to all files described in filename “…” options so it’s better to keep them together. By default it’s /tftpboot directory but I have whole drive dedicated to FreeBSD development environment and mounted under /src/FreeBSD mountpoint. So I keep everything there and my TFTP server root is /src/FreeBSD/tftpboot. TFTP server is standard FreeBSD’s one and config line in inetd.conf(8) looks like:

tftp    dgram   udp     wait    root    /usr/libexec/tftpd      tftpd -l -s /src/FreeBSD/tftpboot

inted should be enabled in rc.conf(5)

inetd_enable="YES"

NFS Server

NFS server is not just one daemon but several services combined. So rc.conf(5) part of config looks like this:

rpcbind_enable="YES"
rpc_statd_enable="YES"
rpc_lockd_enable="YES"
nfs_server_enable="YES"
mountd_enable="YES"

Filesystems that are exported via NFS listed in exports(5). Mine contains following:

# Mind mount points obj and nfs are different
/usr/ports -maproot=0 -network 192.168.10.0/16
/src/FreeBSD/head /src/FreeBSD/nfs/rpi /src/FreeBSD/nfs/armv6 /src/FreeBSD/nfs/am335x -maproot=0 -network 192.168.10.0/16

Note that you can join several directories into one line only if they belong to the same mount point.

NAT

If you’re planning on building ports on the device – you’ll need internet access on it. All my devices are restricted to one LAN with laptop acting as a gateway. I use pf(4) for NATing. Config:
/etc/pf.conf(5)

ext_if=em0
rede="{192.168.0.0/16}"

nat on $ext_if from $rede to any -> ($ext_if)

and rc.conf(5)

gateway_enable="YES"

pf_enable="YES"
pf_rules="/etc/pf.conf"
pf_flags=""

Kernel config

FreeBSD kernel should be properly configured in order to be suitable for mounting root over NFS:

options         NFSCL
options         NFSCLIENT # NFS v3
options         NFS_ROOT
options         BOOTP_NFSROOT
options         BOOTP_COMPAT
options         BOOTP
options         BOOTP_NFSV3
options         BOOTP_WIRED_TO=ue0

BOOTP_WIRED_TO value is SoC-specific. If you do not have full control over your DHCP server (e.g. it’s cable modem) and can’t specify root-path/root-opts you still can hardcode root location by removing BOOTP_NFSROOT and adding

options         ROOTDEVNAME=\"nfs:192.168.10:/src/FreeBSD/nfs/rpi\"

Installation

Depending on your boot sequence installation consists of one or two steps.

Normal system installation, e.g.:

sudo -E make TARGET_ARCH=armv6 DESTDIR=/src/FreeBSD/nfs/rpi -DDB_FROM_SRC installworld
sudo -E make TARGET_ARCH=armv6 DESTDIR=/src/FreeBSD/nfs/rpi -DDB_FROM_SRC distribution

And installing kernel copying kernel to tftpboot directory:

sudo -E make TARGET_ARCH=armv6 DESTDIR=/src/FreeBSD/nfs/rpi -DDB_FROM_SRC installkernel
cp /src/FreeBSD/nfs/rpi/boot/kernel/kernel /src/FreeBSD/tftpboot/kernel.RPI 

In some cases you’d want to use kernel.bin instead of kernel (more on it in next post) so second step would look like

sudo -E make TARGET_ARCH=armv6 DESTDIR=/src/FreeBSD/nfs/rpi -DDB_FROM_SRC KERNEL_EXTRA_INSTALL=kernel.bin installkernel
cp /src/FreeBSD/nfs/rpi/boot/kernel/kernel.bin /src/FreeBSD/tftpboot/kernel.RPI.bin

And if you’re going to use ubldr, there is no need to copy installed kernel anywhere.

More details on different types of binaries and boot process in the next post