. "$BST_LIB" # BST-TIMEOUT: 600 # linuxptp 4.1 -- the IEEE 1588 PTP stack (meta-oe # recipes-connectivity/linuxptp). What ships is nine programs in /usr/sbin -- # ptp4l, phc2sys, pmc, phc_ctl, hwstamp_ctl, nsm, ts2phc, timemaster, tz2alt -- # plus /etc/linuxptp/ptp4l.conf (upstream's copy of configs/default.cfg) and # /etc/linuxptp/brintos-lo.conf (this port's addition). # # THE SUBJECT IS THE MANAGEMENT INTERFACE OVER THE SOFTWARE CLOCK, and it is # taken over a UNIX socket rather than over the wire. ptp4l serves # `pmc -u' from an AF_LOCAL datagram socket, opened by uds_open() in # clock_create(); that path shares no code with the UDPv4 # transport beyond the message codec, so a management reply is a statement # about the clock's data sets and not about whether a PTP packet can leave # this machine. That separation is the whole reason the criterion is # reachable here at all. # # EVERY VALUE THE REPLY IS SCORED ON IS NON-DEFAULT. clockIdentity, domainNumber, # priority1, priority2 and clockClass are all set in brintos-lo.conf to something # config.c's table does not default to (respectively: generated from the # interface MAC, 0, 128, 128, 248), so a green case cannot be a compiled-in # default wearing a configured value's clothes -- and on lo, whose MAC is all # zeroes, the generated identity would be a constant that looks configured. # # THERE IS NO PTP HARDWARE CLOCK ON THIS TARGET and there is not meant to be. # brintos-lo.conf asks for `time_stamping software', which pins phc_index to -1 # in clock_create() and disciplines CLOCK_REALTIME. Upstream's ptp4l.conf asks # for `hardware' and cannot run here at all -- clock_create() has no way to # resolve a PHC index and exits before opening a port -- which is why this port # adds a config rather than relying on the shipped one. Both are asserted below: # ours runs, theirs is still byte for byte upstream's. # # NOTHING HERE WAITS ON A CHILD AND NOTHING IS KILLED. kill(2) reports success # and delivers nothing on this image, SIGKILL included (brintos/linux#21), so # the ptp4l started below outlives the suite and one run per boot is the # supported shape. For the same reason readiness is taken by polling for an # artifact the daemon itself produced, never by a fixed sleep -- relative timers # are broken here (brintos/linux-in-the-browser#120: nanosleep returns early, poll timeouts do # not fire), so a `sleep 2' proves nothing about how much time passed. # # THE ONE LONG-RUNNING FOREGROUND COMMAND IS ALSO BACKGROUNDED, deliberately. # `pmc -u -b 0 GET DEFAULT_DATA_SET' sends its query, prints the reply, and then # has to exit through a 100 ms poll(2) TIMEOUT (pmc.c: tmo = 100 once the # command list is empty, and the loop breaks on poll returning 0). That is # exactly the facility #120 says is unwired, so pmc may well print a perfectly # good answer and never return. Its output is therefore polled for out of a # file, and its exit status is never read -- the criterion is what ptp4l # answered, not whether pmc managed to leave. require /usr/sbin/ptp4l linuxptp/present P4L=/usr/sbin/ptp4l PMC=/usr/sbin/pmc P2S=/usr/sbin/phc2sys PCTL=/usr/sbin/phc_ctl CONF=/etc/linuxptp/brintos-lo.conf STOCK=/etc/linuxptp/ptp4l.conf # Where brintos-lo.conf puts the management sockets, and why they are not under # /var/run: the guest root cannot represent a socket inode, so bind() there # returns EPERM and ptp4l cannot open its UDS ports at all. Repeated here so a # config edit that moves them fails loudly instead of polling a path nobody # binds. pmc needs BOTH -- -s to find this ptp4l, -i for its own local socket, # which it otherwise hardcodes under /var/run and cannot bind either. UDS=/tmp/ptp4l # The values brintos-lo.conf sets, repeated here so a config edit that forgets # this suite fails loudly instead of asserting whatever it now says. WANT_CID=e2c1a6.fffe.1226ff WANT_DOMAIN=44 WANT_PRIO1=101 WANT_PRIO2=202 WANT_CLASS=187 D=${TMPDIR:-/tmp}/bst-linuxptp.$$ rm -rf "$D"; mkdir -p "$D" || { bst_notok linuxptp/workdir "cannot create $D"; exit 0; } # has ... : true iff EVERY needle is a substring. has() { _h=$1; shift for _n in "$@"; do case "$_h" in *"$_n"*) ;; *) return 1 ;; esac; done return 0 } # --- the tools are present and are 4.1 --------------------------------------- # version_show() prints the string version.sh built from major/minor, so this # separates "the recipe built 4.1" from "something older was already here". check_out linuxptp/ptp4l-version "4.1" timeout 30 "$P4L" -v check_out linuxptp/pmc-version "4.1" timeout 30 "$PMC" -v check_out linuxptp/phc2sys-version "4.1" timeout 30 "$P2S" -v # --- both configs ship, and upstream's is untouched -------------------------- if [ -f "$CONF" ]; then bst_ok linuxptp/config-shipped else bst_notok linuxptp/config-shipped "$CONF is not on the image"; fi # The port ADDS a config; it does not substitute one. If a later change starts # overwriting upstream's ptp4l.conf, this is where it shows up rather than as a # surprise for someone who does have a PHC. if [ -f "$STOCK" ]; then st=$(cat "$STOCK" 2>&1) if has "$st" "time_stamping" "hardware"; then bst_ok linuxptp/upstream-config-intact else bst_notok linuxptp/upstream-config-intact "$STOCK no longer carries upstream's 'time_stamping hardware'"; fi else bst_notok linuxptp/upstream-config-intact "$STOCK is not on the image" fi # --- the shipped config is READ, established through the parser's own checks -- # ptp4l has no mode that echoes what it parsed, so the three cases below each # derive from the shipped file by ONE sed and assert the specific diagnostic # config.c emits for that defect. A check can only fire if the line it names was # read, so each is evidence that this exact file reached the parser -- and each # asserts the message rather than merely a non-zero status. sed 's/^domainNumber.*/domainNumber 300/' "$CONF" > "$D/range.conf" sed 's/^time_stamping.*/time_stamping bogus/' "$CONF" > "$D/enum.conf" cp "$CONF" "$D/unknown.conf" && printf 'notAnOption\t1\n' >> "$D/unknown.conf" # A refusing ptp4l exits non-zero, so these are scored by the diagnostic ALONE: # check_out also requires rc=0, which would turn every correct refusal red. diag() { _id=$1; _needle=$2; shift 2 _o=$(timeout 30 "$@" 2>&1) if bst_execfail "$_o"; then bst_unrun "$_id" "$_o" elif has "$_o" "$_needle"; then bst_ok "$_id" else bst_notok "$_id" "want:'$_needle' got:'$(_bst_1line "$_o")'"; fi } diag linuxptp/config-range-checked \ "300 is an out of range value for option domainNumber" "$P4L" -f "$D/range.conf" diag linuxptp/config-enum-checked \ "bogus is a bad value for option time_stamping" "$P4L" -f "$D/enum.conf" diag linuxptp/config-unknown-option \ "unknown option notAnOption" "$P4L" -f "$D/unknown.conf" # --- attributing a ptp4l that did not start --------------------------------- # A red below is a FINDING only if ptp4l failed for a reason this platform has # not already been shown to impose. Two shapes are known and are named in the # case's own message so the run self-attributes: # # brintos/linux#110 -- timerfd_create(2) (283) is absent from the wasm32 # syscall table. port_open() arms a fault timer for every port that is not a # UDS port and treats the failure as fatal, so clock_create() returns NULL and # ptp4l exits before binding its management socket. No config option removes a # port's timers, so there is nothing to work around; the case flips green when # #110 lands. # # brintos/linux#106 -- clock_adjtime(2) (305) absent. Answered in the bundled # config with `free_running 1', so seeing this again means that line was lost. _p4l_cause() { case "$1" in *timerfd_create*) printf '%s' "brintos/linux#110 (timerfd_create(2) unwired; port_open() cannot arm a non-UDS port): " ;; *"failed to read out the clock frequency adjustment"*) printf '%s' "brintos/linux#106 (clock_adjtime(2) unwired; free_running 1 has been lost from the config): " ;; esac } # `ioctl SIOCETHTOOL failed' is emitted on lo and TOLERATED -- sk_get_ts_info() # only warns, and a native run prints it and reaches LISTENING regardless. It is # also the FIRST line ptp4l writes, so with _bst_1line truncating at 100 # characters it once pushed the real error out of the diagnostic entirely and # invited blaming the tsinfo reading. Drop it, unless it is all there is. _p4l_diag() { _p4l_d=$(printf '%s\n' "$1" | grep -v 'ioctl SIOCETHTOOL failed') [ -n "$_p4l_d" ] || _p4l_d=$1 _bst_1line "$_p4l_d" } # --- the criterion, first half: ptp4l runs from the bundled config ----------- # Started in the background and never waited on. Readiness is the UDS socket # ptp4l itself binds in uds_open(), not a sleep: the socket EXISTING is the # management interface being open, so the race closes structurally. "$P4L" -f "$CONF" > "$D/ptp4l.log" 2>&1 & p4lpid=$! i=0; up=0 while [ $i -lt 90 ]; do if [ -S "$UDS" ]; then up=1; break; fi kill -0 "$p4lpid" 2>/dev/null || break i=$((i+1)); sleep 1 done if [ "$up" -eq 1 ]; then bst_ok linuxptp/ptp4l-starts else p4log=$(cat "$D/ptp4l.log" 2>/dev/null) bst_notok linuxptp/ptp4l-starts "$(_p4l_cause "$p4log")no $UDS socket after ${i} polls -- $(_p4l_diag "$p4log")" fi # The UDPv4 port is scored SEPARATELY from the clock, because it is the half # that can legitimately fail here and the criterion does not rest on it. # udp_open() must survive SO_BINDTODEVICE, IP_MULTICAST_TTL and two # IP_ADD_MEMBERSHIP joins on lo -- an interface the kernel gives IFF_LOOPBACK # and not IFF_MULTICAST -- and port_initialize() then arms four timerfds. A # failure in either shows up as the port going FAULTY instead of LISTENING, # and leaves the management interface entirely intact. plog=$(cat "$D/ptp4l.log" 2>/dev/null) if has "$plog" "port 1 (lo): INITIALIZING to LISTENING"; then bst_ok linuxptp/udpv4-port-listening elif has "$plog" "port 1 (lo): INITIALIZING to FAULTY"; then bst_notok linuxptp/udpv4-port-listening "$(_p4l_cause "$plog")the lo port went FAULTY -- $(_p4l_diag "$plog")" else bst_notok linuxptp/udpv4-port-listening "$(_p4l_cause "$plog")no port 1 transition logged -- $(_p4l_diag "$plog")" fi # --- the criterion, second half: pmc reads back the configured data set ------ # `-d 44' is NOT decoration and is not a workaround. port_ignore() drops any # management message whose header domainNumber differs from the clock's, and the # UDS port goes through that same filter -- port_is_uds() only exempts NON- # management traffic. So addressing domain 44 is what "the domain the config # set" MEANS on the wire, and the ticket's bare form is scored below as the # negative half rather than dropped. # # Backgrounded and polled for, never waited on: pmc leaves its loop through a # 100 ms poll(2) timeout (pmc.c, tmo = 100 once the command list empties), which # is the facility brintos/linux-in-the-browser#120 says does not fire. A pmc that answered # correctly and then never returned must still score green. "$PMC" -u -s "$UDS" -i "$D/pmc.rw" -b 0 -d "$WANT_DOMAIN" 'GET DEFAULT_DATA_SET' > "$D/dds.out" 2>&1 & i=0; got=0 while [ $i -lt 60 ]; do if grep -q "clockIdentity" "$D/dds.out" 2>/dev/null; then got=1; break; fi i=$((i+1)); sleep 1 done dds=$(cat "$D/dds.out" 2>&1) if [ "$got" -eq 1 ] && has "$dds" "RESPONSE MANAGEMENT DEFAULT_DATA_SET"; then bst_ok linuxptp/pmc-default-data-set else bst_notok linuxptp/pmc-default-data-set "no DEFAULT_DATA_SET response after ${i} polls -- $(_bst_1line "$dds")" fi # Field by field, read positionally rather than by substring: pmc.c pads each # label to a fixed column, and an assertion that hard-coded that padding would # go red on a cosmetic upstream change while saying nothing about the value. fld() { awk -v k="$1" '$1 == k { print $2; exit }' "$D/dds.out" 2>/dev/null; } check_eq linuxptp/pmc-clock-identity "$WANT_CID" "$(fld clockIdentity)" check_eq linuxptp/pmc-domain-number "$WANT_DOMAIN" "$(fld domainNumber)" check_eq linuxptp/pmc-priority1 "$WANT_PRIO1" "$(fld priority1)" check_eq linuxptp/pmc-priority2 "$WANT_PRIO2" "$(fld priority2)" check_eq linuxptp/pmc-clock-class "$WANT_CLASS" "$(fld clockClass)" # THE NEGATIVE HALF, and it is what stops the case above being a tautology. # This is the ticket's command but for the socket paths -- and, the point, no # -d, so pmc asks on domain 0. `-s' and `-i' are carried over from the positive # case deliberately: without -s this pmc would reach no ptp4l at all and its # silence would be green for a reason that has nothing to do with the domain. A # clock that had quietly stayed on the default domain would answer it, and the # five field assertions above would then be scoring a default. Silence here is # the second, independent proof that this ptp4l is on domain 44. # # Bounded harder than the positive case for a reason: there is nothing to wait # FOR, so the whole budget would be spent every run. A reply that took longer # than the positive one did would be a stranger result than no reply at all. "$PMC" -u -s "$UDS" -i "$D/pmc.d0" -b 0 'GET DEFAULT_DATA_SET' > "$D/dds0.out" 2>&1 & i=0 while [ $i -lt 15 ]; do grep -q "clockIdentity" "$D/dds0.out" 2>/dev/null && break i=$((i+1)); sleep 1 done dds0=$(cat "$D/dds0.out" 2>&1) if has "$dds0" "RESPONSE MANAGEMENT DEFAULT_DATA_SET"; then bst_notok linuxptp/pmc-wrong-domain-filtered "domain 0 was answered, so the clock is not on domain $WANT_DOMAIN -- $(_bst_1line "$dds0")" else bst_ok linuxptp/pmc-wrong-domain-filtered fi # --- phc_ctl against the one clock this target does have --------------------- # posix_clock_open() takes the literal "CLOCK_REALTIME" without going near a # /dev/ptp* char device, so this reaches the system clock on a target with no # PHC. It reads it -- `get' is clock_gettime(2) -- and deliberately does not # write it: the adjusting half of clockadj is ENOSYS here (brintos/linux#106), # which is the same gap free_running exists for. -q keeps the output on stderr # rather than on a syslog socket. check_out linuxptp/phc-ctl-realtime "clock time is" \ timeout 30 "$PCTL" -q CLOCK_REALTIME get # There is no PHC on this target, and the pair below says so from both ends # rather than assuming it: no /dev/ptp* node exists, and phc2sys refuses to run # when pointed at one. # # The refusal is `valid source clock must be selected.', from # phc2sys_static_src_configuration() when clock_add() returns NULL -- NOT the # "unknown clock %s" that posix_clock_open() would print, which is never reached # and was what an earlier draft of this case wrongly asserted. `-O 0' is # required: without a time offset phc2sys refuses at usage() one step sooner, # which would make this case green for the wrong reason. n=$(ls /dev/ptp* 2>/dev/null | wc -l) if [ "$n" -eq 0 ]; then bst_ok linuxptp/no-ptp-device-node else bst_notok linuxptp/no-ptp-device-node "this image grew $n /dev/ptp* node(s); the software-timestamping premise needs re-reading"; fi diag linuxptp/phc2sys-refuses-absent-phc "valid source clock must be selected" \ "$P2S" -s /dev/ptp0 -c CLOCK_REALTIME -O 0 -m -q