-
-
Notifications
You must be signed in to change notification settings - Fork 488
337 lines (310 loc) · 17.4 KB
/
Copy pathimage.yml
File metadata and controls
337 lines (310 loc) · 17.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
name: image
on:
workflow_run:
workflows: [build]
types: [completed]
workflow_dispatch:
inputs:
build_id:
description: 'Build ID to assemble (e.g. nightly-20260520-887328c). Default: channels.nightly from manifest.'
required: false
permissions:
contents: write
jobs:
toolchain:
name: Image
runs-on: ubuntu-latest
# Run after nightly (schedule) or operator dispatch only — PR builds do
# not produce new release assets, so reassembling images on every PR
# build is wasted compute. Mirrors the gate in manifest.yml.
#
# Publish on success AND on partial-failure: see manifest.yml rationale.
# A flaky board doesn't deny image assembly for the platforms that did
# upload a firmware tgz — firmware_url returns empty for missing ones and
# create() skips them.
#
# First clause: on a mirror the nightly's jobs skip, but the run still
# completes and fires workflow_run, so gate the cron-originated path on
# the canonical repo too — see build.yml's preflight. Dispatch-driven
# reassembly is untouched anywhere.
if: >-
(github.event.workflow_run.event != 'schedule' ||
github.repository == 'OpenIPC/firmware') &&
(github.event_name == 'workflow_dispatch' ||
((github.event.workflow_run.event == 'schedule' ||
github.event.workflow_run.event == 'workflow_dispatch') &&
contains(fromJSON('["success", "failure"]'), github.event.workflow_run.conclusion)))
env:
SIGMASTAR: ssc30kd ssc30kq ssc325 ssc333 ssc335 ssc335de ssc337 ssc337de ssc338q ssc377 ssc377d ssc377de ssc377qe ssc378de ssc378qe
INGENIC: t10 t10l t20 t20l t20x t21n t23n t30a t30a1 t30l t30n t30x t31a t31al t31l t31lc t31n t31x
ALLWINNER: v851s
MANIFEST_URL: https://openipc.github.io/firmware/manifest.json
UBOOT_URL: https://github.com/openipc/firmware/releases/download/latest
steps:
- name: Resolve build_id from manifest
id: resolve
env:
INPUT_BUILD_ID: ${{ inputs.build_id }}
run: |
set -eu
curl -fsSL "$MANIFEST_URL" -o /tmp/manifest.json
if [ -n "$INPUT_BUILD_ID" ]; then
BUILD_ID="$INPUT_BUILD_ID"
else
BUILD_ID=$(jq -r '.channels.nightly // ""' /tmp/manifest.json)
fi
if [ -z "$BUILD_ID" ] || [ "$BUILD_ID" = "null" ]; then
echo "::error::No build_id resolved (manifest channels.nightly empty? input not given?)"
exit 1
fi
# Sanity check: does the manifest actually know this build?
if ! jq -e --arg b "$BUILD_ID" '.builds[] | select(.id==$b)' /tmp/manifest.json >/dev/null; then
echo "::error::build_id $BUILD_ID is not present in manifest at $MANIFEST_URL"
exit 1
fi
echo "build_id=$BUILD_ID" >> "$GITHUB_OUTPUT"
echo "Assembling images for $BUILD_ID"
- name: Prepare
env:
BUILD_ID: ${{ steps.resolve.outputs.build_id }}
run: |
MANIFEST=/tmp/manifest.json
# firmware_url <firmware_soc> <variant>
# -> echoes the .nor URL for ${firmware_soc}_${variant} in
# $BUILD_ID, or nothing if the manifest has no such entry.
firmware_url() {
jq -r --arg b "$BUILD_ID" --arg p "${1}_${2}" \
'.builds[] | select(.id==$b) | .platforms[$p].nor.url // empty' \
"$MANIFEST"
}
# create <uboot_board> <firmware_soc> <variant>
# The two SoC arguments differ for Ingenic: u-boot is per
# board (t31al, t31lc, ...), firmware is per family (t31).
create() {
uboot=u-boot-$1-nor.bin
release=target/openipc-$1-nor-$3.bin
mkdir -p output target
if ! wget -nv "$UBOOT_URL/$uboot" -O "output/$1.bin"; then
echo -e "Download failed: $UBOOT_URL/$uboot\n"
return 0
fi
url=$(firmware_url "$2" "$3")
if [ -z "$url" ]; then
echo -e "Skip: no firmware in $BUILD_ID for ${2}_${3}\n"
return 0
fi
if ! wget -nv "$url" -O "output/$2.tgz"; then
echo -e "Download failed: $url\n"
return 0
fi
tar -xf output/$2.tgz -C output
dd if=/dev/zero bs=1K count=5000 status=none | tr '\000' '\377' > $release
dd if=output/$1.bin of=$release bs=1K seek=0 conv=notrunc status=none
dd if=output/uImage.$2 of=$release bs=1K seek=320 conv=notrunc status=none
dd if=output/rootfs.squashfs.$2 of=$release bs=1K seek=2368 conv=notrunc status=none
rm -rf output
echo -e "Created: $release\n"
}
# create_hisi <uboot_artifact> <firmware_soc> <variant> <release_tag> <fw_off_kib>
# HiSilicon V4/V5: u-boot is published per DDR binning (the
# boot-ROM DDR init table is baked into each boot image), and the
# firmware .tgz holds a single pre-packed kernel+rootfs blob
# (firmware.bin.<soc>) flashed at the SoC's firmware-partition
# offset. So assemble one full NOR image per DDR binning:
# u-boot at 0, firmware.bin at <fw_off_kib>.
# Where the image has to land is decided by the u-boot it is paired
# with: the cv610 and dv500 boot images carry their partition table
# in a compiled-in env (a HiSilicon boot table with a gzip'd u-boot
# behind it), and nothing rewrites that table later. Decompressed:
#
# cv6xx 256k(boot),64k(env),2048k(kernel),5120k(rootfs),7168k@0x50000(firmware),-(rootfs_data)
# bootnor: sf read ${kernaddr}=0x50000 ${kernsize}=0x200000; root=/dev/mtdblock3
# dv500 512k(boot),256k(env),6144k(kernel),8192k(rootfs),14336k@0xC0000(firmware),-(rootfs_data)
#
# So the kernel is read as one 2048/6144 KiB slot and the rootfs is
# mounted at a fixed offset behind it. firmware.bin is the FIT with
# the squashfs packed at the next 64 KiB boundary (board
# post-image.sh), which is only the same thing if the FIT happens to
# end exactly at the slot. Split the blob the way sysupgrade's
# do_update_firmware does and put each half where the env looks.
hisi_layout() {
case "$1" in
hi3516cv6xx) boot_kib=256 kern_kib=2048 fw_kib=7168 ;;
*) echo "::error::create_hisi: no NOR layout known for $1"; return 1 ;;
esac
}
create_hisi() {
local uboot="$1" soc="$2" variant="$3" tag="$4" off="$5" nor="${6:-16384}"
local release="target/openipc-${tag}-nor-${variant}.bin"
local boot_kib kern_kib fw_kib fw fitsz fit_kib rootsz root_kib ub_kib got
hisi_layout "$soc" || return 1
rm -rf output; mkdir -p output target
if ! wget -nv "$UBOOT_URL/$uboot" -O "output/$uboot"; then
echo -e "Download failed: $UBOOT_URL/$uboot\n"
return 0
fi
url=$(firmware_url "$soc" "$variant")
if [ -z "$url" ]; then
echo -e "Skip: no firmware in $BUILD_ID for ${soc}_${variant}\n"
return 0
fi
if ! wget -nv "$url" -O "output/$soc.tgz"; then
echo -e "Download failed: $url\n"
return 0
fi
tar -xf "output/$soc.tgz" -C output
fw="output/firmware.bin.$soc"
# The FIT is a flattened device tree: its total size is the
# big-endian word at byte 4. The rootfs starts at the next 64 KiB.
set -- $(od -An -tu1 -j4 -N4 "$fw")
fitsz=$(( ($1 << 24) | ($2 << 16) | ($3 << 8) | $4 ))
if [ "$fitsz" -le 0 ] || [ "$fitsz" -gt "$(stat -c%s "$fw")" ]; then
echo "::error::${soc}_${variant}: firmware.bin does not start with a FIT image"
return 1
fi
fit_kib=$(( (fitsz + 65535) / 65536 * 64 ))
# The squashfs superblock carries its own length (bytes_used, a
# little-endian u64 at byte 40); measure that rather than the
# blob's remainder, which post-image.sh pads to an erase block.
rootsz=$(od -An -tu8 -j "$(( fit_kib * 1024 + 40 ))" -N8 "$fw" | tr -d ' ')
if [ "$(od -An -c -j "$(( fit_kib * 1024 ))" -N4 "$fw" | tr -d ' ')" != "hsqs" ] || [ "${rootsz:-0}" -le 0 ]; then
echo "::error::${soc}_${variant}: no squashfs at ${fit_kib} KiB in firmware.bin"
return 1
fi
root_kib=$(( (rootsz + 4095) / 4096 * 4 )) # mksquashfs pads the image to 4 KiB
ub_kib=$(( ( $(stat -c%s "output/$uboot") + 1023 ) / 1024 ))
# Every bound is the partition u-boot declares, not the space left
# on the chip: past the kernel slot the FIT is truncated by sf read,
# past the rootfs slot the squashfs is written over rootfs_data.
if [ "$ub_kib" -gt "$boot_kib" ]; then
echo "::error::${soc}_${variant}: u-boot ${uboot} is ${ub_kib} KiB, the boot partition is ${boot_kib} KiB (the env sits behind it)"
return 1
fi
if [ "$fit_kib" -gt "$kern_kib" ]; then
echo "::error::${soc}_${variant}: the FIT is ${fit_kib} KiB, the kernel partition is ${kern_kib} KiB"
return 1
fi
if [ "$root_kib" -gt "$(( fw_kib - kern_kib ))" ]; then
echo "::error::${soc}_${variant}: the rootfs is ${root_kib} KiB, the rootfs partition is $(( fw_kib - kern_kib )) KiB"
return 1
fi
if [ "$(( off + fw_kib ))" -gt "$nor" ]; then
echo "::error::${soc}_${variant}: firmware partition ends at $(( off + fw_kib )) KiB on a ${nor} KiB part"
return 1
fi
# NOR canvas, erased (0xff): u-boot at 0, the FIT at the firmware
# offset, the rootfs at the rootfs partition; the env and
# rootfs_data stay erased, so u-boot boots its defaults and /init
# formats the overlay. $nor is the part size in KiB -- a lite
# variant is flashed to an 8 MiB part.
dd if=/dev/zero bs=1K count="$nor" status=none | tr '\000' '\377' > "$release"
dd if="output/$uboot" of="$release" bs=1K seek=0 conv=notrunc status=none
dd if="$fw" of="$release" bs=1K count="$fit_kib" seek="$off" conv=notrunc status=none
dd if="$fw" of="$release" bs=1K skip="$fit_kib" count="$root_kib" seek="$(( off + kern_kib ))" conv=notrunc status=none
# Backstop for the checks above: if any is ever wrong, the canvas
# has grown past $nor and the file is no longer an image of the
# part. Refuse to upload it rather than ship an unflashable
# release.
got=$(stat -c%s "$release")
if [ "$got" -ne "$(( nor * 1024 ))" ]; then
echo "::error::${soc}_${variant}: assembled ${release} is ${got} bytes, expected $(( nor * 1024 ))"
return 1
fi
rm -rf output
echo -e "Created: $release (FIT ${fit_kib} KiB in ${kern_kib}, rootfs ${root_kib} KiB in $(( fw_kib - kern_kib )))\n"
}
for soc in $SIGMASTAR $ALLWINNER; do
create $soc $soc lite
create $soc $soc ultimate
done
for soc in $INGENIC; do
create $soc ${soc:0:3} lite
create $soc ${soc:0:3} ultimate
done
# HiSilicon Hi3516CV610/CV608 (cv6xx family). The kernel+rootfs blob
# is identical across DDR variants; only the per-binning u-boot (DDR
# init table) differs, so emit one full image per DDR topology.
# cv610 spans three topologies (DDR2-64M / DDR3-128M / DDR3-512M);
# cv608 is a single DDR2-64M part. firmware partition @ 0x50000.
# The 20s/00s u-boots are picked per DDR (their 20g/00g siblings
# carry the same DDR table, differing only in the socmodel env tag).
# lite targets 8 MiB parts, ultimate 16 MiB -- the same kernel and
# rootfs blob, on the flash the variant was built for.
# create_hisi returns 1 for an image it refuses to assemble. This
# step runs under Actions' default `bash -e`, so a bare call would
# end the step at the first refusal -- with the Sigmastar, Allwinner
# and Ingenic images already in target/ and the upload never
# reached, nothing would publish for anyone. Collect instead: every
# target gets its turn, what did assemble ships (Upload runs on
# failure too), and the step still goes red so the refusal is seen.
# lite only. The table above is what the shipped u-boot declares,
# and it is the same table on an 8 MiB part and a 16 MiB one --
# there is one boot binary per DDR binning, not one per part size.
# So the firmware partition is 7168 KiB either way, and the cv6xx
# ultimate blob is 9152 KiB: it does not fit the partition at all,
# let alone the 5120 KiB rootfs slot inside it. FLASH_SIZE="16"
# buys it nothing, because the rest of the chip is rootfs_data.
# #2448 added the bounds checks that report this; it is not a
# regression they introduced. Re-add ultimate here when a u-boot
# ships whose mtdparts matches a 16 MiB part -- tracked in #2460.
hisi_failed=
for variant in lite; do
case $variant in lite) nor=8192 ;; *) nor=16384 ;; esac
create_hisi boot-hi3516cv610-10b-nor.bin hi3516cv6xx "$variant" hi3516cv610-ddr2-64m 320 "$nor" || hisi_failed=1
create_hisi boot-hi3516cv610-20s-nor.bin hi3516cv6xx "$variant" hi3516cv610-ddr3-128m 320 "$nor" || hisi_failed=1
create_hisi boot-hi3516cv610-00s-nor.bin hi3516cv6xx "$variant" hi3516cv610-ddr3-512m 320 "$nor" || hisi_failed=1
create_hisi boot-hi3516cv608-nor.bin hi3516cv6xx "$variant" hi3516cv608 320 "$nor" || hisi_failed=1
done
# HiSilicon Hi3519DV500 (aarch64 V5) is not assembled here for the
# same reason, and it has no lite variant to fall back on. Its
# u-boot (dmeb 6-layer and dmebpro 4-layer flyby, both DDR4-2666
# 2 GB) declares 512k(boot),256k(env),6144k(kernel),8192k(rootfs),
# 14336k@0xC0000(firmware); the ultimate blob is 14464 KiB with an
# 8820 KiB rootfs, so it overruns both the rootfs slot and the
# firmware partition. Restore the loop with those numbers in
# hisi_layout once a u-boot exists that declares room for it (#2460).
if [ -n "$hisi_failed" ]; then
echo "::error::one or more HiSilicon NOR images were refused (see above); the rest were assembled and will still be uploaded"
exit 1
fi
# A refused HiSilicon image fails the Prepare step on purpose, and the
# images that did assemble must still ship -- so this runs on failure.
# Not always(): that runs on cancellation too, and create_hisi lays down
# the canvas, u-boot, the FIT and the rootfs as four separate writes, so a
# cancelled run leaves a bootloader on an otherwise blank part. Uploading
# that publishes an unflashable image indistinguishable from a finished
# one. The ${{ }} is load-bearing: a bare `!` starts a YAML tag.
# `image` is a fixed tag and Upload only adds or replaces the files this
# run produced, so an asset we stop assembling would sit on the release
# for good. These six are exactly that: published before #2448 taught
# create_hisi to measure each half against the partition it goes into,
# and mis-laid by the arithmetic it replaced -- a FIT past the end of the
# kernel slot, a rootfs written over rootfs_data. Withdrawing them by
# name, tolerating absence, keeps the release from offering an image this
# workflow no longer builds. Drop a name from the list when its loop
# comes back (#2460).
- name: Withdraw images this workflow no longer assembles
if: ${{ !cancelled() }}
env:
GH_TOKEN: ${{ github.token }}
run: |
for asset in openipc-hi3516cv610-ddr2-64m-nor-ultimate.bin \
openipc-hi3516cv610-ddr3-128m-nor-ultimate.bin \
openipc-hi3516cv610-ddr3-512m-nor-ultimate.bin \
openipc-hi3516cv608-nor-ultimate.bin \
openipc-hi3519dv500-dmeb-nor-ultimate.bin \
openipc-hi3519dv500-dmebpro-nor-ultimate.bin; do
if gh release delete-asset image "$asset" --yes \
--repo "$GITHUB_REPOSITORY" 2>/dev/null; then
echo "withdrawn: $asset"
else
echo "absent: $asset"
fi
done
- name: Upload
if: ${{ !cancelled() }}
uses: softprops/action-gh-release@v2
with:
tag_name: image
make_latest: false
files: target/*.bin