book

LinuxBoot using coreboot, u-root and systemboot

Points of contact: Andrea Barberio, David Hendricks

This chapter describes how to build a LinuxBoot firmware based on coreboot, u-root and systemboot. The examples will focus on x86_64, and the coreboot builds will cover virtual and physical OCP hardware.

Quick Start with coreboot

Run these commands in a directory you create or in /tmp; do so because it creates some files and directories:

$ go get github.com/linuxboot/corebootnerf
$ go run github.com/linuxboot/corebootnerf --fetch
... lots and lots of output!

This produces a coreboot image in coreboot-4.9/build/coreboot.rom You can now run this rom image:

$  qemu-system-x86_64 -serial stdio -bios coreboot-4.9/build/coreboot.rom

And see how it looks when you put this in a coreboot ROM image.

Components

The final image is built on top of multiple open-source components:

These components are built in reverse order. u-root and systemboot are built together in a single step.

Building u-root

The first step is building the initramfs. This is done using the u-root ramfs builder, with additional tools and libraries from systemboot.

u-root is written in Go. We recommend using a relatively recent version of the Go toolchain. At the time of writing the latest is 1.11, and we recommend using at least version 1.10. Previous versions may not be fully supported.

Adjust your PATH to include ${GOPATH}/bin, in order to find the u-root command that we will use in the next steps.

Then, fetch u-root and its dependencies:

go get -u github.com/u-root/u-root

Then build the ramfs in busybox mode, and add fbnetboot, localboot, and a custom uinit to wrap everything together:

u-root -build=bb core github.com/u-root/u-root/cmds/boot/{uinit,localboot,fbnetboot}

This command will generate a ramfs named /tmp/initramfs_${os}_${arch}.cpio, e.g. /tmp/initramfs.linux_amd64.cpio. You can specify an alternative output path with -o. Run u-root -h for additional command line parameters.

Note: the above command will include only pure-Go commands from u-root. If you need to include other files or non-Go binaries, use the -file option in u-root. For example, you may want to include static builds of kexec or flashrom, that we build on https://github.com/systemboot/binaries .

Then, the initramfs has to be compressed. This step is necessary to embed the initramfs in the kernel as explained below, in order to maintain the image size smaller. Linux has a limited XZ compressor, so the compression requires specific options:

xz --check=crc32 --lzma2=dict=512KiB /tmp/initramfs.linux_amd64.cpio

which will produce the file /tmp/initramfs.linux_amd64.cpio.xz.

The kernel compression requirements are documented under Documentation/xz.txt (last checked 2018-12-03) in the kernel docs.

Building a suitable Linux kernel

A sample config to use with Qemu can be downloaded here: linux-4.19.6-linuxboot.config.

You need a relatively recent kernel. Ideally a kernel 4.16, to have support for VPD variables, but a 4.11 can do the job too, if you don’t care about boot entries and want “brute-force” booting only.

We will build a kernel with the following properties:

Download kernel sources

You can either download a tarball from kernel.org, or get it via git and use a version tag. We recommend at least a kernel 4.16, in order to have VPD variables support.

# download the kernel tarball. Replace 4.19.6` with whatever kernel version you want
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.19.6.tar.xz
tar xvJf linux-4.19.6.tar.xz
cd linux-4.19.6
make tinyconfig

You can also check out the linux-stable branch, that will point to the latest stable commit. You need to download it via git as follows:

git clone --depth 1 -b linux-stable
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
cd linux-stable
make tinyconfig

Some more information about tiny configs can be found at https://tiny.wiki.kernel.org (last checked 2018-12-01).

A few fundamental features

Assuming we are running on x86_64, some basic features to enable are:

Requirements for Go 1.11

Go requires a few kernel features to work properly. At the time of writing, you need to enable CONFIG_FUTEX in your kernel config. Older versions of Go may require CONFIG_EPOLL.

In menuconfig:

Additional information about Go’s minimum requirements can be found at https://github.com/golang/go/wiki/MinimumRequirements (last checked 2018-12-01).

Enable devtmpfs

Our system firmware uses u-root, which does not have (intentionally) an udev equivalent. Therefore, to have /dev/ automatically populated at boot time you should enable devtmps.

Simply enable CONFIG_DEVTMPFS and CONFIG_DEVTMPFS_MOUNT in your kernel config

In menuconfig:

Additional drivers

This really depends on your hardware. You may want to add all the relevant drivers for the platforms you plan to run LinuxBoot on. For example you may need to include NIC drivers, file system drivers, and any other device that you need at boot time.

For example, enable SCSI disk, SATA drivers, EXT4, and e1000 NIC driver. In menuconfig:

Enable XZ kernel and initramfs compression support

