• Introducing XDA Computing: Discussion zones for Hardware, Software, and more!    Check it out!

[Guide] Linux on the Nexus 9

Search This thread

sonicadvance1

Member
Dec 25, 2010
42
137
This is a guide about getting a Linux distribution up and running on your Nexus 9. The goal of this thread is to give a basic understanding about how to get a basic system running, but not working throughout all the kinks that come with doing so on tablet style hardware.
This guide will be non-destructive, and won't touch the data on your tablet. Unless of course you've yet to unlock your bootloader, which if that is the case then shame on you.

Some prerequisites before starting this project with your Nexus 9
  • A Linux distribution running on your computer. Either in a virtual machine or as your main OS.
  • A USB flash drive
  • USB OTG cable
Some things that are nice to have
A brief overview about what we are going to be doing here.
First we are going to be building an initramfs. This is a small filesystem with a bare minimum set of tools that can be loaded in to RAM space and use. We will be using this while we are initially getting our kernel up and running, to make sure we didn't mess anything up.

Next we will move on to building the actual kernel for the device. We will be running a Android kernel since Nvidia hasn't been the quickest on their attempt to upstreaming everything required to run this device on a upstream Linux kernel. So we'll be running a 3.10 kernel, which is a heck of a lot better than some 3.0 or 3.4 thing that older devices are on.

Third we will be building our rootfs that will run from an external USB storage device, so we don't affect any data actually on the tablet. I may also add to the guide how to partition your internal storage space so we can use that instead.

[Step 0/4]
We need to set up our build environment on our host Linux distribution before we begin anything. I'm going to assume that the host Linux system we are running is Debian/Ubuntu based, so we'll be using apt to grab our packages we need.
Code:
sudo apt-get install gcc g++ git gcc-4.9-aarch64-linux-gnu g++-4.9-aarch64-linux-gnu libncurses5-dev

This installs the host gcc and AArch64 cross compiler, along with git. We will need these tools to build everything required.

Things that don't work.
  • Rebooting
  • Touchscreen
  • Way more things that people care about
 
Last edited:

sonicadvance1

Member
Dec 25, 2010
42
137
The first step that we will be doing is building our filesystem that we will load in to memory for testing.
This is typically called an initramfs, which tends to be a compressed filesystem that gets copied in to a kernel image once the kernel is built. We won't be using this image for long, as it is mostly to make sure we have everything working correctly before jumping to a full linux distribution.

This step is technically unnecessary, but it brings you up to speed with what initially needs to be done to get something running.

So starting off, we are going to need to pick a target for the filesystem. I've decided to go with buildroot since it is a project that is really easy to get built.

Steps Overview
  • Clone buildroot with git
  • Setup buildroot to cross compile
  • Set up basic options
  • Compile!
1)*Clone buildroot from github.
Code:
git clone https://github.com/buildroot/buildroot.git
cd buildroot

2)*Start configuring buildroot
Code:
make menuconfig

This will bring up a menu for determining how we want to build our buildroot. We have quite a few things to change.
These options enable building an AArch64 capable buildroot using the Linaro AArch64 toolchain, with it outputting a terminal to ttyFIQ0 which is the headphone UART terminal. Along with taking the filesystem and pushing it in to a cpio archive which later the linux kernel understands how to use.

Target options->Target Architecture->AArch64
Toolchain->Toolchain type->External toolchain
  • External toolchain automatically enables the Linaro AArch64 toolchain below it.
System configuration->getty options->TTY port->ttyFIQ0
Filesystem images->cpio the root filesystem

3)*Build the buildroot
Code:
make

This will go through downloading all the packages required to build an AArch64 buildroot. So go make a cup of tea, this should only take a few minutes depending on your internet speed and computer speed.

Once the system has gone through compiling everything, then you'll have a file available.
This file will end up in the `output/images/` folder as the file 'rootfs.cpio'. If that file isn't there then something terrible has happened.

If the file is there, then everything on this step is done! We built our temporary filesystem which we will be putting to use in the next step!
 

sonicadvance1

Member
Dec 25, 2010
42
137
This is really the meat of the guide. Building the kernel correctly is half the battle with running a full Linux distribution on the device. We are going to be pulling our kernel from the official Android repository and changing the default configuration to suit our needs.

Steps Overview
  • Clone kernel with git
  • Configure environment for cross-compiling
  • Configure the kernel for building correctly
  • Test run kernel
1) Clone the kernel using git
This step will take quite a bit of time since it is fairly large.
Code:
git clone https://android.googlesource.com/kernel/tegra.git
cd tegra

2)*Checkout the correct branch
Code:
git checkout android-tegra-flounder-3.10-lollipop-release

3)*Set environment variables for cross-compiling the Linux kernel
Code:
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

4) Configure the kernel
Code:
make flounder_defconfig
make menuconfig

