# (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC. # # Shared values and target rules for Mk and Q platforms. # # Define these vars to suit board #BOARD = COLDCARD_MK4 # These values used to make .DFU files. Flash memory locations. #FIRMWARE_BASE = 0x08020000 #BOOTLOADER_BASE = 0x08000000 #BOOTLOADER_DIR = mk4-bootloader vs bootloader #HW_MODEL = mk4 #PARENT_MKFILE = xx-Makefile MPY_TOP = ../external/micropython PORT_TOP = $(MPY_TOP)/ports/stm32 MPY_CROSS = $(MPY_TOP)/mpy-cross/mpy-cross PYTHON_MAKE_DFU = $(MPY_TOP)/tools/dfu.py PYTHON_DO_DFU = $(MPY_TOP)/tools/pydfu.py DEBUG_BUILD ?= 0 # aka ../cli/signit.py SIGNIT = signit PROD_KEYNUM = -k 1 BUILD_DIR = l-port/build-$(BOARD) MAKE_ARGS = BOARD=$(BOARD) -j 4 EXCLUDE_NGU_TESTS=1 DEBUG_BUILD=$(DEBUG_BUILD) all: $(BOARD)/file_time.c cd $(PORT_TOP) && $(MAKE) $(MAKE_ARGS) clean: cd $(PORT_TOP) && $(MAKE) $(MAKE_ARGS) clean $(RM) dev.dfu dev.bin git clean -xf built clobber: clean rm -f *RC1*.dfu # These trigger the 'all' target when we haven't completed a successful build yet $(BUILD_DIR)/firmware.elf: all $(BUILD_DIR)/firmware0.bin: all $(BUILD_DIR)/firmware1.bin: all firmware.elf: $(BUILD_DIR)/firmware.elf cp $(BUILD_DIR)/firmware.elf . # # Sign and merge various parts # firmware-signed.bin: $(BUILD_DIR)/firmware0.bin $(BUILD_DIR)/firmware1.bin $(SIGNIT) sign -b $(BUILD_DIR) -m $(HW_MODEL) $(VERSION_STRING) -o $@ firmware-signed.dfu: firmware-signed.bin $(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):$< $@ # make the DFU file which is shared for upgrades dfu: firmware-signed.dfu # Build a binary, signed w/ production key # - always rebuild binary for this one .PHONY: dev.dfu dev.dfu: $(BUILD_DIR)/firmware0.bin cd $(PORT_TOP) && $(MAKE) $(MAKE_ARGS) $(SIGNIT) sign -b $(BUILD_DIR) -m $(HW_MODEL) $(VERSION_STRING) $(PROD_KEYNUM) -o dev.bin $(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):dev.bin dev.dfu .PHONY: relink relink: rm -rf $(BUILD_DIR)/firmware?.bin $(BUILD_DIR)/frozen_mpy* # Slow, but works with unmod-ed board: use USB protocol to upgrade (2 minutes) .PHONY: dev dev: dev.dfu ckcc upgrade dev.dfu # Requires special bootrom w/ DFU still enabled .PHONY: up-dfu up-dfu: dev.dfu $(PYTHON_DO_DFU) -u dev.dfu $(BOARD)/file_time.c: make_filetime.py *-Makefile shared.mk ./make_filetime.py $(BOARD)/file_time.c $(VERSION_STRING) cp $(BOARD)/file_time.c . .PHONY: block_height block_height: @python3 make_block_height.py; \ if [ $$? -eq 0 ]; then \ echo "Block Height file already up-to-date."; \ else \ echo "Block Height file updated."; \ git commit -m "update block height" ../shared/block_height.py; \ fi # Make a factory release: using key #1 # - when executed in a repro w/o the required key, it defaults to key zero # - and that's what happens inside the Docker build production.bin: firmware-signed.bin Makefile $(SIGNIT) sign -m $(HW_MODEL) $(VERSION_STRING) -r firmware-signed.bin $(PROD_KEYNUM) -o $@ SUBMAKE = $(MAKE) -f $(PARENT_MKFILE) .PHONY: release release: submods-match code-committed block_height $(SUBMAKE) clean $(SUBMAKE) repro test -f built/production.bin $(SUBMAKE) release-products $(SUBMAKE) tag-source # Make a release-candidate, faster. .PHONY: rc1 rc1: $(SUBMAKE) clean # critical, or else you get a mix of debug/not $(SUBMAKE) DEBUG_BUILD=0 all $(SIGNIT) sign -b $(BUILD_DIR) -m $(HW_MODEL) $(VERSION_STRING) $(PROD_KEYNUM) -o rc1.bin $(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):rc1.bin \ `signit version rc1.bin`-$(HW_MODEL)-RC1-coldcard.dfu $(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):rc1.bin \ -b $(BOOTLOADER_BASE):$(BOOTLOADER_DIR)/releases/$(BOOTLOADER_VERSION)/bootloader.bin \ `signit version rc1.bin`-$(HW_MODEL)-RC1-factory-coldcard.dfu ls -1 *-RC1-*.dfu # Make a test release-candidate but use full repro flow; slower than rc1 above. # - no factory release, does no GIT tagging nor committing # - but still must commit changes because code gets into docker image via local git repo .PHONY: rc2 rc2: RC2_TIMESTAMP = $(shell date "+%F_%H%M") rc2: RC2_FNAME = ./RC2-$(RC2_TIMESTAMP)-$(HW_MODEL)-coldcard.dfu rc2: RC2_FNAME_FACT = ./RC2-$(RC2_TIMESTAMP)-$(HW_MODEL)-factory.dfu rc2: submods-match code-committed block_height $(SUBMAKE) clean $(SUBMAKE) repro test -f built/production.bin $(SIGNIT) sign -m $(HW_MODEL) $(VERSION_STRING) -r built/production.bin $(PROD_KEYNUM) -o built/rc2.bin $(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):built/rc2.bin $(RC2_FNAME) $(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):built/rc2.bin \ -b $(BOOTLOADER_BASE):$(BOOTLOADER_DIR)/releases/$(BOOTLOADER_VERSION)/bootloader.bin $(RC2_FNAME_FACT) ls -1 ./RC2-*.dfu # This target just combines latest version of production firmware with bootrom into a DFU # file, stored in ../releases with appropriately dated file name. .PHONY: release-products release-products: NEW_VERSION = $(shell $(SIGNIT) version built/production.bin) release-products: RELEASE_FNAME = ../releases/$(NEW_VERSION)-$(HW_MODEL)-coldcard.dfu release-products: built/production.bin test ! -f $(RELEASE_FNAME) cp built/file_time.c $(BOARD)/file_time.c -git commit $(BOARD)/file_time.c -m "For $(NEW_VERSION)" $(SIGNIT) sign -m $(HW_MODEL) $(VERSION_STRING) -r built/production.bin $(PROD_KEYNUM) -o built/production.bin $(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):built/production.bin $(RELEASE_FNAME) $(PYTHON_MAKE_DFU) -b $(FIRMWARE_BASE):built/production.bin \ -b $(BOOTLOADER_BASE):$(BOOTLOADER_DIR)/releases/$(BOOTLOADER_VERSION)/bootloader.bin \ $(RELEASE_FNAME:%.dfu=%-factory.dfu) @echo @echo 'Made release: ' $(RELEASE_FNAME) @echo built/production.bin: @echo "To make production build, must run docker code" @false # Use DFU to install the latest production version you have on hand dfu-latest: $(PYTHON_DO_DFU) -u $(LATEST_RELEASE) # Use slow USB upload and reboot method. latest: ckcc upgrade $(LATEST_RELEASE) # verify submodules are checked-out at the correct revision .PHONY: submods-match SUBMODULES := $(shell git config --file ../.gitmodules --name-only --get-regex path) SUBMODULES := $(SUBMODULES:submodule.%.path=%) SUBMODULES := $(filter-out stm32/mk4-bootloader/hal, $(SUBMODULES)) submods-match: @echo Submodules: $(SUBMODULES) git submodule status --cached $(SUBMODULES:%=../%) > sm-want.txt git submodule status $(SUBMODULES:%=../%) > sm-have.txt @echo "Submodules: HAVE" diff sm-want.txt sm-have.txt rm sm-have.txt sm-want.txt @echo "Submodules are right revisions." .PHONY: code-committed code-committed: @echo "" @echo "Are all changes commited already?" git diff --stat --exit-code . @echo '... yes' # Sign a message with the contents of ../releases on the developer's machine .PHONY: sign-release sign-release: (cd ../releases; shasum -a 256 *.dfu *.md | sort -rk 2 | \ gpg --clearsign -u A3A31BAD5A2A5B10 --digest-algo SHA256 --output signatures.txt --yes - ) git commit -m "Signed for $(HW_MODEL) release." ../releases/signatures.txt # Tag source code associate with built release version. # - do "make release" before this step! # - also edit/commit ChangeLog text too # - update & sign signatures file # - and tag everything tag-source: PUBLIC_VERSION = $(shell $(SIGNIT) version built/production.bin) tag-source: sign-release submods-match code-committed git commit --allow-empty -am "New release: "$(PUBLIC_VERSION) echo "Tagging version: " $(PUBLIC_VERSION) git tag -a $(PUBLIC_VERSION) -m "Release "$(PUBLIC_VERSION) git push git push --tags # DFU file of boot and main code # - bootloader is last so it can fail if already installed (maybe) # mostly.dfu: firmware-signed.bin $(BOOTLOADER_DIR)/bootloader.bin Makefile $(PYTHON_MAKE_DFU) \ -b $(FIRMWARE_BASE):firmware-signed.bin \ -b $(BOOTLOADER_BASE):$(BOOTLOADER_DIR)/bootloader.bin $@ # send everything m-dfu: mostly.dfu $(PYTHON_DO_DFU) -u mostly.dfu # unused stlink: cd $(PORT_TOP) && $(MAKE) $(MAKE_ARGS) deploy-stlink # useless, will be ignored by bootloader unsigned-dfu: cd $(PORT_TOP) && $(MAKE) $(MAKE_ARGS) deploy # see $(BOARD)/mpconfigboard.mk tags: cd $(PORT_TOP) && $(MAKE) $(MAKE_ARGS) tags checksum: cd $(PORT_TOP) && $(MAKE) $(MAKE_ARGS) checksum files: cd $(PORT_TOP) && $(MAKE) $(MAKE_ARGS) files # OLD dev junk? # compile and freeze python code PY_FILES = $(shell find ../shared -name \*.py) ALL_MPY_FILES = $(addprefix build/, $(PY_FILES:../shared/%.py=%.mpy)) MPY_FILES = $(filter-out build/obsolete/%, $(ALL_MPY_FILES)) # detailed listing, very handy OBJDUMP = arm-none-eabi-objdump firmware.lss: $(BUILD_DIR)/firmware.elf $(OBJDUMP) -h -S $< > $@ # Dump sizes of all frozen py files; requires recent build. .PHONY: sizes sizes: wc -c $(BUILD_DIR)/frozen_mpy/*.mpy | sort -n # Measure flash impact of a single file. Great for before/after. # make F=foo.py size # where: foo.py is anything in ../shared size: $(MPY_CROSS) -o tmp.mpy -s $F ../shared/$F wc -c tmp.mpy # one time setup, after repo checkout setup: cd $(MPY_TOP) ; git submodule update --init lib/stm32lib cd ../external/libngu; make min-one-time cd $(MPY_TOP)/mpy-cross ; make -ln -s $(PORT_TOP) l-port -ln -s $(MPY_TOP) l-mpy cd $(PORT_TOP)/boards; if [ ! -L COLDCARD ]; then \ ln -s ../../../../../stm32/COLDCARD COLDCARD; fi cd $(PORT_TOP)/boards; if [ ! -L COLDCARD_MK4 ]; then \ ln -s ../../../../../stm32/COLDCARD_MK4 COLDCARD_MK4; fi cd $(PORT_TOP)/boards; if [ ! -L COLDCARD_Q1 ]; then \ ln -s ../../../../../stm32/COLDCARD_Q1 COLDCARD_Q1; fi # Caution: docker container has read access to your source tree # - a readonly copy of source tree, and one output directory # - build products are copied to there, see repro-build.sh # - works from this repo, but starts with copy of HEAD DOCK_RUN_ARGS = -v $(realpath ..):/work/src:ro \ -v $(realpath built):/work/built:rw \ -u $$(id -u):$$(id -g) coldcard-build repro: submods-match code-committed repro: docker build -t coldcard-build - < dockerfile.build (cd ..; docker run $(DOCK_RUN_ARGS) sh src/stm32/repro-build.sh $(VERSION_STRING) $(HW_MODEL) $(PARENT_MKFILE)) # debug: shell into docker container shell: docker run -it $(DOCK_RUN_ARGS) sh # debug: allow docker to write into source tree #DOCK_RUN_ARGS := -v $(realpath ..):/work/src:rw --privileged coldcard-build PUBLISHED_BIN ?= $(wildcard ../releases/*-v$(VERSION_STRING)-$(HW_MODEL)-coldcard.dfu) # final step in repro-building: check you got the right bytes # - but you don't have the production signing key, so that section is removed check-repro: TRIM_SIG = sed -e 's/^00003f[89abcdef]0 .*/(firmware signature here)/' check-repro: firmware-signed.bin ifeq ($(PUBLISHED_BIN),) @echo "" @echo "Need published binary for: $(VERSION_STRING)" @echo "" @echo "Copy it into ../releases" @echo "" else @echo Comparing against: $(PUBLISHED_BIN) test -n "$(PUBLISHED_BIN)" -a -f $(PUBLISHED_BIN) $(RM) -f check-fw.bin check-bootrom.bin $(SIGNIT) split $(PUBLISHED_BIN) check-fw.bin check-bootrom.bin $(SIGNIT) check check-fw.bin $(SIGNIT) check firmware-signed.bin hexdump -C firmware-signed.bin | $(TRIM_SIG) > repro-got.txt hexdump -C check-fw.bin | $(TRIM_SIG) > repro-want.txt diff repro-got.txt repro-want.txt @echo "" @echo "SUCCESS. " @echo "" @echo "You have built a bit-for-bit identical copy of Coldcard firmware for v$(VERSION_STRING)" endif # revert "make release" and it's actions unrelease: BAD_VERSIONS ?= $(shell git tag --contains HEAD^^) unrelease: echo "Trying to forget: $(BAD_VERSIONS)" # # Various debug firmware upload methods. Won't work on production units due to bootrom. # # This is fast for Coinkite devs, but no DFU support in the wild. dfu-up: dev.dfu echo 'dfu' | nc localhost 4444 $(PYTHON_DO_DFU) -u dev.dfu # When device already in DFU mode dfu up2: dev.dfu $(PYTHON_DO_DFU) -u dev.dfu # Slowest DFU, but easier w/ stock tools dfu-slow: dev.dfu dfu-util -d 0483:df11 -a 0 -D $< -R # Super fast, assumes Coldcard already attached and unlocked on this Mac. up: dev.dfu cp dev.dfu /Volumes/COLDCARD/. diskutil eject /Volumes/COLDCARD # Fairly fast, assumes openocd already running, and its current directory is here. ocp-up: dev.dfu echo "load_image dev.dfu $(FIRMWARE_BASE) bin; reset run" | nc localhost 4444 # EOF