Compiling Raspberry Pi kernel modules

Published: 2016-02-21
Updated: 2016-02-22

I have a Raspberry Pi 2 "model B" (the only rpi2 model) and I wanted to get this usb wifi driver, compiled and installed for a linux kernel installed by the "rpi-update" tool. Unlike a packaged kernel provided by a debian-based linux distro which you may be using, for example "linux-image-raspi2" which is available in Ubuntu Mate 15.10 armhf (used by the Ubuntu Mate sdcard image for the rpi2) and for which there is a corresponding "linux-headers-raspi2" which provides all the bits needed to compile an out-of-tree module for use with that kernel, the situation for kernels installed by rpi-update is a bit more complicated. It looks like this process works for rpi-update installed kernels for all Raspberry Pi models, so that's nice.

You'll need curl and build-essential installed.

$ sudo apt-get install curl build-essential

The rpi-update tool downloads (and checks and copies) updated firmware files and compiled linux kernels into the /boot partition. The kernels are kernel.img and kernel7.img - the "7" variant is for the Rasberry Pi 2, which has a processor which supports the ARMv7 instruction set, whereas all other Raspberry Pi models have a processor which supports the ARMv6 instruction set. The runtime-loadable kernel modules which are installed along with the kernel are in folders in /lib/modules. I currently have:

$ ls -l /lib/modules
total 16
drwxr-xr-x 3 root root 4096 Feb 16 17:04 4.1.17+
drwxr-xr-x 3 root root 4096 Feb 16 20:54 4.1.17-v7+
drwxr-xr-x 3 root root 4096 Feb 21 13:57 4.1.18+
drwxr-xr-x 3 root root 4096 Feb 21 21:07 4.1.18-v7+

The kernel I'm currently running on this rpi2 is 4.1.18-v7+ (the "-v7" variant is for the "7" kernel). Once you've rebooted to run the most recent kernel installed by rpi-update, there's no point in keeping the old module dirs (4.1.17+ and 4.1.17-v7+ in my case) as the old kernels they correspond to are overwritten and gone. You can get the version of the currently running kernel with "uname -r" (and you can also see it at the beginning of the kernel log with "dmesg | head").

So we know the kernel that's running, and where its module dir is. We now need the kernel source tree and some other outputs of the build that produced this running kernel. The secret is the Hexxeh/rpi-firmware repo on github, and the file /boot/.firmware_revision which contains a commit hash from that repo. (The raspberrypi/firmware repo on github is very similar, but arranged a bit differently and has different commit hashes. The rpi-update I have seems to use the Hexxeh repo.)

$ cat /boot/.firmware_revision

Now we can get the commit of the raspberrypi/linux repo which was used to build the kernel and modules:

$ FIRMWARE_REV=68febe187617906637e0ac71b0c8b8f581f10078
$ curl -L${FIRMWARE_REV}/git_hash
$ KERNEL_REV=2f9dd6a9a79c7d88068e65e98d3a159ecb7a0a9b

We also need one other file, Module.symvers, which also has a "7" variant which corresponds to the "7" version of the kernel, for the rpi2. (If you don't have a rpi2, you should be using the non-"7" variants throughout these steps.)

$ curl -L${FIRMWARE_REV}/Module7.symvers >Module7.symvers

Cloning a linux kernel git repository on an sdcard in a rpi takes quite a while, but all we want is the checkout from a particular revision, which we can get in a tarball from github:

$ curl -L${KERNEL_REV}.tar.gz >rpi-linux.tar.gz
 ... curl progress ...

Now, assuming you ran the previous commands as a normal user in your home directory, switch to root and do the following:

$ sudo -s
[sudo] password ...:
# cd /usr/src
# mkdir rpi-linux
# cd rpi-linux
# tar --strip-components 1 -xf ${HOME}/rpi-linux.tar.gz
# ls
arch     CREDITS        drivers   include  Kbuild   lib          mm      REPORTING-BUGS  security  usr
block    crypto         firmware  init     Kconfig  MAINTAINERS  net     samples         sound     virt
COPYING  Documentation  fs        ipc      kernel   Makefile     README  scripts         tools

Now add .config (from the running kernel) and Module.symvers:

# modprobe configs
# gunzip -c /proc/config.gz >.config
# cp $HOME/Module7.symvers Module.symvers

Finally run make modules_prepare and link into the appropriate modules dir:

# make modules_prepare
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
  SHIPPED scripts/kconfig/
... a minute or two ...
  HOSTCC  scripts/recordmcount
  HOSTCC  scripts/sortextable
# ln -sv /usr/src/rpi-linux /lib/modules/$(uname -r)/build
?‘/lib/modules/4.1.18-v7+/build?’ -> ?‘/usr/src/rpi-linux?’

That should be it, now I can build this out-of-tree module. (Well this particular one also notes in the README that you have to edit the Makefile just to toggle CONFIG_PLATFORM_I386_PC and CONFIG_PLATFORM_ARM_RPI.)

# cd /usr/src/rtl8812AU_8821AU_linux
# make
make ARCH=arm CROSS_COMPILE= -C /lib/modules/4.1.18-v7+/build M=/usr/src/rtl8812AU_8821AU_linux  modules
make[1]: Entering directory '/usr/src/rpi-linux'
  CC [M]  /usr/src/rtl8812AU_8821AU_linux/core/rtw_cmd.o
... a few minutes ...
  MODPOST 1 modules
  CC      /usr/src/rtl8812AU_8821AU_linux/8812au.mod.o
  LD [M]  /usr/src/rtl8812AU_8821AU_linux/8812au.ko
make[1]: Leaving directory '/usr/src/rpi-linux'
# make install
install -p -m 644 8812au.ko  /lib/modules/4.1.18-v7+/kernel/drivers/net/wireless/
/sbin/depmod -a 4.1.18-v7+
# modprobe 8812au
# lsmod | grep 8812au
8812au                958764  0
cfg80211              389253  1 8812au