This will bring up a menu just like how the buildroot menu came up. We need to set a few options to make sure everything comes up correctly on the Nexus 9.
General setup->Cross-compiler tool prefix->aarch64-linux-gnu-
General setup->Initramfs source file(s)-><The cpio file that was built in the buildroot>
  • Mine was '/home/ryanh/work/N9_Kernel_Tutorial/buildroot/output/images/rootfs.cpio'
Device Drivers->Character devices->Virtual Terminal
Device Drivers->Generic Driver Options->Maintain a devtmpfs filesystem to mount at /dev
Device Drivers->Generic Driver Options->Automount devtmpfs at /dev...
Device Drivers->Network device support->Wireless LAN->Firmware path->/lib/firmware/fw_bcmdhd.bin
Device Drivers->Network device support->Wireless LAN->NVRAM path->/etc/wifi/bcmdhd.cal
Device Drivers->Character devices->Virtual terminal
Device Drivers->Watchdog Timer Support->Tegra watchdog (Disable it)
Device Drivers->Staging drivers->Android->Put the FIQ debugger into console mode by default
Device Drivers->Graphics support->Tegra Framebuffer driver
Device Drivers->Graphics support->Console display driver support->Framebuffer Console support
Device Drivers->Graphics support->Console display driver support->Map the console to the primary display device
Device Drivers->Graphics support->Console display driver support->Framebuffer Console Rotation

We've got to enable the tegra watchdog otherwise the device will reboot automatically every two minutes.
The few other things are needed for a sane Linux environment and also setting the initramfs file as our root filesystem.

5)*Compile the kernel
Code:
make -j5

If you're lucky it will build without error and you'll have a file in 'arch/arm64/boot/' named 'Image.gz-dtb' which is the final kernel file.
With this file we should be able to boot a kernel with a buildroot automatically mounted.

Test run the kernel!
This is where I recommend having your headphone UART cable, because this won't update the screen at all and it will seem like it has locked up. The cable is fairly easy to make with a little bit of know how about soldering and electronics, and will save you from tearing out your hair trying to figure out what went wrong.

Without the cable you won't be able to see any output, so either build the cable or skip this step.
To see output from the cable you need to connect to it with either gnu screen or minicom
Code:
sudo screen /dev/ttyUSB0 115200

To boot the kernel reboot in to the bootloader and then boot the kernel using fastboot
Code:
adb reboot bootloader
fastboot -c "console=ttyFIQ0,115200n8 rw" boot arch/arm64/boot/Image.gz-dtb

Once you do this, over the UART cable you'll see the Linux kernel booting and have a buildroot login terminal.
The default username is 'root' without any password.
Code:
Welcome to Buildroot
buildroot login: root
# uname -a
Linux buildroot 3.10.40-ga3846f1 #2 SMP PREEMPT Sat Dec 27 06:15:13 CST 2014 aarch64 GNU/Linux

This shows that the kernel is booting properly and jumping in to our buildroot correctly.


To go back in to Android from this area just type reboot in to the terminal instance.
 
Last edited:

sonicadvance1

Member
Dec 25, 2010
42
137
The final step in getting a Linux distro is to build our root filesystem on a USB flash drive.

Steps Overview
  • Format flash drive as ext2/ext3/ext4
  • Dump Ubuntu rootfs on to the flash drive
  • Minor configuration
  • Rebuild Linux kernel with new options
The first thing we will want to be doing is formatting a flash drive to a Linux partition type. I am going to be using an ext3 partition on a 64GB USB 3.0 flash drive. I'd recommend at least an 8GB flash drive, anything smaller may have issues with fitting everything on to it.

Once you're done flashing your drive you'll need to download an Ubuntu Core image for AArch64/ARM64/ARMv8.
Download the Ubuntu Core 14.04.1 image*Here. Make sure to grab the 'arm64' tar.gz file, not the 'amd64' file.

Once you have the flash drive formatted and mounted, extract the ubuntu core image to the flash drive.
Code:
sudo tar zxf ubuntu-core-14.04.1-core-arm64.tar.gz -C Mount/
Configuring the Ubuntu Core image for the Nexus 9
Steps Overview
  • Terminal over UART configuration
  • DNS configuration
  • Firmware files
  • Add user
In order to properly get a terminal instance over the UART we have to add a package to the base Ubuntu core image.
This is a small package that all it does is open a new terminal instance over the configured getty instance. The package can be found*Here.
To install it to the root filesystem just unpack it to the root filesystem on the USB flash drive
Code:
sudo tar xf console.tar -C Mount/


We need to set up a DNS server so that the filesystem will be able to resolve addresses via DNS.
Let's just set it to Google's DNS.
Code:
sudo echo 'nameserver 8.8.8.8' >> Mount/etc/resolv.conf


