Labels

Monday 29 September 2014

Debian on Nexus 7: Bootloader and Partitons

This is the start of a series of posts about my foolhardy attempt to get Debian Linux running on my Nexus 7 tablet. Or, more specifically: the Nexus 7 2013 WiFi-Only 16 GB tablet, or "flo" (also known as "razor") for short. This isn't the first time that a desktop flavour of Linux has been made to run on an Android tablet, but its interesting enough.

The first thing to consider is how the tablet stores and boots Android currently. I'll need to work with the standard bootloader that comes with the tablet, to reduce the chances of bricking the device completely. Whatever OS I choose to load on the tablet will need to be presented to the bootloader in the way it expects.

Factory Images


I'll start with Google's own Android factory images. Specifically, the KTU84P image. Unpacking the tarball, and further unzipping the zip file it contains, yields the following image files:
boot.img
recovery.img
system.img
userdata.img
cache.img
These correspond to partitions on the device, which have been labelled in a way that the bootloader understands. With the fastboot tool, we're able to pass these image files to the bootloader on the device over USB, which can then unpack them and flash them to the identified partitions. Cool! Except that, this only works if the image files are in the format that bootloader expects.

The boot.img file is of particular interest, since this is the image file that is actually booted by the bootloader. We don't even have to flash the image file to boot it, fastboot allows us to download the image file to the device for a one-time boot:
$ fastboot boot boot.img
The system.img file contains the contents of the Android system partition (mounted under /system on a running Android device), and the userdata.img file contains the contents of the user data partition (mounted under /data). Whilst it should be possible to split a new OS into this same system/data arrangement, I'm not going to explore that for now. So, I'm interested in which of these partitions is the larger. A simple task with adb shell:
shell@flo:/ $ df
Filesystem               Size     Used     Free   Blksize
/dev                   902.9M   128.0K   902.8M   4096
/sys/fs/cgroup         902.9M    12.0K   902.9M   4096
/mnt/asec              902.9M     0.0K   902.9M   4096
/mnt/obb               902.9M     0.0K   902.9M   4096
/system                827.8M   719.3M   108.5M   4096
/cache                 551.7M     9.9M   541.8M   4096
/data                   11.9G     1.1G    10.9G   4096
/persist                14.5M     4.2M    10.2M   4096
/mnt/shell/emulated     11.9G     1.1G    10.9G   4096
Yes, the 11.9GB user data partition seems like the best bet. So as well as boot.img, I'm also interested in userdata.img.

The Boot Image


So, how is the boot image file structured? This is documented in platform/system/core/mkbootimg/bootimg.h in the Android AOSP sources. The image contains a boot header, a Linux kernel image, a ramdisk image and an optional second stage. Amongst other things, the boot header contains the command line passed to the kernel on boot, which means that this can be configured from the boot image file.

OK, so how do we build the boot image file? Unsurprisingly, it is the mkbootimg tool in the AOSP which does this job. But it takes quite a few parameters, for which appropriate values are not entirely obvious. It would be good to determine the parameters that were passed to mkbootimg to construct the boot.img file provided in the KTU84P factory image. Happily, unmkbootimg is a tool (written by Pete Batard) which is able to do exactly this. Both unmkbootimg and a fixed up version of mkbootimg can be retrieved from Pete's bootimg-tools GitHub repository and built as follows:
$ git clone https://github.com/pbatard/bootimg-tools.git
$ cd bootimg
$ make
$ cp mkbootimg/{mkbootimg,unmkbootimg} ~/bin
Then, turning unmkbootimg on the boot.img from the KTU84P factory image:
$ unmkbootimg -i boot.img
kernel written to 'kernel' (6722240 bytes)
ramdisk written to 'ramdisk.cpio.gz' (492556 bytes)

To rebuild this boot image, you can use the command:
  mkbootimg --base 0 --pagesize 2048 --kernel_offset 0x80208000 --ramdisk_offset 0x82200000 --second_offset 0x81100000
--tags_offset 0x80200100 --cmdline 'console=ttyHSL0,115200,n8 androidboot.hardware=flo user_debug=31 msm_rtb.filter=0x3F ehci-hcd.park=3'
--kernel kernel --ramdisk ramdisk.cpio.gz -o boot.img
The ramdisk is clearly a gzipped CPIO archive, which suggests that it is used to populate an initramfs used by the kernel on boot. That's nice and standard! Also, the kernel image is nicely recognisable:
$ file kernel
kernel: Linux kernel ARM boot executable zImage (little-endian)
This should be enough information to allow me to deploy my own kernel image, and a non-Android initramfs ramdisk, to the tablet. But I'll still need to deploy a root filesystem as well.

The User Data Image


I'll use the user data partition to hold my root filesystem, at least initially. So now I need to figure out what format this image file uses. Looking around the AOSP tree some more, it appears to be platform/system/extras/ext4_utils/mkuserimg.sh that is responsible for building the user image file. This script is a wrapper around the make_ext4fs tool, whose source is provided in the same location. A bit more information is provided on this tutorial page, which suggests that the system, user data and cache image files uses a sparse image format, to ensure that fastboot can hold the image in device memory whilst flashing it to the appropriate partition. These image files are ext4 filesystem images, compressed into a sparse image format.

As with the boot image file, it is probably useful to try unpacking the user data image file from the KTU84P factory image. Under platform/system/core/libsparse in the AOSP is the source code to the simg2img tool, which appears to be able to translate a sparse image to a regular (non-sparse) image. This tool can be built as follows:
$ sudo apt-get install zlib1g-dev
$ git clone https://android.googlesource.com/platform/system/core
$ cd core/libsparse
$ gcc -o simg2img -Iinclude simg2img.c sparse_crc32.c backed_block.c output_file.c sparse.c sparse_err.c sparse_read.c -lz
$ cp simg2img ~/bin
Turning simg2img on the userdata.img from the KTU84P factory image:
$ simg2img ./userdata.img userdata-unpacked.img
$ file userdata-unpacked.img 
userdata-unpacked.img: Linux rev 1.0 ext4 filesystem data, UUID=57f8f4bc-abf4-655f-bf67-946fc0f9f25b (extents) (large files)
$ du -h userdata-unpacked.img
316M userdata-unpacked.img
gives us an ext4 image that we can mount on loopback:
$ sudo mount -o loop userdata-unpacked.img /mnt
$ df -m
Filesystem          1M-blocks   Used Available Use% Mounted on
/dev/loop0              11464     29     11419   1% /mnt
So, the user data image file contains an 11,464 MB ext4 filesystem. This shall define the upper bound of the user data image file that I try to produce later on. The filesystem is, however, completely empty:
$ ls -l /mnt
total 12
drwxr-xr-x.  3 root root 4096 Jan  1  1970 .
drwxr-xr-x  25 root root 4096 Sep  2 20:11 ..
drwx------.  2 root root 4096 Jan  1  1970 lost+found

Next Time...


... will be my attempt at trying to boot a Linux kernel on the tablet, into a non-Android initramfs ramdisk.