Custom Firmware
The ChromeOS firmware is always verified as signed by Google. Developer mode allows you to safely fiddle with the disk and operating system, but the boot process depends on unmodified firmware. There is no provision for modifying the firmware without physically taking the device apart. Modifying your firmware can result in permanent damage.
This page talks about how to use your own custom firmware without damaging the official firmware on your device, by pretending that your custom firmware is a custom kernel.
Firmware as kernel
The normal-mode boot process is roughly this: RO firmware verifies and launches the RW firmware, RW firmware verifies and launches the kernel, the kernel verifies the rootfs as it's read from the disk.
In developer mode, the RW firmware doesn't verify that the kernel is signed by Google, just that it looks like a correctly signed kernel image. It doesn't matter who signed it, so you can build and boot your own kernel and rootfs. On later models, you can also enable booting a custom ChromiumOS kernel from USB.
The ChromeOS firmware doesn't actually look at the kernel, it just looks for a ChromeOS-specific header that describes it. So if your custom kernel isn't actually a Linux kernel, but is a custom build of U-Boot that was packaged as though it was a ChromiumOS kernel, the firmware will happily load it into RAM and jump to it. What happens next is up to you.
Warning
This has been known to work (more or less), but it's pretty annoying and difficult to debug. Most of the time when something goes wrong, you just see the scary boot screen, you press Ctrl-D or Ctrl-U to boot your magic image, and ... the machine silently hangs or reboots. Sometimes it reboots into recovery mode but power cycling restores it. Sometimes you have to run the recovery process to restore it. You shouldn't be able to damage anything, but you can spend a lot of time trying to get it right.
That said, the general process is fairly straightforward. Of course, it helps a lot if you've built and booted your own ChromiumOS image at least once so that you're familiar with the development environment.
Google pushes frequent updates to the running ChromeOS system so that over time it just gets better. However, since the initial portion of the BIOS is read-only, only the read-write part of the BIOS can be updated and the requirement that it interoperate with the RW portion limits the amount of change that can be applied. In addition, updating BIOS can be a scary proposition because uncaught bugs could require users to run the recovery process. That means that significant changes to the BIOS are only made by introducing new ChromeOS devices. As of this writing, there are three major BIOS designs in existence, and each type loads and runs the kernel in different ways.
H2C
The first generation of Chromebooks (Cr-48, Alex, ZGB) uses a BIOS called "H2C".
- H2C is UEFI-based, but with a lot of stuff cut out to speed up the boot process.
- The BIOS is a 64-bit executable, but the ChromeOS kernel is launched in 32-bit mode. Our kernel has to be specially modified to handle this difference.
- The "bootloader stub" is a standard UEFI executable, with source
in
src/third_party/bootstub/.
The purpose of this tiny executable is to
- Update the kernel cmdline to let the kernel find the correct rootfs partition.
- Obtain the memory map from the BIOS that the kernel will need.
- Switch CPU modes from 64-bit to 32-bit.
- Jump to the kernel entry point at 0x100000.
- You can replace the bootloader stub with any other UEFI
application (such as grub2), but it will be broken in two ways:
- The BIOS' console output functions are disabled, so you can't see any output.
- None of the UEFI-provided disk handles are valid, so you can't do any disk IO.
Coreboot
The second generation x86 Chromebooks (Stumpy, Lumpy, Parrot) firmware is a combination of Coreboot and U-Boot.
- Coreboot initializes the bare hardware, then invokes a "payload".
- The payload is U-Boot, configured and customized to boot using our verified boot process without delay.
- Coreboot and U-Boot are both 32-bit executables, as is the kernel entry code (even for 64-bit kernels).
- U-Boot updates the kernel cmdline and zeropage table, before jumping directly to 0x100000. Any "bootstub" code is ignored.
The two nice things about the coreboot BIOS are:
- U-Boot can decide at boot time to enable the console output if it needs to.
- The handoff from BIOS to kernel is a simple 32-bit jump. No trampoline, no reliance on run-time-resident BIOS functions.
U-Boot
The ARM-based Samsung Chromebook (Snow) uses U-Boot alone.
- The read-only firmware consists of three parts:
- BL1/PRE_BOOT is the chip-specific bootloader provided by Samsung.
- BL2/SPL is a very tiny board-specific bootloader derived from U-Boot.
- The RO U-Boot then handles the verification and loading of the RW firmware.
- The read-write firmware is also U-Boot.
- U-Boot passes all the kernel parameters through FDT.
- The kernel is loaded in RAM at a different location from x86.
- The kernel is wrapped as a FIT image, not a raw binary.
Depthcharge
The newest Chromebooks (Haswell and beyond) now use Depthcharge, which is deployed as a payload from Coreboot effectively replacing U-Boot entirely. Depthcharge removes reliance on U-Boot and allows for a lighter boot process with on demand initialization of devices. There is a presentation on Depthcharge available here.
Producing a "kernel" to boot
Step one is to build your custom bootloader.
On x86 coreboot if you're modifying the ChromeOS U-Boot, that may be as simple
as changing CONFIG_SYS_TEXT_BASE
to the expected kernel load address and
running emerge chromeos-u-boot
(cros_workon
first, of course). You'll
probably need to tweak a few other things to enable the vga output or provide an
interactive prompt.
For an ARM Chromebook, you can find detailed instructions on the Using nv-U-Boot on the Samsung ARM Chromebook page.
Step two is to sign your binary like a ChromiumOS kernel:
echo blah > config.txt
futility sign \
--config config.txt \
--arch arm \
--vmlinuz ${MY_BINARY} \
--outfile kernelpart.bin
KPART=$(pwd)/kernelpart.bin
Notes:
- Your binary is specified with the
--vmlinuz
argument. - We use a
config.txt
file for the config file arg, so thatfutility sign
won't complain. - We specify
--arch arm
even if we're building for x86, because x86 kernels have a preamble thatfutility
will try to remove and U-Boot doesn't have that.
Step three is to copy that image to the chromebook and try it out.
chronos# cd /mnt/stateful_partition/ chronos# scp YOU@SOMEWHERE:/PATH/TO/kernelpart.bin .
Install it into partition 4 (assuming we've booted from partition 2):
chronos# dd if=kernelpart.bin of=/dev/sda4 chronos# cgpt add -i 4 -P 9 -T 1 -S 0 /dev/sda
Note that I'm making partition 4 a high priority, but marking it as unsuccessful and with a tries count of 1. That way, when you reboot, you'll go back to your previous (working) ChromeOS kernel in partition 2.
Reboot and if all goes well, your code should run.
An alternate here is to enable USB boot (crossystem dev_boot_usb=1
) and then
boot from an SD card / USB key that you built/burned with cros build-image
/
image_to_usb. In that case you can just put your ${KPART}
straight into
partition 2 and boot it with Ctrl-U.
Known issues
There probably are some. Even within a single type of firmware, each Chromebook has a slightly different version. Testing is done and bugs are fixed right up until final manufacturing, and development continues on the top of tree constantly. The standard Linux kernel doesn't generally trust the BIOS very much, so it often does a lot of its own hardware initialization even on Chrome OS. That means that some firmware bugs may exist that are masked or cleaned up by the kernel, where they won't be exposed by testing. Plus, there can be dependencies between the RO firmware (Coreboot, especially) and the RW firmware that a custom bootloader may not be aware of.
I'm not afraid of damaging things
Well, okay then. Here's a presentation from OSCON 2013 on how to build and install your own firmware, including the read-only parts. Good luck.