Contents

Mainlining Nexus 9: First Boot

/images/Mainline-Nexus-9-p1/mainline_is_booting.png

Having received the Google Nexus 9 tablet recently, I decided to try mainlining it, given that it uses the NVIDIA Tegra K1 (tegra132 variant) that has good mainline support. The Nexus 9 is an Android ARM64 tablet branded as Google but made by HTC, codenamed “flounder”. There seem to be no tegra132 devices with a device tree in mainline right now, apart from a developer board.

Information Gathering

Downstream Device Tree

The device tree used by the downstream Linux kernel cannot be used with the mainline kernel - but it is a good reference for writing a new device tree. It can be dumped by a running device using dtc, the device tree compiler. This program can decompile device trees as well as compile them. The benefit of doing it this way is that the entire device tree will be dumped into one file - unlike the downstream released kernel sources which have a mess of a device tree split up across several files.

First, a static, cross-compiled version of dtc needs to be built (unless you want to copy all the libraries over), which can be done by modifying the Makefile. This can then be copied over to either Android with root access, or TWRP. The device tree can then be dumped using this command:

dtc -I fs -O dts -o flounder-downstream.dts /proc/device-tree

Command line parameters

The cmdline parameters often include useful information such as memory addresses or panel types. Some of these are passed by the bootloader, and some of these are hardcoded in the kernel. The cmdline is easier to find compared to the device tree:

cat /proc/cmdline > cmdline-downstream.txt

My cmdline looks like this - I could already see some useful information such as the framebuffer memory address and size, which will be useful later:

no_console_suspend=1 tegra_wdt.enable_on_probe=1 tegra_wdt.heartbeat=120 androidboot.hardware=flounder tegraid=40.0.0.00.00 vmalloc=256M video=tegrafb console=ttyS0,115200n8 earlyprintk MTS Version=33985182 memtype=0 tzram=4M@3962M ddr_die=1024M@2048M ddr_die=1024M@3072M section=128M lp0_vec=2048@0xf7fff000 tegra_fbmem=12713984@0xac001000 nvdumper_reserved=0xf7800000 core_edp_mv=1150 core_edp_ma=4000 gpt gpt_sector=69631 watchdog=disable nck=1048576@0xf7900000 androidboot.hardware=flounder buildvariant=userdebug androidboot.serialno=HT4B7JT02439 androidboot.baseband=N/A androidboot.mode=normal androidboot.bootloader=3.50.0.0143 swoff=128 flounder.wifimacaddr=B4:CE:F6:08:90:37 androidboot.wificountrycode=DE androidboot.bootreason=hard_reset hw_revision=128 radioflag=0x0 androidboot.misc_pagesize=2048

Boot image

Some bootloaders are picky where certain components such as the kernel and initramfs should be located within the boot image. postmarketOS maintains a tool called pmbootstrap, which amongst many other things, can analyse boot images to find the correct addresses for different sections.

$ pmbootstrap bootimg_analyze boot-downstream.img

deviceinfo_kernel_cmdline="androidboot.hardware=flounder"
deviceinfo_generate_bootimg="true"
deviceinfo_bootimg_qcdt="false"
deviceinfo_bootimg_mtk_mkimage="false"
deviceinfo_bootimg_dtb_second="false"
deviceinfo_flash_pagesize="2048"
deviceinfo_header_version="0"
deviceinfo_flash_offset_base="0x10000000"
deviceinfo_flash_offset_kernel="0x00008000"
deviceinfo_flash_offset_ramdisk="0x01000000"
deviceinfo_flash_offset_second="0x00f00000"
deviceinfo_flash_offset_tags="0x00000100"

This information can be used with mkbootimg to create a boot image that will work with this device.

Writing the mainline device tree

A basic device tree can be written with simple information such as the device compatible and memory address. For tegra132 a source for clk32_in is also needed, but for now I’ve just used a dummy fixed-clock which should be changed later.

// SPDX-License-Identifier: GPL-2.0
/dts-v1/;

#include "tegra132.dtsi"

/ {

	model = "Google Nexus 9";
	compatible = "google,flounder64", "nvidia,tegra132"; /* bootloader wants this */
	chassis = "tablet";

	memory@80000000 {
		device_type = "memory";
		reg = <0x0 0x80000000 0x0 0x80000000>;
	};

	clk32k_in: clock-32k {
		compatible = "fixed-clock";
		clock-frequency = <32768>;
		#clock-cells = <0>;
	};
};

After some testing I found out that I needed to use google,flounder64 as the compatible, like the downstream kernel, otherwise HBOOT (the bootloader) would not select it.

Framebuffer

I don’t have UART access, so I had to setup a framebuffer using the memory address provided by the bootloader to check if mainline is booting. The cmdline has this parameter: tegra_fbmem=12713984@0xac001000 which contains the memory address and size. This, along with the framebuffer size (screen size), is enough to make a simplefb node.

		framebufffer: framebuffer@ac001000 {
			compatible = "simple-framebuffer";
			reg = <0x0 0xac001000 0x0 0xc20000>;
			width = <1536>;
			height = <2048>;
			stride = <(1536 * 4)>;
			format = "a8b8g8r8";
		};

A reserved-memory node should also be added to make sure that the framebuffer memory is not overwitten by something else:

	reserved-memory {
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;
		framebuffer@ac001000 {
			reg = <0x0 0xac001000 0x0 0xc20000>;
			no-map;
		};
	};

I found out that the framebuffer turns into garbage after a while, which is due to the kernel automatically turning off unused clocks. This should be fixed by adding the correct clocks to the simplefb node, but for now I added the clk_ignore_unused bootarg.

Complete Device Tree

// SPDX-License-Identifier: GPL-2.0
/dts-v1/;

#include "tegra132.dtsi"

/ {

	model = "Google Nexus 9";
	compatible = "google,flounder64", "nvidia,tegra132"; /* bootloader wants this */
	chassis = "tablet";

	chosen {
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;

		bootargs = "clk_ignore_unused";
		framebufffer: framebuffer@ac001000 {
			compatible = "simple-framebuffer";
			reg = <0x0 0xac001000 0x0 0xc20000>;
			width = <1536>;
			height = <2048>;
			stride = <(1536 * 4)>;
			format = "a8b8g8r8";
		};
	};

	memory@80000000 {
		device_type = "memory";
		reg = <0x0 0x80000000 0x0 0x80000000>;
	};

	reserved-memory {
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;
		framebuffer@ac001000 {
			reg = <0x0 0xac001000 0x0 0xc20000>;
			no-map;
		};
	};

	clk32k_in: clock-32k {
		compatible = "fixed-clock";
		clock-frequency = <32768>;
		#clock-cells = <0>;
	};
};

Booting

The bootloader for this device requires that the DTB be appended to the kernel. This can be done with cat:

cat arch/arm64/boot/Image.gz arch/arm64/boot/dts/nvidia/tegra132-flounder.dtb > image-dtb

A boot image can be made with mkbootimg using the information found by analysing the stock boot image:

mkbootimg --kernel image-dtb -o boot.img --base 0x10000000 --kernel_offset 0x00008000 --ramdisk_offset 0x01000000 --second_offset 0x00f00000 --tags_offset 0x00000100

Now with the mainline boot image, all that’s left to do is test it:

fastboot boot boot.img

And it boots! The Linux boot logs print on the screen until it kernel panics because it can’t find a root file system.

Next steps

While seeing mainline Linux boot on the device is pretty cool, there needs to be a way for me to be able to run commands on it.

For this, I’ll need to:

  • Get USB working
  • Make a custom initramfs with busybox
  • Find out more information about this device and the Tegra architecture