Merge branch 'nickez/make-config-attempt2'
What changed, and why it matters
This is a large build-system refactor for the BitBox02/BitBox03 firmware repository. It replaces hard-coded Make targets and CMake-generated J-Link scripts with a new Python-based configuration system that lets developers choose product, edition, debug mode, and probe software. There is no change to the actual firmware code that runs on the device, and nothing in the commit suggests a security vulnerability or fix.
No security action required. Treat as a normal build-infrastructure change; review for developer-experience regressions and CI matrix coverage.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The merge commit ‘nickez/make-config-attempt2’ rewrites the top-level Makefile and CI workflow to introduce a make config step that persists choices to a git-ignored config.mk. It adds scripts/build_config.py and scripts/build_product.py to dispatch builds across BitBox02, BitBox02 Nova, and BitBox03 products, and scripts/probe.py to handle flashing/running via J-Link or OpenOCD. It removes the old scripts/CMakeLists.txt template expansion, many per-product Makefile targets, and several stale GDB/OpenOCD scripts. The source CMake now passes $(MAKE) into Cargo to preserve the GNU make jobserver. No runtime firmware logic is modified.
Changed components
MakefileCMakeLists.txtscripts/build_config.pyscripts/build_product.pyscripts/probe.py.github/workflows/ci-common.ymlBUILD.mdAGENTS.mdsrc/CMakeLists.txtInspect captured patch +932 / −570
### .ci/check-tidy
@@ -25,7 +25,7 @@ if test -t 1; then
fi
fi
-for dir in build build-build; do
+for dir in build-bitbox02-cmake-relwithdebinfo build-build; do
if ! test -d ${dir}; then
echo "Missing ${dir}"
exit 1
### .ci/check-unwanted-symbols
@@ -93,19 +93,18 @@ check_bootloader() {
"Use the existing PUKCC SHA-256 implementation in bootloaders instead."
}
-firmware_elf=build/bin/firmware.elf
-if [[ -f "$firmware_elf" ]]; then
+shopt -s nullglob
+for firmware_elf in build-bitbox02*-cmake-*/bin/firmware.elf build-bitbox02*-cmake-*/bin/firmware-btc.elf; do
check_firmware "$firmware_elf"
-fi
+done
-shopt -s nullglob
-for bootloader_elf in build/bin/bootloader-stage0-*.elf build/bin/bootloader-stage1-*.elf; do
+for bootloader_elf in build-bitbox02*-cmake-*/bin/bootloader-stage0-*.elf build-bitbox02*-cmake-*/bin/bootloader-stage1-*.elf; do
check_bootloader "$bootloader_elf"
done
shopt -u nullglob
if [[ "$checked" == 0 ]]; then
- echo "No checked ELF files found in build/bin" >&2
+ echo "No checked ELF files found in build-bitbox02*-cmake-*/bin" >&2
exit 2
fi
### .github/workflows/ci-common.yml
@@ -184,25 +184,35 @@ jobs:
build:
strategy:
matrix:
- target:
- - bootloader-stage0
- - bootloader-stage1
- - firmware
- - firmware-btc
- - firmware-blupgrade-bitbox02-btconly
- - firmware-blupgrade-bitbox02-multi
- - firmware-blupgrade-bitbox02nova-btconly
- - firmware-blupgrade-bitbox02nova-multi
- - firmware-blupgrade-bitbox02-btconly-development
- - factory-setup
- - firmware-debug
- - bitbox03-boot0
- - bitbox03-boot1
- - bitbox03-factorysetup
- - bitbox03-firmware
- - simulator
- - simulator-graphical
- - simulator-graphical-bb03
+ include:
+ - {target: bootloader-stage0, product: bitbox02, edition: multi}
+ - {target: bootloader-stage0, product: bitbox02, edition: btc-only}
+ - {target: bootloader-stage0, product: bitbox02nova, edition: multi}
+ - {target: bootloader-stage0, product: bitbox02nova, edition: btc-only}
+ - {target: bootloader-stage1, product: bitbox02, edition: multi}
+ - {target: bootloader-stage1, product: bitbox02, edition: btc-only}
+ - {target: bootloader-stage1, product: bitbox02nova, edition: multi}
+ - {target: bootloader-stage1, product: bitbox02nova, edition: btc-only}
+ - {target: firmware, product: bitbox02, edition: multi}
+ - {target: firmware, product: bitbox02, edition: btc-only}
+ - {target: firmware, product: bitbox02nova, edition: multi}
+ - {target: firmware, product: bitbox02nova, edition: btc-only}
+ - {target: bootloader-stage0-production, product: bitbox02, edition: multi}
+ - {target: bootloader-stage1-production, product: bitbox02, edition: multi}
+ - {target: firmware-blupgrade-bitbox02-btconly, product: bitbox02, edition: multi}
+ - {target: firmware-blupgrade-bitbox02-multi, product: bitbox02, edition: multi}
+ - {target: firmware-blupgrade-bitbox02nova-btconly, product: bitbox02nova, edition: multi}
+ - {target: firmware-blupgrade-bitbox02nova-multi, product: bitbox02nova, edition: multi}
+ - {target: firmware-blupgrade-bitbox02-btconly-development, product: bitbox02, edition: multi}
+ - {target: factorysetup, product: bitbox02, edition: multi}
+ - {target: firmware, product: bitbox02, edition: multi, debug: 'yes'}
+ - {target: simulator, product: bitbox02, edition: multi}
+ - {target: simulator-graphical, product: bitbox02, edition: multi}
+ - {target: simulator-graphical-bb03, product: bitbox02, edition: multi}
+ - {target: bootloader-stage0, product: bitbox03, edition: multi}
+ - {target: bootloader-stage1, product: bitbox03, edition: multi}
+ - {target: factorysetup, product: bitbox03, edition: multi}
+ - {target: firmware, product: bitbox03, edition: multi}
runs-on: ubuntu-22.04
container:
image: ${{ inputs.container-repo }}:${{ inputs.container-version }}
@@ -223,17 +233,21 @@ jobs:
- name: Bootstrap Cargo config
run: ./scripts/bootstrap-cargo-config
+ - name: Configure product
+ run: make config CONFIG_ARGS="--product ${{ matrix.product }} --edition ${{ matrix.edition }} --debug ${{ matrix.debug || 'no' }}"
+
- name: Build ${{ matrix.target }}
run: make -j$(($(nproc)+1)) ${{ matrix.target }}
- name: Check unwanted symbols
- if: (matrix.target == 'firmware' || startsWith(matrix.target, 'bootloader-stage')) && !cancelled()
+ if: matrix.product != 'bitbox03' && matrix.debug != 'yes' && (matrix.target == 'firmware' || startsWith(matrix.target, 'bootloader-stage')) && !cancelled()
run: ./.ci/check-unwanted-symbols
- name: Print hashes
run: |
{
- for dir in build*/bin src/rust/target/thumbv8m.main-none-eabihf/debug; do
+ for dir in build*/bin build-bitbox03-cargo/thumbv8m.main-none-eabihf/debug \
+ build-bitbox03-cargo/thumbv8m.main-none-eabihf/release; do
if [ -d "$dir" ]; then
find "$dir" -maxdepth 1 -type f -print0
fi
@@ -245,13 +259,14 @@ jobs:
if: github.event_name == 'push' && !cancelled()
uses: actions/upload-artifact@v4
with:
- name: ${{matrix.target}}-${{github.sha}}.bin
+ name: ${{matrix.product}}-${{matrix.edition}}-${{matrix.target}}${{ matrix.debug == 'yes' && '-debug' || '' }}-${{github.sha}}.bin
path: |
build*/bin/*.bin
build*/bin/*.elf
build*/bin/*.map
build*/bin/simulator*
- src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-*
+ build-bitbox03-cargo/thumbv8m.main-none-eabihf/debug/bitbox03-*
+ build-bitbox03-cargo/thumbv8m.main-none-eabihf/release/bitbox03-*
# Build simulators for macos aarch64
build-macos:
@@ -318,11 +333,11 @@ jobs:
- name: Mark directory as safe
run: git config --global --add safe.directory $GITHUB_WORKSPACE
- - name: Build ${{ matrix.target }}
+ - name: Build documentation
run: |
make bootstrap
- (mkdir -p build; cd build; cmake -DDOC_GRAPHS=NO ..)
- make -j$(($(nproc)+1)) docs
+ cmake -S . -B build -DDOC_GRAPHS=NO
+ make -C build -j$(($(nproc)+1)) doc
report-artifacts:
needs: [build]
### .gitignore
@@ -25,3 +25,5 @@ bitbox02.egg-info
/py/bitbox02/build
/py/bitbox02/dist
.venv
+
+/config.mk
### AGENTS.md
@@ -110,10 +110,22 @@ run-unit-tests` and
Use `<context>: <summary>` for commit subjects. Inspect recent commits touching the affected
code and reuse the established context prefix and style.
-Write commits with a ≤50 character subject (including the prefix), blank line, and explanatory body;
-reference issues via `refs #1234` or `fixes #1234`. Keep patches atomic—avoid
-mixing formatting and logic. Pull requests should outline the change, list verification commands or
-screenshots, and flag hardware requirements. Wait to squash until reviews conclude.
+Write commits with a ≤50 character subject (including the prefix), blank line, and explanatory body.
+
+Start the body with why the change is needed: describe the concrete problem, limitation, or goal
+and its impact. Then explain the resulting behavior, using a before/after example when helpful.
+For behavior-preserving changes, say so and explain the maintenance or structural benefit. Include
+implementation details only when they explain the approach, a tradeoff, or an important constraint;
+avoid a file-by-file inventory or a list of edits without motivation.
+
+Write for a reader who has not seen the conversation. Use the relevant task context and available
+prior sessions to recover the motivation, then check the description against the final diff. Do not
+invent rationale or describe abandoned plans as implemented. Keep validation concise and secondary
+to the motivation and behavior, and distinguish completed checks from unverified behavior.
+
+Keep patches atomic—avoid mixing formatting and logic. Pull requests should outline the change,
+list verification commands or screenshots, and flag hardware requirements. Wait to squash until
+reviews conclude.
## Various
### BUILD.md
@@ -150,37 +150,91 @@ cd bitbox02-firmware
> git remote add upstream https://github.com/bitboxswiss/bitbox02-firmware
> ```
-### Build the firmware
+### Configure and build
-Run the following commands to enter the container and build the firmware:
+Inside the development container, run `make bootstrap` once to initialize submodules. From the
+host, use `./scripts/dev_exec.sh make <target>` for builds (or set
+`BITBOX_FW_EXEC_MODE=docker` to use the container toolchain).
```sh
-make dockerdev
-make bootstrap
+make config
make firmware
+make factorysetup
+make bootloader-stage0
+make bootloader-stage1
```
-`make bootstrap` must be run before compiling firmware. It initializes submodules and writes the
-local Cargo configuration needed for the firmware ARM targets.
+`make config` remembers selections in the git-ignored `config.mk`. It prompts for product, board,
+edition (`multi` or `btc-only`), debug builds (`no` or `yes`), probe software, and SWD speed in kHz,
+automatically selecting fields with one choice. `DEBUG` defaults to `no`; `SWD_SPEED` defaults to
+`4000` for all products and both probe backends.
+Probe hardware is fixed by the board:
-> [!TIP]
-> If you have multiple cores you can speed up compilation by passing `-j<N>`, for example `-j8`.
+| Product | Board | Probe | Software (default first) |
+| --- | --- | --- | --- |
+| `bitbox02` | `bitbox02` | J-Link | `jlink`, `openocd` |
+| `bitbox02nova` | `bitbox02nova` | J-Link | `jlink`, `openocd` |
+| `bitbox03` | `dev-kit` | ST-Link | `openocd` |
+| `bitbox03` | `testboard` | J-Link | `openocd`, `jlink` |
+| `bitbox03` | `bitbox03` (disabled) | J-Link | `jlink`, `openocd` |
-### Build the bootloader
+BitBox03 supports `dev-kit` and `testboard` selections; the testboard defaults to OpenOCD with
+the J-Link adapter and currently uses the dev-kit build target. The production board remains
+disabled until its build support is implemented. Both BitBox03 editions currently build the same stub.
-Run the following commands to enter the container and build the bootloader:
+Without a configuration, defaults are BitBox02, multi edition, and J-Link software. Explicit
+arguments run noninteractively; omitted fields use built-in defaults, independent of saved choices:
```sh
-make dockerdev
-make bootloader
+make config CONFIG_ARGS="--product bitbox02 --edition btc-only --probe-software openocd"
+make config CONFIG_ARGS="--product bitbox03 --board dev-kit"
+make config CONFIG_ARGS="--product bitbox03 --board testboard"
+make config CONFIG_ARGS="--product bitbox03 --debug yes"
+make config CONFIG_ARGS="--product bitbox03 --swd-speed 1000"
+make config CONFIG_ARGS="--defaults"
```
-> [!NOTE]
-> To create a bootloader for a development or a production device, use `make
-> bootloader-devdevice` or `make bootloader-production` respectively.
+SWD speed must be a positive integer. It is saved in `config.mk` and can also be overridden for a
+command, for example `make debug-server SWD_SPEED=1000`.
-> [!NOTE]
-> To run unsigned firmwares you need a development bootloader.
+The debug setting is also saved in `config.mk` and can be overridden per command with `DEBUG=yes`
+or `DEBUG=no`. Build, flash, and run commands use the same configured profile.
+
+Invalid input, EOF, or Ctrl-C preserves the previous file. Interactive product changes reset
+incompatible saved choices. Run configuration separately from builds: `make config firmware` is
+rejected because Make has already parsed the old settings. Configuration and compilation require
+neither installed probe software nor attached hardware.
+
+With `DEBUG=no`, BitBox02/Nova primary targets use `RelWithDebInfo` and BitBox03 uses Cargo release
+builds. `DEBUG=yes` selects debug builds for all products, with their existing RTT features.
+Firmware follows the selected edition; factorysetup is edition-independent; stage0/stage1 select
+development images for the configured product and edition. Build directories are flat:
+
+| Backend | Directory |
+| --- | --- |
+| BitBox02/Nova CMake | `build-<product>-cmake-<profile>` |
+| BitBox03 Cargo | `build-bitbox03-cargo` |
+
+For example, `build-bitbox02-cmake-relwithdebinfo/bin/firmware.elf`,
+`build-bitbox02nova-cmake-debug/bin/firmware-btc.elf`, and
+`build-bitbox03-cargo/thumbv8m.main-none-eabihf/debug/bitbox03-firmware`.
+Cargo receives an absolute target directory; backend-managed contents keep their native layouts.
+CMake Rust feature caches remain inside their product/profile directory. Images and editions share
+those caches. Switching products and switching back reuses previous results; changing probe
+software or SWD speed does not affect compilation. Configuration never cleans. `make clean` removes
+build trees but preserves `config.mk`. Host tests and simulators still use `build-build` and
+`build-build-noasan`.
+
+`bootloader-stage{0,1}-{production,development}` builds all BitBox02/Nova editions, sequentially in
+each product's directory. Production/development upgrade-firmware and upgrade-assets aliases are
+retained. Named product commands always use that product's directory.
+
+The old `firmware-btc`, `factory-setup`, per-product stage build shortcuts, and
+`bitbox03-*` build shortcuts are replaced by configuration plus the primary targets above. Backend
+targets and artifact basenames remain unchanged. Additional BitBox03 board/edition implementations
+are deferred.
+
+Pass `-j<N>` to speed up a build, for example `make -j8 firmware`. Top-level aliases run sequentially.
### Build the simulator
@@ -221,25 +275,67 @@ interface](https://developer.arm.com/documentation/101636/0100/Debug-and-Trace/J
Plug **both** the J-Link probe and the BitBox02 into the computer using USB. A
USB hub can be used.
-#### Flash bootloader using J-Link
-
-Load the bootloader by JLink (requires `JLinkExe` in `$PATH`).
+#### Flash or run a built image
+
+Build first, then use the matching command. These commands consume existing ELF files and report
+the build command if one is missing.
+
+| Image | Program, verify, reset/run | Run in GDB |
+| --- | --- | --- |
+| Firmware | `make flash-firmware` | `make run-firmware` |
+| Factorysetup | `make flash-factorysetup` (BitBox02/Nova only) | `make run-factorysetup` |
+| Stage0 | `make flash-bootloader-stage0` | `make run-bootloader-stage0` |
+| Stage1 | `make flash-bootloader-stage1` | `make run-bootloader-stage1` |
+
+`run-<image>` selects the ELF built by `make <image>` with the same `DEBUG` setting and immediately
+continues with GDB attached. For example, `make firmware DEBUG=yes` followed by
+`make run-firmware DEBUG=yes` builds and runs the debug image.
+
+The selected probe software chooses SEGGER Commander/GDB Server or OpenOCD. For BitBox02/Nova,
+OpenOCD uses `scripts/openocd-bitbox02.cfg` with the J-Link adapter; install an OpenOCD build
+containing the upstream
+[`target/atsame5x.cfg`](https://raw.githubusercontent.com/openocd-org/openocd/master/tcl/target/atsame5x.cfg)
+SAMD51 driver. BitBox03 uses the shared `scripts/openocd-bitbox03.cfg` with `target/stm32u5x.cfg`.
+The product configuration supplies the board's adapter through `PROBE_ADAPTER`: ST-Link for the
+dev-kit and J-Link for the testboard. Both share the reset and RTT work-area settings.
+
+Start `make debug-server` in a separate terminal and leave it running in the foreground before
+using a `run-*` command. SEGGER uses port 2331 with download verification enabled;
+OpenOCD uses 3333. GDB resets/halts, loads the ELF, verifies sections, sets VTOR, SP and PC from the
+image vector table, and issues `c`. It stays attached for Ctrl-C and breakpoints. The startup commands
+live in [`scripts/openocd.gdb`](scripts/openocd.gdb) and [`scripts/jlink.gdb`](scripts/jlink.gdb).
+Edit the script for your probe software to add breakpoints or comment out the final `c` to stop
+at entry. The helper selects the script and supplies the image vector address each time.
+
+Flashing and GDB use the built ELF directly for all products, including BitBox02/Nova stage1,
+whose header is finalized during the build. Load addresses come from the ELF. J-Link Commander
+uses `loadfile` with download verification. See the documentation for
+[J-Link ELF loading](https://kb.segger.com/J-Link_Commander#LoadFile),
+[GDB loading](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Target-Commands.html), and
+[SEGGER download verification](https://kb.segger.com/J-Link_GDB_Server#-vd).
+
+BitBox03 factorysetup is a RAM image and has no flash command. Use `make debug-server` followed by
+`make run-factorysetup` with the matching `DEBUG` setting to load and verify it in RAM,
+initialize the vector/entry state, and resume without resetting after loading. Flash commands
+reset/run after programming.
+
+#### Run J-Link Commander scripts
+
+Run `.jlink` scripts directly with `JLinkExe` from the repository root. For example, to read the
+shared memory area on BitBox02/Nova:
```sh
-make jlink-flash-bootloader
+JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -ExitOnError 1 \
+ -CommanderScript scripts/print-memory-shared.jlink
```
-> [!NOTE]
-> To flash a bootloader for a development device
-> `make jlink-flash-bootloader-development`.
-
-#### Flash firmware using J-Link
+Replace the script path with the required script, such as `scripts/set-new-screen.jlink`,
+`scripts/reset-version.jlink`, or `scripts/bb02-set-factory-randomness.jlink`. These maintenance
+scripts use BitBox02/Nova memory addresses. Run from the repository root so scripts can find
+relative paths to data files.
-Load the firmware by JLink:
-
-```sh
-make jlink-flash-firmware
-```
+`-speed` is the SWD speed in kHz. Direct `JLinkExe` commands do not read `config.mk`; specify the
+device and speed explicitly. `ATSAMD51J20` is the device for both BitBox02 and BitBox02 Nova.
#### Flash firmware using bootloader and python cli client
@@ -260,7 +356,7 @@ make flash-dev-firmware
make docs
```
-To view the results, open `build/docs/html/index.html` in a web browser.
+To view the results, open `build-bitbox02-cmake-relwithdebinfo/docs/html/index.html` in a web browser.
### Debugging
@@ -293,48 +389,21 @@ The graphical simulator does it with the flag `--preseed`. The original
simulator loads it if you restore from mnemonic.
-#### Debugging using the J-Link probe and GDB
-
-The *debug firmware* enables pretty printing of panics over [RTT](https://www.segger.com/products/debug-probes/j-link/technology/about-real-time-transfer/).
+#### Debugging and RTT
-Run the following commands to build the debug firmware.
+Build the desired profile, start `make debug-server` in another terminal, then use the matching
+run command. For example, use `make firmware` followed by `make run-firmware`, or
+`make factorysetup DEBUG=yes` followed by `make run-factorysetup DEBUG=yes`.
-```sh
-make dockerdev
-make firmware-debug
-```
-
-Run the following command to run the J-Link GDB Server.
-
-```sh
-make jlink-gdb-server
-```
+Images with RTT enabled provide panic logging over
+[RTT](https://www.segger.com/products/debug-probes/j-link/technology/about-real-time-transfer/).
-> [!IMPORTANT]
-> The J-Link GDB Server must be left running in the background.
+Let the image run until its RTT channels have initialized. With OpenOCD, interrupt GDB, issue
+`rtt_start`, and continue with `c`. This starts channel 0 on port 19021 and channel 1 (API traffic)
+on port 19022. SEGGER provides RTT on port 19021. Connect with `make rtt-client`.
+RTT availability follows existing build features; it is not enabled by selecting a probe backend.
-Run the following command to connect with telnet to the J-Link GDB Server to
-see the RTT output.
-
-```sh
-make rtt-client
-```
-
-Run the following command to run GDB. GDB will connect to the J-Link GDB
-server, flash the debug firmware and then start execution from the bootloader
-(as if the device was just plugged in).
-
-```sh
-make run-debug
-```
-
-> [!TIP]
-> After rebuilding the firmware, exit GDB and rerun `run-debug` to flash and reset the device.
-
-> [!TIP]
-> The initial set of GDB commands that are run are specified in the [gdb init
-> script](./scripts/jlink.gdb). You may want to modify it if you are debugging
-> something specific.
+After rebuilding, exit GDB and rerun the matching `run-*` command to reload the image.
> [!TIP]
> In debug builds you can use the following functions to log:
### CMakeLists.txt
@@ -308,7 +308,6 @@ endif()
add_subdirectory(external)
add_subdirectory(src)
-add_subdirectory(scripts)
#-----------------------------------------------------------------------------
# Build Documentation
### Makefile
@@ -1,155 +1,83 @@
# SPDX-License-Identifier: Apache-2.0
-# This makefile is used as a command runner and not for tracking dependencies between recipies
-
-UNAME_S := $(shell uname -s)
-
+# Command aliases, not a dependency graph. Keep setup explicit in recipes.
.DEFAULT_GOAL := firmware
+.NOTPARALLEL:
SANITIZE ?= ON
+ifneq ($(filter config,$(MAKECMDGOALS)),)
+ifneq ($(words $(MAKECMDGOALS)),1)
+$(error Run make config separately so subsequent commands use the saved settings)
+endif
+else
+-include config.mk
+endif
+PRODUCT ?= bitbox02
+BOARD ?= $(if $(filter bitbox03,$(PRODUCT)),dev-kit,$(PRODUCT))
+EDITION ?= multi
+DEBUG ?= no
+PROBE_SOFTWARE ?= $(if $(filter bitbox03,$(PRODUCT)),openocd,jlink)
+SWD_SPEED ?= 4000
+CONFIG_OPTIONS = --product "$(PRODUCT)" --board "$(BOARD)" --edition "$(EDITION)" --debug "$(DEBUG)" --probe-software "$(PROBE_SOFTWARE)" --swd-speed "$(SWD_SPEED)"
+
+config:
+ python3 scripts/build_config.py $(CONFIG_ARGS)
+
bootstrap:
git submodule update --init --recursive
./scripts/bootstrap-cargo-config
-build/Makefile:
- ./scripts/bootstrap-cargo-config
- mkdir -p build
- cd build && cmake -DCMAKE_TOOLCHAIN_FILE=arm.cmake ..
- $(MAKE) -C py/bitbox02
-
-build-debug/Makefile:
- ./scripts/bootstrap-cargo-config
- mkdir -p build-debug
- cd build-debug && cmake -DCMAKE_TOOLCHAIN_FILE=arm.cmake -DCMAKE_BUILD_TYPE=DEBUG ..
- $(MAKE) -C py/bitbox02
-
-build-build/Makefile:
+IMAGES := firmware factorysetup bootloader-stage0 bootloader-stage1
+FLASH_IMAGES := $(IMAGES)
+ifeq ($(PRODUCT),bitbox03)
+FLASH_IMAGES := $(filter-out factorysetup,$(FLASH_IMAGES))
+endif
+BUILD_TARGETS := $(IMAGES) \
+ bootloader-stage0-production bootloader-stage0-development \
+ bootloader-stage1-production bootloader-stage1-development \
+ bootloader-upgrade-assets bootloader-upgrade-assets-development docs rust-docs
+BUILD_TARGETS += $(foreach product,bitbox02 bitbox02nova,$(foreach edition,multi btconly,\
+ firmware-blupgrade-$(product)-$(edition) firmware-blupgrade-$(product)-$(edition)-development))
+
+$(BUILD_TARGETS):
+ +python3 scripts/build_product.py $(CONFIG_OPTIONS) $@
+
+$(addprefix flash-,$(FLASH_IMAGES)):
+ python3 scripts/probe.py $(CONFIG_OPTIONS) flash $(patsubst flash-%,%,$@)
+$(addprefix run-,$(IMAGES)):
+ python3 scripts/probe.py $(CONFIG_OPTIONS) run $(patsubst run-%,%,$@)
+debug-server:
+ python3 scripts/probe.py $(CONFIG_OPTIONS) server
+
+build-build:
./scripts/bootstrap-cargo-config
- mkdir -p build-build
- cd build-build && cmake .. -DSANITIZE_ADDRESS=$(SANITIZE) -DSANITIZE_UNDEFINED=$(SANITIZE)
+ test -f build-build/Makefile || cmake -S . -B build-build -DSANITIZE_ADDRESS=$(SANITIZE) -DSANITIZE_UNDEFINED=$(SANITIZE)
$(MAKE) -C py/bitbox02
# ubsan/asan not supported with simulators and rust unit tests
-build-build-noasan/Makefile:
+build-build-noasan:
./scripts/bootstrap-cargo-config
- mkdir -p build-build-noasan
- cd build-build-noasan && cmake .. -DSANITIZE_ADDRESS=OFF -DSANITIZE_UNDEFINED=OFF
+ test -f build-build-noasan/Makefile || cmake -S . -B build-build-noasan -DSANITIZE_ADDRESS=OFF -DSANITIZE_UNDEFINED=OFF
$(MAKE) -C py/bitbox02
-# Directory for building for "host" machine according to gcc convention
-build: build/Makefile
-
-# Directory for building debug build for "host" machine according to gcc convention
-build-debug: build-debug/Makefile
-
-# Directory for building for "build" machine according to gcc convention
-build-build: build-build/Makefile
-
-# Directory for building for "build" machine according to gcc convention
-# Should only be used for rust unit tests since we didn't add support to
-# address santizers when they link code compiled with gcc.
-build-build-noasan: build-build-noasan/Makefile
-
-firmware: | build
- $(MAKE) -C build firmware.elf
-firmware-btc: | build
- $(MAKE) -C build firmware-btc.elf
-firmware-debug: | build-debug
- $(MAKE) -C build-debug firmware.elf
-
-firmware-blupgrade-bitbox02-btconly: | build
- $(MAKE) -C build firmware-blupgrade-bitbox02-btconly.elf
-firmware-blupgrade-bitbox02-multi: | build
- $(MAKE) -C build firmware-blupgrade-bitbox02-multi.elf
-firmware-blupgrade-bitbox02nova-btconly: | build
- $(MAKE) -C build firmware-blupgrade-bitbox02nova-btconly.elf
-firmware-blupgrade-bitbox02nova-multi: | build
- $(MAKE) -C build firmware-blupgrade-bitbox02nova-multi.elf
-firmware-blupgrade-bitbox02-btconly-development: | build
- $(MAKE) -C build firmware-blupgrade-bitbox02-btconly-development.elf
-firmware-blupgrade-bitbox02-multi-development: | build
- $(MAKE) -C build firmware-blupgrade-bitbox02-multi-development.elf
-firmware-blupgrade-bitbox02nova-btconly-development: | build
- $(MAKE) -C build firmware-blupgrade-bitbox02nova-btconly-development.elf
-firmware-blupgrade-bitbox02nova-multi-development: | build
- $(MAKE) -C build firmware-blupgrade-bitbox02nova-multi-development.elf
-
-# Stage0 aggregate targets build all production/development variants.
-bootloader-stage0: | build
- $(MAKE) -C build bootloader-stage0
-bootloader-stage0-production: | build
- $(MAKE) -C build bootloader-stage0-production
-bootloader-stage0-development: | build
- $(MAKE) -C build bootloader-stage0-development
-
-# Per-product stage0 targets build their matching ELF/bin.
-bootloader-stage0-bitbox02-btconly-development: | build
- $(MAKE) -C build bootloader-stage0-bitbox02-btconly-development.elf
-bootloader-stage0-bitbox02-btconly-production: | build
- $(MAKE) -C build bootloader-stage0-bitbox02-btconly-production.elf
-bootloader-stage0-bitbox02-multi-development: | build
- $(MAKE) -C build bootloader-stage0-bitbox02-multi-development.elf
-bootloader-stage0-bitbox02-multi-production: | build
- $(MAKE) -C build bootloader-stage0-bitbox02-multi-production.elf
-bootloader-stage0-bitbox02nova-btconly-development: | build
- $(MAKE) -C build bootloader-stage0-bitbox02nova-btconly-development.elf
-bootloader-stage0-bitbox02nova-btconly-production: | build
- $(MAKE) -C build bootloader-stage0-bitbox02nova-btconly-production.elf
-bootloader-stage0-bitbox02nova-multi-development: | build
- $(MAKE) -C build bootloader-stage0-bitbox02nova-multi-development.elf
-bootloader-stage0-bitbox02nova-multi-production: | build
- $(MAKE) -C build bootloader-stage0-bitbox02nova-multi-production.elf
-
-# Stage1 aggregate targets build all production/development variants.
-# The per-product stage1 targets build their matching ELF/bin.
-bootloader-stage1: | build
- $(MAKE) -C build bootloader-stage1
-bootloader-stage1-production: | build
- $(MAKE) -C build bootloader-stage1-production
-bootloader-stage1-development: | build
- $(MAKE) -C build bootloader-stage1-development
-bootloader-stage1-bitbox02-btconly-development: | build
- $(MAKE) -C build bootloader-stage1-bitbox02-btconly-development.elf
-bootloader-stage1-bitbox02-btconly-production: | build
- $(MAKE) -C build bootloader-stage1-bitbox02-btconly-production.elf
-bootloader-stage1-bitbox02-multi-development: | build
- $(MAKE) -C build bootloader-stage1-bitbox02-multi-development.elf
-bootloader-stage1-bitbox02-multi-production: | build
- $(MAKE) -C build bootloader-stage1-bitbox02-multi-production.elf
-bootloader-stage1-bitbox02nova-btconly-development: | build
- $(MAKE) -C build bootloader-stage1-bitbox02nova-btconly-development.elf
-bootloader-stage1-bitbox02nova-btconly-production: | build
- $(MAKE) -C build bootloader-stage1-bitbox02nova-btconly-production.elf
-bootloader-stage1-bitbox02nova-multi-development: | build
- $(MAKE) -C build bootloader-stage1-bitbox02nova-multi-development.elf
-bootloader-stage1-bitbox02nova-multi-production: | build
- $(MAKE) -C build bootloader-stage1-bitbox02nova-multi-production.elf
-
-bootloader-upgrade-assets: | build
- $(MAKE) -C build bootloader-upgrade-assets
-bootloader-upgrade-assets-development: | build
- $(MAKE) -C build bootloader-upgrade-assets-development
-
-factory-setup: | build
- $(MAKE) -C build factory-setup.elf
-factory-setup-debug: | build-debug
- $(MAKE) -C build-debug factory-setup.elf
-docs: | build
- $(MAKE) -C build doc
-rust-docs: | build
- $(MAKE) -C build rust-docs
-simulator: | build-build-noasan
+simulator:
+ $(MAKE) build-build-noasan
$(MAKE) -C build-build-noasan simulator
-simulator-graphical: | build-build-noasan
+simulator-graphical:
+ $(MAKE) build-build-noasan
$(MAKE) -C build-build-noasan simulator-graphical
-simulator-graphical-bb03: | build-build-noasan
+simulator-graphical-bb03:
+ $(MAKE) build-build-noasan
$(MAKE) -C build-build-noasan simulator-graphical-bb03
-run-simulator: | simulator
+run-simulator:
+ $(MAKE) simulator
./build-build-noasan/bin/simulator
-unit-test: | build-build
+unit-test:
+ $(MAKE) build-build
$(MAKE) -C build-build
# Must compile C tests before running them
-run-unit-tests: | build-build
+run-unit-tests:
+ $(MAKE) build-build
if command -v setarch >/dev/null 2>&1 && setarch "$$(uname -m)" -R true >/dev/null 2>&1; then \
CTEST_OUTPUT_ON_FAILURE=1 setarch "$$(uname -m)" -R $(MAKE) -C build-build test; \
else \
@@ -161,77 +89,22 @@ run-unit-tests: | build-build
run-rust-unit-tests:
./scripts/bootstrap-cargo-config
cargo test --manifest-path src/rust/Cargo.toml --all-features -- --test-threads 1
-run-rust-clippy: | build-build-noasan
+run-rust-clippy:
+ $(MAKE) build-build-noasan
${MAKE} -C build-build-noasan rust-clippy
-#./build/bin/test_ui_component_gestures;
run-valgrind-on-unit-tests:
$(MAKE) unit-test
bash -ec 'for exe in build-build/bin/test_*; do valgrind --leak-check=yes --track-origins=yes --error-exitcode=1 --exit-on-first-error=yes $$exe; done'
+flash-dev-firmware: FIRMWARE_BIN = build-$(PRODUCT)-cmake-$(if $(filter yes,$(DEBUG)),debug,relwithdebinfo)/bin/firmware$(if $(filter btc-only,$(EDITION)),-btc).bin
flash-dev-firmware:
- ./py/load_firmware.py build/bin/firmware.bin --yes
-
-# Per-product development stage0/stage1 J-Link wrappers flash already-built images.
-jlink-flash-bootloader-stage0-bitbox02-btconly-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage0-bitbox02-btconly-development.jlink
-jlink-flash-bootloader-stage0-bitbox02-multi-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage0-bitbox02-multi-development.jlink
-jlink-flash-bootloader-stage0-bitbox02nova-btconly-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage0-bitbox02nova-btconly-development.jlink
-jlink-flash-bootloader-stage0-bitbox02nova-multi-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage0-bitbox02nova-multi-development.jlink
-jlink-flash-bootloader-stage1-bitbox02-btconly-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage1-bitbox02-btconly-development.jlink
-jlink-flash-bootloader-stage1-bitbox02-multi-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage1-bitbox02-multi-development.jlink
-jlink-flash-bootloader-stage1-bitbox02nova-btconly-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage1-bitbox02nova-btconly-development.jlink
-jlink-flash-bootloader-stage1-bitbox02nova-multi-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/bootloader-stage1-bitbox02nova-multi-development.jlink
-
-jlink-flash-firmware: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/firmware.jlink
-jlink-flash-firmware-btc: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/firmware-btc.jlink
-jlink-flash-factory-setup: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/factory-setup.jlink
-jlink-flash-firmware-debug: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build-debug/scripts/firmware.jlink
+ @case "$(PRODUCT)" in bitbox02|bitbox02nova) ;; *) echo "flash-dev-firmware is only supported on BitBox02/Nova" >&2; exit 1 ;; esac
+ @case "$(EDITION)" in multi|btc-only) ;; *) echo "Invalid edition: $(EDITION)" >&2; exit 1 ;; esac
+ @case "$(DEBUG)" in yes|no) ;; *) echo "Invalid debug setting: $(DEBUG)" >&2; exit 1 ;; esac
+ @test -f "$(FIRMWARE_BIN)" || { echo "Image not found: $(FIRMWARE_BIN). Build it first with: make firmware DEBUG=$(DEBUG)" >&2; exit 1; }
+ python3 py/load_firmware.py "$(FIRMWARE_BIN)" --yes
-jlink-flash-firmware-blupgrade-bitbox02-btconly-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/firmware-blupgrade-bitbox02-btconly-development.jlink
-jlink-flash-firmware-blupgrade-bitbox02-multi-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/firmware-blupgrade-bitbox02-multi-development.jlink
-jlink-flash-firmware-blupgrade-bitbox02nova-btconly-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/firmware-blupgrade-bitbox02nova-btconly-development.jlink
-jlink-flash-firmware-blupgrade-bitbox02nova-multi-development: | build
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./build/scripts/firmware-blupgrade-bitbox02nova-multi-development.jlink
-
-jlink-flash-set-new-screen:
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./scripts/set-new-screen.jlink
-jlink-flash-set-original-screen:
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./scripts/set-original-screen.jlink
-jlink-flash-reset-version:
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./scripts/reset-version.jlink
-jlink-flash-set-securechip-optiga:
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./scripts/set-securechip-optiga.jlink
-jlink-flash-set-bb02plus:
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./scripts/set-bb02plus.jlink
-jlink-flash-bb02-set-factory-randomness:
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./scripts/bb02-set-factory-randomness.jlink
-jlink-erase-firmware-quick:
- JLinkExe -NoGui 1 -if SWD -device ATSAMD51J20 -speed 4000 -autoconnect 1 -CommanderScript ./scripts/erase-firmware-quick.jlink
-jlink-gdb-server:
- JLinkGDBServer -nogui -if SWD -device ATSAMD51J20 -speed 4000
rtt-client:
telnet localhost 19021
-run-debug:
- arm-none-eabi-gdb -x scripts/jlink.gdb build-debug/bin/firmware.elf
-run-bootloader-stage0:
- arm-none-eabi-gdb -x scripts/jlink-bootloader-stage0.gdb build/bin/bootloader-stage0-bitbox02-multi-development.elf
-run-bootloader-stage1:
- arm-none-eabi-gdb -x scripts/jlink-bootloader-stage1.gdb build/bin/bootloader-stage1-bitbox02-multi-development.elf
-run-factory-setup-debug:
- arm-none-eabi-gdb -x scripts/jlink.gdb build-debug/bin/factory-setup.elf
dockerinit:
./scripts/container.sh build --pull -t shiftcrypto/firmware_v2:$(shell cat .containerversion) .
dockerpull:
@@ -246,56 +119,22 @@ generate-atecc608-config:
cd tools/atecc608 && go run main.go
ci:
./.ci/ci
-prepare-tidy: | build build-build
- $(MAKE) -C build rust-cbindgen
+prepare-tidy:
+ +python3 scripts/build_product.py $(CONFIG_OPTIONS) prepare-tidy
+ $(MAKE) build-build
$(MAKE) -C build-build rust-cbindgen
clean:
- rm -rf build build-build build-debug build-build-noasan src/rust/target
+ rm -rf build build-debug build-build build-build-noasan build-bitbox02-cmake-* build-bitbox02nova-cmake-* build-bitbox03-cargo src/rust/target
# When you vendor rust libs avoid duplicates
vendor-rust-deps:
./external/vendor-rust.sh
-# It is important that cargo is executed from `src/rust` so that it loads the
-# configuration for the vendored dependencies.
-bitbox03-boot0:
- (cd src/rust; cargo bitbox03-boot0-stm32u5a9j-dk)
- arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-boot0
- arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-boot0
-bitbox03-boot0-release:
- (cd src/rust; cargo bitbox03-boot0-stm32u5a9j-dk-release)
- arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-boot0
- arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-boot0
-bitbox03-boot1:
- (cd src/rust; cargo bitbox03-boot1-stm32u5a9j-dk)
- python3 scripts/image_header.py finalize-elf src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-boot1
- arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-boot1
- arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-boot1
-bitbox03-boot1-release:
- (cd src/rust; cargo bitbox03-boot1-stm32u5a9j-dk-release)
- python3 scripts/image_header.py finalize-elf src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-boot1
- arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-boot1
- arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-boot1
-bitbox03-factorysetup:
- (cd src/rust; cargo bitbox03-factorysetup-stm32u5a9j-dk)
- arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-factorysetup
- arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-factorysetup
-bitbox03-factorysetup-release:
- (cd src/rust; cargo bitbox03-factorysetup-stm32u5a9j-dk-release)
- arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-factorysetup
- arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-factorysetup
-bitbox03-firmware:
- (cd src/rust; cargo bitbox03-firmware-stm32u5a9j-dk)
- python3 scripts/image_header.py finalize-elf src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-firmware
- arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-firmware
- arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-firmware
-bitbox03-firmware-release:
- (cd src/rust; cargo bitbox03-firmware-stm32u5a9j-dk-release)
- python3 scripts/image_header.py finalize-elf src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-firmware
- arm-none-eabi-size src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-firmware
- arm-none-eabi-size -Ax src/rust/target/thumbv8m.main-none-eabihf/release/bitbox03-firmware
-
-flash-bitbox03-boot0-openocd:
- ./scripts/flash-bitbox03-boot0-openocd.sh
-flash-bitbox03-boot1-openocd:
- ./scripts/flash-bitbox03-boot1-openocd.sh
+# Mark all command aliases phony, including generated image/variant names.
+.PHONY: $(BUILD_TARGETS) $(addprefix flash-,$(FLASH_IMAGES)) $(addprefix run-,$(IMAGES))
+.PHONY: config bootstrap debug-server \
+ build-build build-build-noasan simulator simulator-graphical simulator-graphical-bb03 \
+ run-simulator unit-test run-unit-tests run-rust-unit-tests run-rust-clippy \
+ run-valgrind-on-unit-tests flash-dev-firmware \
+ rtt-client dockerinit dockerpull dockerdev dockerrel generate-protobufs \
+ generate-atecc608-config ci prepare-tidy clean vendor-rust-deps
### scripts/CMakeLists.txt
@@ -1,31 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0
-
-if(PYTHONINTERP_FOUND)
- # This template flashes with an offset
- foreach(target ${FIRMWARES})
- execute_process(
- COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/expand_template ${CMAKE_CURRENT_SOURCE_DIR}/template-firmware.jlink file=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.bin -o ${target}.jlink
- WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
- )
- endforeach()
- foreach(target ${BB02_BLUPD_FIRMWARE_TARGETS})
- execute_process(
- COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/expand_template ${CMAKE_CURRENT_SOURCE_DIR}/template-firmware.jlink file=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.bin -o ${target}.jlink
- WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
- )
- endforeach()
-
- foreach(target ${BB02_STAGE0_TARGETS})
- execute_process(
- COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/expand_template ${CMAKE_CURRENT_SOURCE_DIR}/template-stage0.jlink file=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.bin -o ${target}.jlink
- WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
- )
- endforeach()
-
- foreach(target ${BB02_STAGE1_TARGETS})
- execute_process(
- COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/expand_template ${CMAKE_CURRENT_SOURCE_DIR}/template-stage1.jlink file=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.bin -o ${target}.jlink
- WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
- )
- endforeach()
-endif()
### scripts/build_config.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: Apache-2.0
+
+"""Product configuration and shared build/probe image metadata."""
+
+from __future__ import annotations
+
+import argparse
+from dataclasses import dataclass
+import os
+from pathlib import Path
+import sys
+import tempfile
+
+ROOT = Path(__file__).resolve().parent.parent
+PRODUCTS = ("bitbox02", "bitbox02nova", "bitbox03")
+CHIP_TYPES = {
+ "bitbox02": "ATSAMD51J20",
+ "bitbox02nova": "ATSAMD51J20",
+ "bitbox03": "STM32U5A9NJ",
+}
+EDITIONS = ("multi", "btc-only")
+IMAGES = ("firmware", "factorysetup", "bootloader-stage0", "bootloader-stage1")
+CARGO_TARGET = "thumbv8m.main-none-eabihf"
+DEFAULT_SWD_SPEED = 4000
+# Disabled boards retain their intended probe mapping here.
+BOARDS = {
+ "bitbox02": {"bitbox02": ("jlink", True)},
+ "bitbox02nova": {"bitbox02nova": ("jlink", True)},
+ "bitbox03": {
+ "dev-kit": ("stlink", True),
+ "testboard": ("jlink", True),
+ "bitbox03": ("jlink", False),
+ },
+}
+
+
+@dataclass(frozen=True)
+class Config:
+ product: str = "bitbox02"
+ board: str = "bitbox02"
+ edition: str = "multi"
+ probe_software: str = "jlink"
+ swd_speed: int = DEFAULT_SWD_SPEED
+ debug: str = "no"
+
+ @property
+ def profile(self) -> str:
+ if self.debug == "yes":
+ return "debug"
+ return "release" if self.product == "bitbox03" else "relwithdebinfo"
+
+ @property
+ def chip_type(self) -> str:
+ return CHIP_TYPES[self.product]
+
+ @property
+ def probe_hardware(self) -> str:
+ return BOARDS[self.product][self.board][0]
+
+ @property
+ def software_choices(self) -> tuple[str, ...]:
+ return ("openocd",) if self.probe_hardware == "stlink" else ("jlink", "openocd")
+
+ def validate(self) -> Config:
+ if self.product not in BOARDS:
+ raise ValueError(f"Unknown product: {self.product}")
+ if self.board not in BOARDS[self.product]:
+ raise ValueError(f"Invalid board {self.board} for {self.product}")
+ if not BOARDS[self.product][self.board][1]:
+ raise ValueError(f"Board {self.board} is disabled (not implemented)")
+ if self.edition not in EDITIONS:
+ raise ValueError(f"Invalid edition: {self.edition}")
+ if self.debug not in ("no", "yes"):
+ raise ValueError(f"Invalid debug setting: {self.debug}")
+ if self.probe_software not in self.software_choices:
+ raise ValueError(
+ f"{self.board} requires probe software: {', '.join(self.software_choices)}"
+ )
+ if self.swd_speed <= 0:
+ raise ValueError("SWD speed must be a positive integer in kHz")
+ return self
+
+ def describe(self) -> None:
+ print(f"Product: {self.product}; board: {self.board}; edition: {self.edition}")
+ print(f"Debug: {self.debug}; profile: {self.profile}")
+ print(f"Probe hardware: {self.probe_hardware}; software: {self.probe_software}")
+ print(f"SWD speed: {self.swd_speed} kHz")
+ if self.product == "bitbox03":
+ print("BitBox03: multi and btc-only currently build the same stub.")
+ if self.board == "testboard":
+ print("BitBox03 testboard currently uses the dev-kit build target.")
+
+
+def defaults(product: str = "bitbox02") -> Config:
+ if product not in PRODUCTS:
+ raise ValueError(f"Unknown product: {product}")
+ return Config(
+ product,
+ "dev-kit" if product == "bitbox03" else product,
+ "multi",
+ "openocd" if product == "bitbox03" else "jlink",
+ )
+
+
+def add_config_arguments(parser: argparse.ArgumentParser) -> None:
+ parser.add_argument("--product", choices=PRODUCTS)
+ parser.add_argument("--board")
+ parser.add_argument("--edition", choices=EDITIONS)
+ parser.add_argument("--debug", choices=("no", "yes"), help="Debug build (default: no)")
+ parser.add_argument("--probe-software", choices=("jlink", "openocd"))
+ parser.add_argument("--swd-speed", type=int, help="SWD speed in kHz (default: 4000)")
+
+
+def from_args(args: argparse.Namespace) -> Config:
+ base = defaults(args.product or "bitbox02")
+ return Config(
+ base.product,
+ args.board or base.board,
+ args.edition or base.edition,
+ args.probe_software or base.probe_software,
+ args.swd_speed if args.swd_speed is not None else base.swd_speed,
+ args.debug or base.debug,
+ ).validate()
+
+
+def read_config(path: Path) -> dict[str, str]:
+ if not path.exists():
+ return {}
+ result = {}
+ for line in path.read_text(encoding="utf-8").splitlines():
+ key, sep, value = line.partition("=")
+ if sep and key.strip() in (
+ "PRODUCT",
+ "BOARD",
+ "EDITION",
+ "DEBUG",
+ "PROBE_SOFTWARE",
+ "SWD_SPEED",
+ ):
+ result[key.strip()] = value.strip()
+ return result
+
+
+def choose(label: str, choices: tuple[str, ...], saved: str) -> str:
+ default = saved if saved in choices else choices[0]
+ if len(choices) == 1:
+ print(f"{label}: {default}")
+ return default
+ answer = input(f"{label} ({', '.join(choices)}) [{default}]: ").strip()
+ if answer and answer not in choices:
+ raise ValueError(f"Invalid {label.lower()}: {answer}")
+ return answer or default
+
+
+def interactive(saved: dict[str, str]) -> Config:
+ product = choose("Product", PRODUCTS, saved.get("PRODUCT", "bitbox02"))
+ base = defaults(product)
+ boards = BOARDS[product]
+ for board, (probe, enabled) in boards.items():
+ if not enabled:
+ print(f"Board {board}: disabled (not implemented; {probe} probe)")
+ board = choose(
+ "Board", tuple(b for b in boards if boards[b][1]), saved.get("BOARD", base.board)
+ )
+ edition = choose("Edition", EDITIONS, saved.get("EDITION", base.edition))
+ debug = choose("Debug", ("no", "yes"), saved.get("DEBUG", base.debug))
+ config = Config(product, board, edition, base.probe_software)
+ print(f"Probe hardware: {config.probe_hardware} (derived from board)")
+ software = choose(
+ "Probe software", config.software_choices, saved.get("PROBE_SOFTWARE", base.probe_software)
+ )
+ saved_speed = saved.get("SWD_SPEED", str(base.swd_speed))
+ speed = input(f"SWD speed in kHz [{saved_speed}]: ").strip() or saved_speed
+ return Config(product, board, edition, software, int(speed), debug).validate()
+
+
+def save_config(path: Path, config: Config) -> None:
+ config.validate()
+ # Replace only after all input has been validated, on the same filesystem.
+ name = None
+ try:
+ with tempfile.NamedTemporaryFile(
+ mode="w", dir=path.parent, prefix=".config-", delete=False, encoding="utf-8"
+ ) as outfile:
+ name = outfile.name
+ outfile.write("# Generated by make config. Local configuration; do not commit.\n")
+ for key in ("product", "board", "edition", "debug", "probe_software", "swd_speed"):
+ outfile.write(f"{key.upper()} = {getattr(config, key)}\n")
+ os.replace(name, path)
+ finally:
+ if name and os.path.exists(name):
+ os.unlink(name)
+
+
+def build_dir(config: Config, profile: str | None = None, root: Path = ROOT) -> Path:
+ if config.product == "bitbox03":
+ return root / "build-bitbox03-cargo"
+ return root / f"build-{config.product}-cmake-{profile or config.profile}"
+
+
+@dataclass(frozen=True)
+class Image:
+ name: str
+ elf: Path
+ vectors: int
+ ram: bool = False
+
+
+def image_info(
+ config: Config,
+ image: str,
+ root: Path = ROOT,
+ variant: str = "development",
+) -> Image:
+ config.validate()
+ profile = config.profile
+ if image not in IMAGES:
+ raise ValueError(f"Unknown image: {image}")
+ if config.product == "bitbox03":
+ name = {
+ "bootloader-stage0": "bitbox03-boot0",
+ "bootloader-stage1": "bitbox03-boot1",
+ "firmware": "bitbox03-firmware",
+ "factorysetup": "bitbox03-factorysetup",
+ }[image]
+ address = {
+ "bootloader-stage0": 0x08002000,
+ "bootloader-stage1": 0x08010000,
+ "firmware": 0x08052000,
+ "factorysetup": 0x20040000,
+ }[image]
+ vectors = address + (0x400 if image in ("firmware", "bootloader-stage1") else 0)
+ elf = build_dir(config, root=root) / CARGO_TARGET / profile / name
+ return Image(name, elf, vectors, image == "factorysetup")
+ if image.startswith("bootloader-"):
+ name = f"{image}-{config.product}-{config.edition.replace('-', '')}-{variant}"
+ address = 0 if image == "bootloader-stage0" else 0x2000
+ else:
+ name = (
+ "factory-setup"
+ if image == "factorysetup"
+ else ("firmware-btc" if config.edition == "btc-only" else "firmware")
+ )
+ address = 0x10000
+ vectors = address + (0x400 if image == "bootloader-stage1" else 0)
+ directory = build_dir(config, profile, root) / "bin"
+ return Image(name, directory / f"{name}.elf", vectors)
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ add_config_arguments(parser)
+ parser.add_argument("--defaults", action="store_true")
+ args = parser.parse_args()
+ try:
+ config = (
+ from_args(args) if len(sys.argv) > 1 else interactive(read_config(ROOT / "config.mk"))
+ )
+ save_config(ROOT / "config.mk", config)
+ config.describe()
+ except (EOFError, KeyboardInterrupt):
+ print("\nConfiguration cancelled; config.mk unchanged.", file=sys.stderr)
+ return 1
+ except (ValueError, OSError) as exc:
+ print(f"Configuration not saved: {exc}", file=sys.stderr)
+ return 1
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
### scripts/build_product.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: Apache-2.0
+
+"""Dispatch command aliases to the existing CMake and Cargo targets."""
+
+import argparse
+import os
+from pathlib import Path
+import shlex
+import subprocess
+import sys
+
+from build_config import (
+ ROOT,
+ IMAGES,
+ Config,
+ add_config_arguments,
+ build_dir,
+ defaults,
+ from_args,
+ image_info,
+)
+
+
+def run(command: list[str], cwd: Path = ROOT) -> None:
+ print(shlex.join(command), flush=True)
+ # GNU make puts short flags first, followed by a space and other options.
+ # Preserve an empty first field so long options are not treated as short flags.
+ flags = os.environ.get("MAKEFLAGS", "").split(" ", 1)[0]
+ if "n" in flags:
+ return
+ # Keep GNU make's jobserver available to recursive backend builds.
+ subprocess.run(command, cwd=cwd, check=True, close_fds=False)
+
+
+def cmake(config: Config, profile: str, targets: list[str]) -> None:
+ directory = build_dir(config, profile)
+ run([str(ROOT / "scripts/bootstrap-cargo-config")])
+ if not (directory / "Makefile").exists():
+ run(
+ [
+ "cmake",
+ "-S",
+ str(ROOT),
+ "-B",
+ str(directory),
+ "-DCMAKE_TOOLCHAIN_FILE=arm.cmake",
+ f"-DCMAKE_BUILD_TYPE={profile.upper()}",
+ ]
+ )
+ run(["make", "-C", str(ROOT / "py/bitbox02")])
+ run(["make", "-C", str(directory), *targets])
+
+
+def build_image(config: Config, image: str, variant: str = "development") -> None:
+ profile = config.profile
+ info = image_info(config, image, variant=variant)
+ if config.product != "bitbox03":
+ cmake(config, profile, [f"{info.name}.elf"])
+ return
+ config.describe()
+ run([str(ROOT / "scripts/bootstrap-cargo-config")])
+ # Execute Cargo from src/rust so it loads the vendored dependency configuration.
+ suffix = "-release" if profile == "release" else ""
+ run(
+ [
+ "cargo",
+ f"{info.name}-stm32u5a9j-dk{suffix}",
+ *shlex.split(os.environ.get("CARGOFLAGS", "")),
+ "--target-dir",
+ str(build_dir(config)),
+ ],
+ ROOT / "src/rust",
+ )
+ if image in ("firmware", "bootloader-stage1"):
+ run([sys.executable, "scripts/image_header.py", "finalize-elf", str(info.elf)])
+ run(["arm-none-eabi-size", str(info.elf)])
+ run(["arm-none-eabi-size", "-Ax", str(info.elf)])
+
+
+def dispatch(config: Config, target: str) -> None:
+ if target in IMAGES:
+ build_image(config, target)
+ elif target.startswith("firmware-blupgrade-"):
+ product = target.split("-")[2]
+ cmake(defaults(product), "relwithdebinfo", [target + ".elf"])
+ elif target in (
+ "bootloader-stage0-production",
+ "bootloader-stage0-development",
+ "bootloader-stage1-production",
+ "bootloader-stage1-development",
+ ):
+ image, variant = target.rsplit("-", 1)
+ # These aggregates retain their original all-product/all-edition coverage.
+ for product in ("bitbox02", "bitbox02nova"):
+ for edition in ("multi", "btc-only"):
+ base = defaults(product)
+ build_image(
+ Config(product, base.board, edition, base.probe_software),
+ image,
+ variant=variant,
+ )
+ elif target in ("bootloader-upgrade-assets", "bootloader-upgrade-assets-development"):
+ suffix = "-development" if target.endswith("-development") else ""
+ for product in ("bitbox02", "bitbox02nova"):
+ cmake(
+ defaults(product),
+ "relwithdebinfo",
+ [
+ f"bootloader-upgrade-assets-{product}-{edition}{suffix}"
+ for edition in ("multi", "btconly")
+ ],
+ )
+ elif target in ("docs", "rust-docs", "prepare-tidy"):
+ cmake(
+ defaults(),
+ "relwithdebinfo",
+ [{"docs": "doc", "rust-docs": "rust-docs", "prepare-tidy": "rust-cbindgen"}[target]],
+ )
+ else:
+ raise ValueError(f"Unknown build target: {target}")
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ add_config_arguments(parser)
+ parser.add_argument("target")
+ args = parser.parse_args()
+ try:
+ dispatch(from_args(args), args.target)
+ except (ValueError, OSError, subprocess.CalledProcessError) as exc:
+ print(str(exc), file=sys.stderr)
+ return 1
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
### scripts/expand_template
@@ -1,30 +0,0 @@
-#!/usr/bin/env python3
-"""Expand python templates"""
-
-from string import Template
-import argparse
-import sys
-
-def main():
- """Main function"""
- parser = argparse.ArgumentParser(
- description="Expands files according to python string.Template rules.")
- parser.add_argument("file")
- parser.add_argument("-o", "--output")
- parser.add_argument("mapping", metavar="NAME=VALUE", nargs="*")
-
- args = parser.parse_args()
-
- kwargs = {pair[0]: pair[1] for pair in [x.split("=") for x in args.mapping]}
- with open(args.file) as template_file:
- template = Template(template_file.read())
- res = template.substitute(**kwargs)
-
- if args.output:
- with open(args.output, "w") as output_file:
- output_file.write(res)
- else:
- sys.stdout.write(res)
-
-if __name__ == "__main__":
- main()
### scripts/flash-bitbox03-boot0-openocd.sh
@@ -1,15 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-image="${1:-$repo_root/src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-boot0}"
-config="$repo_root/scripts/stm32u5a9j-dk.cfg"
-
-if [[ ! -f "$image" ]]; then
- echo "image not found: $image" >&2
- exit 1
-fi
-
-exec openocd \
- -f "$config" \
- -c "program \"$image\" verify reset exit"
### scripts/flash-bitbox03-boot1-openocd.sh
@@ -1,15 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-image="${1:-$repo_root/src/rust/target/thumbv8m.main-none-eabihf/debug/bitbox03-boot1}"
-config="$repo_root/scripts/stm32u5a9j-dk.cfg"
-
-if [[ ! -f "$image" ]]; then
- echo "image not found: $image" >&2
- exit 1
-fi
-
-exec openocd \
- -f "$config" \
- -c "program \"$image\" verify reset exit"
### scripts/jlink-bootloader-stage0.gdb
@@ -1,21 +0,0 @@
-# Connect to jlink gdb server
-target extended-remote :2331
-
-# It seems more reliable to reset the chip before loading the new firmware. It
-# is also how they do it in the example in the wiki:
-# https://kb.segger.com/J-Link_GDB_Server#Console
-
-# Reset the CPU
-monitor reset
-
-# load the firmware into ROM
-load
-
-#break Reset_Handler
-#break HardFault_Handler
-#break NMI_Handler
-#break MemManage_Handler
-
-# start running
-# change `continue` to `stepi` to stop execution at the start if you want to set breakpoints etc.
-continue
### scripts/jlink-bootloader-stage1.gdb
@@ -1,32 +0,0 @@
-# Connect to jlink gdb server
-target extended-remote :2331
-
-# It seems more reliable to reset the chip before loading the new bootloader
-# stage. It is also how they do it in the example in the wiki:
-# https://kb.segger.com/J-Link_GDB_Server#Console
-
-# Reset the CPU
-monitor reset
-
-# load the bootloader stage into ROM
-load
-
-define bootload
- monitor reset
- # Set VTOR (Vector Table Offset Register) to where stage1 vectors are located.
- set *(uint32_t*)0xE000ED08=0x2400
- # Set stack pointer to initial stack pointer according to exception table.
- set $sp = *(uint32_t*)0x2400
- # Set the program counter to the reset handler (second item in exception table)
- set $pc = *(uint32_t*)0x2404
-end
-bootload
-
-#break Reset_Handler
-#break HardFault_Handler
-#break NMI_Handler
-#break MemManage_Handler
-
-# start running
-# change `continue` to `stepi` to stop execution at the start if you want to set breakpoints etc.
-continue
### scripts/jlink.gdb
@@ -1,32 +1,21 @@
-# Connect to jlink gdb server
-target extended-remote :2331
+# SPDX-License-Identifier: Apache-2.0
-# It seems more reliable to reset the chip before loading the new firmware. It
-# is also how they do it in the example in the wiki:
-# https://kb.segger.com/J-Link_GDB_Server#Console
+# scripts/probe.py supplies the image's $vectors address.
-# Reset the CPU
+# Reset before loading, as recommended by the SEGGER GDB Server examples.
+target extended-remote :2331
monitor reset
+monitor halt
-# load the firmware into ROM
load
+compare-sections
+# Rust images can select the Rust expression parser; these are C expressions.
+set language c
+set {unsigned int}0xe000ed08 = $vectors
+set $sp = *(unsigned int*)$vectors
+set $pc = *(unsigned int*)($vectors + 4)
+set $xpsr = 0x01000000
+set language auto
-define bootload
- monitor reset
- # Set VTOR (Vector Table Offset Register) to where the firmware is located
- set *(uint32_t*)0xE000ED08=0x10000
- # Set stack pointer to initial stack pointer according to exception table.
- set $sp = *(uint32_t*)0x10000
- # Set the program counter to the reset handler (second item in exception table)
- set $pc = *(uint32_t*)0x10004
-end
-bootload
-
-#break Reset_Handler
-#break HardFault_Handler
-#break NMI_Handler
-#break MemManage_Handler
-
-# start running
-# change `continue` to `stepi` to stop execution at the start if you want to set breakpoints etc.
-continue
+# Add breakpoints here, or comment out c to stop before the first instruction.
+c
### scripts/openocd-bitbox02.cfg
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: Apache-2.0
+# BitBox02 and Nova always use a J-Link probe, including with OpenOCD.
+source [find interface/jlink.cfg]
+# Upstream SAM E5x/D5x driver, including ATSAMD51J20.
+source [find target/atsame5x.cfg]
+
+# Keep the flash work area beyond the RTT control block and buffers.
+$_TARGETNAME configure -work-area-phys 0x20002000
+adapter speed 4000
### scripts/openocd-bitbox03.cfg
@@ -1,4 +1,9 @@
-source [find interface/jlink.cfg]
+# SPDX-License-Identifier: Apache-2.0
+# The product configuration supplies the board's probe; default to the dev-kit.
+if {![info exists PROBE_ADAPTER]} {
+ set PROBE_ADAPTER stlink
+}
+source [find interface/$PROBE_ADAPTER.cfg]
transport select swd
@@ -13,4 +18,4 @@ cortex_m reset_config sysresetreq
set _TARGETNAME [target current]
$_TARGETNAME configure -work-area-phys 0x20002000
-adapter speed 3300
+adapter speed 4000
### scripts/openocd.gdb
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: Apache-2.0
+
+# scripts/probe.py supplies the image's $vectors address.
+
+# Start RTT only after the application has initialized its channels.
+define rtt_start
+ monitor rtt setup 0x20000200 0x1000 "SEGGER RTT"
+ monitor rtt start
+ monitor rtt server start 19021 0
+ monitor rtt server start 19022 1
+end
+
+target extended-remote :3333
+monitor reset init
+monitor halt
+
+load
+compare-sections
+# Rust images can select the Rust expression parser; these are C expressions.
+set language c
+set {unsigned int}0xe000ed08 = $vectors
+set $sp = *(unsigned int*)$vectors
+set $pc = *(unsigned int*)($vectors + 4)
+set $xpsr = 0x01000000
+set language auto
+
+# Add breakpoints here, or comment out c to stop before the first instruction.
+c
### scripts/probe.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: Apache-2.0
+
+"""Flash built images or load and continue them in an attached GDB session."""
+
+import argparse
+from dataclasses import replace
+from pathlib import Path
+import shutil
+import signal
+import subprocess
+import sys
+import tempfile
+
+from build_config import ROOT, IMAGES, Config, Image, add_config_arguments, from_args, image_info
+
+
+def require_tool(tool: str) -> None:
+ if shutil.which(tool) is None:
+ raise ValueError(f"Required tool not found in PATH: {tool}")
+
+
+def require_artifact(path: Path, target: str) -> None:
+ if not path.is_file():
+ raise ValueError(f"Image not found: {path}\nBuild it first with: make {target}")
+
+
+def tcl_quote(path: Path) -> str:
+ value = str(path)
+ for char in ("\\", '"', "$", "[", "]"):
+ value = value.replace(char, "\\" + char)
+ return '"' + value + '"'
+
+
+def openocd_command(config: Config) -> list[str]:
+ filename = "openocd-bitbox03.cfg" if config.product == "bitbox03" else "openocd-bitbox02.cfg"
+ return [
+ "openocd",
+ "-c",
+ f"set PROBE_ADAPTER {config.probe_hardware}",
+ "-f",
+ str(ROOT / "scripts" / filename),
+ "-c",
+ f"adapter speed {config.swd_speed}",
+ "-c",
+ "gdb_port 3333",
+ ]
+
+
+def jlink_gdb_server(config: Config) -> list[str]:
+ return [
+ "JLinkGDBServer",
+ "-nogui",
+ "-if",
+ "SWD",
+ "-device",
+ config.chip_type,
+ "-speed",
+ str(config.swd_speed),
+ "-port",
+ "2331",
+ "-RTTTelnetPort",
+ "19021",
+ "-vd",
+ ]
+
+
+def jlink_exe(config: Config) -> list[str]:
+ return [
+ "JLinkExe",
+ "-NoGui",
+ "1",
+ "-if",
+ "SWD",
+ "-device",
+ config.chip_type,
+ "-speed",
+ str(config.swd_speed),
+ "-autoconnect",
+ "1",
+ "-ExitOnError",
+ "1",
+ ]
+
+
+def server_command(config: Config) -> list[str]:
+ if config.probe_software == "openocd":
+ return openocd_command(config)
+ return jlink_gdb_server(config)
+
+
+def openocd_flash_commands(info: Image) -> str:
+ path = tcl_quote(info.elf)
+ return f"program {path} verify reset exit"
+
+
+def jlink_flash_commands(info: Image) -> str:
+ # Commander paths are quoted to support workspaces containing spaces.
+ path = str(info.elf)
+ if any(char in path for char in ('"', "\n", "\r")):
+ raise ValueError("Unsupported character in J-Link image path")
+ return f'r\nh\nloadfile "{path}" 0 noreset\nr\ng\nq\n'
+
+
+def execute(config: Config, action: str, image: str = "firmware") -> None:
+ config.validate()
+ if action == "server":
+ command = server_command(config)
+ require_tool(command[0])
+ subprocess.run(command, check=True)
+ return
+ info = image_info(config, image)
+ if action == "flash" and info.ram:
+ raise ValueError(f"{config.product} {image} runs from RAM; use make run-{image} instead")
+ target = f"{image} DEBUG={config.debug}"
+ require_artifact(info.elf, target)
+ if action == "flash":
+ command = (
+ openocd_command(config) if config.probe_software == "openocd" else jlink_exe(config)
+ )
+ require_tool(command[0])
+ if config.probe_software == "openocd":
+ subprocess.run([*command, "-c", openocd_flash_commands(info)], check=True)
+ else:
+ with tempfile.TemporaryDirectory(prefix="bitbox-probe-") as tmp:
+ script = Path(tmp) / "flash.jlink"
+ if info.elf.suffix != ".elf":
+ # Give extensionless Cargo artifacts an ELF suffix for Commander.
+ elf = Path(tmp) / "image.elf"
+ elf.symlink_to(info.elf.resolve())
+ info = replace(info, elf=elf)
+ script.write_text(jlink_flash_commands(info), encoding="utf-8")
+ subprocess.run([*command, "-CommanderScript", str(script)], check=True)
+ return
+ require_tool("arm-none-eabi-gdb")
+ # Ctrl-C must interrupt the inferior without terminating the attached GDB.
+ previous = signal.signal(signal.SIGINT, lambda _signum, _frame: None)
+ try:
+ subprocess.run(
+ [
+ "arm-none-eabi-gdb",
+ "-q",
+ str(info.elf),
+ "-ex",
+ f"set $vectors = {info.vectors:#x}",
+ "-x",
+ str(ROOT / "scripts" / f"{config.probe_software}.gdb"),
+ ],
+ check=True,
+ )
+ finally:
+ signal.signal(signal.SIGINT, previous)
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ add_config_arguments(parser)
+ parser.add_argument("action", choices=("flash", "run", "server"))
+ parser.add_argument("image", choices=IMAGES, nargs="?", default="firmware")
+ args = parser.parse_args()
+ try:
+ execute(from_args(args), args.action, args.image)
+ except (ValueError, OSError, subprocess.CalledProcessError) as exc:
+ print(str(exc), file=sys.stderr)
+ return 1
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
### scripts/stm32u5.gdb
@@ -1,22 +0,0 @@
-# Helper function to start RTT server in openocd. Remember to let the firmware
-# run until RTT channels have been initialized before running this command.
-# Sets up channel 0 in/out for terminals and channel 1 for API requests/responses
-define rtt_start
- monitor rtt setup 0x20000200 0x1000
- monitor rtt start
- monitor rtt server start 19021 0
- monitor rtt server start 19022 1
-end
-
-define r
- monitor reset halt
-end
-
-target extended-remote :3333
-
-monitor reset init
-
-load
-
-# Step into first instruction
-si
### scripts/stm32u5a9j-dk.cfg
@@ -1,16 +0,0 @@
-source [find interface/stlink.cfg]
-
-transport select swd
-
-reset_config srst_only connect_assert_srst
-#reset_config none
-
-source [find target/stm32u5x.cfg]
-
-cortex_m reset_config sysresetreq
-
-# set working area beyond the RTT buffers so they don't get clobbered
-set _TARGETNAME [target current]
-$_TARGETNAME configure -work-area-phys 0x20002000
-
-adapter speed 3300
### scripts/template-firmware.jlink
@@ -1,3 +0,0 @@
-loadbin $file 0x10000
-r
-q
### scripts/template-stage0.jlink
@@ -1,3 +0,0 @@
-loadbin $file 0x00000000
-r
-q
### scripts/template-stage1.jlink
@@ -1,3 +0,0 @@
-loadbin $file 0x00002000
-r
-q
### scripts/tidy
@@ -14,21 +14,21 @@ command -v ${CLANGTIDY} >/dev/null 2>&1 || { echo >&2 "${CLANGTIDY} is missing";
JQ=${JQ:-jq}
command -v ${JQ} >/dev/null 2>&1 || { echo >&2 "${JQ} is missing"; exit 1; }
-if test ! -d build -a ! -d build-build; then
- echo >&2 "neither build nor build-build is a directory"
+if ! compgen -G 'build-bitbox02*-cmake-*/compile_commands.json' >/dev/null && test ! -f build-build/compile_commands.json; then
+ echo >&2 "no product or host compilation database found"
echo >&2 "please run 'make <target>' before running tidy to generate compile_commands.json"
exit 1
fi
-for dir in build build-build; do
+for dir in build-bitbox02*-cmake-* build-build; do
if ! test -d ${dir}; then
continue
fi
# Exclude drivers and external for now. We get the base file set from compile_commands.json, as
# clang-tidy unfortunately tries to lint a provided source anyway even if it is not part of the
# build.
- SOURCES=$(cat ${dir}/compile_commands.json | ${JQ} 'map_values(.file) | .[]' | grep -v -e '/drivers/' -e '/external/' -e '/test/unit-test/u2f/' -e '/build/' -e '/build-build/' | sort | uniq | xargs echo)
+ SOURCES=$(cat ${dir}/compile_commands.json | ${JQ} 'map_values(.file) | .[]' | grep -v -e '/drivers/' -e '/external/' -e '/test/unit-test/u2f/' -e '/build-[^/]*/' -e '/build-build/' | sort | uniq | xargs echo)
${CLANGTIDY} -quiet -p ${dir} ${SOURCES}
done
### src/CMakeLists.txt
@@ -207,6 +207,12 @@ find_program(CBINDGEN cbindgen)
# cargo is the rust build system and dependency manager
find_program(CARGO cargo)
+# Referencing $(MAKE) marks Cargo recipes as recursive, preserving GNU make's
+# jobserver pipes. JOB_SERVER_AWARE would require CMake 3.28 or newer.
+if(CMAKE_GENERATOR MATCHES "Makefiles")
+ set(CARGO_JOBSERVER_ENV "BITBOX_CARGO_MAKE=$(MAKE)")
+endif()
+
if(CMAKE_BUILD_TYPE STREQUAL "DEBUG")
set(RUST_PROFILE "debug")
else()
@@ -332,6 +338,7 @@ foreach(type ${RUST_LIBS})
OUTPUT ${lib} ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/lib${type}_rust_c.a dummy
COMMAND
${CMAKE_COMMAND} -E env
+ ${CARGO_JOBSERVER_ENV}
CMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}
RUSTFLAGS=${RUSTFLAGS}
${CARGO_CC_FLAGS_ENV}Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.