6LoWPAN kernel on a Raspberry Pi

May 26, 2014

6LoWPAN support in the Linux kernel is sufficiently new right now that you can't just use any old kernel version - you have to ensure you're using a new enough kernel if you want to do anything useful. This presents a problem since the custom kernel normally used on the Raspberry Pi is forked from an older version of the kernel. Fortunately however, support for the RPi in the vanilla kernel has reached the point where we can now run a vanilla Linux kernel on the RPi rather than the custom RPi kernel.

Actually, even the mainline kernel does not currently have sufficiently complete 6LoWPAN and 802.15.4 code to interoperate well with other platforms like Contiki OS. Our task therefore is to get the net-next kernel running on an RPi.

The net-next kernel is a branch maintained by David Miller which holds a variety of network-related changesets as they make their way into the mainline branch. As far as I know, net-next is where all 6LoWPAN and 802.15.4 Linux development is taking place and is the preferred kernel branch to work from right now if you're using either.

Previously, development had been occurring on David Miller's net-next branch. Alexander Aring now maintains linux-wpan and linux-wpan-next branches based on bluetooth and bluetooth-next, as well as a fork of the userspace tools here. Fortunately this change between branches does not seem to have caused any problems with running on the RPi.

Substituting a vanilla Linux kernel for the forked RPi Linux kernel is simple enough, but there are some noteworthy obstacles. First is that booting a vanilla kernel will require us to use U-Boot *, which the RPi does not use by default. Second is that RPi support in the vanilla kernel is not 100% done yet. The complete list of omissions is unknown to me, but I believe HDMI is the main thing that's missing. Fortunately for us though, everything that we need to turn the RPi into a 6LoWPAN border router is working.

Aside from that, this won't be too different from compiling a kernel in any other case.

* Actually, this may not be true anymore. Looking at the configuration options for the (closed source) RPi bootloader and this discussion, it seems that the necessary functionality for loading a vanilla Linux kernel may have been present for some time already. I expect I'll try this some day, but I'd still rather have a proper configurable bootloader and I imagine most would agree. Thus, U-Boot stays


Compiling the Linux kernel on an RPi is an overnight process, so we're going to want to cross-compile on something quicker instead. You can skip the cross-compiling and proceed with the remaining steps on an RPi if you need to, but you can expect it to be quite slow.

To cross-compile, we'll need to fetch an arm-linux-gnueabi build environment if we don't already have it.

On Arch, we can do something like:

pacaur -S arm-linux-gnueabi-gcc

 On a Debian-ish system, something like:

apt-get install gcc-arm-linux-gnueabi 

That will pull in what we need for cross-compiling, including the binaries like arm-linux-gnueabi-gcc and arm-linux-gnueabi-size. Of course, you might need to install some other development packages (base-devel, build-essential, ...) if you haven't previously.

Now we can prefix make commands with CROSS_COMPILE=arm-linux-gnueabi- to compile for RPi. For convenience, you might prefer to make an alias or just export the variables:

alias armmake="CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm INSTALL_MOD_PATH=.mods make"


export CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm INSTALL_MOD_PATH=.mods

ARCH=arm and INSTALL_MOD_PATH=.mods will be used for the Linux kernel and modules.

building the Linux kernel

First we'll grab the kernel source:

git clone https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git 

This can take some time even on a good connection, so you might want to start it before doing anything else.

Once git finishes cloning the kernel source, we can go ahead and configure the kernel. The default bcm2835 config will be our starting point but we'll need to add some things to it for our purposes.

CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm make bcm2835_defconfig

This will result in a kernel configuration which will run on the RPi, but it won't be very useful to us without support for 6LoWPAN, 802.15.4, or an 802.15.4 device driver - AT86RF233 in my case.

To configure the kernel the way we need it, we'll use make menuconfig...

ARCH=arm make menuconfig

...to enable the following options:


Whether we enable them as modules or as built-ins doesn't much matter, but we'll have to be sure to copy the modules over to the RPi later if we choose to build any now.

I enabled CONFIG_IEEE802154_AT86RF230 for the 802.15.4 module I'm using, but you'll need to enable a different driver if you have different hardware.

These next options aren't required but they're pretty standard and may well be desirable. You should enable them if you don't otherwise know what to do. (the first one is required if you want to build anything as a module instead of built-in)


At this point, we can proceed to build the kernel (and modules, if applicable):

CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm make
CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm make modules
CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm INSTALL_MOD_PATH=.mods make modules_install

We now have arch/arm/boot/zImage (the kernel) and arch/arm/boot/dtb/bcm2835-rpi-b.dtb (the device tree), which are the two files we need to boot our new kernel, but we still need a way to tell the kernel about the 802.15.4 device we've hooked up to our RPi.

patching the device tree

The device tree is what recent ARM systems (among others) use to describe the hardware to the kernel at boottime. x86 systems have BIOS; embedded systems have device tree. It's needed for things which are sufficiently low-level that they that aren't capable of runtime enumeration, like SPI devices, I2C devices, and many others.

The 802.15.4 device I'm using (AT86RF233) is an SPI device, so it can't be auto-enumerated and needs to go into the device tree. Therefore, I've added the following to the bottom of arch/arm/boot/dts/bcm2835-rpi-b.dts to tell the kernel how to access it. This essentially tells the kernel which driver to use and what the pinout is. This is the code you should use if you're using this module on an RPi, as I am:

&spi {
    status = "okay";
    at86rf233@0 {
        compatible = "atmel,at86rf233";
        reg = <0>;
        interrupts = <23 1>;
        interrupt-parent = <&gpio>;
        reset-gpio = <&gpio 24 1>;
        sleep-tpio = <&gpio 25 1>;
        spi-max-frequency = <500000>;

After patching a .dts file, we'll need to rebuild the .dtb files:

ARCH=arm CROSS_COMPILE=arch-arm-gnueabi- make dtbs

Now arch/arm/boot/dts/bcm2835-rpi-b.dtb contains a binary of our patched device tree which tells the kernel how to access our AT86RF233.

building U-Boot

With our kernel and device tree ready, we now need to get U-Boot compiled and set up. Stephen Warren maintains a U-Boot branch with some RPi-specific code that hasn't yet been moved into mainline U-Boot. That's the branch we're going to use.

git clone https://github.com/swarren/u-boot.git
cd u-boot
git checkout rpi_dev

 We'll need to configure for rpi_b before we can compile:

make rpi_b_config

I found it necessary to apply a small patch to U-Boot to make it work with some (all?) of my SD cards:

sed -i 's/MMC_MODE_HS_52MHz | //' drivers/mmc/sdhci.c

Presumably this patch isn't required for everyone, but as I haven't yet isolated the actual problem, it seems necessary that I include it here.

Now we can build:

CROSS_COMPILE=arm-linux-gnueabi- make

This gives us u-boot.bin, which is the bootable binary we need. The RPi (start.elf, specifically) will load this binary (rather than directly loading a Linux kernel, as it does normally) which will then load our Linux kernel in accordance with its configuration.

configuring U-Boot

U-Boot needs to be told how to boot our new Linux kernel. Its configuration resides in /boot/uEnv.txt. So we need to create this file on the RPi with the following contents:

bootargs=earlyprintk console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p5 rootwait dwc_otg.lpm_enable=0
bootcmd=load mmc 0:1 ${kernel_addr_r} zImage; load mmc 0:1 ${fdt_addr_r} zImage.dtb; bootz ${kernel_addr_r} - ${fdt_addr_r}

(As always, mind the line breaks. There are only two lines there - bootargs=... and bootcmd=...)

This configuration will cause U-Boot to automatically load /boot/zImage (the Linux kernel) and /boot/zImage.dtb (the device tree) and boot the kernel with the options given in bootargs. You might need to customize your bootargs for your Linux distribution and your own preferences, particularly the root directive in case your root partition is different. You can check your existing /boot/cmdline.txt to see what arguments your RPi is currently booting with. This file isn't used by U-Boot and we won't be touching it, so you can always refer to it later if something isn't right.

installing and rebooting

Now that we've got everything compiled, we can copy it all over to the RPi:

scp net-next/arch/arm/boot/zImage root@alarmpi:/boot/zImage
scp net-next/arch/arm/boot/dts/bcm2835-rpi-b.dtb root@alarmpi:/boot/zImage.dtb
rsync -rlhic net-next/.mods/lib/modules/ root@alarmpi:/lib/modules/
scp u-boot/u-boot.bin root@alarmpi:/boot/kernel.img

(Don't forget to first back up your existing /boot/kernel.img if you care to.)

At this point, we can reboot into the new kernel and try out the new functionality.

linux-zigbee userspace tools

Despite the historical name, the linux-zigbee project is no longer related to ZigBee at all, but rather the open replacement - 6LoWPAN over 802.15.4.

The linux-zigbee userspace tools are what we use to manage 802.15.4 devices. We'll need them in order to do anything useful with our new 6LoWPAN Linux kernel. The source code is at git://git.code.sf.net/p/linux-zigbee/linux-zigbee and it compiles uneventfully following the build instructions. Contrary to building the Linux kernel on an RPi, building the linux-zigbee tools on an RPi will take only a few minutes.

With the linux-zigbee userspace tools installed, we now have the iz binary (among a few others) which we will use to configure the 802.15.4 device. At this point we can finally verify that we do in fact have a valid 802.15.4 device recognized by the kernel:

iz listphy

That should return something like this:

wpan-phy0  IEEE 802.15.4 PHY object
    page: 0  channel: 11
    channels on page 0: 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

bringing up the 6LoWPAN network

iz is to 802.15.4 what iw is to 802.11. We can use it and ip to bring up the 6LoWPAN network like this:

iz add wpan-phy0 wpan0 00:00:00:00:00:00:00:01
iz set wpan0 0x999 0x1 11
ip link add link wpan0 name lowpan0 type lowpan
ip addr add 2001:db8:dead:beef::1/64 dev lowpan0
ip link set wpan0 up
ip link set lowpan0 up

Upon doing this, we now have a lowpan0 interface with IP address 2001:db8:dead:beef::1. To configure a second RPi on the same network, we'd want to substitute 1 for 2 in the iz addiz set, and ip addr add commands. Following that, we could ping our second RPi over the 6LoWPAN network like this:

ping6 2001:db8:dead:beef::2

And of course, with the proper network configuration, we could now just as easily ping the second RPi (or other 6LoWPAN node) from a device connected to the ethernet port of the first RPi. Thus, the RPi has become a 6LoWPAN border router for our ethernet network. This is left as an exercise for the reader until I have time for the next article.

In the mean time, I can vouch that the procedure I've described here does produce a setup which can function as a 6LoWPAN border router capable of  interoperating with devices running Contiki OS. I use it several times a day to turn my lights on and off.


Leave a comment