Adding a board
Bringing up a new device is mostly writing config layers — a build resolves from TOML across the config model’s axes, so a new board is a set of small TOML files plus any vendored blobs and kernel fragments. The one exception is a genuinely new chip family, which also needs a small Rust change; see What needs code.
Which track are you on? This is the bring-up track (resolve → update → verify → build) — for a board or patch that has no lock yet. If you only want to build one of the shipped recipes, take the shorter Getting started track (doctor → build) instead. To add a patch rather than a board, see Adding a patch; to name a new build point on a board that is already here, see Authoring a recipe.
Start with the generator.
boot2deb new-device <name>scaffolds the device (and a matching recipe) for you — it offers the valid SoC/boot-method/kernel/feature choices, fills every derivable value, and leaves the researched ones marked# TODO:. Run it (--soc <soc>non-interactively, or answer the prompts on a terminal; add--overlay <dir>to scaffold into your own tree), then edit the TODO values below. The rest of this page explains what those files mean and which values you must research. Seenew-device.
Where your files go: overlay or in-tree
Two ways to add a board, chosen by intent:
- Out-of-tree overlay — you are bringing up a board for yourself. Put the files in
your own directory and pass
--overlay <dir>; there is nothing to fork, and the board’s lock is written back beside it. This is the third-party path — see Overlays. - In-tree — you are contributing a board back to boot2deb. Add the files to the vendored tree and open a pull request.
The files are identical either way; only their location differs. The rest of this page describes those files.
What needs code
Most of a board is data, but three axes are closed Rust enums — Arch, Soc, and
BootMethod in crates/core/src/model.rs — chosen for type safety and exhaustiveness
checking. A board built on a chip family that already exists (any RK35xx SoC, the
rockchip-rkbin boot method) needs no code: the variant is already there. A genuinely
new family does:
- New SoC (e.g. a non-Rockchip chip) — add a variant to the
Socenum near the top ofmodel.rsand to itskebab_enum!invocation grouped just below the enum definitions, then rebuild. The compiler flags everymatchthat must now handle it. - New architecture or new boot method — the same, on
Arch/BootMethod. A new boot method also needs the engine taught how to write its payloads.
This is a deliberate boundary: closed enums give the compiler a single source of truth and catch a half-added target at compile time, at the cost of a recompile for a new family. Within an existing family it is pure config.
The layers to write
Work from the bottom of the hardware stack up, adding only what is new:
First, check whether the board joins a family that is already here. If its SoC and
boot method are both supported, a new board can be a device file and nothing else — no
overlay, no kernel, no engine change. That is not an aspiration: the ASUS
C100P and Chromebit CS10
each ship as a single TOML, and the Chromebit is a stick PC with no SD slot, no keyboard,
no EC and no analog audio. The rule that makes it work is that anything true of the whole
family belongs on the SoC layer, not on the board that happened to need it first —
socs/rk3288/ carries the family’s radio blobs, initramfs module list and network stack
for exactly that reason. When you find yourself copying a file from one board to another,
move it up instead.
-
arch (
arches/<arch>.toml) — only for a CPU architecture not already present. Arch-wide kbuild facts: the cross triple, kbuild’sARCH=, and the kernel image path. (u-boot takes noARCH=from here — its defconfig carries it.) -
soc (
socs/<soc>.toml, plussocs/<soc>/overlay/for files baked into the rootfs) — the SoC’s shared properties: device-tree directory, force-loaded modules, arch, and any SoC-wide firmware packages.- Media-accel sources are optional and ride the feature. Supply the
one
[[userspace]]entry per vendor tree the part has,[ffmpeg.base], and[ffmpeg.rockchip]stanzas here only if a board of this SoC will enable amedia-accel-*feature (the feature compiles them into.debs); copy the block fromsocs/rk3588.toml. A headless SoC that never transcodes omits them entirely. Selecting arequires_media_accelfeature on a SoC that lacks them is a resolve-time error, so the coupling is checked, not assumed.
- Media-accel sources are optional and ride the feature. Supply the
one
-
boot-method (
boot-methods/<method>.toml) — how this family boots. The file’s shape depends on the method, because the methods differ in kind:rockchip-rkbincompiles a bootloader: the u-boot source + ref and the raw-gap offsets (whereidbloaderandu-boot.itbsit outside any partition, and where the rootfs partition starts).depthchargecompiles nothing — the firmware is the board’s own and what it loads is the signed kernel — so the file carries the ChromeOS kernel partition’s geometry, its GPT attribute bits, and the command line to sign into the kernel.
A field from the other method is an unknown field and fails to parse, which is the point: an image cannot half-belong to two boot chains.
boot-methods/<method>/overlay/ships any boot-time files (e.g. the extlinux generator), andoverlay-pre/ships config a package’s own maintainer scripts must see while they run (see Two overlay stages). -
device (
devices/<device>.toml) — the board itself, stating only its deltas: itssoc,boot_method,supported_boot_methods,kernel_dtb,image_size,hostname,supported_kernels/default_kernel,default_suite,default_layout, plus whatever its boot method requires:- The slug must be a host name —
[A-Za-z0-9-]labels joined by., no leading or trailing-, at most 64 characters.hostnamedefaults to it, so anything else would hand the image a name it cannot come up under; usemy-board, notmy_board. See A value that becomes a file or a line is checked at resolve.boot2deb new-devicenarrows this further for generated boards, to lowercase, digits, and dashes. - under
rockchip-rkbin: auboot_defconfig, and — only if the board departs from the SoC’s defaults — an[rkbin]block. The bootloader blobs are inherited from the soc layer and merged per field, so a board on the SoC’s usual memory omits the block entirely; a board with different DRAM overrides justtpl. - under
depthcharge: a[depthcharge]block naming the board profile and the series the unit supports. Nouboot_defconfig, no blobs — this board compiles no bootloader, and resolution does not ask it to. device_config_fragmentsgotcha: naming a fragment here makes its file mandatory.device_config_fragments = ["device/my-board"]requiresfragments/device/my-board.configto exist — a missing file failsresolve. A board with no board-specific kconfig deltas usesdevice_config_fragments = []to add none. Do not name a fragment you have not written.- A variant of a board already here uses
extends. If your board is another one with one difference — a block enabled for bring-up, a different DTB or DRAM fitting — writeextends = "<other-device>"and state only the deltas. It inherits that device’s keys and itsoverlay/tree, so the parent board’s driver tuning, units, and keymaps reach your image and any file of them can be overridden by shipping your own copy at the same path. Do not hand-copy the other device’s file: most arrays replace rather than append across the merge, so restate any list you extend — the five that describe the board (caveats,expect,nonfree_firmware_packages,packages,exclude) accumulate instead. See A variant board extends another.
- The slug must be a host name —
-
kmod (
kmods/<name>.toml) — only if the board carries hardware whose driver is in nobody’s kernel tree. Runboot2deb list-kmodsfirst: if a kmod for the chip already exists, your board needs one line,device_kmods = ["<name>"], and nothing else. If not, write the layer — the vendor repo, itsref, thesubdirmake M=builds in, the modules to ship — and put any patch of your own underkmods/<name>/patches/. Everything in that file is a property of the driver, so state nothing board-specific there; a device cannot override a kmod’s fields, and a board needing different build flags is a second kmod, not an override. See Out-of-tree modules are their own layer. -
kernel (
kernels/<kernel>.toml) — the orthogonal kernel axis. Ask first whether the board needs a kernel of yours at all.- If Debian’s own kernel already runs the hardware — which it does for any SoC and
board that are fully upstream — write a
flavor = "distro-package"definition naming the package (linux-image-armmp) and you are done. No source ref, no defconfig, no fragments, no patches, and one definition serves every suite. This is the better answer where it applies:aptkeeps the board’s kernel patched, which a kernel you compiled does not. - Otherwise write a
mainlineorvendordefinition with its source refs,.configfragments, and patch series. Version-coupled, so a new kernel version is a new file. A compiled kernel that applies no series writespatch_series = "none"and then never reads thepatchesrepo.
Note that a distro kernel and the compile-only device fields (
device_dts,device_config_fragments,device_patch_series,device_kmods) are mutually exclusive, and resolution says so: nothing would ever build the DTB, merge the fragments, apply the series, or give the modules a tree to build against. - If Debian’s own kernel already runs the hardware — which it does for any SoC and
board that are fully upstream — write a
Two overlay stages
Each layer may ship two trees of files that are copied into the rootfs:
overlay/— laid in after every package. It therefore wins over whatever the packages shipped, which is what nearly all config wants.overlay-pre/— laid in before any package is installed. This is for config a package’s own maintainer scripts must see while they run, where winning afterwards is too late because the package already acted. The Veyron Chromebooks are the clearest case: the initramfs module list underusr/share/initramfs-tools/modules.d/has to precede the kernel package, or the first initramfs is built without the drivers that reach the root device and then thrown away and rebuilt.
Boot-method config that resolution derives — the depthcharge-tools board profile, the
signed cmdline, the initramfs MODULES=/COMPRESS= settings — is generated into the
same pre-install stage rather than authored as an overlay file, for the same
before-the-package reason. A layer does not ship those; it states the values they come
from.
Use overlay/ unless the package acts before your file would arrive.
Supporting assets:
- blobs (
blobs/<soc>/) — vendored bootloader binaries the device/boot-method references. - fragments (
fragments/<name>.config) — kernel.configfragments merged onto the base defconfig, referenced by name from a kernel or device. - patch series — lives in the separate
patchesrepo, referenced by the kernel; see Adding a patch. Omitted entirely by apatch_series = "none"kernel. - board device tree — only when the board’s
.dtsis not yet upstream; seedevice_dts.
Finally, a recipe (recipes/<device>/<leaf>.toml) pins one point across the axes — device,
kernel, suite, features, layout.
Values you must research
Most fields fail loudly at resolve: bad image geometry, a missing fragment, or an
unvendored keyring are all caught up front. Two fields are not validated until the
stage that consumes them compiles, so a typo produces a late, confusing failure:
| Value | Layer | Fails at |
|---|---|---|
kernel_dtb | device | the kernel build — the DTB is not produced (unless the board carries device_dts, below, which makes this a resolve-time check) |
uboot_defconfig | device | the u-boot build — unknown defconfig |
Take both from the board’s upstream support: kernel_dtb is the device tree the mainline
kernel builds for the board (under arch/<arch>/boot/dts/<dt_dir>/), and
uboot_defconfig is the board’s u-boot defconfig. Confirm each exists in the exact
kernel/u-boot versions you pin before you trust a green resolve.
When the board’s device tree is not upstream
A freshly-supported SoC often has every driver in mainline but none of its boards. Fork
the nearest in-tree board .dts, put it in your overlay, and list it in device_dts:
kernel_dtb = "rockchip/rk3576-my-box.dtb"
device_dts = ["devices/my-box/dts/rk3576-my-box.dts"]
The kernel stage copies it into the tree and registers the DTB with kbuild, so it ships
in the linux-image deb like any in-tree board. kernel_dtb is then validated at
resolve — it must be the DTB one of those sources builds — so the table above no longer
applies to it. Iterate with build <recipe> --stage dtb, which rebuilds only the DTB.
Keep device_dts for the new board file. An edit to an existing upstream .dts is a
patch in the kernel’s patch series; a source that would overwrite an in-tree file is
refused rather than silently shadowing it.
Bring it up
With the layers written, use the CLI’s checks as guardrails. This is the same sequence Authoring a recipe uses — a new board only differs in having written the device file first:
# 1. Does it resolve to a coherent build point? Also runs the geometry / fragment /
# keyring preflight, so this is a real coherence gate, not just a merge print.
boot2deb resolve <recipe>
# 2. Is the host equipped to build it? Before `update`, so a missing host requirement
# costs nothing but a re-run rather than a network round trip.
boot2deb doctor <recipe>
# 3. Resolve upstream refs + hash blobs into the lock. The only command that consults
# the network for pins; the verify gates below all read the lock it writes.
boot2deb update <recipe> --kernel-ref <ref>
# 4. Does the patch series apply cleanly to the pinned kernel? Auto-fetches the locked
# kernel — no hand-cloned tree — or add --kernel-src ../linux if you have a checkout.
boot2deb verify-patches <recipe>
# 5. Does the .config generate (and, with --reference-config, match a reference)?
boot2deb verify-config <recipe>
# 6. Build.
boot2deb build <recipe>
resolve, update, and the two verify-* commands fail with a typed error before any
compile starts, so most config mistakes surface in seconds rather than partway through a
build. The verify-* commands auto-fetch the pinned source trees, so this whole sequence
works on a fresh clone with no hand-cloned kernel — see
Verification.
A worked example: a second RK3588 board
A board on an existing SoC needs only a device file and a recipe — arch, soc, and
boot-method all reuse the shipped layers. Suppose my-board is another RK3588 module.
devices/my-board.toml:
description = "My RK3588 board"
soc = "rk3588" # reuse the shipped SoC layer
boot_method = "rockchip-rkbin"
supported_boot_methods = ["rockchip-rkbin"]
uboot_defconfig = "my-board-rk3588_defconfig" # research: must exist in u-boot
kernel_dtb = "rockchip/rk3588-my-board.dtb" # research: must exist in the kernel
device_config_fragments = [] # no board-specific kconfig deltas
supported_kernels = ["rk3588-mainline-7.2"]
default_kernel = "rk3588-mainline-7.2"
default_suite = "forky"
default_layout = "combined"
hostname = "my-board" # defaults to the slug, which is already one
image_size = "2G"
[rkbin] # board-memory-specific DDR init
atf = "rk3588_bl31_v1.51.elf"
tpl = "rk3588_ddr_lp4_2112MHz_lp5_2400MHz_v1.19.bin"
recipes/my-board/forky.toml:
device = "my-board"
kernel = "rk3588-mainline-7.2"
suite = "forky"
features = ["media-accel-rockchip"] # or [] for a plain image
layout = "combined"
Then run the bring-it-up sequence against my-board/forky. A new SoC
would additionally need socs/<soc>.toml (with the required userspace/ffmpeg stanzas) and
its fragments; a new family would need the code change from
What needs code.
Document your board
Give each board a page under Boards, the way the Turing RK1 page does, since flashing is inherently per-board — a Turing Pi module flashes through the BMC, a standalone SBC takes an SD card or a maskrom loader, a laptop boots UEFI. A useful skeleton:
# <Board name>
The `<recipe>` recipe builds a bootable Debian <suite> image for the <board> (<SoC>).
It pins kernel `<ver>`, u-boot `<ver>`, and <features>.
Build it as in [Getting started](../getting-started.md):
boot2deb build <recipe>
## Flash
<how this board takes an image: card reader, BMC, maskrom, UEFI…>
## Serial console
<UART pins / adapter / baud>
## First boot
<credentials, resize-on-first-boot, hostname>