We've got to grab the firmware files from the Nexus 9 in order for the device to stop spamming warnings at us in the console.
These are used for multiple things, so it is a good idea to grab them. You can grab these either from a factory image or directly from the Nexus 9. I chose to grab mine directly from the Nexus 9.
Code:
sudo mkdir Mount/lib/firmware
sudo mkdir Mount/etc/wifi
sudo adb pull /vendor/firmware Mount/lib/firmware/
sudo adb pull /system/etc/wifi/bcmdhd.cal Mount/etc/wifi/

We need to add a user to the root filesystem. This is a fairly annoying step because we actually need to chroot in to the filesystem.
There is a really nice guide to doing this on a ARMv7 filesystem*Here. This won't work for our system because we are working with ARMv8 instead.
So we are going to use that guide as a base but change it over to support what we need to do for AArch64.
First thing we've got to do is build qemu as a static binary for AArch64
This is fairly straight forward.
Code:
git clone git://git.qemu-project.org/qemu.git
cd qemu
sudo apt-get build-dep qemu
./configure --target-list=aarch64-linux-user *--static --disable-werror
make -j5
This will get us a binary in the aarch64-linux-user folder called 'qemu-aarch64'
We will need to rename this to 'qemu-arm64-static' and move it in to the '/usr/bin/' folder inside of our root partition
Once qemu is inside of the root partition, we will be able to chroot in to it and add our user.
So go in to the root directory of our filesystem we are generating, and run a few basic commands.

Set up some mounts inside of the chroot

Code:
for m in `echo 'sys dev proc'`; do sudo mount /$m ./$m -o bind; done
*Chroot in to the root filesystem
Code:
sudo LC_ALL=C chroot . /bin/bash
Now we are inside of the root filesystem, we can add the new user to it.
Let's just add a new user named 'ubuntu'. The first command will ask for a password for your user. The rest will add it to some default groups to make sure it can do things.
Code:
adduser ubuntu
addgroup ubuntu adm
addgroup ubuntu sudo

Once the user is added you can exit the root filesystem with a regular exit command, then we have to make sure to unmount all of the mounts we did prior to chrooting in to the filesystem.
Code:
for m in `echo 'sys dev proc'`; do sudo umount ./$m; done

Make sure to cleanly unmount the flash drive so everything is written to it.

Reconfigure the kernel to boot from flash drive instead of initramfs
1)*Go in to the kernel configuration
Code:
make menuconfig

Change the configuration to remove the initramfs
General Setup->Initial RAM filesystem and RAM disk (initramfs/initrd) support (Disable it)

Then exit the menu and rebuild the kernel
Code:
make -j5
Running the Ubuntu Core Image
So with the device sitting at the bootloader we will need to boot the new kernel
Code:
fastboot -c "fbcon=rotate:1 root=/dev/sda1 rootwait rw" boot arch/arm64/boot/Image.gz-dtb

This will boot the kernel and then it'll wait until you plug in the flash drive to continue booting.
So plug the USB hub in to the USB OTG cable. Then plug your USB flash drive and USB keyboard in to the USB hub.
After that plug the USB OTG cable in to the tablet and it will continue booting.
Give it roughly 20-60 seconds and it will show a login prompt on the devices screen.
You'll be able to login to the core image with the user you created earlier.

With some packages installed like the xubuntu-desktop environment you can have a full desktop available.
 
Last edited:

sonicadvance1

Member
Dec 25, 2010
42
137
Reserved for QA - Additional Information - Misc

Information
Hardware acceleration
Potentially hardware acceleration should be possible using the Nouveau video driver. This is completely untested, and the Nouveau version included with Ubuntu 14.04 isn't new enough to have GK20a support. One would most likely need to build Nouveau from ToT to get support.

Known Issues
  • If something tries enabling the wifi chipset then the kernel will hardlock and cause the device to reboot. It may be best to disable the broadcom driver in the kernel until that is figured out.
  • Bluetooth doesn't seem to work. Not sure why not. Seems to show up as a device, but it can't be used.
 
Last edited:

USBhost

Recognized Contributor
Oct 23, 2013
5,990
6,977
u:r:usbhost:s0
Cant wait to try!!

---------- Post added at 08:35 PM ---------- Previous post was at 08:31 PM ----------

Where did you get your headphone UART cable
 

sonicadvance1

Member
Dec 25, 2010
42
137
I took some time to figure out how to fix the integrated wireless LAN in the tablet.
This requires grabbing an additional file from the tablet and configuring a few kernel options so they know where the new file locations are since previously they are configured to Android specific locations.
Seems to work fine on my 802.11n 2.4Ghz network.
 

farmerbb

Senior Member
Oct 9, 2011
459
341
Somewhere, Utah
Running into a snag while compiling the kernel. It always fails at this exact spot on Ubuntu 14.10. Any ideas? (first time compiling a kernel from source :) )

