267 lines
11 KiB
Markdown
267 lines
11 KiB
Markdown
Rebuilding coreboot image generation
|
||
====================================
|
||
|
||
Current situation
|
||
-----------------
|
||
Chrome OS (CrOS) probably has the most complex image bundling process in the
|
||
coreboot ecosystem. To make CrOS features more accessible to the wider
|
||
coreboot community, we want to move these capabilities into upstream
|
||
coreboot’s build system.
|
||
|
||
Right now, the CrOS build system creates coreboot images, and various
|
||
instances of the payload (with different configuration options), plus some
|
||
more files (eg. EC firmware), then passes them to a CrOS-specific utility
|
||
(`bundle_firmware.py`) to build the final image from that.
|
||
|
||
`bundle_firmware` adds a flashmap (fmap) to the final image and creates
|
||
additional CBFS filesystems in fmap regions. It then extracts some files from
|
||
the original CBFS region (that was put in place carefully to later match to
|
||
the default fmap region) and copies some of them into the others, as well as
|
||
putting more data (eg. the bitmap data, keys) as raw data into other fmap
|
||
regions.
|
||
|
||
With the recent addition of more files to CBFS, both on the coreboot side
|
||
(dsdt, FSP, and so on) and with Chrome OS specifics (eg. more files describing
|
||
boot screens) we either need to expand the scope of bundle\_firmware or move
|
||
the capability to build complex images to upstream coreboot’s build system.
|
||
This document proposes to do the latter and outlines how this could be
|
||
achieved.
|
||
|
||
Problems with the current build system parts
|
||
--------------------------------------------
|
||
One common sentiment is that it should be possible to reuse some of the
|
||
existing mechanisms that are supposed to be supplanted by this.
|
||
The main concern during this design that precluded their use was that none of
|
||
them provides a comprehensive solution to building complex coreboot based
|
||
images:
|
||
* fmap.dts and fmd provide a flash layout, but no assignment of files of regions
|
||
* cbfs-files-y ends up as an internal make variable using
|
||
`weird|formatting|to|deal|with|make’s|limitations`
|
||
* make isn’t powerful enough to deal with ordering these entries in said
|
||
variable to guarantee success if there’s enough room for the files. While that
|
||
could be added, that becomes more make macro work indistinguishable from magic
|
||
that people fail to understand, break and with good reason complain about
|
||
to work around such issues, Chrome OS firmware uses a custom tool with even
|
||
more special cases to finally build the image it needs. If coreboot upstream
|
||
is to support vboot, it should also be powerful enough not to need magic tools
|
||
that only live within downstream projects.
|
||
|
||
Requirements
|
||
------------
|
||
A complete Chrome OS coreboot image consists of (depending on the device)
|
||
* platform specific data in raw fmap regions (eg IFD, ME firmware),
|
||
* the bootblock (coming from the bootblock),
|
||
* three copies of coreboot, consisting of the stages (verstage, romstage,
|
||
ramstage) plus data,
|
||
* depthcharge plus data (with each of the coreboot copies),
|
||
* EC firmware files (with each of the coreboot copies),
|
||
* signatures over several parts of the image and
|
||
* some final checksumming over parts of the image to satisfy boot ROM
|
||
tests on ARM
|
||
|
||
A complete upstream coreboot image (with fallback/normal switch configuration,
|
||
using a yet to be implemented switching scheme based on fmaps) consists of
|
||
* platform specific data in raw fmap regions (eg IFD, ME firmware),
|
||
* two copies of coreboot, consisting of
|
||
* the bootblock and
|
||
* the stages (romstage, ramstage) plus data,
|
||
* payload plus data (with each of the coreboot copies),
|
||
|
||
Since a single platform is potentially built with different payload
|
||
configurations (eg. modding a Chromebook to not use the verified Chrome OS
|
||
boot scheme), some concerns need to be kept separate:
|
||
* Platform requirements that have nothing to do with the payload or boot schemes
|
||
* IFD, ME, … need to copied to the right place
|
||
* boot ROM requirements such as checksums must be honored
|
||
* Payload/boot scheme requirements
|
||
* Having one to three regions with certain files copied into them
|
||
|
||
Proposal
|
||
--------
|
||
The proposal is based on manifest files that describe certain aspects of the
|
||
final image.
|
||
The number of manifest files may change over time, but this seems to be a
|
||
reasonable approach for now. As long as coreboot uses fmap and cbfs, there
|
||
should be few need to change the language, since composition is done through
|
||
files.
|
||
|
||
The final image is generated by a utility that is handed a number of manifests
|
||
and the size of the flash (derived from `CONFIG_ROM_SIZE`). These manifest files
|
||
deal with different concerns, with the following an example that should match
|
||
current use cases:
|
||
|
||
Chipset manifest
|
||
----------------
|
||
The chipset details if there are any non-coreboot regions, and assigns them
|
||
names, locations, sizes and file contents and prepares a region for what is
|
||
“platform visible” (eg. IFD’s BIOS region) that may be of flexible size
|
||
(depending on the flash chip’s size). For the purpose of this document, that
|
||
region is called “BIOS”.
|
||
It can also specify if there’s a post processing requirement on the final
|
||
image.
|
||
|
||
coreboot manifest
|
||
-----------------
|
||
coreboot provides lists of the files it generates for each category it’s
|
||
building (eg. bootblock, verstage, romstage, ramstage). They not only contain
|
||
the stages themselves, but also additional files (eg. dsdt belongs to ramstage
|
||
since that’s where it is used)
|
||
|
||
Boot method manifest
|
||
--------------------
|
||
The boot method manifest can subdivide the BIOS region, eg. using it directly
|
||
(for coreboot’s “simple” bootblock), splitting it in two (for coreboot’s
|
||
fallback/normal) or in many parts (for Chrome OS, which requires two CBFS
|
||
regions, one for GBB, several for VPD, …).
|
||
It also specifies which of the file lists specified earlier belong in which
|
||
region (eg. with verstage verifying romstage, verstage needs to be only in
|
||
Chrome OS’ RO region, while romstage belongs in RO and both RW regions).
|
||
It can also specify a post processing step that is executed before the
|
||
chipset’s.
|
||
|
||
Payload and additional manifests
|
||
--------------------------------
|
||
External components should also provide manifests to add files to categories.
|
||
This way the payload and other components (eg. EC firmware) can be developed
|
||
without needing to touch the central boot method manifest (that likely resides
|
||
in the coreboot tree, given that coreboot needs to deal with choosing fmap
|
||
regions already).
|
||
|
||
coreboot build system
|
||
---------------------
|
||
The coreboot build system will be split more distinctly in two phases: The
|
||
first is about building the files (with results like romstage.elf), while the
|
||
second phase covers the assembly of the final image.
|
||
|
||
By having a global picture of the final image’s requirements, we can also
|
||
avoid issues where files added earlier may prevent later additions that have
|
||
stricter constraints - without resorting to hacks like
|
||
https://chromium-review.googlesource.com/289491 that reorder the file addition
|
||
manually.
|
||
|
||
Example
|
||
-------
|
||
As an example, we’ll define an Intel-based board with a postprocessing tool
|
||
(something that doesn’t exist, but isn’t hard to imagine):
|
||
|
||
It specifies an IFD region, an ME, and the BIOS region. After the image is
|
||
built, the entire image needs to be processed (although the tool likely works
|
||
only on a small part of it)
|
||
|
||
It’s built in a Chrome OS-like configuration (simplified at places to avoid
|
||
distracting from the important parts), so it has three CBFS regions, and
|
||
several data regions for its own purpose (similar to GBB, FWID, VPD, …). After
|
||
the regions are filled, one data region must be post-processed to contain
|
||
signatures to enable verifying other regions.
|
||
|
||
Chipset manifest
|
||
================
|
||
# A region called IFD, starting at 0, ending at 4K
|
||
region IFD: 0 4K
|
||
# Add the specified file “raw” into the region.
|
||
# If the file is smaller than the region, put it at the bottom and fill up
|
||
# with 0xff
|
||
raw IFD: build/ifd.bin align=bottom empty=0xff
|
||
# Call the postprocessor on the data that ends up in IFD (in this example it
|
||
# might lock the IFD)
|
||
postprocess IFD: util/ifdprocess -l
|
||
|
||
# a region called ME, starting at 4K, ending at 2M
|
||
region ME: 4K 2M
|
||
raw ME: 3rdparty/blobs/soc/intel/xanadu/me.bin align=bottom empty=0x00
|
||
|
||
# a region called BIOS, starting at 2M, filling up the free space
|
||
# filling up fails (build error) if two regions are requested to fill up
|
||
# against each other
|
||
region BIOS: 2M *
|
||
|
||
# This would define a region that covers the last 4K of flash.
|
||
# The BIOS region specified above will end right before it instead of
|
||
# expanding to end of flash
|
||
# region AUX: -4K -0
|
||
|
||
# specify the tool that post-processes the entire image.
|
||
postprocess image: util/intelchksum/intelchksum.sh
|
||
|
||
coreboot manifest
|
||
=================
|
||
# declare that build/verstage.elf belongs into the group ‘verstage’
|
||
# these groups are later referred to by the “cbfs” command.
|
||
group verstage: build/verstage.elf stage xip name=fallback/verstage
|
||
group romstage: build/romstage.elf stage xip name=fallback/romstage
|
||
group ramstage: build/ramstage.elf stage name=fallback/ramstage
|
||
compression=lzma
|
||
group ramstage: build/dsdt.aml compression=lzma
|
||
|
||
boot method manifest
|
||
====================
|
||
# Define RO as region inside BIOS, covering the upper half of the image.
|
||
# It’s a build error if the result crosses outside BIOS.
|
||
# math expressions are wrapped with ( ),
|
||
# and mentions of regions therein always refer to their size
|
||
subregion BIOS RO: ( image / 2 ) -0
|
||
|
||
# Define RW to cover the rest of BIOS.
|
||
# The order of RW and RO doesn’t matter except to keep comments clearer.
|
||
# Dynamic items like RW (“*”) will be sized to fill unused space after
|
||
# everything else is placed.
|
||
subregion BIOS RW: 0 *
|
||
|
||
# It may be necessary to separate the RO/RW definition into another manifest
|
||
# file
|
||
# that defines the RO configuration of the flash
|
||
|
||
# Some more subregions, with dynamically calculated sizes
|
||
subregion RW RW_A: 0 ( RW / 2 )
|
||
subregion RW RW_B: * -0
|
||
subregion RW_A FW_MAIN_A: RW_A * -0
|
||
subregion RW_A VBLOCK_A: 0 64K
|
||
# foo +bar specifies start + size, not (start, end)
|
||
# also, start is given as “the end of VBLOCK_A”
|
||
# (while using a region in the “end” field means “start of region”)
|
||
subregion RW_A FWID_A: VBLOCK_A +64
|
||
|
||
# To make the example not too verbose, RO only has the CBFS region
|
||
subregion RO BOOTSTUB: 0 *
|
||
|
||
# Postprocess the data that ends up in VBLOCK_A,
|
||
# passing the listed regions as additional arguments.
|
||
# Circular dependencies are build errors.
|
||
postprocess VBLOCK_A(FW_MAIN_A): signtool
|
||
|
||
# binding files to regions indirectly through groups
|
||
cbfs BOOTSTUB: verstage, romstage, ramstage, payload
|
||
cbfs FW_MAIN_A: romstage, ramstage, payload
|
||
|
||
# defining defaults: unless overridden, in all regions that use CBFS (“*”),
|
||
# we want all files to come with SHA256 hashes.
|
||
# Wildcard defaults have lower priority than specific defaults.
|
||
# Other conflicts lead to a build error.
|
||
cbfsdefaults *: hash=sha3
|
||
|
||
payload manifest
|
||
================
|
||
group payload: payload.elf payload
|
||
group payload: bootscreen.jpg name=splashscreen.jpg type=splashscreen
|
||
|
||
EC firmware manifest
|
||
====================
|
||
# overrides the cbfsdefault above
|
||
group payload: ecrw.bin name=ecrw hash=sha256
|
||
group payload: pdrw.bin name=pdrw hash=sha256
|
||
|
||
manifest parsing
|
||
================
|
||
The exact BNF is work in progress.
|
||
|
||
Some parser rules are
|
||
* one line per statement
|
||
* # introduces a command until the end of line
|
||
|
||
Some processing rules
|
||
* When there’s a conflict (eg. two statements on what to do to a region,
|
||
overlap, anything that can’t be determined), that is a build error.
|
||
* the order of statements doesn’t matter, enabling simple addition of more
|
||
manifests where the need arises.
|
||
|