From 9f2eedf1faf506d5f6ccc049908227c13b517c9b Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 25 Oct 2026 05:44:45 +0000 Subject: [PATCH 1/2] ci: build and smoke-test openipc-allwinner (V831, V833) build-allwinner.yml is build-anyka.yml for the new openipc-allwinner branch: one leg per SoC through its build.sh, which makes u-boot--nor.bin and u-boot--mmc.bin (the same layout with an SPL that loads U-Boot from an SD card, for unbricking); the 256 KiB check for both; a two-boot smoke test per SoC and kind in qemu-bench's v831/v833 machines -- the -nor image from an 8 MiB flash, the -mmc one from a 64 MiB card with the flash erased -- where the second boot must load the environment the first saved; release to the `allwinner` tag and publish to OpenIPC/firmware latest. The build legs also install xz-utils (U-Boot is LZMA-packed to fit the boot partition). The -mmc legs need qemu-bench's boot ROM stand-in to boot from the card (qemu-bench #204). The smoke step was run locally against all four images and passed. README and CLAUDE.md describe the family. --- .github/workflows/build-allwinner.yml | 385 ++++++++++++++++++++++++++ CLAUDE.md | 34 +++ README.md | 75 +++++ 3 files changed, 494 insertions(+) create mode 100644 .github/workflows/build-allwinner.yml diff --git a/.github/workflows/build-allwinner.yml b/.github/workflows/build-allwinner.yml new file mode 100644 index 000000000000..8e36ecc30a39 --- /dev/null +++ b/.github/workflows/build-allwinner.yml @@ -0,0 +1,385 @@ +name: Build allwinner + +on: + # Pull requests against this branch change the workflow itself; they + # build the current openipc-allwinner. Code changes reach it through + # workflow_call from the caller on openipc-allwinner, which passes the revision + # to build. Only workflow_dispatch can release or publish. + pull_request: + branches: [openipc] + paths: + - .github/workflows/build-allwinner.yml + workflow_call: + inputs: + ref: + description: openipc-allwinner revision to build + type: string + required: true + workflow_dispatch: + inputs: + release: + description: Publish to this repo's Allwinner 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-allwinner 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-allwinner' }} + 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: + # Built on every run, but held back from release/publish + # (hold: true) until qemu-hisilicon's master has the v831/v833 + # machines to smoke-test them (they boot there from their + # allwinner-v83x branch), or the images have booted on hardware. + - { soc: v831, files: "output/u-boot-v831-*.bin", hold: true } + - { soc: v833, files: "output/u-boot-v833-*.bin", hold: true } + + 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 xz-utils + } + 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 is the SPL (eGON.BT0) + # and the LZMA-packed U-Boot behind it. + 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" + # On a card the image starts at 8 KiB and the first partition + # at 1 MiB, but it must also fit the NOR it is there to flash. + check_kind mmc 256 "output/*${soc}*-mmc.bin" + exit $rc + + # A held SoC's artifact is named hold-u-boot-, which the + # release and publish jobs' u-boot-* download pattern skips. + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.hold && 'hold-' || '' }}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 v831/v833 machines, which do the boot ROM's job (load + # the eGON.BT0 SPL into SRAM; the SPL brings up the DDR and unpacks + # U-Boot), and asserts it reaches the autoboot prompt. A SoC that is not + # held is a hard gate on release; a held one is smoke-tested when the + # machine exists and skipped with a warning when it does not. + # + # The second boot must 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: + include: + - { soc: v831, kind: nor, hold: true } + - { soc: v831, kind: mmc, hold: true } + - { soc: v833, kind: nor, hold: true } + - { soc: v833, kind: mmc, hold: true } + 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: ${{ matrix.hold && 'hold-' || '' }}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 }}' + kind='${{ matrix.kind }}' + UBOOT="u-boot-${soc}-${kind}.bin" + test -f "$UBOOT" + echo "booting $UBOOT" + # nor: 8 MiB NOR, U-Boot at 0, the rest erased. mmc: the NOR + # erased, U-Boot at 8 KiB of a 64 MiB card, which the boot ROM + # tries first; the environment is in the NOR either way. + python3 - "$UBOOT" "$kind" /tmp/flash.bin /tmp/sd.img <<'PY' + import sys + u = open(sys.argv[1], 'rb').read() + img = bytearray(b'\xff' * (8 << 20)) + if sys.argv[2] == 'nor': + img[:len(u)] = u + else: + sd = bytearray(64 << 20) + sd[8192:8192 + len(u)] = u + open(sys.argv[4], 'wb').write(sd) + open(sys.argv[3], 'wb').write(img) + PY + FLASH="-M ${soc},flash-file=/tmp/flash.bin" + [ "$kind" = mmc ] && FLASH="$FLASH -drive if=sd,format=raw,file=/tmp/sd.img" + chmod +x ./qemu-arm + if ! ./qemu-arm -M help | grep -q "^${soc} "; then + if [ '${{ matrix.hold }}' = true ]; then + echo "::warning::qemu-hisilicon has no ${soc} machine yet; ${soc} is held, not smoke-tested" + exit 0 + fi + echo "FAIL: qemu-hisilicon has no ${soc} machine" + exit 1 + fi + # 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 Allwinner 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: allwinner + name: Allwinner + 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-v831/v833 names and leaves the other repositories' files alone + # (OpenIPC/u-boot-allwinner publishes u-boot-v851s-nor.bin). 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: + AW_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-allwinner-$(date -u +%Y%m%dT%H%M%SZ)-${AW_SHA::7}" \ + --release latest \ + --sha "$AW_SHA" \ + --built-at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --assets 'published/*.bin' diff --git a/CLAUDE.md b/CLAUDE.md index 387dfc9dbd27..1d39b0834093 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,6 +28,11 @@ own branch: loader, built from source in the tree. CI: `build-novatek.yml`, called from that branch's `ci.yml`. Its own `CLAUDE.md` covers the loader, the image layout and the kernel hand-off. +- `openipc-allwinner`: U-Boot for Allwinner V831/V833 (sun8iw19), on + the same mainline release with the same OpenIPC layer, as a mainline + sunxi SoC with its own SPL and DRAM driver. CI: `build-allwinner.yml`, + called from that branch's `ci.yml`. Its own `CLAUDE.md` covers the SPL, + the DRAM port 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 @@ -148,6 +153,35 @@ The build logic lives here. Other branches call it: (OpenIPC/website `service/internal/builds/verify.go`). `build-novatek.yml` has to be added there before the first `publish`. +## `build-allwinner.yml` + +- It is `build-anyka.yml` for `openipc-allwinner`: the same job graph and + `resolve`/`ref` rules, NOR only (256 KiB boot partition: the SPL, then + U-Boot LZMA-packed at 0x8000). +- The build legs also install `xz-utils`, for the LZMA image. +- **`hold`:** both SoCs are held (artifacts `hold-u-boot-`, skipped + by release and publish) until qemu-hisilicon's master has the `v831` + and `v833` machines. A held SoC's smoke leg warns and passes when the + machine is missing; one that is not held fails. Releasing them means + setting `hold: false` in both matrices. +- **Two kinds per SoC:** `u-boot--nor.bin` and `-mmc.bin` (SD card + boot: the SPL reads U-Boot from the card); one artifact holds both, the + smoke matrix has a leg per kind. +- **Smoke test:** two boots, `-nor` from an 8 MiB flash holding only the + image, `-mmc` from a 64 MiB card with it at 8 KiB and the flash erased. + It passes on `Hit Ctrl-C`, and fails if the second boot reports a bad + environment CRC (the environment is in the flash for both). +- **Release and publish:** + - `release` uploads to the `allwinner` tag. + - `publish` shares the `publish-firmware-latest` concurrency group and + reports a `uboot-allwinner-…` build id. OpenIPC/u-boot-allwinner + publishes `u-boot-v851s-nor.bin` to the same release; the names do + not overlap. + - openipc.org accepts the report only from workflows it allowlists + (OpenIPC/website `service/internal/builds/verify.go`). + `build-allwinner.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 24d82245bb0a..ceb62e5addc9 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![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) [![anyka build](https://github.com/OpenIPC/u-boot/actions/workflows/build-anyka.yml/badge.svg)](https://github.com/OpenIPC/u-boot/actions/workflows/build-anyka.yml) [![novatek build](https://github.com/OpenIPC/u-boot/actions/workflows/build-novatek.yml/badge.svg)](https://github.com/OpenIPC/u-boot/actions/workflows/build-novatek.yml) +[![allwinner build](https://github.com/OpenIPC/u-boot/actions/workflows/build-allwinner.yml/badge.svg)](https://github.com/OpenIPC/u-boot/actions/workflows/build-allwinner.yml) Unified repository for the U-Boot bootloaders maintained by [OpenIPC](https://openipc.org) for supported SoCs. @@ -21,6 +22,7 @@ family — not bootloader sources. | `openipc-gk710x` | U-Boot for Goke GK7101/GK7102 | | `openipc-anyka` | U-Boot for Anyka AK3918EV200, AK391xEV300, AK391xEV330 | | `openipc-novatek` | U-Boot for Novatek NT98562, NT98566 | +| `openipc-allwinner` | U-Boot for Allwinner V831, V833 | | `main` | Fork of upstream mainline U-Boot, kept for rebasing | New families are added here as they are ported, each with its own @@ -213,6 +215,20 @@ holds all three, to be written at flash offset 0 as it is: | `0x20000` | U-Boot, LZMA-packed | | `0x40000` | the environment (not in the image) | +**SD card (unbricking):** `u-boot--mmc.bin` is the same image with +an SPL that loads U-Boot from the card. The boot ROM tries the card +before the flash, so a camera with a broken or empty flash boots from a +card that has it at 8 KiB, before the first partition: + +```sh +dd if=u-boot-v831-mmc.bin of=/dev/sdX bs=1k seek=8 conv=fsync +``` + +Booted that way, U-Boot reads its update files from the card's FAT +partition (`source` is `fatload mmc 0`): `run ub` writes +`u-boot--nor.bin` to the flash, `run fwupd` `rootfs.squashfs.` +and `uImage.`. Remove the card and the camera boots from the flash. + OpenIPC kernels for these SoCs carry no device tree: U-Boot boots them with the loader's, giving Linux the first `osmem` of the DDR and the vendor media modules the rest, as the vendor U-Boot does. @@ -232,6 +248,44 @@ The loader needs an Arm bare-metal toolchain (`arm-none-eabi-`, else the one U-Boot uses; `LOADER_CROSS_COMPILE` overrides). Images land in `output/` as `u-boot--nor.bin`. +## allwinner + +U-Boot for the Allwinner V831 and V833 (sun8iw19), on the same mainline +release (v2022.10) and with the same OpenIPC boot layer as +`openipc-hisilicon`, as a mainline sunxi SoC with its own SPL: no vendor +boot0, OP-TEE or boot package. + +**NOR:** `v831` (64 MiB DDR2 in the package), `v833` (128 MiB DDR3). + +The boot ROM loads the SPL from flash offset 0 into SRAM. The SPL brings +up the DDR -- the vendor boot0's DRAM library, translated to C -- and +unpacks U-Boot, LZMA-packed, from offset 0x8000. `u-boot--nor.bin` +holds both, ~170 KiB, to be written at flash offset 0 as it is: + +| Offset | Contents | +| --- | --- | +| `0x00000` | the SPL (eGON.BT0) | +| `0x08000` | U-Boot, LZMA-packed | +| `0x40000` | the environment (not in the image) | + +OpenIPC kernels for these SoCs carry no device tree: U-Boot boots them +with the vendor's (the Tina SDK's reference board), built into U-Boot, +in the normal world with PSCI, as the vendor's OP-TEE and U-Boot do. + +These images have so far run only on qemu-hisilicon's V831/V833 +machines (which are not on its master yet), not on a camera. + +```sh +git clone --branch openipc-allwinner https://github.com/OpenIPC/u-boot.git +cd u-boot + +bash build.sh # v831 v833 +bash build.sh v831 # a single SoC +``` + +The toolchain is `arm-linux-gnueabi-`, and `xz` packs U-Boot. Images land +in `output/` as `u-boot--nor.bin`. + ## CI The [build-hisilicon](.github/workflows/build-hisilicon.yml) workflow runs @@ -347,6 +401,27 @@ Its `release` input fills this repository's gh workflow run build-novatek.yml -f release=true -f publish=true ``` +### allwinner + +The [build-allwinner](.github/workflows/build-allwinner.yml) workflow has +the same shape. It runs on pull requests to this branch that change it, +on pushes and pull requests to `openipc-allwinner` (through that branch's +`ci.yml`), and manually. Both SoCs are held (`hold: true`): built on every +run, not released or published, until qemu-hisilicon's master has the +v831/v833 machines; until then their smoke legs warn and pass. With a +machine, each image is booted -- the `-nor` one from an 8 MiB flash, the +`-mmc` one from a card with the flash erased -- then a second time, which +must load the environment the first saved. + +Its `release` input will fill this repository's +[`Allwinner` release](../../releases/tag/allwinner); `publish` uploads to +`OpenIPC/firmware` `latest`, where OpenIPC/u-boot-allwinner publishes only +`u-boot-v851s-nor.bin`, and reports to openipc.org. + +```sh +gh workflow run build-allwinner.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 From 77d7399baa6b3253e1b10d0866f945561fd27166 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 25 Oct 2026 05:51:55 +0000 Subject: [PATCH 2/2] ci, docs: allwinner: address review -- no hold, SD block in its section - The hold on both SoCs was meant to go before the push (qemu-bench has had the v831/v833 machines since #203) and did not: the artifacts were named hold-u-boot-*, so release and publish would have found nothing. Both SoCs are now released like the other families; the smoke legs are a hard gate. - The smoke test now also fails if the first boot does not report saving the environment, so the second boot's CRC check is known to test a saved one (the OpenIPC helper saves the default environment when it finds the area erased). - README: the SD card block had landed in the novatek section; it is in the allwinner one, which also names the -mmc images. The smoke step, extracted from the workflow, passes for v831/v833 x nor/mmc on qemu-bench master plus #204. --- .github/workflows/build-allwinner.yml | 43 +++++++++++-------------- CLAUDE.md | 8 ++--- README.md | 45 +++++++++++++-------------- 3 files changed, 43 insertions(+), 53 deletions(-) diff --git a/.github/workflows/build-allwinner.yml b/.github/workflows/build-allwinner.yml index 8e36ecc30a39..c897135f6eb4 100644 --- a/.github/workflows/build-allwinner.yml +++ b/.github/workflows/build-allwinner.yml @@ -60,12 +60,8 @@ jobs: fail-fast: false matrix: include: - # Built on every run, but held back from release/publish - # (hold: true) until qemu-hisilicon's master has the v831/v833 - # machines to smoke-test them (they boot there from their - # allwinner-v83x branch), or the images have booted on hardware. - - { soc: v831, files: "output/u-boot-v831-*.bin", hold: true } - - { soc: v833, files: "output/u-boot-v833-*.bin", hold: true } + - { soc: v831, files: "output/u-boot-v831-*.bin" } + - { soc: v833, files: "output/u-boot-v833-*.bin" } steps: - name: Checkout @@ -132,12 +128,10 @@ jobs: check_kind mmc 256 "output/*${soc}*-mmc.bin" exit $rc - # A held SoC's artifact is named hold-u-boot-, which the - # release and publish jobs' u-boot-* download pattern skips. - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: ${{ matrix.hold && 'hold-' || '' }}u-boot-${{ matrix.soc }} + name: u-boot-${{ matrix.soc }} path: ${{ matrix.files }} if-no-files-found: error @@ -172,9 +166,9 @@ jobs: # QEMU smoke test -- boots each freshly-built image from flash through # qemu-hisilicon's v831/v833 machines, which do the boot ROM's job (load # the eGON.BT0 SPL into SRAM; the SPL brings up the DDR and unpacks - # U-Boot), and asserts it reaches the autoboot prompt. A SoC that is not - # held is a hard gate on release; a held one is smoke-tested when the - # machine exists and skipped with a warning when it does not. + # U-Boot), and asserts it reaches the autoboot prompt. Every SoC has a + # machine model, so every image is a hard gate on release. The -mmc legs + # boot from the SD card, which the machines' boot ROM tries first. # # The second boot must load the environment the first saved, which # proves the driver's writes land where its reads find them. @@ -186,10 +180,10 @@ jobs: fail-fast: false matrix: include: - - { soc: v831, kind: nor, hold: true } - - { soc: v831, kind: mmc, hold: true } - - { soc: v833, kind: nor, hold: true } - - { soc: v833, kind: mmc, hold: true } + - { soc: v831, kind: nor } + - { soc: v831, kind: mmc } + - { soc: v833, kind: nor } + - { soc: v833, kind: mmc } steps: - name: Download qemu-arm uses: actions/download-artifact@v4 @@ -199,7 +193,7 @@ jobs: - name: Download u-boot artifact uses: actions/download-artifact@v4 with: - name: ${{ matrix.hold && 'hold-' || '' }}u-boot-${{ matrix.soc }} + name: u-boot-${{ matrix.soc }} - name: Install QEMU runtime deps run: | @@ -240,14 +234,6 @@ jobs: FLASH="-M ${soc},flash-file=/tmp/flash.bin" [ "$kind" = mmc ] && FLASH="$FLASH -drive if=sd,format=raw,file=/tmp/sd.img" chmod +x ./qemu-arm - if ! ./qemu-arm -M help | grep -q "^${soc} "; then - if [ '${{ matrix.hold }}' = true ]; then - echo "::warning::qemu-hisilicon has no ${soc} machine yet; ${soc} is held, not smoke-tested" - exit 0 - fi - echo "FAIL: qemu-hisilicon has no ${soc} machine" - exit 1 - fi # 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 @@ -278,6 +264,13 @@ jobs: exit 1 fi done + # The first boot finds the environment erased and the OpenIPC + # helper saves the default one (no command over the UART needed); + # the second must read it back. + if ! grep -q 'Saving Environment to SPIFlash' /tmp/uboot1.txt; then + echo "FAIL: the first boot did not save the environment" + exit 1 + fi if grep -q 'bad CRC' /tmp/uboot2.txt; then echo "FAIL: the environment saved on the first boot did not land" exit 1 diff --git a/CLAUDE.md b/CLAUDE.md index 1d39b0834093..d21fbecc7473 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -159,11 +159,9 @@ The build logic lives here. Other branches call it: `resolve`/`ref` rules, NOR only (256 KiB boot partition: the SPL, then U-Boot LZMA-packed at 0x8000). - The build legs also install `xz-utils`, for the LZMA image. -- **`hold`:** both SoCs are held (artifacts `hold-u-boot-`, skipped - by release and publish) until qemu-hisilicon's master has the `v831` - and `v833` machines. A held SoC's smoke leg warns and passes when the - machine is missing; one that is not held fails. Releasing them means - setting `hold: false` in both matrices. +- **No `hold`:** both SoCs have qemu-hisilicon machines (`v831`, + `v833`). The `-mmc` legs need its boot ROM stand-in to boot from the SD + card (qemu-bench #204). - **Two kinds per SoC:** `u-boot--nor.bin` and `-mmc.bin` (SD card boot: the SPL reads U-Boot from the card); one artifact holds both, the smoke matrix has a leg per kind. diff --git a/README.md b/README.md index ceb62e5addc9..11c684e5fd65 100644 --- a/README.md +++ b/README.md @@ -215,20 +215,6 @@ holds all three, to be written at flash offset 0 as it is: | `0x20000` | U-Boot, LZMA-packed | | `0x40000` | the environment (not in the image) | -**SD card (unbricking):** `u-boot--mmc.bin` is the same image with -an SPL that loads U-Boot from the card. The boot ROM tries the card -before the flash, so a camera with a broken or empty flash boots from a -card that has it at 8 KiB, before the first partition: - -```sh -dd if=u-boot-v831-mmc.bin of=/dev/sdX bs=1k seek=8 conv=fsync -``` - -Booted that way, U-Boot reads its update files from the card's FAT -partition (`source` is `fatload mmc 0`): `run ub` writes -`u-boot--nor.bin` to the flash, `run fwupd` `rootfs.squashfs.` -and `uImage.`. Remove the card and the camera boots from the flash. - OpenIPC kernels for these SoCs carry no device tree: U-Boot boots them with the loader's, giving Linux the first `osmem` of the DDR and the vendor media modules the rest, as the vendor U-Boot does. @@ -255,7 +241,8 @@ release (v2022.10) and with the same OpenIPC boot layer as `openipc-hisilicon`, as a mainline sunxi SoC with its own SPL: no vendor boot0, OP-TEE or boot package. -**NOR:** `v831` (64 MiB DDR2 in the package), `v833` (128 MiB DDR3). +**NOR and SD card:** `v831` (64 MiB DDR2 in the package), `v833` +(128 MiB DDR3). The boot ROM loads the SPL from flash offset 0 into SRAM. The SPL brings up the DDR -- the vendor boot0's DRAM library, translated to C -- and @@ -268,12 +255,26 @@ holds both, ~170 KiB, to be written at flash offset 0 as it is: | `0x08000` | U-Boot, LZMA-packed | | `0x40000` | the environment (not in the image) | +**SD card (unbricking):** `u-boot--mmc.bin` is the same image with +an SPL that loads U-Boot from the card. The boot ROM tries the card +before the flash, so a camera with a broken or empty flash boots from a +card that has it at 8 KiB, before the first partition: + +```sh +dd if=u-boot-v831-mmc.bin of=/dev/sdX bs=1k seek=8 conv=fsync +``` + +Booted that way, U-Boot reads its update files from the card's FAT +partition (`source` is `fatload mmc 0`): `run ub` writes +`u-boot--nor.bin` to the flash, `run fwupd` `rootfs.squashfs.` +and `uImage.`. Remove the card and the camera boots from the flash. + OpenIPC kernels for these SoCs carry no device tree: U-Boot boots them with the vendor's (the Tina SDK's reference board), built into U-Boot, in the normal world with PSCI, as the vendor's OP-TEE and U-Boot do. These images have so far run only on qemu-hisilicon's V831/V833 -machines (which are not on its master yet), not on a camera. +machines, not on a camera. ```sh git clone --branch openipc-allwinner https://github.com/OpenIPC/u-boot.git @@ -284,7 +285,7 @@ bash build.sh v831 # a single SoC ``` The toolchain is `arm-linux-gnueabi-`, and `xz` packs U-Boot. Images land -in `output/` as `u-boot--nor.bin`. +in `output/` as `u-boot--nor.bin` and `u-boot--mmc.bin`. ## CI @@ -406,12 +407,10 @@ gh workflow run build-novatek.yml -f release=true -f publish=true The [build-allwinner](.github/workflows/build-allwinner.yml) workflow has the same shape. It runs on pull requests to this branch that change it, on pushes and pull requests to `openipc-allwinner` (through that branch's -`ci.yml`), and manually. Both SoCs are held (`hold: true`): built on every -run, not released or published, until qemu-hisilicon's master has the -v831/v833 machines; until then their smoke legs warn and pass. With a -machine, each image is booted -- the `-nor` one from an 8 MiB flash, the -`-mmc` one from a card with the flash erased -- then a second time, which -must load the environment the first saved. +`ci.yml`), and manually. Every SoC has a qemu-hisilicon machine +model, so every image is smoke-tested: booted -- the `-nor` one from an +8 MiB flash, the `-mmc` one from a card with the flash erased -- then a +second time, which must load the environment the first saved. Its `release` input will fill this repository's [`Allwinner` release](../../releases/tag/allwinner); `publish` uploads to