. "$BST_LIB" # BST-TIMEOUT: 3400 # # One openocd parse, bounded at 30 s by bst_probe, and dozens of unbounded dry # runs and stand-in sessions of som-program-nrf91, som-recover-nrf91 and # som-test-nrf91, each a few seconds. The whole suite takes 1134 s under the # HardwareJS on a build host; the bound is three times that. # # The nRF9151 SOM's pair, som-program-nrf91 and som-test-nrf91 # (brintos-som-station-nrf91), on whichever station image carries it: the nRF # station core-image-brintos-nrf-sdk, and the F7 station, which keeps both. # Section 2 covers the releases the nRF station carries (brintos-nrf-firmware). bst_ok som-nrf91/suite-canary # first line: absorbs brintos/linux#96, which drops it require openocd som-nrf91/openocd require som-program-nrf91 som-nrf91/present W=${TMPDIR:-/tmp}/bst-som-nrf91.$$ rm -rf "$W" if mkdir -p "$W" 2>/dev/null; then bst_ok som-nrf91/workdir else bst_notok som-nrf91/workdir "cannot create working directory $W" exit 0 fi S=/usr/share/openocd/scripts # --- 1. what is decidable without the board ----------------------------------- # Writing the SOM needs its J-Link and its console, both granted # in the browser, so the write is a manual step of the test plan; what is # decidable without them is asserted here: # # * both commands are installed and print their usage, and the board config # parses through the seam to nrf91.cpu, over SWD, at 1000 kHz, on a J-Link; # * an image is checked before anything is touched: a dry run reports an Intel # HEX's data and UICR bytes, takes no backup unless --backup asks (then into # /tmp by default), and a record that is not Intel HEX, data outside the # application flash, a .bin with no --addr and a backup directory that # cannot be written are each refused; # * against a stand-in openocd that keeps the Tcl of every session the script # opens: with --backup the flash is backed up before it is erased; erase, write and verify # are one session with no reset between the erase and the verify, and its # only `reset run' waits for the verify to pass (a reset of an erased # nRF91x1 locks its debug access); the HEX handed to openocd carries no UICR # record (it is write-once); nothing runs nrf91_recover; and a UICR that # differs from the chip's, or a locked chip, stops after the probe; # * som-test-nrf91 refuses the lines it must never send before it opens any # port, and a console that is not there is a rig failure (exit 3), not a # test result. # # Every run passes --no-console: on a bench with the SOM granted, a dry run # would otherwise find its FT230X and set the line up. NW=$W/nrf91 mkdir -p "$NW/stub" "$NW/ses" check som-nrf91/install-path test -x /usr/bin/som-program-nrf91 check_out som-nrf91/help "usage: som-program-nrf91" som-program-nrf91 --help check som-nrf91/test-install-path test -x /usr/bin/som-test-nrf91 check_out som-nrf91/test-help "usage: som-test-nrf91" som-test-nrf91 --help NB=$S/board/emcraft-som-nrf9151.cfg check som-nrf91/board-file test -f "$NB" nboard=$(sed -n p "$NB" 2>/dev/null) case "$nboard" in *"find mfg-jtag.cfg"*) bst_ok som-nrf91/board-uses-seam ;; *) bst_notok som-nrf91/board-uses-seam "the nRF9151 board config does not source mfg-jtag.cfg" ;; esac bst_probe som-nrf91/parse "$NW/parse" 30 \ openocd -f "$NB" \ -c 'puts "names=[target names] speed=$BRINTOS_JTAG_SPEED iface=$BRINTOS_JTAG_IFACE transport=[transport select]"' \ -c shutdown nparse=$(sed -n p "$NW/parse" "$NW/parse.err" 2>/dev/null) case "$nparse" in *"invalid command name"*|*"no such file"*|*"Can't find"*) bst_notok som-nrf91/parse-clean "the parse reported a missing command or file: '$(_bst_1line "$nparse")'" ;; *) bst_ok som-nrf91/parse-clean ;; esac for want in "nrf91.cpu:target" "speed=1000:speed" "iface=interface/jlink.cfg:adapter" "transport=swd:transport"; do case "$nparse" in *"${want%%:*}"*) bst_ok som-nrf91/parse-${want#*:} ;; *) bst_notok som-nrf91/parse-${want#*:} "the parse does not answer '${want%%:*}': '$(_bst_1line "$nparse")'" ;; esac done # Fixtures, made here so that each case poses exactly one property. good.hex is 16 bytes at 0x0 plus a UICR record taken from the 1.2-114 # hello_mcuboot release (0xFF8130, two bytes of it left at 0xFF); far.hex puts # its data at 1 MiB, past the application flash; bad.hex is not Intel HEX. printf '%s\n' ':10000000000102030405060708090A0B0C0D0E0F78' ':0200000400FFFB' \ ':10813000008000000000020001000000FFFFFA5074' ':00000001FF' > "$NW/good.hex" printf '%s\n' ':020000040010EA' ':10000000000102030405060708090A0B0C0D0E0F78' ':00000001FF' > "$NW/far.hex" printf '%s\n' 'this is not a HEX record' > "$NW/bad.hex" printf 'sixteen bytes!!\n' > "$NW/raw.bin" out=$(som-program-nrf91 --dry-run --no-console --image "$NW/good.hex" 2>&1); rc=$? case "$rc:$out" in 0:*"content: 16 bytes up to 0x10; UICR records: 16 bytes (compared, never written)"*"PASS -- resolved"*) bst_ok som-nrf91/dry-run-hex ;; *) bst_notok som-nrf91/dry-run-hex "rc=$rc: '$(_bst_1line "$out")'" ;; esac case "$out" in *"backup: off (--backup to take one)"*) bst_ok som-nrf91/backup-default-off ;; *) bst_notok som-nrf91/backup-default-off "a dry run did not say the backup is off: '$(_bst_1line "$out")'" ;; esac out=$(som-program-nrf91 --dry-run --no-console --backup --image "$NW/good.hex" 2>&1); rc=$? case "$out" in *"backup to: /tmp (RAM"*) bst_ok som-nrf91/backup-default-tmp ;; *) bst_notok som-nrf91/backup-default-tmp "a --backup dry run did not name /tmp as the backup directory: '$(_bst_1line "$out")'" ;; esac # refused : ok iff som-program-nrf91 exits 1 with a FAIL naming . refused() { _id=$1; _n=$2; shift 2 _o=$(som-program-nrf91 --dry-run --no-console "$@" 2>&1); _r=$? case "$_r:$_o" in 1:*"FAIL -- "*"$_n"*) bst_ok som-nrf91/$_id ;; *) bst_notok som-nrf91/$_id "rc=$_r, want a FAIL naming '$_n': '$(_bst_1line "$_o")'" ;; esac } refused nrf91-refuses-not-hex "is not a HEX record" --image "$NW/bad.hex" refused nrf91-refuses-outside "outside the application flash" --image "$NW/far.hex" refused nrf91-refuses-bin-no-addr "a .bin needs --addr" --image "$NW/raw.bin" refused nrf91-refuses-addr-typo "bad --addr 'ox10000'" --image "$NW/raw.bin" --addr ox10000 refused nrf91-refuses-addr-word "bad --addr 'size'" --image "$NW/raw.bin" --addr size refused nrf91-refuses-addr-octal "bad --addr '010'" --image "$NW/raw.bin" --addr 010 refused nrf91-refuses-speed "fails above 2000 kHz" --image "$NW/good.hex" --speed 4000 out=$(som-program-nrf91 --dry-run --no-console --image "$NW/raw.bin" --addr 0x2000 2>&1); rc=$? case "$rc:$out" in 0:*"content: 16 bytes at 0x2000"*"PASS -- resolved"*) bst_ok som-nrf91/dry-run-bin-addr ;; *) bst_notok som-nrf91/dry-run-bin-addr "rc=$rc: '$(_bst_1line "$out")'" ;; esac out=$(SOM_BACKUP_DIR=/nonexistent/som-backup som-program-nrf91 --dry-run --no-console --backup --image "$NW/good.hex" 2>&1); rc=$? case "$rc:$out" in 1:*"/nonexistent/som-backup is not writable"*"nothing was written"*) bst_ok som-nrf91/backup-dir-refused ;; *) bst_notok som-nrf91/backup-dir-refused "rc=$rc, want a refusal naming the directory: '$(_bst_1line "$out")'" ;; esac # The stand-in keeps the Tcl of each session in $NRF_STUB_DIR/ses/.tcl and # answers as the board did on 2026-10-09: a 0x9151 with APPROTECT off, a gate that # holds, a 1 MiB backup, and an erase, write and verify that pass. NRF_STUB_UICR_BAD # and NRF_STUB_LOCKED make its probe report a foreign UICR or a locked chip, # NRF_STUB_IMAGE_BAD an image openocd will not load, NRF_STUB_ERASED a flash left # erased by an unfinished write, and NRF_STUB_WRITE_FAIL a write that fails. printf '%s\n' '#!/bin/sh' \ 'echo x >> "$NRF_STUB_DIR/count"; n=$(grep -c "" "$NRF_STUB_DIR/count")' \ 'f=; for a; do case "$a" in *.tcl) f=$a ;; esac; done' \ '[ -n "$f" ] && sed -n p "$f" > "$NRF_STUB_DIR/ses/$n.tcl"' \ 'if [ -n "$f" ] && grep -q "uicr-bad" "$f"; then' \ ' echo "Info : VTarget = 2.925 V"; echo "SOM part=0x9151"; echo "SOM approtect=0x50fa50fa"' \ ' [ -n "$NRF_STUB_LOCKED" ] && echo "Error: AP lock engaged"' \ ' if [ -n "$NRF_STUB_IMAGE_BAD" ]; then echo "Error: checksum mismatch in IHEX record"; echo "SOM image=bad "; else echo "SOM image=ok"; fi' \ ' if [ -n "$NRF_STUB_ERASED" ]; then echo "SOM w0=0xffffffff"; echo "SOM w4=0xffffffff"; else echo "SOM w0=0x20010000"; echo "SOM w4=0x00000201"; fi' \ ' echo "SOM uicr-bad=${NRF_STUB_UICR_BAD:-0}"; echo "SOM uicr-same=14"; echo "SOM uicr-free=2"' \ 'elif [ -n "$f" ] && grep -q "gate=" "$f"; then echo "SOM gate=pass"' \ 'elif [ -n "$f" ] && grep -q "dump_image" "$f"; then' \ ' sed -n "s/.*dump_image \([^ ]*\) [^ ]* \([0-9]*\)}.*/\1 \2/p" "$f" | while read -r b z; do dd if=/dev/zero of="$b" bs="$z" count=1 2>/dev/null; done' \ ' echo "SOM backup=pass"' \ 'elif [ -n "$f" ] && grep -q "flash write_image" "$f"; then' \ ' i=$(sed -n "s/.*flash write_image \(erase \)\{0,1\}\([^ }]*\).*/\2/p" "$f" | sed -n 1p)' \ ' sed -n p "$i" > "$NRF_STUB_DIR/written.hex"' \ ' if [ -n "$NRF_STUB_WRITE_FAIL" ]; then echo "SOM erase=0 "; echo "SOM write=1 Error: flash write failed"; echo "SOM result=fail"' \ ' else echo "SOM erase=0 "; echo "SOM write=0 "; echo "SOM verify=0 "; echo "SOM result=pass"; fi' \ 'fi' > "$NW/stub/openocd" chmod +x "$NW/stub/openocd" # nrf_stub_run [env...] : run a write against the stand-in; sets $out, $rc, $nses. # Its TMPDIR is an empty one of its own: a retry session a real failed write # left in /tmp would otherwise stop every run here (a caller may name another). nrf_stub_run() { rm -rf "$NW/ses" "$NW/count" "$NW/written.hex" "$NW/tmp0"; mkdir -p "$NW/ses" "$NW/tmp0" rm -f "$NW"/nrf91-flash-* out=$(env PATH="$NW/stub:$PATH" NRF_STUB_DIR="$NW" SOM_BACKUP_DIR="$NW" TMPDIR="$NW/tmp0" "$@" \ som-program-nrf91 --no-console ${NRF_ARGS:-} --image "$NW/good.hex" 2>&1); rc=$? nses=$(grep -c "" "$NW/count" 2>/dev/null); nses=${nses:-0} } NRF_STUB_IDS="nrf91-stub-write nrf91-erase-touched-default nrf91-backup-pages nrf91-erase-all nrf91-backup-before-erase nrf91-one-session-no-reset nrf91-uicr-not-written nrf91-no-recover nrf91-foreign-uicr-stops nrf91-locked-stops nrf91-probe-tests-image nrf91-bad-image-stops nrf91-erased-stops nrf91-retry-no-reset" # As for som-program-f7 above: the script starts only once the stand-in has # answered, so a broken stand-in cannot hand the run to the real openocd. : > "$NW/count" if NRF_STUB_DIR="$NW" "$NW/stub/openocd" stub-check >/dev/null 2>&1 && [ -s "$NW/count" ] \ && [ "$(PATH="$NW/stub:$PATH" command -v openocd)" = "$NW/stub/openocd" ]; then # Probe, gate, write: no backup unless --backup asks for one. nrf_stub_run case "$rc:$nses:$out" in 0:3:*"PASS -- wrote $NW/good.hex"*) bst_ok som-nrf91/stub-write ;; *) bst_notok som-nrf91/stub-write "rc=$rc, $nses session(s), want 3 and a PASS: '$(_bst_1line "$out")'" ;; esac # By default only the pages the image covers are erased, in the write itself. if grep -q "flash write_image erase" "$NW"/ses/*.tcl 2>/dev/null && ! grep -q "erase_sector" "$NW"/ses/*.tcl 2>/dev/null; then bst_ok som-nrf91/erase-touched-default else bst_notok som-nrf91/erase-touched-default "sessions: '$(_bst_1line "$(cat "$NW"/ses/*.tcl 2>/dev/null)")'"; fi # With --backup, only those pages are backed up first: good.hex is one page. NRF_ARGS="--backup" nrf_stub_run man=$(ls "$NW"/nrf91-flash-*.pages 2>/dev/null) if grep -q "dump_image $NW/nrf91-flash-[0-9TZ]*-0x00000000.bin 0 4096}" "$NW"/ses/*.tcl 2>/dev/null \ && [ -n "$man" ] && [ "$(grep -c '' "$man")" = 1 ] && grep -q '0x00000000 4096 [0-9a-f]\{64\}$' "$man"; then bst_ok som-nrf91/backup-pages else bst_notok som-nrf91/backup-pages "manifest '$man': '$(_bst_1line "$(cat "$man" 2>/dev/null)")'; out: '$(_bst_1line "$out")'"; fi NRF_ARGS="--erase all --backup" nrf_stub_run if grep -q "flash erase_sector 0 0 last" "$NW"/ses/*.tcl 2>/dev/null \ && grep -q "dump_image $NW/nrf91-flash-[0-9TZ]*.bin 0 1048576}" "$NW"/ses/*.tcl 2>/dev/null; then bst_ok som-nrf91/erase-all else bst_notok som-nrf91/erase-all "rc=$rc: '$(_bst_1line "$out")'"; fi NRF_ARGS="--backup" nrf_stub_run wses=$(grep -l "flash write_image" "$NW"/ses/*.tcl 2>/dev/null) bses=$(grep -l "dump_image" "$NW"/ses/*.tcl 2>/dev/null) wn=$(basename "${wses:-none}" .tcl); bn=$(basename "${bses:-none}" .tcl) case "$wn:$bn" in *[!0-9:]*) bst_notok som-nrf91/backup-before-erase "backup session '$bn', write session '$wn'" ;; *) if [ "$bn" -lt "$wn" ]; then bst_ok som-nrf91/backup-before-erase else bst_notok som-nrf91/backup-before-erase "the backup (session $bn) came after the erase (session $wn)"; fi ;; esac # Exactly two resets: `reset halt` on the way in, and the pass clause's `reset run`. if [ -n "$wses" ] && awk ' { c = gsub(/reset/, "&") } c { n += c; if (n == 1) { r1 = NR; first = $0 } else { r2 = NR; second = $0 } } /flash erase_sector|flash write_image erase/ { e = NR } /flash write_image/ { w = NR } /verify_image/ { v = NR } END { exit !(n == 2 && first == "reset halt" && r1 < e && e <= w && w < v && v < r2 && second == "if {$rc == 0} { reset run; echo \"SOM result=pass\" } else { echo \"SOM result=fail\" }") }' "$wses"; then bst_ok som-nrf91/one-session-no-reset else bst_notok som-nrf91/one-session-no-reset "the write session: '$(_bst_1line "$(sed -n p "${wses:-/dev/null}")")'" fi # Decoded rather than grepped: an extended-address record for 0x00FF may stay # (it writes nothing); a data record at 0xFF0000 or above may not. if [ -s "$NW/written.hex" ] && grep -q '^:10000000000102' "$NW/written.hex" && awk ' function h(x) { return index("0123456789ABCDEF", toupper(x)) - 1 } function b(x) { return h(substr(x, 1, 1)) * 16 + h(substr(x, 2, 1)) } { t = substr($0, 8, 2) } t == "04" { base = (b(substr($0, 10, 2)) * 256 + b(substr($0, 12, 2))) * 65536 } t == "00" && base + b(substr($0, 4, 2)) * 256 + b(substr($0, 6, 2)) >= 16711680 { uicr = 1 } END { exit uicr }' "$NW/written.hex"; then bst_ok som-nrf91/uicr-not-written else bst_notok som-nrf91/uicr-not-written "the HEX handed to openocd: '$(_bst_1line "$(sed -n p "$NW/written.hex" 2>/dev/null)")'"; fi if grep -qi -E 'recover|mass_erase' "$NW"/ses/*.tcl 2>/dev/null; then bst_notok som-nrf91/no-recover "a session asked for a recover or a mass erase" else bst_ok som-nrf91/no-recover; fi nrf_stub_run NRF_STUB_UICR_BAD=1 case "$rc:$nses:$out" in 1:1:*"built for other keys"*"nothing was written"*) bst_ok som-nrf91/foreign-uicr-stops ;; *) bst_notok som-nrf91/foreign-uicr-stops "rc=$rc, $nses session(s), want 1 and a refusal: '$(_bst_1line "$out")'" ;; esac nrf_stub_run NRF_STUB_LOCKED=1 case "$rc:$nses:$out" in 1:1:*"debug access is locked"*"nothing was written"*) bst_ok som-nrf91/locked-stops ;; *) bst_notok som-nrf91/locked-stops "rc=$rc, $nses session(s), want 1 and a refusal: '$(_bst_1line "$out")'" ;; esac nrf_stub_run NRF_STUB_IMAGE_BAD=1 pses=$(sed -n p "$NW/ses/1.tcl" 2>/dev/null) case "$pses" in *"test_image "*"image.hex"*) case "$pses" in *reset*) bst_notok som-nrf91/probe-tests-image "the probe resets: '$(_bst_1line "$pses")'" ;; *) bst_ok som-nrf91/probe-tests-image ;; esac ;; *) bst_notok som-nrf91/probe-tests-image "the probe does not test the image: '$(_bst_1line "$pses")'" ;; esac case "$rc:$nses:$out" in 1:1:*"openocd cannot load"*"checksum mismatch"*"nothing was written"*) bst_ok som-nrf91/bad-image-stops ;; *) bst_notok som-nrf91/bad-image-stops "rc=$rc, $nses session(s), want 1 and a refusal: '$(_bst_1line "$out")'" ;; esac nrf_stub_run NRF_STUB_ERASED=1 case "$rc:$nses:$out" in 1:1:*"application flash is erased"*"nothing was written"*) bst_ok som-nrf91/erased-stops ;; *) bst_notok som-nrf91/erased-stops "rc=$rc, $nses session(s), want 1 and a refusal: '$(_bst_1line "$out")'" ;; esac # A failed write keeps a retry under $TMPDIR whose session halts without a reset. rm -rf "$NW/tmp"; mkdir -p "$NW/tmp" nrf_stub_run NRF_STUB_WRITE_FAIL=1 TMPDIR="$NW/tmp" rt=$(ls -d "$NW"/tmp/som-nrf91-retry-*/retry.tcl 2>/dev/null | sed -n 1p) if [ "$rc" = 1 ] && [ -n "$rt" ] && [ "$(sed -n 2p "$rt")" = halt ] && [ "$(grep -c reset "$rt")" = 1 ] \ && grep -q '^if {$rc == 0} { reset run' "$rt"; then bst_ok som-nrf91/retry-no-reset else bst_notok som-nrf91/retry-no-reset "rc=$rc, retry '${rt:-none}': '$(_bst_1line "$(sed -n p "${rt:-/dev/null}")")'"; fi else for id in $NRF_STUB_IDS; do bst_notok som-nrf91/$id "the stand-in openocd in $NW/stub did not run, so som-program-nrf91 was not started" done fi # som-test-nrf91: what it must never send is refused before any port is opened, # whatever the flags; a console that is not there ends the run as a rig failure. out=$(som-test-nrf91 --send "at AT+CFUN=0" 2>&1); rc=$? case "$rc:$out" in 2:*"refused, nothing sent"*) bst_ok som-nrf91/test-refuses-modem-off ;; *) bst_notok som-nrf91/test-refuses-modem-off "rc=$rc: '$(_bst_1line "$out")'" ;; esac out=$(som-test-nrf91 --allow-writes --send "at AT%XFACTORYRESET" 2>&1); rc=$? case "$rc:$out" in 2:*"refused, nothing sent"*) bst_ok som-nrf91/test-refuses-factory-reset ;; *) bst_notok som-nrf91/test-refuses-factory-reset "rc=$rc: '$(_bst_1line "$out")'" ;; esac # A second line behind an allowed one: refused. The allowed line alone gets past # the check and on to the (missing) console. out=$(som-test-nrf91 --send "$(printf 'version\nat AT+CFUN=4')" 2>&1); rc=$? case "$rc:$out" in 2:*"refused, nothing sent"*) bst_ok som-nrf91/test-refuses-newline ;; *) bst_notok som-nrf91/test-refuses-newline "rc=$rc: '$(_bst_1line "$out")'" ;; esac out=$(som-test-nrf91 --send version --console "$NW/no-such-tty" --out "$NW/t2" 2>&1); rc=$? case "$rc:$out" in 2:*"refused"*) bst_notok som-nrf91/test-allows-version "rc=$rc: '$(_bst_1line "$out")'" ;; *) bst_ok som-nrf91/test-allows-version ;; esac out=$(som-test-nrf91 --console "$NW/no-such-tty" --quick --out "$NW/t" 2>&1); rc=$? case "$rc:$out" in 3:*"FAIL -- rig"*) bst_ok som-nrf91/test-no-console ;; *) bst_notok som-nrf91/test-no-console "rc=$rc, want 3 and a rig failure: '$(_bst_1line "$out")'" ;; esac # --- 2. the releases this machine carries, and the modem check ----------------- # --app NAME resolves to /usr/share/brintos-som/nrf9151/NAME/NAME.hex, whose # PROVENANCE supplies the version the board must report after the write and the # oldest modem firmware the release runs on. The comparison itself is pinned at # source: it runs only against a board's console, which no suite has. FW=/usr/share/brintos-som/nrf9151 if [ -d "$FW" ]; then for a in asset_tracker_v2 hello_mcuboot; do P=$FW/$a/PROVENANCE check som-nrf91/fw-$a-file test -s "$FW/$a/$a.hex" check som-nrf91/fw-$a-provenance test -s "$P" want=$(sed -n 's/^sha256: *//p' "$P" 2>/dev/null) have=$(sha256sum "$FW/$a/$a.hex" 2>/dev/null | cut -d' ' -f1) if [ -z "$want" ]; then bst_notok som-nrf91/fw-$a-sha256 "no sha256 in $P" else check_eq som-nrf91/fw-$a-sha256 "$want" "$have"; fi out=$(som-program-nrf91 --dry-run --no-console --app "$a" 2>&1); rc=$? echo "# fw-$a dry run, the HEX split included: $(printf '%s\n' "$out" | sed -n 's/.*PASS -- resolved .*\(\[[0-9]* min [0-9]* s\]\)$/\1/p')" case "$rc:$out" in 0:*"image: $FW/$a/$a.hex (release $a, hex"*"UICR records: 80 bytes (compared, never written)"*"modem: needs nrf91x1_2.0.1 or later"*"PASS -- resolved"*) bst_ok som-nrf91/fw-$a-dry-run ;; *) bst_notok som-nrf91/fw-$a-dry-run "rc=$rc: '$(_bst_1line "$out")'" ;; esac sp=$FW/$a/split if [ -n "$have" ] && [ "$(sed -n 's/^SHA //p' "$sp/split.txt" 2>/dev/null)" = "$have" ] \ && [ -s "$sp/image.hex" ] && [ -s "$sp/uicr.tcl" ]; then bst_ok som-nrf91/fw-$a-split else bst_notok som-nrf91/fw-$a-split "no build-time split for this hex's sha256 in $sp"; fi case "$out" in *"split: precomputed at build ($sp)"*) bst_ok som-nrf91/fw-$a-split-used ;; *) bst_notok som-nrf91/fw-$a-split-used "the dry run split the hex itself: '$(_bst_1line "$out")'" ;; esac case "$a" in asset_tracker_v2) pg="161 of 256 (4 KiB) in 5 range(s)" ;; *) pg="61 of 256 (4 KiB) in 4 range(s)" ;; esac case "$out" in *"pages: $pg; erase: touched"*) bst_ok som-nrf91/fw-$a-pages ;; *) bst_notok som-nrf91/fw-$a-pages "want 'pages: $pg; erase: touched': '$(_bst_1line "$out")'" ;; esac case "$out" in *"expect: '$a v1.2-114' (from the provenance) -- not checked, no console"*) bst_ok som-nrf91/fw-$a-expect ;; *) bst_notok som-nrf91/fw-$a-expect "the dry run did not take the version to check from the provenance: '$(_bst_1line "$out")'" ;; esac done if ls "$FW" "$FW"/* 2>/dev/null | grep -q -i -E 'key|debug|dfu|\.zip$|\.pem$'; then bst_notok som-nrf91/fw-release-only "a key, debug or DFU file is shipped: $(_bst_1line "$(ls -R "$FW")")" else bst_ok som-nrf91/fw-release-only; fi else for id in fw-asset_tracker_v2-file fw-hello_mcuboot-file fw-asset_tracker_v2-pages fw-hello_mcuboot-pages fw-asset_tracker_v2-split fw-hello_mcuboot-split fw-asset_tracker_v2-split-used fw-hello_mcuboot-split-used fw-release-only; do bst_skip som-nrf91/$id "no releases on this image -- brintos-nrf-firmware is not installed" done fi refused app-unknown "no release 'nosuch' on this machine" --app nosuch refused app-not-a-name "--app takes a release name" --app ../asset_tracker_v2 refused app-and-image "--app and --image both name an image" --app asset_tracker_v2 --image "$NW/good.hex" # The modem comparison, taken out of the installed script and run on its own. eval "$(sed -n '/^modem_at_least()/,/^}/p' /usr/bin/som-program-nrf91)" if command -v modem_at_least >/dev/null 2>&1; then bst_ok som-nrf91/modem-compare-defined for c in "nrf91x1_2.0.2 nrf91x1_2.0.1 0" "nrf91x1_2.0.1 nrf91x1_2.0.1 0" "nrf91x1_2.0.10 nrf91x1_2.0.2 0" \ "nrf91x1_2.0.0 nrf91x1_2.0.1 1" "nrf91x1_1.9.9 nrf91x1_2.0.1 1" "nrf9160_1.3.5 nrf91x1_2.0.1 1"; do set -- $c modem_at_least "$1" "$2"; r=$? [ "$r" -eq 0 ] || r=1 check_eq som-nrf91/modem-compare-$1-vs-$2 "$3" "$r" done else bst_notok som-nrf91/modem-compare-defined "som-program-nrf91 has no modem_at_least()" fi # The check runs before the probe, so a refusal touches nothing. m=$(grep -n '^# --- 0. the modem firmware' /usr/bin/som-program-nrf91 | cut -d: -f1) p=$(grep -n '^# --- 1. probe' /usr/bin/som-program-nrf91 | cut -d: -f1) if [ -n "$m" ] && [ -n "$p" ] && [ "$m" -lt "$p" ]; then bst_ok som-nrf91/modem-check-before-probe else bst_notok som-nrf91/modem-check-before-probe "the modem check is not ahead of the probe (lines '$m' and '$p')"; fi # --- som-recover-nrf91 ------------------------------------------------------------- # What a recover decides, and the shape of its one write session, against a # stand-in openocd that answers as an nRF91x1's CTRL-AP: locked or open # (NRF_R_APS), erase protection off or on (NRF_R_EPS), another chip (NRF_R_IDR). # A real recover erases the chip, UICR included, so the suite never runs one. RW=$W/recover; mkdir -p "$RW/stub" "$RW/ses" check som-nrf91/recover-installed test -x /usr/bin/som-recover-nrf91 check_out som-nrf91/recover-help "usage: som-recover-nrf91" som-recover-nrf91 --help rrefused() { _id=$1; _n=$2; shift 2 out=$(som-recover-nrf91 "$@" 2>&1); rc=$? case "$rc:$out" in [12]:*"$_n"*) bst_ok som-nrf91/$_id ;; *) bst_notok som-nrf91/$_id "rc=$rc, want a refusal naming '$_n': '$(_bst_1line "$out")'" ;; esac; } printf 'x' > "$RW/x.bin" printf '%s\n' ':0100000000FF' ':0200000400FFFB' ':04803000000000004C' ':00000001FF' > "$RW/ep.hex" # UICR pages: all 0xFF, then the two APPROTECT words opened, then ERASEPROTECT set. # Built by concatenation: the image's busybox dd has no conv=notrunc. ff() { dd if=/dev/zero bs="$1" count=1 2>/dev/null | tr '\000' '\377'; } ff 4096 > "$RW/u-locked.bin" { printf '\372\120\372\120'; ff 40; printf '\372\120\372\120'; ff 4048; } > "$RW/u-open.bin" { printf '\372\120\372\120'; ff 40; printf '\372\120\372\120'; dd if=/dev/zero bs=4 count=1 2>/dev/null; ff 4044; } > "$RW/u-ep.bin" # u-open.bin with one KMU byte, at 0x410, cleared. { printf '\372\120\372\120'; ff 40; printf '\372\120\372\120'; ff 992; printf '\000'; ff 3055; } > "$RW/u-kmu.bin" rrefused recover-no-image "usage: som-recover-nrf91" rrefused recover-bin-refused "is not an Intel HEX" --image "$RW/x.bin" --dry-run rrefused recover-eraseprotect-record "writes ERASEPROTECT" --image "$RW/ep.hex" --dry-run rrefused recover-uicr-locked "does not open the access port" --image "$NW/good.hex" --uicr "$RW/u-locked.bin" --dry-run rrefused recover-uicr-eraseprot "sets ERASEPROTECT" --image "$NW/good.hex" --uicr "$RW/u-ep.bin" --dry-run out=$(som-recover-nrf91 --image "$NW/good.hex" --uicr "$RW/u-open.bin" --dry-run 2>&1); rc=$? case "$rc:$out" in 0:*"uicr: to write back $RW/u-open.bin"*"; the image's 16 UICR bytes left out"*"ERASEALL through the CTRL-AP"*"PASS -- resolved"*) bst_ok som-nrf91/recover-dry-run-uicr ;; *) bst_notok som-nrf91/recover-dry-run-uicr "rc=$rc: '$(_bst_1line "$out")'" ;; esac if [ -r /usr/share/brintos-som/nrf9151/asset_tracker_v2/asset_tracker_v2.hex ]; then out=$(som-recover-nrf91 --app asset_tracker_v2 --dry-run 2>&1); rc=$? case "$rc:$out" in 0:*"uicr: to write APPROTECT and SECUREAPPROTECT = 0x50FA50FA, then the image's 80 UICR bytes"*"PASS -- resolved"*) bst_ok som-nrf91/recover-dry-run-app ;; *) bst_notok som-nrf91/recover-dry-run-app "rc=$rc: '$(_bst_1line "$out")'" ;; esac else bst_skip som-nrf91/recover-dry-run-app "no releases on this image"; fi printf '%s\n' '#!/bin/sh' \ 'echo x >> "$RS/count"; n=$(grep -c "" "$RS/count")' \ 'f=; for a; do case "$a" in *.tcl) f=$a ;; esac; done' \ '[ -n "$f" ] && sed -n p "$f" > "$RS/ses/$n.tcl"' \ 'if [ -n "$f" ] && grep -q "eraseprotect-status" "$f"; then' \ ' echo "SOM idr=${NRF_R_IDR:-0x12880000}"; echo "SOM approtect-status=${NRF_R_APS:-0}"; echo "SOM eraseprotect-status=${NRF_R_EPS:-1}"' \ 'elif [ -n "$f" ] && grep -q "uicr-read" "$f"; then' \ ' echo "SOM approtect-status=${NRF_R_AFTER:-3}"' \ ' if [ "${NRF_R_AFTER:-3}" -ge 3 ]; then' \ ' sed -n "s/.*dump_image \([^ ]*\) .*/\1/p" "$f" | while read -r b; do cp "${NRF_R_UICR:-$RS/u-open.bin}" "$b"; done; echo "SOM uicr-read=pass"; echo "SOM uicr-tries=1"' \ ' echo "SOM rerun=${NRF_R_RERUN:-0} "' \ ' fi' \ 'elif [ -n "$f" ] && grep -q "dump_image" "$f"; then' \ ' sed -n "s/.*dump_image \([^ ]*\) [^ ]* \([0-9]*\)}.*/\1 \2/p" "$f" | while read -r b z; do dd if=/dev/zero of="$b" bs="$z" count=1 2>/dev/null; done' \ ' echo "SOM backup=pass"' \ 'elif [ -n "$f" ] && grep -q "nrf91_recover" "$f"; then' \ ' sed -n "s/.*verify_image \([^ ]*\) 0x00FF8000 bin.*/\1/p" "$f" | while read -r v; do stat -c %s "$v" > "$RS/uicr-verified"; done' \ ' echo "SOM recover=0 "; echo "SOM halt=0 "; echo "SOM uicr=0 "; echo "SOM write=0 "; echo "SOM verify=0 "; echo "SOM uicr-verify=0 "; echo "SOM result=pass"' \ 'fi' > "$RW/stub/openocd" chmod +x "$RW/stub/openocd" # rrun [env...] : a recover of good.hex against the stand-in, $RARGS added; # sets $out, $rc, $nses, and $rses (the recover session's Tcl, if any). Its # TMPDIR is one of its own: a recover sets aside the retry sessions there, and # a real one in /tmp must stay for the board it belongs to. RARGS is cleared at # the end: bash keeps a VAR=value given before a function call (POSIX leaves it # open), and a later `rrun' would have run with --yes. rrun() { rm -rf "$RW/ses" "$RW/count" "$RW/uicr-verified" "$RW"/nrf91-recover-*; mkdir -p "$RW/ses" "$RW/tmp" out=$(env PATH="$RW/stub:$PATH" RS="$RW" SOM_BACKUP_DIR="$RW" TMPDIR="$RW/tmp" "$@" \ som-recover-nrf91 --image "$NW/good.hex" ${RARGS:-} 2>&1); rc=$? nses=$(grep -c "" "$RW/count" 2>/dev/null); nses=${nses:-0} rses=$(grep -l "nrf91_recover" "$RW"/ses/*.tcl 2>/dev/null | sed -n 1p) RARGS= } RSTUB_IDS="recover-locked recover-session-order recover-no-program recover-no-eraseprotect recover-sets-aside-retry recover-needs-yes recover-eraseprotected-stops recover-other-chip-stops recover-open-needs-force recover-open-force-backup recover-uicr-file-written recover-uicr-head-verified recover-uicr-after-equal recover-uicr-after-differs recover-relocked-fails recover-after-retries recover-after-rerun recover-rerun-fails" : > "$RW/count" if RS="$RW" "$RW/stub/openocd" stub-check >/dev/null 2>&1 && [ -s "$RW/count" ] \ && [ "$(PATH="$RW/stub:$PATH" command -v openocd)" = "$RW/stub/openocd" ]; then RARGS=--yes rrun case "$rc:$nses:$out" in 0:3:*"PASS -- recovered: erased, UICR APPROTECT 0x50FA50FA"*) bst_ok som-nrf91/recover-locked ;; *) bst_notok som-nrf91/recover-locked "rc=$rc, $nses session(s), want 3 and a PASS: '$(_bst_1line "$out")'" ;; esac # One session: the erase, then the two APPROTECT words, the write, the verify, # and only then a reset. if [ -n "$rses" ] && awk ' /nrf91_recover/ { r = NR } /flash fillw 0x00FF8000 0x50FA50FA/ { a = NR } /flash fillw 0x00FF802C 0x50FA50FA/ { b = NR } /flash write_image/ { w = NR } /verify_image/ { if (!v) v = NR } /reset run/ { x = NR } END { exit !(r && a > r && b >= a && w > b && v > w && x > v) }' "$rses"; then bst_ok som-nrf91/recover-session-order else bst_notok som-nrf91/recover-session-order "the recover session: '$(_bst_1line "$(sed -n p "${rses:-/dev/null}")")'"; fi if grep -q -E '(^|[^_a-z])program |reset (halt|init)' "$RW"/ses/*.tcl 2>/dev/null; then bst_notok som-nrf91/recover-no-program "a session runs program or a reset halt/init" else bst_ok som-nrf91/recover-no-program; fi if grep -q -i '0x00FF8030' "$RW"/ses/*.tcl 2>/dev/null; then bst_notok som-nrf91/recover-no-eraseprotect "a session addresses ERASEPROTECT" else bst_ok som-nrf91/recover-no-eraseprotect; fi # A failed write's retry session from before the erase no longer applies, # and left in place it would stop som-program-nrf91: once the erase has run # it is set aside, and a recover refused before the erase leaves it. rm -rf "$RW/tmp"; mkdir -p "$RW/tmp/som-nrf91-retry-7"; : > "$RW/tmp/som-nrf91-retry-7/retry.tcl" rrun if [ -e "$RW/tmp/som-nrf91-retry-7/retry.tcl" ]; then RARGS=--yes rrun if [ "$rc" = 0 ] && [ ! -e "$RW/tmp/som-nrf91-retry-7" ] && [ -e "$RW/tmp/som-nrf91-recovered-7/retry.tcl" ]; then case "$out" in *"retry: set aside $RW/tmp/som-nrf91-retry-7"*) bst_ok som-nrf91/recover-sets-aside-retry ;; *) bst_notok som-nrf91/recover-sets-aside-retry "the run did not say so: '$(_bst_1line "$out")'" ;; esac else bst_notok som-nrf91/recover-sets-aside-retry "rc=$rc, left: '$(ls "$RW/tmp" 2>/dev/null | tr '\n' ' ')': '$(_bst_1line "$out")'"; fi else bst_notok som-nrf91/recover-sets-aside-retry "a recover refused before the erase set the retry session aside"; fi rm -rf "$RW/tmp" rrun case "$rc:$nses:$out" in 1:1:*"give --yes"*"nothing was done"*) bst_ok som-nrf91/recover-needs-yes ;; *) bst_notok som-nrf91/recover-needs-yes "rc=$rc, $nses session(s): '$(_bst_1line "$out")'" ;; esac RARGS=--yes rrun NRF_R_EPS=0 case "$rc:$nses:$out" in 1:1:*"ERASEPROTECT is set"*) bst_ok som-nrf91/recover-eraseprotected-stops ;; *) bst_notok som-nrf91/recover-eraseprotected-stops "rc=$rc, $nses session(s): '$(_bst_1line "$out")'" ;; esac RARGS=--yes rrun NRF_R_IDR=0x00000000 case "$rc:$nses:$out" in 1:1:*"no nRF91x1 CTRL-AP"*) bst_ok som-nrf91/recover-other-chip-stops ;; *) bst_notok som-nrf91/recover-other-chip-stops "rc=$rc, $nses session(s): '$(_bst_1line "$out")'" ;; esac RARGS=--yes rrun NRF_R_APS=3 case "$rc:$nses:$out" in 1:1:*"needs no recover"*) bst_ok som-nrf91/recover-open-needs-force ;; *) bst_notok som-nrf91/recover-open-needs-force "rc=$rc, $nses session(s): '$(_bst_1line "$out")'" ;; esac RARGS="--yes --force" rrun NRF_R_APS=3 bn=$(grep -l dump_image "$RW"/ses/*.tcl 2>/dev/null | sed -n 1p); bn=$(basename "${bn:-x}" .tcl) rn=$(basename "${rses:-y}" .tcl) case "$rc:$nses:$out" in 0:4:*"backup: $RW/nrf91-recover-"*"PASS -- recovered"*) if [ "$bn" -lt "$rn" ] 2>/dev/null; then bst_ok som-nrf91/recover-open-force-backup else bst_notok som-nrf91/recover-open-force-backup "the backup (session $bn) is not before the recover ($rn)"; fi ;; *) bst_notok som-nrf91/recover-open-force-backup "rc=$rc, $nses session(s): '$(_bst_1line "$out")'" ;; esac RARGS="--yes --uicr $RW/u-open.bin" rrun if [ "$rc" = 0 ] && grep -q "flash write_image $RW/u-open.bin 0x00FF8000 bin" "${rses:-/dev/null}" \ && ! grep -q "flash fillw" "${rses:-/dev/null}"; then bst_ok som-nrf91/recover-uicr-file-written else bst_notok som-nrf91/recover-uicr-file-written "rc=$rc: '$(_bst_1line "$out")'"; fi # The KMU part (from 0x400) reads back erased until a reset: the erase # session verifies the APPROTECT words and stops short of it. vs=$(cat "$RW/uicr-verified" 2>/dev/null) if [ "${vs:-0}" -ge 48 ] && [ "${vs:-0}" -le 1024 ]; then bst_ok som-nrf91/recover-uicr-head-verified else bst_notok som-nrf91/recover-uicr-head-verified "the erase session verifies ${vs:-no} bytes at 0x00FF8000; want 48 to 1024"; fi case "$rc:$out" in 0:*"read back after the reset: all 4096 bytes equal $RW/u-open.bin"*"PASS -- recovered"*) bst_ok som-nrf91/recover-uicr-after-equal ;; *) bst_notok som-nrf91/recover-uicr-after-equal "rc=$rc: '$(_bst_1line "$out")'" ;; esac RARGS="--yes --uicr $RW/u-open.bin" rrun NRF_R_UICR="$RW/u-kmu.bin" case "$rc:$nses:$out" in 1:3:*"after the reset is not $RW/u-open.bin"*) bst_ok som-nrf91/recover-uicr-after-differs ;; *) bst_notok som-nrf91/recover-uicr-after-differs "rc=$rc, $nses session(s): '$(_bst_1line "$out")'" ;; esac RARGS=--yes rrun NRF_R_AFTER=1 case "$rc:$nses:$out" in 1:3:*"locked again at its reset"*) bst_ok som-nrf91/recover-relocked-fails ;; *) bst_notok som-nrf91/recover-relocked-fails "rc=$rc, $nses session(s): '$(_bst_1line "$out")'" ;; esac # The CPU may not answer just after the reset: the session waits for the port # and examines the CPU again before each UICR read. aft=$(grep -l "uicr-read" "$RW"/ses/*.tcl 2>/dev/null | sed -n 1p) if [ -n "$aft" ] && grep -q 'apreg 4 0x0c]; if {$s >= 3} break; sleep 1000' "$aft" \ && grep -q 'sleep 1000 }; incr i; set rc \[catch {nrf91.cpu arp_examine; dump_image ' "$aft"; then bst_ok som-nrf91/recover-after-retries else bst_notok som-nrf91/recover-after-retries "the after-reset session: '$(_bst_1line "$(sed -n p "${aft:-/dev/null}")")'"; fi # Reading the UICR during the boot stalled the application: after the read # the chip is reset once more, with no vector catch left set. if [ -n "$aft" ] && awk ' /dump_image/ { d = NR } /cortex_m vector_catch none/ { c = NR } /reset run/ { r = NR } END { exit !(d && c > d && r >= c) }' "$aft"; then bst_ok som-nrf91/recover-after-rerun else bst_notok som-nrf91/recover-after-rerun "the after-reset session: '$(_bst_1line "$(sed -n p "${aft:-/dev/null}")")'"; fi RARGS=--yes rrun NRF_R_RERUN=1 case "$rc:$nses:$out" in 1:3:*"could not reset the chip after reading its UICR"*) bst_ok som-nrf91/recover-rerun-fails ;; *) bst_notok som-nrf91/recover-rerun-fails "rc=$rc, $nses session(s): '$(_bst_1line "$out")'" ;; esac else for id in $RSTUB_IDS; do bst_unrun som-nrf91/$id "the stand-in openocd is not first on PATH" done fi # --- som-test-nrf91 --suite qa --------------------------------------------------- # QA Test Plan 3.1, 3.2, 3.4 and 3.6 against a stand-in board: a shell script on # the FIFO pair that --console DIR opens, answering as asset_tracker_v2 1.2-114's # Zephyr shell. Its QB_* variables delay or alter what it prints; it keeps every # line it receives in rx. A `kernel reboot cold', or the stand-in openocd's # reset, replays the boot. The FIFOs are under $TMPDIR (mkfifo is refused on # the virtiofs root), and this shell holds both open read-write for the length # of a run, so no open of either blocks; closing them ends the stand-in. QW=$W/qa; QD=$QW/con mkdir -p "$QW/stub" "$QW/scripts/interface" "$QW/rel/asset_tracker_v2" : > "$QW/board.cfg"; : > "$QW/scripts/interface/cmsis-dap.cfg" out=$(som-test-nrf91 --suite qa --console "$NW/no-such-tty" --out "$QW/r" 2>&1); rc=$? case "$rc:$out" in 2:*"give --allow-reset (nothing was opened)"*) bst_ok som-nrf91/qa-needs-allow-reset ;; *) bst_notok som-nrf91/qa-needs-allow-reset "rc=$rc, want 2 and a refusal: '$(_bst_1line "$out")'" ;; esac # Without --suite and without --allow-reset the run gets to the console: qa is not a default suite. out=$(som-test-nrf91 --console "$NW/no-such-tty" --out "$QW/r" 2>&1); rc=$? case "$rc:$out" in 3:*"FAIL -- rig"*) bst_ok som-nrf91/qa-not-default ;; *) bst_notok som-nrf91/qa-not-default "rc=$rc, want 3 and a rig failure: '$(_bst_1line "$out")'" ;; esac out=$(som-test-nrf91 --allow-reset --send "fs write test1 54 65 73 74" 2>&1); rc=$? case "$rc:$out" in 2:*"refused, nothing sent"*) bst_ok som-nrf91/qa-fs-write-needs-flag ;; *) bst_notok som-nrf91/qa-fs-write-needs-flag "rc=$rc: '$(_bst_1line "$out")'" ;; esac out=$(som-test-nrf91 --allow-fs-writes --send "fs write test1 54 65 73 74" --console "$NW/no-such-tty" --out "$QW/r" 2>&1); rc=$? case "$rc:$out" in 2:*"refused"*) bst_notok som-nrf91/qa-fs-write-with-flag "rc=$rc: '$(_bst_1line "$out")'" ;; *) bst_ok som-nrf91/qa-fs-write-with-flag ;; esac out=$(som-test-nrf91 --allow-fs-writes --send "kernel reboot cold" 2>&1); rc=$? case "$rc:$out" in 2:*"refused, nothing sent"*) bst_ok som-nrf91/qa-reboot-needs-flag ;; *) bst_notok som-nrf91/qa-reboot-needs-flag "rc=$rc: '$(_bst_1line "$out")'" ;; esac # The rows `fs read lfs1/atest' prints, the hex columns copied from the plan. sed -n p > "$QW/ramp" <<'EOF' 00000000 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ................ 00000010 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F ................ 00000020 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F !"#$%&'()*+,-./ 00000030 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 0123456789:;<=>? 00000040 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F @ABCDEFGHIJKLMNO 00000050 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F PQRSTUVWXYZ[\]^_ 00000060 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F `abcdefghijklmno 00000070 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F pqrstuvwxyz{|}~. 00000080 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F ................ 00000090 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F ................ 000000A0 A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF ................ 000000B0 B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF ................ 000000C0 C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF ................ 000000D0 D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF ................ 000000E0 E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF ................ 000000F0 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE 00 ................ 00000100 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ................ 00000110 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F ................ 00000120 20 21 22 23 24 25 26 27 28 29 2A 2B !"#$%&'()*+ EOF # The stand-in board. QB_START_VER, QB_START_DELAY: the Start line's version # and seconds after the banner; QB_NOCLOUD: no CLOUD_EVT_CONNECTED; QB_FW_EXTRA: # text after the version line; QB_NOHWREV: no Hardware revision; QB_UNPROV: no # credentials; QB_STACKS=over: a thread at 91 %, and one at 90.9 % printed as # 90 %; QB_RAMP_BAD: one byte wrong in row 6; QB_NO_TEST1: test1 lost at a reset. sed -n p > "$QW/board" <<'EOF' D=$1; R=$2 p() { for l; do printf '%s\r\n' "$l"; done; } th() { printf '0x%s %-32s (real size %4d):\tunused %4d\tusage %4d / %4d (%2d %%)\r\n' "$1" "$2" "$3" $(($3 - $4)) "$4" "$3" $(($4 * 100 / $3)); } pause() { read -r u _ < /proc/uptime; e=$((${u%.*} + $1)); while read -r u _ < /proc/uptime && [ "${u%.*}" -lt "$e" ]; do sleep 1; done; } boot() { read -r u _ < /proc/uptime; t0=${u%.*} p '*** Booting nRF Connect SDK v2.8.0-a2386bfc8401 ***' '*** Using Zephyr OS v3.7.99-0bc3393fb112 ***' [ -z "${QB_START_DELAY:-}" ] || pause "$QB_START_DELAY" p "[00:00:00.254,150] main: Start asset_tracker_v2 v${QB_START_VER:-1.2-114-release} on nrf9151som" [ -n "${QB_NOCLOUD:-}" ] || p '[00:00:07.401,033] app_event_manager: CLOUD_EVT_CONNECTED' printf 'uart:~$ ' } read -r u _ < /proc/uptime; t0=${u%.*}; n=0; rm_a=0; rm_t=0 tr '\r' '\n' < "$D/in" | while IFS= read -r l; do printf '%s\n' "$l" >> "$D/rx" case "$l" in '@swd-reset'|'kernel reboot cold') [ "$l" = '@swd-reset' ] || p "$l"; boot; continue ;; esac p "$l" case "$l" in '') ;; help) p 'Available commands:' ' at : Execute an AT command' ' device : Device commands' \ ' fs : File system related commands' ' help : Prints the help message.' \ ' kernel : Kernel commands' ' version : Firmware version' ;; kernel) p 'kernel - Kernel commands' 'Subcommands:' ' cycles : Kernel cycles.' ' reboot : Reboot.' \ ' stacks : List threads stack usage.' ' thread : Thread commands.' ' uptime : Kernel uptime.' ;; 'kernel uptime') n=$((n + 1)); read -r u _ < /proc/uptime; p "Uptime: $(( (${u%.*} - t0) * 1000 + n )) ms" ;; device) p 'device - Device commands' 'Subcommands:' ' levels : List configured devices by levels' \ ' list : List configured devices' ;; version) p "Firmware version: asset_tracker_v2 v1.2-114-release on nrf9151som${QB_FW_EXTRA:+ (debug)}" [ -n "${QB_NOHWREV:-}" ] || p 'Hardware revision: 0' ;; 'at AT%CMNG=1') [ -n "${QB_UNPROV:-}" ] || p '%CMNG: 16842753,0,"2C43952EE9E000FF2ACC4E2ED0897C0A72AD5FA72C3D934E81741CBD54F05BD1"' \ '%CMNG: 16842753,1,"7F2C2A0A7E2C7FDFC7AE6E33A1F23A9A4F2A5E10B1F0B5C4D9D39C5E1A0C9B27"' '%CMNG: 16842753,2' p OK ;; 'kernel thread stacks') th 20001a40 main 2048 1024 case "${QB_STACKS:-}" in over) th 2000c1e8 date_time_work_q 1280 1163; th 20003000 sysworkq 2048 1864 ;; *) th 2000c1e8 date_time_work_q 1280 1152; th 20003000 sysworkq 2048 1100 ;; esac th 20004000 idle 320 64; th 20010000 'IRQ 00' 2048 640 ;; fs) p 'fs - File system related commands' 'Subcommands:' ' cat : Concatenate files' \ ' cd : Change working directory' ' erase_write_test : Erase/write test' \ ' ls : List files in current directory' ' read : Read from file' \ ' read_test : Read test' ' rm : Remove file' ' trunc : Truncate file' \ ' write : Write file' ;; 'fs ls') p 'lfs1/' ;; 'fs cd lfs1'|'fs write test1 54 65 73 74'|'fs trunc test1') ;; 'fs erase_write_test atest 300 2') p 'Loop #1 done in 151ms.' 'Loop #2 done in 147ms.' 'Total: 298ms, Per loop: ~149ms, Speed: ~2.0KiBps' ;; 'fs read_test atest 2') p 'File size: 300.0B' 'Loop #1 done in 12ms.' 'Loop #2 done in 12ms.' 'Total: 24ms, Per loop: ~12ms, Speed: ~24.4KiBps' ;; 'fs read test1') p 'File size: 4' '00000000 54 65 73 74 Test' ;; 'fs cat test1'|'fs cat lfs1/test1') printf 'Test' ;; # no newline: the real board's prompt follows on the same line 'fs ls lfs1/'|'fs ls /lfs1') [ "$rm_a" = 1 ] || p atest; [ "$rm_t" = 1 ] || [ -n "${QB_NO_TEST1:-}" ] || p test1 ;; 'fs read lfs1/atest') p 'File size: 300' if [ -n "${QB_RAMP_BAD:-}" ]; then sed '6s/ 55 / 56 /' "$R"; else sed -n p "$R"; fi | while IFS= read -r r; do p "$r"; done ;; 'fs rm /lfs1/atest') rm_a=1 ;; 'fs rm /lfs1/test1') rm_t=1 ;; *) p "${l%% *}: command not found" ;; esac printf 'uart:~$ ' done > "$D/out" EOF # The stand-in openocd keeps its arguments and the Tcl files it is given, and # resets the stand-in board. printf '%s\n' '#!/bin/sh' \ '{ echo "--- call"; for a; do echo "arg $a"; case "$a" in *.tcl) sed "s/^/tcl /" "$a" ;; esac; done; } >> "$QB_DIR/ocd"' \ 'printf "@swd-reset\n" >> "$QB_DIR/in"' > "$QW/stub/openocd" chmod +x "$QW/stub/openocd" QREL=$QW/rel if [ -r /usr/share/brintos-som/nrf9151/asset_tracker_v2/PROVENANCE ]; then QREL=/usr/share/brintos-som/nrf9151 else printf '%s\n' 'release: 1.2-114, nRF Connect SDK 2.8.0' 'expect: asset_tracker_v2 v1.2-114' \ 'origin: https://example.invalid/asset_tracker_v2-on-nrf9151som-v1.2-114-04151903.tar.xz (sha256 0), file asset_tracker_v2_on_nrf9151som_v1.2-114-04151903_release.hex' \ > "$QW/rel/asset_tracker_v2/PROVENANCE"; fi # qa_case ARGS... : one som-test-nrf91 run against a fresh stand-in started # with $QENV; sets $out and $rc, and leaves the stand-in's rx and ocd in $QD. qa_case() { rm -rf "$QD"; mkdir -p "$QD"; : > "$QD/rx" mkfifo "$QD/in" "$QD/out" exec 7<> "$QD/in" 8<> "$QD/out" env $QENV sh "$QW/board" "$QD" "$QW/ramp" < /dev/null > /dev/null 2>&1 7>&- 8>&- & out=$(env PATH="$QW/stub:$PATH" QB_DIR="$QD" SOM_BOARD_CFG="$QW/board.cfg" SOM_OPENOCD_SCRIPTS="$QW/scripts" \ SOM_FW_DIR="$QREL" som-test-nrf91 --console "$QD" --out "$QW/r" "$@" 7>&- 8>&- 2>&1); rc=$? exec 7>&- 8>&- } # qa_has ... : ok iff the last run's output holds each text. qa_has() { _id=$1; shift for _t; do case "$out" in *"$_t"*) ;; *) bst_notok som-nrf91/$_id "no '$_t' in: '$(_bst_1line "$out")'"; return ;; esac; done bst_ok som-nrf91/$_id } nreb() { grep -c -x 'kernel reboot cold' "$QD/rx" 2>/dev/null; } QA_IDS="qa-console-pass qa-version-from-provenance qa-version-pass qa-stacks-pass qa-fs-pass qa-fs-removed qa-reset-shell qa-end-stable-after-resets qa-console-wrong-version qa-version-depends qa-stacks-unprovisioned qa-fs-skip-no-flag qa-console-start-late qa-version-extra-text qa-stacks-over-known qa-stacks-unrounded qa-fs-ramp-row qa-fs-removed-after-fail qa-console-swd qa-swd-reset-only qa-version-no-hwrev qa-stacks-no-cloud qa-fs-test1-lost" mkdir -p "$QD" if mkfifo "$QD/probe" 2>/dev/null && [ "$(PATH="$QW/stub:$PATH" command -v openocd)" = "$QW/stub/openocd" ]; then V=asset_tracker_v2; S="QA Test Plan v23" QENV=""; qa_case --suite qa --allow-reset --allow-fs-writes qa_has qa-console-pass "qa.console PASS -- $S 3.1: the banner and 'main: Start $V v1.2-114-release on nrf9151som' within" \ "(soft reset over the shell, not the plan's power cycle)" qa_has qa-version-from-provenance "qa: expects $V v1.2-114-release (from the PROVENANCE in $QREL/$V)" qa_has qa-version-pass "qa.version PASS -- $S 3.2: Firmware version: $V v1.2-114-release on nrf9151som; Hardware revision: 0" # A thread at exactly 90 % is within the limit. qa_has qa-stacks-pass "qa.stacks PASS -- $S 3.4: CLOUD_EVT_CONNECTED" "5 threads, highest 90.0 % date_time_work_q (1152 / 1280); limit 90 %" qa_has qa-fs-pass "qa.fs PASS -- $S 3.6: lfs1 mounted;" if grep -qx 'fs rm /lfs1/atest' "$QD/rx" && grep -qx 'fs rm /lfs1/test1' "$QD/rx"; then bst_ok som-nrf91/qa-fs-removed else bst_notok som-nrf91/qa-fs-removed "the stand-in received: '$(_bst_1line "$(sed -n p "$QD/rx")")'"; fi if [ "$(nreb)" = 3 ] && [ ! -e "$QD/ocd" ]; then bst_ok som-nrf91/qa-reset-shell else bst_notok som-nrf91/qa-reset-shell "$(nreb) kernel reboot cold, want 3 and no openocd: '$(_bst_1line "$(sed -n p "$QD/ocd" 2>/dev/null)")'"; fi case "$rc:$out" in 0:*"end.stable PASS"*"PASS -- "*) bst_ok som-nrf91/qa-end-stable-after-resets ;; *) bst_notok som-nrf91/qa-end-stable-after-resets "rc=$rc, want 0 and end.stable PASS: '$(_bst_1line "$out")'" ;; esac QENV="QB_START_VER=1.2-113-release QB_UNPROV=1"; qa_case --suite qa --allow-reset --reset shell --qa-version 1.2-114-release qa_has qa-console-wrong-version "qa.console FAIL -- $S 3.1: the board started '$V v1.2-113-release on nrf9151som', want '$V v1.2-114-release on nrf9151som'" qa_has qa-version-depends "qa.version SKIP -- $S 3.2: depends on qa.console (3.1)" if [ "$(nreb)" = 1 ]; then qa_has qa-stacks-unprovisioned "qa.stacks SKIP -- $S 3.4: not provisioned (state:provisioned)" else bst_notok som-nrf91/qa-stacks-unprovisioned "$(nreb) resets, want 1: an unprovisioned board is not reset for 3.4"; fi if grep -q '^fs' "$QD/rx"; then bst_notok som-nrf91/qa-fs-skip-no-flag "an fs line was sent: '$(_bst_1line "$(grep '^fs' "$QD/rx")")'" else qa_has qa-fs-skip-no-flag "qa.fs SKIP -- $S 3.6: writes the board's LittleFS: --allow-fs-writes"; fi # The Start line 35 s after the banner is outside the plan's 30 s. QENV="QB_START_DELAY=35 QB_UNPROV=1"; qa_case --suite qa --allow-reset --reset shell --qa-version 1.2-114-release qa_has qa-console-start-late "qa.console FAIL -- $S 3.1: no 'main: Start' line within 30 s of the reset" QENV="QB_FW_EXTRA=1 QB_STACKS=over QB_RAMP_BAD=1" qa_case --suite qa --allow-reset --reset shell --allow-fs-writes --qa-version 1.2-114-release qa_has qa-version-extra-text "qa.version FAIL -- $S 3.2: want 'Firmware version: $V v1.2-114-release on nrf9151som', the board printed 'Firmware version: $V v1.2-114-release on nrf9151som (debug)'" # The asset_tracker profile knows 1.2-114's stacks: a FAIL there is an XFAIL. qa_has qa-stacks-over-known "qa.stacks XFAIL -- $S 3.4: " "OVER 91.0 % sysworkq (1864 / 2048); limit 90 %" "(known issue)" # 1163 / 1280 is 90.9 %; the board prints its percentage rounded down, as 90 %. qa_has qa-stacks-unrounded "qa.stacks XFAIL -- $S 3.4: OVER 90.9 % date_time_work_q (1163 / 1280); OVER 91.0 %" qa_has qa-fs-ramp-row "qa.fs FAIL -- $S 3.6: row 6 (offset 0x50) of lfs1/atest: want 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F, the board printed: 00000050 50 51 52 53 54 56 56" if grep -qx 'fs rm /lfs1/atest' "$QD/rx" && grep -qx 'fs rm /lfs1/test1' "$QD/rx"; then bst_ok som-nrf91/qa-fs-removed-after-fail else bst_notok som-nrf91/qa-fs-removed-after-fail "the stand-in received: '$(_bst_1line "$(sed -n p "$QD/rx")")'"; fi QENV="QB_NOHWREV=1 QB_NOCLOUD=1 QB_NO_TEST1=1" qa_case --suite qa --allow-reset --reset swd --speed 500 --adapter cmsis-dap --allow-fs-writes --qa-cloud-wait 2 \ --qa-version 1.2-114-release --profile generic --known qa.version qa_has qa-console-swd "qa.console PASS -- $S 3.1:" "(soft reset over SWD, not the plan's power cycle)" # Each reset is one openocd run whose only commands are init, reset run and # shutdown: a read during the boot stalled 1.2-114's application. if [ "$(nreb)" = 0 ] && awk -v cfg="$QW/board.cfg" ' function chk() { if (!(na == 6 && a[1] == "-f" && a[2] ~ /prelude\.tcl$/ && a[3] == "-f" && a[4] == cfg && a[5] == "-c" && a[6] == "init; reset run; shutdown" && sp && ifc)) bad = 1 } /^--- call$/ { if (c) chk(); c++; na = 0; sp = 0; ifc = 0; next } /^arg / { a[++na] = substr($0, 5) } $0 == "tcl set BRINTOS_JTAG_SPEED 500" { sp = 1 } $0 == "tcl set BRINTOS_JTAG_IFACE interface/cmsis-dap.cfg" { ifc = 1 } END { if (c) chk(); exit !(c == 3 && !bad) }' "$QD/ocd" 2>/dev/null; then bst_ok som-nrf91/qa-swd-reset-only else bst_notok som-nrf91/qa-swd-reset-only "$(nreb) shell resets; openocd ran: '$(_bst_1line "$(sed -n p "$QD/ocd" 2>/dev/null)")'"; fi qa_has qa-version-no-hwrev "qa.version XFAIL -- $S 3.2: want 'Hardware revision: 0', the board printed no Hardware revision line" "(known issue)" qa_has qa-stacks-no-cloud "qa.stacks FAIL -- $S 3.4: booted, but no CLOUD_EVT_CONNECTED within 2 s" qa_has qa-fs-test1-lost "qa.fs FAIL -- $S 3.6: 'fs ls lfs1/' printed no line matching '^test1\$'" else for id in $QA_IDS; do bst_notok som-nrf91/$id "no FIFO under $QD, or the stand-in openocd is not first on PATH, so som-test-nrf91 was not started" done fi rm -rf "$W"