From 0d8a293199042e0aad5a6a8ec18bb7b2c375f949 Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Mon, 6 Jul 2026 10:29:48 +0200 Subject: [PATCH] Dockerize build fixes: GCC 14 toolchain, clean build, flash helper Bring the headless Docker build closer to the official OpenGD77 build and add a flashing helper. docker/Dockerfile - Replace Ubuntu's stock gcc-arm-none-eabi (GCC 10.3.1) with the Arm GNU Toolchain 14.2.rel1 (GCC 14). The official firmware is built with GCC 14 (gnu-tools-for-stm32 14.3); GCC 10 produced a firmware that hard-faults on the DMR path. Guard the image build to fail if the compiler is not GCC 14. - git config --system safe.directory '*' so the build stamps a real GitID instead of #UNKNOWN. docker/build.sh - rm -rf build before compiling. obj/ lives on the host mount and was being reused across toolchain changes, silently relinking stale .o files (an earlier rebuild produced a byte-identical GCC-10 image because of this). docker/firmware.mk - Add -DNDEBUG to exactly mirror the STM32CubeIDE "MDUV380_FW" release config (which defines both NDEBUG and DEBUG). flash.sh - Helper to flash via opengd77_stm32_firmware_loader.py with model, secondary language (auto-builds the .gla files) and the DMR codec donor. .gitignore - Ignore generated *.gla files and the host-side languages_builder binary. Note: the GCC 14 rebuild did NOT resolve the DMR-receive hang; that fault is in the firmware source revision (this tree is a snapshot; the known-good build is commit ebd7100, not present here) and is out of scope of these build changes. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 4 ++ docker/Dockerfile | 33 ++++++++++++-- docker/build.sh | 3 ++ docker/firmware.mk | 3 +- flash.sh | 106 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 flash.sh diff --git a/.gitignore b/.gitignore index ee228ea..ef7714d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ .DS_Store build/ codec_bin_section_1.bin + +# Generated language plugin files + host-side builder binary +*.gla +languages_builder diff --git a/docker/Dockerfile b/docker/Dockerfile index 8562b73..5c473b9 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,14 +8,39 @@ FROM --platform=linux/amd64 ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive +# IMPORTANT: use the same GCC major version as the official OpenGD77 builds. +# Ubuntu 22.04's stock gcc-arm-none-eabi is GCC 10.3.1, which miscompiles the +# timing-critical DMR / HR-C6000 path: the radio hard-faults (FreeRTOS +# configASSERT -> disable IRQs -> spin forever, needs a battery pull) the moment +# the AMBE codec runs on DMR TX. FM is unaffected. The official firmware is built +# with "gnu-tools-for-stm32 14.3.rel1" (GCC 14.3); we pin the equivalent vanilla +# Arm GNU Toolchain (GCC 14.2) so our build matches. +ARG ARM_TOOLCHAIN_VERSION=14.2.rel1 +ARG ARM_TOOLCHAIN_TARBALL=arm-gnu-toolchain-${ARM_TOOLCHAIN_VERSION}-x86_64-arm-none-eabi.tar.xz +ARG ARM_TOOLCHAIN_URL=https://developer.arm.com/-/media/Files/downloads/gnu/${ARM_TOOLCHAIN_VERSION}/binrel/${ARM_TOOLCHAIN_TARBALL} + RUN apt-get update && apt-get install -y --no-install-recommends \ - gcc-arm-none-eabi \ - binutils-arm-none-eabi \ - libnewlib-arm-none-eabi \ - libstdc++-arm-none-eabi-newlib \ make \ git \ + wget \ + xz-utils \ ca-certificates \ && rm -rf /var/lib/apt/lists/* +# Download and install the Arm GNU Toolchain (bare-metal, arm-none-eabi). +RUN wget -q -O /tmp/arm-toolchain.tar.xz "${ARM_TOOLCHAIN_URL}" \ + && mkdir -p /opt/arm-toolchain \ + && tar -xJf /tmp/arm-toolchain.tar.xz -C /opt/arm-toolchain --strip-components=1 \ + && rm /tmp/arm-toolchain.tar.xz + +ENV PATH="/opt/arm-toolchain/bin:${PATH}" + +# Fail the image build early if the toolchain is not the expected GCC major. +RUN arm-none-eabi-gcc --version | grep -q ' 14\.' \ + && echo "Toolchain OK: $(arm-none-eabi-gcc --version | head -1)" + +# git refuses to report a version for a repo owned by another user (the mounted +# /work). Trust it so the build stamps a real GitID instead of #UNKNOWN. +RUN git config --system --add safe.directory '*' + WORKDIR /work diff --git a/docker/build.sh b/docker/build.sh index 4f51f13..951d9c5 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -12,6 +12,9 @@ chmod +x tools/codec_cleaner.Linux ( cd application/source/linkerdata && ../../../tools/codec_cleaner.Linux -C ) echo "==> Building firmware" +# Clean build: obj/ lives on the host mount and would otherwise be reused across +# toolchain changes, silently relinking stale .o files (e.g. an old GCC build). +rm -rf build mkdir -p build cd build make -f /work/docker/firmware.mk -j"$(nproc)" diff --git a/docker/firmware.mk b/docker/firmware.mk index f80c279..b2ac72c 100644 --- a/docker/firmware.mk +++ b/docker/firmware.mk @@ -21,7 +21,8 @@ TARGET := OpenMDUV380 CPU := -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb # ---- Preprocessor defines ------------------------------------------------- -DEFS := -DUSE_HAL_DRIVER -DSTM32F405xx -DPLATFORM_MDUV380 -DDEBUG +# NDEBUG + DEBUG mirrors the STM32CubeIDE "MDUV380_FW" release config exactly. +DEFS := -DUSE_HAL_DRIVER -DSTM32F405xx -DPLATFORM_MDUV380 -DNDEBUG -DDEBUG # Short git hash if available, otherwise UNKNOWN (matches the .cproject rule). GITVERSION := $(shell git -C .. rev-parse --short HEAD 2>/dev/null || echo UNKNOWN) diff --git a/flash.sh b/flash.sh new file mode 100644 index 0000000..db20f9e --- /dev/null +++ b/flash.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# +# flash.sh — flash the OpenGD77 firmware to an MD-UV380 / DM-1701 in DFU mode, +# optionally merging a secondary language and the DMR (AMBE codec) donor firmware. +# +# The radio's language slot #1 is always English. This script injects ONE +# secondary language (slot #2) via the loader's -L option. Re-flash with a +# different language to change it. +# +# Usage: +# ./flash.sh # defaults: model MD-UV380, language German +# ./flash.sh -L French # flash with French as the secondary language +# ./flash.sh -m DM-1701 -L Italian +# ./flash.sh -L none # English only, no secondary language +# ./flash.sh -s /path/donor.bin # (re)register the DMR codec donor firmware +# ./flash.sh --list-languages # show available languages and exit +# ./flash.sh -l # list attached DFU devices and exit +# +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FW_DIR="$REPO_DIR/MDUV380_firmware" +LOADER="$FW_DIR/tools/opengd77_stm32_firmware_loader.py" +FIRMWARE="$FW_DIR/build/OpenMDUV380.bin" +LANG_SRC="$FW_DIR/application/include/user_interface/languages/src" + +# ---- defaults ------------------------------------------------------------- +MODEL="MD-UV380" # or: DM-1701, MD-9600, MD-2017, MD-380 +LANGUAGE="German" # a language name (see --list-languages), or "none" +DONOR="" # official V26.45 firmware; empty = use the saved one + +usage() { sed -n '3,25p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; } +lc() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; } # lowercase (bash 3.2 safe) + +# ---- parse args ----------------------------------------------------------- +while [[ $# -gt 0 ]]; do + case "$1" in + -m|--model) MODEL="$2"; shift 2 ;; + -L|--lang|--language) LANGUAGE="$2"; shift 2 ;; + -s|--source|--donor) DONOR="$2"; shift 2 ;; + -f|--firmware) FIRMWARE="$2"; shift 2 ;; + -l|--list) python3 "$LOADER" -l; exit $? ;; + --list-languages) + echo "Available languages (pass to -L):" + ( cd "$LANG_SRC" && ls *.gla 2>/dev/null | sed 's/\.gla$//' ) \ + || ls "$LANG_SRC"/../*.h 2>/dev/null | xargs -n1 basename | sed 's/\.h$//' + echo " none (English only)" + exit 0 ;; + -h|--help) usage 0 ;; + *) echo "Unknown option: $1" >&2; usage 1 ;; + esac +done + +# ---- sanity checks -------------------------------------------------------- +[[ -f "$LOADER" ]] || { echo "!! Loader not found: $LOADER" >&2; exit 1; } +[[ -f "$FIRMWARE" ]] || { echo "!! Firmware not found: $FIRMWARE (run ./build-firmware.sh first)" >&2; exit 1; } + +# ---- resolve / build the language file ------------------------------------ +LANG_ARGS=() +if [[ -n "$LANGUAGE" && "$(lc "$LANGUAGE")" != "none" ]]; then + # Build the .gla files once if they're missing. + if ! ls "$LANG_SRC"/*.gla >/dev/null 2>&1; then + echo "==> Building language (.gla) files..." + ( cd "$LANG_SRC" && gcc -w -I../ -o languages_builder languages_builder.c && ./languages_builder >/dev/null ) + fi + # Case-insensitive match of the requested name to an actual .gla file. + GLA="" + for f in "$LANG_SRC"/*.gla; do + base="$(basename "$f" .gla)" + if [[ "$(lc "$base")" == "$(lc "$LANGUAGE")" ]]; then GLA="$f"; break; fi + done + if [[ -z "$GLA" ]]; then + echo "!! Unknown language '$LANGUAGE'. Available:" >&2 + ( cd "$LANG_SRC" && ls *.gla | sed 's/\.gla$//' | sed 's/^/ /' ) >&2 + exit 1 + fi + echo "==> Secondary language: $(basename "$GLA" .gla)" + LANG_ARGS=(-L "$GLA") +else + echo "==> Secondary language: none (English only)" +fi + +# ---- donor firmware (DMR / AMBE codec) ------------------------------------ +DONOR_ARGS=() +if [[ -n "$DONOR" ]]; then + [[ -f "$DONOR" ]] || { echo "!! Donor firmware not found: $DONOR" >&2; exit 1; } + echo "==> DMR donor (this run): $DONOR" + DONOR_ARGS=(-s "$DONOR") +elif grep -qi 'sourcestm32firmware *= *..*' "$HOME/.gd77firmwareloader.ini" 2>/dev/null; then + saved="$(grep -i 'sourcestm32firmware' "$HOME/.gd77firmwareloader.ini" | cut -d= -f2- | xargs)" + echo "==> DMR donor: using saved donor -> $saved" +else + echo "==> DMR donor: NONE registered — firmware will flash as FM-only." + echo " (pass -s /path/to/official_V26.45.bin once to enable DMR)" +fi + +# ---- flash ---------------------------------------------------------------- +echo "==> Model: $MODEL" +echo "==> Firmware: $FIRMWARE" +echo "==> Make sure the radio is in DFU mode (power on holding PTT + SK1)." +echo +set -x +exec python3 "$LOADER" -m "$MODEL" \ + "${DONOR_ARGS[@]+"${DONOR_ARGS[@]}"}" \ + "${LANG_ARGS[@]+"${LANG_ARGS[@]}"}" \ + -f "$FIRMWARE"