If you work on ChromeOS kernel you need to use chroot environment for development. This is kind of virtual machine where you have need development packages installed.
But today we’ll try to make a kernel development environment that does not use chroot. Why? There are several reasons: chroot is a large complex setup that requires a lot of sources to download and build, it is more complicated and slower than “normal” kernel development where you just need make. I also wanted to learn how ChromeOS kernel is built/installed to target machine and writing such script is a great exercise.
Note that you still need chroot if you are going to create a recovery USB image. This image contains full ChromeOS distribution that includes kernel and all required userspace tool. You need this image to boot and setup target machine environment the first time. Make sure that you built image with development options enabled. I usually build recovery image with following flags:
./build_image –noenable_rootfs_verification test –enable_serial=ttyS0
In this article I’ll describe how I setup simple chroot-less development environment for ChromeOS kernel on my Linux Arch machine. I am going to build kernel for arm64 target platform. If you have 32bit arm or x86 platform you need to modify instructions accordingly.
First you need cross-toolchain for your board. I use an arm64 board and I need aarch64 toolchain. Arch has it in AUR repository. Install the toolchain with
yaourt -S aarch64-linux-gnu-gcc aarch64-linux-gnu-binutils aarch64-linux-gnu-gdb
It will take a while, so take your coffee break.
In addition to the toolchain you need to install other chromeos development tools that we will be using:
yaourt -S hdctools-git dtc vboot-utils uboot-tools bc
Now let’s compile our kernel. First you need to create kernel .config file that tells what features/drivers we want to compile:
yes ” | make $ARCH_FLAGS oldconfig
then compile it:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
You’ll get vmlinux binary. Now we need to create u-boot format image that contains vmlnux plus device tree information. Device tree (dts) is hardware configuration description that tells kernel what hardware chips present on the board, how these chips connected and what drivers should be used.
mkimage -D ‘-I dts -O dtb -p 1024’ -f kernel.its kernel.img
kernel.its is file tells where to find dts files. Later I’ll show you an example of such file.
The image may contain multiple device tree configurations and thus can be booted on multiple board that have similar architecture. Board firmware (in our case it is coreboot) knows what device tree should be used to configure the kernel.
The next step is to create signed ChromeOS kernel binary. It requires several additional files:
futility vbutil_kernel –pack kernel.bin \
–keyblock kernel.keyblock \
–signprivate kernel_data_key.vbprivk \
–version 1 \
–config config.txt \
–bootloader bootloader.bin \
–vmlinuz kernel.img \
Finally you have kernel.bin that we are going to write to target machine flash chip.
The flash chip on my device is /dev/mmcblk0. To examine the ChromeOS partition table run cgpt show /dev/mmcblk0 . You’ll see that there are three pairs of ChromeOS kernel/rootfs partitions. It is very handy to have several kernel/rootfs partitions for development. I usually keep KERN-A/ROOT-A pristine and deploy my changes to KERN-B/ROOT-B. Sometime my development kernel does not work as expected or crashes, then coreboot will reboot into known-good kernel (KERN-A).
It is interesting to learn how coreboot chooses partition to boot from. Each GPT partition contains additional flags used by coreboot: “successful”, “tries”, “priority”. Coreboot scans all partitions on all storage devices, selects all partitions with data that looks like valid kernel, then chooses one with the largest priority value and boots it. Partition must have either “successful” or “tries” field non-zero. If “successful” was zero then coreboot decrements “tries” and writes back to storage. And we use this feature to boot our development kernel. We set KERN-B partition “priority” to a large value (e.g. 15), “successful” flag to zero and “tries” to one.
cgpt add -i 4 -S 0 -T 1 -P 15 /dev/mmcblk0
Thus coreboot will load KERN-B once, update “tries” to zero. If my development kernel crashes or if I simply reboot target machine then stable KERN-A will be used. Isn’t it smart? Here is the code that chooses the partition (LoadKernel() function).
Kernel boot options contain “root=PARTUUID=%U/PARTNROFF=1” that says root partition is right next after kernel partition. If kernel is /dev/mmcblk0p2 then root is /dev/mmcblk0p3 and so on.
The last thing to do is to copy kernel modules to the target machine. If you have kernel and modules compiled with different gcc versions then you might have ABI incompatibility and your kernel will crash when tries to load a module. We “install” kernel modules at the local machine, mount remote /dev/mmcblk0p5 (ROOT-B) and then rsync to remote target
make INSTALL_MOD_PATH=~/tmp/mytemproot ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules_install firmware_install
Reboot your target development board and it will boot into your custom kernel.
Here is another thing that makes our life easier – password-less access to the board. We are going to use testing ssh keys that our development image contains. Download keys from ChromeOS repository and then configure ssh client with something like this
Where devboard1 is hostname that I use for my arm64 board. I configured my router to match board MAC address to ‘devboard1’. See ‘man ssh_config’ for more details about ssh client configuration. After configuring it I can ssh to my board as ‘ssh devboard1’ and no password is required. It is very convenient.
Here is ready shell script that does everything we described above – builds kernel and deploys to a remove target machine, sets GPT flags, … The script expects following directory structure
* * kernel.keyblock
* * kernel_data_key.vbprivk
* * config.txt
* * kernel.its
Then run update_kernel.sh $TARGET_MACHINE