diff --git a/petalinux-qspi/SKILL.md b/petalinux-qspi/SKILL.md new file mode 100644 index 0000000..9751388 --- /dev/null +++ b/petalinux-qspi/SKILL.md @@ -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. diff --git a/petalinux-qspi/agents/openai.yaml b/petalinux-qspi/agents/openai.yaml new file mode 100644 index 0000000..24e4946 --- /dev/null +++ b/petalinux-qspi/agents/openai.yaml @@ -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." diff --git a/petalinux-qspi/references/board-intake.md b/petalinux-qspi/references/board-intake.md new file mode 100644 index 0000000..94ed50a --- /dev/null +++ b/petalinux-qspi/references/board-intake.md @@ -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/.md` only after these facts are verified. Mark every unverified item explicitly; placeholders are not deployment permission. + diff --git a/petalinux-qspi/references/cache-and-delivery.md b/petalinux-qspi/references/cache-and-delivery.md new file mode 100644 index 0000000..2d15df2 --- /dev/null +++ b/petalinux-qspi/references/cache-and-delivery.md @@ -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 \scripts\qspi_cache.py capture ` + --board-profile \references\hjx9009.md ` + --input HDF= ` + --input SYSTEM_USER_DTSI= ` + --input ROOTFS_CONFIG= ` + --value PETALINUX_VERSION=2018.1 ` + --value VIVADO_VERSION=2018.1 ` + --value BOOT_COMPOSITION=FSBL+U-Boot ` + --output \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 \scripts\qspi_cache.py check ` + --current \build_inputs.json ` + --delivery +``` + +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. + diff --git a/petalinux-qspi/references/hjx9009.md b/petalinux-qspi/references/hjx9009.md new file mode 100644 index 0000000..b1b6b63 --- /dev/null +++ b/petalinux-qspi/references/hjx9009.md @@ -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 `, 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. + diff --git a/petalinux-qspi/references/workflow.md b/petalinux-qspi/references/workflow.md new file mode 100644 index 0000000..0e557a0 --- /dev/null +++ b/petalinux-qspi/references/workflow.md @@ -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. + diff --git a/petalinux-qspi/scripts/qspi_cache.py b/petalinux-qspi/scripts/qspi_cache.py new file mode 100644 index 0000000..f44e61b --- /dev/null +++ b/petalinux-qspi/scripts/qspi_cache.py @@ -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())