Table of contents
Prerequisites for porting Heads
Prerequisites:
- coreboot port: since Heads is a coreboot payload, the board must have fully completed and actively maintained coreboot support. Any known issues should be acceptable to end users. Exceptions include the Purism and Dasharo coreboot forks, where coreboot is used from their respective forks.
- TPM module: Measured boot and the usable security features are at the core of what Heads is about. These features depend on the TPM. Therefore, the board must have a TPM module. If it uses fTPM and you plan to neuter the Intel Management Engine (ME), the fTPM must remain functional under coreboot even after neutering ME. Otherwise, a dTPM is required for this board.
- The final flash layout must have enough space for the Heads payload. Concretely, the size of the flash chip(s) must be at least be 12MB and the size of the BIOS region must be large enough to take the Heads payload. Usually, this means shrinking another region, the ME, and reallocating the freed space to the BIOS region. However, changing the ME blob or flash layout is not always possible as Intel Bootguard prevents booting with such modifications in most modern boards unless the board vendor chose otherwise.
- Technical skills: the person porting the board must have basic knowledge of coreboot, Linux, Bash, Git, and Python to complete the port. While the community is committed to helping, an alternative option is a financial contribution for consultancy services.
- External programmer: an external programmer is required to flash Heads and, if necessary, to recover from a brick.
- Users and Board-testers: There should be multiple users and testers interested in using the board, as this makes maintenance and testing easier and benefits the entire ecosystem. You may, of course, port it for yourself first and allow others to join later. However, you must commit to testing—especially after coreboot version bumps or Linux kernel updates. If testing is not done in a timely manner, the board will be moved to an “unmaintained, untested” status. This would be unfortunate, potentially a waste of time, and disappointing for everyone involved.
- GPU: dGPUs are problematic in Heads for various reasons. While successful ports on older machines with dGPUs existed (e.g. t530 and w530), security may be affected, and there is currently no clear solution for that. In case you are curious, please read a longer discussion on the Matrix coreboot channel starting with this post.
The scheme depicts a port cycle.
Files needed to be created/modified are depicted on the scheme.
Prerequisites for building the ROM
The goal is to build a ROM before the code review. NEW_BOARD
refers to the name of your board (e.g., t480
). It is recommended to clone the Heads GitHub repository and create a new branch:
git clone https://github.com/linuxboot/heads.git
git checkout -b Poc_NEW_BOARD
modules/coreboot:
coreboot fork that will be used should be added under heads/modules/coreboot
. This will be the version you reference from the board config files. You should aim for building the board with an existing coreboot version, usually the main version that most boards use. If you need a different coreboot version, consider patching an existing version without breaking any other boards depending on it, of course. However, if you need to add a new coreboot fork, it should be done here. This is not ideal and not recommended, as there are already 2 different coreboot forks. It often leads to maintenance and resources burden.
binary blobs:
Check the coreboot configuration for the ported board. This will indicate which binary blobs are required and where they are expected to be located. Heads expects architecture-/board-specific scripts under blobs/*
which do the magic, called by the main makefile that handles everything in Heads. These scripts must create reproducible binary blobs when invoked. The blobs should be placed in the blobs/NEW_BOARD/
. Make sure the coreboot config file references the correct path for each blob and the blobs are added to .gitignore
so that they are not accidentally committed to git. Generally, three binary blobs are required: Management Engine (ME), Intel Flash Descriptor Region (IFD), and Gigabit Ethernet (GBE). The IFD and GBE can be extracted from a donor board using coreboot’s ifdtool. For more details, refer to the upstream documentation. On some boards it may be necessary to provide more blobs to coreboot, for instance an MRC blob for RAM initialization if coreboot cannot distribute the blob itself for legal reasons.
Please note, if the ME is neutered, the IFD, coreboot CBFS region, and ME neutering space should be adjusted accordingly. Rationale: the ME region defined under IFD must fit. With the IFD ME region reduced, the BIOS region can grow with the freed ME space. With the BIOS region augmented, the CBFS region of the coreboot configuration must be increased to fit the “maximized” space.
Please note the GBE MAC address should be forged to: 00:DE:AD:C0:FF:EE MAC
. It can be done with nvmutil. Due to licensing restrictions, the ME firmware cannot be uploaded to the GitHub. However, scripts can be used to build it locally and within CircleCI (a gray area legally, but still possible). GBE and IFD blobs can be uploaded directly to GitHub. Please explain how they were obtained in the commit message.
- Note: When calling scripts in Nix-based environments, Python must be invoked explicitly, as Nix does not allow executing Python scripts directly from files. One can use last clean example for t480:
python ./finalimage.py
will work and just./finalimage.py
will not work. The blobs folder should have a script.sh which handles downloading, deactivating ME etc. It should also contain a README.md file briefly explaining the process. Hashes of the blobs should be stored either inREADME.md
or inhashes.txt file
. Furthermore, the script must ensure the integrity of the blobs it produced by comparing a SHA256 hash.
NEW_BOARD.mk:
Create a new heads/targets/NEW_BOARD.mk
file which deals with calling blobs/script.sh*, download and extraction, and placing the blobs in correct location. This will be used by the main makefile and defines the board specific targets and dependencies, such as the binary blobs. For additional details, please see Makefiles.
board.config:
There should be a file NEW_BOARD-hotp-maximized.config
, which inherits all the parameters from NEW_BOARD-maximized.config
and adds HOTP verification. Those files should be located in the folder heads/boards/NEW_BOARD-hotp-maximized
and heads/boards/NEW_BOARD-maximized
, respectively. This is Heads specific configuration and should be adapted from a similar platform. The top of the board config files is also a good place to put some technical board-specific documentation, known issues etc. You should point in NEW_BOARD-hotp-maximized
and NEW_BOARD-maximized
to the coreboot version e.g. export CONFIG_COREBOOT_VERSION=X.Y.Z.
- that was modified under modules/coreboot
(see corresponding section above). Additionally, the configurations should reference the appropriate coreboot and linux configs created below:
CONFIG_COREBOOT_CONFIG=config/coreboot-NEW_BOARD.config
CONFIG_LINUX_CONFIG=config/linux-NEW_BOARD.config
- For early testing, enabling debug mode is a good idea. However, these options should be removed before merging:
export CONFIG_DEBUG_OUTPUT=y export CONFIG_ENABLE_FUNCTION_TRACING_OUTPUT=y
Note: This may cause glitches where the screen output appears overwritten until an arrow key is pressed. This is normal for DEBUG mode, as the additional tracing output uses the same console as fbwhiptail. The issue will disappear once debug options are removed from the board configs.
- It is important to define the correct TPM configuration (TPM 1.2 vs. TPM 2.0), ensuring they are mutually exclusive, similar to the coreboot configuration (described in coreboot config below). The selected option misses
#
:#TPM2 requirements export CONFIG_TPM2_TOOLS=y export CONFIG_PRIMARY_KEY_TYPE=ecc
#TPM1 requirements #export CONFIG_TPM=y
In the configuration, the binary blobs must be specified as build targets, as coreboot depends on these blobs. It is the line at the bottom of the board config
BOARD_TARGETS := NEW_BOARD
. It ensures that the main Heads makefile builds the targets specified in theNEW_BOARD.mk
file.
coreboot.config:
You need a coreboot configuration for the new board. Ideally, this config should be adapted from the closest possible platform. You can inspect existing configurations for boards in the master branch and select one with a similar architecture, if available. Next, you can create a coreboot config by copying it from the closest platform and adapt it accordingly. You may use coreboot’s make menuconfig
for NEW_BOARD
:
cp config/coreboot-CLOSEST_PLATFORM.config config/coreboot-NEW_BOARD.config
./docker_repro.sh make coreboot.modify_and_save_oldconfig_in_place
Note, the configuration needs to define the correct path references to all binary blobs, that are not provided by coreboot, on most architectures this includes at least IFD
, ME
and GBE
(see above). The configuration file path should be: heads/config/coreboot-NEW_BOARD.config
.
- Note: TPM measured boot (
CONFIG_TPM_MEASURED_BOOT=y
) and verified boot (CONFIG_VBOOT_LIB=y
) should be enabled. Ensure that you select CONFIG_TPM_MEASURED_BOOT and CONFIG_VBOOT_LIB in Kconfig. Furthermore, enable the TPM and pick the correct TPM version for the board. Other parameters depend on the board. It is up to you to determine the correct settings, as not all community members will have access to your board.git diff
betweencoreboot-CLOSEST_PLATFORM.config
andcoreboot-NEW_BOARD.config
may help.
linux.config:
This should be adapted from a similar board. The file path should be heads/config/linux-NEW_BOARD.config
.
CircleCI:
Modify heads/.circleci/config.yml
to add support for the NEW_BOARD
. Initially, configure it to depend directly on musl-cross-make. At this stage, do not reuse caches—this simplifies debugging and ensures a clean build process.
# NEW_BOARD is based on `X.Y.Z`. coreboot release, not sharing any buildstack from now, depend on muscl-cross cache
- build:
name: NEW_BOARD-hotp-maximized
target: NEW_BOARD-hotp-maximized
subcommand: ""
requires:
- x86-musl-cross-make
CircleCI creates reproducible builds, allowing users to verify that the “flashable” roms were indeed produced by the given source code. In the development/debugging phase, it helps to ensure that you talk about the same build. It optimizes the collaboration between peers-board owners. Moreover, CI builds are significantly faster than local builds, reducing overall development time. As circleCI always builds all boards it makes it easier to find regressions early.
- Optional: local builds. If you need to build locally (e.g. to ensure that some features work) pay attention to the helper functions at the end of the Makefiles. It is strongly recommended that local builders review the end of the Makefiles (including modules/*files), as these helper functions were designed to facilitate coreboot and Linux version bumps. For a completely clean build (the most radical approach), either clone the Heads repository again or remove all build artifacts using:
./docker_repro.sh BOARD=NEW_BOARD real.clean
Building all tools needed will take a while depending on the number of cores and RAM available on your machine. For a less radical approach, run:
./docker_repro.sh BOARD=NEW_BOARD real.remove_canary_files-extract_patch_rebuild_what_changed
Then, inject any changes into the coreboot fork canary file so that the build system refetches, repatches, and rebuilds the coreboot fork:
echo "bogus" | sudo tee build/x86/coreboot-t480/.canary
Note, patches that attempt to create files that are not expected to exist but exist will fail, showing at console what files already existed that couldn’t be created. In this case, you need to remove them manually
rm -rf
, and restart the build with./docker_repro.sh BOARD=NEW_BOARD
which will progress until each modules/* required per board config is successfully built.
Testing
For thorough testing—especially for a new board—using the following template (tasks to check) may be beneficial. Copy and paste this template into the GitHub message window when reporting test results. Mark completed tasks with an x
inside the brackets [ ]. Replace X.Y.Z
with the relevant information. It is recommended also to upload relevant screenshots For additional safety, you may wish to remove the metadata from these files.
- [ ] Successful external flash link to circleci: https://X.Y.Z. from commit `X.Y.Z.` using external programmer model `X.Y.Z.` on `X.Y.Z.` Voltage mode
- [ ] Boots successfully after the flashing:
- [ ] Setting clock prompt on first reboot: ok if triggered correctly after initial flashing and CMOS battery disconnected
- [ ] Clean boot detected (no keyring, nothing installed on disk): usb boot proposed and followed
- [ ] Boots on usb
- [ ] OS `X.Y.Z.` install and reboot
- [ ] Heads functionality- no pubkey detected, but OS detected -> OEM-Factory-reset proposed. Done with `X.Y.Z.` hardwarekey e.g. nk3
- [ ] On reboot after re-ownership: generate new HOTP/TOTP
- [ ] wifi works based on OS `X.Y.Z.`
- [ ] PR0
- [ ] flashprog -p internal (not locked)
- [ ] lock_chip (locks the platform with PR0, if PR0 patch applied in fork or under `patches/coreboot-X.Y.Z.` and coreboot config contain proper preparation of the platform)
- [ ] flashprog -p internal (reports locked)
Debugging
Future debugging from Heads: enter recovery console, with a formatted USB thumb drive (fat32/exfat/ext3/ext4) and then
mount-usb --mode rw
cp /tmp/debug.log /media
cbmem -L > /media/coreboot_measured_boot.txt
cbmem -1 > /media/coreboot_cbmem_console.txt
cbmem -t > /media/coreboot_cbmem_stages_timestamps.txt
umount /media
And then provide log files in a subsequent comment. That would be really helpful if something is still wrong. Of course, this should be modified based on the problem faced.
Pull Request (PR)
Create an initial PR, with commits relative roughly to the steps above. 1 commit should correspond to 1 major bullet point. Note that Heads is all about reproducibility. Therefore, your commit messages should explain why you made the change and how you got there. For instance, explain how to create a certain binary blob or better write a script that can be run by everyone.
Then lets collaborate in the PR. Create a heads fork in your own repository, follow the contribution guide as you go, make sure you sign and sign-off commits and don’t stress too much if this seems a lot. Get a PR started and we will collaborate there.
Remove debugging mode:
One of last commits should remove the debugging mode, functions tracing and use quiet mode instead.
Write a flashing/disassembling guide
Take pictures as you disassemble your board and flash the Heads ROM. You can use them later to write a guide. This will help less technical community members using the board. Your effort will improve the ecosystem. If you cannot manage to finish writing the guide but talented enough to finish the port just create an issue on the GitHub and drop pictures. We will try to find time to help. The guide is essential part of the port.
Contribute to maintenance
Refer to Contributing on GitHub page
Heads is free as free beer. In the spirit of open-source software, free knowledge, and communal goodwill, support the Heads and the open source development Insurgo Initiative - Open Collective. The supply of free beer cannot be infinite. If everyone takes without giving back, at some point, the keg runs dry, and so does the goodwill. If you enjoy free beer, contribute in some form—whether by bringing code, docs, or financial support. The cycle must be maintained to keep the ecosystem alive. No one wants to be the last guest realizing the fridge is empty and no one restocked it. Together, it is possible to keep the free beer spirit alive—open, shared, and always refreshing. Cheers!
You may examine all github PRs with the label ‘port’ to gather additional information. Good luck!