Q2w3l1W.png
 

sonicadvance1

Member
Dec 25, 2010
42
137
Looks like it's having a fit in a required module that is enabled for the tegra platform.
Looks like a new warning that has cropped up in a newer version of GCC? My AArch64 gcc version that I'm running is 4.8.2. I can install 4.9.1 and test there but it will have to be later.

GCC is definitely right in this case though, Qualcomm managed to screw up a basic memset function call. The zero is supposed to be the middle argument, and GCC recognizes it as an warning now. Which gets upgraded to an error with some configuration.
If you know light C/C++ you can change those two lines that it is complaining about to be correct.
Should be
memset(modem->ftrace_cmd, 0, sizeof(modem->ftrace_cmd));
and
memset(modem->msr_info_list, 0, sizeof(modem->msr_info_list));

For those two lines at 1912 and 1915 respectively.
Looks like I'm going to have to start a patch set for this to work around Qualcomm failing.
 
  • Like
Reactions: farmerbb

farmerbb

Senior Member
Oct 9, 2011
459
341
Somewhere, Utah
Looks like it's having a fit in a required module that is enabled for the tegra platform.
Looks like a new warning that has cropped up in a newer version of GCC? My AArch64 gcc version that I'm running is 4.8.2. I can install 4.9.1 and test there but it will have to be later.

GCC is definitely right in this case though, Qualcomm managed to screw up a basic memset function call. The zero is supposed to be the middle argument, and GCC recognizes it as an warning now. Which gets upgraded to an error with some configuration.
If you know light C/C++ you can change those two lines that it is complaining about to be correct.
Should be
memset(modem->ftrace_cmd, 0, sizeof(modem->ftrace_cmd));
and
memset(modem->msr_info_list, 0, sizeof(modem->msr_info_list));

For those two lines at 1912 and 1915 respectively.
Looks like I'm going to have to start a patch set for this to work around Qualcomm failing.

I made the changes to qcom_usb_modem_power.c as you listed, and got the kernel to compile okay.

Starting from a fresh Ubuntu 14.10 install (inside a VM), I ended up installing these additional packages in addition to the ones you list in step 0:
Code:
libncurses5-dev libc6:i386 libstdc++6:i386 zlib1g:i386 qemu binfmt-support qemu-user-static android-tools-adb android-tools-fastboot

To get the kernel to compile, I also needed to symlink aarch64-linux-gnu-gcc-4.9 to aarch64-linux-gnu-gcc in /usr/bin, for make to recognize the correct gcc program.

But anyway, I've gotten my N9 booted to the Ubuntu login prompt :) Now I just need to grab a USB keyboard so that I can actually login!

Thanks so much for the guide! Can't wait for MultiROM to be ported to our device, to make this easier to boot :)
 
  • Like
Reactions: Atcold

USBhost

Recognized Contributor
Oct 23, 2013
5,990
6,977
u:r:usbhost:s0
I made the changes to qcom_usb_modem_power.c as you listed, and got the kernel to compile okay.

Starting from a fresh Ubuntu 14.10 install (inside a VM), I ended up installing these additional packages in addition to the ones you list in step 0:
Code:
libncurses5-dev libc6:i386 libstdc++6:i386 zlib1g:i386 qemu binfmt-support qemu-user-static android-tools-adb android-tools-fastboot

To get the kernel to compile, I also needed to symlink aarch64-linux-gnu-gcc-4.9 to aarch64-linux-gnu-gcc in /usr/bin, for make to recognize the correct gcc program.

But anyway, I've gotten my N9 booted to the Ubuntu login prompt :) Now I just need to grab a USB keyboard so that I can actually login!

Thanks so much for the guide! Can't wait for MultiROM to be ported to our device, to make this easier to boot :)

ii cant wait
 

t1mur

Senior Member
Jan 20, 2013
126
58
Excellent guide. Thank you very much.
It appears the "*" in this configure command below may be a typo?

Code:
git clone git://git.qemu-project.org/qemu.git
cd qemu
sudo apt-get build-dep qemu
./configure --target-list=aarch64-linux-user *--static --disable-werror
make -j5

Anyway, I am running into a more serious issue (Problem resolved. See bottom.) executing dynamically build aarch64 binaries. On my 14.10 x86 machine, I am able to execute statically build aarch64 binaries using my new qemu-aarch64 emulator. I am not able to execute dynamically build aarch64 binaries. For instance, when I try to execute bin/bash from the mounted root filesystem directly on the host, it will complain:

/lib/ld-linux-aarch64.so.1: No such file or directory

Now this is all right, because I don't have the required aarch64 shared libs on my x86 machine. So I copied qemu-aarch64 to /usr/bin/qemu-aarch64-static on the mounted root filesystem. However, when I run chroot (executing the same /bin/bash):

