diff --git a/.gitattributes b/.gitattributes index 4e560bb50..ab32f7524 100644 --- a/.gitattributes +++ b/.gitattributes @@ -47,6 +47,8 @@ dfu-suffix binary dfu-util binary upload-reset binary hid-flash binary +uf2upload binary +uf2upload.exe binary *.class binary *.dll binary *.exe binary @@ -54,4 +56,3 @@ hid-flash binary *.jpg binary *.pdf binary *.png binary - diff --git a/LICENCE.md b/LICENCE.md index 6e92b637d..a61fc5827 100644 --- a/LICENCE.md +++ b/LICENCE.md @@ -4,6 +4,7 @@ This files includes licensing information for parts of this repository. * [busybox-w32](https://frippery.org/busybox/) * [dfu-util](http://dfu-util.sourceforge.net/) (also available [here](src/dfu-util/COPYING)) * upload-reset +* uf2upload [GPLv3](#gplv3) is used for: * [HID-flash](https://github.com/Serasidis/STM32_HID_Bootloader) diff --git a/README.md b/README.md index 391dbc37b..901ab1706 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,31 @@ [![GitHub commits](https://img.shields.io/github/commits-since/stm32duino/Arduino_Tools/2.5.0.svg)](https://github.com/stm32duino/Arduino_Tools/compare/2.4.0...main) Contains upload tools for STM32 based boards and some other usefull scripts. + +## UF2 uploader + +`uf2upload` is a self-contained host tool for a board with a UF2 mass-storage +bootloader. It first checks whether the target UF2 volume is already mounted; +when it is, it copies the converted image directly. Otherwise it performs a +USB CDC 1200-bps touch, waits for the volume label and copies the result. It +has no Python or shell dependency at upload time: + +``` +uf2upload --input firmware.bin --port COM7 --volume XIAOC5BOOT \ + --address 0x08008000 --family 0x00C5C5C5 --max-size 1015808 +``` + +Use `--skip-touch` to disable the CDC touch when no bootloader volume is +currently mounted. The source is built for the host package with +`make -C src/uf2upload`. + +Linux ARM64/AArch64 builds are provided under `linux/aarch64/uf2upload`. +From an x86_64 Linux host, they can be rebuilt with: + +``` +make -C src/uf2upload aarch64 +``` + +The ARM64 build is cross-compiled; see +[`src/linux-aarch64/LOCAL_VALIDATION.md`](src/linux-aarch64/LOCAL_VALIDATION.md) +for QEMU-based validation and its limitations. diff --git a/linux/aarch64/uf2upload b/linux/aarch64/uf2upload new file mode 100644 index 000000000..0e5b38076 Binary files /dev/null and b/linux/aarch64/uf2upload differ diff --git a/linux/x86_64/uf2upload b/linux/x86_64/uf2upload new file mode 100644 index 000000000..d4c1ebcfa Binary files /dev/null and b/linux/x86_64/uf2upload differ diff --git a/src/build_mac.sh b/src/build_mac.sh index 07785bb8a..d5b3ac14a 100755 --- a/src/build_mac.sh +++ b/src/build_mac.sh @@ -170,3 +170,14 @@ clang \ -Werror \ -o "${ARCH_DIR}/upload_reset" \ src/upload_reset/unix/upload_reset.c + +clang++ \ + ${CC_ARCH_ARG} \ + ${CC_MIN_OS_VERSION_ARG} \ + ${BASE_CFLAGS} \ + ${BASE_LDFLAGS} \ + -std=c++17 \ + -Wall \ + -Werror \ + -o "${ARCH_DIR}/uf2upload" \ + src/uf2upload/uf2upload.cpp diff --git a/src/linux-aarch64/Dockerfile b/src/linux-aarch64/Dockerfile index de71993ed..a666b1a57 100644 --- a/src/linux-aarch64/Dockerfile +++ b/src/linux-aarch64/Dockerfile @@ -1,5 +1,5 @@ # Dockerfile for cross-compiling ARM64 Linux binaries -# This builds dfu-util, hid-flash, and upload_reset for aarch64 +# This builds the STM32 host tools, including uf2upload, for Linux aarch64. FROM ubuntu:22.04 @@ -57,10 +57,13 @@ RUN git clone https://github.com/Serasidis/STM32_HID_Bootloader.git && \ ${STRIP} hid-flash && \ cp hid-flash /build/output/bin/ -# Build upload_reset from source +# Build upload_reset and UF2 uploader from source COPY src/upload_reset/unix/upload_reset.c /build/upload_reset.c +COPY src/uf2upload/uf2upload.cpp /build/uf2upload.cpp RUN ${CC} -o /build/output/bin/upload_reset /build/upload_reset.c && \ - ${STRIP} /build/output/bin/upload_reset + ${STRIP} /build/output/bin/upload_reset && \ + ${CXX} -O2 -std=c++17 -Wall -Wextra -Werror -o /build/output/bin/uf2upload /build/uf2upload.cpp && \ + ${STRIP} /build/output/bin/uf2upload # Create output directory structure RUN mkdir -p /output @@ -70,7 +73,8 @@ RUN cp /build/output/bin/dfu-util /output/ && \ cp /build/output/bin/dfu-prefix /output/ && \ cp /build/output/bin/dfu-suffix /output/ && \ cp /build/output/bin/hid-flash /output/ && \ - cp /build/output/bin/upload_reset /output/ + cp /build/output/bin/upload_reset /output/ && \ + cp /build/output/bin/uf2upload /output/ # Verify binaries are ARM64 RUN file /output/* | grep aarch64 @@ -129,6 +133,10 @@ RUN echo "========================================" && \ file /usr/local/bin/upload_reset && \ (upload_reset --help 2>&1 | head -5 || echo "(upload_reset executed successfully)") && \ echo "" && \ + echo "=== Testing uf2upload ===" && \ + file /usr/local/bin/uf2upload && \ + uf2upload --help && \ + echo "" && \ echo "========================================" && \ echo "✓ All ARM64 binaries are working!" && \ echo "========================================" diff --git a/src/linux-aarch64/LOCAL_VALIDATION.md b/src/linux-aarch64/LOCAL_VALIDATION.md new file mode 100644 index 000000000..218a8825e --- /dev/null +++ b/src/linux-aarch64/LOCAL_VALIDATION.md @@ -0,0 +1,122 @@ +# Local Linux ARM64 validation for `uf2upload` + +## Scope + +This document describes how to validate the Linux ARM64 (`aarch64`) host build +from an x86_64 Linux machine, including Ubuntu in WSL2. It is useful when an +ARM64 development board is not available. + +The tested executable is `linux/aarch64/uf2upload`. It runs on a Linux ARM64 +computer such as a Raspberry Pi 64-bit OS, ARM server, or ARM64 Linux VM; it +does **not** run on the STM32C5. + +## What this can prove + +- The source cross-compiles with the AArch64 GNU toolchain. +- The output is an AArch64 ELF and declares the expected Linux runtime + dependencies. +- When run through QEMU user-mode emulation, the binary starts and its + command-line, UF2 conversion, directory discovery, and file-copy paths work. + +## What this cannot prove + +- A physical ARM64 Linux host can enumerate the board's USB CDC and mass-storage + interfaces. +- A real CDC 1200-bps request resets the board into TinyUF2. +- A real `XIAOC5BOOT` filesystem accepts the copied UF2 and programs flash. +- ARM32 (`armhf`) support. This document covers only 64-bit ARM (`aarch64`). + +## Prerequisites on Ubuntu x86_64 / WSL2 + +```bash +sudo apt update +sudo apt install -y g++-aarch64-linux-gnu binutils-aarch64-linux-gnu \ + libc6-dev-arm64-cross file qemu-user-static xxd +``` + +`qemu-user-static` may register the ARM64 interpreter automatically through +`binfmt_misc`. If it does not, use `qemu-aarch64-static` explicitly as shown +below. + +## Cross-build and inspect + +From the Arduino_Tools repository root: + +```bash +make -C src/uf2upload aarch64 +file linux/aarch64/uf2upload +aarch64-linux-gnu-readelf -d linux/aarch64/uf2upload | grep NEEDED +``` + +Expected `file` output includes: + +```text +ELF 64-bit ... ARM aarch64 ... interpreter /lib/ld-linux-aarch64.so.1 +``` + +The binary is dynamically linked and normally needs the ARM64 Linux loader, +`libstdc++.so.6`, `libgcc_s.so.1`, and `libc.so.6` supplied by the eventual +ARM64 Linux distribution. + +## QEMU execution with an ARM64 sysroot + +Use the installed cross sysroot. The exact directory can vary; on Ubuntu it is +usually `/usr/aarch64-linux-gnu`. + +```bash +sysroot=/usr/aarch64-linux-gnu +qemu-aarch64-static -L "$sysroot" linux/aarch64/uf2upload --help +``` + +This must print the uploader usage text and exit successfully. + +## Simulated volume and UF2 conversion + +`uf2upload` searches only `/Volumes`, `/media`, `/run/media`, and `/mnt` for +the volume label. Create an isolated test path under `/mnt`; do not point this +at a real bootloader volume. + +```bash +set -eu + +repo_root="$(git rev-parse --show-toplevel)" +work="$(mktemp -d)" +mount_root="/mnt/uf2upload-arm64-test-$$" +trap 'sudo rm -rf "$mount_root"; rm -rf "$work"' EXIT + +printf '\\x01\\x02\\x03\\x04' > "$work/firmware.bin" +sudo mkdir -p "$mount_root/XIAOC5BOOT" +sudo chown -R "$(id -u):$(id -g)" "$mount_root" + +qemu-aarch64-static -L /usr/aarch64-linux-gnu \ + "$repo_root/linux/aarch64/uf2upload" \ + --input "$work/firmware.bin" \ + --volume XIAOC5BOOT \ + --address 0x08008000 \ + --family 0x00C5C5C5 \ + --max-size 1015808 \ + --timeout 1000 + +test -f "$mount_root/XIAOC5BOOT"/* +stat -c '%n %s bytes' "$mount_root/XIAOC5BOOT"/* +xxd -g 4 -l 32 "$mount_root/XIAOC5BOOT"/* +``` + +For the 4-byte input, the copied UF2 must be 512 bytes. The first two words are +the UF2 magic values; the family field is `0x00C5C5C5` in little-endian order. + +## Optional pseudo-terminal check + +`socat` can provide a pseudo-terminal pair to exercise the Linux `termios` +1200-bps setup path. This is a code-path test only, not a USB CDC test. Arrange +for the simulated `XIAOC5BOOT` directory to appear while the uploader is +waiting, then pass one pseudo-terminal path through `--port`. + +## Result wording + +If every command above succeeds, use wording such as: + +> The Linux ARM64 `uf2upload` binary was cross-built on Ubuntu x86_64 and +> executed under QEMU user-mode emulation. Command-line parsing, UF2 generation, +> simulated volume discovery, and file copy were validated. Physical ARM64 USB +> CDC and TinyUF2 hardware upload have not yet been validated. diff --git a/src/linux-aarch64/README.md b/src/linux-aarch64/README.md index db0c9b8fc..54f569cc4 100644 --- a/src/linux-aarch64/README.md +++ b/src/linux-aarch64/README.md @@ -11,6 +11,7 @@ These scripts use Docker to cross-compile the following binaries from x86_64 to - **dfu-suffix** - DFU file suffix tool - **hid-flash** - HID bootloader flash utility - **upload_reset** - STM32 reset utility +- **uf2upload** - CDC 1200-bps touch and UF2 mass-storage uploader ## Prerequisites @@ -61,3 +62,17 @@ Removes all built ARM64 binaries from the `linux/aarch64/` directory. ### `Dockerfile` Multi-stage Docker build with integrated testing. + +## Build only `uf2upload` + +For a local cross-build of just the UF2 uploader, install an AArch64 GNU C++ +cross-compiler and run: + +```bash +make -C src/uf2upload aarch64 +file linux/aarch64/uf2upload +``` + +The resulting executable is for a Linux ARM64/AArch64 host, not for the STM32 +target. See [LOCAL_VALIDATION.md](LOCAL_VALIDATION.md) for local cross-build and +QEMU validation steps, including their limitations. diff --git a/src/linux-aarch64/build.sh b/src/linux-aarch64/build.sh index 41c302aa0..6d198f3ef 100755 --- a/src/linux-aarch64/build.sh +++ b/src/linux-aarch64/build.sh @@ -58,6 +58,7 @@ docker cp "${CONTAINER_ID}:/dfu-prefix" "${OUTPUT_DIR}/" docker cp "${CONTAINER_ID}:/dfu-suffix" "${OUTPUT_DIR}/" docker cp "${CONTAINER_ID}:/hid-flash" "${OUTPUT_DIR}/" docker cp "${CONTAINER_ID}:/upload_reset" "${OUTPUT_DIR}/" +docker cp "${CONTAINER_ID}:/uf2upload" "${OUTPUT_DIR}/" echo "" echo "Step 5: Cleaning up container..." @@ -70,6 +71,7 @@ chmod +x "${OUTPUT_DIR}/dfu-prefix" chmod +x "${OUTPUT_DIR}/dfu-suffix" chmod +x "${OUTPUT_DIR}/hid-flash" chmod +x "${OUTPUT_DIR}/upload_reset" +chmod +x "${OUTPUT_DIR}/uf2upload" echo "" echo "Step 7: Verifying built binaries..." @@ -79,6 +81,7 @@ file "${OUTPUT_DIR}/dfu-prefix" file "${OUTPUT_DIR}/dfu-suffix" file "${OUTPUT_DIR}/hid-flash" file "${OUTPUT_DIR}/upload_reset" +file "${OUTPUT_DIR}/uf2upload" echo "----------------------------------------" echo "" @@ -92,6 +95,7 @@ echo "All required binaries have been built:" echo " - dfu-util, dfu-prefix, dfu-suffix (DFU programming)" echo " - hid-flash (HID bootloader)" echo " - upload_reset (Reset utility)" +echo " - uf2upload (CDC touch and UF2 mass-storage upload)" echo "" echo "Files in ${OUTPUT_DIR}:" ls -lh "${OUTPUT_DIR}" diff --git a/src/linux-aarch64/clean.sh b/src/linux-aarch64/clean.sh index 8895e5eff..134ed0319 100755 --- a/src/linux-aarch64/clean.sh +++ b/src/linux-aarch64/clean.sh @@ -21,6 +21,7 @@ BINARIES=( "dfu-suffix" "hid-flash" "upload_reset" + "uf2upload" ) echo "Output directory: ${OUTPUT_DIR}" diff --git a/src/uf2upload/Makefile b/src/uf2upload/Makefile new file mode 100644 index 000000000..463639b5a --- /dev/null +++ b/src/uf2upload/Makefile @@ -0,0 +1,34 @@ +CXX ?= g++ +CXXFLAGS ?= -O2 -std=c++17 -Wall -Wextra -Werror +AARCH64_CXX ?= aarch64-linux-gnu-g++ +AARCH64_STRIP ?= aarch64-linux-gnu-strip +AARCH64_DEST = ../../linux/aarch64/uf2upload + +ifeq ($(OS),Windows_NT) + DEST = ../../win/uf2upload.exe + LDFLAGS ?= -static -static-libgcc -static-libstdc++ +else + LDFLAGS ?= + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + DEST = ../../macosx/uf2upload + else + DEST = ../../linux/x86_64/uf2upload + endif +endif + +.PHONY: all aarch64 clean + +all: $(DEST) + +$(DEST): uf2upload.cpp + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $< + +aarch64: $(AARCH64_DEST) + +$(AARCH64_DEST): uf2upload.cpp + $(AARCH64_CXX) $(CXXFLAGS) -o $@ $< + $(AARCH64_STRIP) $@ + +clean: + rm -f ../../win/uf2upload.exe ../../macosx/uf2upload ../../linux/x86_64/uf2upload $(AARCH64_DEST) diff --git a/src/uf2upload/uf2upload.cpp b/src/uf2upload/uf2upload.cpp new file mode 100644 index 000000000..d6c51c4e6 --- /dev/null +++ b/src/uf2upload/uf2upload.cpp @@ -0,0 +1,490 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Cross-platform UF2 uploader for Arduino boards using a USB CDC touch and + * a UF2 mass-storage bootloader. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +namespace { + +constexpr uint32_t UF2_MAGIC_START0 = 0x0A324655UL; +constexpr uint32_t UF2_MAGIC_START1 = 0x9E5D5157UL; +constexpr uint32_t UF2_MAGIC_END = 0x0AB16F30UL; +constexpr uint32_t UF2_FLAG_FAMILY_ID_PRESENT = 0x00002000UL; +constexpr size_t UF2_BLOCK_SIZE = 512U; +constexpr size_t UF2_PAYLOAD_SIZE = 256U; + +struct Options { + std::string input; + std::string port; + std::string volume; + uint32_t address = 0U; + uint32_t family = 0U; + size_t max_size = 0U; + unsigned int timeout_ms = 10000U; + bool skip_touch = false; +}; + +void usage(const char *program) +{ + std::cout << "Usage: " << program << " --input [--port ]" + << " --volume