. "$BST_LIB" # BST-TIMEOUT: 2100 # the bounds below sum to 1,990 s, and a passing run took up to 190 s in Chrome before section 10 # # File stats, sha256 over 120 KB, over the 5.8 MB kernel (and its CRC32, in Tcl) # and over the ~3 MB runtime pair, seven runs bounded # at 30 s each by bst_probe (three openocd, four som-program-f7 dry runs), and # nineteen unbounded runs (--help, a bash -n, three dry runs, seven writes against # a stand-in openocd, seven interactive bashes): about 15 s on the host. # Section 9 adds three TFTP fetches bounded at 60 s each by bst_run, two waits # of about 20 s each on UDP 69, and the TFTP server's start and stop, unbounded: # up to there the suite took about 30 s on the host and 130 to 190 s in Chrome. # Sections 10 and 11 add som-mfg-f7 runs against a stand-in board, bounded at 30 # to 120 s each, and section 11 seven som-provenance-f7 runs bounded at 30 or 60 s. # # The F7 programming station's own half of the manufacturing image: # the operator command som-program-f7 (brintos-som-station), the firmware it # writes (brintos-som-firmware), and the openocd config seam both depend on. # # --------------------------------------------------------------------------- # WHAT THIS SUITE CAN AND CANNOT SEE, WHICH IS THE SAME SPLIT 99-openocd TAKES. # # Programming a board needs an adapter on the USB bus, and on this platform a # device arrives only through a WebUSB grant a human makes in the browser. So # the write itself is a manual step in the test plan, and what is decidable # without hardware is asserted here: # # * the command is installed, runs, and prints its usage; # * the firmware is installed and is the binary its PROVENANCE names -- the # sha256 is recomputed on target, so a swapped or truncated image is a red # rather than a surprise on the bench; # * the kernel the board's U-Boot fetches is installed, is the binary its own # provenance names, and the TFTP root reaches it under the name a board asks # for; # * the image resolution order is what the script claims, checked through # --dry-run, which touches no hardware; # * the adapter seam exists, the board config goes through it, and the # override an operator gives as --adapter reaches the interpreter; # * the board config still parses with the seam in place, and the speed # override the script uses reaches the interpreter; # * a write takes no backup and no read-back unless --backup or --readback # asks for one, --backup takes it before the write, and the read-back resets # the target first, so it runs at the programming clock; a write that fails # before openocd starts the erase says nothing was written, and one that # fails after it, or whose output could not be kept, says the flash is in an # unknown state -- checked against a stand-in openocd that records each # session the script opens; # * the console shell's banner gives the state of som-mfg-f7's MAC list. # # The station's TFTP server is checked the way an operator starts it: the # options and remap rule the image writes, /etc/init.d/tftpd-hpa start, a # listener on UDP 69, a relative name and a link into /tmp each fetched # byte-exact over loopback, a name outside /var/lib/tftpboot refused, and the # stop. lo is raised, and its address restored, where a fetch from 127.0.0.1 # could not work. # # On an image that is not the station (core-image-brintos-full carries the # command but not the firmware or documents), the firmware, dry-run-image, # dry-run-source, document, runtime and TFTP server cases skip, and the dry runs # use a placeholder --image. # # The last two are the valuable ones. openocd opens the adapter only at `init`, # so a config sources to completion with no device present and `-c shutdown` # ends the session before the init that would reach for USB. bst_ok som-station/suite-canary # first line: absorbs brintos/linux#96, which drops it require openocd som-station/openocd require som-program-f7 som-station/present W=${TMPDIR:-/tmp}/bst-som-station.$$ rm -rf "$W" if mkdir -p "$W" 2>/dev/null; then bst_ok som-station/workdir else bst_notok som-station/workdir "cannot create working directory $W" exit 0 fi S=/usr/share/openocd/scripts F=/usr/share/brintos-som/stm32f7 # A second station's adapter, used below to exercise the override. Any interface # config openocd ships would do; this is the model the VF6 station ran. ALT=interface/ftdi/olimex-arm-usb-ocd-h.cfg # The suite's own, empty, media directory: an operator's images in # /mnt/media/stm32f7 change no case below. Section 11 poses its own. SOM_MEDIA_DIR=$W/nomedia export SOM_MEDIA_DIR mkdir -p "$SOM_MEDIA_DIR" # --- 1. the operator command -------------------------------------------------- check som-station/install-path test -x /usr/bin/som-program-f7 check_eq som-station/which "/usr/bin/som-program-f7" "$(command -v som-program-f7)" check_out som-station/help "usage: som-program-f7" som-program-f7 --help # /bin/sh runs it, but an operator may type `bash som-program-f7`. if command -v bash >/dev/null 2>&1; then check som-station/bash-parses bash -n /usr/bin/som-program-f7 else bst_skip som-station/bash-parses "no bash on this image" fi # --- 2. the firmware is the binary its provenance names ----------------------- # Any one mark of the station image arms the firmware and document cases, so # losing one of them there is a red, not a skip. STATION= if grep -qs "brintOS SOM programming station" /etc/motd \ || [ -f /var/lib/dpkg/info/brintos-som-firmware.list ] \ || [ -f /var/lib/dpkg/info/brintos-som-station-guide.list ]; then STATION=1 fi FW_IDS="firmware-file provenance-file provenance-version provenance-prompt provenance-target firmware-sha256 firmware-size kernel-file kernel-provenance-version kernel-sha256 kernel-size kernel-crc32 tftp-link tftp-reaches-kernel" if [ -n "$STATION" ]; then IMGOPT="" else for id in $FW_IDS; do bst_skip som-station/$id "not the station image -- it carries no station firmware" done printf 'placeholder\n' > "$W/placeholder.bin" IMGOPT="--image $W/placeholder.bin" fi if [ -z "$IMGOPT" ]; then check som-station/firmware-file test -s "$F/u-boot.bin" check som-station/provenance-file test -s "$F/PROVENANCE" # Every file in this suite is read with sed, not cat: busybox cat copies # through sendfile(2), which on an uncached file off the virtio-fs root leaks # the -517 restart sentinel or never returns; sed reads with read(2). # BRINTOS-WORKAROUND: glibc#14 remove -- when sendfile(2) no longer leaks the restart sentinel, read these files with cat again. prov=$(sed -n p "$F/PROVENANCE" 2>/dev/null) # cortexm-2.1.2, the release target_som-stm32f7.exp programs -- not the LCD # starter kit's 2.4.0, which runs on the same SOM and is a different product. case "$prov" in *"U-Boot 2010.03-cortexm-2.1.2"*) bst_ok som-station/provenance-version ;; *) bst_notok som-station/provenance-version "PROVENANCE does not name the cortexm-2.1.2 U-Boot: '$(_bst_1line "$prov")'" ;; esac case "$prov" in *"STM32F7-SOM>"*) bst_ok som-station/provenance-prompt ;; *) bst_notok som-station/provenance-prompt "PROVENANCE does not name the U-Boot prompt the station checks for" ;; esac case "$prov" in *"0x08000000"*) bst_ok som-station/provenance-target ;; *) bst_notok som-station/provenance-target "PROVENANCE does not name the eNVM address" ;; esac # Recomputed on target rather than trusted: this is the only case that would # catch an image that changed after the recipe recorded it. want=$(printf '%s\n' "$prov" | sed -n 's/^sha256: *//p') have=$(sha256sum "$F/u-boot.bin" 2>/dev/null | cut -d' ' -f1) check_eq som-station/firmware-sha256 "$want" "$have" wantsz=$(printf '%s\n' "$prov" | sed -n 's/^size: *\([0-9]*\).*/\1/p') havesz=$(stat -c %s "$F/u-boot.bin" 2>/dev/null) check_eq som-station/firmware-size "$wantsz" "$havesz" # The kernel, which the board's U-Boot fetches from this machine over TFTP and # writes into NOR. Its record is a file of its own, read the same way. check som-station/kernel-file test -s "$F/rootfs.uImage" kprov=$(sed -n p "$F/rootfs.uImage.PROVENANCE" 2>/dev/null) case "$kprov" in *"Linux-4.2.0-cortexm-2.1.2"*) bst_ok som-station/kernel-provenance-version ;; *) bst_notok som-station/kernel-provenance-version "rootfs.uImage.PROVENANCE does not name the cortexm-2.1.2 kernel: '$(_bst_1line "$kprov")'" ;; esac want=$(printf '%s\n' "$kprov" | sed -n 's/^sha256: *//p') have=$(sha256sum "$F/rootfs.uImage" 2>/dev/null | cut -d' ' -f1) check_eq som-station/kernel-sha256 "$want" "$have" wantsz=$(printf '%s\n' "$kprov" | sed -n 's/^size: *\([0-9]*\).*/\1/p') havesz=$(stat -c %s "$F/rootfs.uImage" 2>/dev/null) check_eq som-station/kernel-size "$wantsz" "$havesz" # The CRC32 is what the operator compares at the U-Boot prompt; it has to be the # one this file has, not one copied from somewhere else. want=$(printf '%s\n' "$kprov" | sed -n 's/^crc32: *\([0-9a-f]*\).*/\1/p') printf '%s\n' 'set f [open [lindex $argv 0] rb]' 'puts [format %08x [zlib crc32 [read $f]]]' >"$W/crc32.tcl" have=$(tclsh "$W/crc32.tcl" "$F/rootfs.uImage" 2>/dev/null) check_eq som-station/kernel-crc32 "${want:-}" "${have:-}" # The TFTP root reaches both images as stm32f7/, through one link. The # link is absolute in the package and relative in the image, so it is compared # resolved. check_eq som-station/tftp-link "$F" "$(readlink -f /var/lib/tftpboot/stm32f7 2>/dev/null)" check som-station/tftp-reaches-kernel test -s /var/lib/tftpboot/stm32f7/rootfs.uImage fi # --- 3. the resolution order the script promises ------------------------------ # --dry-run resolves and reports without touching the adapter. With no operator # media mounted, the built-in copy is what a station would program. bst_probe som-station/dry-run "$W/dry" 30 som-program-f7 --dry-run $IMGOPT dry=$(sed -n p "$W/dry" "$W/dry.err" 2>/dev/null) if [ -z "$IMGOPT" ]; then case "$dry" in *"$F/u-boot.bin"*) bst_ok som-station/dry-run-image ;; *) bst_notok som-station/dry-run-image "--dry-run did not resolve $F/u-boot.bin: '$(_bst_1line "$dry")'" ;; esac case "$dry" in *"built-in"*) bst_ok som-station/dry-run-source ;; *) bst_notok som-station/dry-run-source "--dry-run did not name the built-in copy as the source" ;; esac else bst_skip som-station/dry-run-image "no built-in firmware on this image -- dry run driven with --image" bst_skip som-station/dry-run-source "no built-in firmware on this image -- dry run driven with --image" fi case "$dry" in *"PASS"*) bst_ok som-station/dry-run-verdict ;; *) bst_notok som-station/dry-run-verdict "--dry-run did not end in PASS: '$(_bst_1line "$dry")'" ;; esac # An operator's U-Boot in the media directory comes before the built-in one. mkdir -p "$W/media1" printf 'placeholder\n' > "$W/media1/u-boot.bin" out=$(SOM_MEDIA_DIR=$W/media1 som-program-f7 --dry-run 2>&1); rc=$? case "$rc:$out" in 0:*"image: $W/media1/u-boot.bin (media,"*"PASS"*) bst_ok som-station/dry-run-media ;; *) bst_notok som-station/dry-run-media "rc=$rc, want $W/media1/u-boot.bin named as media: '$(_bst_1line "$out")'" ;; esac # /var/tmp is a dangling link on this image, so the backup and readback # directory is found at start, and an explicit one that cannot be written is # refused before openocd runs. Only --backup and --readback need one. dryb=$(som-program-f7 --dry-run --backup $IMGOPT 2>&1) wd=$(printf '%s\n' "$dryb" | sed -n 's/^som-program-f7: workdir: *\([^ ]*\).*/\1/p' | head -n 1) if [ -n "$wd" ] && [ -d "$wd" ] && [ -w "$wd" ]; then bst_ok som-station/workdir-writable else bst_notok som-station/workdir-writable "--dry-run --backup named no writable workdir: '$wd'"; fi out=$(SOM_BACKUP_DIR=/nonexistent/som-backup som-program-f7 --dry-run --backup $IMGOPT 2>&1); rc=$? case "$rc:$out" in 1:*"is not a writable directory; nothing was written"*) bst_ok som-station/backup-dir-refused ;; *) bst_notok som-station/backup-dir-refused "rc=$rc, want a refusal naming the directory: '$(_bst_1line "$out")'" ;; esac # The board config raises the clock above its 150 kHz connect speed only on a # reset, so a read-back after a bare halt runs at 150 kHz. The stand-in openocd # logs each session's arguments and answers every dump with the image; with # SOM_STUB_FAIL it fails the write the way openocd's program does, before the # erase (init) or after it (erase), its lines on stderr as openocd's echo is. mkdir -p "$W/stub" printf '%s\n' '#!/bin/sh' 'echo "$*" >> "$SOM_STUB_LOG"' \ 'case "${SOM_STUB_FAIL:-}:$*" in' \ ' init:*"-c program "*) echo "** OpenOCD init failed **" >&2; exit 1 ;;' \ ' erase:*"-c program "*) echo "** Programming Started **" >&2; echo "** Programming Failed **" >&2; exit 1 ;;' \ 'esac' \ 'for a; do case "$a" in "dump_image "*) set -- $a; dd if="$SOM_STUB_IMAGE" of="$2" 2>/dev/null ;; esac; done' \ > "$W/stub/openocd" chmod +x "$W/stub/openocd" img=${IMGOPT#--image }; [ -n "$img" ] || img=$F/u-boot.bin : > "$W/stub.log" # A stand-in that cannot run would let PATH fall through to the real openocd and # a granted adapter, so the script starts only once the stand-in has answered. SOM_STUB_LOG="$W/stub.log" "$W/stub/openocd" stub-check 2>/dev/null if [ -s "$W/stub.log" ] && [ "$(PATH="$W/stub:$PATH" command -v openocd)" = "$W/stub/openocd" ]; then : > "$W/stub.log" out=$(PATH="$W/stub:$PATH" SOM_STUB_LOG="$W/stub.log" SOM_STUB_IMAGE="$img" \ SOM_BACKUP_DIR="$W" som-program-f7 --image "$img" --backup --readback 2>&1); rc=$? rbs=$(grep som-readback "$W/stub.log") case "$rc:$rbs" in 0:*"-c reset halt -c dump_image "*) bst_ok som-station/readback-after-reset ;; *) bst_notok som-station/readback-after-reset "rc=$rc, the read-back session: '$(_bst_1line "$rbs")', the run: '$(_bst_1line "$out")'" ;; esac # With no options, and with the flags older scripts pass, the write is the # only session: no backup and no read-back unless asked for. Whether the old # flags do nothing or turn an option off cannot be told apart here, since # both options are off already; what this pins is that they are accepted. for c in "default-writes-only:" "old-flags-accepted:--no-backup --no-readback"; do id=${c%%:*} : > "$W/stub.log" out=$(PATH="$W/stub:$PATH" SOM_STUB_LOG="$W/stub.log" SOM_STUB_IMAGE="$img" \ SOM_BACKUP_DIR="$W" som-program-f7 --image "$img" ${c#*:} 2>&1); rc=$? ses=$(sed -n p "$W/stub.log") n=$(grep -c . "$W/stub.log") case "$rc:$n:$ses" in *dump_image*) bst_notok som-station/$id "a session read the flash: '$(_bst_1line "$ses")'" ;; 0:1:*"-c program "*) bst_ok som-station/$id ;; *) bst_notok som-station/$id "rc=$rc, $n session(s): '$(_bst_1line "$ses")', the run: '$(_bst_1line "$out")'" ;; esac done # --backup is a session of its own, reading the eNVM into the workdir, and # it comes before the write. : > "$W/stub.log" out=$(PATH="$W/stub:$PATH" SOM_STUB_LOG="$W/stub.log" SOM_STUB_IMAGE="$img" \ SOM_BACKUP_DIR="$W" som-program-f7 --image "$img" --backup 2>&1); rc=$? s1=$(sed -n 1p "$W/stub.log"); s2=$(sed -n 2p "$W/stub.log"); n=$(grep -c . "$W/stub.log") case "$rc:$n:$s1|$s2" in 0:2:*"-c dump_image $W/envm-"*"|"*"-c program "*) bst_ok som-station/backup-before-write ;; *) bst_notok som-station/backup-before-write "rc=$rc, $n session(s), first: '$(_bst_1line "$s1")', second: '$(_bst_1line "$s2")'" ;; esac # Where openocd's program stops decides what the FAIL line may say. : > "$W/stub.log" out=$(PATH="$W/stub:$PATH" SOM_STUB_LOG="$W/stub.log" SOM_STUB_IMAGE="$img" SOM_STUB_FAIL=init \ som-program-f7 --image "$img" 2>&1); rc=$? case "$rc:$out" in *"unknown state"*) bst_notok som-station/write-unreached "a failure before the erase claimed an unknown state: '$(_bst_1line "$out")'" ;; 1:*"** OpenOCD init failed **"*"FAIL -- openocd stopped before the erase"*"nothing was written"*) bst_ok som-station/write-unreached ;; *) bst_notok som-station/write-unreached "rc=$rc, the run: '$(_bst_1line "$out")'" ;; esac : > "$W/stub.log" out=$(PATH="$W/stub:$PATH" SOM_STUB_LOG="$W/stub.log" SOM_STUB_IMAGE="$img" SOM_STUB_FAIL=erase \ SOM_BACKUP_DIR="$W" som-program-f7 --image "$img" --backup 2>&1); rc=$? case "$rc:$out" in *"nothing was written"*) bst_notok som-station/write-failed "a failure after the erase said nothing was written: '$(_bst_1line "$out")'" ;; 1:*"** Programming Failed **"*"the flash is in an unknown state (the backup of the previous contents is $W/envm-"*) bst_ok som-station/write-failed ;; *) bst_notok som-station/write-failed "rc=$rc, the run: '$(_bst_1line "$out")'" ;; esac # A copy of the output that could not be kept proves nothing about the erase: # a mktemp naming a directory makes tee fail. mkdir -p "$W/badtmp" "$W/logdir" printf '%s\n' '#!/bin/sh' "echo $W/logdir" > "$W/badtmp/mktemp" chmod +x "$W/badtmp/mktemp" : > "$W/stub.log" out=$(PATH="$W/badtmp:$W/stub:$PATH" SOM_STUB_LOG="$W/stub.log" SOM_STUB_IMAGE="$img" SOM_STUB_FAIL=init \ som-program-f7 --image "$img" 2>&1); rc=$? case "$rc:$out" in *"nothing was written"*) bst_notok som-station/write-unlogged "an unkept log was read as no erase: '$(_bst_1line "$out")'" ;; 1:*"** OpenOCD init failed **"*"the flash is in an unknown state"*) bst_ok som-station/write-unlogged ;; *) bst_notok som-station/write-unlogged "rc=$rc, the run: '$(_bst_1line "$out")'" ;; esac else for id in readback-after-reset default-writes-only old-flags-accepted backup-before-write write-unreached write-failed write-unlogged; do bst_notok som-station/$id "the stand-in openocd in $W/stub did not run, so som-program-f7 was not started" done fi # --- 4. the adapter seam ------------------------------------------------------ # What is asserted is the mechanism, not the model. The seam carries a default # and honours BRINTOS_JTAG_IFACE, so a station driving another Olimex is a # --adapter argument rather than an edit -- and must not be red for it. check som-station/seam-file test -f "$S/mfg-jtag.cfg" seam=$(sed -n p "$S/mfg-jtag.cfg" 2>/dev/null) case "$seam" in *"BRINTOS_JTAG_IFACE"*) bst_ok som-station/seam-variable ;; *) bst_notok som-station/seam-variable "mfg-jtag.cfg takes no adapter override: '$(_bst_1line "$seam")'" ;; esac iface=$(printf '%s\n' "$seam" | sed -n 's/^[[:space:]]*set BRINTOS_JTAG_IFACE[[:space:]]*//p' | tail -1) check som-station/seam-default-installed test -f "$S/$iface" check som-station/alt-iface-installed test -f "$S/$ALT" board=$(sed -n p "$S/board/emcraft-som-stm32f7.cfg" 2>/dev/null) case "$board" in *"find mfg-jtag.cfg"*) bst_ok som-station/board-uses-seam ;; *) bst_notok som-station/board-uses-seam "the board config does not source mfg-jtag.cfg" ;; esac case "$board" in *"reset-end"*) bst_ok som-station/board-reset-end ;; *) bst_notok som-station/board-reset-end "the board config installs no reset-end speed handler" ;; esac # --- 5. it parses through the seam, and the override reaches the interpreter --- # stm32f7x.cfg re-asserts 2 MHz on every reset, so the board config chooses the # programming speed in a reset-end handler; som-program-f7 --speed overrides it # by setting the variable before the config is sourced. Both halves are checked # here because a silently ignored override would only show up as a slow flash. bst_probe som-station/parse "$W/parse" 30 \ openocd -f "$S/board/emcraft-som-stm32f7.cfg" \ -c 'puts "names=[target names] speed=$BRINTOS_JTAG_SPEED"' -c shutdown parse=$(sed -n p "$W/parse" "$W/parse.err" 2>/dev/null) case "$parse" in *"invalid command name"*|*"no such file"*|*"Can't find"*) bst_notok som-station/parse-clean "the parse reported a missing command or file: '$(_bst_1line "$parse")'" ;; *) bst_ok som-station/parse-clean ;; esac case "$parse" in *STM32F756.cpu*) bst_ok som-station/parse-target ;; *) bst_notok som-station/parse-target "target names does not answer STM32F756.cpu: '$(_bst_1line "$parse")'" ;; esac case "$parse" in *"speed=2000"*) bst_ok som-station/parse-default-speed ;; *) bst_notok som-station/parse-default-speed "the board config's default programming speed is not 2000: '$(_bst_1line "$parse")'" ;; esac bst_probe som-station/parse-override "$W/over" 30 \ openocd -c "set BRINTOS_JTAG_SPEED 1234" -f "$S/board/emcraft-som-stm32f7.cfg" \ -c 'puts "speed=$BRINTOS_JTAG_SPEED"' -c shutdown over=$(sed -n p "$W/over" "$W/over.err" 2>/dev/null) case "$over" in *"speed=1234"*) bst_ok som-station/parse-override-honoured ;; *) bst_notok som-station/parse-override-honoured "the board config overrode the caller's speed: '$(_bst_1line "$over")'" ;; esac # The adapter override, through the same path. A clean parse is what says the # named interface config was found and sourced -- `find` reports a miss loudly, # and nothing else stands between a second station's adapter and a failed run. bst_probe som-station/parse-iface "$W/iface" 30 \ openocd -c "set BRINTOS_JTAG_IFACE $ALT" -f "$S/board/emcraft-som-stm32f7.cfg" \ -c 'puts "iface=$BRINTOS_JTAG_IFACE"' -c shutdown ifout=$(sed -n p "$W/iface" "$W/iface.err" 2>/dev/null) case "$ifout" in *"iface=$ALT"*) bst_ok som-station/parse-iface-honoured ;; *) bst_notok som-station/parse-iface-honoured "the seam overrode the caller's adapter: '$(_bst_1line "$ifout")'" ;; esac case "$ifout" in *"invalid command name"*|*"no such file"*|*"Can't find"*) bst_notok som-station/parse-iface-clean "the parse reported a missing command or file: '$(_bst_1line "$ifout")'" ;; *) bst_ok som-station/parse-iface-clean ;; esac # --- 6. --adapter, which is what an operator types ---------------------------- bst_probe som-station/adapter-dry-run "$W/adapt" 30 \ som-program-f7 --dry-run $IMGOPT --adapter olimex-arm-usb-ocd-h adapt=$(sed -n p "$W/adapt" "$W/adapt.err" 2>/dev/null) case "$adapt" in *"$ALT (override)"*) bst_ok som-station/adapter-dry-run-named ;; *) bst_notok som-station/adapter-dry-run-named "--adapter did not resolve to $ALT: '$(_bst_1line "$adapt")'" ;; esac # A bare name outside interface/ftdi/: the J-Link's config is interface/jlink.cfg. bst_probe som-station/adapter-jlink-dry-run "$W/jlink" 30 \ som-program-f7 --dry-run $IMGOPT --adapter jlink jlink=$(sed -n p "$W/jlink" "$W/jlink.err" 2>/dev/null) case "$jlink" in *"interface/jlink.cfg (override)"*) bst_ok som-station/adapter-jlink-named ;; *) bst_notok som-station/adapter-jlink-named "--adapter jlink did not resolve to interface/jlink.cfg: '$(_bst_1line "$jlink")'" ;; esac # And the ST-Link's: interface/stlink.cfg, one config for every ST-Link model. bst_probe som-station/adapter-stlink-dry-run "$W/stlink" 30 \ som-program-f7 --dry-run $IMGOPT --adapter stlink stlink=$(sed -n p "$W/stlink" "$W/stlink.err" 2>/dev/null) case "$stlink" in *"interface/stlink.cfg (override)"*) bst_ok som-station/adapter-stlink-named ;; *) bst_notok som-station/adapter-stlink-named "--adapter stlink did not resolve to interface/stlink.cfg: '$(_bst_1line "$stlink")'" ;; esac # An adapter that is not installed has to fail here rather than at the bench, # where it would surface as openocd failing to open a device. if som-program-f7 --dry-run $IMGOPT --adapter no-such-adapter >"$W/bad" 2>&1; then bst_notok som-station/adapter-unknown "an interface config that is not installed was accepted" elif grep -q "no such adapter config" "$W/bad"; then bst_ok som-station/adapter-unknown else bst_notok som-station/adapter-unknown "refused, but not for the adapter: '$(_bst_1line "$(sed -n p "$W/bad")")'" fi # --- 7. the procedure ships with the machine ---------------------------------- # An operator -- or the agent driving one -- reads this image, not the chat the # image arrived in, so the documents are part of the deliverable. DOC=/usr/share/brintos-som-station/flashing-an-stm32f7-som-from-brintos.md NETDOC=/usr/share/brintos-som-station/usb-ethernet-and-tftp.md DOC_IDS="readme-file doc-file netdoc-file motd-file readme-names-command readme-names-mfg motd-names-mfg readme-names-doc readme-names-netdoc motd-names-doc motd-shown banner-mac-none banner-mac-last banner-mac-used-up banner-mac-no-list banner-mac-malformed banner-mac-empty-lastmac" if [ -z "$STATION" ]; then for id in $DOC_IDS; do bst_skip som-station/$id "not the station image -- it carries no station documents" done else check som-station/readme-file test -s /README.md check som-station/doc-file test -s "$DOC" check som-station/netdoc-file test -s "$NETDOC" check som-station/motd-file test -s /etc/motd # Each one has to POINT somewhere: a README that does not name the command, or a # motd that does not name the document, is a file that passed a size test. check_out som-station/readme-names-command "som-program-f7" sed -n p /README.md check_out som-station/readme-names-mfg "som-mfg-f7" sed -n p /README.md check_out som-station/motd-names-mfg "som-mfg-f7" sed -n p /etc/motd check_out som-station/readme-names-doc "$DOC" sed -n p /README.md check_out som-station/readme-names-netdoc "$NETDOC" sed -n p /README.md check_out som-station/motd-names-doc "brintos-som-station" sed -n p /etc/motd # The console shell is interactive bash with HOME=/; /.bashrc prints the motd there. out=$(HOME=/ bash -ic true 2>/dev/null : an interactive console shell on $BD prints . banner() { out=$(HOME=/ SOM_STATION_DIR="$BD" bash -ic true 2>/dev/null "$BD/mac/maclist" banner banner-mac-none "MAC list C0:B1:3C:00:00:10..C0:B1:3C:00:00:1F: none handed out yet; 0 used, 16 left" printf 'C0:B1:3C:00:00:1a\n' > "$BD/mac/lastmac" banner banner-mac-last "last handed out C0:B1:3C:00:00:1A; 11 used, 5 left" printf 'C0:B1:3C:00:00:1F\n' > "$BD/mac/lastmac" banner banner-mac-used-up "used up, lastmac C0:B1:3C:00:00:1F is the end of the range" : > "$BD/mac/lastmac" banner banner-mac-empty-lastmac "lastmac exists but holds no MAC" rm -f "$BD/mac/lastmac" printf 'C0:B1:3C:00:00:1F C0:B1:3C:00:00:10\n' > "$BD/mac/maclist" banner banner-mac-malformed "maclist: FROM is above TO; som-mfg-f7 refuses it" fi # --- 8. the runtime the machine launches with --------------------------------- # In Developer mode the site runs /boot/hardwarejs.html and refuses to launch # without it, so a station image missing the pair is one nobody can boot. RT=/usr/share/brintos-hardwarejs-runtime/PROVENANCE RT_IDS="runtime-html runtime-js runtime-provenance runtime-html-sha256 runtime-js-sha256" if [ -z "$STATION" ]; then for id in $RT_IDS; do bst_skip som-station/$id "not the station image -- it carries no runtime of its own" done else check som-station/runtime-html test -s /boot/hardwarejs.html check som-station/runtime-js test -s /boot/hardwarejs.js check som-station/runtime-provenance test -s "$RT" for f in html js; do want=$(sed -n "s/^sha256: *\([0-9a-f]*\) hardwarejs.$f\$/\1/p" "$RT" 2>/dev/null) have=$(sha256sum /boot/hardwarejs.$f 2>/dev/null | cut -d' ' -f1) if [ -z "$want" ]; then bst_notok som-station/runtime-$f-sha256 "no sha256 for hardwarejs.$f in $RT" else check_eq som-station/runtime-$f-sha256 "$want" "$have" fi done fi # --- 9. the TFTP server, as an operator starts it ----------------------------- # The image writes /etc/default/tftpd-hpa and /etc/tftpd-hpa.remap itself # (core-image-brintos-manufacturing.bb); any other image carries the package's # own, which start a server this platform cannot run. T=/var/lib/tftpboot TFTPD_IDS="tftpd-options tftpd-remap tftpd-loopback tftpd-start tftpd-listening tftpd-relative-name tftpd-link-made tftpd-link-to-tmp tftpd-outside-refused tftpd-stop" if [ -z "$STATION" ]; then for id in $TFTPD_IDS; do bst_skip som-station/$id "not the station image -- it carries no TFTP server configuration of its own" done else # No -s, because the per-request chroot() fails here, and -u root, because the # default user is a privilege drop this kernel cannot make (the image recipe # carries both reasons). So the directory argument is all that confines it. check_eq som-station/tftpd-options "-l -u root -m /etc/tftpd-hpa.remap $T" \ "$(sed -n 's/^OPTIONS="\(.*\)"$/\1/p' /etc/default/tftpd-hpa 2>/dev/null)" # Without -s a name with no leading / has no directory; the rule gives it one. check_eq som-station/tftpd-remap "r ^([^/]) $T/\\1" \ "$(sed -e '/^[[:space:]]*#/d' -e '/^[[:space:]]*$/d' /etc/tftpd-hpa.remap 2>/dev/null)" # lo boots up with 127.0.0.1. A fetch from 127.0.0.1 fails with `Network is # unreachable' once lo has lost its address, which `udhcpc -i lo' does in the # deconfig step of /etc/udhcpc.d/50default, and never completes once lo is down. # Both are restored here for the fetches and put back at the end; raising lo # assigns 127.0.0.1, so a lo that was down without it ends down with it. A run # the runner kills mid-section leaves lo, the server and the fixture files as # they were then. lo_flags() { ip link show lo 2>/dev/null | sed -n 's/.*<\([^>]*\)>.*/,\1,/p'; } lo_addr() { case "$(ip -4 addr show dev lo 2>/dev/null)" in *"inet 127.0.0.1/"*) return 0 ;; esac; return 1; } LO_RAISED= LO_ADDED= LO_OK= case "$(lo_flags)" in *,UP,*) ;; *) ip link set lo up 2>/dev/null case "$(lo_flags)" in *,UP,*) LO_RAISED=1 ;; esac ;; esac if ! lo_addr; then ip addr add 127.0.0.1/8 dev lo 2>/dev/null lo_addr && LO_ADDED=1 fi case "$(lo_flags)" in *,UP,*) lo_addr && LO_OK=1 ;; esac if [ -n "$LO_OK" ]; then bst_ok som-station/tftpd-loopback else bst_notok som-station/tftpd-loopback "lo is not up with 127.0.0.1 (raised here: ${LO_RAISED:-no}, address added here: ${LO_ADDED:-no}): '$(_bst_1line "$(ip addr show lo 2>&1)")'"; fi # 0: a socket on port 69; 1: none; 2: /proc/net/udp could not be read, which # the wait treats as "not yet". Builtins only, so the sleep in the wait is a # real one (linux-in-the-browser#120). udp69() { _hdr= while read -r _sl _la _rest; do _hdr=1 case "$_la" in *:0045) return 0 ;; esac done < /proc/net/udp [ -n "$_hdr" ] || return 2 return 1 } udp69_wait() { # $1 = 0 for a listener, 1 for none; about 20 s _t0= _n=0 read -r _u _ < /proc/uptime && _t0=${_u%.*} while :; do udp69; [ $? -eq "$1" ] && return 0 _n=$((_n + 1)) _d=$_n if [ -n "$_t0" ] && read -r _u _ < /proc/uptime; then _d=$(( ${_u%.*} - _t0 )); fi [ "$_d" -ge 20 ] && return 1 sleep 1 done } # A server an operator already started is left alone: a second start is refused # by start-stop-daemon, and stopping it would take it from them. The fetches # below then exercise that server rather than this image's options. TFTPD_STARTED= if udp69; then bst_skip som-station/tftpd-start "a server was already listening on UDP 69 -- left running, not restarted" else check som-station/tftpd-start /etc/init.d/tftpd-hpa start TFTPD_STARTED=1 fi TFTPD_UP= if udp69_wait 0; then TFTPD_UP=1; bst_ok som-station/tftpd-listening else bst_notok som-station/tftpd-listening "nothing listens on UDP 69 after the start: '$(_bst_1line "$(sed -n p /proc/net/udp)")'" fi # Without both, a fetch only waits out its bound. NOFETCH= [ -n "$LO_OK" ] || NOFETCH="lo is not up with 127.0.0.1" [ -n "$TFTPD_UP" ] || NOFETCH="${NOFETCH:+$NOFETCH; }no server listens on UDP 69" # tftp_get : fetch from this machine as the station guide does, # into $W/got; sets $rc and $out. tftp_get() { rm -f "$W/got" bst_run 60 tftp -m binary 127.0.0.1 -c get "$1" "$W/got" } # tftp_same : ok iff comes back byte-exact. tftp_same() { if [ -n "$NOFETCH" ]; then bst_notok som-station/$1 "not fetched: $NOFETCH" return fi tftp_get "$2" if [ "$rc" -eq 0 ] && cmp -s "$3" "$W/got"; then bst_ok som-station/$1 elif bst_execfail "$out"; then bst_unrun som-station/$1 "$out" else bst_notok som-station/$1 "rc=$rc, $(stat -c %s "$W/got" 2>/dev/null || echo no) bytes back for $(stat -c %s "$3" 2>/dev/null): '$(_bst_1line "$out")'"; fi } # Random bytes, so netascii or a short read cannot pass; sizes off a 512-byte # block, so the last block is a partial one. The fixtures go into the TFTP root, # which an anonymous session on brintos.io serves read-only: there the cases # that need them skip, and any other failure to write them is red. REL=bst-som-station.$$.bin LNK=bst-som-station-link.$$.bin TMPF=/tmp/bst-som-station-tftp.$$.bin dd if=/dev/urandom of="$W/rel.bin" bs=1000 count=5 2>/dev/null dd if=/dev/urandom of="$TMPF" bs=1000 count=99 2>/dev/null # unstaged : report a case whose fixture could not be written. unstaged() { case "$2" in *"Read-only file system"*) bst_skip som-station/$1 "$T is read-only on this machine: $(_bst_1line "$2")" ;; *) bst_notok som-station/$1 "not staged: $(_bst_1line "$2")" ;; esac } if [ "$(stat -c %s "$W/rel.bin" 2>/dev/null)" != 5000 ]; then unstaged tftpd-relative-name "the 5000-byte reference was not made in $W" elif ! err=$(dd if="$W/rel.bin" of="$T/$REL" 2>&1) || ! cmp -s "$W/rel.bin" "$T/$REL"; then unstaged tftpd-relative-name "dd into $T: $err" else tftp_same tftpd-relative-name "$REL" "$W/rel.bin" fi # The guide's way to serve a large file: into /tmp, which is memory, and a link # to it from the TFTP root, which is the slow stored root. The link is judged by # reading it back; ln's status and message go in the detail. if [ "$(stat -c %s "$TMPF" 2>/dev/null)" != 99000 ]; then unstaged tftpd-link-made "the 99000-byte file was not made in /tmp" unstaged tftpd-link-to-tmp "the 99000-byte file was not made in /tmp" else ln -s "$TMPF" "$T/$LNK" 2>"$W/ln.err"; lnrc=$? lnerr=$(sed -n p "$W/ln.err") lnk=$(readlink "$T/$LNK" 2>/dev/null) if [ "$lnk" = "$TMPF" ]; then bst_ok som-station/tftpd-link-made tftp_same tftpd-link-to-tmp "$LNK" "$TMPF" else case "$lnerr" in *"Read-only file system"*) unstaged tftpd-link-made "$lnerr" ;; *) bst_notok som-station/tftpd-link-made "ln -s rc=$lnrc '$(_bst_1line "$lnerr")', the link reads '$lnk'" ;; esac unstaged tftpd-link-to-tmp "no link to fetch through: ${lnerr:-the link reads '$lnk'}" fi fi if [ -n "$NOFETCH" ]; then bst_notok som-station/tftpd-outside-refused "not asked: $NOFETCH" else tftp_get /etc/motd if bst_execfail "$out"; then bst_unrun som-station/tftpd-outside-refused "$out" elif [ -s "$W/got" ]; then bst_notok som-station/tftpd-outside-refused "/etc/motd was served from outside $T: $(stat -c %s "$W/got") bytes" else case "$out" in *"Error code 2: Forbidden directory"*) bst_ok som-station/tftpd-outside-refused ;; *) bst_notok som-station/tftpd-outside-refused "nothing came back, but not as a refusal: rc=$rc '$(_bst_1line "$out")'" ;; esac fi fi rm -f "$T/$REL" "$T/$LNK" "$TMPF" if [ -z "$TFTPD_STARTED" ]; then bst_skip som-station/tftpd-stop "the server was not started by this suite" else stop=$(/etc/init.d/tftpd-hpa stop 2>&1); src=$? if ! udp69_wait 1; then bst_notok som-station/tftpd-stop "UDP 69 still has a listener after the stop (rc=$src): '$(_bst_1line "$stop")'" start-stop-daemon --stop --quiet --name in.tftpd-hpa --signal KILL >/dev/null 2>&1 elif [ -z "$TFTPD_UP" ]; then bst_notok som-station/tftpd-stop "not judged: no server listened on UDP 69" elif [ "$src" -ne 0 ]; then bst_notok som-station/tftpd-stop "the stop exited $src, and the server had already gone: '$(_bst_1line "$stop")'" else bst_ok som-station/tftpd-stop fi fi [ -z "$LO_ADDED" ] || ip addr del 127.0.0.1/8 dev lo 2>/dev/null [ -z "$LO_RAISED" ] || ip link set lo down 2>/dev/null fi # --- 10. som-mfg-f7, the whole run, against a stand-in board -------------------- # The run itself needs a board, an adapter and a cable, so it is a manual step in # the test plan. What is decidable here is everything the script decides: the # MAC it takes from the list and the lists it refuses, the override, and the # commands a board receives in order, from a stand-in U-Boot and shell on a pty # (data/som-mfg-f7-board.exp) with a stand-in som-program-f7 on PATH. The # station directory is the suite's own, never the station's MAC list. check som-station/mfg-installed test -x /usr/bin/som-mfg-f7 check som-station/mfg-statedir test -d /var/lib/brintos-som/mac check_out som-station/mfg-help "usage: som-mfg-f7" som-mfg-f7 --no-override --help check_rc som-station/mfg-usage-refused 2 som-mfg-f7 --no-override --bogus MFG_IDS="mfg-mac-first mfg-mac-next mfg-mac-used-up mfg-mac-outside mfg-mac-mixed-oui mfg-mac-from-above-to mfg-mac-no-list mfg-override-runs mfg-override-bypassed mfg-run-pass mfg-run-commands mfg-run-lastmac mfg-run-program-args mfg-badcrc mfg-badcrc-lastmac mfg-keepmac-refused mfg-keepmac-lastmac mfg-tftp-retry mfg-mac-option-list-untouched mfg-badsave mfg-mac-empty-lastmac" if [ -z "$STATION" ]; then for id in $MFG_IDS; do bst_skip som-station/$id "not the station image -- som-mfg-f7 reads its expected versions from the station firmware's provenance" done else SD=$W/station mkdir -p "$SD/mac" # mfg : som-mfg-f7 with the suite's station directory, no # board and no network; sets $rc and $out. mfg() { _b=$1; shift; bst_run "$_b" env SOM_STATION_DIR="$SD" som-mfg-f7 --no-net --console /dev/null "$@"; } # mfg_case : judge the last mfg run. mfg_case() { case "$rc:$out" in "$2":*"$3"*) bst_ok som-station/$1 ;; *) if bst_execfail "$out"; then bst_unrun som-station/$1 "$out" else bst_notok som-station/$1 "rc=$rc want=$2, want:'$3' got:'$(_bst_1line "$out")'"; fi ;; esac } printf '# a range of sixteen\nC0:B1:3C:00:00:10 C0:B1:3C:00:00:1F\n' > "$SD/mac/maclist" rm -f "$SD/mac/lastmac" mfg 60 --dry-run; mfg_case mfg-mac-first 0 "mac: C0:B1:3C:00:00:10, the next from the list" printf 'C0:B1:3C:00:00:1a\n' > "$SD/mac/lastmac" mfg 60 --dry-run; mfg_case mfg-mac-next 0 "mac: C0:B1:3C:00:00:1B, the next from the list" printf 'C0:B1:3C:00:00:1F\n' > "$SD/mac/lastmac" mfg 30 --dry-run; mfg_case mfg-mac-used-up 1 "the MAC list is used up" printf 'C0:B1:3C:00:00:05\n' > "$SD/mac/lastmac" mfg 30 --dry-run; mfg_case mfg-mac-outside 1 "lies outside the range" rm -f "$SD/mac/lastmac" printf 'C0:B1:3C:00:00:10 C0:B1:3D:00:00:1F\n' > "$SD/mac/maclist" mfg 30 --dry-run; mfg_case mfg-mac-mixed-oui 1 "the first three octets of FROM and TO differ" printf 'C0:B1:3C:00:00:1F C0:B1:3C:00:00:10\n' > "$SD/mac/maclist" mfg 30 --dry-run; mfg_case mfg-mac-from-above-to 1 "FROM is above TO" rm -f "$SD/mac/maclist" mfg 30 --dry-run; mfg_case mfg-mac-no-list 1 "no MAC list at $SD/mac/maclist" # A fixed copy in the station directory runs in place of the installed script, # and says so; --no-override runs the installed one. printf '%s\n' 'puts "OVERRIDE-RAN $argv"' 'exit 0' > "$SD/som-mfg-f7.exp" mfg 30 --dry-run case "$rc:$out" in 0:*"running the override $SD/som-mfg-f7.exp"*"OVERRIDE-RAN"*) bst_ok som-station/mfg-override-runs ;; *) bst_notok som-station/mfg-override-runs "rc=$rc: '$(_bst_1line "$out")'" ;; esac mfg 30 --no-override --help case "$rc:$out" in *OVERRIDE-RAN*) bst_notok som-station/mfg-override-bypassed "--no-override ran the override: '$(_bst_1line "$out")'" ;; 0:*"usage: som-mfg-f7"*) bst_ok som-station/mfg-override-bypassed ;; *) bst_notok som-station/mfg-override-bypassed "rc=$rc: '$(_bst_1line "$out")'" ;; esac rm -f "$SD/som-mfg-f7.exp" # The whole run. The stand-in som-program-f7 records its arguments and prints # the line the real one ends with; it must be the one PATH finds, or the run # would reach a granted adapter. mkdir -p "$W/mfgbin" printf '%s\n' '#!/bin/sh' 'echo "$*" > "$SOM_STUB_ARGS"' \ 'echo "som-program-f7: image: /usr/share/brintos-som/stm32f7/u-boot.bin (built-in)"' \ 'echo "som-program-f7: PASS -- wrote /usr/share/brintos-som/stm32f7/u-boot.bin to 0x08000000"' \ > "$W/mfgbin/som-program-f7" chmod +x "$W/mfgbin/som-program-f7" KSIZE=$(sed -n 's/^size: *\([0-9]*\) bytes$/\1/p' "$F/rootfs.uImage.PROVENANCE" 2>/dev/null) # board : one run against the stand-in; sets $rc, # $out, and $W/recv (the lines the board received). board() { _bm=$1; _be=$2; shift 2 rm -f "$W/recv" "$W/stub.args" bst_run 120 env PATH="$W/mfgbin:$PATH" SOM_STUB_ARGS="$W/stub.args" SOM_STATION_DIR="$SD" \ expect "$BST_DATA/som-mfg-f7-board.exp" "$_bm" "$_be" "$KSIZE" "$W/recv" --log "$W/$_bm.log" "$@" } if [ "$(PATH="$W/mfgbin:$PATH" command -v som-program-f7)" != "$W/mfgbin/som-program-f7" ] || [ -z "$KSIZE" ]; then for id in mfg-run-pass mfg-run-commands mfg-run-lastmac mfg-run-program-args mfg-badcrc mfg-badcrc-lastmac mfg-keepmac-refused mfg-keepmac-lastmac mfg-tftp-retry mfg-mac-option-list-untouched mfg-badsave mfg-mac-empty-lastmac; do bst_notok som-station/$id "not run: the stand-in som-program-f7 is not first on PATH, or no kernel size in $F/rootfs.uImage.PROVENANCE ('$KSIZE')" done else printf 'C0:B1:3C:00:00:10 C0:B1:3C:00:00:1F\n' > "$SD/mac/maclist" printf 'C0:B1:3C:00:00:1A\n' > "$SD/mac/lastmac" board good C0:B1:3C:83:80:7D case "$out" in *"PASS -- C0:B1:3C:00:00:1B programmed"*"TOOL-RC=0"*) bst_ok som-station/mfg-run-pass ;; *) bst_notok som-station/mfg-run-pass "rc=$rc: '$(_bst_1line "$out")'" ;; esac # target_som-stm32f7's steps 2-7, in its order, with this station's addresses, # plus the TFTP timeout set in step 5 and cleared in step 7. want="printenv ethaddr|prot off all|erase 60000000 +20001|reset|setenv serverip 192.168.7.1|setenv ipaddr 192.168.7.2|setenv ethaddr C0:B1:3C:00:00:1B|setenv bootdelay 1|saveenv|setenv image stm32f7/rootfs.uImage|setenv tftptimeout 1000|run update|reset|ping -c 3 192.168.7.1|reboot -f|setenv serverip 192.168.0.1|setenv ipaddr 192.168.0.2|setenv gatewayip 192.168.0.1|setenv image rootfs.uImage|setenv bootdelay 3|setenv tftptimeout|saveenv" check_eq som-station/mfg-run-commands "$want" "$(sed -n p "$W/recv" 2>/dev/null | tr '\n' '|' | sed 's/|$//')" check_eq som-station/mfg-run-lastmac "C0:B1:3C:00:00:1B" "$(sed -n 1p "$SD/mac/lastmac" 2>/dev/null)" check_eq som-station/mfg-run-program-args "--no-backup --no-readback" "$(sed -n 1p "$W/stub.args" 2>/dev/null)" # A kernel copy bootm rejects ends the run at the boot, before the shipping # environment is written. The board was given the list's next MAC in step 4, # so lastmac has moved on: the next board must not get it again. board badcrc C0:B1:3C:83:80:7D last=$(sed -n '$p' "$W/recv" 2>/dev/null) case "$out:$last" in *"FAIL -- step 6"*"Bad Data CRC"*"TOOL-RC=1"*:reset) bst_ok som-station/mfg-badcrc ;; *) bst_notok som-station/mfg-badcrc "rc=$rc, the board's last command '$last': '$(_bst_1line "$out")'" ;; esac check_eq som-station/mfg-badcrc-lastmac "C0:B1:3C:00:00:1C" "$(sed -n 1p "$SD/mac/lastmac" 2>/dev/null)" # --keep-mac on a board showing U-Boot's built-in MAC is refused before the # environment is erased. board good C0:B1:3C:88:88:85 --keep-mac case "$out:$(sed -n p "$W/recv" 2>/dev/null)" in *"FAIL -- step 2"*"no MAC of its own"*"TOOL-RC=1"*:"printenv ethaddr") bst_ok som-station/mfg-keepmac-refused ;; *) bst_notok som-station/mfg-keepmac-refused "rc=$rc, the board received '$(_bst_1line "$(sed -n p "$W/recv" 2>/dev/null)")': '$(_bst_1line "$out")'" ;; esac check_eq som-station/mfg-keepmac-lastmac "C0:B1:3C:00:00:1C" "$(sed -n 1p "$SD/mac/lastmac" 2>/dev/null)" # U-Boot gives the first fetch up and would start it again by itself: the # run stops it with a Ctrl-C and runs update again, as lib.exp does. --mac # leaves the list alone. board tftpretry C0:B1:3C:83:80:7D --mac C0:B1:3C:00:00:99 n=$(grep -c '^run update$' "$W/recv" 2>/dev/null) case "$n:$out" in 2:*"fetch 1: U-Boot gave it up"*"PASS -- C0:B1:3C:00:00:99 programmed"*"TOOL-RC=0"*) bst_ok som-station/mfg-tftp-retry ;; *) bst_notok som-station/mfg-tftp-retry "update sent $n times, rc=$rc: '$(_bst_1line "$out")'" ;; esac check_eq som-station/mfg-mac-option-list-untouched "C0:B1:3C:00:00:1C" "$(sed -n 1p "$SD/mac/lastmac" 2>/dev/null)" # A saveenv U-Boot does not finish fails the run at that step: the prompt # after it is not the proof. board badsave C0:B1:3C:83:80:7D --mac C0:B1:3C:00:00:99 case "$out" in *"FAIL -- step 4"*"not Erased"*"TOOL-RC=1"*) bst_ok som-station/mfg-badsave ;; *) bst_notok som-station/mfg-badsave "rc=$rc: '$(_bst_1line "$out")'" ;; esac # A lastmac emptied (a write cut short) would hand FROM out again: refused. : > "$SD/mac/lastmac" mfg 30 --dry-run; mfg_case mfg-mac-empty-lastmac 1 "exists but holds no MAC" fi fi # --- 11. an operator's own images -------------------------------------------- # An operator puts a U-Boot and a kernel of their own in /mnt/media/stm32f7, or # names them with --image and --kernel, and som-provenance-f7 writes the records # som-mfg-f7 checks them against. The kernels here are stand-in uImages whose # sizes differ from the built-in one, so only their own records can pass them. # Images are made with dd and printf and read with sed, never copied with cp or # cat (the sendfile note in section 2). OP_IDS="prov-installed prov-uboot-version prov-uboot-prompt prov-uboot-sha256 prov-kernel-version prov-kernel-size prov-refuses-junk prov-refuses-prompt prov-prompt-given prov-refuses-builtin mfg-tftp-run-link mfg-kernel-norecord-refused mfg-kernel-arg-pass mfg-kernel-arg-commands mfg-kernel-arg-named mfg-kernel-media-pass mfg-kernel-media-named mfg-kernel-runlink-refused mfg-kernel-runlink-kept" if [ -z "$STATION" ]; then for id in $OP_IDS; do bst_skip som-station/$id "not the station image -- the records are checked against the station firmware's own" done elif [ "$(PATH="$W/mfgbin:$PATH" command -v som-program-f7)" != "$W/mfgbin/som-program-f7" ] || [ -z "$KSIZE" ]; then for id in $OP_IDS; do bst_notok som-station/$id "not run: the stand-in som-program-f7 is not first on PATH, or no kernel size in $F/rootfs.uImage.PROVENANCE ('$KSIZE')" done else OP=$W/op mkdir -p "$OP/named" "$OP/media" "$OP/other" KNAME=$(sed -n 's/^version: *//p' "$F/rootfs.uImage.PROVENANCE" 2>/dev/null) # mk_uimage : a uImage header and a payload. mk_uimage() { { printf '\047\005\031\126'; dd if=/dev/zero bs=28 count=1 2>/dev/null printf '%s' "$2"; dd if=/dev/zero bs=1 count=$((32 - ${#2})) 2>/dev/null dd if=/dev/zero bs="$3" count=1 2>/dev/null; } > "$1" } # prov_line : one line of a record. prov_line() { sed -n "s/^$2: *//p" "$1" 2>/dev/null; } # prov_case : judge the last som-provenance-f7 run. prov_case() { case "$rc:$out" in "$2":*"$3"*) bst_ok som-station/$1 ;; *) bst_notok som-station/$1 "rc=$rc want=$2, want:'$3' got:'$(_bst_1line "$out")'" ;; esac } check som-station/prov-installed test -x /usr/bin/som-provenance-f7 # The built-in U-Boot under another name: its record must say what the # built-in one does. dd if="$F/u-boot.bin" of="$OP/named/u-boot-2.1.2.bin" bs=65536 2>/dev/null bst_run 60 som-provenance-f7 --origin "the suite" "$OP/named/u-boot-2.1.2.bin" check_eq som-station/prov-uboot-version "$(prov_line "$F/PROVENANCE" version)" "$(prov_line "$OP/named/PROVENANCE" version)" check_eq som-station/prov-uboot-prompt "$(prov_line "$F/PROVENANCE" prompt)" "$(prov_line "$OP/named/PROVENANCE" prompt)" check_eq som-station/prov-uboot-sha256 "$(prov_line "$F/PROVENANCE" sha256)" "$(prov_line "$OP/named/PROVENANCE" sha256)" mk_uimage "$OP/named/rootfs-2.1.2.uImage" "$KNAME" 2000 bst_run 60 som-provenance-f7 --origin "the suite" "$OP/named/rootfs-2.1.2.uImage" check_eq som-station/prov-kernel-version "$KNAME" "$(prov_line "$OP/named/rootfs-2.1.2.uImage.PROVENANCE" version)" check_eq som-station/prov-kernel-size "2064 bytes" "$(prov_line "$OP/named/rootfs-2.1.2.uImage.PROVENANCE" size)" printf 'not an image\n' > "$OP/junk" bst_run 30 som-provenance-f7 "$OP/junk" prov_case prov-refuses-junk 1 'carries no "U-Boot 20..." banner' printf 'U-Boot 2099.01-other (Jan 01 2099 - 00:00:00)\000OTHER> \000' > "$OP/other/u-boot.bin" bst_run 30 som-provenance-f7 "$OP/other/u-boot.bin" prov_case prov-refuses-prompt 1 "does not carry the prompt" bst_run 30 som-provenance-f7 --prompt "OTHER>" "$OP/other/u-boot.bin" case "$rc:$(prov_line "$OP/other/PROVENANCE" prompt)" in 0:"OTHER>") bst_ok som-station/prov-prompt-given ;; *) bst_notok som-station/prov-prompt-given "rc=$rc, the record's prompt '$(prov_line "$OP/other/PROVENANCE" prompt)': '$(_bst_1line "$out")'" ;; esac bst_run 30 som-provenance-f7 "$F/u-boot.bin" prov_case prov-refuses-builtin 1 "a built-in image" # A kernel other than the built-in one is served through a link made for the # run in /tmp/som-mfg-f7, which the TFTP root reaches. The link is relative in # the image, and its target need not exist yet, which busybox's readlink -f # then resolves from the current directory: so it is resolved from its own. l=$(readlink /var/lib/tftpboot/som-mfg-f7 2>/dev/null) case $l in ""|/*) ;; *) l=$(cd /var/lib/tftpboot && cd "$(dirname "$l")" && pwd -P)/${l##*/} ;; esac check_eq som-station/mfg-tftp-run-link "/tmp/som-mfg-f7" "$l" # With no record of its own it is checked against the built-in record, and its # size refuses it before anything touches a board. mk_uimage "$OP/bare.uImage" "$KNAME" 2000 mfg 30 --dry-run --mac C0:B1:3C:00:00:99 --kernel "$OP/bare.uImage" mfg_case mfg-kernel-norecord-refused 1 "its record $F/rootfs.uImage.PROVENANCE says $KSIZE" # --kernel with its own record: the whole run against the stand-in board. ksave=$KSIZE KSIZE=2064 board good C0:B1:3C:83:80:7D --mac C0:B1:3C:00:00:99 --kernel "$OP/named/rootfs-2.1.2.uImage" case "$out" in *"PASS -- C0:B1:3C:00:00:99 programmed"*"TOOL-RC=0"*) bst_ok som-station/mfg-kernel-arg-pass ;; *) bst_notok som-station/mfg-kernel-arg-pass "rc=$rc: '$(_bst_1line "$out")'" ;; esac case "|$(sed -n p "$W/recv" 2>/dev/null | tr '\n' '|')" in *"|setenv image som-mfg-f7/rootfs.uImage|"*"|setenv image rootfs.uImage|"*) bst_ok som-station/mfg-kernel-arg-commands ;; *) bst_notok som-station/mfg-kernel-arg-commands "the board did not get som-mfg-f7/rootfs.uImage in step 5 and rootfs.uImage in step 7: '$(_bst_1line "$(sed -n p "$W/recv" 2>/dev/null)")'" ;; esac ksum=$(sha256sum "$OP/named/rootfs-2.1.2.uImage" 2>/dev/null | cut -d' ' -f1) case "$out" in *"kernel: $OP/named/rootfs-2.1.2.uImage (argument, 2064 bytes), sha256 $ksum"*"kernel record: $OP/named/rootfs-2.1.2.uImage.PROVENANCE"*) bst_ok som-station/mfg-kernel-arg-named ;; *) bst_notok som-station/mfg-kernel-arg-named "the run did not name the kernel, its sha256 and its record: '$(_bst_1line "$out")'" ;; esac # The operator's kernel in the media directory, with no --kernel. mk_uimage "$OP/media/rootfs.uImage" "$KNAME" 3000 bst_run 60 som-provenance-f7 "$OP/media/rootfs.uImage" SOM_MEDIA_DIR=$OP/media KSIZE=3064 board good C0:B1:3C:83:80:7D --mac C0:B1:3C:00:00:99 SOM_MEDIA_DIR=$W/nomedia KSIZE=$ksave case "$out" in *"PASS -- C0:B1:3C:00:00:99 programmed"*"TOOL-RC=0"*) bst_ok som-station/mfg-kernel-media-pass ;; *) bst_notok som-station/mfg-kernel-media-pass "rc=$rc: '$(_bst_1line "$out")'" ;; esac case "$out" in *"kernel: $OP/media/rootfs.uImage (media, 3064 bytes)"*) bst_ok som-station/mfg-kernel-media-named ;; *) bst_notok som-station/mfg-kernel-media-named "the run did not take the media kernel: '$(_bst_1line "$out")'" ;; esac # A file where the run makes its link is refused, and left as it was. rm -f /tmp/som-mfg-f7/rootfs.uImage mkdir -p /tmp/som-mfg-f7 mk_uimage /tmp/som-mfg-f7/rootfs.uImage "$KNAME" 2000 mfg 30 --dry-run --mac C0:B1:3C:00:00:99 --kernel "$OP/named/rootfs-2.1.2.uImage" mfg_case mfg-kernel-runlink-refused 1 "is where each run links the kernel it serves" if [ -f /tmp/som-mfg-f7/rootfs.uImage ] && [ ! -L /tmp/som-mfg-f7/rootfs.uImage ]; then bst_ok som-station/mfg-kernel-runlink-kept else bst_notok som-station/mfg-kernel-runlink-kept "/tmp/som-mfg-f7/rootfs.uImage is no longer the file: '$(ls -l /tmp/som-mfg-f7/rootfs.uImage 2>&1)'" fi rm -f /tmp/som-mfg-f7/rootfs.uImage fi rm -rf "$W"