. "$BST_LIB" # BST-TIMEOUT: 120 # 54 cases, ~60 execs, no loops, no corpus and no waits. # # MEASURED: `==BST-SUITE devmem2 elapsed=21s bound=120s # # cases=54==` on this image (2026-08-26), so the bound is # # ~5.7x its own measurement. Every case either decides in # # argv with no syscall, or is refused at mmap(2) on the # # first page it asks for; none of them can block, because # # the only loop in the program (the read/write of a mapped # # cell) is downstream of a mapping this machine does not # # grant. The margin is wide rather than tight on purpose: # # if brintos/linux#30 is fixed, 21 of these cases # # stop skipping and start doing real work, so the bound has # # to cover the suite this file BECOMES, not only the one it # # is while the platform is broken. # devmem2 -- a /dev/mem poker on a machine whose /dev/mem cannot be mapped. # # devmem2 is ONE 160-line C file and ONE binary. It exists to mmap(2) a page of # /dev/mem and read or write a cell inside it, and that is the whole program: # there is no option parser, no library, no configuration, and no second mode. # So the honest question for this port is not "which of its features work" but # "how far down its single path does this machine let it get", and the answer is # measured below rather than assumed. # # WHAT THIS MACHINE ACTUALLY DOES, MEASURED ON TARGET DURING THE PORT. All three # rungs were observed, in this order, and each is what gates the next: # # 1. /dev/mem EXISTS AND OPENS. It is NOT absent, which is the thing a reader # coming to this suite will expect. `crw------- 1 root root 1, 1 /dev/mem` # is present via devtmpfs -- CONFIG_DEVMEM is `default y` in # drivers/char/Kconfig and the wasm32 defconfig does not turn it off, so the # mem chardev is registered and /proc/devices carries `1 mem`. open(2) with # O_RDWR|O_SYNC succeeds. devmem2 therefore prints `/dev/mem opened.` on # STDOUT for every address, and the open-failure rung is UNREACHABLE here. # (/dev/kmem and /dev/port do not exist -- only /dev/mem does.) # # 2. mmap(2) IS REFUSED, AND THAT IS THE PORT'S REAL FAILURE PATH. devmem2 asks # for PROT_READ|PROT_WRITE / MAP_SHARED, and this kernel refuses writable # MAP_SHARED file-backed mappings outright -- it says so in its own voice # and names its own issue: # [k] hwjs-mmap pid=N file-backed mmap REFUSED (writable MAP_SHARED # (write-back) unsupported -- fall back to read()/write()): fd=3 # len=0x1000 prot=0x3 flags=0x1 off=0x0 -> errno 19 # (linux-in-the-browser#72) # THE ISSUE THAT OWNS THIS IS brintos/linux#30 (a writable MAP_SHARED # mapping, file or anonymous, returns ENODEV). The #72 the kernel cites in # its own line is the PROVENANCE OF THE LOUD FAILURE, not an open defect: # #72 was the "file-backed mmap silently returns zero pages" bug, it was # FIXED substrate-side on 2026-07-11 (hardwarejs adee932 + brintos/linux # effe6a1628aa), and that fix is exactly what makes the unsupported cases # -- writable MAP_SHARED among them -- fail loudly with ENODEV instead of # handing back a pointer to wrong data. So this is a STANDING LIMITATION # being reported correctly, not a regression waiting on a fix. # devmem2 reports it through its FATAL macro as # Error at line 90, file devmem2.c (19) [No such device] # on STDERR, exit 1. The refusal is a property of the MAPPING, not of the # address: 0x0 and the one region /proc/iomem actually reports # (virtio-mmio-fs) are refused identically, so there is no address on this # machine that would work. # # 3. THE KERNEL'S OWN SUGGESTED FALLBACK DOES NOT WORK EITHER, which is worth # recording because it is the obvious next thing to try. read(2) on # /dev/mem returns EFAULT (`dd: error reading '/dev/mem': Bad address`, # 0 bytes copied) -- drivers/char/mem.c rejects the range in # valid_phys_addr_range() before it ever copies. So devmem2 has no route to # a byte of physical memory on this target by either mechanism. # # WHAT IS THEREFORE ASSERTED FOR REAL, and it is genuinely most of the program: # # - THE ARGV LAYER, which is the only part decided before any syscall. It is # exactly one decision -- `argc < 2` -- and everything else devmem2 accepts # without inspection, which is itself the finding. The usage block is pinned # line by line, on the right stream, with the right status, and `%s` is # shown to be argv[0] rather than a constant by invoking the same binary # under two names. # # - THE TWO-RUNG LATTICE THIS MACHINE REACHES, AND THE FACT THAT THE RUNGS ARE # DISTINGUISHABLE. `/dev/mem opened.` on stdout and the mmap FATAL on stderr # are one execution, and asserting the two streams SEPARATELY and WHOLE is # what shows execution really got past open(2) and stopped at mmap(2) -- # a binary that trapped on open would produce neither, and one that trapped # on the first ioctl-ish call would produce the first and no diagnostic. # # - THE ORDERING OF THE ARGUMENT CHECKS AGAINST THE SYSCALLS, which is the # least obvious thing about this program and the easiest to get wrong when # reading it. `Illegal data type` lives AFTER the mmap, in the switch on the # mapped pointer -- so on this machine `devmem2 0 z` does NOT report an # illegal type, it reports the mmap refusal. Several cases below assert # exactly that, because a future reader looking at the source will assume # the opposite. # # WHAT IS PROBED AT RUNTIME AND NOT HARDCODED. The read and write paths are the # point of the program and they are unreachable here -- but they are gated on a # PROBE THAT RUNS devmem2 AND READS WHAT IT DID, never on a hardcoded skip. The # probe classifies this boot into one of three states (open refused / mapped / # refused at mmap) and each state runs its own assertions. The moment # linux-in-the-browser#72 is fixed, `mapped` becomes true here and the read, # write, alignment and illegal-type cases run their REAL assertions with no edit # to this file -- and the mmap-refusal cases stop being asserted, so the suite # does not then go red for the platform having been repaired. # # THE GATED EXPECTATIONS ARE MEASURED TOO, not transcribed from the source. They # were read off a native x86-64 build of the same SRCREV # (5b395a946894eb4f4ef5d07c80a50a88573a541e) with ONE character changed -- # open("/dev/mem") -> open("/dev/zero"), which an ordinary user can mmap # MAP_SHARED -- so the field widths, the two-space/one-space split between the # `Read at address ` and `Write at address ` labels, the uppercase hex, and the # exit statuses are observations rather than readings of a format string. The # unmodified binary's own strings are what the ungated cases use. # # TWO UPSTREAM BEHAVIOURS ARE PINNED RATHER THAN PATCHED (devmem2 is a # third-party public repository; see no-public-upstream-reports.md): # - THERE IS NO OPTION PARSING AND NO --version, --help OR -h. argv[1] goes # straight to strtoul(), which consumes what it can and returns 0 otherwise # WITHOUT reporting anything, so `devmem2 --help` and `devmem2 notanumber` # are both simply "address 0". The cases below pin that, so a release that # adds an option parser reddens them rather than passing silently. # - argv[4] AND BEYOND ARE IGNORED. The program reads argv[1..3] only. # # ONE RECIPE-SPECIFIC BEHAVIOUR IS PINNED. meta-oe's recipe compiles with # `CFLAGS += "-DFORCE_STRICT_ALIGNMENT"`, which is the only build-time choice in # the recipe and is invisible in the source unless you go looking for the # #ifdef. It makes fixup_addr() round the target DOWN to its access size, so an # unaligned address reads the aligned cell while the diagnostic still echoes the # address the user asked for. That divergence between the echoed target and the # pointer actually dereferenced is asserted (gated), because it is the one thing # a rebuild without the flag would change. require devmem2 devmem2/present W=${TMPDIR:-/tmp}/bst-devmem2.$$ out=$W.out; err=$W.err : > "$out"; : > "$err" TAB=$(printf '\t') # --------------------------------------------------------------- identity ---- check_out devmem2/path "/usr/bin/devmem2" command -v devmem2 # One binary, no scripts, no library, no header: -dev and -dbg are both produced # EMPTY by this recipe, so ${PN} is the whole package and /usr/bin/devmem2 is # the whole of ${PN}. check devmem2/is-file test -f /usr/bin/devmem2 check devmem2/is-executable test -x /usr/bin/devmem2 # ------------------------------------------------------- the device node ------ # Read the live system rather than asserting a constant. These are not devmem2's # behaviour -- they are the PRECONDITION its behaviour is a consequence of, and # recording them is what makes the mmap refusal below attributable to the # mapping rather than to a missing device. memnode=0; [ -c /dev/mem ] && memnode=1 memdev=0; grep -q '^ *1 mem$' /proc/devices 2>/dev/null && memdev=1 if [ "$memnode" = 1 ]; then check devmem2/devmem-is-chardev test -c /dev/mem # major 1 minor 1 is the mem chardev; anything else is a different device # wearing the name, which would make every case below mean something else. check devmem2/devmem-major-minor sh -c "ls -l /dev/mem | grep -qE ' 1, *1 '" else bst_skip devmem2/devmem-is-chardev "no /dev/mem node on this image: the probe below reports what devmem2 does instead" bst_skip devmem2/devmem-major-minor "no /dev/mem node on this image" fi if [ "$memdev" = 1 ]; then check_out devmem2/proc-devices-mem "1 mem" sh -c "grep -w mem /proc/devices" else bst_skip devmem2/proc-devices-mem "this kernel registers no 'mem' chardev in /proc/devices" fi # ------------------------------------------- RUNG 1: argv, before any syscall -- # `argc < 2` is the program's ONLY pre-syscall decision. Usage on STDERR, # exit 1, and -- asserted separately because a combined-output check cannot see # it -- NOTHING on stdout. check_rc devmem2/noargs-rc 1 sh -c "devmem2 > '$out' 2>'$err'" check_eq devmem2/noargs-no-stdout "" "$(cat "$out")" check_out devmem2/noargs-usage-label "Usage:" cat "$err" check_out devmem2/noargs-usage-form "{ address } [ type [ data ] ]" cat "$err" check_out devmem2/noargs-usage-address "${TAB}address : memory address to act upon" cat "$err" check_out devmem2/noargs-usage-type "${TAB}type : access operation type : [b]yte, [h]alfword, [w]ord, [l]ong" cat "$err" check_out devmem2/noargs-usage-data "${TAB}data : data to be written" cat "$err" # The usage line interpolates argv[0] with %s. Invoking the SAME binary under # two ABSOLUTE names is what shows that, and distinguishes it from a baked-in # string. Absolute both times on purpose: argv[0] for a PATH lookup is a # property of the SHELL (busybox ash passes the bare name, dash passes the # resolved path), so asserting it would pin the runner's shell rather than # devmem2's formatting. alias2=${TMPDIR:-/tmp}/bst-devmem2-alias.$$ /usr/bin/devmem2 2>"$err" 1>/dev/null check_out devmem2/noargs-argv0-abs "Usage:${TAB}/usr/bin/devmem2 {" cat "$err" if cp /usr/bin/devmem2 "$alias2" 2>/dev/null && chmod +x "$alias2" 2>/dev/null; then "$alias2" 2>"$err" 1>/dev/null check_out devmem2/noargs-argv0-varies "Usage:${TAB}$alias2 {" cat "$err" rm -f "$alias2" 2>/dev/null else bst_skip devmem2/noargs-argv0-varies "could not copy the binary to $alias2, so it cannot be invoked under a second name" fi # ------------------------------------------------------------------ probe ---- # Run devmem2 once and READ WHAT IT DID. Nothing below is hardcoded: the three # states are set only from this binary's own output on this boot. devmem2 0 > "$out" 2>"$err" opened=0; mapped=0 grep -q '^/dev/mem opened\.$' "$out" 2>/dev/null && opened=1 grep -q '^Memory mapped at address ' "$out" 2>/dev/null && mapped=1 probe_out=$(cat "$out" 2>/dev/null) probe_err=$(cat "$err" 2>/dev/null) # Collapse the two readings into ONE state, because they are a CHAIN and not two # independent facts: a failed open(2) means mmap(2) was never ATTEMPTED, which is # not the same thing as mmap having been REFUSED. Keeping them as separate # booleans makes `not mapped` mean both, and the mmap-refusal cases then assert a # diagnostic that a machine with no /dev/mem never produced -- six false reds, in # exactly the "absent device" configuration this suite is supposed to handle. # (Caught by dry-running this file against a native build on a host whose # /dev/mem refuses EACCES; on target the open succeeds and it would not have # shown up.) if [ "$mapped" = 1 ]; then stage=mapped elif [ "$opened" = 1 ]; then stage=mmapfail else stage=openfail fi # ------------------------------------------- RUNG 2: open(2), and its two ways -- if [ "$opened" = 1 ]; then # THE OPEN SUCCEEDED. This is the state this machine is actually in, and # it is the opposite of what "no usable memory device" suggests. check_rc devmem2/open-ok-stdout-exact 0 sh -c "devmem2 0 2>/dev/null | grep -qx '/dev/mem opened.'" # ...and it is on STDOUT, not stderr. Asserted as its own case because the # FATAL that follows is on stderr and a combined check cannot tell them apart. check_eq devmem2/open-ok-not-on-stderr "" "$(devmem2 0 2>&1 1>/dev/null | grep '/dev/mem opened' || true)" bst_skip devmem2/open-fail-fatal "this machine's /dev/mem opens (O_RDWR|O_SYNC succeeded), so the open-failure rung is unreachable -- see the mmap cases" bst_skip devmem2/open-fail-line "this machine's /dev/mem opens, so line 84 is unreachable" bst_skip devmem2/open-fail-no-stdout "this machine's /dev/mem opens, so the pre-open silence cannot be observed" else # THE OPEN FAILED. devmem2's FATAL macro reports the line of the open -- # line 84 -- with errno and strerror, on stderr, exit 1. Nothing reaches # stdout because the `/dev/mem opened.` printf is downstream of it. check_rc devmem2/open-fail-fatal 1 sh -c "devmem2 0 > '$out' 2>'$err'" check_out devmem2/open-fail-line "Error at line 84, file devmem2.c" cat "$err" check_eq devmem2/open-fail-no-stdout "" "$(devmem2 0 2>/dev/null)" bst_skip devmem2/open-ok-stdout-exact "/dev/mem did not open on this boot: $(_bst_1line "$probe_err")" bst_skip devmem2/open-ok-not-on-stderr "/dev/mem did not open on this boot" fi # ------------------------------------------ RUNG 3: mmap(2) -- the real gate --- if [ "$stage" != mmapfail ]; then # EITHER the mapping was granted (so there is no refusal to assert), OR the # open never succeeded (so mmap was never attempted and there is no refusal # to assert either). Both are skips, with DIFFERENT reasons -- a reader has # to be able to tell "the platform was repaired" from "there is no device". if [ "$stage" = mapped ]; then _mwhy="mmap(2) SUCCEEDED on this boot -- brintos/linux#30 appears fixed; the read/write cases below are running for real" else _mwhy="open(2) on /dev/mem failed, so mmap(2) was never ATTEMPTED and there is no refusal to assert: $(_bst_1line "$probe_err")" fi for _c in mmap-refused-rc mmap-refused-line mmap-refused-errno \ mmap-refused-stream mmap-refused-iomem \ mmap-preempts-type mmap-preempts-type-rc mmap-preempts-write; do bst_skip "devmem2/$_c" "$_mwhy" done else # THE REFUSAL, WHICH IS THIS PORT'S DOCUMENTED FAILURE PATH. Exit 1, the # FATAL line number is the mmap's (90, not the open's 84), and the errno is # the kernel's own -- currently 19/ENODEV from the writable-MAP_SHARED # refusal (linux-in-the-browser#72). The line number is asserted separately # from the errno so that a platform which starts refusing for a DIFFERENT # reason reddens the errno case alone and stays legible. check_rc devmem2/mmap-refused-rc 1 sh -c "devmem2 0 > '$out' 2>'$err'" check_out devmem2/mmap-refused-line "Error at line 90, file devmem2.c" cat "$err" check_out devmem2/mmap-refused-errno "(19) [No such device]" cat "$err" # The stream split, whole and separate: stdout carries the open notice and # ONLY that, stderr carries the FATAL and ONLY that. This pair is what shows # execution really crossed open(2) and stopped at mmap(2). check_eq devmem2/mmap-refused-stream "/dev/mem opened." "$(devmem2 0 2>/dev/null)" # The refusal is about the MAPPING, not the ADDRESS. Drive it at the one # physical region this machine actually reports, read out of /proc/iomem at # runtime rather than typed in, and assert it is refused identically. If # /proc/iomem reports nothing, say so instead of inventing an address. iomem_addr=$(sed -n 's/^ *\([0-9a-fA-F]\{1,\}\)-.*/\1/p' /proc/iomem 2>/dev/null | head -1) if [ -n "$iomem_addr" ]; then devmem2 "0x$iomem_addr" 2>"$err" 1>/dev/null check_out devmem2/mmap-refused-iomem "Error at line 90, file devmem2.c" cat "$err" else bst_skip devmem2/mmap-refused-iomem "/proc/iomem reports no region on this machine, so there is no real physical address to contrast address 0 against" fi # THE ORDERING PROOF, and the least obvious thing in this program. The # `Illegal data type` check lives AFTER the mmap, in the switch on the # mapped pointer -- so an illegal type is NOT reported here. Reading the # source invites the opposite conclusion, which is why this is asserted # rather than remarked on. devmem2 0 z 2>"$err" 1>/dev/null check_out devmem2/mmap-preempts-type "Error at line 90, file devmem2.c" cat "$err" check_eq devmem2/mmap-preempts-type-rc "1" "$(devmem2 0 z >/dev/null 2>&1; echo $?)" # Same for the write path: argv[3] is never looked at, because the switch # it feeds is downstream of the mapping too. devmem2 0 w 0xdeadbeef 2>"$err" 1>/dev/null check_out devmem2/mmap-preempts-write "Error at line 90, file devmem2.c" cat "$err" fi # ------------------------------------------------ argv acceptance, measured ---- # devmem2 validates NOTHING about argv[1]: strtoul consumes what it can and # returns 0 otherwise, silently. These cases pin that by showing the three # spellings land in the SAME state as address 0 -- whatever that state is on # this machine, which is why they compare against the probe rather than against # a constant. check_eq devmem2/argv-garbage-same-as-zero "$probe_err" "$(devmem2 notanumber 2>&1 1>/dev/null)" check_eq devmem2/argv-dashdash-help-same "$probe_err" "$(devmem2 --help 2>&1 1>/dev/null)" check_eq devmem2/argv-dash-V-same "$probe_err" "$(devmem2 -V 2>&1 1>/dev/null)" # ...and they reach the same stdout, so it is genuinely the same execution path # and not two different failures that happen to share a message. _nop() { sed 's/0x[0-9a-f][0-9a-f]*/P/g'; } # mmap's %p differs per exec (ASLR) check_eq devmem2/argv-garbage-same-stdout \ "$(printf '%s\n' "$probe_out" | _nop)" "$(devmem2 notanumber 2>/dev/null | _nop)" # argv[4] and beyond are ignored -- the program reads argv[1..3] only. check_eq devmem2/argv-extra-ignored "$probe_err" "$(devmem2 0 w 0x1 EXTRA IGNORED 2>&1 1>/dev/null)" # There is no version option and no help option ANYWHERE in the package: no # -DVERSION in the recipe, no version constant in the source, and no option # parser to carry one. The two cases above ARE that proof -- `--help` and `-V` # reaching byte-identical output to a bare address is what an absent parser # looks like from outside, and it is a stronger statement than the presence or # absence of a string in the binary. # ------------------------------------------------------------ mapped-gated ---- # The point of the program. These run for real the moment mmap(2) is granted, # and skip on a probe that measured its refusal -- never on an assumption. # Every expectation here was measured on a native x86-64 build of the same # SRCREV with open("/dev/mem") -> open("/dev/zero"), which is mmap-able by an # ordinary user; the field widths and label spacing are observations. if [ "$mapped" = 1 ]; then # The default access type is 'w' (word): 8 uppercase hex digits, and note # the TWO spaces in `Read at address ` against the one in `Write at `. check_out devmem2/read-default-label "Read at address 0x00000000" sh -c "devmem2 0 2>/dev/null" check_out devmem2/read-mapped-notice "Memory mapped at address 0x" sh -c "devmem2 0 2>/dev/null" check_rc devmem2/read-rc 0 sh -c "devmem2 0 >/dev/null 2>&1" # The four access types differ ONLY in the field width, which is 2*size: # byte 2, halfword 4, word 8, long 16. That arithmetic is the whole of the # type system, so asserting the widths is asserting the types. check devmem2/read-byte-width sh -c "devmem2 0 b 2>/dev/null | tail -1 | grep -qE '\\): 0x[0-9A-F]{2}$'" check devmem2/read-half-width sh -c "devmem2 0 h 2>/dev/null | tail -1 | grep -qE '\\): 0x[0-9A-F]{4}$'" check devmem2/read-word-width sh -c "devmem2 0 w 2>/dev/null | tail -1 | grep -qE '\\): 0x[0-9A-F]{8}$'" check devmem2/read-long-width sh -c "devmem2 0 l 2>/dev/null | tail -1 | grep -qE '\\): 0x[0-9A-F]{16}$'" # tolower() on argv[2][0], so an uppercase type letter is the same type. check_eq devmem2/read-type-case-insensitive \ "$(devmem2 0 w 2>/dev/null | tail -1 | sed 's/(0x[0-9a-f]*)/(P)/')" \ "$(devmem2 0 W 2>/dev/null | tail -1 | sed 's/(0x[0-9a-f]*)/(P)/')" # An illegal type is reported AFTER the mapping, on stderr, exit 2 -- the # only exit status in the program that is not 0 or 1. check_rc devmem2/type-illegal-rc 2 sh -c "devmem2 0 z >/dev/null 2>'$err'" check_out devmem2/type-illegal-msg "Illegal data type 'z'." cat "$err" # ...and only argv[2][0] is looked at, so a longer word takes its first char. devmem2 0 zebra 2>"$err" 1>/dev/null check_out devmem2/type-illegal-firstchar "Illegal data type 'z'." cat "$err" # An EMPTY type argument takes argv[2][0] = NUL, which tolower() passes # through and %c prints as a NUL byte -- a genuine boundary, adjacent to the # case above and taking the other branch of nothing but string length. check_rc devmem2/type-empty-rc 2 sh -c "devmem2 0 '' >/dev/null 2>&1" # The write path: argv[3] through strtoul, then a readback from the same # cell, on one line whose label has ONE space where the read's has two. check_out devmem2/write-label "Write at address 0x" sh -c "devmem2 0 w 0x1 2>/dev/null" check_out devmem2/write-readback "readback 0x" sh -c "devmem2 0 w 0x1 2>/dev/null" check_rc devmem2/write-rc 0 sh -c "devmem2 0 w 0x1 >/dev/null 2>&1" # A write also prints the read that preceded it -- both lines, in order. check devmem2/write-emits-both sh -c "devmem2 0 w 0x1 2>/dev/null | grep -c '^\\(Read\\|Write\\) at address' | grep -qx 2" # FORCE_STRICT_ALIGNMENT (the recipe's only build-time choice): fixup_addr # rounds the dereferenced pointer DOWN to the access size, while the # diagnostic still echoes the address the user asked for. So a word read at # 0x13 echoes 0x00000013 and dereferences a pointer ending in 0. Rebuilt # without the flag, the pointer would end in 3 and this case is what says so. check_out devmem2/align-echoes-request "Read at address 0x00000013" sh -c "devmem2 0x13 w 2>/dev/null" check devmem2/align-rounds-pointer sh -c "devmem2 0x13 w 2>/dev/null | tail -1 | grep -qE '\\(0x[0-9a-f]*0\\):'" # strtoul's base detection: 0x10, 020 and 16 are the same address, and the # echo is what proves the argument was parsed rather than ignored. check_out devmem2/base-hex "0x00000010" sh -c "devmem2 0x10 w 2>/dev/null | tail -1" check_out devmem2/base-octal "0x00000010" sh -c "devmem2 020 w 2>/dev/null | tail -1" check_out devmem2/base-dec "0x00000010" sh -c "devmem2 16 w 2>/dev/null | tail -1" else if [ "$stage" = mmapfail ]; then _why="mmap(2) REFUSED on this boot: $(_bst_1line "$probe_err") -- writable MAP_SHARED file-backed mmap is unsupported (brintos/linux#30); the probe ran devmem2 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 read-default-label read-mapped-notice read-rc \ read-byte-width read-half-width read-word-width read-long-width \ read-type-case-insensitive \ type-illegal-rc type-illegal-msg type-illegal-firstchar type-empty-rc \ write-label write-readback write-rc write-emits-both \ align-echoes-request align-rounds-pointer \ base-hex base-octal base-dec; do bst_skip "devmem2/$_c" "$_why" done fi rm -f "$out" "$err" 2>/dev/null