From 5ac683f0891b9b9ee77f8a523a3bd6c9b201e7aa Mon Sep 17 00:00:00 2001 From: Dimasik Date: Sun, 18 Oct 2026 12:24:44 +0000 Subject: [PATCH] ci: build, smoke-test and release the Goke GK7101/GK7102 U-Boot build-gk710x.yml is build-xm.yml for the openipc-gk710x branch: resolve -> build (gk7101, gk7102) + qemu -> qemu_smoke -> release (gk710x tag) / publish (OpenIPC/firmware latest, openipc.org). NOR only, 256 KiB boot partition including the boot ROM header. The smoke test boots each image from an 8 MiB flash image twice on qemu-hisilicon's goke machines; the second boot must load the environment the first saved. The flash file is written byte-swapped per 32-bit word, as the chip holds it. README and CLAUDE.md describe the family and the workflow. openipc.org must allowlist build-gk710x.yml before the first publish. The smoke step was run locally against both images with qemu-hisilicon master: PASS for gk7101 and gk7102. --- .github/workflows/build-gk710x.yml | 356 +++++++++++++++++++++++++++++ CLAUDE.md | 26 +++ README.md | 56 +++++ 3 files changed, 438 insertions(+) create mode 100644 .github/workflows/build-gk710x.yml diff --git a/.github/workflows/build-gk710x.yml b/.github/workflows/build-gk710x.yml new file mode 100644 index 000000000000..bf83a2ef505e --- /dev/null +++ b/.github/workflows/build-gk710x.yml @@ -0,0 +1,356 @@ +name: Build gk710x + +on: + # Pull requests against this branch change the workflow itself; they + # build the current openipc-gk710x. Code changes reach it through + # workflow_call from the caller on openipc-gk710x, which passes the revision + # to build. Only workflow_dispatch can release or publish. + pull_request: + branches: [openipc] + paths: + - .github/workflows/build-gk710x.yml + workflow_call: + inputs: + ref: + description: openipc-gk710x revision to build + type: string + required: true + workflow_dispatch: + inputs: + release: + description: Publish to this repo's GK710x release + type: boolean + default: false + publish: + description: Publish to OpenIPC/firmware latest and report to openipc.org + type: boolean + default: false + +# The shape of build-hisilicon.yml: +# build (matrix, every SoC) -> qemu (prep) -> qemu_smoke (hard gate) -> +# release (own, `release` input) + publish (OpenIPC/firmware latest + +# openipc.org report, `publish` input). +# build.sh takes SoCs as arguments; the matrix calls it with one SoC per leg. + +jobs: + # Resolve the openipc-gk710x revision this run builds from. The workflow + # lives on the openipc branch, so github.sha is the openipc commit, not + # the revision being built; release provenance must name the commit that + # build.sh actually compiled. + resolve: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + sha: ${{ steps.rev.outputs.sha }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || 'openipc-gk710x' }} + path: src + + - name: Resolve revision + id: rev + run: echo "sha=$(git -C src rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + build: + needs: resolve + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - { soc: gk7101, files: "output/u-boot-gk7101-*.bin" } + - { soc: gk7102, files: "output/u-boot-gk7102-*.bin" } + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # The commit resolve recorded, not the ref again: every leg then + # builds the revision the release and openipc.org report name, + # even if the branch moves while the matrix runs. + ref: ${{ needs.resolve.outputs.sha }} + + - name: Install dependencies + timeout-minutes: 15 + run: | + # apt mirrors on hosted runners intermittently HANG on the + # mirrorlist fetch (not just fail). Bound each attempt with its own + # `timeout` so a hang is killed fast and the retry actually fires. + install() { + sudo timeout 180 apt-get update && \ + sudo timeout 240 apt-get install -y --no-install-recommends \ + build-essential gcc-arm-linux-gnueabi device-tree-compiler \ + bison flex bc libssl-dev python3 + } + for i in 1 2 3 4; do + install && break + [ "$i" = 4 ] && { echo "apt failed after 4 attempts" >&2; exit 1; } + echo "apt attempt $i failed/timed out; retrying in $((i*10))s" >&2 + sleep $((i*10)) + done + + - name: Build ${{ matrix.soc }} + run: bash build.sh ${{ matrix.soc }} + + - name: Check partition fit + run: | + # The boot partition from this tree's mtdparts: NOR boot = 256 KiB + # (env at 0x40000). An image larger than it overflows into env and + # bricks the device on upgrade. The image includes the boot ROM + # header. + soc='${{ matrix.soc }}' + rc=0 + check_kind() { + kind=$1 + limit_kb=$2 + pattern=$3 + found=0 + for f in $pattern; do + [ -f "$f" ] || continue + found=1 + # Compare bytes: rounding down to KiB would pass an image up + # to 1023 bytes into the environment. + size=$(stat -c %s "$f") + limit=$((limit_kb * 1024)) + echo "- $f: [${size}/${limit} bytes]" + if [ "$size" -gt "$limit" ]; then + echo "-- size exceeded by: $((size - limit)) bytes" >&2 + rc=1 + fi + done + [ "$found" = 1 ] || { echo "missing ${kind} image for ${soc} (${pattern})" >&2; rc=1; } + } + check_kind nor 256 "output/*${soc}*-nor.bin" + exit $rc + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: u-boot-${{ matrix.soc }} + path: ${{ matrix.files }} + if-no-files-found: error + + # Build qemu-system-arm once and share it with every smoke leg. + qemu: + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - name: Install QEMU build deps + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq --no-install-recommends \ + build-essential ninja-build meson pkg-config \ + libglib2.0-dev libpixman-1-dev libfdt-dev zlib1g-dev \ + libslirp-dev python3 python3-venv + + - name: Build qemu-system-arm + run: | + git clone --depth 1 --branch master \ + https://github.com/widgetii/qemu-hisilicon qemu-hisilicon + cd qemu-hisilicon + bash qemu/setup.sh + cp qemu-src/build/qemu-system-arm "$GITHUB_WORKSPACE/qemu-arm" + chmod +x "$GITHUB_WORKSPACE/qemu-arm" + + - name: Upload qemu-arm + uses: actions/upload-artifact@v4 + with: + name: qemu-arm + path: qemu-arm + + # QEMU smoke test -- boots each freshly-built image from flash through + # qemu-hisilicon's goke machines, which do the boot ROM's job from the + # image's header (DDR set-up, copy to the load address), and asserts it + # reaches the autoboot prompt. Every SoC has a machine model, so every + # SoC is a hard gate on release. + # + # The flash controller packs bytes MSB first, so the chip holds every + # 32-bit word of the image byte-swapped: the flash file is built that + # way. The second boot must then load the environment the first saved, + # which proves the driver's writes land where its reads find them. + qemu_smoke: + needs: [build, qemu] + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + soc: [gk7101, gk7102] + steps: + - name: Download qemu-arm + uses: actions/download-artifact@v4 + with: + name: qemu-arm + + - name: Download u-boot artifact + uses: actions/download-artifact@v4 + with: + name: u-boot-${{ matrix.soc }} + + - name: Install QEMU runtime deps + run: | + # qemu-arm is built once in the `qemu` job and downloaded here, + # so this leg needs the shared libraries it links against. + sudo apt-get update -qq + sudo apt-get install -y -qq --no-install-recommends \ + libglib2.0-0 libpixman-1-0 libfdt1 zlib1g libslirp0 + if ldd ./qemu-arm | grep -qi 'not found'; then + echo "qemu-arm has unresolved libraries:" >&2 + ldd ./qemu-arm >&2 + exit 1 + fi + + - name: Smoke-test in QEMU + timeout-minutes: 3 + run: | + soc='${{ matrix.soc }}' + UBOOT="u-boot-${soc}-nor.bin" + test -f "$UBOOT" + echo "booting $UBOOT" + # 8 MiB NOR, U-Boot at 0, the rest erased, every word byte-swapped + python3 - "$UBOOT" /tmp/flash.bin <<'PY' + import sys + img = bytearray(b'\xff' * (8 << 20)) + u = open(sys.argv[1], 'rb').read() + img[:len(u)] = u + open(sys.argv[2], 'wb').write( + b''.join(bytes(img[i:i + 4])[::-1] for i in range(0, len(img), 4))) + PY + FLASH="-M ${soc},flash-file=/tmp/flash.bin" + chmod +x ./qemu-arm + # Two boots: the first finds an erased environment and saves the + # default one; the second must load it back. + for boot in 1 2; do + mkfifo /tmp/uart.in /tmp/uart.out + ./qemu-arm $FLASH \ + -nographic -serial pipe:/tmp/uart \ + -monitor none > /tmp/qemu.log 2>&1 & + QEMU_PID=$! + # Wait for the autoboot prompt, not the banner: the banner prints + # before the board init that can hang. Up to 30 s. + cat /tmp/uart.out > /tmp/uboot$boot.txt & + CAT_PID=$! + for i in $(seq 1 60); do + grep -q 'Hit Ctrl-C' /tmp/uboot$boot.txt 2>/dev/null && break + sleep 0.5 + done + kill "$QEMU_PID" "$CAT_PID" 2>/dev/null || true + wait 2>/dev/null || true + rm -f /tmp/uart.in /tmp/uart.out + echo "== boot $boot: captured u-boot output ==" + cat /tmp/uboot$boot.txt + echo + if ! grep -q 'Hit Ctrl-C' /tmp/uboot$boot.txt; then + echo "FAIL: boot $boot did not reach the autoboot prompt" + echo + echo "== qemu stderr ==" + tail -40 /tmp/qemu.log || true + exit 1 + fi + done + if grep -q 'bad CRC' /tmp/uboot2.txt; then + echo "FAIL: the environment saved on the first boot did not land" + exit 1 + fi + echo "PASS: u-boot reached the autoboot prompt, and its saved environment survived a reset" + + # Mirror every built image to this repository's own GK710x release. + release: + needs: [resolve, build, qemu_smoke] + if: ${{ github.event_name == 'workflow_dispatch' && inputs.release }} + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + pattern: u-boot-* + + - name: Upload to releases + uses: softprops/action-gh-release@v2 + with: + tag_name: gk710x + name: GK710x + target_commitish: ${{ needs.resolve.outputs.sha }} + prerelease: true + files: artifacts/**/*.bin + fail_on_unmatched_files: true + + # Publish every image to the OpenIPC/firmware `latest` release and tell + # openipc.org what was uploaded. openipc.org keeps, per file name, the + # newest uboot build that names it, so this run adds or refreshes the + # u-boot-gk710* names and leaves the other repositories' files alone. The + # concurrency group, shared with build-hisilicon.yml, keeps a second run + # from uploading between this run's upload and its report. + publish: + needs: [resolve, build, qemu_smoke] + if: ${{ github.event_name == 'workflow_dispatch' && inputs.publish }} + runs-on: ubuntu-latest + timeout-minutes: 30 + concurrency: + group: publish-firmware-latest + cancel-in-progress: false + permissions: + contents: read + id-token: write + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + pattern: u-boot-* + + - name: Upload to OpenIPC/firmware latest release + env: + GH_TOKEN: ${{ secrets.FIRMWARE_RELEASE_TOKEN }} + run: | + set -eu + # Releases API occasionally returns 502/503. Retry with backoff: + # 10s, 20s, 30s, 40s -- total ~100s before giving up. + upload() { + local f="$1" i + for i in 1 2 3 4 5; do + if gh release upload latest "$f" --clobber -R OpenIPC/firmware; then + return 0 + fi + [ "$i" = "5" ] && return 1 + echo "upload attempt $i failed; sleeping $((i*10))s" >&2 + sleep $((i*10)) + done + } + find artifacts -type f -name '*.bin' | sort | while read -r f; do + echo "publishing $f" + upload "$f" + done + + # The push script is OpenIPC/firmware's, at a fixed commit: the only + # code this job checks out, since it holds an id-token. + - uses: actions/checkout@v4 + with: + repository: OpenIPC/firmware + ref: 7fa63ce694cb5d27c19d4f19058ba2c044849db4 + sparse-checkout: .github/scripts + path: firmware + + # The contract is PUSH.md in OpenIPC/website (source `uboot`, release + # `latest`, one id per run). The digests are of the files uploaded + # above, hashed here, so the push describes exactly those bytes. + - name: Report to openipc.org + env: + GK_SHA: ${{ needs.resolve.outputs.sha }} + run: | + set -eu + mkdir published + find artifacts -type f -name '*.bin' -exec cp {} published/ \; + python3 firmware/.github/scripts/push_build.py \ + --source uboot \ + --build-id "uboot-gk710x-$(date -u +%Y%m%dT%H%M%SZ)-${GK_SHA::7}" \ + --release latest \ + --sha "$GK_SHA" \ + --built-at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --assets 'published/*.bin' diff --git a/CLAUDE.md b/CLAUDE.md index 116e0e8d38b4..8e8ea026692b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,6 +14,10 @@ own branch: - `openipc-xm`: U-Boot for Xiongmai XM510/XM530/XM550, on the same mainline release with the same OpenIPC layer. CI: `build-xm.yml`, called from that branch's `ci.yml`. Its own `CLAUDE.md` covers the run-in-place boot. +- `openipc-gk710x`: U-Boot for Goke GK7101/GK7102, on the same mainline + release with the same OpenIPC layer. CI: `build-gk710x.yml`, called from + that branch's `ci.yml`. Its own `CLAUDE.md` covers the boot header, the + flash byte order and the kernel hand-off. - `main`: a fork of upstream mainline U-Boot, kept for rebasing. To change bootloader code, check out `openipc-hisilicon`, or read it with @@ -74,6 +78,28 @@ The build logic lives here. Other branches call it: - openipc.org keeps the newest build per file name, so the two workflows' pushes do not displace each other. +## `build-gk710x.yml` + +- It is `build-xm.yml` for `openipc-gk710x`: the same job graph and + `resolve`/`ref` rules, NOR only (256 KiB boot partition, header + included). +- **No `hold`:** both SoCs have qemu-hisilicon machines (`gk7101`, + `gk7102`; qemu-hisilicon #159 or later). +- **Smoke test:** two boots of an 8 MiB flash image holding only U-Boot. + - The image is written **byte-swapped per 32-bit word**, as the chip + holds it. The flash controller packs bytes MSB first, and the ROM and + drivers move whole little-endian words; `openipc-gk710x`'s `CLAUDE.md` + explains it. + - It passes on `Hit Ctrl-C`, and fails if the second boot reports a bad + environment CRC. +- **Release and publish:** + - `release` uploads to the `gk710x` tag. + - `publish` shares the `publish-firmware-latest` concurrency group and + reports a `uboot-gk710x-…` build id. + - openipc.org accepts the report only from workflows it allowlists + (OpenIPC/website `service/internal/builds/verify.go`). `build-gk710x.yml` + has to be added there before the first `publish`. + ## Invariants to keep when editing `build-hisilicon.yml` - **Three SoC lists must agree**: the `build` matrix, the `qemu_smoke` matrix, diff --git a/README.md b/README.md index b7d02bb22d25..2dc53330bad1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![hisilicon build](https://github.com/OpenIPC/u-boot/actions/workflows/build-hisilicon.yml/badge.svg)](https://github.com/OpenIPC/u-boot/actions/workflows/build-hisilicon.yml) [![xiongmai build](https://github.com/OpenIPC/u-boot/actions/workflows/build-xm.yml/badge.svg)](https://github.com/OpenIPC/u-boot/actions/workflows/build-xm.yml) +[![gk710x build](https://github.com/OpenIPC/u-boot/actions/workflows/build-gk710x.yml/badge.svg)](https://github.com/OpenIPC/u-boot/actions/workflows/build-gk710x.yml) Unified repository for the U-Boot bootloaders maintained by [OpenIPC](https://openipc.org) for supported SoCs. @@ -15,6 +16,7 @@ family — not bootloader sources. | `openipc` (this branch) | Unified documentation and CI workflows | | `openipc-hisilicon` | U-Boot for HiSilicon/Goke IP-camera SoCs | | `openipc-xm` | U-Boot for Xiongmai XM510/XM530/XM550 | +| `openipc-gk710x` | U-Boot for Goke GK7101/GK7102 | | `main` | Fork of upstream mainline U-Boot, kept for rebasing | New families are added here as they are ported, each with its own @@ -90,6 +92,41 @@ Images land in `output/` as `u-boot--nor.bin` and, for xm530/xm550, The toolchain is `arm-linux-gnueabi-`, as for hisilicon. +## gk710x + +U-Boot for Goke's own ARM1176 camera SoCs, the GK7101 and GK7102 -- not +the HiSilicon-derived GK7205/GK7605 above -- on the same mainline release +(v2022.10) and with the same OpenIPC boot layer as `openipc-hisilicon`. + +**NOR:** `gk7101`, `gk7102`. GK7101S/GK7102S are a different die and are +not covered. + +The boot ROM runs the DDR set-up listed in the image's header and copies +U-Boot into DDR, so `u-boot--nor.bin` is the header and `u-boot.bin`, +written at flash offset 0. It is in CPU byte order, as `sf write` stores +it; the flash chip holds every 32-bit word byte-swapped, so an image for +an external programmer must be swapped. + +The vendor kernel needs what the vendor U-Boot leaves it besides ATAGs: +its HAL blob, memory layout words and the board's GPIO map, from which it +configures every pin. This U-Boot provides all three. The GPIO map is the +vendor reference boards' default layout (rb-sc1035 v2.1, sc1045, imx225, +ov9750, jxh42 v2.0, gc1024; GK7101 rb-ov2710 v2.2); `setenv gpiomap v1` +selects the other one (rb-ov9712, ar0130, jxh42 v1.0, GK7101 imx222). + +These images have so far run only on qemu-hisilicon's `gk7101` and +`gk7102` machines, not on a camera. SD card recovery is not enabled. + +```sh +git clone --branch openipc-gk710x https://github.com/OpenIPC/u-boot.git +cd u-boot + +bash build.sh # gk7101 gk7102 +bash build.sh gk7102 # a single SoC +``` + +Images land in `output/` as `u-boot--nor.bin`. + ## CI The [build-hisilicon](.github/workflows/build-hisilicon.yml) workflow runs @@ -149,6 +186,25 @@ Its `release` input fills this repository's gh workflow run build-xm.yml -f release=true -f publish=true ``` +### gk710x + +The [build-gk710x](.github/workflows/build-gk710x.yml) workflow has the +same shape. It runs on pull requests to this branch that change it, on +pushes and pull requests to `openipc-gk710x` (through that branch's +`ci.yml`), and manually. Every SoC has a qemu-hisilicon machine model, so +every image is smoke-tested: booted from an 8 MiB flash image (built +byte-swapped, as the chip holds it), then a second time, which must load +the environment the first boot saved. + +Its `release` input fills this repository's +[`GK710x` release](../../releases/tag/gk710x); `publish` uploads to +`OpenIPC/firmware` `latest`, where no other repository publishes +`u-boot-gk710*` names, and reports to openipc.org. + +```sh +gh workflow run build-gk710x.yml -f release=true -f publish=true +``` + ## Adding a SoC family 1. Push the port on a new branch, based on a mainline U-Boot release or