Labels

Sunday 23 November 2014

Debian on Nexus 7: Booting Debian

In this post, I'm going to attempt to boot the Nexus 7 into a Debian environment. It won't be able to do very much, but it should at least boot up OK.

The User Data Partition


As discussed in an earlier post, I've decided to use the tablet's user data partition (of 11GB or so in size) to hold the root filesystem. From Android (with adb shell), I can work out exactly what partition this is on the tablet's internal storage:
shell@flo:/data $ mount
...
/dev/block/platform/msm_sdcc.1/by-name/system /system ext4 ...
/dev/block/platform/msm_sdcc.1/by-name/cache /cache ext4 ...
/dev/block/platform/msm_sdcc.1/by-name/userdata /data ext4 ...
/dev/block/platform/msm_sdcc.1/by-name/persist /persist ext4 ...
...
shell@flo:/data $ ls -l /dev/block/platform/msm_sdcc.1/by-name/userdata
lrwxrwxrwx root     root              2014-09-01 21:45 userdata -> /dev/block/mmcblk0p30
So the user data partition is mmcblk0p30, under /dev/block in Android (this corresponds to /dev/mmcblk0p30 in my minimal ramdisk).

I already know that I need to use the make_ext4fs tool to build a sparse image file to package the root filesystem for flashing onto the Nexus 7 using fastboot. I fetched the AOSP sources for this tool as follows (using the commit points where the build worked for me):
$ cd ~/Android/Nexus7-Debian
$ mkdir -p platform/{system,external}
$ cd platform/system
$ git clone https://android.googlesource.com/platform/system/core
$ git clone https://android.googlesource.com/platform/system/extras
$ cd core
$ git reset --hard 39ab11d
$ cd ../extras
$ git reset --hard 5fb7c3e
$ cd ../../external
$ git clone https://android.googlesource.com/platform/external/libselinux
$ cd libselinux
$ git reset --hard f76c30b
and (after examining how the Android.mk files did things) built the tool with the following:
$ sudo apt-get install zlib1g-dev
$ cd ~/Android/Nexus7-Debian/platform
$ gcc -o make_ext4fs -Iexternal/libselinux/include -Isystem/core/include -Isystem/core/libsparse/include -DHOST \
    system/extras/ext4_utils/{make_ext4fs.c,ext4fixup.c,ext4_utils.c,allocate.c,contents.c,extent.c,indirect.c,uuid.c,sha1.c,wipe.c,crc16.c,ext4_sb.c,make_ext4fs_main.c} \
    external/libselinux/src/{callbacks.c,check_context.c,freecon.c,init.c,label.c,label_file.c,label_android_property.c} \
    system/core/libsparse/{backed_block.c,output_file.c,sparse.c,sparse_crc32.c,sparse_err.c,sparse_read.c} -lz
$ ls
external  make_ext4fs  system
I then ensured that the make_ext4fs binary was copied to a suitable location on my path. Next, I need to try creating a test sparse image file to see if I can really flash the user data partition.

I'll quickly create a dummy root filesystem (with nothing particularly useful inside it):
$ mkdir /tmp/fstest
$ touch /tmp/fstest/IT_WORKS
and package it into a sparse image file:
$ cd ~/Android/Nexus7-Debian/out
$ make_ext4fs -s -l 11770M fstest.img /tmp/fstest
$ ls
boot.img  fstest.img
The -s argument specifies that a sparse image should be produced, and the -l argument specifies the image size. The meaning of these arguments were deduced from platform/system/extras/ext4_utils/mkuserimg.sh in the AOSP. The value of 11,770MB was found by experimentation, comparing the result with the user data image file from the KTU84P Android factory image.

So, now I have my test user data image (alongside my minimal ramdisk boot image), I can try this out on the tablet. I can use fastboot to flash the user data image in the usual way:
$ fastboot flash userdata fstest.img
erasing 'userdata'...
OKAY [  1.314s]
sending 'userdata' (136890 KB)...
OKAY [  4.292s]
writing 'userdata'...
OKAY [  5.797s]
finished. total time: 11.404s
And I'll boot and login to the minimal ramdisk as usual, and try mounting the user data partition:
$ fastboot boot boot.img
downloading 'boot.img'...
OKAY [  0.256s]
booting...
OKAY [  0.026s]
finished. total time: 0.282s
$ telnet 192.168.1.1
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.

nexus-7-initrd login: root
~ # mount /dev/mmcblk0p30 /mnt
~ # ls /mnt
IT_WORKS    lost+found
 ~ #
