. "$BST_LIB" # BST-TIMEOUT: 180 # ~50 cases, ~60 execs of a 160 KB module, no loops over a # # corpus and no waits. PREDICTED, not yet measured: this # # suite has never run -- the port was built at recipe scope # # only, so there is no ==BST-SUITE line for it yet. The # # bound is sized from 99-devmem2 (54 cases, 21s measured), # # which is the same shape of suite against the same device; # # 180s is ~8x that. Every case here either decides in argv # # with no syscall at all, or is refused at mmap(2) on the # # first page it asks for, so none of them can block. # pxaregs -- a PXA register poker on a machine that is not a PXA and cannot map # /dev/mem. 1.14, meta-oe recipes-support/pxaregs. ONE 1975-line C file, ONE # binary in ${sbindir}, no library, no configure script, no dependency past libc. # # WHAT THIS PROGRAM ACTUALLY IS. It carries a table of 1320 PXA SoC register # definitions compiled into the binary -- name, physical address, bit shift, # mask, format letter and a human description -- and it has exactly three modes, # chosen by argc alone: # argc==1 dump every register in the table # argc==2 dump the registers whose name CONTAINS argv[1] (strstr) # argc==3 set the single register whose name EQUALS argv[1] (strcmp) # argc>=4 usage, exit 1 # argv[1] is uppercased IN PLACE before any of that, so the lookup is # effectively case-insensitive. # # THE PREMISE THIS SUITE HAD TO CORRECT, and it is the thing worth reading here. # It is natural to assume -- the ticket did -- that PRINTING the compiled-in # table is pure computation and must work on any machine, with only the register # ACCESS refused. THAT IS NOT WHAT THE PROGRAM DOES. dumpall() and # dumpmatching() both go through dumpentry(), and dumpentry()'s FIRST act for # each new address is # data = getmem(lastaddr); # which opens /dev/mem and mmap()s it. Every printf in the listing is downstream # of that call. So on a machine where the mapping is refused, `pxaregs` and # `pxaregs ` print NOTHING AT ALL -- not a truncated table, not a header, # nothing -- and exit 1 with the diagnostic on stderr. There is no listing path # that does not first map physical memory, and no argument that produces one. # MEASURED, on a native x86-64 build of the identical patched source run as an # ordinary user (so the open is refused): both spellings produce empty stdout. # # WHAT IS THEREFORE ASSERTABLE ABOUT THE TABLE ANYWAY, which is most of what the # ticket wanted and is the substance of this file. The argc==3 path checks the # name BEFORE it maps anything: setreg() walks the table counting exact matches # and, if the count is not exactly 1, prints # No or more than one matching register found # on STDOUT and exits 1 WITHOUT opening /dev/mem. That gives a clean two-valued # oracle for table membership on a machine with no mapping: # name IS in the table -> execution reaches open/mmap -> the /dev/mem or # mmap diagnostic on STDERR, exit 1 # name is NOT in it -> the not-found line on STDOUT, exit 1, no syscall # The two are distinguishable by STREAM as well as by text, which is why the # cases below assert stdout and stderr separately rather than combined. Every # membership assertion in this file is built on that oracle -- it is the only way # to interrogate the compiled-in table on this platform, and it works today. # # THE COUNT IS 1320 AND EVERY NAME IS UNIQUE. Verified by preprocessing the # patched source with the recipe's own macro set (it defines neither # CONFIG_ARCH_PXA_IDP nor CONFIG_ARCH_RAMSES) and sorting the table's names: # 1320 entries, 1320 distinct. So the "or more than one" half of that message is # UNREACHABLE through an exact name -- only the zero-match half can fire. Said # here because the message reads as though both were live. # # THE FOUR PATCHES ARE ASSERTED THROUGH THAT ORACLE, which is the only evidence # on target that do_patch ran at all: # serial.patch adds HW_MCR and HW_MSR (2 entries) # usb.patch adds UP2OCR and four UP2OCR_* fields (5 entries) # i2c.patch drops the includes -- invisible at runtime, and # the build succeeding IS its assertion # munmap.patch fixes munmap(0,...) -> munmap(map,...) in both accessors. # UNREACHABLE HERE and deliberately not asserted: it is # downstream of a successful mmap, which this machine refuses. # The 7 added names are asserted PRESENT, and CPLD_* -- which exist only under # CONFIG_ARCH_PXA_IDP / CONFIG_ARCH_RAMSES -- are asserted ABSENT. That absence # is what pins which preprocessor arm this binary was compiled from, and a # rebuild that started defining either macro reddens it. # # WHAT THIS MACHINE DOES WITH /dev/mem IS NOT ASSUMED, IT IS PROBED. The sibling # port 99-devmem2 MEASURED all of it on this image on 2026-08-26 and it is worth # restating, because it is the opposite of what "no PXA behind this machine" # suggests: # /dev/mem EXISTS and OPENS. It is a chardev 1,1 via devtmpfs; CONFIG_DEVMEM # is `default y` and the wasm32 defconfig does not turn it off. So the # open-failure rung is UNREACHABLE here. # mmap(2) IS REFUSED. This kernel does not support writable MAP_SHARED # mappings, file-backed or anonymous, and returns ENODEV -- brintos/linux#30. # pxaregs asks for PROT_READ|PROT_WRITE / MAP_SHARED, so it is refused. # PREDICTED FOR THIS BOOT, NOT YET MEASURED (this suite has never run): the # access cases land on the mmap rung and report `mmap(): No such device`, exit 1, # empty stdout. The suite does not hardcode that -- it runs the binary once, reads # which rung it reached, and asserts that rung. If brintos/linux#30 is fixed, the # probe reports `mapped`, the listing and write cases below start asserting for # real with no edit to this file, and the refusal cases stop being asserted, so a # repaired platform does not redden the suite. # # THE GATED EXPECTATIONS ARE MEASURED TOO, not transcribed from format strings. # They were read off a native x86-64 build of the identical patched source with # the open redirected to a large sparse regular file (which an ordinary user CAN # map MAP_SHARED), so the column widths, the 4x8 bit grouping, the leading blank # line before each register block and the shift/mask arithmetic on the write path # are observations rather than readings of printf(). # # THERE IS NO SEGFAULT PATH, and the suite says so explicitly. Both accessors # check `fd < 0` after open and `map == (void*)-1` after mmap before they # dereference anything, so a refused mapping exits(1) through perror() rather # than trapping. The rc cases below assert exactly 1 -- not merely non-zero -- # because a value >= 128 is a signal death and is the defect this entry exists # to find. # # TWO UPSTREAM BEHAVIOURS ARE PINNED RATHER THAN PATCHED (pxaregs is third-party; # see no-public-upstream-reports.md): # - AN UNKNOWN NAME ON THE argc==2 PATH IS SILENT AND EXITS 0. dumpmatching() # simply matches nothing and falls off the end of main. It is NOT rejected, # it is not diagnosed, and the status is SUCCESS. Pinned below, because it # is the opposite of the argc==3 behaviour and a reader will expect them to # agree. # - THE USAGE BLOCK GOES TO STDOUT, not stderr, while carrying exit 1. # Asserted on the right stream for the same reason. # BST-TIMEOUT-OK: GRANDFATHERED, NOT CLASSIFIED. This suite landed while the # timeout(1) gate was in flight on another PR, so neither review could see the # other, and its bounds have not been through the BRINTOS-1645 sweep. They # are inert, like every other timeout(1), on a kernel without the # brintos/linux#77 fix: there the cases pass because the wrapped commands exit # on their own, not because a bound holds. Some plausibly ARE load-bearing -- an # `iio_info -u ip:...' against a TEST-NET address, a bare `thd' that daemonises # -- and classifying those needs the suite's author, not a mechanical # conversion by someone who cannot test it. # This waiver unblocks the build and marks the debt; it does not claim the # bounds are fine. require pxaregs pxaregs/present W=${TMPDIR:-/tmp}/bst-pxaregs.$$ out=$W.out; err=$W.err : > "$out"; : > "$err" # The not-found line, verbatim from the source. Named once: nine cases compare # against it, and a typo in one of them would otherwise pass by asserting a # substring of itself. NOTFOUND="No or more than one matching register found" # --------------------------------------------------------------- identity ---- # ${sbindir}, not ${bindir} -- the recipe's do_install says so and this is the # one packaging fact the ticket asked for by name. check_out pxaregs/path "/usr/sbin/pxaregs" command -v pxaregs check pxaregs/is-file test -f /usr/sbin/pxaregs check pxaregs/is-executable test -x /usr/sbin/pxaregs # One binary and nothing else: -dev and -dbg are both produced EMPTY by this # recipe, so ${PN} is the whole package and /usr/sbin/pxaregs is the whole of it. check pxaregs/no-bindir-copy test ! -e /usr/bin/pxaregs # ------------------------------------------------------- the device node ------ # Read the live system rather than asserting a constant. These are not pxaregs' # behaviour -- they are the PRECONDITION its behaviour is a consequence of, and # recording them is what makes the refusal below attributable to the MAPPING # rather than to a missing device. memnode=0; [ -c /dev/mem ] && memnode=1 if [ "$memnode" = 1 ]; then check pxaregs/devmem-is-chardev test -c /dev/mem check pxaregs/devmem-major-minor sh -c "ls -l /dev/mem | grep -qE ' 1, *1 '" else bst_skip pxaregs/devmem-is-chardev "no /dev/mem node on this image: the probe below reports what pxaregs does instead" bst_skip pxaregs/devmem-major-minor "no /dev/mem node on this image" fi # ------------------------------------------- RUNG 1: argv, before any syscall -- # `argc >= 4` is the only branch of main that neither reads the table nor opens # anything. Usage on STDOUT -- asserted as its own case because it is the # surprising stream -- with exit 1 and NOTHING on stderr. check_rc pxaregs/usage-rc 1 sh -c "pxaregs a b c > '$out' 2>'$err'" check_out pxaregs/usage-dump-all "- to dump all known registers" cat "$out" check_out pxaregs/usage-dump-one "- to dump named register" cat "$out" check_out pxaregs/usage-set-one "- to set named register" cat "$out" check_eq pxaregs/usage-not-on-stderr "" "$(cat "$err")" # The usage lines interpolate argv[0] with %s. Invoking by ABSOLUTE path is what # shows that, and distinguishes it from a baked-in string. Absolute on purpose: # argv[0] for a PATH lookup is a property of the SHELL, so asserting the bare # name would pin the runner's shell rather than pxaregs' formatting. /usr/sbin/pxaregs a b c > "$out" 2>/dev/null check_out pxaregs/usage-argv0 "Usage: /usr/sbin/pxaregs" cat "$out" # ------------------------------------------------ the table-membership oracle -- # argc==3 counts EXACT matches before it maps anything, so a name absent from the # table is refused with no syscall at all. This is the whole basis of the table # assertions below; assert its two halves are really distinguishable first. # # A name that cannot be in any register table. Refused on STDOUT, exit 1, and -- # the load-bearing half -- NOTHING on stderr, which is what proves no open(2) was # attempted rather than merely that the program failed. check_rc pxaregs/unknown-set-rc 1 sh -c "pxaregs ZZZNOSUCHREG 0 > '$out' 2>'$err'" check_eq pxaregs/unknown-set-msg "$NOTFOUND" "$(cat "$out")" check_eq pxaregs/unknown-set-no-stderr "" "$(cat "$err")" # The SAME unknown name on the argc==2 path is SILENT and exits 0. This pair is # the pinned upstream inconsistency: same name, same table, opposite verdict, # decided by nothing but argc. check_rc pxaregs/unknown-dump-rc 0 sh -c "pxaregs ZZZNOSUCHREG > '$out' 2>'$err'" check_eq pxaregs/unknown-dump-silent-stdout "" "$(cat "$out")" check_eq pxaregs/unknown-dump-silent-stderr "" "$(cat "$err")" # _in_table : the name reaches the access path, i.e. it is NOT # refused by the count check. Asserted as the ABSENCE of the not-found line on # stdout, which is a statement about the table alone and stays true whichever # rung the access path then fails at -- so these cases do not have to be gated on # the probe below. _in_table() { _o=$(pxaregs "$2" 0 2>/dev/null) case "$_o" in *"$NOTFOUND"*) bst_notok "$1" "'$2' is NOT in the compiled-in table: $(_bst_1line "$_o")" ;; *) bst_ok "$1" ;; esac } # _not_in_table : refused by the count check, on stdout. _not_in_table() { _o=$(pxaregs "$2" 0 2>/dev/null) case "$_o" in *"$NOTFOUND"*) bst_ok "$1" ;; *) bst_notok "$1" "'$2' IS in the compiled-in table, but this build should not carry it: $(_bst_1line "$_o")" ;; esac } # --------------------------------------------------- the table, and the patches -- # A register from the stock 1.14 table, as a positive control for the oracle # itself: if this one came back "not found" the oracle would be broken and every # patch case below would be meaningless. _in_table pxaregs/table-has-pmcr PMCR _in_table pxaregs/table-has-ibmr IBMR _in_table pxaregs/table-has-isar ISAR # serial.patch: two HWUART registers. The ONLY on-target evidence that this # patch was applied. _in_table pxaregs/serial-patch-hw-mcr HW_MCR _in_table pxaregs/serial-patch-hw-msr HW_MSR # usb.patch: the UP2OCR register and its four bit-fields. _in_table pxaregs/usb-patch-up2ocr UP2OCR _in_table pxaregs/usb-patch-up2ocr-seos UP2OCR_SEOS _in_table pxaregs/usb-patch-up2ocr-hxoe UP2OCR_HXOE _in_table pxaregs/usb-patch-up2ocr-hxs UP2OCR_HXS _in_table pxaregs/usb-patch-up2ocr-idon UP2OCR_IDON # The CPLD_* block is compiled in ONLY under CONFIG_ARCH_PXA_IDP or # CONFIG_ARCH_RAMSES. The recipe's do_compile passes neither, so these must be # ABSENT -- which is what pins which arm of the #if this binary came from. A # rebuild that started defining either macro reddens exactly these three. _not_in_table pxaregs/idp-arm-not-compiled-cs CPLD_CS _not_in_table pxaregs/idp-arm-not-compiled-flash CPLD_FLASH _not_in_table pxaregs/idp-arm-not-compiled-kbd CPLD_KB_ROW # argv[1] is uppercased IN PLACE before the lookup, so a lowercase spelling finds # the same entry. Asserted through the oracle: if the uppercasing were dropped, # `up2ocr_seos` would match nothing and come back with the not-found line. _in_table pxaregs/argv-uppercased-lower UP2OCR_SEOS _in_table pxaregs/argv-uppercased-mixed Up2Ocr_Seos # The count check is EXACT-match, not substring: `UP2OCR_` is a prefix of four # real entries and an exact match for none, so the set path refuses it. This is # what separates the argc==3 strcmp from the argc==2 strstr. _not_in_table pxaregs/set-is-exact-match UP2OCR_ # ------------------------------------------------------------------ probe ---- # Run the binary once on a name that IS in the table and READ WHICH RUNG it # reached. Nothing below is hardcoded: the three states are set only from this # binary's own behaviour on this boot. A timeout bounds it, because "does not # hang" is one of the things being asserted. timeout 20 pxaregs PMCR 0 > "$out" 2>"$err"; probe_rc=$? probe_out=$(cat "$out" 2>/dev/null) probe_err=$(cat "$err" 2>/dev/null) opened=1; mapped=0 case "$probe_err" in *'open("/dev/mem")'*) opened=0 ;; esac case "$probe_err" in *'mmap()'*) mapped=0 ;; *) [ "$opened" = 1 ] && mapped=1 ;; esac if [ "$mapped" = 1 ]; then stage=mapped elif [ "$opened" = 1 ]; then stage=mmapfail else stage=openfail fi # The refusal is BOUNDED and is not a signal death, whichever rung it lands on. # Asserted before the rungs are split, because it is true of all of them and it # is the specific defect the ticket named: a tool that maps and dereferences # without checking would trap here instead of exiting. check_eq pxaregs/access-terminates "0" "$([ "$probe_rc" -ne 124 ] && echo 0 || echo 1)" if [ "$stage" = mapped ]; then check_eq pxaregs/access-not-a-signal-death "0" "$([ "$probe_rc" -lt 128 ] && echo 0 || echo 1)" else check_eq pxaregs/access-rc-is-one "1" "$probe_rc" check_eq pxaregs/access-not-a-signal-death "0" "$([ "$probe_rc" -lt 128 ] && echo 0 || echo 1)" # The diagnostic is on STDERR and stdout is EMPTY -- every printf in the # listing is downstream of the failed getmem(), so a refused access prints # no table at all. This is the corrected premise, asserted. check_eq pxaregs/access-refused-no-stdout "" "$probe_out" fi # ------------------------------------------- RUNG 2: open(2), and its two ways -- if [ "$opened" = 1 ]; then bst_skip pxaregs/open-fail-perror "this machine's /dev/mem opens (O_RDWR|O_SYNC succeeded), so the open-failure rung is unreachable -- see the mmap cases" bst_skip pxaregs/open-fail-errno "this machine's /dev/mem opens, so there is no open errno to carry" else # perror("open(\"/dev/mem\")") -- the prefix is the program's, the text # after the colon is the kernel's errno string. Asserted separately so a # machine that starts refusing for a DIFFERENT reason reddens the errno # case alone and stays legible. check_out pxaregs/open-fail-perror 'open("/dev/mem")' cat "$err" check pxaregs/open-fail-errno sh -c "grep -q 'open(\"/dev/mem\"): .' '$err'" bst_skip pxaregs/mmap-refused-perror "open(2) on /dev/mem failed, so mmap(2) was never ATTEMPTED: $(_bst_1line "$probe_err")" bst_skip pxaregs/mmap-refused-errno "open(2) on /dev/mem failed, so mmap(2) was never attempted" bst_skip pxaregs/mmap-refused-enodev "open(2) on /dev/mem failed, so mmap(2) was never attempted" fi # ------------------------------------------ RUNG 3: mmap(2) -- the real gate --- if [ "$stage" = mmapfail ]; then # THE REFUSAL, WHICH IS THIS PORT'S DOCUMENTED FAILURE PATH on this # platform. perror("mmap()"), exit 1, nothing on stdout. check_out pxaregs/mmap-refused-perror "mmap()" cat "$err" check pxaregs/mmap-refused-errno sh -c "grep -q 'mmap(): .' '$err'" # The errno itself, asserted as its OWN case. ENODEV is brintos/linux#30 -- # writable MAP_SHARED unsupported. If the platform starts refusing for # another reason this is the one case that reddens, and it should. if grep -q 'mmap(): No such device' "$err" 2>/dev/null; then bst_ok pxaregs/mmap-refused-enodev else bst_notok pxaregs/mmap-refused-enodev "expected ENODEV (brintos/linux#30, writable MAP_SHARED unsupported); got: $(_bst_1line "$probe_err")" fi # The refusal is a property of the MAPPING, not of the address or the mode: # a READ attempt on a different register is refused identically, so there is # no register on this machine that would work. timeout 20 pxaregs HW_MCR > "$out" 2>"$err" check_out pxaregs/mmap-refused-on-read "mmap()" cat "$err" check_eq pxaregs/mmap-refused-read-no-stdout "" "$(cat "$out")" elif [ "$stage" = mapped ]; then for _c in mmap-refused-perror mmap-refused-errno mmap-refused-enodev \ mmap-refused-on-read mmap-refused-read-no-stdout; do bst_skip "pxaregs/$_c" "mmap(2) SUCCEEDED on this boot -- brintos/linux#30 appears fixed; the listing cases below are running for real" done else for _c in mmap-refused-on-read mmap-refused-read-no-stdout; do bst_skip "pxaregs/$_c" "open(2) on /dev/mem failed, so mmap(2) was never attempted: $(_bst_1line "$probe_err")" done fi # ------------------------------------------------------------ mapped-gated ---- # THE POINT OF THE PROGRAM: the register listing, its filtering, and the write. # These are unreachable while brintos/linux#30 stands, and they run for real the # moment it is fixed -- gated on the PROBE above, never on a hardcoded skip. # # Every expectation here was MEASURED on a native x86-64 build of the identical # patched source with the /dev/mem open redirected to a large sparse regular # file, which an ordinary user can map MAP_SHARED. The column widths, the 4x8 # bit grouping and the shift/mask arithmetic are observations. if [ "$mapped" = 1 ]; then # A register block is: a blank line, the description, then the register # line -- name in 24 columns, 0x%08x, then the 32 bits in four groups of # eight. The bit grouping is the one piece of formatting a width or # endianness change in the table would disturb. timeout 30 pxaregs UP2OCR > "$out" 2>/dev/null check_out pxaregs/dump-desc-line "USB Port 2 Output Control Register" cat "$out" check pxaregs/dump-reg-line sh -c "grep -qE '^UP2OCR +0x[0-9a-f]{8} ([01]{8} ){3}[01]{8}\$' '$out'" # strstr, so ONE argument selects the register AND its four bit-fields -- # five table entries from one prefix. This is the filtering the ticket asked # for, asserted by the subset it selects. check_out pxaregs/dump-field-seos "UP2OCR_SEOS" cat "$out" check_out pxaregs/dump-field-hxoe "UP2OCR_HXOE" cat "$out" check_out pxaregs/dump-field-hxs "UP2OCR_HXS" cat "$out" check_out pxaregs/dump-field-idon "UP2OCR_IDON" cat "$out" check_out pxaregs/dump-field-desc "OTG ID Read Enable" cat "$out" # ...and it selects ONLY those: a bit-field line is printed for an entry # whose shift or mask is non-trivial, and UP2OCR's four are the only ones # whose name contains UP2OCR. check_eq pxaregs/dump-prefix-selects-five "5" "$(grep -c '^UP2OCR' "$out")" # A substring that is not a prefix selects a DIFFERENT subset -- the two # HWUART registers serial.patch added, at two different addresses, so this # also shows a new address starts a new block. timeout 30 pxaregs HW_M > "$out" 2>/dev/null check_out pxaregs/dump-substring-mcr "HWUART Modem Control Register" cat "$out" check_out pxaregs/dump-substring-msr "HWUART Modem Status Register" cat "$out" # THE WRITE PATH, and the shift/mask arithmetic that is the whole of it. # UP2OCR_SEOS is shift 24, mask 7: writing 3 must land as 0x03000000 in the # parent register and read back as 3 in the field. Measured, both halves. timeout 30 pxaregs UP2OCR_SEOS 3 >/dev/null 2>&1 check_rc pxaregs/set-rc 0 sh -c "timeout 30 pxaregs UP2OCR_SEOS 3 >/dev/null 2>&1" timeout 30 pxaregs UP2OCR > "$out" 2>/dev/null check pxaregs/set-lands-shifted sh -c "grep -qE '^UP2OCR +0x03000000' '$out'" check pxaregs/set-field-reads-back sh -c "grep -qE '^UP2OCR_SEOS +3 ' '$out'" else if [ "$stage" = mmapfail ]; then _why="mmap(2) REFUSED on this boot: $(_bst_1line "$probe_err") -- writable MAP_SHARED is unsupported (brintos/linux#30). Every listing printf is downstream of that mapping, so there is no table output to assert; the probe ran pxaregs and read this, it is not hardcoded" else _why="open(2) on /dev/mem failed on this boot, so no mapping was ever attempted: $(_bst_1line "$probe_err") -- probed, not assumed" fi for _c in dump-desc-line dump-reg-line \ dump-field-seos dump-field-hxoe dump-field-hxs dump-field-idon \ dump-field-desc dump-prefix-selects-five \ dump-substring-mcr dump-substring-msr \ set-rc set-lands-shifted set-field-reads-back; do bst_skip "pxaregs/$_c" "$_why" done fi # ------------------------------------------------ the write changes nothing ---- # The ticket asks that a refused write be asserted to change nothing. With the # mapping refused there is no observable register state to compare -- so what is # asserted instead is the only thing that CAN be: the write attempt fails the # same way a read does, at the same rung, having produced no output. Comparing # a "before" and "after" read would be asserting that two identical refusals are # identical, which is not evidence about a register. if [ "$stage" != mapped ]; then timeout 20 pxaregs UP2OCR_SEOS 3 > "$out" 2>"$err"; _wrc=$? check_eq pxaregs/write-refused-rc "1" "$_wrc" check_eq pxaregs/write-refused-no-stdout "" "$(cat "$out")" check_eq pxaregs/write-refused-same-as-read "$probe_err" "$(cat "$err")" else bst_skip pxaregs/write-refused-rc "mmap(2) succeeded on this boot, so the write is not refused -- the set cases above ran for real" bst_skip pxaregs/write-refused-no-stdout "mmap(2) succeeded on this boot" bst_skip pxaregs/write-refused-same-as-read "mmap(2) succeeded on this boot" fi rm -f "$out" "$err" 2>/dev/null