Code:
sudo LC_ALL=C chroot . /bin/bash
Now we are inside of the root filesystem, we can add the new user to it.

...I am getting this:

chroot: failed to run command ‘/bin/bash’: No such file or directory

I think this is due to the same issue as above: that qemu is looking for aarch64 shared libs (/lib/ld-linux-aarch64.so.1 is needed to run bash) on my host machine. Should it not, due to chroot, look for them in the mounted root filesystem (where they actually are)?

Other than this... my ARM64 kernel is booting fine. The only problem is, that I cannot login to it yet, because I wasn't able to add a new user.

Edit: Interestingly, the following "works":

Code:
$ sudo chroot . usr/bin/qemu-aarch64-static /bin/bash
bash: /usr/bin/groups: No such file or directory
[email protected]:/#

Now inside the chroot running the aarch64 variant of /bin/bash, I am still unable to start any aarch64 executables. But tab-completion works and I am able to find and run /usr/bin/qemu-aarch64-static (the x86 binary). Could this be a problem with any binfmts mappings inside chroot? Odd.

Edit 2: Solved. Just tried from another machine (10.04) and here, everything is working perfectly. I probably have chroot hosed on my 1st machine. Excellent guide. One last note: if you don't have qemu installed on your host machine, you may need to run the following:

Code:
sudo apt-get install qemu binfmt-support qemu-user-static
 
Last edited:

farmerbb

Senior Member
Oct 9, 2011
459
341
Somewhere, Utah
Excellent guide. Thank you very much.
It appears the "*" in this configure command below may be a typo?



Anyway, I am running into a more serious issue executing dynamically build aarch64 binaries. On my 14.10 x86 machine, I am able to execute statically build aarch64 binaries using my new qemu-aarch64 emulator. I am not able to execute dynamically build aarch64 binaries. For instance, when I try to execute bin/bash from the mounted root filesystem directly on the host, it will complain:

/lib/ld-linux-aarch64.so.1: No such file or directory

Now this is all right, because I don't have the required aarch64 shared libs on my x86 machine. So I copied qemu-aarch64 to /usr/bin/qemu-aarch64-static on the mounted root filesystem. However, when I run chroot (executing the same /bin/bash):



...I am getting this:

chroot: failed to run command ‘/bin/bash’: No such file or directory

I think this is due to the same issue as above: that qemu is looking for aarch64 shared libs (/lib/ld-linux-aarch64.so.1 is needed to run bash) on my host machine. Should it not, due to chroot, look for them in the mounted root filesystem (where they actually are)?

Other than this... my ARM64 kernel is booting fine. The only problem is, that I cannot login to it yet, because I wasn't able to add a new user.

Edit: Interestingly, the following "works":

Code:
$ sudo chroot . usr/bin/qemu-aarch64-static /bin/bash
bash: /usr/bin/groups: No such file or directory
[email protected]:/#

Now inside the chroot running the aarch64 variant of /bin/bash, I am still unable to start any aarch64 executables. But tab-completion works and I am able to find and run /usr/bin/qemu-aarch64-static (the x86 binary). Could this be a problem with any binfmts mappings inside chroot? Odd.
I ran into these exact issues as well, and was eventually able to get the chroot working properly

Make sure you have the qemu, binfmt-support, qemu-user-static packages installed, and run 'update-binfmts --display' to ensure that aarch64 support is registered properly. Also make sure you've named the 'qemu-aarch64' binary as 'qemu-arm64-static' (not 'qemu-aarch64-static') at usr/bin on the flash drive (the Ubuntu Core image recognizes its architecture as arm64, not aarch64)
 
Last edited:

t1mur

Senior Member
Jan 20, 2013
126
58
I ran into these exact issues as well, and was eventually able to get the chroot working properly

Make sure you have the qemu, binfmt-support, qemu-user-static packages installed, and run 'update-binfmts --display' to ensure that aarch64 support is registered properly. Also make sure you've named the 'qemu-aarch64' binary as 'qemu-arm64-static' (not 'qemu-aarch64-static') at usr/bin on the flash drive (the Ubuntu Core image recognizes its architecture as arm64, not aarch64)

Ah... it's really good to know one isn't alone. Hehe. Well, it looks like 14.10 is better suited for compiling the arm kernel (gcc-4.9-aarch64 etc.), but doing the chroot is smoother on 14.04.

But how do I get wifi and/or USB ethernet up? And why do have to look at this?

unable to stat /etc/sudoers: no such file or directory

Did you get this too?
 

farmerbb

Senior Member
Oct 9, 2011
459
341
Somewhere, Utah
Ah... it's really good to know one isn't alone. Hehe. Well, it looks like 14.10 is better suited for compiling the arm kernel (gcc-4.9-aarch64 etc.), but doing the chroot is smoother on 14.04.