Looks like it worked! Checking the partition sizes:
~ # df -h
Filesystem                Size      Used Available Use% Mounted on
none                    894.7M         0    894.7M   0% /dev
none                    903.9M         0    903.9M   0% /tmp
none                    903.9M      4.0K    903.9M   0% /var
/dev/mmcblk0p30          11.3G    129.4M     11.2G   1% /mnt
It appears I only have 11.3GB for this image (as opposed to 11.9GB shown by Android for its user data partition), but this should be good enough. I'm now ready to create a Debian root filesystem.

Creating a Debian Filesystem


I'll go for a Debian 7 ("wheezy") filesystem, which I will build using multistrap (using the information from this page). This requires the following packages to be installed:
$ sudo apt-get install multistrap binfmt-support qemu qemu-user-static
I did find that I was hit by this bug, which I had to work around by removing the reference to $forceeyes on line 989 of /usr/sbin/multistrap before I could continue. I first created a multistrap configuration:
$ cd ~/Android/Nexus7-Debian
$ mkdir debian
$ cd debian
$ gvim multistrap.conf
which I populated with the following:
[General]
noauth=true
unpack=true
debootstrap=Wheezy
aptsources=Wheezy
arch=armel
directory=./rootfs

[Wheezy]
packages=udev netbase net-tools ifupdown iputils-ping isc-dhcp-server isc-dhcp-client inetutils-inetd telnetd apt module-init-tools procps unzip sudo wget dialog libncurses5-dev vim
source=http://ftp.uk.debian.org/debian/
keyring=debian-archive-keyring
components=main contrib non-free
suite=wheezy
This initial set of packages should be enough to replicate the functionality that I have with the minimal ramdisk (plus a couple of other things, like vim). I'll most likely need to add more packages to this initial set in time, to get enough functionality to allow the tablet to fetch subsequent packages itself over some network. Any dependency requirements that these packages have are automatically fulfilled. With my configuration ready, I kicked off multistrap with:
$ sudo multistrap -f ./multistrap.conf
...
Multistrap system installed successfully in /home/simon/Android/Nexus7-Debian/debian/rootfs/.
Now I have to configure the system appropriately before I can boot it with the tablet. To do this, I can enter a chroot shell and use qemu to perform transparent user emulation for any ARM binaries that I need to invoke:
$ sudo cp /usr/bin/qemu-arm-static rootfs/usr/bin
$ sudo mount -t proc proc rootfs/proc
$ sudo chroot ./rootfs /bin/bash
I have no name!@my-pc:/#
I can now perform the necessary post-install package setup routines:
I have no name!@my-pc:/# /var/lib/dpkg/info/dash.preinst install
I have no name!@my-pc:/# dpkg --configure -a
Some packages may complain about not being able to start things (because of the chroot environment), but this should be OK. Now to lay down the configuration. I first edited /etc/inittab and commented out the following lines (disabling getty invocations for virtual consoles which are not being used):
1:2345:respawn:/sbin/getty 38400 tty1
2:23:respawn:/sbin/getty 38400 tty2
3:23:respawn:/sbin/getty 38400 tty3
4:23:respawn:/sbin/getty 38400 tty4
5:23:respawn:/sbin/getty 38400 tty5
6:23:respawn:/sbin/getty 38400 tty6
Next, I created /etc/fstab with the following contents:
# file system     mount point     type    options                 dump    pass
proc              /proc           proc    nodev,noexec,nosuid     0       0
sysfs             /sys            sysfs   nodev,noexec,nosuid     0       0
/dev/mmcblk0p30   /               ext4    errors=remount-ro       0       1
and created /etc/hostname with an appropriate hostname:
nexus-7
I then set a static IP address for the USB RNDIS network interface by adding the following to /etc/network/interfaces:
auto usb0
iface usb0 inet static
address 192.168.1.1
netmask 255.255.255.0
and configured the DHCP server to use this interface by setting the INTERFACES variable in /etc/default/isc-dhcp-server to:
INTERFACES="usb0"
I also set an appropriate DHCP configuration by adding the following to /etc/dhcp/dhcpd.conf (as well as commenting out the enabled option lines):
subnet 192.168.1.0 netmask 255.255.255.0 {
  range 192.168.1.20 192.168.1.254;
}
To use the telnet daemon with inetd, I created /etc/inetd.conf with the following contents:
telnet stream tcp nowait root /usr/sbin/in.telnetd telnetd
All that remains is to set an appropriate root password, and add a normal user:
I have no name!@my-pc:/# passwd root
I have no name!@my-pc:/# adduser simon
I can also reduce the size of the initial image by 100MB or so, by clearing apt's cache:
I have no name!@my-pc:/# apt-get clean
The system should now be ready, so I can leave the chroot environment and tidy up. On my system, syslogd appeared to have been started during the post-install process, so I had to stop that too (and clean up its /dev/xconsole FIFO pipe).
I have no name!@my-pc:/# pkill syslogd
I have no name!@my-pc:/# rm -f /dev/xconsole
I have no name!@my-pc:/# exit
exit
$ sudo umount rootfs/proc
I can now package this up into a sparse image, and deploy this to the tablet:
$ cd ~/Android/Nexus7-Debian/out
$ sudo bash -c "PATH=$PATH make_ext4fs -s -l 11770M debian-rootfs.img ../debian/rootfs"
$ fastboot flash userdata debian-rootfs.img

