add petalinux qspi transition workflow

This commit is contained in:
bowei
2026-09-15 03:17:59 +08:00
parent 170f4eb53f
commit 188d067d31
7 changed files with 594 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
---
name: petalinux-qspi
description: Build, reuse, deploy, recover, and hardware-validate a PetaLinux QSPI transition system from a user-supplied Vivado HDF/XSA and board information. Use when the user asks to create or install a QSPI rescue/transition Linux, update BOOT.BIN or image.ub in QSPI, prepare removable storage from that Linux, or determine whether an existing QSPI delivery is current. Supports a validated HJX9009 profile and intake routes for emv9.3, ipots, and other boards.
---
# PetaLinux QSPI transition system
Create a small, recoverable Linux in QSPI that can bring up networking, diagnose storage, and prepare the later production system. Reuse a delivery only when its inputs and artifacts are provably identical; deployment and hardware acceptance are always repeated for the target board.
## 1. Select the board first
If the user has not already named the board, ask exactly this choice before inspecting or changing a project:
1. HJX9009
2. emv9.3
3. ipots
4. others
- For **HJX9009**, read [references/hjx9009.md](references/hjx9009.md) before planning or editing.
- For **emv9.3** or **ipots**, the electrical/deployment profile is not implemented yet. Read [references/board-intake.md](references/board-intake.md), collect and verify its inputs, then create a board reference before any QSPI write. Never borrow HJX9009 constants.
- For **others**, require the hardware details in `board-intake.md`. A schematic, Vivado configuration report, or vendor board file is required; stop before build/deployment if boot straps, flash topology, or storage wiring remains unknown.
## 2. Establish inputs and authority
Read the applicable project `AGENTS.md`/`CLAUDE.md` and bmem project/machine memory before substantive work. Machine paths, IP addresses, serial ports, relay mappings, tool locations, usernames, and credentials come from live discovery, bmem pointers, or `~\skills_env.md`; never put them in this skill or copy values from an old note.
Require or locate:
- user-supplied Vivado hardware handoff (`.hdf` for PetaLinux 2018.1-era flows, `.xsa` for newer flows), plus bitstream only when the requested BOOT composition needs it;
- matching Vivado/SDK/PetaLinux versions; do not silently upgrade a hardware handoff;
- board schematic/configuration evidence and the exact target board identity;
- accessible serial console, power control, JTAG, and a proven recovery image/path before erasing QSPI;
- current host, VM, TFTP, and board network facts verified on the live interfaces.
Ask for the exact destructive target and show the selected board, image hashes, flash ranges, and recovery path immediately before the first QSPI erase/program operation if they were not already explicit in the user's deployment request.
## 3. Decide whether to rebuild
Read [references/cache-and-delivery.md](references/cache-and-delivery.md). Use `scripts/qspi_cache.py capture` to fingerprint all build-defining inputs and `scripts/qspi_cache.py check` to evaluate an existing delivery.
Skip the build only when all are true:
- board profile and its revision match;
- HDF/XSA and every included overlay/config/script hash match;
- PetaLinux, Vivado, SDK, boot composition, QSPI layout, and relevant parameters match;
- `BOOT.BIN`, `image.ub.qspi`, `flash_layout.json`, `summary.json`, and checksum manifest pass validation;
- prior build gates G0-G3 are PASS.
File existence, timestamps, an unchanged bitstream alone, or a prior successful boot are insufficient. A reusable build still goes through G4-G6 on the present hardware. If any fingerprint field is missing or differs, rebuild.
## 4. Execute the gated workflow
Read [references/workflow.md](references/workflow.md) and apply gates in order:
- **G0 preflight:** resolve inputs, live network, free space, serial/JTAG/power, QSPI recovery, and a no-write baseline.
- **G1 hardware import:** create a clean project and import the exact HDF/XSA. Archive generated configuration before overlays.
- **G2 configuration:** enable QSPI, initramfs, MTD, network, storage drivers, and required deployment/diagnostic tools. Audit generated device trees and bootloader configuration against the selected board reference.
- **G3 build/package:** build without hidden errors; package a deliberate BOOT composition; create hashes, input fingerprint, Flash layout, and manifest.
- **G4 boot-region deployment:** program/verify BOOT.BIN, then cold boot into the intended U-Boot. Preserve a recovery route.
- **G5 image deployment:** transfer, size-check, erase only the image region, write, full-readback, CRC/hash, and byte-compare `image.ub.qspi`. Save the exact QSPI-only `bootcmd` only after verification.
- **G6 system acceptance:** perform at least two full power-cycle boots; prove BootROM/U-Boot/FIT/Linux origin, MTD layout, networking, tools, and target storage behavior.
Never let a failed transfer fall through into erase/write commands. Use checked steps and stop on the first missing expected response or nonzero result. A successful build or `sf write` is not acceptance.
## 5. Storage initialization boundary
The transition system may contain partitioning and filesystem tools, but insertion does not authorize formatting.
1. Power off before inserting media when required by the board workflow.
2. Boot from QSPI and prove the QSPI path again.
3. Enumerate the target and read identity, CID, capacity, current mounts, partition table, and sample sectors without writing.
4. If no card is physically inserted, record `WAITING_FOR_MEDIA`, not a driver failure.
5. Format or deploy a production filesystem only after the target device is unambiguous and the user has requested that destructive step.
Never infer eMMC from a Linux name such as `/dev/mmcblk0`; it may be removable SD.
## 6. Evidence and handoff
- Put automatic logs/state under the project `Notes/auto/`, never in the `Notes/` root.
- Record exact artifact sizes, SHA-256, U-Boot CRC, flash offsets/lengths, bootcmd, live IPs, gate status, and evidence paths.
- Distinguish static inspection, VM build, flash write/readback, warm boot, cold boot, and real-media results.
- Do not claim completion while a required physical medium is absent; state the precise remaining gate.
- At substantive-work completion, import changed user-facing Notes and a session summary through bmem, add durable project decisions/issues at the correct layer, and run `bmem sync`. Report local/central/remote sync outcomes separately.
+4
View File
@@ -0,0 +1,4 @@
interface:
display_name: "PetaLinux QSPI"
short_description: "Build, reuse, deploy, and verify a QSPI rescue Linux"
default_prompt: "Use $petalinux-qspi to build or reuse and deploy a QSPI transition system for my selected board."
+41
View File
@@ -0,0 +1,41 @@
# Board intake for emv9.3, ipots, and other boards
Do not build or erase QSPI until the following facts are backed by a schematic, Vivado report, vendor board definition, or direct measurement.
## Required identity
- board name, PCB revision, serial/asset identity, SoC/FPGA exact part;
- Vivado hardware handoff and its tool version;
- expected PetaLinux/SDK version and whether migration is authorized;
- intended role of the transition system and later production storage.
## Boot and QSPI
- boot-mode strap/switch mapping, active levels, currently selected setting, and when it is sampled;
- flash manufacturer/part, single/dual, stacked/parallel/cascade topology, bus width, total bytes, page and erase sizes;
- PS/PL controller, chip-selects, MIO/pin mapping, voltage, reset and power dependencies;
- recovery method if the boot region becomes invalid;
- required BOOT.BIN contents: FSBL, U-Boot, optional bitstream/PMUFW/ATF, and why.
## Console and networking
- UART instance, pin routing, baud rate and physical COM adapter;
- Ethernet controller, PHY model/address/interface/reset/clock;
- live host/VM/board subnet, route, firewall and TFTP binding. Discover these each run; do not copy old addresses.
## Later storage
- storage type (removable SD, soldered eMMC, NAND, USB, other), controller instance, pin/MIO mapping and bus width;
- card-detect/write-protect/power-enable wiring and polarity;
- voltage switching capability and maximum safe frequency;
- expected Linux block-device evidence; never assume numbering before enumeration.
## Acceptance decisions
- exact QSPI layout with non-overlap and erase alignment;
- maximum image sizes and RAM load addresses;
- cold-boot count, network checks, storage checks, and whether blank-media formatting is part of this request;
- board-specific hazards and the stop/recovery threshold.
Create `references/<board-name>.md` only after these facts are verified. Mark every unverified item explicitly; placeholders are not deployment permission.
@@ -0,0 +1,53 @@
# Build reuse and delivery contract
## Input fingerprint
Fingerprint every item that can change generated boot behavior:
- selected board profile file;
- HDF/XSA and optional bitstream;
- project config, rootfs config, kernel config fragments, device-tree overlays, U-Boot overlays/BIF, recipes and custom files;
- PetaLinux, Vivado and SDK versions;
- BOOT.BIN composition;
- QSPI geometry/layout and boot parameters that are not machine-local.
Do not fingerprint credentials, transient logs, IP addresses discovered only for deployment, or build timestamps. Store the generated file as `build_inputs.json` in the delivery.
Example:
```powershell
python <skill>\scripts\qspi_cache.py capture `
--board-profile <skill>\references\hjx9009.md `
--input HDF=<path-to-system.hdf> `
--input SYSTEM_USER_DTSI=<path-to-system-user.dtsi> `
--input ROOTFS_CONFIG=<path-to-rootfs-config> `
--value PETALINUX_VERSION=2018.1 `
--value VIVADO_VERSION=2018.1 `
--value BOOT_COMPOSITION=FSBL+U-Boot `
--output <work>\build_inputs.json
```
After building, copy that exact `build_inputs.json` into the delivery and generate `SHA256SUMS` for the delivery artifacts. Do not include `SHA256SUMS` itself in that file.
## Reuse check
```powershell
python <skill>\scripts\qspi_cache.py check `
--current <work>\build_inputs.json `
--delivery <delivery-directory>
```
Exit 0 and `reusable: true` allow skipping G1-G3. Any other result requires rebuild or repair. Reuse never skips target-board programming/readback/cold-boot gates.
## Required delivery files
- `BOOT.BIN`
- `image.ub.qspi`
- `flash_layout.json`
- `summary.json`
- `build_inputs.json`
- `SHA256SUMS`
- human-readable manifest containing versions, composition, hashes, layout, bootcmd, gate states, known limitations and evidence paths
`flash_layout.json` must describe total/erase/page bytes, non-overlapping partitions, and the image file, offset, byte count, erase length and RAM load address. Image size must fit the named partition; offsets and erase lengths must align to the actual erase size.
+82
View File
@@ -0,0 +1,82 @@
# HJX9009 validated board profile
This profile comes from the HJX-ADRV9009-X2 schematic and a PetaLinux 2018.1 hardware run. Reconfirm the PCB revision and live probes for every target; schematic constants do not replace target identity.
## Boot and QSPI
- SoC: XC7Z100FF900.
- Boot device straps are `MIO[5:4:3]`; `100` is QSPI and is the schematic default. The schematic implements resistor straps, not a user boot DIP. Boot mode is sampled at power-on reset.
- `MIO2=0` selects cascaded dual-QSPI mode by default.
- U5 and U6 are S25FL128S-family serial flashes. Validated U-Boot probe: `s25fl128s_64k`, 32 MiB total, 128 KiB erase, 512-byte reported page. Treat the live `sf probe` result as mandatory before erase.
- A TF card does not alter the QSPI boot selection. The transition U-Boot bootcmd must contain only `sf probe`, QSPI `sf read`, documented board fixups, and `bootm`; no implicit `mmc` fallback.
Validated transition layout:
| Region | Offset | Size |
|---|---:|---:|
| boot | `0x00000000` | `0x00500000` |
| bootenv | `0x00500000` | `0x00020000` |
| kernel | `0x00520000` | `0x01800000` |
| spare | `0x01D20000` | `0x002E0000` |
The transition `BOOT.BIN` contains FSBL and U-Boot only. It deliberately excludes the PL bitstream and `image.ub`. The kernel region stores a FIT `image.ub.qspi` containing kernel, DTB and initramfs rootfs. Calculate the actual image read and erase lengths for each build; never copy an old length.
## J1 microSD/TF interface
HJX9009 has no soldered eMMC. J1 is removable microSD through U12 MAX13035E:
- SD0 CLK/CMD/DAT0..3: MIO40..45;
- card detect `SDIO_SDDET`: MIO13;
- card side is 3.3 V and PS side is 1.8 V;
- no UHS 1.8 V card-side switching and no write-protect signal;
- MIO46..53 are occupied by Ethernet/UART/MDIO functions, so SD1 must remain disabled.
Required SD device-tree intent:
```dts
&sdhci0 {
status = "okay";
u-boot,dm-pre-reloc;
bus-width = <4>;
max-frequency = <50000000>;
xlnx,has-cd = <1>;
xlnx,has-power = <0>;
xlnx,has-wp = <0>;
no-1-8-v;
disable-wp;
};
&sdhci1 {
status = "disabled";
};
```
Do not add `non-removable`. On the validated Linux 4.14 native SDHCI path, card presence is read directly from the SDHCI Present State register; `cd-inverted` does not correct native detection and must not be used as a substitute for the proper CD selector.
## SD0 card-detect selector
The validated HDF enabled SD0 on MIO40..45 but left `PCW_SD0_GRP_CD_ENABLE=0`, so generated FSBL selected EMIO 56 instead of schematic MIO13. Inspect the new HDF and generated `ps7_init.c` every time:
- if generation selects MIO13 correctly, do not apply a workaround;
- otherwise prefer correcting the Vivado PS7 configuration/HDF and rebuilding FSBL;
- the validated transition-only fallback, applied immediately before `bootm`, is:
```text
sd0_cd_fix=mw.l 0xF8000008 0x0000DF0D; mw.l 0xF8000830 0x000D0037; mw.l 0xF8000004 0x0000767B
```
Verify `0xF8000830=0x000D0037` in Linux. With no card inserted, the validated SDHCI Present State at `0xE0100024` was `0x01FA0000` (Card Present bit clear). Absence of `/dev/mmcblk0` is expected only when the operator confirms no card is inserted.
## Network and boot details
- GEM0 uses an Atheros 8031 PHY at MDIO address 0 with `rgmii-id`.
- Discover MAC and IP values from the target/project; do not reuse a prior board address.
- The prior validated build needed a documented PHY register fix before networking. Re-evaluate it against the supplied HDF/U-Boot rather than applying it blindly.
- The initramfs may need `CONFIG_SYS_BOOTM_LEN=0x0F000000`; confirm against the generated FIT and available DDR.
## Acceptance markers
Cold-boot evidence must show, in order: U-Boot banner, `SF: Detected s25fl128s_64k`, exact `device 0 offset 0x520000, size <current-size>`, FIT loading, and the transition Linux login. In Linux require rootfs type `rootfs`, the four expected MTD regions, `mmc0` on `e0100000.sdhci`, live network/TFTP, and storage tools.
If no TF card is present, finish at `WAITING_FOR_MEDIA` and power off. After insertion, prove QSPI boot again, then identify `/dev/mmcblk0` and do read-only media checks before any partition or format operation.
+64
View File
@@ -0,0 +1,64 @@
# Gated build and deployment workflow
## G0 - preflight
1. Identify the project and target board; read its board reference.
2. Hash the user-provided HDF/XSA before copying it.
3. Verify matching tool versions and enough VM/build-disk space.
4. Discover live Windows/VM/board addresses and the TFTP interface. Prove both ping and actual file transfer when available.
5. Capture serial boot output, current environment, QSPI probe, boot mode evidence, MTD layout and current storage without writing.
6. Verify JTAG/power control and a tested recovery method. Stop before erase if recovery is not credible.
7. Capture the build input fingerprint and run the reuse check.
## G1-G3 - build or verified reuse
For a rebuild, create a clean project, import the exact handoff, archive the generated baseline, then apply only reviewed board overlays. Configure an initramfs root filesystem and include at least:
- QSPI/MTD and board Ethernet/PHY support;
- selected SD/eMMC host support;
- `fdisk` or `parted`, `blkid`, FAT formatting, ext4 formatting/checking, `mount`, `umount`, archive, checksum and TFTP tools;
- enough `CONFIG_SYS_BOOTM_LEN` for the uncompressed initramfs.
Build and package with logs and exit codes. Treat warnings about a detected flash differing from the device-tree expectation as unresolved board-profile drift even if access currently works.
Before deployment validate:
- generated DT contains the intended controller nodes and no conflicting alternate controller;
- BOOT.BIN contains exactly the declared components;
- image size fits its partition and DDR load range;
- `qspi_cache.py check` or equivalent delivery validation passes;
- all artifacts have stable size and SHA-256.
## G4 - boot region
Back up the known-good boot image when possible. Prefer the vendor-supported flash programmer with verify. If it fails, use a board-proven JTAG-to-DDR recovery path only after checking its address, size and marker protocol. Never improvise a recovery write on an unverified board.
After programming, fully remove and restore power. Confirm the intended U-Boot banner and QSPI probe. Repeat the cold boot; do not count a warm reset.
## G5 - image region
Use separate checked operations:
1. download the exact image and assert transferred byte count;
2. compute RAM CRC/hash and match the manifest;
3. probe QSPI and assert geometry;
4. erase `ceil(image_bytes / erase_bytes) * erase_bytes`, bounded by the image partition;
5. write exactly `image_bytes`;
6. read back exactly `image_bytes` to a second RAM address;
7. compare CRC/hash and every byte;
8. only then save the QSPI-only bootcmd.
The bootcmd must make the origin unambiguous: probe QSPI, read the declared offset/length, apply only documented board fixes, and boot that RAM address. Do not add silent SD/network fallback unless explicitly required.
## G6 - hardware acceptance
For at least two complete power cycles, retain console evidence of:
- BootROM mode/source evidence, QSPI probe, exact QSPI read offset/length and FIT load;
- Linux release, initramfs mounted as root, expected MTD partitions;
- board Ethernet/PHY identity, live address and real TFTP transfer;
- required storage host registration and device-tree/register evidence;
- required deployment tools.
With removable media inserted, first perform a read-only gate: device enumeration, CID, capacity, partition table, current mounts, and bounded sample reads. Formatting and production deployment are separate destructive phases.
+267
View File
@@ -0,0 +1,267 @@
#!/usr/bin/env python3
"""Capture build-defining inputs and validate reuse of a QSPI delivery."""
from __future__ import print_function
import argparse
import hashlib
import json
import os
import shutil
import sys
import tempfile
REQUIRED_ARTIFACTS = (
"BOOT.BIN", "image.ub.qspi", "flash_layout.json", "summary.json",
"build_inputs.json", "MANIFEST.md", "SHA256SUMS",
)
def sha256_file(path):
digest = hashlib.sha256()
with open(path, "rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest().upper()
def parse_assignments(values, option):
parsed = {}
for item in values:
if "=" not in item:
raise ValueError("{} requires NAME=VALUE: {}".format(option, item))
name, value = item.split("=", 1)
name = name.strip()
if not name or name in parsed:
raise ValueError("invalid or duplicate {} name: {}".format(option, name))
parsed[name] = value
return parsed
def canonical_fingerprint(payload):
base = dict(payload)
base.pop("fingerprint_sha256", None)
encoded = json.dumps(base, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
return hashlib.sha256(encoded.encode("utf-8")).hexdigest().upper()
def capture(args):
profile = os.path.abspath(args.board_profile)
if not os.path.isfile(profile):
raise ValueError("board profile not found: {}".format(profile))
inputs = parse_assignments(args.input, "--input")
values = parse_assignments(args.value, "--value")
files = {}
for name in sorted(inputs):
path = os.path.abspath(inputs[name])
if not os.path.isfile(path):
raise ValueError("input file not found for {}: {}".format(name, path))
files[name] = {
"path_hint": os.path.basename(path),
"bytes": os.path.getsize(path),
"sha256": sha256_file(path),
}
payload = {
"schema_version": 1,
"board_profile": {
"name": os.path.splitext(os.path.basename(profile))[0],
"sha256": sha256_file(profile),
},
"files": files,
"values": dict(sorted(values.items())),
}
payload["fingerprint_sha256"] = canonical_fingerprint(payload)
output = os.path.abspath(args.output)
parent = os.path.dirname(output)
if parent and not os.path.isdir(parent):
os.makedirs(parent)
with open(output, "w") as stream:
json.dump(payload, stream, indent=2, sort_keys=True)
stream.write("\n")
print(json.dumps({"captured": output, "fingerprint": payload["fingerprint_sha256"]}))
return 0
def read_json(path):
with open(path, "r") as stream:
return json.load(stream)
def parse_checksums(path):
entries = {}
with open(path, "r") as stream:
for number, raw in enumerate(stream, 1):
line = raw.strip()
if not line:
continue
parts = line.split(None, 1)
if len(parts) != 2 or len(parts[0]) != 64:
raise ValueError("invalid SHA256SUMS line {}".format(number))
name = parts[1].lstrip("* ")
pieces = name.replace("\\", "/").split("/")
if name == "SHA256SUMS" or os.path.isabs(name) or ".." in pieces:
raise ValueError("unsafe checksum entry: {}".format(name))
entries[name] = parts[0].upper()
return entries
def validate_layout(delivery, errors):
try:
layout = read_json(os.path.join(delivery, "flash_layout.json"))
total = int(layout["flash"]["total_bytes"])
erase = int(layout["flash"]["erase_bytes"])
regions = []
for part in layout["partitions"]:
offset = int(str(part["offset"]), 0)
size = int(str(part["size"]), 0)
if offset % erase or size % erase:
errors.append("partition not erase-aligned: {}".format(part["name"]))
if size <= 0 or offset < 0 or offset + size > total:
errors.append("partition out of flash bounds: {}".format(part["name"]))
regions.append((offset, offset + size, part["name"]))
regions.sort()
for left, right in zip(regions, regions[1:]):
if left[1] > right[0]:
errors.append("overlapping partitions: {} and {}".format(left[2], right[2]))
image = layout["image"]
actual = os.path.getsize(os.path.join(delivery, image["file"]))
declared = int(image["bytes"])
if actual != declared:
errors.append("image byte count differs: actual {} declared {}".format(actual, declared))
image_offset = int(str(image["flash_offset"]), 0)
erase_length = int(str(image["erase_length"]), 0)
if image_offset % erase or erase_length % erase or erase_length < actual:
errors.append("image erase range is invalid or unaligned")
if not [r for r in regions if r[0] <= image_offset and image_offset + erase_length <= r[1]]:
errors.append("image erase range is not contained in a partition")
except Exception as exc:
errors.append("flash layout validation failed: {}".format(exc))
def check(args):
delivery = os.path.abspath(args.delivery)
errors = []
for name in REQUIRED_ARTIFACTS:
if not os.path.isfile(os.path.join(delivery, name)):
errors.append("missing artifact: {}".format(name))
if errors:
print(json.dumps({"reusable": False, "errors": errors}, indent=2))
return 2
try:
current = read_json(args.current)
stored = read_json(os.path.join(delivery, "build_inputs.json"))
if canonical_fingerprint(current) != current.get("fingerprint_sha256"):
errors.append("current input fingerprint is internally inconsistent")
if canonical_fingerprint(stored) != stored.get("fingerprint_sha256"):
errors.append("stored input fingerprint is internally inconsistent")
if current.get("fingerprint_sha256") != stored.get("fingerprint_sha256"):
errors.append("build-defining inputs differ")
except Exception as exc:
errors.append("input fingerprint validation failed: {}".format(exc))
try:
sums = parse_checksums(os.path.join(delivery, "SHA256SUMS"))
for name in REQUIRED_ARTIFACTS:
if name == "SHA256SUMS":
continue
expected = sums.get(name)
if not expected:
errors.append("artifact absent from SHA256SUMS: {}".format(name))
elif sha256_file(os.path.join(delivery, name)) != expected:
errors.append("checksum mismatch: {}".format(name))
except Exception as exc:
errors.append("checksum validation failed: {}".format(exc))
validate_layout(delivery, errors)
try:
summary = read_json(os.path.join(delivery, "summary.json"))
for gate in ("G0", "G1", "G2", "G3"):
if summary.get("gates", {}).get(gate, {}).get("status") != "PASS":
errors.append("prior build gate is not PASS: {}".format(gate))
except Exception as exc:
errors.append("summary validation failed: {}".format(exc))
print(json.dumps({"reusable": not errors, "delivery": delivery, "errors": errors}, indent=2, sort_keys=True))
return 0 if not errors else 2
def self_test(_args):
root = tempfile.mkdtemp(prefix="qspi-cache-test-")
try:
profile = os.path.join(root, "board.md")
source = os.path.join(root, "system.hdf")
current = os.path.join(root, "current.json")
delivery = os.path.join(root, "delivery")
os.makedirs(delivery)
with open(profile, "w") as stream:
stream.write("board\n")
with open(source, "wb") as stream:
stream.write(b"hdf")
capture(argparse.Namespace(board_profile=profile, input=["HDF=" + source], value=["PETALINUX_VERSION=2018.1"], output=current))
payload = read_json(current)
if payload["fingerprint_sha256"] != canonical_fingerprint(payload):
raise RuntimeError("fingerprint self-test failed")
shutil.copyfile(current, os.path.join(delivery, "build_inputs.json"))
with open(os.path.join(delivery, "BOOT.BIN"), "wb") as stream:
stream.write(b"boot")
with open(os.path.join(delivery, "image.ub.qspi"), "wb") as stream:
stream.write(b"image")
with open(os.path.join(delivery, "MANIFEST.md"), "w") as stream:
stream.write("# test\n")
with open(os.path.join(delivery, "flash_layout.json"), "w") as stream:
json.dump({
"flash": {"total_bytes": 262144, "erase_bytes": 65536},
"partitions": [
{"name": "boot", "offset": "0x0", "size": "0x10000"},
{"name": "kernel", "offset": "0x10000", "size": "0x30000"},
],
"image": {"file": "image.ub.qspi", "bytes": 5, "flash_offset": "0x10000", "erase_length": "0x10000"},
}, stream)
with open(os.path.join(delivery, "summary.json"), "w") as stream:
json.dump({"gates": dict((g, {"status": "PASS"}) for g in ("G0", "G1", "G2", "G3"))}, stream)
checksum_names = [name for name in REQUIRED_ARTIFACTS if name != "SHA256SUMS"]
with open(os.path.join(delivery, "SHA256SUMS"), "w") as stream:
for name in checksum_names:
stream.write("{} {}\n".format(sha256_file(os.path.join(delivery, name)), name))
if check(argparse.Namespace(current=current, delivery=delivery)) != 0:
raise RuntimeError("valid delivery self-test failed")
with open(os.path.join(delivery, "image.ub.qspi"), "ab") as stream:
stream.write(b"corrupt")
if check(argparse.Namespace(current=current, delivery=delivery)) == 0:
raise RuntimeError("corrupt delivery self-test failed")
print(json.dumps({"self_test": "PASS"}))
return 0
finally:
shutil.rmtree(root, ignore_errors=True)
def main():
parser = argparse.ArgumentParser(description=__doc__)
sub = parser.add_subparsers(dest="command")
capture_parser = sub.add_parser("capture", help="capture build-defining inputs")
capture_parser.add_argument("--board-profile", required=True)
capture_parser.add_argument("--input", action="append", default=[])
capture_parser.add_argument("--value", action="append", default=[])
capture_parser.add_argument("--output", required=True)
capture_parser.set_defaults(func=capture)
check_parser = sub.add_parser("check", help="check whether a delivery is reusable")
check_parser.add_argument("--current", required=True)
check_parser.add_argument("--delivery", required=True)
check_parser.set_defaults(func=check)
test_parser = sub.add_parser("self-test", help="run a non-destructive internal test")
test_parser.set_defaults(func=self_test)
args = parser.parse_args()
if not hasattr(args, "func"):
parser.print_help()
return 2
try:
return args.func(args)
except Exception as exc:
print(json.dumps({"error": str(exc)}), file=sys.stderr)
return 2
if __name__ == "__main__":
sys.exit(main())