But how do I get wifi and/or USB ethernet up? And why do have to look at this?

unable to stat /etc/sudoers: no such file or directory

Did you get this too?

I'm still working on getting networking and X.org running myself. It will recognize my ASIX USB adapter (in the dmesg log) but eth0 doesn't show up and 'ifconfig eth0 up' doesn't work. Haven't tried Wi-Fi yet. My low-level Linux knowledge isn't all that great :( However, I can still chroot back into the filesystem on my PC and update/install packages, etc. in the meantime.

No I don't see the /etc/sudoers error.
 

Top Liked Posts

  • There are no posts matching your filters.
  • 27
    This is a guide about getting a Linux distribution up and running on your Nexus 9. The goal of this thread is to give a basic understanding about how to get a basic system running, but not working throughout all the kinks that come with doing so on tablet style hardware.
    This guide will be non-destructive, and won't touch the data on your tablet. Unless of course you've yet to unlock your bootloader, which if that is the case then shame on you.

    Some prerequisites before starting this project with your Nexus 9
    • A Linux distribution running on your computer. Either in a virtual machine or as your main OS.
    • A USB flash drive
    • USB OTG cable
    Some things that are nice to have
    A brief overview about what we are going to be doing here.
    First we are going to be building an initramfs. This is a small filesystem with a bare minimum set of tools that can be loaded in to RAM space and use. We will be using this while we are initially getting our kernel up and running, to make sure we didn't mess anything up.

    Next we will move on to building the actual kernel for the device. We will be running a Android kernel since Nvidia hasn't been the quickest on their attempt to upstreaming everything required to run this device on a upstream Linux kernel. So we'll be running a 3.10 kernel, which is a heck of a lot better than some 3.0 or 3.4 thing that older devices are on.

    Third we will be building our rootfs that will run from an external USB storage device, so we don't affect any data actually on the tablet. I may also add to the guide how to partition your internal storage space so we can use that instead.

    [Step 0/4]
    We need to set up our build environment on our host Linux distribution before we begin anything. I'm going to assume that the host Linux system we are running is Debian/Ubuntu based, so we'll be using apt to grab our packages we need.
    Code:
    sudo apt-get install gcc g++ git gcc-4.9-aarch64-linux-gnu g++-4.9-aarch64-linux-gnu libncurses5-dev

    This installs the host gcc and AArch64 cross compiler, along with git. We will need these tools to build everything required.

    Things that don't work.
    • Rebooting
    • Touchscreen
    • Way more things that people care about
    23
    The final step in getting a Linux distro is to build our root filesystem on a USB flash drive.

    Steps Overview
    • Format flash drive as ext2/ext3/ext4
    • Dump Ubuntu rootfs on to the flash drive
    • Minor configuration
    • Rebuild Linux kernel with new options
    The first thing we will want to be doing is formatting a flash drive to a Linux partition type. I am going to be using an ext3 partition on a 64GB USB 3.0 flash drive. I'd recommend at least an 8GB flash drive, anything smaller may have issues with fitting everything on to it.

    Once you're done flashing your drive you'll need to download an Ubuntu Core image for AArch64/ARM64/ARMv8.
    Download the Ubuntu Core 14.04.1 image*Here. Make sure to grab the 'arm64' tar.gz file, not the 'amd64' file.

    Once you have the flash drive formatted and mounted, extract the ubuntu core image to the flash drive.
    Code:
    sudo tar zxf ubuntu-core-14.04.1-core-arm64.tar.gz -C Mount/
    Configuring the Ubuntu Core image for the Nexus 9
    Steps Overview
    • Terminal over UART configuration
    • DNS configuration
    • Firmware files
    • Add user
    In order to properly get a terminal instance over the UART we have to add a package to the base Ubuntu core image.
    This is a small package that all it does is open a new terminal instance over the configured getty instance. The package can be found*Here.
    To install it to the root filesystem just unpack it to the root filesystem on the USB flash drive
    Code:
    sudo tar xf console.tar -C Mount/


    We need to set up a DNS server so that the filesystem will be able to resolve addresses via DNS.
    Let's just set it to Google's DNS.
    Code:
    sudo echo 'nameserver 8.8.8.8' >> Mount/etc/resolv.conf


    We've got to grab the firmware files from the Nexus 9 in order for the device to stop spamming warnings at us in the console.
    These are used for multiple things, so it is a good idea to grab them. You can grab these either from a factory image or directly from the Nexus 9. I chose to grab mine directly from the Nexus 9.
    Code:
    sudo mkdir Mount/lib/firmware
    sudo mkdir Mount/etc/wifi
    sudo adb pull /vendor/firmware Mount/lib/firmware/
    sudo adb pull /system/etc/wifi/bcmdhd.cal Mount/etc/wifi/

    We need to add a user to the root filesystem. This is a fairly annoying step because we actually need to chroot in to the filesystem.
    There is a really nice guide to doing this on a ARMv7 filesystem*Here. This won't work for our system because we are working with ARMv8 instead.
    So we are going to use that guide as a base but change it over to support what we need to do for AArch64.
    First thing we've got to do is build qemu as a static binary for AArch64
    This is fairly straight forward.
    Code:
    git clone git://git.qemu-project.org/qemu.git
    cd qemu
    sudo apt-get build-dep qemu
    ./configure --target-list=aarch64-linux-user *--static --disable-werror
    make -j5
    This will get us a binary in the aarch64-linux-user folder called 'qemu-aarch64'
    We will need to rename this to 'qemu-arm64-static' and move it in to the '/usr/bin/' folder inside of our root partition
    Once qemu is inside of the root partition, we will be able to chroot in to it and add our user.
    So go in to the root directory of our filesystem we are generating, and run a few basic commands.

    Set up some mounts inside of the chroot

    Code:
    for m in `echo 'sys dev proc'`; do sudo mount /$m ./$m -o bind; done
    *Chroot in to the root filesystem
    Code:
    sudo LC_ALL=C chroot . /bin/bash
    Now we are inside of the root filesystem, we can add the new user to it.
    Let's just add a new user named 'ubuntu'. The first command will ask for a password for your user. The rest will add it to some default groups to make sure it can do things.
    Code:
    adduser ubuntu
    addgroup ubuntu adm
    addgroup ubuntu sudo

    Once the user is added you can exit the root filesystem with a regular exit command, then we have to make sure to unmount all of the mounts we did prior to chrooting in to the filesystem.
    Code:
    for m in `echo 'sys dev proc'`; do sudo umount ./$m; done

    Make sure to cleanly unmount the flash drive so everything is written to it.

    Reconfigure the kernel to boot from flash drive instead of initramfs
    1)*Go in to the kernel configuration
    Code:
    make menuconfig

    Change the configuration to remove the initramfs
    General Setup->Initial RAM filesystem and RAM disk (initramfs/initrd) support (Disable it)

    Then exit the menu and rebuild the kernel
    Code:
    make -j5
    Running the Ubuntu Core Image
    So with the device sitting at the bootloader we will need to boot the new kernel
    Code:
    fastboot -c "fbcon=rotate:1 root=/dev/sda1 rootwait rw" boot arch/arm64/boot/Image.gz-dtb

    This will boot the kernel and then it'll wait until you plug in the flash drive to continue booting.
    So plug the USB hub in to the USB OTG cable. Then plug your USB flash drive and USB keyboard in to the USB hub.
    After that plug the USB OTG cable in to the tablet and it will continue booting.
    Give it roughly 20-60 seconds and it will show a login prompt on the devices screen.
    You'll be able to login to the core image with the user you created earlier.

    With some packages installed like the xubuntu-desktop environment you can have a full desktop available.
    15
    The first step that we will be doing is building our filesystem that we will load in to memory for testing.
    This is typically called an initramfs, which tends to be a compressed filesystem that gets copied in to a kernel image once the kernel is built. We won't be using this image for long, as it is mostly to make sure we have everything working correctly before jumping to a full linux distribution.

    This step is technically unnecessary, but it brings you up to speed with what initially needs to be done to get something running.

    So starting off, we are going to need to pick a target for the filesystem. I've decided to go with buildroot since it is a project that is really easy to get built.

    Steps Overview
    • Clone buildroot with git
    • Setup buildroot to cross compile
    • Set up basic options
    • Compile!
    1)*Clone buildroot from github.
    Code:
    git clone https://github.com/buildroot/buildroot.git
    cd buildroot

    2)*Start configuring buildroot
    Code:
    make menuconfig

    This will bring up a menu for determining how we want to build our buildroot. We have quite a few things to change.
    These options enable building an AArch64 capable buildroot using the Linaro AArch64 toolchain, with it outputting a terminal to ttyFIQ0 which is the headphone UART terminal. Along with taking the filesystem and pushing it in to a cpio archive which later the linux kernel understands how to use.

    Target options->Target Architecture->AArch64
    Toolchain->Toolchain type->External toolchain
    • External toolchain automatically enables the Linaro AArch64 toolchain below it.
    System configuration->getty options->TTY port->ttyFIQ0
    Filesystem images->cpio the root filesystem

    3)*Build the buildroot
    Code:
    make

    This will go through downloading all the packages required to build an AArch64 buildroot. So go make a cup of tea, this should only take a few minutes depending on your internet speed and computer speed.

    Once the system has gone through compiling everything, then you'll have a file available.
    This file will end up in the `output/images/` folder as the file 'rootfs.cpio'. If that file isn't there then something terrible has happened.

    If the file is there, then everything on this step is done! We built our temporary filesystem which we will be putting to use in the next step!
    15
    This is really the meat of the guide. Building the kernel correctly is half the battle with running a full Linux distribution on the device. We are going to be pulling our kernel from the official Android repository and changing the default configuration to suit our needs.

    Steps Overview
    • Clone kernel with git
    • Configure environment for cross-compiling
    • Configure the kernel for building correctly
    • Test run kernel
    1) Clone the kernel using git
    This step will take quite a bit of time since it is fairly large.
    Code:
    git clone https://android.googlesource.com/kernel/tegra.git
    cd tegra

    2)*Checkout the correct branch
    Code:
    git checkout android-tegra-flounder-3.10-lollipop-release

    3)*Set environment variables for cross-compiling the Linux kernel
    Code:
    export ARCH=arm64
    export CROSS_COMPILE=aarch64-linux-gnu-

    4) Configure the kernel
    Code:
    make flounder_defconfig
    make menuconfig

    This will bring up a menu just like how the buildroot menu came up. We need to set a few options to make sure everything comes up correctly on the Nexus 9.
    General setup->Cross-compiler tool prefix->aarch64-linux-gnu-
    General setup->Initramfs source file(s)-><The cpio file that was built in the buildroot>
    • Mine was '/home/ryanh/work/N9_Kernel_Tutorial/buildroot/output/images/rootfs.cpio'
    Device Drivers->Character devices->Virtual Terminal
    Device Drivers->Generic Driver Options->Maintain a devtmpfs filesystem to mount at /dev
    Device Drivers->Generic Driver Options->Automount devtmpfs at /dev...
    Device Drivers->Network device support->Wireless LAN->Firmware path->/lib/firmware/fw_bcmdhd.bin
    Device Drivers->Network device support->Wireless LAN->NVRAM path->/etc/wifi/bcmdhd.cal
    Device Drivers->Character devices->Virtual terminal
    Device Drivers->Watchdog Timer Support->Tegra watchdog (Disable it)
    Device Drivers->Staging drivers->Android->Put the FIQ debugger into console mode by default
    Device Drivers->Graphics support->Tegra Framebuffer driver
    Device Drivers->Graphics support->Console display driver support->Framebuffer Console support
    Device Drivers->Graphics support->Console display driver support->Map the console to the primary display device
    Device Drivers->Graphics support->Console display driver support->Framebuffer Console Rotation

    We've got to enable the tegra watchdog otherwise the device will reboot automatically every two minutes.
    The few other things are needed for a sane Linux environment and also setting the initramfs file as our root filesystem.

    5)*Compile the kernel
    Code:
    make -j5

    If you're lucky it will build without error and you'll have a file in 'arch/arm64/boot/' named 'Image.gz-dtb' which is the final kernel file.
    With this file we should be able to boot a kernel with a buildroot automatically mounted.

    Test run the kernel!
    This is where I recommend having your headphone UART cable, because this won't update the screen at all and it will seem like it has locked up. The cable is fairly easy to make with a little bit of know how about soldering and electronics, and will save you from tearing out your hair trying to figure out what went wrong.

    Without the cable you won't be able to see any output, so either build the cable or skip this step.
    To see output from the cable you need to connect to it with either gnu screen or minicom
    Code:
    sudo screen /dev/ttyUSB0 115200

    To boot the kernel reboot in to the bootloader and then boot the kernel using fastboot
    Code:
    adb reboot bootloader
    fastboot -c "console=ttyFIQ0,115200n8 rw" boot arch/arm64/boot/Image.gz-dtb

    Once you do this, over the UART cable you'll see the Linux kernel booting and have a buildroot login terminal.
    The default username is 'root' without any password.
    Code:
    Welcome to Buildroot
    buildroot login: root
    # uname -a
    Linux buildroot 3.10.40-ga3846f1 #2 SMP PREEMPT Sat Dec 27 06:15:13 CST 2014 aarch64 GNU/Linux

    This shows that the kernel is booting properly and jumping in to our buildroot correctly.


    To go back in to Android from this area just type reboot in to the terminal instance.
    6
    Reserved for QA - Additional Information - Misc

    Information
    Hardware acceleration
    Potentially hardware acceleration should be possible using the Nouveau video driver. This is completely untested, and the Nouveau version included with Ubuntu 14.04 isn't new enough to have GK20a support. One would most likely need to build Nouveau from ToT to get support.

    Known Issues
    • If something tries enabling the wifi chipset then the kernel will hardlock and cause the device to reboot. It may be best to disable the broadcom driver in the kernel until that is figured out.
    • Bluetooth doesn't seem to work. Not sure why not. Seems to show up as a device, but it can't be used.