Reconfiguring the Ramdisk


I also need to make a minor change to my minimal ramdisk image, to allow it to boot Debian. This is accomplished by having it run a shell script as init, rather than using BusyBox's own init implementation. This shell script performs the necessary steps to mount the user data partition and transfer the boot process over to the init executable contained there. The script can also perform some tablet-specific tasks, such as enabling USB RNDIS.
$ cd ~/Android/Nexus7-Debian/initrd/rootfs
$ sudo rm init
$ sudo gvim init
I created the following shell script to act as init (after seeing the example on this page):
#!/bin/sh

# Mount core filesystems
mount -t proc none /proc
mount -t sysfs none /sys  

# Blink the white LED to show that we're booting
echo 255 > /sys/class/leds/white/brightness
echo 1 > /sys/class/leds/white/device/blink

# Tickle the Android composite gadget driver into RNDIS mode
echo 0 > /sys/class/android_usb/android0/enable
echo 18d1 > /sys/class/android_usb/android0/idVendor
echo 2d04 > /sys/class/android_usb/android0/idProduct
echo 239 > /sys/class/android_usb/android0/bDeviceClass
echo 2 > /sys/class/android_usb/android0/bDeviceSubClass
echo 1 > /sys/class/android_usb/android0/bDeviceProtocol

echo LGE > /sys/class/android_usb/android0/f_rndis/manufacturer
echo 18D1 > /sys/class/android_usb/android0/f_rndis/vendorID
echo 1 > /sys/class/android_usb/android0/f_rndis/wceis

echo rndis > /sys/class/android_usb/android0/functions
echo 1 > /sys/class/android_usb/android0/enable

# Mount the real rootfs
mount /dev/mmcblk0p30 /mnt

# Stop blinking the white LED to show that we're ready
echo 255 > /sys/class/leds/white/brightness
echo 0 > /sys/class/leds/white/device/blink

# Unmount core filesystems
umount /proc
umount /sys

# Switch to the real rootfs
exec switch_root /mnt /sbin/init
To keep this shell script minimal, I've avoided using devtmpfs. So the dev tree in the minimal ramdisk needs to contain the device nodes necessary to support this process. The only node that is missing is the block device node for the tablet's user data partition. I therefore created this additional node, alongside making the shell script executable:
$ sudo chmod 755 init
$ sudo mknod -m 600 dev/mmcblk0p30 b 179 30
I then rebuilt the ramdisk in the usual way:
$ sudo bash -c "find . | cpio -o -H newc | gzip > ../initrd-rootfs.cpio.gz"
$ cd ../../out
$ mkbootimg --base 0 --pagesize 2048 --kernel_offset 0x80208000 --ramdisk_offset 0x82200000 \
      --second_offset 0x81100000 --tags_offset 0x80200100 \
      --cmdline 'console=ttyHSL0,115200,n8 msm_rtb.filter=0x3F ehci-hcd.park=3' \
      --kernel ../kernel/msm/arch/arm/boot/zImage \
      --ramdisk ../initrd/initrd-rootfs.cpio.gz -o boot.img

Attempting the Boot


I can now try booting the tablet into Debian:
$ fastboot boot boot.img
downloading 'boot.img'...
OKAY [  0.256s]
booting...
OKAY [  0.026s]
finished. total time: 0.282s
$ telnet 192.168.1.1
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.
Debian GNU/Linux 7
nexus-7 login: simon
Password: 
Linux nexus-7 3.4.0-g03485a6-dirty #2 SMP PREEMPT Tue Nov 4 21:32:26 GMT 2014 armv7l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
simon@nexus-7:~$ mount
/dev/mmcblk0p30 on / type ext4 (rw,relatime,data=ordered)
tmpfs on /run type tmpfs (rw,nosuid,noexec,relatime,size=185120k,mode=755)
tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,relatime,size=10240k,mode=755)
tmpfs on /run/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,size=370220k)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620)
Hurrah!! Debian is now running, from the user data partition! There's still a little way to go before it can be self-sufficient, but this is definitely awesome!

Next Time ...


... I'll see if I can get WiFi networking to work.

No comments:

Post a Comment