# BST-TIMEOUT: 300 # MEASURED: see the ==BST-SUITE== line on any run. The # # scoring forks almost nothing -- every case below is a # # shell string compare against values awk lifted from ONE # # probe run -- so the cost is the single exec of the probe. # # 300 is the bound the ticket gates and the nightly pass. . "$BST_LIB" # libnvme 1.8 -- the NVMe management library nvme-cli is built on. It carries # the spec's structure and enumeration definitions, the helpers that construct # and decode admin and I/O commands, the status and error-string tables, a # sysfs topology scanner, a JSON configuration reader/writer over json-c, and # the ioctl transport that carries all of it to the kernel. What ships under # --disable-shared is /usr/lib/libnvme.a and /usr/lib/libnvme-mi.a, thirteen # headers, and two .pc files. # # --------------------------------------------------------------------------- # NOTHING THAT SHIPS CAN DRIVE A LINE OF IT, WHICH IS WHY THERE IS A PROBE. # # nvme-cli is a SEPARATE recipe and is not in this image. The five example # programs upstream builds are discarded by `meson install`, each takes its # arguments on argv, and each opens a device or a socket in its first lines. # So on an image with no compiler there is no consumer at all, and "the files # are present" would have been the whole of what a self-test could say. The # probe is built IN THE RECIPE against the archive that ships, so it is # relinked every build and cannot go stale against a library that moved under # it. # # --------------------------------------------------------------------------- # THE PROBE DEFINES ioctl() ITSELF, AND THAT IS THE WHOLE REASON THE COMMAND # CONSTRUCTION IS MEASURABLE HERE. # # libnvme is not a message composer like libnetfilter_cttimeout next door: it # OWNS the transport, and what nvme-cli actually depends on is the cdw layout # libnvme builds for each command. Testing that normally needs a controller. # It does not need one here, because the probe defines ioctl() in its own # translation unit -- so on a --disable-shared link the linker resolves # libnvme's calls there and never pulls libc's ioctl.o, and every command the # library composes is captured before it would have reached a device. # # THIS IS UPSTREAM'S OWN TECHNIQUE, reproduced the way a static image can. # libnvme ships twenty self-driving tests that all pass with no NVMe hardware # (verified on the native control build), and test/ioctl/mock.c is exactly this # mock -- but upstream injects it with LD_PRELOAD, which needs a runtime loader # this image does not have. That is why the recipe passes -Dtests=false: those # tests could not be installed and could not be run. The technique survives; # the harness does not. # # --------------------------------------------------------------------------- # THE ORACLE, AND WHY A TARGET-ONLY RUN COULD NOT SUPPLY IT. # # Every value below is pinned from a native x86-64 build of this same 1.8 # source against json-c 0.17, both --disable-shared. NVMe structures are DMA # layouts the specification fixes IN BYTES -- 4096 for an identify page, 512 # for a SMART log, 64 for an error log entry -- and every scalar on them is # little-endian by spec on every host. So the two architectures MUST agree # exactly and a disagreement is a real defect, never an expected difference. # # That is what a target-only run cannot give. Decoding a page the library # itself encoded would prove only that its writer and its reader agree: a port # that packed struct nvme_id_ctrl differently would round-trip through its own # mistake and report every field correct. So the F and L groups decode BYTE # FIXTURES poked at the spec's own offsets, not structures the library filled, # and the probe reads their scalars with its own little-endian accessors -- # libnvme installs none, because le16_to_cpu and friends live in ccan/, a # build-time subtree `meson install` does not ship. # # The Z group is scored FIRST because a disagreement there explains every # value mismatch after it. It also pins the four NVME_IOCTL_* request codes, # which _IOWR ENCODES the passthru struct's size into -- so the kernel makes # the same assertion the census does, and a port that changed that size would # be answered ENOTTY by a real device rather than silently misread. # # --------------------------------------------------------------------------- # THE EXPECTED RED: THE E GROUP, ONE CAUSE, LEFT RED ON PURPOSE. # # The ticket asks that enumerating a real controller be attempted once. # nvme_scan() walks /sys/class/nvme-subsystem, which a guest with no NVMe # device does not have, so E05 cannot succeed here. It is reported FAILING and # NOT skipped, so it flips green by itself the day the platform gains a device # -- that is the regression signal this group is bought for. E01-E03 are the # same finding decomposed (does the sysfs directory exist, does /dev/nvme0), # so a red names which prerequisite is missing rather than only that the scan # came back empty. # # E04 IS THE CONTROL AND IS SCORED STRICTLY. nvme_scan() must still return a # root object on a machine with no device -- it is a topology scan, not a # device open. Green E04 with red E05 says "there is no controller to find"; # both red would say "the scan itself failed", which is a different fault in a # different place and would make the attribution wrong. # # --------------------------------------------------------------------------- # FIVE UPSTREAM BEHAVIOURS PINNED RATHER THAN FIXED. # # All third-party behaviour in a package we port, not brintOS defects and not # something this port changes. Per the project's no-public-upstream-reports # rule nothing is filed for them; they are pinned here and written up in the # test plan, so a change in any of them is a deliberate change of subject # rather than a silent one. The native control answers identically to the # target on every one, which is what makes them upstream's and not wasm32's. # # * nvme_id_ns_flbas_to_lbaf_inuse() DECODES THE WRONG LBA FORMAT INDEX. # FLBAS carries the index in bits 3:0 and its two HIGH bits in 6:5, so the # value is (higher << 4) | lower and runs 0..63. libnvme composes # (higher >> 1) | lower. F40 pins the obvious half -- format 59 answers 11 # -- and F42 pins the worse half: for an odd `higher` the stray bit lands # in bit 0 of the LOW nibble, so format 42 also answers 11. Two different # namespaces decode to the same legal-looking index. F43 is the same input # decoded correctly by hand, so the delta is stated rather than implied. # * nvme_status_to_string() ON A NEGATIVE STATUS RETURNS strerror(errno) -- # the caller's CURRENT errno, which has nothing to do with the value handed # in. S22/S23 pin it with errno set deliberately, because otherwise the # answer is whatever the previous call left behind and "Success" comes back # for -1. # * nvme_get_feature_length() CANNOT DISTINGUISH "no payload" FROM "I have # not been taught this feature". V19/V1a pin two feature ids the enum # itself defines -- IOCS Profile and FDP -- being refused by the same # default arm as the entirely unknown 0xf0 in V18. # * THE 32-BIT .reftag FIELD IS DEAD AT THE CURRENT ABI. nvme_io() selects # its tag path by comparing args_size against two struct offsets: at the # current size it reads .reftag_u64 through pif/sts and IGNORES .reftag # entirely, and only a caller declaring the OLD size gets .reftag honoured. # C15 and C17 are the same call differing only in args_size, and they # compose different cdw14s. Either one alone reads like a bug. # * libnvme.pc CARRIES A PUBLIC `Requires: json-c, openssl` on the static # arm, for two dependencies no installed header exposes. That is what meson # emits for a default_library=static build -- a static consumer really does # need both at link time -- so it is over-broad rather than wrong. Converted, # meson moves the same list to `Requires.private:` and writes no # `Requires:` line. The WHOLE field is # pinned on either arm, so a future recipe that tightened it, or that # dropped a dependency, shows up as one named red. # # --------------------------------------------------------------------------- # ONE ASSERTION THAT IS ALSO THE ABI CHECK. C18 does not pin the two args_size # bounds as numbers, because it must not: they are offsets inside a struct that # CONTAINS POINTERS, so both shrink on this 32-bit target while sizeof(args) # shrinks with them. What has to hold on both arms is the RELATION -- v1 < v2, # sizeof == v2, and the same 16-byte gap between them -- and that is what is # scored. It is the one place in this library where a 32-bit target could # genuinely take a different branch from the oracle. R=${BST_LIBNVME_ROOT:-} PROBE=${BST_LIBNVME_PROBE:-$R/usr/libexec/libnvme-tests/libnvme-probe} ARCHIVE=$R/usr/lib/libnvme.a MIARCHIVE=$R/usr/lib/libnvme-mi.a PCFILE=$R/usr/lib/pkgconfig/libnvme.pc MIPCFILE=$R/usr/lib/pkgconfig/libnvme-mi.pc INC=$R/usr/include # Asserted against the .pc rather than assumed: a recipe bumped without # regenerating the pinned values below must go red on ONE named case instead of # silently changing the subject of the other two hundred. WANT_VERSION=1.8 if [ ! -f "$ARCHIVE" ]; then bst_skip libnvme/present "libnvme not installed on this image" exit 0 fi bst_ok libnvme/present check libnvme/staticlib test -s "$ARCHIVE" check libnvme/staticlib-mi test -s "$MIARCHIVE" check libnvme/pkgconfig test -s "$PCFILE" check libnvme/pkgconfig-mi test -s "$MIPCFILE" check libnvme/hdr-main test -s "$INC/libnvme.h" check libnvme/hdr-mi test -s "$INC/libnvme-mi.h" check libnvme/hdr-types test -s "$INC/nvme/types.h" check libnvme/hdr-ioctl test -s "$INC/nvme/ioctl.h" check libnvme/hdr-tree test -s "$INC/nvme/tree.h" check libnvme/hdr-util test -s "$INC/nvme/util.h" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape libnvme/no-shared "$R/usr/lib/libnvme.so" "$R/usr/lib/libnvme.so.1" "$R/usr/lib/libnvme-mi.so" "$R/usr/lib/libnvme-mi.so.1" pcver=$(sed -n 's/^Version: *//p' "$PCFILE" 2>/dev/null | head -1) check_eq libnvme/pkgconfig-version "$WANT_VERSION" "$pcver" mipcver=$(sed -n 's/^Version: *//p' "$MIPCFILE" 2>/dev/null | head -1) check_eq libnvme/pkgconfig-version-mi "$WANT_VERSION" "$mipcver" check_out libnvme/pkgconfig-libs "-lnvme" cat "$PCFILE" # See the header comment: a PUBLIC Requires: for dependencies no installed # header exposes, beside a sibling .pc that correctly declares none. # # BOTH ENTRIES ARE PINNED IN ONE CASE ON PURPOSE, and openssl joined the field # under BRINTOS-1517 rather than appearing on its own: the ORDER meson emits is # the order the dependencies are declared in src/meson.build, so splitting this # into two greps would drop the one property a whole-field compare has -- that # a recipe which reordered, duplicated or silently lost a dependency is a red # here rather than two greps that both still match. The case was named # `-jsonc` when json-c was the only entry; the assertion never was. # # The field is read from the arm that shipped: converted, meson writes the same # string to `Requires.private:` and no `Requires:` line at all, and pkg-config # still resolves it for the --static question the distro asks on every # consumer's behalf (conf/distro/brintos.conf, brintos-static-pkgconfig.bbclass). if bst_ps_converted "$R/usr/lib/libnvme.so" "$R/usr/lib/libnvme.so.1*"; then pcreq=$(sed -n 's/^Requires\.private: *//p' "$PCFILE" 2>/dev/null | head -1) else pcreq=$(sed -n 's/^Requires: *//p' "$PCFILE" 2>/dev/null | head -1) fi check_eq libnvme/pkgconfig-requires "json-c >= 0.13, openssl >= 1.1.0" "$pcreq" mipcreq=$(sed -n 's/^Requires: *//p' "$MIPCFILE" 2>/dev/null | head -1) check_eq libnvme/pkgconfig-requires-mi-empty "" "$mipcreq" # --------------------------------------------------------------------------- # The symbol census is a SAMPLE and says so. libnvme.map exports 298 names, far # too many to enumerate usefully, so the forty-two below are one per subsystem # the probe actually drives. It is NOT the exhaustive census the smaller # sibling libraries can afford. # # EVERY NAME HERE IS ONE libnvme.map ACTUALLY EXPORTS, and getting that wrong # is worth recording because the mistake is invisible. The names a consumer # WRITES are mostly not exported: nvme_identify_ctrl, nvme_identify_ns, # nvme_identify_active_ns_list, nvme_get_log_smart, nvme_get_log_error, # nvme_get_log_fw_slot, nvme_read and nvme_write are all `static inline` # wrappers in ioctl.h over the four generic entry points that ARE exported -- # nvme_identify, nvme_get_log_page, nvme_submit_admin_passthru and nvme_io. # Grepping a binary archive for an inline name is not a symbol test at all: # when this suite first named those eight, two matched anyway out of debug # info and four did not, so it reported a difference that said nothing about # either. The evidence that the inline layer works is the C group, which # drives all eight through to a fully composed command. # grep -a: the archive is binary and some greps refuse it without it. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi for sym in nvme_identify nvme_get_log_page nvme_io \ nvme_submit_admin_passthru nvme_get_features nvme_set_features \ nvme_get_features_num_queues nvme_set_features_power_mgmt \ nvme_format_nvm nvme_admin_passthru nvme_admin_passthru64 \ nvme_get_nsid \ nvme_status_to_string nvme_status_to_errno nvme_errno_to_string \ nvme_get_feature_length nvme_get_feature_length2 \ nvme_get_directive_receive_length \ nvme_init_ctrl_list nvme_init_dsm_range nvme_init_copy_range \ nvme_uuid_from_string nvme_uuid_to_string nvme_uuid_random \ nvme_uuid_find \ nvme_create_root nvme_free_tree nvme_scan nvme_read_config \ nvme_update_config nvme_dump_config nvme_root_set_application \ nvme_lookup_host nvme_lookup_subsystem nvme_lookup_ctrl \ nvme_first_host nvme_subsystem_first_ctrl nvme_ctrl_get_transport \ nvmf_hostnqn_generate nvmf_hostnqn_from_file nvmf_hostid_from_file \ nvme_get_version; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libnvme/sym-$sym" else bst_notok "libnvme/sym-$sym" "$sym absent from $ARCHIVE" fi done # json-c is a real DEPENDS and the J group is the whole of what consumes it. A # recipe that lost it would compile src/nvme/no-json.c instead, every J case # would answer -ENOTSUP, and the archive would carry no json_object_* reference # at all -- which is what this case notices directly, so "json-c went missing" # is distinguishable from "the config round trip broke". if $_grepq "json_object_new_object" "$ARCHIVE" 2>/dev/null; then bst_ok libnvme/jsonc-linked else bst_notok libnvme/jsonc-linked "no json_object_* reference in $ARCHIVE -- the recipe compiled no-json.c, so every JSON helper returns -ENOTSUP" fi # openssl is a real DEPENDS too (BRINTOS-1517) and CONFIG_OPENSSL is what # decides WHICH ARM OF src/nvme/linux.c GETS COMPILED, so this pair is the # on-target proof that the recipe's -Dopenssl=enabled actually took. It is # asserted on the archive rather than inferred from a build log because the # build log is not on the image and the archive is the thing that shipped. # # TWO CASES, IN OPPOSITE DIRECTIONS, because either alone is satisfiable by a # library built the wrong way. A grep for an OpenSSL symbol says the crypto arm # is present; it does NOT say the stub is gone, and the stub's refusal message # is a string constant that would still be sitting in the archive if both arms # had somehow been compiled. A grep for the absent refusal says the stub is # gone; on its own that is equally consistent with a library that lost # nvme_gen_dhchap_key altogether. Together they pin the branch. # # EVP_MAC_fetch is the name to grep for and it is api-version specific: openssl # in this image is 3.5.7, so meson sets CONFIG_OPENSSL_3 and linux.c takes the # EVP_MAC/OSSL_PARAM path. A future image on an openssl 1.x would set # CONFIG_OPENSSL_1, take the HMAC_CTX path, and go red here CORRECTLY -- the # pinned key in 99-nvme-cli.test's A08 is the same value on both arms, so # nothing else would notice the change of implementation. if $_grepq "EVP_MAC_fetch" "$ARCHIVE" 2>/dev/null; then bst_ok libnvme/openssl-linked else bst_notok libnvme/openssl-linked "no EVP_MAC_fetch reference in $ARCHIVE -- CONFIG_OPENSSL is not set, so linux.c compiled its stub arm and every key transformation answers a refusal" fi if $_grepq "HMAC transformation not supported" "$ARCHIVE" 2>/dev/null; then bst_notok libnvme/no-openssl-stub "the stub arm's refusal message is still in $ARCHIVE -- linux.c was compiled without CONFIG_OPENSSL" else bst_ok libnvme/no-openssl-stub fi # --------------------------------------------------------------------------- # The probe. Both it and the archive are this ticket's deliverable, so a skip # on either is a broken port and not a legitimate absence -- but an exec the # shell could not stage at all is platform noise, and bst_unrun tells those two # apart. if [ ! -x "$PROBE" ]; then bst_skip libnvme/probe-runs "$PROBE absent from the image" exit 0 fi tmp=${TMPDIR:-/tmp} pout=$tmp/bst-libnvme-out.$$ "$PROBE" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok libnvme/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun libnvme/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" rm -f "$pout" exit 0 else bst_notok libnvme/probe-runs "probe exited $prc: $(_bst_1line "$(head -c 300 "$pout")")" fi # A probe killed part-way still emits usable lines, so the run is scored either # way -- but PROBE-END is what separates "these values are the whole story" # from "the rest is missing", and want() reports an absent value as its own red # rather than as a mismatch. It matters here because the V5x group drives the # library's one VLA and the E group opens the sysfs tree, either of which would # take PROBE-END with it while leaving every earlier group scored. if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libnvme/probe-complete else bst_notok libnvme/probe-complete "probe output has no PROBE-END terminator (last line: $(tail -1 "$pout" 2>/dev/null))" fi # Not a scored value -- the JSON nvme_dump_config() wrote to the probe's own # stdout. Asserted as a marker pair rather than line by line, because the # renderer's exact whitespace is not this port's subject; what matters is that # it emitted an object at all, which no-json.c could not. if sed -n '/^JSONDUMP-BEGIN$/,/^JSONDUMP-END$/p' "$pout" 2>/dev/null | grep -q '"hostnqn"'; then bst_ok libnvme/json-dump-renders else bst_notok libnvme/json-dump-renders "nvme_dump_config() wrote no hostnqn between the JSONDUMP markers" fi # One awk pass lifts every case line into a V_ shell variable, so the two # hundred comparisons below fork nothing. First occurrence wins, which is also # what makes the library's JSON dump harmless: its lines carry no leading id # and are never lifted. eval "$(awk ' function q(v) { gsub(/'"'"'/, "'"'"'\\'"'"''"'"'", v); return "'"'"'" v "'"'"'" } $1 ~ /^[CEFGJLSUVZ][0-9][0-9a-z]+$/ && seen[$1]++ == 0 { id = $1; v = $0; sub(/^[^ ]+ ?/, "", v); print "V_" id "=" q(v) } ' "$pout")" # want : the strict comparison. An absent # value is its own message -- "the probe stopped before here" and "the probe # answered differently" are different findings and a bare mismatch would hide # the first behind an empty got. want() { eval "_got=\${V_$1-}" if [ -z "$_got" ]; then bst_notok "libnvme/$2" "no value on target (probe stopped before $1); want='$3'" elif [ "$_got" = "$3" ]; then bst_ok "libnvme/$2" else bst_notok "libnvme/$2" "want:'$3' got:'$_got'" fi } # The JSON config round trip has its own attribution, and it carries the # MEASUREMENT rather than a claim: J12a is the inode size the filesystem # reports, J12b is how many bytes a read() of that same inode actually # returned, J12c is the head of what came back, and J13a is the errno # nvme_read_config() ended on. Those four are diagnostics, not scored cases -- # they exist so a red here says WHICH of "the file was never written", "the # file reads back empty" and "the file reads back unparseable" happened, # instead of leaving a reader with a bare -1. LOCALE_HELD="held by brintos/glibc#8 (https://brintos.io/brintos/glibc/issues/8): newlocale(LC_NUMERIC_MASK,\"C\",) fails EINVAL on this arm, so json_tokener_parse_ex() returns NULL before reading a byte and json-c cannot parse any JSON on this image -- uselocale ${V_G01-?}, duplocale ${V_G02-?}, newlocale(NUMERIC,dup) ${V_G03-?}, newlocale(ALL,NULL) ${V_G04-?}, newlocale(NUMERIC,NULL) ${V_G11-?}, newlocale(ALL,dup) ${V_G12-?}, setlocale '${V_G05-?}'; with a real thread locale installed ${V_G10-?}" # want_g : strict, but a red carries the # locale-chain measurement, which is what names the mechanism. want_g() { eval "_got=\${V_$1-}" if [ "$_got" = "$3" ]; then bst_ok "libnvme/$2" else bst_notok "libnvme/$2" "want:'$3' got:'$_got' -- $LOCALE_HELD" fi } JSON_HELD="libnvme's config code is NOT the cause -- json-c's parser is (see G06-G08): $LOCALE_HELD; the file itself is fine, see J12a/J12b/J12c/J13a: inode ${V_J12a-?}, read() returned ${V_J12b-?}, head='${V_J12c-?}', nvme_read_config errno ${V_J13a-?}" # want_j : strict, but a red carries the file # evidence above. want_j() { eval "_got=\${V_$1-}" if [ "$_got" = "$3" ]; then bst_ok "libnvme/$2" elif [ -z "$_got" ]; then bst_notok "libnvme/$2" "no value on target (probe stopped before $1); want='$3' -- $JSON_HELD" else bst_notok "libnvme/$2" "want:'$3' got:'$_got' -- $JSON_HELD" fi } NVME_DEV=${V_E03-} NVME_HELD="held by E05 controller-enumerated: this guest has no NVMe device, so /sys/class/nvme-subsystem is empty and nvme_scan() finds no controller to enumerate (E01=${V_E01-} E02=${V_E02-} E03=${V_E03-} counts=${V_E06-})" # want_e : same comparison, but a red while # the guest has no NVMe device carries its cause. Green is scored green by the # same test as want(), so these flip by themselves the day the platform gains a # device rather than needing this file edited. want_e() { eval "_got=\${V_$1-}" if [ "$_got" = "$3" ]; then bst_ok "libnvme/$2" elif [ "$NVME_DEV" != "1" ]; then bst_notok "libnvme/$2" "want:'$3' got:'$_got' -- $NVME_HELD" elif [ -z "$_got" ]; then bst_notok "libnvme/$2" "no value on target (probe stopped before $1); want='$3'" else bst_notok "libnvme/$2" "want:'$3' got:'$_got'" fi } # --- Z: the layout census ------------------------------------------------- # Scored first: a disagreement here explains every value mismatch after it. # Z01-Z24 are structure sizes the NVMe spec fixes in bytes; Z30-Z39 are field # offsets sampled across the WHOLE of each 4096-byte identify page rather than # only near its front, because a packing fault surfaces first at the field # after a reserved run. Z40 is the one place a pointer reaches the ioctl wire: # `addr` and `metadata` are __u64 in the uapi even on a 32-bit target, so both # must read 8 here while Z41 reads 4 -- if Z40 ever tracked Z41 the struct # would have shrunk and the kernel would reject every command. want Z01 sizeof-id-ctrl '4096' want Z02 sizeof-id-ns '4096' want Z03 sizeof-smart-log '512' want Z04 sizeof-error-log-page '64' want Z05 sizeof-firmware-slot '512' want Z06 sizeof-ns-id-desc '4' want Z07 sizeof-passthru-cmd '72' want Z08 sizeof-passthru-cmd64 '80' want Z09 sizeof-lba-range-type-entry '64' want Z10 sizeof-dsm-range '16' want Z11 sizeof-id-ctrl-nvm '4096' want Z12 sizeof-ns-list '4096' want Z13 sizeof-ctrl-list '4096' want Z14 sizeof-id-psd '32' want Z15 sizeof-lba-status '8' want Z16 sizeof-resv-status '64' want Z17 sizeof-self-test-log '564' want Z18 sizeof-cmd-effects-log '4096' want Z19 sizeof-endurance-group-log '512' want Z20 sizeof-telemetry-log '512' want Z21 sizeof-lba-status-desc '16' want Z22 sizeof-copy-range '32' want Z23 sizeof-id-uuid-list '4096' want Z24 sizeof-nvmset-attr '128' want Z30 offsets-id-ctrl-identity '0 4 24 64 77' want Z31 offsets-id-ctrl-version '78 80 96 112 111' want Z32 offsets-id-ctrl-caps '256 512 516 520 524' want Z33 offsets-id-ctrl-tail '536 768 2048 3072' want Z34 offsets-id-ns-head '0 8 16 24 25' want Z35 offsets-id-ns-tail '26 104 120 128 384' want Z36 offsets-smart-head '0 1 3 5 32' want Z37 offsets-smart-counters '64 96 176 192 216' want Z38 offsets-error-log '0 8 10 12 16' want Z39 offsets-passthru-cmd '0 4 24 40' want Z40 passthru-ptr-fields-are-u64 '8 8' # Z41 IS THE ONLY CASE IN THIS FILE PINNED TO A TARGET-SPECIFIC VALUE, AND IT # IS THE CONTROL FOR Z40. Z40 asserts that the ioctl struct's `addr` and # `metadata` are still 8 bytes wide, which is a LAYOUT fact and must not # change. Z41 asserts that this machine's own pointer and long are 4, which is # an ARCHITECTURE fact and must. Green Z40 beside green Z41 is the statement # worth having -- the uapi field stayed 64-bit on a 32-bit host, so libnvme # widened the pointer rather than truncating it. Pinning Z41 to the oracle's # '8 8' would have made the pair meaningless, and is exactly the mistake the # first run of this suite made. want Z41 host-word-sizes '4 4' want Z42 ioctl-request-codes '0xc0484e41 0xc0484e43 0xc0504e47 0xc0504e48' want Z43 api-constants '0xffffffff 16 4096 0' want Z44 status-code-types '0 1 2 3 7' # --- F: identify pages decoded from byte fixtures -------------------------- # A 4096-byte buffer with values poked at the SPEC's own offsets, cast to the # struct and read back. This is the direction that matters: a consumer # receives bytes from a controller and must decode them, so the fixture is the # controller. F18/F19/F20/F38 are the derived readings a consumer actually # prints -- version triple, capability bits, queue-entry exponents, LBA size -- # so a red names the reading rather than only the raw field. # F40/F42/F43: see the flbas item in the header comment. want F01 id-ctrl-vid '0x144d' want F02 id-ctrl-ssvid '0x2211' want F03 id-ctrl-sn '[S3EVNX0K123456 ]' want F04 id-ctrl-mn '[BRINTOS TEST CONTROLLER 4096B FIXTURE ]' want F05 id-ctrl-fr '[1B2QEXM7]' want F06 id-ctrl-mdts '7' want F07 id-ctrl-cntlid '1' want F08 id-ctrl-ver '0x00010400' want F09 id-ctrl-ctratt '0x00000180' want F10 id-ctrl-cntrltype '1' want F11 id-ctrl-oacs '0x005f' want F12 id-ctrl-sqes-cqes '0x66 0x44' want F13 id-ctrl-nn '2' want F14 id-ctrl-oncs '0x005d' want F15 id-ctrl-fna '0x04' want F16 id-ctrl-sgls '0x00000005' want F17 id-ctrl-subnqn '[nqn.2014.08.org.nvmexpress:144d144dS3EVNX0K123456]' want F18 id-ctrl-ver-decoded '1.4.0' want F19 id-ctrl-oacs-bits '1 1 1' want F20 id-ctrl-sqes-cqes-nibbles '6 6 4 4' want F30 id-ns-nsze '1902592' want F31 id-ns-ncap '1902592' want F32 id-ns-nuse '1024' want F33 id-ns-flags '0x02 1 0x10 0x1f 0x0f' want F34 id-ns-nguid '00112233445566778899aabbccddeeff' want F35 id-ns-eui64 '0102030405060708' want F36 id-ns-lbaf0 'ms=0 ds=9 rp=0' want F37 id-ns-lbaf1 'ms=8 ds=12 rp=2' want F38 id-ns-lba-sizes '512 4096' want F39 flbas-inuse-simple '0' want F40 flbas-inuse-drops-high-bits '11' want F42 flbas-inuse-corrupts-low-nibble '11' want F43 flbas-inuse-correct-by-hand '59 42' # --- L: log pages decoded from byte fixtures ------------------------------- # Same construction as F. L05-L08 are the 128-bit counters, scored as raw hex # so a byte-order fault is readable without arithmetic, and L12 reduces one of # them the way nvme-cli does. L11 is the Kelvin-to-Celsius conversion every # consumer performs. L25 decomposes the error log's packed status field, whose # phase-tag-in-bit-0 layout is the single easiest thing to decode off by one. want L01 smart-critical-warning '0x02' want L02 smart-temperature '299' want L03 smart-spare-and-used '100 10 3' want L04 smart-endu-grp-warn '0x01' want L05 smart-data-units-read '40e20100000000000000000000000000' want L06 smart-host-reads '11220000000000000000000000000000' want L07 smart-ctrl-busy-time '39050000000000000000000000000000' want L08 smart-num-err-log-entries '07000000000000000000000000000000' want L09 smart-temp-times '12 3' want L10 smart-temp-sensors '300 301' want L11 smart-temperature-celsius '26' want L12 smart-128bit-reduced '123456' want L20 error-log-count '42' want L21 error-log-sqid-cmdid '3 123' want L22 error-log-status-fields '0x8002 0x0040' want L23 error-log-lba '48879' want L24 error-log-nsid-vs '5 1' want L25 error-log-status-decoded 'phase=0 sc=0x01 sct=0 crd=0 m=0 dnr=1' want L30 fw-slot-afi '0x11' want L31 fw-slot-afi-decoded 'acti=1 next=1' want L32 fw-slot-revisions '[1B2QEXM7] [1B2QEXM8]' want L33 fw-slot-count '7' want L40 ns-id-desc-header '3 16' want L41 ns-id-desc-uuid '8b3df55f3f4b4c9ea1b2c3d4e5f60718' want L42 ns-id-desc-types '1 2 3 4' # --- S: status and error strings ------------------------------------------- # S01-S12 walk the generic table. S20-S2b reach the other four tables through # the SCT bits in 10:8 that select them -- and S25/S27 are the SAME codes with # the SCT omitted, answering from the generic table instead, which is what # makes "you must carry the SCT" an assertion rather than a remark. # S22/S23: see the negative-status item in the header comment. want S01 status-success '0|Successful Completion: The command completed without error' want S02 status-invalid-opcode '22|Invalid Command Opcode: A reserved coded value or an unsupported value in the command opcode field' want S03 status-invalid-field '22|Invalid Field in Command: A reserved coded value or an unsupported value in a defined field' want S04 status-data-xfer-error '5|Data Transfer Error: Transferring the data or metadata associated with a command experienced an error' want S05 status-internal '5|Internal Error: The command was not completed successfully due to an internal error' want S06 status-abort-queue '11|Command Aborted due to SQ Deletion: The command was aborted due to a Delete I/O Submission Queue' want S07 status-invalid-ns '22|Invalid Namespace or Format: The namespace or the format of that namespace is invalid' want S08 status-lba-range '121|LBA Out of Range: The command references an LBA that exceeds the size of the namespace' want S09 status-cap-exceeded '28|Capacity Exceeded: Execution of the command has caused the capacity of the namespace to be exceeded' want S10 status-ns-not-ready '13|Namespace Not Ready: The namespace is not ready to be accessed' want S11 status-cmdid-conflict '98|Command ID Conflict: The command identifier is already in use' want S12 status-sgl-invalid-last '22|Invalid SGL Segment Descriptor: The command includes an invalid SGL Last Segment or SGL Segment descriptor' want S20 status-invalid-field-fabrics '22|Invalid Field in Command: A reserved coded value or an unsupported value in a defined field' want S21 status-unknown-generic '5|unrecognized' want S22 status-negative-returns-strerror 'No space left on device' want S23 status-negative-returns-strerror-2 'Permission denied' want S24 status-zns-with-sct '5|Zoned Boundary Error: Invalid Zone Boundary crossing' want S25 status-zns-without-sct 'unrecognized' want S26 status-auth-required-fabrics '1|Authentication Required: NVMe in-band authentication is required and the queue has not yet been authenticated' want S27 status-auth-required-not-fabrics 'unrecognized' want S28 status-media-write-fault '5|Write Fault: The write data could not be committed to the media' want S29 status-path-ana-loss '5|Asymmetric Access Persistent Loss: The controller is in a persistent loss state with the requested namespace' want S2a status-connect-invalid-param '22|Connect Invalid Parameters: One or more of the command parameters' want S2b status-vendor-specific 'Vendor Specific Status' want S30 errno-addrnotavail 'cannot assign requested address' want S31 errno-opnotsupp 'not supported' want S32 errno-already 'already connected' want S33 errno-inval 'invalid arguments/configuration' want S40 status-field-decompose '130 0 16514' want S41 status-equals '0 1' want S42 status-code-with-sct '130 1' # --- C: command construction through the interposed ioctl ------------------ # The real path a consumer drives. Each case scores EVERY field libnvme put in # the passthru struct, so a wrong cdw names its command rather than reddening # a single opaque digest. C20-C22 are the three error paths (an NVMe status, a # failing ioctl, a refused descriptor) and C23 proves the payload really is the # controller's bytes rather than the caller's zeroes -- without it every green # above would be compatible with a library that composed correctly and copied # nothing back. # C15/C17/C18: see the reftag and ABI items in the header comment. want C00 identify-ctrl-rc '0' want C01 cmd-identify-ctrl 'op=0x06 fl=0x00 nsid=0x00000000 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=4096 cdw10=0x00000001 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=0 n=1' want C02 cmd-identify-ns 'op=0x06 fl=0x00 nsid=0x00000003 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=4096 cdw10=0x00000000 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=0 n=1' want C03 cmd-identify-active-ns-list 'op=0x06 fl=0x00 nsid=0x00000000 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=4096 cdw10=0x00000002 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=0 n=1' want C04 cmd-identify-allocated-ns-list 'op=0x06 fl=0x00 nsid=0x00000000 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=4096 cdw10=0x00000010 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=0 n=1' want C05 cmd-get-log-smart-all 'op=0x02 fl=0x00 nsid=0xffffffff cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=512 cdw10=0x007f0002 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=0 n=1' want C06 cmd-get-log-smart-rae 'op=0x02 fl=0x00 nsid=0x00000001 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=512 cdw10=0x007f8002 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=0 n=1' want C07 cmd-get-log-error 'op=0x02 fl=0x00 nsid=0xffffffff cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=256 cdw10=0x003f0001 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=0 n=1' want C08 cmd-get-log-fw-slot 'op=0x02 fl=0x00 nsid=0xffffffff cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=512 cdw10=0x007f0003 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=0 n=1' want C09 cmd-get-features-num-queues 'op=0x0a fl=0x00 nsid=0x00000000 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=0 cdw10=0x00000007 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=0 n=1' want C10 cmd-set-features-power-mgmt 'op=0x09 fl=0x00 nsid=0x00000000 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=0 cdw10=0x00000002 cdw11=0x00000022 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=0 n=1' want C11 cmd-set-features-temp-thresh 'op=0x09 fl=0x00 nsid=0x00000000 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=0 cdw10=0x00000004 cdw11=0x00130157 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=0 n=1' want C12 cmd-format-nvm 'op=0x80 fl=0x00 nsid=0x00000001 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=0 cdw10=0x00000200 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 to=600000 n=1' want C13 cmd-admin-passthru 'op=0xc0 fl=0x00 nsid=0x00000009 cdw2=0x11223344 cdw3=0x55667788 mlen=0 dlen=512 cdw10=0x0a0b0c0d cdw11=0x1a1b1c1d cdw12=0x2a2b2c2d cdw13=0x3a3b3c3d cdw14=0x4a4b4c4d cdw15=0x5a5b5c5d to=12345 n=1' want C14 admin-passthru-result '0xfeedface' want C15 cmd-read-current-abi 'op=0x02 fl=0x00 nsid=0x00000001 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=4096 cdw10=0x55667788 cdw11=0x11223344 cdw12=0x00000007 cdw13=0x00000000 cdw14=0x00000000 cdw15=0xffff1234 to=0 n=1' want C16 cmd-write-reftag64 'op=0x01 fl=0x00 nsid=0x00000002 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=512 cdw10=0x00000000 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x33221100 cdw15=0x00000000 to=0 n=1' want C17 cmd-read-legacy-abi 'op=0x02 fl=0x00 nsid=0x00000001 cdw2=0x00000000 cdw3=0x00000000 mlen=0 dlen=4096 cdw10=0x55667788 cdw11=0x11223344 cdw12=0x00000007 cdw13=0x00000000 cdw14=0xdeadbeef cdw15=0xffff1234 to=0 n=1' want C18 io-args-abi-relation 'v1