Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
356 changes: 356 additions & 0 deletions .github/workflows/build-gk710x.yml
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +75 to +85

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Vendor builds fail in the ci environment 📘 Rule violation ☼ Reliability

The build leg installs only a subset of the required tools and never changes /bin/sh to bash. When
vendor recipes require pycryptodome, u-boot-tools, lzop, or bash-specific /bin/sh behavior,
the build can fail before producing its boot image.
Agent Prompt
## Issue description
The GK710x build leg does not install `u-boot-tools` or `lzop`, does not install `pycryptodome`, and does not point `/bin/sh` to bash as required by the vendor build recipes.

## Fix Focus Areas
- .github/workflows/build-gk710x.yml[75-85]

## Recommended Fix
Match the established build dependency setup by installing `u-boot-tools` and `lzop`, installing `pycryptodome` with pip, and creating the `/bin/sh` symlink to `/bin/bash` before invoking `build.sh`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

}
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:
Comment on lines +135 to +139

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Smoke setup can skip revision resolution 📘 Rule violation ☼ Reliability

The qemu job has no needs: resolve, unlike the build job, so QEMU preparation is independent of
the resolved source job. A run can therefore start QEMU preparation before the revision is pinned,
leaving the declared resolve -> build + qemu graph unenforced.
Agent Prompt
## Issue description
The QEMU preparation job is not dependent on `resolve`, so it can begin before the workflow pins the source revision even though the required job graph places `resolve` before both build and QEMU preparation.

## Fix Focus Areas
- .github/workflows/build-gk710x.yml[135-139]

## Recommended Fix
Add `needs: resolve` to the `qemu` job so revision resolution completes before QEMU preparation starts, while retaining the existing `qemu_smoke` dependency on both `build` and `qemu`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

- 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
Comment on lines +253 to +255

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. The smoke test can miss failed saves 🐞 Bug ≡ Correctness

qemu_smoke waits for the autoboot prompt and kills QEMU without issuing a save command or checking
that a save completed. If the first boot does not persist its environment, the second boot is judged
solely by the absence of the literal bad CRC message rather than by loading a value written on the
first boot.
Agent Prompt
## Issue description
The two-boot smoke test does not explicitly save an environment value or verify that the second boot loaded it.

## Fix Focus Areas
- .github/workflows/build-gk710x.yml[223-256]

## Recommended Fix
On the first boot, interrupt autoboot, set a distinctive environment variable, run the save command, and wait for confirmation before stopping QEMU. On the second boot, verify that the variable has the saved value.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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 \
Comment on lines +343 to +350

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. New releases miss the website record 🐞 Bug ≡ Correctness

The publish job uploads assets before push_build.py reports them, but the new workflow is not
yet on the website's stated allowlist. Until that separate allowlist change is deployed, a manual
publish can update OpenIPC/firmware and then have its openipc.org report rejected.
Agent Prompt
## Issue description
Publishing uploads release assets before reporting them, but the website does not yet accept reports from this workflow.

## Fix Focus Areas
- .github/workflows/build-gk710x.yml[308-356]
- CLAUDE.md[95-101]

## Recommended Fix
Add `build-gk710x.yml` to the OpenIPC/website workflow allowlist and deploy that change before the first publish run. Keep the publish input unused until the verifier accepts reports from this workflow.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

--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'
26 changes: 26 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Loading
Loading