Esta página aún no está traducida; se muestra en inglés.
The Board Registry
The registry holds every fact about a radio that the build itself doesn't need: its identity, status, build options,
flashing route, codec route and the web site's data. The tools, CI, the docs and the site read it. The build reads no
TOML: make keeps its own facts in board.mk, config.mk and memory.ld, and the registry reads those through
make.
tools/boards.py loads and checks the registry. It needs Python 3.11 or later (tomllib); scripts find one with
tools/rfoundry_python.sh.
Where each fact lives
Each fact is in one file. A value another file already holds is derived from it, never typed twice.
| File | Holds |
|---|---|
boards/<id>/board.toml |
Identity, status, build option text, CI extras, flashing family, bootloader and stub data, codec route, the site's data |
platform/<p>/platform.toml |
ISA, CPU, toolchain, platform stubs, flash base, sector geometries, codec symbols |
boards/<id>/board.mk |
PLATFORM, APP_BASE (the image address), HSE_VALUE (the crystal, for the STM32F4 stub), BOOT_*, BOARD_DIRS, BUILD_OPTIONS and each option's <NAME>_VALUES, read with make print-vars so the board's and the platform's config.mk and their conditionals count. LDSCRIPT defaults to memory.ld |
boards/<id>/memory.ld |
The application region (MEMORY FLASH, from __app_start) and the codec symbols (__codec_*). Every *.ld in the board directory is read for the codec symbols |
boards/<id>/*.c |
The board_t .name values: the names the debug link reports |
boards/<id>/pins.h |
The pins the stub uses: the power latch and key (HR_C7000), the SPI flash's PIN_FLASH_SCK, _MISO, _MOSI, _CS and the idle_cs pins (STM32F4), each as GPIOx, GPIO_PIN_n on STM32F4 |
Derived values:
| Value | From |
|---|---|
| Flashing link (USB, serial cable) | The family |
| CPU | The platform, unless [hardware] cpu names another |
| Protected range | The platform's flash_base up to the application base |
| Erasable sectors | The platform geometry from the application base up |
| Stub NAND flag | drivers/flash/spi_nand in BOARD_DIRS |
| Stub power pins | PIN_POWER_LATCH and PIN_POWER_KEY in pins.h, when hold_power is set |
| Stub board record (STM32F4) | spi and idle_cs, the PIN_FLASH_* pins, the bus's alternate function from the platform's board_stub.spi_af, HSE_VALUE and APP_BASE |
| Debug link names | The board_t names, each build option value's board_name, debuglink_aliases, the id |
| Codec windows of a linker slot | The __codec_slot symbols |
| Codec windows of a stock file | The stock file's profile in tools/rfoundry/rfoundry/stockfw.py |
board.toml
The schema in tools/boards.py (BOARD_SCHEMA) lists every key; check refuses an unknown or missing one. Copy a
neighbor on the same platform.
Identity
| Key | Type | Meaning |
|---|---|---|
id |
string | The directory name; make BOARD=<id> |
name |
string | Maker and model, as the site and the tools show it |
maker, model |
string | The two parts of the name |
also |
list of strings | The same radio sold under other names |
kind |
handheld or mobile |
|
part_of |
string, optional | Another board this one is a build of |
debuglink_aliases |
list, optional | Names older firmware reported on the debug link |
looks |
list of tables, optional | Other bodies the board ships in that look too different to share its picture: { id, name, also }. Each is its own radio card on the site, drawn in site/static/img/radios/src/<id>-<look id>.svg (the site build refuses a missing drawing), and links to the board's page. name and every entry of also (the radios in that body) must be in the board's also; the board's own card lists the rest |
[docs]
| Key | Meaning |
|---|---|
hardware |
The hardware spec, docs/spec/hardware-<id>.md |
codec |
Optional: the codec spec |
install |
Optional: the user manual's page for moving from stock firmware |
bringup |
Optional: the bring-up checklist |
check fails when a page doesn't exist. The README's radio table links the board to its bringup page, else its
hardware page.
[hardware]
One-line summaries for the site and the generated tables; the facts stay in docs.hardware.
| Key | Meaning |
|---|---|
cpu |
Optional: overrides the platform's CPU in the tables |
bands, power, display |
Short text |
gallery |
The display class (160x128); check requires the UI gallery to have it, and the frame buffer in board.mk (GFX_FB_BYTES, GFX_MAX_ROWS, GFX_INDEXED) to hold it |
unconfirmed |
Optional: hardware switches (hardware in tests/hil/features.toml) the board neither builds nor lists in BOARD_UNSUPPORTED or BOARD_NOT_WRITTEN (the rule), because no source says yet whether the radio has the part: switch to what is unknown. check fails on any other hardware switch that is off by default, and on an entry for a switch the board builds or lists. The feature matrix shows it as ? |
clock |
{ rtc, note }: the real-time clock. rtc is backed (keeps time through power-off), unbacked (only while on), none or unknown; note gives the source. The feature matrix and the site show it |
variants |
Optional: the hardware variants sold under the model, as [[hardware.variants]] tables (below) |
variant_pattern |
With variants: a regular expression over the stock firmware's version text whose first group is the variant code |
variant_check |
With variants: how a user reads the code on the radio, one or two sentences. The flasher and the radio page show it |
A variant is { code, name, level, notice, runs }:
| Key | Meaning |
|---|---|
code |
What variant_pattern captures; unique within the board |
name |
Short text: what sets this hardware apart |
runs |
{ board, options }: the image it runs. board is this board when variants share an image, another board id when the variant needs its own. options names build option values of that board ([[build_options]]) the image must be built with. No runs: not supported, and no level or notice |
level |
With runs: bring-up, testing or supported, as in [status] |
notice |
Required below supported: what doesn't work yet. The flasher shows it as a warning when it reads or the user picks the variant |
The web flasher reads the code before it writes (the stock console, the vendor identify reply) or asks the user, and
refuses an image the variant doesn't run (site/static/js/flasher/variant.js). check requires a supported board
to have a supported variant that runs its own image.
[status]
| Key | Values | Meaning |
|---|---|---|
level |
bring-up, testing, supported |
How far the port is |
summary |
string | What works, then what is open, in one sentence or two; Markdown links allowed. The README, the user manual and the web site show it |
release |
stable, testing |
stable: release images are built and rfoundry fetches them |
[[build_options]]
A hardware build option: one radio model sold in versions that need different images (one band per radio, for
example). board.mk holds the build's side: BUILD_OPTIONS := BAND and BAND_VALUES := uhf vhf, the default first.
board.toml holds the words. check requires the same names and values in the same order.
| Key | Meaning |
|---|---|
name |
The make variable (BAND) |
title, help |
The question the site and rfoundry ask, and how to tell the answer |
values |
A list of { id, title, text, board_name }: the make value, its label and description, the name the radio reports on the debug link |
The Makefile defines RFOUNDRY_<NAME>_<VALUE> as 1 or 0 for every value. A value other than the default adds -<value>
to the build directory and the image: make BOARD=<id> BAND=vhf writes build/<id>-vhf/rfoundry_<id>-vhf.bin.
Building has the user side.
[ci]
| Key | Meaning |
|---|---|
extra |
A list of { name, make }: more configurations ci.sh builds for this board, with their make arguments |
[flash]
| Key | Meaning |
|---|---|
family |
The flashing route, by protocol: usb-dfu, serial-loader or serial-iap |
keep_codec |
True when an update leaves the codec in place |
sectors |
Optional: a geometry of the platform other than its default_sectors |
stock_download |
Optional: where the stock firmware can be downloaded |
stock_versions |
The stock versions the tools know |
backup |
{ stock, rfoundry, spi_size }: what a backup reads from stock firmware and from RadioFoundry (none or full), and the SPI flash size |
notice |
Optional: a warning the web flasher shows on every step once the radio is picked, for a choice the radio can't check itself (two images that differ in hardware the radio can't report, for example) |
[flash.bootloader]:
| Key | Meaning |
|---|---|
steps |
How to enter the bootloader, one step per item, for the web flasher and the manual |
keys |
Optional: the short form the tools print |
hold |
Optional: the keys held to enter it, by key name (the board's KEY_* in lower case, ["ptt", "sk1"]). The web flasher marks them on the radio's picture and names them under it; steps stays the instructions. Each must be a key of the board (tools/boards.py check) and tagged in its drawing or listed in data-undrawn (Radio images) |
id |
Optional: the string the loader answers, also the magic of its files (serial-loader) |
sign |
Optional: what the radio shows in the bootloader |
[flash.dfu] (usb-dfu): vid, pid, string1, product (the USB identity) and key_sha256, key_prefix (the
per-model XOR key's hash and first bytes, never the key). Optional layout: the bootloader's alt 0 interface string,
added only once it was read on a radio in its bootloader. The flashers then accept no other device. Without it they
refuse a layout that shows only the bootloader's sectors (from 0x08000000, at most 48 KB): that is the radio's own
firmware switched on normally, which has the bootloader's IDs and strings but whose erase can reach the bootloader.
Original firmware version
[flash.original_version] (optional): a hardware option (board_t.hwopts, debuglink.md)
that RadioFoundry can't detect but that the version of the radio's original firmware settles, such as a board revision
when the manufacturer ships one firmware line per revision. The installers read that version before they ask for the
bootloader, and store the choice over the debug link once RadioFoundry runs (rfoundry's origversion.py, the web
flasher's origversion.js; web-flasher.md).
| Key | Meaning |
|---|---|
read |
How the version is read. dfu-info: the original firmware, running normally, is a DFU device with the bootloader's USB IDs ([flash.dfu] required); block 0 command A2 02, then UPLOAD, answers the version (first byte 0x56; the bootloader answers 0x54). The request sequence is in the radio's hardware page |
interface |
The DFU interface string the original firmware shows while it runs (spaces not counted). The installers send the read only to a device showing it, and refuse to write to one |
hwopt |
The option's name, as hal_hwopt_t names it |
major |
The choice for each major version: { "06" = "<choice>", ... }, two digits each, every choice one of the option's labels |
tools/boards.py check refuses a choice or option name that doesn't appear in the board's sources. A version whose
major isn't listed settles nothing: the radio then uses its own fallback.
Backup before the install
[flash.original_backup] (optional, with [flash.original_version]): what the web flasher backs up through the
original firmware's or the bootloader's DFU device before an install (origbackup.js,
web-flasher.md). The commands are in the radio's hardware page.
| Key | Meaning |
|---|---|
info |
The info block codes (A2 ss), two hex digits each, in the order read |
mirror |
The most to ask for in the one snapshot of the internal flash from the start of flash, before program mode (at most 0xFFFF) |
spi |
The SPI bytes the original firmware reads in program mode (at most backup.spi_size) |
quick |
The SPI bytes of the quick read (the codeplug areas) |
loader_interface |
The bootloader's DFU interface string (spaces not counted), which tells it from the original firmware |
loader_spi |
The SPI bytes the bootloader reads |
[flash.stub] (a board on a platform with board_stub): id (the board id the stub echoes, unique per stub; 0xFE is reserved for the stub tools' test profiles) and
tested (flashed through the stub on a radio), then the keys the platform's board_stub names:
| Platform stub | Required | Optional |
|---|---|---|
stubtool (HR_C7000) |
hold_power (the stub drives the power latch), progmem (program flash size) |
stock_console (the stock firmware answers a console the web flasher reads), read_rate, write_test (the data flash offset of the 16 KB --write-test erases and programs; the check refuses one that overlaps the flash map, a protected region or the chip's end, or a NAND. Without it the tool writes no data flash) |
f4stub (STM32F4) |
spi = { bus, mode, hz } (the SPI flash's bus 1-3, SPI mode 0-3, fastest clock) |
idle_cs (up to two pins.h names of other chip selects on that bus, held high) |
A key of another platform's stub fails the check.
Protected regions
[[flash.protect]] lists flash that no tool erases or writes: factory data that can't be made again, the calibration
first. One table per region:
[[flash.protect]]
mem = "data"
start = 0x1000
end = 0xC9000
what = "factory calibration: the page tagged 2 in the stock codeplug area (hardware-<radio>.md 6.4)"
| Key | Meaning |
|---|---|
mem |
The memory: progmem (the MCU's program flash), data (the SPI NOR data flash), nand (the SPI NAND's main area, without spare bytes) or secreg (the SPI NOR flash's security registers) |
start, end |
The range, end exclusive, as offsets in that memory: progmem from the platform's flash_base, data and nand from 0, secreg 256 bytes per register from register 1 at 0 |
what |
What lies there, with the spec section that establishes it in parentheses |
tools/boards.py check refuses an entry:
- whose memory the board doesn't have (
dataandsecregneedbackup.spi_sizeand no NAND;nandneeds aspi_nand_tin the board's sources) - that runs past the memory's end
- that isn't on the memory's erase boundaries: the platform's sectors (or 4 KB) for
progmem, 4 KB fordata, a block fornand, a register forsecreg - that overlaps another entry, the application or the codec slot
- whose
whatcites no section
A region whose place moves (remapped by a bad-block table, for example) is protected over every place it can be. The
registry carries the list as protect (registry.json, radios.json flash, flash-boards.json boards, c7000
and f4stub).
What the tools do with it. tools/rfoundry/rfoundry/protect.py and site/static/js/flasher/protect.js hold the one
check each language uses:
| Write | Behavior |
|---|---|
| An install, a test write, a single sector | Refused before anything is erased |
| A restore of a whole memory (program flash backup through the stub, internal flash through DFU, SPI flash through the STM32 stub, codeplug through the CPS) | Carved: the protected sectors keep what the radio has, the rest is written |
| A security register restore | Refused when a protected register differs (a register is written whole) |
A loader that erases a fixed area (the vendor IAP and serial loaders, the DFU updater in tools/stm32_dfu.py) |
Refused when the area holds a protected region: those erases can't be carved |
| A whole-chip erase | Refused on a memory with any protected region. No tool sends one |
The stub tools check every erase and program command they send, not only the high-level writes. RadioFoundry's own firmware keeps its own rules for what it writes (the CPS shim refuses its areas, cps-protocol.md 2.3).
[codec]
| Key | Meaning |
|---|---|
tool |
The tool that extracts or merges it: codec_extract or hrc7000_codec |
stock |
A list of SHA-256 digests: the stock files rfoundry merges the codec from (stock-file placement) |
profile |
The codec id the firmware reports, for a codec in a linker slot |
sources |
Where a slot codec comes from, for messages |
installed |
Stock versions that leave exactly this codec in place |
A board has either stock, or profile with a linker codec slot. With stock, the profile and sources come from the
stock files' profiles. The files must hold the same codec; each may put its tables at other addresses. The board's
codec windows are the union of every file's layout. The image must stay out of all of them, and keeping the codec keeps
all of them. A merge writes and checks the layout of the file it merges
(codec-placement.md).
[cps]
radio_types: the radio types the open-firmware CPS radio info reports for this radio
(open-firmware-cps.md 4). RadioFoundry's CPS shim reports them, and the web flasher
checks them before it backs up a radio that runs another open firmware. The registry carries them as
cps_radio_types, with rom_base (the platform's flash_base, where the CPS's area 5 offsets start).
[frame]
How the site draws the radio: shape, body and accent colors, lens size.
platform.toml
| Key | Meaning |
|---|---|
id, isa, cpu |
The platform directory, the ISA, the SoC |
stubs |
Platform stubs ci.sh builds with the platform's compiler |
board_stub |
The stub its boards' [flash.stub] is for: tool, the required keys, the optional ones and, for SPI wiring, spi_af (the alternate function per bus). Absent: no [flash.stub] |
codec_symbols |
The linker symbols every board's scripts must define |
flash_base |
Where program flash starts; the factory loader holds it up to the image |
default_sectors, [sectors] |
The flash geometries, [index, address, size] in the bootloader's erase order |
[toolchain]:
| Key | Meaning |
|---|---|
id, name |
The toolchain (arm, csky) and its display name. CI has one firmware job per toolchain (firmware (Arm)), and .github/actions/toolchain sets it up by id. Platforms with the same id build in the same job and must have the same [toolchain] table |
cc |
The compiler make print-cc names |
build |
The script that installs or builds it |
version |
The release CI and releases build with |
sha256 |
Optional: the SHA-256 of the archive the script downloads |
image |
Optional: the container image that holds the toolchain (RFOUNDRY_CSKY_IMAGE overrides it) |
The toolchain scripts read these values with tools/platform_toolchain.sh, which needs no Python. check fails when
a script, Dockerfile or workflow under tools/ or .github/ sets <ID>_GCC_VERSION or <ID>_GCC_SHA256 (for
example ARM_GCC_VERSION) to a literal other than the pin: take it from platform.toml.
Commands
| Command | Output |
|---|---|
tools/boards.py list [--platform P[,Q]] [--toolchain T[,U]] [--release R] [--comma] |
Board ids; an unknown platform or toolchain fails. tools/ci.sh --toolchain and the CI workflows select boards with it |
tools/boards.py get <id> [key.path] |
One value; a list prints one item per line |
tools/boards.py platform <p> [key.path] |
One platform value |
tools/boards.py builds <id> [--extra] |
The board's builds besides the default, or its [ci] extras |
tools/boards.py json [--for all|rfoundry|build] |
A merged view, below |
tools/boards.py check |
Every inconsistency, and every radio named outside its own places (Radio names); ci.sh's registry step runs it |
tools/boards.py docs [--check] [--floor REF] |
Writes the generated blocks of the README, the user manual's radio pages, the feature matrix (its Built by default table, and its Checked on each radio table from the bring-up records in docs/testing/results/), the bug report form's and the pull request template's radio lists, and the survey's Port cells (tools/registry_docs.py). --check lists the stale ones instead; --floor REF fails when a radio's marks or status in the README read lower than at commit REF. ci.sh runs --check --floor origin/main |
tools/boards.py self-test |
The loader's own tests |
make registry regenerates tools/rfoundry/rfoundry/registry.json when a source changed. make does too, before
every build.
The views
| View | Format | For |
|---|---|---|
--for all |
none (the TOML with a derived table) |
Scripts in this tree |
--for rfoundry |
rfoundry-board-registry/2 |
rfoundry, the release tool and the site. Committed as registry.json, so they run on any Python 3 and inside the rfoundry executable |
--for build |
rfoundry-build-matrix/1 |
Tools that build from a checkout |
The build view
tools/boards.py json --for build gives a tool that builds images from a checkout everything it needs, with nothing
from outside the tree. It is a stable interface: a change that breaks a reader changes format.
| Key | Contents |
|---|---|
format |
rfoundry-build-matrix/1 |
variants |
The release variants: full, then each config/variants/<name>.mk |
platforms.<p> |
isa, cpu, toolchain (the id), cc, version, image (null: no container), install, sha256 (null when none) |
boards.<id> |
name, platform, release, released (true for stable), first_stage, build_options, builds |
boards.<id>.build_options |
A list of { name, default, values } |
boards.<id>.builds |
One entry per combination of option values, the all-default one first |
Each build:
| Key | Example (a board <id> with BAND=vhf) |
|---|---|
options |
{"BAND": "vhf"} |
make |
["BOARD=<id>", "BAND=vhf"]: the make arguments; add VARIANT=<name> for a variant |
release |
["--board", "<id>", "--build-option", "BAND=vhf"]: the tools/release.py arguments; add --variant <name> |
image_id |
<id>-vhf |
build_dir |
build/<id>{variant}-vhf |
image |
build/<id>{variant}-vhf/rfoundry_<id>-vhf.bin: what make writes and the release carries |
release_image, release_manifest |
rfoundry-<id>{variant}-vhf-{version}.bin and .json: what tools/release.py writes |
In the names, {variant} is empty for the full build and -<name> for a variant; {version} is the release tag. One
rule makes all of these names: tools/rfoundry/rfoundry/names.py. The Makefile follows it too, and check compares
make's BUILD and TARGET with the view for every build.
Radio names
AGENTS.md rule 4 says only a radio's own files carry its name. tools/check_generic.py (run by
tools/boards.py check) enforces it. The names come from the registry: each board's id, model and also, matched
without regard to case, hyphens or a letter suffix (XR-100UV, XR100, xr100). A maker's word in front is dropped.
| Allowed place | What it covers |
|---|---|
boards/<id>/ |
The radio's own code, tests and data |
| The radio's pages | A docs/ path with the id in it (hardware-<id>.md, <id>-bringup.md, results/<id>/), a [docs] page no other board shares, and any directory named <id> (codec_maps/<id>/) |
| List pages | docs/spec/radios.md, docs/user/23-radios.md, docs/feature-matrix.md |
| Generated blocks | Between the markers tools/boards.py docs writes, in every marker style, and registry.json |
| File references | A path or a file name in the text points to the radio's own page |
tools/generic_allow.txt |
A glob and its reason. An entry no file needs is an error |
Everything else fails, tests included, and so does a file whose own path names a radio. Fix it by naming the function, the chip or the platform, or by reading the value from the board. The allowlist holds the debt from before the check; don't add new code to it.
Changing the registry
- A new key goes in the schema, in this page and in the reader that needs it. If the build already knows the value, derive it instead.
- Run
tools/boards.py checkandmake registry, and commit the regeneratedregistry.jsonwith the change. - A new board: Adding a radio.