The u-root-based RAMFS will be compressed with XZ and embedded in the kernel. Hence you need to enable XZ compression support. Make sure to have at least CONFIG_HAVE_KERNEL_XZ, CONFIG_KERNEL_XZ, CONFIG_DECOMPRESS_XZ.

In menuconfig:

Enable VPD

VPD stands for Vital Product Data. We use VPD to store boot configuration for localboot and fbnetboot, similarly to UEFI’s boot variables. Linux supports VPD out of the box, but you need at least a kernel 4.16.

Make sure to have CONFIG_GOOGLE_VPD enabled in your kernel config.

In menuconfig:

TPM support

This also depends on your needs. If you plan to use TPM, and this is supported by your platform, make sure to enable CONFIG_TCG_TPM.

In menuconfig:

Include the initramfs

As mentioned above, the kernel will embed the compressed initramfs image. Your kernel configuration should point to the appropriate file using the CONFIG_INITRAMFS_SOURCE directive. E.g.

CONFIG_INITRAMFS_SOURCE="/path/to/initramfs_linux.x86_64.cpio.xz"`

In menuconfig:

Default hostname

We use “linuxboot” as default hostname. You may want to adjust it to a different value, You need to set CONFIG_DEFAULT_HOSTNAME for the purpose, e.g.

CONFIG_DEFAULT_HOSTNAME="linuxboot"

In menuconfig:

Build the kernel

Once your configuration is ready, build the kernel as usual:

make -j$(nproc --ignore=1)

The image will be located under arch/${ARCH}/boot/bzImage if your architecture supports bzImage (e.g. x86).

For more details on how to build a kernel, see https://kernelnewbies.org/KernelBuild (last checked 2018-12-01).

Building coreboot

In this step we will build coreboot using the Linux kernel image that we built at the previous step as payload. This build is for a Qemu x86 target, the process may be somehow different for other platforms.

Steps overview:

Download coreboot

Our preferred method is to download coreboot from the git repository:

git clone https://review.coreboot.org/coreboot.git
cd coreboot

Build the compiler toolchain

This step is required to have, among other things, reproducible builds, and a compiler toolchain that is known to work with coreboot.

make crossgcc-i386 CPUS=$(nproc) BUILD_LANGUAGES=c

The step above may ask you to install a few additional libraries or headers, do so as requested, with the exception of gcc-gnat, that we won’t need.

Configure coreboot for Qemu and our payload

Run make menuconfig to enter the coreboot configuration menus. Then:

Specify the platform we will run on:

Specify a large enough flash chip and CBFS size:

Specify our payload:

Then save your configuration and exit menuconfig.

Build coreboot

This is done with a simple

make -j$(nproc)

The coreboot build system will clone the relevant submodules, if it was not done already, and will build a coreboot ROM file that will contain the initialization code, and our bzImage payload. The output file is at build/coreboot.rom.

If everything works correctly you will get an output similar to the following:

This image contains the following sections that can be manipulated with this tool:

'COREBOOT' (CBFS, size 16776704, offset 512)

It is possible to perform either the write action or the CBFS add/remove actions on every section listed above.
To see the image's read-only sections as well, rerun with the -w option.
    CBFSPRINT  coreboot.rom

FMAP REGION: COREBOOT
Name                           Offset     Type           Size   Comp
cbfs master header             0x0        cbfs header        32 none
fallback/romstage              0x80       stage           15300 none
fallback/ramstage              0x3cc0     stage           51805 none
config                         0x10780    raw               155 none
revision                       0x10880    raw               576 none
cmos_layout.bin                0x10b00    cmos_layout       548 none
fallback/dsdt.aml              0x10d80    raw              6952 none
fallback/payload               0x12900    simple elf    5883908 none
(empty)                        0x5af140   null         10800216 none
bootblock                      0xffbdc0   bootblock       16384 none

Built emulation/qemu-q35 (QEMU x86 q35/ich9)

Putting everything together

TODO

Defining boot entries

TODO

Running on a virtual machine

The image built with the above steps can run on a QEMU virtual machine, using the machine type q35, as specified in the coreboot mainboard section. Assuming that your coreboot image is located at build/coreboot.rom, you can run the following command:

sudo qemu-system-x86_64\        # sudo is required to enable KVM below
    -M q35 \                    # the machine type specified in the coreboot mainboard configuration
    -enable-kvm \               # use KVM to avail of hardware virtualization extensions
    -bios build/coreboot.rom \  # the coreboot ROM to run as system firmware
    -m 1024 \                   # the amount of RAM in MB
    -object rng-random,filename=/dev/urandom,id=rng0 \
                                # RNG to avoid DHCP lockups when waiting for entropy
    -nographic                  # redirect all the output to the console

If everything has been done correctly you should see, in order, the output from coreboot, linux, u-root, and systemboot. You can press ctrl-c when Systemboot instructs you to do so, to enter the u-root shell.

Running on real OCP hardware

TODO