Labels

Saturday 11 October 2014

Debian on Nexus 7: Building a Kernel

In this post, I'll look at building a custom Linux kernel image for the Nexus 7 which can be used later on to boot a ramdisk containing a minimal user space environment. I've decided to keep this as its own post, to keep it manageable. I'll cover the creation and booting of the ramdisk in my next post.

Cross Compiler Toolchain


As this is going to involve compiling C source code to run natively on the tablet, I'm going to need to get a C cross compiler toolchain. I could use Android's own ARM toolchain for this but, as it turns out, Ubuntu 14.04 also provides a suitable toolchain in the gcc-arm-linux-gnueabi package. This targets the armel Embedded Application Binary Interface (EABI), which Debian has supported from Lenny onwards. It would also be worth looking into targeting the armhf ABI in future, as this allows use of the hardware floating point unit.

To fetch the toolchain, along with a few other useful packages:
$ sudo apt-get install build-essential libncurses5-dev gcc-arm-linux-gnueabi
As the name suggests, build-essential provides some essential packages for software building. libncurses5-dev is required by the kernel's menuconfig configuration tool, to provide an ANSI-based user interface in a terminal.

Building the Kernel Image


And so now onto the main challenge of building the custom Linux kernel image. Unfortunately, this is one area where I can't avoid the Android sources. The branch of the kernel source provided by the AOSP for this tablet contains important drivers, which are not currently available in the mainline kernel. Whilst it would be entirely possible to port these drivers from the Android kernel source to a copy of the mainline source, this is not something I currently have the time or inclination to do. So, my kernel source tree shall be taken from the AOSP.

The starting point then is Google's building kernels guide. This guide lists the various kernel source trees provided by the AOSP, and how they match up to devices (listed by codename). As mentioned in the previous post, the Nexus 7 2013 WiFi-Only device is known by the codenames flo and razor. So, the guide indicates that the kernel/msm tree should be used, with the build configuration flo_defconfig. The guide also describes how the kernel version can be extracted from a kernel image file. Helpfully, my Nexus 7 displays the kernel version in its "About device" settings page. I'm using the KTU84P factory image, and the kernel version in this image is 3.4.0-g03485a6. The g03485a6 is the important bit, as this describes the git commit that the kernel source tree was checked out from when the kernel image was built. The leading g is dropped, giving the commit ID 03485a6. Looking through the web interface for the kernel/msm tree, I can see that this commit corresponds to the head of the android-msm-flo-3.4-kitkat-mr2 branch. Therefore, currently, simply checking out the head of this branch should give me what I need:
$ mkdir kernel
$ cd kernel
$ git clone https://android.googlesource.com/kernel/msm.git
$ cd msm
$ git checkout android-msm-flo-3.4-kitkat-mr2
$ ls
android           Documentation  Kbuild             mm              sound
AndroidKernel.mk  drivers        Kconfig            net             tools
arch              firmware       kernel             README          usr
block             fs             lib                REPORTING-BUGS  virt
COPYING           include        MAINTAINERS        samples
CREDITS           init           make_defconfig.sh  scripts
crypto            ipc            Makefile           security
There appears the nice and familiar Linux kernel source tree. Next, I need to select the appropriate kernel configuration for this tablet. This is achieved with:
$ ARCH=arm SUBARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make flo_defconfig
The ARCH, SUBARCH and CROSS_COMPILE environment variables indicate to the kernel build system the target architecture and compiler toolchain that it should use. Instead of specifying them on each call to make, I could also simply export them in the current shell but, for clarity, I'll continue to use them with each command. flo_defconfig is the name of the build configuration (as mentioned above).

Now I have a configuration which would produce a fully functional Android kernel for my tablet. But I want to customise it some more, to give me a kernel more suited for non-Android use. So, I'll enter the kernel's menuconfig configuration tool and make some tweaks:
$ ARCH=arm SUBARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make menuconfig
In the current configuration, certain important features have been disabled. I enabled the configuration options with the parameter names listed below:
CONFIG_DEVTMPFS=y
CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
Typing / at any time in menuconfig will allow you to search for a configuration option by its parameter name. Once an option has been located, the spacebar toggles between unselected (< >), built-in (<*>) or module (<M>). In the notation used above (which is that of the configuration file itself), unselected is n, built-in is y and module is m. Sometimes, built-in or module will be unavailable for an option, depending on what the rest of the configuration looks like.

There's also a few options I don't need for now. Such as SELinux:
CONFIG_SECURITY_SELINUX=n
CONFIG_EXT4_FS_SECURITY=n
It is also possible to remove quite a few Android-specific drivers that make a Linux kernel capable of supporting the Android user space. This includes things such as Android's Binder IPC driver, and the Anonymous Shared Memory (ASH) subsystem. Unfortunately, disabling the Android-specific drivers did cause a couple of build errors:
  • The function kgsl_get_vma_from_start_addr() in drivers/gpu/msm/kgsl.c was no longer used because CONFIG_ASHMEM was unselected, leading to a fatal warning. Enclosing the function in a #ifdef CONFIG_ASHMEM...#endif block appeared to fix this issue.
  • The file drivers/staging/prima/CORE/WDI/TRP/CTS/src/wlan_qct_wdi_cts.c erroneously switched from including mach/msm_snd.h to including msm_snd.h because CONFIG_ANDROID was unselected. Simply removing this conditional behaviour appeared to fix this issue.
After fixing the source files above, these Android-specific drivers could be disabled by setting:
CONFIG_ANDROID=n
It probably wouldn't have done any harm to leave in these Android-specific drivers, I'm just curious to see if I can boot the kernel without them. However, one Android driver that I do not want to disable is the Android USB gadget driver (CONFIG_USB_G_ANDROID). As I'll describe later on, this will be fundamental in establishing basic communication (as in access to a shell) with the minimal user space environment.

Now I'm finished making my configuration changes, I'll exit menuconfig (saving the changes) and kick off the kernel build with:
$ ARCH=arm SUBARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make -j8
The -j argument governs the number of tasks that the build will perform in parallel. I tend to set this to twice the number of (real) CPU cores on my system (I want the build to finish ASAP!). My machine has an Intel Core i7-4770 processor which is quad-core, so I use -j8. The build still takes a good few minutes, however. The build then finishes with the usual notification:
  Kernel: arch/arm/boot/zImage is ready
And sure enough:
$ file arch/arm/boot/zImage
arch/arm/boot/zImage: Linux kernel ARM boot executable zImage (little-endian)
I have a kernel image! I'm now ready to create a ramdisk image to accompany it.

Next Time...


... I will create the ramdisk image and boot the tablet into a minimal Linux userspace environment.

No comments:

Post a Comment