pax_global_header00006660000000000000000000000064143103402650014510gustar00rootroot0000000000000052 comment=41da9d525554aa3b8056015caf90f880df9b27cc isochron-0.9/000077500000000000000000000000001431034026500132045ustar00rootroot00000000000000isochron-0.9/.gitignore000066400000000000000000000000521431034026500151710ustar00rootroot00000000000000isochron *.o *.d docs/man/ docs/pdf/ tags isochron-0.9/COPYING000066400000000000000000000430761431034026500142510ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. isochron-0.9/Makefile000066400000000000000000000055401431034026500146500ustar00rootroot00000000000000VERSION := $(shell ./setlocalversion) MY_CPPFLAGS := -DVERSION=\"${VERSION}\" $(CPPFLAGS) MY_CPPFLAGS += $(shell ./toolchain_deps.sh "$(CC)" "$(MY_CPPFLAGS)") MY_CFLAGS := -Wall -Wextra -Werror -Wno-error=sign-compare \ -Wno-error=missing-field-initializers \ -Wno-unused-parameter $(CFLAGS) MY_LDFLAGS := $(LDFLAGS) CHECK := sparse CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ -Wbitwise -Wno-return-void -Wno-unknown-attribute $(CF) ifeq ($(C),1) REAL_CC := $(CC) CC := cgcc export REAL_CC endif prefix ?= /usr/local exec_prefix ?= ${prefix} bindir ?= ${exec_prefix}/bin datarootdir ?= ${prefix}/share mandir ?= ${datarootdir}/man PKG_CONFIG ?= pkg-config INSTALL ?= install src := \ argparser.o \ common.o \ daemon.o \ isochron.o \ log.o \ management.o \ orchestrate.o \ ptpmon.o \ rcv.o \ report.o \ rtnl.o \ send.o \ sk.o \ syncmon.o \ sysmon.o symlinks := \ isochron-daemon \ isochron-orchestrate \ isochron-send \ isochron-rcv \ isochron-report objs := $(addprefix src/, $(src)) deps := $(patsubst %.o, %.d, $(objs)) md_docs := $(wildcard docs/*.md) manpages := $(patsubst docs/%.md, docs/man/%, $(md_docs)) # Input: path to manpage file from sources # Output: DESTDIR-prefixed install location get_man_section = $(lastword $(subst ., ,$1)) get_manpage_destination = $(join $(DESTDIR)${mandir}/man, \ $(join $(call get_man_section,$1)/, \ $(subst docs/man/,,$1))) ifeq (, $(shell which $(PKG_CONFIG))) $(error No pkg-config binary in $(PATH)) endif LIBMNL_CFLAGS := $(shell ${PKG_CONFIG} --cflags libmnl) LIBMNL_LDFLAGS := $(shell ${PKG_CONFIG} --libs libmnl) ifeq (, $(LIBMNL_CFLAGS)$(LIBMNL_LDFLAGS)) $(error pkg-config could not find libmnl) endif MY_CFLAGS += $(LIBMNL_CFLAGS) MY_LDFLAGS += $(LIBMNL_LDFLAGS) TARGET := isochron all: $(TARGET) man man: $(manpages) docs/man/%: docs/%.md @mkdir -p $(@D) pandoc --standalone --to man $^ -o $@ # include all .d files -include $(deps) $(TARGET): $(objs) $(CC) $^ -o $@ $(MY_LDFLAGS) -lm -pthread %.o: %.c $(CC) $(MY_CPPFLAGS) $(MY_CFLAGS) -MMD -c $< -o $@ ifeq ($(C),1) $(CHECK) $(CHECKFLAGS) $(MY_CPPFLAGS) $(MY_CFLAGS) $< endif clean: rm -f $(objs) $(deps) $(TARGET) rm -f docs/man/* install-manpages: $(manpages) $(foreach manpage, $^, $(INSTALL) -m 0644 -D $(manpage) \ $(call get_manpage_destination,$(manpage));) install-binaries: $(TARGET) $(INSTALL) -m 0755 -D $(TARGET) $(DESTDIR)${bindir}/isochron $(foreach symlink, $(symlinks), \ ln -sf $(TARGET) $(DESTDIR)${bindir}/$(symlink);) install-completion: bash-completion/isochron $(INSTALL) -m 0644 -D $< $(DESTDIR)${datarootdir}/bash-completion/completions/isochron $(foreach symlink, $(symlinks), \ ln -sf $(TARGET) $(DESTDIR)${datarootdir}/bash-completion/completions/$(symlink);) install: install-binaries install-manpages install-completion isochron-0.9/README.md000066400000000000000000000033671431034026500144740ustar00rootroot00000000000000isochron ======== [![Coverity Scan Build Status](https://scan.coverity.com/projects/25104/badge.svg)](https://scan.coverity.com/projects/isochron) The isochron program is a real-time application for testing Time Sensitive Networking equipment. It works by monitoring the network synchronization status and sending time-triggered Ethernet packets. It has a server-client architecture and it measures network latency by taking multiple timestamps (some hardware, some software) along the path of the packets. Complex packet collision patterns can be created and measured by orchestrating (coordinating the transmission times of) isochron nodes on multiple stations. isochron requires a network interface with the ability to retrieve hardware RX and TX timestamps of non-PTP packets. It also makes use of additional network interface offloads, such as time-aware scheduling (`tc-taprio`), or time specified departure (`tc-etf` and `SO_TXTIME`), if those are available. The full documentation is available in the `docs/` folder. Building -------- isochron links with the following libraries: * libmnl (https://git.netfilter.org/libmnl/) Building is simply a matter of running: ```bash # Build everything make # Build just isochron make isochron # Build just the man pages using pandoc make man ``` Installation requires running at least one of the following (by default, `DESTDIR` is empty, and `prefix` is `/usr/local`): ```bash # Install everything make install DESTDIR=/path/to/target prefix=/usr # Install just isochron make install-binaries DESTDIR=/path/to/target prefix=/usr # Install just the bash-completion script make install-completion DESTDIR=/path/to/target prefix=/usr # Install just the man pages make install-manpages DESTDIR=/path/to/target prefix=/usr ``` isochron-0.9/VERSION000066400000000000000000000000051431034026500142470ustar00rootroot00000000000000v0.9 isochron-0.9/bash-completion/000077500000000000000000000000001431034026500162705ustar00rootroot00000000000000isochron-0.9/bash-completion/isochron000066400000000000000000000053521431034026500200440ustar00rootroot00000000000000__isochron_subprog_complete() { local prev=$1 && shift local cur=$1 && shift local prog=$1 && shift local subprog local words local helptext local long_opts=() local short_opts=() local wordlist="" case "$(basename ${prog} 2> /dev/null)" in isochron) if [ $# -lt 1 ]; then return fi subprog=$1 && shift ;; isochron-daemon) prog="isochron" subprog="daemon" ;; isochron-orchestrate) prog="isochron" subprog="orchestrate" ;; isochron-report) prog="isochron" subprog="report" ;; isochron-rcv) prog="isochron" subprog="rcv" ;; isochron-send) prog="isochron" subprog="send" ;; *) return ;; esac words=("$@") helptext="$(${prog} ${subprog} --help 2>&1)" while IFS= read -r line; do local filepath=false local ifname=false local long_opt= local short_opt= local num_args= local opts= local ifaces= opts=${line%%: *} kind=${line##*: } kind=${kind%% (optional)} short_opt=${opts%%|*} long_opt=${opts##*|} # Don't suggest options that are already there if ! [[ " ${words[*]} " =~ " ${short_opt} " ]]; then short_opts=( ${short_opts[@]} ${short_opt} ) fi if ! [[ " ${words[*]} " =~ " ${long_opt} " ]]; then long_opts=( ${long_opts[@]} ${long_opt} ) fi case ${kind} in "Help text"|"Boolean") num_args=0 ;; "File path") filepath=true ;; "Network interface") ifname=true ;; *) num_args=1 ;; esac if [ "${prev}" = "${short_opt}" ] || [ "${prev}" = "${long_opt}" ]; then # If the option requires an argument and this is the argument, # don't attempt to complete it unless we know how. if [ "${num_args}" = 0 ]; then continue elif [ ${filepath} = true ]; then COMPREPLY=( $(compgen -f -- "${cur}") ) return elif [ ${ifname} = true ]; then for iface in $(cd /sys/class/net && ls -d */); do ifaces="${ifaces} ${iface%%/}" done COMPREPLY=( $(compgen -W "${ifaces}" -- "${cur}") ) return else return fi fi done < <(printf '%s\n' "${helptext}" | grep ': ') wordlist="${short_opts[@]} ${long_opts[@]}" COMPREPLY=( $(compgen -W "${wordlist}" -- "${cur}") ) } __isochron_complete() { local cur="" local prev="" local words=() _get_comp_words_by_ref -n : cur prev words if [ "$(basename ${prev} 2> /dev/null)" = "isochron" ]; then COMPREPLY=( $(compgen -W "daemon orchestrate send rcv report -h --help -v --version" -- "${cur}") ) return fi __isochron_subprog_complete "${prev}" "${cur}" ${words[@]} } complete -F __isochron_complete isochron complete -F __isochron_complete isochron-daemon complete -F __isochron_complete isochron-orchestrate complete -F __isochron_complete isochron-send complete -F __isochron_complete isochron-rcv complete -F __isochron_complete isochron-report isochron-0.9/docs/000077500000000000000000000000001431034026500141345ustar00rootroot00000000000000isochron-0.9/docs/isochron-daemon.8.md000066400000000000000000000037401431034026500177150ustar00rootroot00000000000000% isochron-daemon(8) | ISOCHRON NAME ==== isochron-daemon - Start an isochron program waiting for management commands SYNOPSIS ======== **isochron** daemon \[_OPTIONS_\] DESCRIPTION =========== This command starts a long-running process that listens for connections from an isochron orchestrator. The daemon can receive further instructions from the orchestrator. OPTIONS ======= `-h`, `--help` : prints the short help message and exits `-l`, `--log-file` <`PATH`> : after becoming a daemon, the program can redirect its standard output and standard error to the text file specified here. Optional, defaults to `/dev/null`. `-p`, `--pid-file` <`PATH`> : after spawning a daemon process, the main program overwrites the text file provided here with a single line containing a decimal number representing the process ID of the daemon. Optional, defaults to no PID file being created. `-P`, `--stats-port` <`NUMBER`> : specify the TCP port on which the daemon program is listening for incoming connections. This socket is used for management and statistics. Optional, defaults to port 5000. `-S`, `--stats-address` <`NUMBER`> : specify the IP address on which the daemon program is listening for incoming connections. This socket is used for management and statistics. Supports binding to a given network device using the `address%device` syntax (example: `--stats-address ::%vrf0`). Optional, defaults to ::, with a fallback to 0.0.0.0 if IPv6 is not available. EXAMPLES ======== To start and then stop a daemon and view its log file: ``` isochron daemon \ --log-file isochron.log \ --pid-file isochron.pid \ --stats-port 5001 tail -F isochron.log & kill $(pidof tail) kill $(cat isochron.pid) ``` AUTHOR ====== isochron was written by Vladimir Oltean SEE ALSO ======== isochron(8) isochron-orchestrate(1) COMMENTS ======== This man page was written using [pandoc](http://pandoc.org/) by the same author. isochron-0.9/docs/isochron-orchestrate.1.md000066400000000000000000000107161431034026500207670ustar00rootroot00000000000000% isochron-orchestrate(1) | ISOCHRON NAME ==== isochron-orchestrate - Coordinate isochron daemons SYNOPSIS ======== **isochron** orchestrate \[_OPTIONS_\] DESCRIPTION =========== This command opens an orchestration file containing descriptions of daemons: how to reach them, their names, roles and parameters for the test. It connects to these daemons, informs them of their parameters, and coordinates them such that they start sending traffic only when their synchronization offset becomes lower than the required threshold. After the test is done, the packet logs are gathered by the orchestrator from each sender and its associated receiver, and saved on the local filesystem. OPTIONS ======= `-h`, `--help` : prints the short help message and exits `-F`, `--input-file` <`PATH`> : specify the path to the input orchestration file. ORCHESTRATION FILE FORMAT ========================= The orchestration file has an INI-style format supporting multi-line statements, and with comments being delineated by the # character. Each section denotes an orchestration node, and the lines that follow have a "key = value" format that describe parameters for this node. `host` : denotes the IP address through which the isochron daemon can be reached by the orchestrator. `port` : denotes the TCP port through which the isochron daemon can be reached by the orchestrator. `exec` : denotes the command to be executed by the isochron daemon. The syntax is identical to what would be specified as command line arguments to `isochron-send`. The expected behavior of a daemon in the role of a sender is also identical to that of a dedicated sender, with some exceptions. The `--output-file` is interpreted by the orchestrator, not by the daemon (therefore, files are saved on the orchestrator's filesystem). Communication through the management socket does not take place between an orchestrated sender and its receiver. Instead, the orchestrator deduces the address and port of the receiver through the `--client` and `--stats-port` arguments of the sender, and connects by itself to the receiver. An orchestrated sender does not monitor sync status by itself and does not decide when to start sending test packets. Instead, these are controlled by the orchestrator. EXAMPLES ======== It is possible to orchestrate two senders running on hosts A (10.0.0.1) and B (10.0.0.2), sending towards two receivers both on host C (10.0.0.3), from a management node D, for the purpose of creating packet collisions and measuring the resulting latency. The commands on nodes A and B are: ``` ptp4l -i eth0 -2 -P --step_threshold 0.00002 & phc2sys -a -rr --step_threshold 0.00002 & isochron daemon ``` The commands on node C are: ``` ptp4l -i eth0 -2 -P --step_threshold 0.00002 & phc2sys -a -rr --step_threshold 0.00002 & isochron rcv --interface eth0 --stats-port 5000 --etype 0xdead & isochron rcv --interface eth0 --stats-port 5001 --etype 0xdeaf & ``` The commands on node D are (the double backslashes are to prevent the shell from interpreting them when creating the heredoc, the resulting file will have simple backslashes): ``` cat <<- EOF > orchestration.txt [A] host = 10.0.0.1 port = 5000 exec = isochron send \\ --client 10.0.0.3 \\ --stats-port 5000 \\ --interface eth0 \\ --num-frames 10 \\ --base-time 0.000000000 \\ --cycle-time 0.01 \\ --frame-size 1500 \\ --sync-threshold 100 \\ --cpu-mask 0x1 \\ --sched-fifo \\ --sched-priority 98 \\ --etype 0xdead \\ --output-file isochron-host-a.dat [B] host = 10.0.0.2 port = 5000 exec = isochron send \\ --client 10.0.0.3 \\ --stats-port 5001 \\ --interface eth0 \\ --num-frames 10 \\ --base-time 0.000000100 \\ --cycle-time 0.01 \\ --frame-size 1500 \\ --sync-threshold 100 \\ --cpu-mask 0x1 \\ --sched-fifo \\ --sched-priority 98 \\ --etype 0xdeaf \\ --output-file isochron-host-b.dat EOF isochron orchestrate --input-file orchestration.txt isochron report --summary --input-file isochron-host-a.dat isochron report --summary --input-file isochron-host-b.dat ``` AUTHOR ====== isochron was written by Vladimir Oltean SEE ALSO ======== isochron(8) isochron-send(8) isochron-daemon(8) COMMENTS ======== This man page was written using [pandoc](http://pandoc.org/) by the same author. isochron-0.9/docs/isochron-rcv.8.md000066400000000000000000000134051431034026500172430ustar00rootroot00000000000000% isochron-rcv(8) | ISOCHRON NAME ==== isochron-rcv - Start an isochron test in the role of a receiver SYNOPSIS ======== **isochron** rcv \[_OPTIONS_\] DESCRIPTION =========== This command starts a long-running process that listens for connections from an isochron sender, logs timestamps for the received test packets, and sends the logged data back. OPTIONS ======= `-h`, `--help` : prints the short help message and exits `-i`, `--interface` <`IFNAME`> : specify the network interface on which packets will be received `-d`, `--dmac` <`MACADDRESS`> : specify the destination MAC address used by the application for recognizing test packets. Can be either unicast or multicast. Necessary only for the L2 transport (plain Ethernet). Optional, the interface's unicast MAC address is used by default. `-e`, `--etype` <`NUMBER`> : specify the EtherType used by the application for recognizing test packets sent using the L2 transport. Optional, defaults to `0xdead`. `-P`, `--stats-port` <`NUMBER`> : specify the TCP port on which the receiver program is listening for incoming connections. This socket is used for management and statistics. Optional, defaults to port 5000. `-s`, `--frame-size` <`NUMBER`> : specify the size of test frames. The size is counted from the first octet of the destination MAC address until the last octet of data before the FCS. `-S`, `--stats-address` <`NUMBER`> : specify the IP address on which the receiver program is listening for incoming connections. This socket is used for management and statistics. Supports binding to a given network device using the `address%device` syntax (example: `--stats-address ::%vrf0`). Optional, defaults to ::, with a fallback to 0.0.0.0 if IPv6 is not available. `-q`, `--quiet` : this option suppresses error messages regarding invalid test packets. Optional, defaults to false. `-f`, `--sched-fifo` : when set, the program requests the kernel to change its scheduling policy to `SCHED_FIFO` for the duration of the test. `-r`, `--sched-rr` : when set, the program requests the kernel to change its scheduling policy to `SCHED_RR` for the duration of the test. `-H`, `--sched-priority` <`NUMBER`> : when either `--sched-fifo` or `--sched-rr` is used, the program requests the kernel to change its scheduling priority for the duration of the test. `-O`, `--utc-tai-offset` <`NUMBER`> : the program uses the `CLOCK_TAI` time base for its timers and for all reported timestamps, and this option specifies the correction in seconds to apply to software timestamps, which are taken by the kernel in the `CLOCK_REALTIME` (UTC) time base. If this option is present, isochron will also change the kernel's `CLOCK_TAI` offset to the specified value, to ensure that its timers fire correctly. If the option is absent, isochron queries the kernel's `CLOCK_TAI` offset and attempts to use that. If isochron can also query the UTC offset from ptp4l's `TIME_PROPERTIES_DATA_SET` using management messages, it does that and compares that offset to the kernel's UTC offset. The UTC offset reported by ptp4l has the highest priority, and if the application detects that this is different from the kernel's `CLOCK_TAI` offset, it changes the kernel offset to the value queried from ptp4l. `-2`, `--l2` : this option specifies that the plain Ethernet transport should be used for the test packets. Optional, defaults to true unless the sender overrides this via the management socket. `-4`, `--l4` : this option specifies that the UDP transport should be used for test packets. Optional, defaults to false unless the sender overrides this via the management socket. `-U`, `--unix-domain-socket` <`PATH`> : isochron queries ptp4l's state by creating and sending PTP management messages over a local UNIX domain socket. This option specifies the path of this socket in the filesystem. Optional, defaults to `/var/run/ptp4l`. `-N`, `--domain-number` <`NUMBER`> : this option provides the domainNumber value to be used when constructing PTP management messages sent to the ptp4l process. It must coincide with the domainNumber used by ptp4l, otherwise it will not respond to management messages. Optional, defaults to 0. `-t`, `--transport-specific` <`NUMBER`> : this option provides the transportSpecific value to be used when constructing PTP management messages sent to the ptp4l process. It must coincide with the transportSpecific used by ptp4l, otherwise it will not respond to management messages. Optional, defaults to 0. Note that PTP variants such as IEEE 802.1AS/gPTP require this value to be set to a different value such as 1. `-R`, `--num-readings` <`NUMBER`> : isochron monitors the synchronization quality between the NIC's PTP Hardware Clock (PHC) and the system time by successively reading the system time, the PHC time and the system time again, several times in a row, and picking the group of 3 time readouts that took the least amount of time overall. This option specifies how many readouts should be performed before picking the fastest one. Optional, defaults to 5. EXAMPLES ======== To start an isochron receiver with PTP synchronization: ``` ip link set eth0 up && ip addr add 192.168.100.2/24 dev eth0 ptp4l -i eth0 -2 -P --step_threshold 0.00002 & phc2sys -a -rr --step_threshold 0.00002 & isochron rcv \ --interface eth0 \ --quiet \ --sched-rr \ --sched-priority 98 ``` AUTHOR ====== isochron was written by Vladimir Oltean SEE ALSO ======== isochron(8) isochron-send(8) isochron-report(1) COMMENTS ======== This man page was written using [pandoc](http://pandoc.org/) by the same author. isochron-0.9/docs/isochron-report.1.md000066400000000000000000000172161431034026500177610ustar00rootroot00000000000000% isochron-report(1) | ISOCHRON NAME ==== isochron-report - Gather statistics from logged isochron data SYNOPSIS ======== **isochron** report \[_OPTIONS_\] DESCRIPTION =========== This command opens an isochron.dat file generated by `isochron send` and filters the requested data from it. OPTIONS ======= `-h`, `--help` : prints the short help message and exits `-F`, `--input-file` <`PATH`> : specify the path to the input file. Optional, defaults to "isochron.dat". `-m`, `--summary` : optionally calculate and print a summary of the built-in metrics. `-s`, `--start` <`NUMBER`> : specify the sequence number of the first packet to be taken into consideration for printing and for statistics calculation. Optional; defaults to sequence number 1 (the first packet). `-S`, `--stop` <`NUMBER`> : specify the sequence number of the last packet to be taken into consideration for printing and for statistics calculation. Optional; defaults to a sequence number equal to the number of packets of the test (the last packet). `-f`, `--printf-format` <`STRING`> : specify the format in which a packet will be printed. Optional; if not specified, per-packet information is not printed. `-a`, `--printf-args` <`STRING`> : specify the built-in variables which will be printed per packet. PRINTF FORMAT ============= The `--printf-format` argument specifies a free-form string that can also contain up to 256 printf-like codes prefixed by the `%` (percent) character. The printf codes will be replaced by isochron for each packet with internal variables taken from the log. The newline character is not added automatically between packets. The printf codes understood by isochron are: `%d` : print a built-in variable as a signed integer in decimal format. `%u` : print a built-in variable as an unsigned integer in decimal format. `%x` : print a built-in variable as an unsigned integer in hexadecimal format. `%T` : print a built-in variable in human-readable time format (`sec.nsec`). PRINTF VARIABLES ================ The `--printf-args` argument is an array of single-character isochron variable codes. The program associates, in left-to-right order, each variable code with the printf code from the format specifier in order to figure out how to print it. The variable codes understood by isochron are: `A` : advance time as defined by `isochron send --advance-time`. Can be printed using `%d`, `%u`, `%x` or `%T`. `B` : base time as defined by `isochron send --base-time`, then adjusted by the sender application using the shift time and advanced into the immediate future at the time of the test. The base time minus the advance time denotes the programmed time of the wakeup timer for the sender's first packet. Can be printed using `%d`, `%u`, `%x` or `%T`. `C` : cycle time as defined by `isochron send --cycle-time`. Can be printed using `%d`, `%u`, `%x` or `%T`. `H` : shift time as defined by `isochron send --shift-time`. Can be printed using `%d`, `%u`, `%x` or `%T`. `W` : window size as defined by `isochron send --window-size`. Can be printed using `%d`, `%u`, `%x` or `%T`. `S` : scheduled TX time of the packet (the time at which the packet must hit the wire). Can be printed using `%d`, `%u`, `%x` or `%T`. `w` : the actual value of the `CLOCK_TAI` system clock when the sender starts executing code again immediately after its wakeup timer for the packet has expired. Can be printed using `%d`, `%u`, `%x` or `%T`. `T` : TX hardware timestamp of the packet, taken by the NIC of the sender. Can be printed using `%d`, `%u`, `%x` or `%T`. `t` : TX software timestamp of the packet, taken by the NIC driver of the sender right before hardware transmission. Can be printed using `%d`, `%u`, `%x` or `%T`. `s` : TX software timestamp of the packet, taken by the network stack prior to entering the packet scheduler (qdisc). Can be printed using `%d`, `%u`, `%x` or `%T`. `q` : sequence number of the packet, starting from 1. Can be printed using `%u` or `%x`. `a` : the arrival time of the packet, i.e. the actual value of the `CLOCK_TAI` system clock when the receiver starts executing code again immediately after fully receiving the packet. Can be printed using `%d`, `%u`, `%x` or `%T`. `R` : RX hardware timestamp of the packet, taken by the NIC of the receiver. Can be printed using `%d`, `%u`, `%x` or `%T`. `r` : RX software timestamp of the packet, taken by the NIC driver right after reception from hardware. Can be printed using `%d`, `%u`, `%x` or `%T`. BUILT-IN METRICS ================ When running with the `--summary` option, isochron defines some latency related metrics and calculates the following statistics on them: maximum value, minimum value, packet sequence number associated with the min and max, mean value, standard deviation. The built-in metrics are: Path delay : R - T (HW TX timestamp to HW RX timestamp) Sender latency : t - w (actual sender wakeup time to SW TX timestamp) MAC latency : T - S (scheduled TX time to HW TX timestamp) Application latency budget ("time to spare per cycle") : S - T (time until HW TX timestamp would exceed scheduled TX time) Wakeup latency : w - (S - A) (programmed wakeup time, i.e. scheduled TX time minus advance time, to actual wakeup time) Driver latency (actually driver + qdisc latency) : t - s (pre-qdisc timestamp to driver-level software TX timestamp) Arrival latency : a - R (HW RX timestamp to application) Notice how the "MAC latency" and the "Application latency budget" is the same metric, but calculated in reverse. The data is interpreted by the application depending on whether the hardware was expected to send the packet right away, or queue it until the scheduled TX time like in the case of the tc-taprio and tc-etf qdiscs. EXAMPLES ======== To obtain the summary of the built-in metrics: ``` isochron report \ --input-file isochron.dat \ --summary ``` To see the detailed network timestamps for a single packet: ``` isochron report \ --input-file isochron.dat \ --printf-format "seqid %u scheduled for %T, TX qdisc %T sw %T hw %T, RX hw %T sw %T\n" \ --printf-args "qSstTRr" \ --start 173972 --stop 173972 ``` To export data in comma-separated value format, for calculating user-defined metrics externally: ``` isochron report \ --input-file isochron.dat \ --printf-format "%d,%d\n" \ --printf-args "TR" \ > isochron.csv ``` User-defined arithmetic on the built-in isochron variables can also be delegated to a scripting language interpreter such as Python, by configuring the isochron printf format specifier to generate output in Python syntax: ``` isochron report \ --printf-format "pdelay=%d - %d\nprint(\"path_delay[%u] =\", pdelay)\n" \ --printf-args "RTq" \ | python3 - ``` For more complex arithmetic, the per-packet internal variables can be stored inside arrays: ``` printf "wakeup_latency = {}\n" > isochron_data.py isochron report \ --printf-format "wakeup_latency[%u] = %d - (%d - %d)\n" \ --printf-args "qwSA" \ >> isochron_data.py cat << 'EOF' > isochron_postprocess.py #!/usr/bin/env python3 from isochron_data import wakeup_latency import numpy as np w = np.array(list(wakeup_latency.values())) print("Wakeup latency: min {}, max {}, mean {}, median {}, stdev {}".format(np.min(w), np.max(w), np.mean(w), np.median(w), np.std(w))) EOF python3 ./isochron_postprocess.py ``` AUTHOR ====== isochron was written by Vladimir Oltean SEE ALSO ======== isochron(8) isochron-send(8) isochron-rcv(8) COMMENTS ======== This man page was written using [pandoc](http://pandoc.org/) by the same author. isochron-0.9/docs/isochron-send.8.md000066400000000000000000000325761431034026500174140ustar00rootroot00000000000000% isochron-send(8) | ISOCHRON NAME ==== isochron-send - Start an isochron test in the role of a sender SYNOPSIS ======== **isochron** send \[_OPTIONS_\] DESCRIPTION =========== This command sends test packets using the specified transport (plain Ethernet or UDP). OPTIONS ======= `-h`, `--help` : prints the short help message and exits `-i`, `--interface` <`IFNAME`> : specify the network interface on which packets will be sent `-d`, `--dmac` <`MACADDRESS`> : specify the destination MAC address to be used for the test packets. Should coincide with the MAC address that the receiver listens for. Can be either unicast or multicast. Necessary only for the L2 transport (plain Ethernet). Optional if the `--client` option is also specified, case in which the sender can directly query the receiver for the destination MAC address it listens for. `-A`, `--smac` <`MACADDRESS`> : specify the source MAC address to be used for the test packets. Optional, defaults to the network interface's unicast address. Necessary only for the L2 transport. `-p`, `--priority` <`NUMBER`> : specify the `SO_PRIORITY` (traffic class) to communicate to the kernel for test packets. Used by qdiscs such as tc-mqprio or tc-taprio. Optional, defaults to 0. `-P`, `--stats-port` <`NUMBER`> : specify the TCP port on which the receiver program is listening for incoming connections. This socket is used for management and statistics. Optional, defaults to port 5000. `-b`, `--base-time` <`TIME`> : specify the scheduled transmission time for the first packet. This can be further shifted forward and backwards in time with the `--shift-time` argument. This time can be in the past, and in that case it is automatically advanced by an integer number of cycles until it becomes larger than the current time by at least one second. The time base is CLOCK_TAI. Optional, defaults to 0. `-a`, `--advance-time` <`TIME`> : specify the amount in advance of the scheduled packet transmission time that isochron will wake up at. Optional, defaults to the cycle time minus the window size, so that the sender will wake up at the earliest possible moment and have the longest possible amount of time for preparing for transmission. `-S`, `--shift-time` <`TIME`> : shift the base time by the specified amount of nanoseconds, either in the past or in the future. Useful when enqueuing packets into a NIC which uses a tc-taprio qdisc and the time slot corresponding to the applications's traffic class is not the first one. When used in this way, the base time of the application can be specified as equal to the base time of the tc-taprio schedule, and the shift time can be specified as the length of all time slots prior to the one in which the application should enqueue. Optional, defaults to 0. `-c`, `--cycle-time` <`TIME`> : specify the interval between consecutive wakeup times for the purpose of sending a packet. `-w`, `--window-size` <`TIME`> : in case the NIC uses a tc-taprio schedule, specify the duration in nanoseconds of the time slot corresponding to the application's priority. This will prevent isochron from waking up too early and potentially enqueuing the packet prematurely in its time slot from the previous cycle. With a correctly configured window size, the wakeup time will be set no earlier than the end of the previous time slot, making this condition impossible (assuming proper system clock synchronization). Optional, defaults to 0. `-n`, `--num-frames` <`NUMBER`> : specify the number of packets to send for this test. Optional, if left unspecified the program will run indefinitely, but will not collect logs. `-s`, `--frame-size` <`NUMBER`> : specify the size of test frames. The size is counted from the first octet of the destination MAC address until the last octet of data before the FCS. `-T`, `--no-ts` : disable the process of collecting TX timestamps. `-v`, `--vid` <`NUMBER`> : insert a VLAN header with the specified VLAN ID in the test packets. The VLAN PCP is set to be equal to the priority configured with `--priority`. This results in lower overhead compared to using a kernel VLAN interface to insert the VLAN tag. Optional, defaults to no VLAN header being inserted. `-C`, `--client` <`IPADDRESS`> : specify the IPv4 or IPv6 address at which the receiver is listening for management/statistics connections. Optional, defaults to not attempting to connect to the receiver. In this case, the sender operates in a limited mode where it does not collect logs or check for the receiver's sync status or expected destination MAC address. The receiver will also not log packets unless the sender connects to it. `-q`, `--quiet` : when not connected to the receiver's management/statistics socket, the sender will, by default, print the packets and their TX timestamps, to standard output at the end of the test. This option suppresses the print. Optional, defaults to false. `-e`, `--etype` <`NUMBER`> : specify the EtherType for test packets sent using the L2 transport. Optional, defaults to `0xdead`. `-o`, `--omit-sync` : when set, the sender will not monitor the local (and optionally remote, if `--client` is used) ptp4l and phc2sys processes for synchronization status, and will proceed to send test packets regardless. Optional, defaults to false. `-y`, `--omit-remote-sync` : when set, will only monitor the sync status of the local station. The assumption is that the receiver interface is implicitly synchronized (shares the same PHC as the sender interface), and therefore no ptp4l instance runs on it, so the sync status cannot be monitored. Optional, defaults to false. `-m`, `--tracemark` : when set, the sender will write to the kernel's ftrace buffer in order to mark the moment when it wakes up for transmitting a packet, and the moment after the packet has been enqueued into the kernel. The option is useful for debugging latency issues together with trace-cmd and kernelshark, since the packet's sequence number is logged, and therefore, latencies reported by `isochron report` can be quickly be associated with the kernel trace buffer. Optional, defaults to false. `-Q`, `--taprio` : when set, the sender will record this information to the output file. This changes the interpretation of the logged data, for example TX timestamps with tc-taprio are expected to be higher than the scheduled transmission time, otherwise they are expected to be lower. The option is expected to be set when enqueuing to a NIC where tc-taprio is used as the qdisc. `-x`, `--txtime` : when set, the sender will use the `SO_TXTIME` socket option when enqueuing packets to the kernel. This also changes the interpretation of logged data similar to `--taprio`. The TX time requested by the sender is equal to the scheduled transmission time for the packet. This option is expected to be set when enqueuing to a NIC where tc-etf is used as the qdisc. `-D`, `--deadline` : when set, this sets the `SOF_TXTIME_DEADLINE_MODE` flag for the data socket. This can only be used together with `--txtime`. This option changes the kernel's interpretation of the TX time, in that it is no longer the PTP time at which the packet should be sent, but rather the latest moment in time at which the packet should be sent. `-f`, `--sched-fifo` : when set, the program requests the kernel to change its scheduling policy to `SCHED_FIFO` for the duration of the test. `-r`, `--sched-rr` : when set, the program requests the kernel to change its scheduling policy to `SCHED_RR` for the duration of the test. `-H`, `--sched-priority` <`NUMBER`> : when either `--sched-fifo` or `--sched-rr` is used, the program requests the kernel to change its scheduling priority for the duration of the test. `-M`, `--cpu-mask` <`NUMBER`> : a bit mask of CPUs on which the sender thread is allowed to be scheduled. The other threads of the program are not affected by this selection. Optional, defaults to the CPU affinity of the isochron process. `-O`, `--utc-tai-offset` <`NUMBER`> : the program uses the `CLOCK_TAI` time base for its timers and for all reported timestamps, and this option specifies the correction in seconds to apply to software timestamps, which are taken by the kernel in the `CLOCK_REALTIME` (UTC) time base. If this option is present, isochron will also change the kernel's `CLOCK_TAI` offset to the specified value, to ensure that its timers fire correctly. If the option is absent, isochron queries the kernel's `CLOCK_TAI` offset and attempts to use that. If isochron can also query the UTC offset from ptp4l's `TIME_PROPERTIES_DATA_SET` using management messages, it does that and compares that offset to the kernel's UTC offset. The UTC offset reported by ptp4l has the highest priority, and if the application detects that this is different from the kernel's `CLOCK_TAI` offset, it changes the kernel offset to the value queried from ptp4l. `-J`, `--ip-destination` <`IPADDRESS`> : this option specifies the IPv4 or IPv6 address of the receiver, which will be placed in the test packet datagrams. Mandatory if the UDP transport is used. Note that when using the UDP transport, the destination IP address should have a static entry in the kernel's IP neighbor table, to avoid unpredictable latencies caused by the kernel's neighbor resolution process. The isochron program does not have control over which interface will be used for sending the test packets, so the user should ensure that the kernel's routing table will select the correct interface for this destination IP address. `-2`, `--l2` : this option specifies that the plain Ethernet transport should be used for the test packets. Optional, defaults to true. Cannot be used together with `--l4`. `-4`, `--l4` : this option specifies that the UDP transport should be used for test packets. Optional, defaults to false. Cannot be used together with `--l2`. `-W`, `--data-port` <`NUMBER`> : if the UDP transport is used, this option specifies the destination UDP port for test packets. Optional, defaults to 6000. `-U`, `--unix-domain-socket` <`PATH`> : isochron queries ptp4l's state by creating and sending PTP management messages over a local UNIX domain socket. This option specifies the path of this socket in the filesystem. Optional, defaults to `/var/run/ptp4l`. `-N`, `--domain-number` <`NUMBER`> : this option provides the domainNumber value to be used when constructing PTP management messages sent to the ptp4l process. It must coincide with the domainNumber used by ptp4l, otherwise it will not respond to management messages. Optional, defaults to 0. `-t`, `--transport-specific` <`NUMBER`> : this option provides the transportSpecific value to be used when constructing PTP management messages sent to the ptp4l process. It must coincide with the transportSpecific used by ptp4l, otherwise it will not respond to management messages. Optional, defaults to 0. Note that PTP variants such as IEEE 802.1AS/gPTP require this value to be set to a different value such as 1. `-X`, `--sync-threshold` <`TIME`> : when the program is configured to monitor the sync status of ptp4l and phc2sys, this option specifies the positive threshold in nanoseconds by which the absolute offset reported by these external programs is qualified as sufficient to start the test. Mandatory unless `--omit-sync` is specified. `-R`, `--num-readings` <`NUMBER`> : isochron monitors the synchronization quality between the NIC's PTP Hardware Clock (PHC) and the system time by successively reading the system time, the PHC time and the system time again, several times in a row, and picking the group of 3 time readouts that took the least amount of time overall. This option specifies how many readouts should be performed before picking the fastest one. Optional, defaults to 5. `-F`, `--output-file` <`PATH`> : save the packet timestamps to a file that can be queried at a later time using `isochron report`. Defaults to "isochron.dat". This requires the `--client` option, since logging only TX timestamps is not supported. EXAMPLES ======== To start an isochron sender with PTP synchronization and a tc-taprio qdisc: ``` ip link set eth0 up && ip addr add 192.168.100.1/24 dev eth0 ptp4l -i eth0 -2 -P --step_threshold 0.00002 & phc2sys -a -rr --step_threshold 0.00002 & tc qdisc add dev eth0 root taprio num_tc 5 \ map 0 1 2 3 4 \ queues 1@0 1@1 1@2 1@3 1@4 \ base-time 0 \ sched-entry S 10 50000 \ sched-entry S 0f 450000 \ flags 2 taskset $((1 << 0)) isochron send \ --cpu-mask $((1 << 1)) \ --interface eth0 \ --cycle-time 0.0005 \ --frame-size 64 \ --num-frames 1000000 \ --client 192.168.100.2 \ --quiet \ --sync-threshold 2000 \ --output-file isochron.dat \ --taprio \ --priority 4 \ --sched-rr \ --sched-priority 98 \ --window-size 50000 ``` AUTHOR ====== isochron was written by Vladimir Oltean SEE ALSO ======== isochron(8) isochron-rcv(8) isochron-report(1) COMMENTS ======== This man page was written using [pandoc](http://pandoc.org/) by the same author. isochron-0.9/docs/isochron.8.md000066400000000000000000000227021431034026500164530ustar00rootroot00000000000000% isochron(8) | ISOCHRON NAME ==== isochron - Time sensitive network testing tool SYNOPSIS ======== **isochron** _VERB_ \[_OPTIONS_\] _VERB_ := { daemon | orchestrate | send | rcv | report } DESCRIPTION =========== The isochron is a Linux user space application for testing timing characteristics of endpoints and bridges in Ethernet networks. The tool works by sending network packets between one instance of the program (the sender) and another (the receiver) and taking timestamps at various points along the way. The receiver has a second network socket through which it transmits its collected timestamps back towards the sender. The sender aggregates all timestamps and optionally records them to a file which can be queried at a later time. Timestamps are taken in 4 different time bases which must be in sync with one another: * Software timestamping using the sender's system clock * Hardware timestamping inside the sender's NIC * Hardware timestamping inside the receiver's NIC * Software timestamping using the receiver's system clock To ensure that direct comparison between timestamps from different time bases is possible, the program monitors the synchronization status of external programs like ptp4l (which synchronizes a local NIC to a remote NIC) and phc2sys (which synchronizes the system clock to a local NIC) from the linuxptp project. Isochron (isochronous traffic == running at equal intervals) is intended to be used in conjunction with Time Sensitive Networking equipment as defined by IEEE 802.1 and IEEE 802.3. The measurements can be used for profiling the software latency of the endpoints or the forwarding latency of switches. The isochron program, in both the sender and the receiver role, requires a network card with the ability to perform hardware timestamping of any packet, since the isochron test packets are not PTP event messages. If used in a time sensitive network, isochron expects to be the exclusive owner of a traffic class, and it must be informed of that that traffic class' time slot length and offset within the global schedule. Below is an example where isochron uses priority 5, ptp4l uses priority 7, these two traffic classes have media reservations using a time-aware shaper, and the other applications are classified as best-effort and go towards their own time slot. ``` base-time base-time + cycle-time |---------------------------------|---------------------------------| Ethernet media reservation: |xxxxxxxxxxxxxxxxxxxxxxx|---------|xxxxxxxxxxxxxxxxxxxxxxx|---------| tc012346 <---------------------> <---------------------> best effort best effort |-----------------------|xxxx|----|-----------------------|xxxx|----| tc5 <--> <--> isochron isochron |----------------------------|xxxx|----------------------------|xxxx| tc7 <--> <--> ptp4l ptp4l ``` Focusing on a single time slot (isochron's traffic class 5), the diagram below overlaps a few timelines in order to detail how isochron interprets the command line arguments and how the time is spent: ``` cycle (N - 1) cycle N cycle (N + 1) base-time - cycle-time base-time base-time + cycle-time | | | | Media reservation | | | for isochron | | v v v |-----------------------|xxxx|----|-----------------------|xxxx|----|--> | window T0 T1 | window T2 T3 | | size <----> | size <----> | | | | | The shift time specifies | | | the offset of the time slot | shift-time (H) | | from the beginning of the cycle |---------------------->| | | | | | |-----------------------|xxxx|----|-----------------------|xxxx|----|--> | | | | The advance time specifies | | | how much in advance of the | advance-time (A) | | deadline to wake up |<---------------------------| | | | | | | |-----------------------|xxxx|----|-----------------------|xxxx|----|--> | | | | isochron schedules a wakeup | | | at application base time b and | | | actually wakes up at time w | | | | | |----------------------------|xxxxxxx|------------------------------|--> | b | w | | | | | The time spent by isochron from | | | wakeup until it enqueues a | | | packet is negligible and not | | | measured. The next timestamp | | | is the pre-qdisc software | | | TX timestamp (s). | | | | | |---------------------------------|--|xxxxx|------------------------|--> | | w s | | isochron requests the NIC | | | driver to take a software TX | | | timestamp (t). | | | | | |---------------------------------|--------|xxxxx|------------------|--> | | s t | | isochron requests the NIC | | | driver to take a hardware TX | | | timestamp (T). | | | There are multiple cases. | | | | | | If no TSN qdisc is used, the | | | packet is transmitted right | | | away, earlier than the | | | scheduled TX time if all | | | is well. | | | | T2 T3 | |---------------------------------|-----------------------|xxxx|----|--> |---------------------------------|--------------|xxxxx|------------|--> | | t T | | If tc-taprio is used, the | | | packet is transmitted as soon | | | as the MAC gate opens (which | | | may be a few hundred ns later | | | than the scheduled TX time). | | | | T2 T3 | |---------------------------------|-----------------------|xxxx|----|--> |---------------------------------|--------------|xxxxxxxxx|--------|--> | | t T ~= S | | If tc-etf is used, the | | | packet is transmitted according | | | to the specified SO_TXTIME cmsg | | | cmsg, and there is no MAC gate | | | open/close event per se. | | | | | |---------------------------------|--------------|xxxxxxxxx|--------|--> | | t T ~= S | T0, T2: MAC gate open events for cyclic scheduled traffic T1, T3: MAC gate close events ``` When a regular qdisc is used, the deadline which needs to be satisfied by isochron is `(T < S)`. When the tc-taprio qdisc is used, the deadline is `(t < T2)`. When the tc-etf qdisc is used, the deadline is `(s + delta < S)`, where `delta` is the "fudge factor" of the `tc-etf` qdisc. BUGS ==== Versions starting with `0.x` should be expected to be unstable, and not necessarily maintain binary compatibility on the network (between sender and receiver) or on the filesystem (the format of the report file). When using an unstable version, it is expected that the producer and the consumer of the data are running the same version of isochron. AUTHOR ====== isochron was written by Vladimir Oltean SEE ALSO ======== isochron-daemon(8) isochron-orchestrate(1) isochron-send(8) isochron-rcv(8) isochron-report(1) taprio(8) etf(8) COMMENTS ======== This man page was written using [pandoc](http://pandoc.org/) by the same author. isochron-0.9/frer/000077500000000000000000000000001431034026500141425ustar00rootroot00000000000000isochron-0.9/frer/8021cb-board1.json.template000066400000000000000000000013641431034026500207200ustar00rootroot00000000000000{ "rules": [ { "match": { "ingress-port-mask": [ "swp4" ], "dmac": "%BOARD2_MAC_ADDRESS%", "vid": "%BOARD1_VID%" }, "action": { "type": "split", "egress-port-mask": [ "swp0", "swp1" ] } }, { "match": { "ingress-port-mask": [ "swp4" ], "dmac": "%BOARD3_MAC_ADDRESS%", "vid": "%BOARD1_VID%" }, "action": { "type": "split", "egress-port-mask": [ "swp0", "swp1" ] } }, { "match": { "egress-port": "swp4", "dmac": "%BOARD1_MAC_ADDRESS%", "vid": "%BOARD2_VID%" }, "action": { "type": "recover" } }, { "match": { "egress-port": "swp4", "dmac": "%BOARD1_MAC_ADDRESS%", "vid": "%BOARD3_VID%" }, "action": { "type": "recover" } } ] } isochron-0.9/frer/8021cb-board2.json.template000066400000000000000000000013641431034026500207210ustar00rootroot00000000000000{ "rules": [ { "match": { "ingress-port-mask": [ "swp4" ], "dmac": "%BOARD1_MAC_ADDRESS%", "vid": "%BOARD2_VID%" }, "action": { "type": "split", "egress-port-mask": [ "swp0", "swp1" ] } }, { "match": { "ingress-port-mask": [ "swp4" ], "dmac": "%BOARD3_MAC_ADDRESS%", "vid": "%BOARD2_VID%" }, "action": { "type": "split", "egress-port-mask": [ "swp0", "swp1" ] } }, { "match": { "egress-port": "swp4", "dmac": "%BOARD2_MAC_ADDRESS%", "vid": "%BOARD1_VID%" }, "action": { "type": "recover" } }, { "match": { "egress-port": "swp4", "dmac": "%BOARD2_MAC_ADDRESS%", "vid": "%BOARD3_VID%" }, "action": { "type": "recover" } } ] } isochron-0.9/frer/8021cb-board3.json.template000066400000000000000000000013641431034026500207220ustar00rootroot00000000000000{ "rules": [ { "match": { "ingress-port-mask": [ "swp4" ], "dmac": "%BOARD1_MAC_ADDRESS%", "vid": "%BOARD3_VID%" }, "action": { "type": "split", "egress-port-mask": [ "swp0", "swp1" ] } }, { "match": { "ingress-port-mask": [ "swp4" ], "dmac": "%BOARD2_MAC_ADDRESS%", "vid": "%BOARD3_VID%" }, "action": { "type": "split", "egress-port-mask": [ "swp0", "swp1" ] } }, { "match": { "egress-port": "swp4", "dmac": "%BOARD3_MAC_ADDRESS%", "vid": "%BOARD1_VID%" }, "action": { "type": "recover" } }, { "match": { "egress-port": "swp4", "dmac": "%BOARD3_MAC_ADDRESS%", "vid": "%BOARD2_VID%" }, "action": { "type": "recover" } } ] } isochron-0.9/frer/8021cb-load-config.sh000077500000000000000000000307131431034026500175640ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: BSD-3-Clause # Copyright 2019 NXP set -e -u -o pipefail total_port_list= total_vid_list= usage() { echo "Usage:" echo "$0: read from stdin" echo "$0 -h|--help: show usage" echo "$0 -f|--file : read from .json file" exit } error() { local lineno="$1" local code="${2:-1}" echo "Error on line ${lineno}; status ${code}, exiting." exit "${code}" } trap 'error ${LINENO}' ERR tsntool_bin=$(which tsntool) O=`getopt -l help,file: -- hf: "$@"` || exit 1 eval set -- "$O" while true; do case "$1" in -h|--help) usage; exit 0;; -f|--file) file="$2"; shift 2;; --) shift; break;; *) echo "unrecognized argument $1"; exit 1;; esac done if [[ -z "${file+x}" ]]; then usage fi if ! [[ -f ${file} ]]; then echo "${file}: No such file or directory" exit 1 else json=$(jq "." ${file}) fi strip_quotes() { sed -e 's|"||g' } tsntool_macaddr() { local macaddr=$1 local awk_program=' \ { \ split($1, m, ":"); \ print "0x" m[1] m[2] m[3] m[4] m[5] m[6]; \ }' echo "${macaddr}" | awk "${awk_program}" } tsntool() { echo tsntool $@ ${tsntool_bin} $@ } clear_stream_table() { for eth in swp0 swp1 swp2 swp3 swp4; do for ssid in $(seq 0 127); do tsntool cbstreamidset --index $ssid --nullstreamid \ --streamhandle $ssid --device $eth \ --disable >/dev/null || : done done } # Recommended read: Figure 16-23. Overview of Per-Stream Filtering and Policing # (Qci) from LS1028ARM.pdf # # Stream Identity Table Stream Filter Instance Table # (aka cbstreamidset) (aka qcisfiset) # +-----------+-----------+---------------+ +---------------+-----------+--------+-------+------+ # | Port list | Stream ID | Stream Handle | | Stream Handle | Port list | Filter | Meter | Gate | # +-----------+-----------+---------------+ +---------------+-----------+--------+-------+------+ # | 1 | NULL | 1234 |--+----->| 1234 | 1 | xxxxxx | 5 | 11 | # | ... | ... | ... | | | ... | ... | ... | ... | ... | # | 3 | NULL | 1357 |-------->| 1357 | 3 | yyyyyy | 29 | 11 | # | 2 | SMAC/VLAN | 5678 |-------->| 5678 | 2 | zzzzzz | 29 | 43 | # | ... | ... | ... | | | ... | ... | ... | ... | ... | # | 1 | NULL | 1234 |--+ +---------------+-----------+--------+-------+------+ # +-----------+-----------+---------------+ | | # | | # +---------------------------------------------------------------+ | # | +----------------------------------+ # | Flow Meter Instance Table | Stream Gate Instance Table # | (aka qcifmiset) | (aka qcisgiset) # | +----------+-------------------+ | +---------+------------+-----------+ # | | Meter ID | Meter Parameters | | | Gate ID | Gate State | Gate List | # | +----------+-------------------+ | +---------+------------+-----------+ # +->| 29 | CIR, CBS, EIR etc | +->| 11 | Open | 0..n | # | 5 | CIR, CBS, EIR etc | | 43 | Closed | 0..m | # +----------+-------------------+ +---------+------------+-----------+ # # The above is valid for both Felix and ENETC. # On Felix only, the entries in the Stream Identity Table have not only a SFID # for indexing the Stream Filter Instance Table, but also an optional SSID for # indexing the Seamless Stream Table # # Seamless Stream Table with GEN_REC_TYPE=0 (Generation) # +-------------+---------------------+----------------+----------------+----------------+------------+ # | Input ports | Enable stream split | Enable Seq Gen | Starting seqid | Seqid num bits | Split mask | # +-------------+---------------------+----------------+----------------+----------------+------------+ # | | | | | | | # | | | | | | | # | | | | | | | # +-------------+---------------------+----------------+----------------+----------------+------------+ # # Seamless Stream Table with GEN_REC_TYPE=1 (Recovery) # +---------------------+----------------+-------------+-----------------+-----------+----------------+---------------------+ # | Enable Seq Recovery | Seqid num bits | Seq history | Seq history len | R-Tag Pop | Reset on Rogue | Force Store/Forward | # +---------------------+----------------+-------------+-----------------+-----------+----------------+---------------------+ # | | | | | | | | # | | | | | | | | # | | | | | | | | # +---------------------+----------------+-------------+-----------------+-----------+----------------+---------------------+ stream_rules=() split_actions=() recover_actions=() add_stream_rule() { local ssid="$1" local match="$2" local egress_port="$3" local dmac="$(echo ${match} | jq '.dmac' | strip_quotes)" local vid="$(echo ${match} | jq '.vid' | strip_quotes)" # Configure a Seamless Stream IDs (SSID) for outbound traffic on this # port. stream_rules+=("tsntool cbstreamidset --device ${egress_port} --nullstreamid \ --nulldmac $(tsntool_macaddr ${dmac}) --nullvid ${vid} \ --streamhandle ${ssid} --index ${ssid} --enable") total_port_list="${total_port_list} ${egress_port}" total_vid_list="${total_vid_list} ${vid}" printf 'Stream rule %d: match {DMAC %s, VID %d} towards port %s\n' \ "${ssid}" "${dmac}" "${vid}" "${egress_port}" } add_split_action() { local ssid="$1" local ingress_port_mask="$2" local egress_port_mask="$3" local ingress_ports="" local split_ports="" local iport_mask=0 local split_mask=0 local seq_len=16 local seq_num=0 for swp in $(echo "${ingress_port_mask}" | jq -r -c '.[]'); do local chip_port="${swp#swp}" ingress_ports="${ingress_ports} ${swp}" iport_mask=$((${iport_mask} | $((1 << chip_port)))) total_port_list="${total_port_list} ${swp}" done for swp in $(echo "${egress_port_mask}" | jq -r '.[]'); do local chip_port="${swp#swp}" split_ports="${split_ports} ${swp}" split_mask=$((${split_mask} | $((1 << chip_port)))) total_port_list="${total_port_list} ${swp}" done # The switch port specified as parameter to --device here does not # matter, it is simply an anchor for tsntool to talk to the switch # driver. As a convention, swp0 will be used. # # The --index option points to the SSID for which the generation rule is # applied. It must match the --streamhandle option for cbstreamidset. # # The --iport_mask specifies on which ingress switch ports this # sequence generation rule will match. This is in contrast with # --device swp3 specified to cbstreamidset, which specifies the egress # switch port. By specifying e.g. the 0x3f port mask, the rule will # match traffic coming from any ingress port. # # The --split_mask argument configures the egress ports onto which this # stream generation rule will replicate the packets. This is in # addition to the standard L2 forwarding rules. split_actions+=("tsntool cbgen --device swp0 --index ${ssid} \ --seq_len ${seq_len} --seq_num ${seq_num} \ --iport_mask ${iport_mask} --split_mask ${split_mask}") printf 'Split action for rule %d: ingress ports: %s split ports: %s\n' \ "${ssid}" "${ingress_ports}" "${split_ports}" } add_recover_action() { local ssid="$1" local passthrough="$2" local seq_len=16 local his_len=31 if [ $passthrough = true ]; then local opts="" local label="Passthrough" else local opts="--rtag_pop_en" local label="Sequence recovery" fi # The port does not matter recover_actions+=("tsntool cbrec --device swp0 --index ${ssid} \ --seq_len ${seq_len} --his_len ${his_len} \ ${opts}") printf '%s action for rule %d\n' \ "${label}" "${ssid}" } # The felix driver is very picky about the order of the tsntool commands. To be # precise, the actions (cbgen, cbrec) can't come before the match # (cbstreamidset). But this is chicken-and-egg, because we need to parse the # action for cbgen to figure out the --device for cbstreamidset. # So make it a 2-part system. The add_stream_rule, add_split_action and # add_recover_action functions just add to their respective arrays, and apply # them in the required order here. apply_tsntool_commands() { for stream_rule in "${stream_rules[@]}"; do $stream_rule done for split_action in "${split_actions[@]}"; do $split_action done for recover_action in "${recover_actions[@]}"; do $recover_action done } do_bridging() { [ -d /sys/class/net/br0 ] && ip link del dev br0 ip link add name br0 type bridge stp_state 0 vlan_filtering 1 ip link set br0 arp off ip link set br0 up for swp in swp0 swp1 swp2 swp3 swp4; do ip link set dev ${swp} master br0 # No pvid by default, so no untagged communication. bridge vlan del vid 1 dev ${swp} done } add_passthrough_vlans() { local vlan_list="$1" for vid in $(echo "${vlan_list}" | jq -r -c '.[]'); do echo "Passthrough vid $vid" total_vid_list="${total_vid_list} ${vid}" done } limit_rogue_traffic() { local iface="$1" ip link set dev ${iface} multicast off echo 1 > /proc/sys/net/ipv6/conf/${iface}/disable_ipv6 } drop_looped_traffic() { local iface="$1" local this_host=$(ip link show dev eno2 | awk '/link\/ether/ {print $2; }') if tc qdisc show dev ${iface} | grep clsact; then tc qdisc del dev ${iface} clsact; fi tc qdisc add dev ${iface} clsact tc filter add dev ${iface} ingress flower skip_sw dst_mac ff:ff:ff:ff:ff:ff action drop tc filter add dev ${iface} ingress flower skip_sw src_mac ${this_host} action drop } install_vlans() { # Remove duplicates total_port_list=$(echo -e "${total_port_list// /\\n}" | sort -u) total_vid_list=$(echo -e "${total_vid_list// /\\n}" | sort -u) for vid in ${total_vid_list}; do for swp in ${total_port_list}; do bridge vlan add dev ${swp} vid ${vid} done done for port in ${total_port_list}; do # Don't drop traffic coming from the CPU port if [ ${port} = swp4 ]; then continue fi drop_looped_traffic "${port}" done } do_bridging clear_stream_table limit_rogue_traffic eno2 num_rules=$(jq '.rules|length' <<< "${json}") for i in `seq 0 $((${num_rules}-1))`; do rule=$(jq ".rules[$i]" <<< "${json}") match=$(echo ${rule} | jq '.match') action=$(echo ${rule} | jq '.action') action_type=$(echo ${action} | jq ".type" | strip_quotes) # Add the action first, we need to fix up the match with info from it. case ${action_type} in "split") ingress_port_mask=$(echo "${match}" | jq -r '.["ingress-port-mask"]') egress_port_mask=$(echo "${action}" | jq -r '.["egress-port-mask"]') add_split_action "${i}" "${ingress_port_mask}" "${egress_port_mask}" # It is weird that for splitting, one port of the actions's # egress port mask also needs to be specified as part of the # match. Hardware quirk. # Instead of taking the egress-port property from the match # rule as we do for recovery, take it to be the first port from # the egress-port-mask of the action. egress_port=$(echo "${egress_port_mask}" | jq -r -c '.[0]') ;; "recover") add_recover_action "${i}" false egress_port=$(echo "${match}" | jq -r '.["egress-port"]') ;; "passthrough") add_recover_action "${i}" true egress_port=$(echo "${match}" | jq -r '.["egress-port"]') ;; *) echo "Invalid action type $action_type" exit 1 esac add_stream_rule "${i}" "${match}" "${egress_port}" done apply_tsntool_commands passthrough_vlans=$(jq '.["passthrough-vlans"]' <<< "${json}") if ! [ "${passthrough_vlans}" = "null" ]; then add_passthrough_vlans "${passthrough_vlans}" fi install_vlans isochron-0.9/frer/8021cb.sh000077500000000000000000000122661431034026500154070ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: BSD-3-Clause # Copyright 2019 NXP set -e -u -o pipefail export TOPDIR=$(cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd) # +--------------------------------+ # Board 3: | | # | | # +------------------------------|----------------------------+ | # | | | | # | +--------+ +--------+ +--------+ +--------+ +--------+ | | # | | | | | | | | | | | | | # | | MAC0 | | SWP0 | | SWP1 | | SWP2 | | SWP3 | | | # | | | | | | | | | | | | | # +-+--------+----+--------+-+--------+-+--------+-+--------+-+ | # | | # | | # +----------+ | # Board 2: | | # | | # +------------------------------|----------------------------+ | # | | | | # | +--------+ +--------+ +--------+ +--------+ +--------+ | | # | | | | | | | | | | | | | # | | MAC0 | | SWP0 | | SWP1 | | SWP2 | | SWP3 | | | # | | | | | | | | | | | | | # +-+--------+----+--------+-+--------+-+--------+-+--------+-+ | # | | # | | # +----------+ | # Board 1: | | # | | # +------------------------------|----------------------------+ | # | | | | # | +--------+ +--------+ +--------+ +--------+ +--------+ | | # | | | | | | | | | | | | | # | | MAC0 | | SWP0 | | SWP1 | | SWP2 | | SWP3 | | | # | | | | | | | | | | | | | # +-+--------+----+--------+-+--------+-+--------+-+--------+-+ | # | | # | | # +-------------------------------------------+ usage() { echo "Usage:" echo "$0 1|2|3" return 1 } board1_ip_address="172.15.0.1" board2_ip_address="172.15.0.2" board3_ip_address="172.15.0.3" board1_mac_address="00:04:9f:63:35:ea" board2_mac_address="00:04:9f:63:35:eb" board3_mac_address="00:04:9f:63:35:ec" board1_vid="101" board2_vid="102" board3_vid="103" # 1 -> 2: split at B1.SWP1, recover at B2.SWP4 # 1 -> 3: split at B1.SWP0, recover at B3.SWP4 # 2 -> 1: split at B2.SWP1, recover at B1.SWP4 # 2 -> 3: split at B2.SWP2, recover at B3.SWP4 # 3 -> 1: split at B3.SWP0, recover at B3.SWP4 # 3 -> 2: split at B3.SWP2, recover at B3.SWP4 [ $# = 1 ] || usage num=$1; shift eval $(echo my_ip=\$board${num}_ip_address) eval $(echo my_mac=\$board${num}_mac_address) eval $(echo my_vid=\$board${num}_vid) ip link set dev eno2 address ${my_mac} ip addr flush dev eno2 && ip addr add ${my_ip}/24 dev eno2 sed -e "s|%BOARD1_MAC_ADDRESS%|${board1_mac_address}|g" \ -e "s|%BOARD2_MAC_ADDRESS%|${board2_mac_address}|g" \ -e "s|%BOARD3_MAC_ADDRESS%|${board3_mac_address}|g" \ -e "s|%BOARD1_VID%|${board1_vid}|g" \ -e "s|%BOARD2_VID%|${board2_vid}|g" \ -e "s|%BOARD3_VID%|${board3_vid}|g" \ ${TOPDIR}/8021cb-board${num}.json.template > \ ${TOPDIR}/8021cb-board${num}.json ${TOPDIR}/8021cb-load-config.sh -f ${TOPDIR}/8021cb-board${num}.json echo "Adding VLAN mangling rules (see with 'tc filter show dev eno2 egress && tc filter show dev eno2 ingress')" if tc qdisc show dev eno2 | grep clsact; then tc qdisc del dev eno2 clsact; fi tc qdisc add dev eno2 clsact tc filter add dev eno2 egress flower \ dst_mac $board1_mac_address \ action vlan push id $my_vid tc filter add dev eno2 egress flower \ dst_mac $board2_mac_address \ action vlan push id $my_vid tc filter add dev eno2 egress flower \ dst_mac $board3_mac_address \ action vlan push id $my_vid tc filter add dev eno2 protocol 802.1Q ingress flower \ dst_mac $my_mac vlan_id $board1_vid \ action vlan pop tc filter add dev eno2 protocol 802.1Q ingress flower \ dst_mac $my_mac vlan_id $board2_vid \ action vlan pop tc filter add dev eno2 protocol 802.1Q ingress flower \ dst_mac $my_mac vlan_id $board3_vid \ action vlan pop # Accept all VLAN tags on ingress ethtool -K eno2 rx-vlan-filter off echo "Populating the ARP table..." for board in 1 2 3; do if [ ${board} = ${num} ]; then continue fi eval $(echo other_mac=\$board${board}_mac_address) eval $(echo other_ip=\$board${board}_ip_address) arp -s ${other_ip} ${other_mac} dev eno2 done echo "Ready to send/receive traffic. IP address of board is ${my_ip}" isochron-0.9/frer/README.md000066400000000000000000000242621431034026500154270ustar00rootroot00000000000000IEEE 802.1CB ============ This portion contains a demonstration of 802.1CB on the NXP LS1028A-RDB board. It assumes running on a kernel patched to support the tsntool genetlink, like the one below: https://source.codeaurora.org/external/qoriq/qoriq-components/linux/log/?h=lf-5.15.y Assuming a Buildroot/OpenIL user space, perform the following changes to `nxp_ls1028ardb-64b_defconfig`: ``` diff --git a/configs/nxp_ls1028ardb-64b_defconfig b/configs/nxp_ls1028ardb-64b_defconfig index ecd87d5b20c5..5a13571f3874 100644 --- a/configs/nxp_ls1028ardb-64b_defconfig +++ b/configs/nxp_ls1028ardb-64b_defconfig @@ -171,3 +171,7 @@ BR2_PACKAGE_IPERF=y BR2_TOOLCHAIN_HAS_THREADS=y BR2_PACKAGE_IPERF3=y BR2_PACKAGE_TCPDUMP=y +BR2_PACKAGE_RSYNC=y +BR2_PACKAGE_JQ=y + +BR2_GLOBAL_PATCH_DIR="board/nxp/ls1028ardb/patches/" ``` Add the tsn-scripts patches to the OpenIL kernel for LS1028A-RDB: ``` rsync -avr deps/patches /path/to/openil/board/nxp/ls1028ardb/ ``` Build with: ``` make nxp_ls1028ardb-64b_defconfig O=output-ls1028ardb make O=output-ls1028ardb | tee build.log ``` Copy this folder to the home directory of the root user on the three boards. You can either do that after build, offline, or transfer the scripts over the network. Offline: ``` [tsn-scripts] # rsync -avr . /path/to/openil/output-ls1028ardb/target/root/tsn-scripts # Run "make" again to integrate the files newly added to the rootfs into the # sdcard.img [openil] # make O=output-ls1028ardb ``` Online: ``` [root@board1] # ip addr add 10.0.0.111/24 dev eno0 [root@board2] # ip addr add 10.0.0.112/24 dev eno0 [root@board3] # ip addr add 10.0.0.113/24 dev eno0 [tsn-scripts] # for board in 10.0.0.111 10.0.0.112 10.0.0.113; do rsync -avr . root@${board}:./tsn-scripts/; done ``` On each board, make sure that all Ethernet ports are up first. Typically this is the task of a network manager (and for OpenIL it is already done), but if you are not running one, it must be done manually: ``` [root@OpenIL]# for eth in eno2 eno3 swp0 swp1 swp4; do ip link set dev $eth up; done [root@board1]# ./tsn-scripts/8021cb.sh 1 [root@board2]# ./tsn-scripts/8021cb.sh 2 [root@board3]# ./tsn-scripts/8021cb.sh 3 ``` Expected output from one board: ``` [root@LS1028ARDB ~] # ./tsn-scripts/8021cb.sh 1 [ 1019.200668] 000: device swp4 left promiscuous mode [ 1019.200751] 000: br0: port 5(swp4) entered disabled state [ 1019.241530] 000: device swp3 left promiscuous mode [ 1019.241716] 000: br0: port 4(swp3) entered disabled state [ 1019.273439] 001: device swp2 left promiscuous mode [ 1019.273619] 001: br0: port 3(swp2) entered disabled state [ 1019.305353] 001: device swp1 left promiscuous mode [ 1019.305513] 001: br0: port 2(swp1) entered disabled state [ 1019.337409] 001: device swp0 left promiscuous mode [ 1019.337422] 001: device eno3 left promiscuous mode [ 1019.337639] 001: br0: port 1(swp0) entered disabled state [ 1019.475234] 001: br0: port 1(swp0) entered blocking state [ 1019.475269] 001: br0: port 1(swp0) entered disabled state [ 1019.475534] 001: device swp0 entered promiscuous mode [ 1019.475538] 001: device eno3 entered promiscuous mode [ 1019.475657] 001: br0: port 1(swp0) entered blocking state [ 1019.475663] 001: br0: port 1(swp0) entered forwarding state [ 1019.481086] 001: br0: port 2(swp1) entered blocking state [ 1019.481120] 001: br0: port 2(swp1) entered disabled state [ 1019.481599] 001: device swp1 entered promiscuous mode [ 1019.481810] 001: br0: port 2(swp1) entered blocking state [ 1019.481817] 001: br0: port 2(swp1) entered forwarding state [ 1019.487008] 001: br0: port 3(swp2) entered blocking state [ 1019.487040] 001: br0: port 3(swp2) entered disabled state [ 1019.487250] 001: device swp2 entered promiscuous mode [ 1019.493268] 001: br0: port 4(swp3) entered blocking state [ 1019.493296] 001: br0: port 4(swp3) entered disabled state [ 1019.493516] 001: device swp3 entered promiscuous mode [ 1019.498647] 001: br0: port 5(swp4) entered blocking state [ 1019.498681] 001: br0: port 5(swp4) entered disabled state [ 1019.498901] 001: device swp4 entered promiscuous mode [ 1019.499044] 001: br0: port 5(swp4) entered blocking state [ 1019.499050] 001: br0: port 5(swp4) entered forwarding state Split action for rule 0: ingress ports: swp4 split ports: swp0 swp1 Stream rule 0: match {DMAC 00:04:9f:63:35:eb, VID 101} towards port swp0 Split action for rule 1: ingress ports: swp4 split ports: swp0 swp1 Stream rule 1: match {DMAC 00:04:9f:63:35:ec, VID 101} towards port swp0 Sequence recovery action for rule 2 Stream rule 2: match {DMAC 00:04:9f:63:35:ea, VID 102} towards port swp4 Sequence recovery action for rule 3 Stream rule 3: match {DMAC 00:04:9f:63:35:ea, VID 103} towards port swp4 tsntool cbstreamidset --device swp0 --nullstreamid --nulldmac 0x00049f6335eb --nullvid 101 --streamhandle 0 --index 0 --enable null stream identify, tagged is 0 echo reply:swp0 echo reply:0 tsntool cbstreamidset --device swp0 --nullstreamid --nulldmac 0x00049f6335ec --nullvid 101 --streamhandle 1 --index 1 --enable null stream identify, tagged is 0 echo reply:swp0 echo reply:0 tsntool cbstreamidset --device swp4 --nullstreamid --nulldmac 0x00049f6335ea --nullvid 102 --streamhandle 2 --index 2 --enable null stream identify, tagged is 0 echo reply:swp4 echo reply:0 tsntool cbstreamidset --device swp4 --nullstreamid --nulldmac 0x00049f6335ea --nullvid 103 --streamhandle 3 --index 3 --enable null stream identify, tagged is 0 echo reply:swp4 echo reply:0 tsntool cbgen --device swp0 --index 0 --seq_len 16 --seq_num 0 --iport_mask 16 --split_mask 3 echo reply:swp0 echo reply:0 tsntool cbgen --device swp0 --index 1 --seq_len 16 --seq_num 0 --iport_mask 16 --split_mask 3 echo reply:swp0 echo reply:0 tsntool cbrec --device swp0 --index 2 --seq_len 16 --his_len 31 --rtag_pop_en echo reply:swp0 echo reply:0 tsntool cbrec --device swp0 --index 3 --seq_len 16 --his_len 31 --rtag_pop_en echo reply:swp0 echo reply:0 Adding VLAN mangling rules (see with 'tc filter show dev eno2 egress && tc filter show dev eno2 ingress') Populating the ARP table... Ready to send/receive traffic. IP address of board is 172.15.0.1 ``` Then follow the commands printed by the scripts above. In the OpenIL rootfs, tcpdump and iperf3 are installed by default. Ping test: ``` [root@board1 ~] # ping 172.15.0.2 PING 172.15.0.2 (172.15.0.2): 56 data bytes 64 bytes from 172.15.0.2: seq=2 ttl=64 time=0.443 ms 64 bytes from 172.15.0.2: seq=3 ttl=64 time=0.368 ms 64 bytes from 172.15.0.2: seq=4 ttl=64 time=0.403 ms 64 bytes from 172.15.0.2: seq=5 ttl=64 time=0.378 ms 64 bytes from 172.15.0.2: seq=6 ttl=64 time=0.369 ms 64 bytes from 172.15.0.2: seq=7 ttl=64 time=0.374 ms 64 bytes from 172.15.0.2: seq=8 ttl=64 time=0.374 ms 64 bytes from 172.15.0.2: seq=9 ttl=64 time=0.401 ms 64 bytes from 172.15.0.2: seq=10 ttl=64 time=0.361 ms 64 bytes from 172.15.0.2: seq=11 ttl=64 time=0.393 ms 64 bytes from 172.15.0.2: seq=12 ttl=64 time=0.387 ms 64 bytes from 172.15.0.2: seq=13 ttl=64 time=0.374 ms 64 bytes from 172.15.0.2: seq=14 ttl=64 time=0.387 ms 64 bytes from 172.15.0.2: seq=15 ttl=64 time=0.399 ms 64 bytes from 172.15.0.2: seq=16 ttl=64 time=0.374 ms 64 bytes from 172.15.0.2: seq=17 ttl=64 time=0.419 ms ^C --- 172.15.0.2 ping statistics --- 18 packets transmitted, 16 packets received, 11% packet loss round-trip min/avg/max = 0.361/0.387/0.443 ms ``` Currently, due to an unidentified issue, the first 2 packets in a TSN stream are always lost. No packet loss is expected afterwards. iperf test: ``` [root@board2 ~] # iperf3 -s [root@board1 ~] # iperf3 -c 172.15.0.2 Connecting to host 172.15.0.2, port 5201 [ 5] local 172.15.0.1 port 35772 connected to 172.15.0.2 port 5201 [ ID] Interval Transfer Bitrate Retr Cwnd [ 5] 0.00-1.00 sec 103 MBytes 860 Mbits/sec 1124 4.24 KBytes [ 5] 1.00-2.00 sec 87.1 MBytes 730 Mbits/sec 921 19.8 KBytes [ 5] 2.00-3.00 sec 105 MBytes 880 Mbits/sec 1064 17.0 KBytes [ 5] 3.00-4.00 sec 106 MBytes 888 Mbits/sec 1080 18.4 KBytes [ 5] 4.00-5.00 sec 106 MBytes 888 Mbits/sec 1099 26.9 KBytes [ 5] 5.00-6.00 sec 106 MBytes 889 Mbits/sec 1110 24.0 KBytes [ 5] 6.00-7.00 sec 106 MBytes 889 Mbits/sec 1045 24.0 KBytes [ 5] 7.00-8.00 sec 106 MBytes 887 Mbits/sec 1075 21.2 KBytes [ 5] 8.00-9.00 sec 106 MBytes 889 Mbits/sec 1118 18.4 KBytes [ 5] 9.00-10.00 sec 106 MBytes 888 Mbits/sec 1127 18.4 KBytes - - - - - - - - - - - - - - - - - - - - - - - - - [ ID] Interval Transfer Bitrate Retr [ 5] 0.00-10.00 sec 1.01 GBytes 869 Mbits/sec 10763 sender [ 5] 0.00-10.00 sec 1.01 GBytes 869 Mbits/sec receiver ``` iperf Done. You may unplug any single cable at a time from the setup and still notice zero downtime. The third board is there in order to introduce asymmetric path delay between board 2 (sender) and 1 (receiver) on the two member streams. This is a challenge in 802.1CB as the history size of the sliding window needs to account for such differences in path delay. This is currently a knob for the user to tune depending on network. In lack of a third board, the scripts may be in principle adapted to work in a 2-board ring setup. For cabling and further information, see the comments inside 8021cb.sh. The TSN streams recognized by each board are described in the .json files. In principle the number of TSN streams in the network needs to scale with the square of the number of boards, but this depends on what traffic paths are required. In this case, there are 9 TSN streams in total. A TSN stream is recognized by the pair formed by {destination MAC, VLAN ID} - called "NULL stream identification". Each board has its own MAC address on eno2 which identifies it as a sender, and also each board needs to send traffic using its own VLAN ID which identifies it as a sender. So actually the number of TSN streams is the # of senders times the # of receivers. Out of the 9 TSN streams, each board needs to be aware of only 4 streams: - The 2 TSN streams for traffic sent to the other boards: DMAC=$(other board's MAC), VID=$(my vid) - The 2 TSN streams for traffic coming from each other board to itself: DMAC=$(my MAC), VID=$(other board's VID). For the rest of 5 TSN streams, the board is a simple pass-through and performs L2 forwarding. isochron-0.9/frer/deps/000077500000000000000000000000001431034026500150755ustar00rootroot00000000000000isochron-0.9/frer/deps/patches/000077500000000000000000000000001431034026500165245ustar00rootroot00000000000000isochron-0.9/frer/deps/patches/linux/000077500000000000000000000000001431034026500176635ustar00rootroot00000000000000isochron-0.9/frer/deps/patches/linux/0001-arm64-dts-fsl-ls1028a-rdb-enable-swp5-and-move-NPI-p.patch000066400000000000000000000046351431034026500321370ustar00rootroot00000000000000From c8eee499efa5755f252ba2b55865c23390231ba0 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Tue, 28 Jan 2020 23:55:17 +0200 Subject: [PATCH 1/1] arm64: dts: fsl: ls1028a-rdb: enable swp5 and move NPI port to it This patch moves the NPI port from swp4 to swp5 on the Felix switch, and makes the 2.5 port pair (eno2/swp4) operate in plain untagged mode (previously it worked in DSA tagging mode). The switch engine for 802.1CB FRER uses the MAC table (FDB) to assign packets to a Seamless Stream ID (SSID). In turn, the MAC table needs the Analyzer module (ANA) to inspect the frames. But traffic that passes to/from the NPI port (which is what the DSA driver uses as a CPU port) necessarily has the BYPASS flag set in the injection header, which means "bypass the analyzer module". So traffic originated from the NPI port will not be correctly assigned to a SSID for 802.1CB. As a workaround, we enable the other port pair and use that for 802.1CB traffic that needs to be terminated on the local CPU. Before: After: eno2 eno3 eno2 eno3 | | Untagged | | | DSA x disabled traffic | | DSA | tags | including | | tags | | 802.1CB | | swp4 swp5 swp4 swp5 Of course, in this context "untagged" means "not tagged with an injection/extraction DSA tag", it has nothing to do with the VLAN or 802.1CB R-Tag. Signed-off-by: Vladimir Oltean --- .../boot/dts/freescale/fsl-ls1028a-rdb.dts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/arch/arm64/boot/dts/freescale/fsl-ls1028a-rdb.dts b/arch/arm64/boot/dts/freescale/fsl-ls1028a-rdb.dts index 2c92bcdb0d62..d396457e9a00 100644 --- a/arch/arm64/boot/dts/freescale/fsl-ls1028a-rdb.dts +++ b/arch/arm64/boot/dts/freescale/fsl-ls1028a-rdb.dts @@ -239,6 +239,16 @@ status = "disabled"; }; +&enetc_port2 { + fixed-link { + pause; + }; +}; + +&enetc_port3 { + status = "okay"; +}; + &enetc_mdio_pf3 { qsgmii_phy1: ethernet-phy@10 { reg = <0x10>; @@ -292,6 +302,19 @@ }; }; +&mscc_felix_port4 { + /delete-property/ ethernet; + + fixed-link { + pause; + }; +}; + +&mscc_felix_port5 { + status = "okay"; + ethernet = <&enetc_port3>; +}; + &sai4 { status = "okay"; }; -- 2.25.1 isochron-0.9/setlocalversion000077500000000000000000000045571431034026500163610ustar00rootroot00000000000000#!/bin/sh # SPDX-License-Identifier: GPL-2.0 # # This scripts adds local version information from git. # # If something goes wrong, send a mail the kernel build mailinglist # (see MAINTAINERS) and CC Nico Schottelius # . # # usage() { echo "Usage: $0 [srctree]" >&2 exit 1 } srctree=. if test $# -gt 0; then srctree=$1 shift fi if test $# -gt 0 -o ! -d "$srctree"; then usage fi scm_version() { local short short=false cd "$srctree" if test "$1" = "--short"; then short=true fi # Check for git and a git repo. if test -z "$(git rev-parse --show-cdup 2>/dev/null)" && head=$(git rev-parse --verify HEAD 2>/dev/null); then exact_match=$(git describe --exact-match 2>/dev/null) # If we are at a tagged commit (like "v2.6.30-rc6"), we print # it directly in the "else" branch. if [ -z "$exact_match" ]; then # If only the short version is requested, don't bother # running further git commands if $short; then echo "+" return fi # If we are past a tagged commit (like # "v2.6.30-rc5-302-g72357d5"), we pretty print it. # # Ensure the abbreviated sha1 has exactly 12 # hex characters, to make the output # independent of git version, local # core.abbrev settings and/or total number of # objects in the current repository - passing # --abbrev=12 ensures a minimum of 12, and the # awk substr() then picks the 'g' and first 12 # hex chars. if atag="$(git describe --abbrev=12 2>/dev/null)"; then echo "$atag" | awk -F- '{printf("%s-%05d-%s", $1, $(NF-1),substr($(NF),0,13))}' # If we don't have a tag at all we print -g{commitish}, # again using exactly 12 hex chars. else head="$(echo $head | cut -c1-12)" printf '%s%s' -g $head fi else printf '%s' $exact_match fi # Check for uncommitted changes. # First, with git-status, but --no-optional-locks is only # supported in git >= 2.14, so fall back to git-diff-index if # it fails. Note that git-diff-index does not refresh the # index, so it may give misleading results. See # git-update-index(1), git-diff-index(1), and git-status(1). if { git --no-optional-locks status -uno --porcelain 2>/dev/null || git diff-index --name-only HEAD } | grep -qvE '^(.. )?scripts/package'; then printf '%s' -dirty fi # All done with git return else cat VERSION fi } scm_version isochron-0.9/src/000077500000000000000000000000001431034026500137735ustar00rootroot00000000000000isochron-0.9/src/argparser.c000066400000000000000000000250711431034026500161320ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright 2019-2021 NXP */ #include #include #include #include #include #include #include "argparser.h" #include "common.h" int string_replace_escape_sequences(char *str) { char *end_ptr = str + strlen(str); char code, replacement; const char *p = str; char *backslash; while ((backslash = strchr(p, '\\')) != NULL) { if (backslash + 1 >= end_ptr) { fprintf(stderr, "Illegal backslash placement at the end of the printf format\n"); return -EINVAL; } code = *(backslash + 1); switch (code) { case 'a': /* alert (beep) */ replacement = '\a'; break; case '\\': /* backslash */ replacement = '\\'; break; case 'b': /* backspace */ replacement = '\b'; break; case 'r': /* carriage return */ replacement = '\r'; break; case '"': /* double quote */ replacement = '"'; break; case 'f': /* formfeed */ replacement = '\f'; break; case 't': /* horizontal tab */ replacement = '\t'; break; case 'n': /* newline */ replacement = '\n'; break; case '0': /* null character */ replacement = '\0'; break; case '\'': /* single quote */ replacement = '\''; break; case 'v': /* vertical tab */ replacement = '\v'; break; case '?': /* question mark */ replacement = '\?'; break; case '\n': /* line continuation */ replacement = ' '; break; default: fprintf(stderr, "Unrecognized escape sequence %c\n", code); return -EINVAL; } *backslash = replacement; memmove(backslash + 1, backslash + 2, end_ptr - (backslash + 2)); p = backslash + 1; end_ptr--; } *end_ptr = '\0'; return 0; } char *string_trim_whitespaces(char *str) { size_t len; char *end; /* Trim leading space */ while (isspace(*str)) str++; /* All spaces? */ len = strlen(str); if (!len) return str; /* Trim trailing space */ end = str + len - 1; while (end > str && isspace(*end)) end--; /* Write new null terminator */ *(end + 1) = 0; return str; } char *string_trim_comments(char *str) { char *pound, *single_quote, *double_quote, *first_quote, *next_quote; char *substr = str; while (strlen(substr)) { pound = strchr(substr, '#'); if (!pound) break; single_quote = strchr(substr, '\''); double_quote = strchr(substr, '"'); if (!single_quote && !double_quote) { /* We have a comment and no quotes, * strip the rest of the line */ *pound = 0; break; } else if (single_quote && !double_quote) { first_quote = single_quote; } else if (!single_quote && double_quote) { first_quote = double_quote; } else { /* We have both kinds of quotes, choose the first one */ if (single_quote - substr < double_quote - substr) first_quote = single_quote; else first_quote = double_quote; } next_quote = strchr(first_quote + 1, *first_quote); if (!next_quote) { fprintf(stderr, "Unterminated quoted string: \"%s\"\n", str); *pound = 0; break; } /* Continue the search after the closing quote */ substr = next_quote + 1; } return str; } static int mac_addr_from_string(unsigned char *to, char *from) { unsigned long byte; char *p = from; int i; for (i = 0; i < ETH_ALEN; i++) { byte = strtoul(p, &p, 16); to[i] = (unsigned char)byte; if (i == (ETH_ALEN - 1) && *p != 0) /* 6 bytes processed but more are present */ return -EFBIG; else if (i != (ETH_ALEN - 1) && *p == ':') p++; } return 0; } int ip_addr_from_string(const char *string, struct ip_address *ip) { char *percent, *if_name; size_t len; int rc; percent = strchr(string, '%'); if (percent) { if_name = percent + 1; len = strlen(if_name); if (!len || len >= IFNAMSIZ) { fprintf(stderr, "Invalid interface name %s\n", if_name); return -EINVAL; } *percent = 0; strcpy(ip->bound_if_name, if_name); } rc = inet_pton(AF_INET6, string, &ip->addr6); if (rc > 0) { ip->family = AF_INET6; } else { rc = inet_pton(AF_INET, string, &ip->addr); if (rc > 0) { ip->family = AF_INET; } else { fprintf(stderr, "IP address %s not in known format\n", string); return -1; } } return 0; } static int get_time_from_string(clockid_t clkid, __s64 *to, char *from) { struct timespec now_ts = {0}; __kernel_time_t sec; int relative = 0; long nsec = 0; __s64 now = 0; int size; if (from[0] == '+') { relative = 1; from++; } errno = 0; sec = strtol(from, &from, 0); if (errno) { perror("Failed to read seconds"); return -EINVAL; } if (from[0] == '.') { char nsec_buf[] = "000000000"; int i; /* The format is "sec.nsec" */ from++; if (strlen(from) > 9) { fprintf(stderr, "Nanosecond format too long, would truncate: %s\n", from); return -ERANGE; } size = sprintf(nsec_buf, "%s", from); for (i = size; i < 9; i++) nsec_buf[i] = '0'; errno = 0; /* Force base 10 here, since leading zeroes will make * strtol think this is an octal number. */ nsec = strtol(nsec_buf, NULL, 10); if (errno) { perror("Failed to extract ns info"); return -EINVAL; } } else { /* The format is "nsec" */ nsec = sec; sec = 0; } if (relative) { clock_gettime(clkid, &now_ts); now = timespec_to_ns(&now_ts); } *to = sec * NSEC_PER_SEC + nsec; *to += now; return 0; } static const char * const prog_arg_type_str[] = { [PROG_ARG_MAC_ADDR] = "MAC address", [PROG_ARG_UNSIGNED] = "Unsigned integer", [PROG_ARG_LONG] = "Long integer", [PROG_ARG_TIME] = "Time in sec.nsec format", [PROG_ARG_FILEPATH] = "File path", [PROG_ARG_IFNAME] = "Network interface", [PROG_ARG_STRING] = "String", [PROG_ARG_BOOL] = "Boolean", [PROG_ARG_IP] = "IP address", [PROG_ARG_HELP] = "Help text", }; static int required_args[] = { [PROG_ARG_MAC_ADDR] = 1, [PROG_ARG_UNSIGNED] = 1, [PROG_ARG_LONG] = 1, [PROG_ARG_TIME] = 1, [PROG_ARG_FILEPATH] = 1, [PROG_ARG_IFNAME] = 1, [PROG_ARG_STRING] = 1, [PROG_ARG_BOOL] = 0, [PROG_ARG_IP] = 1, [PROG_ARG_HELP] = 0, }; void prog_usage(const char *prog_name, struct prog_arg *prog_args, int prog_args_size) { int i; fprintf(stderr, "%s usage:\n", prog_name); for (i = 0; i < prog_args_size; i++) fprintf(stderr, "%s|%s: %s%s\n", prog_args[i].short_opt, prog_args[i].long_opt, prog_arg_type_str[prog_args[i].type], prog_args[i].optional ? " (optional)" : ""); } static int prog_parse_one_arg(char *val, const struct prog_arg *match) { struct prog_arg_filepath filepath; struct prog_arg_ifname ifname; struct prog_arg_string string; unsigned long *unsigned_ptr; struct prog_arg_time time; struct ip_address *ip_ptr; bool *boolean_ptr; long *long_ptr; bool *help_ptr; int rc; switch (match->type) { case PROG_ARG_MAC_ADDR: rc = mac_addr_from_string(match->mac.buf, val); if (rc < 0) { pr_err(rc, "Could not read %s: %m\n", match->long_opt); return rc; } break; case PROG_ARG_UNSIGNED: unsigned_ptr = match->unsigned_ptr.ptr; errno = 0; *unsigned_ptr = strtoul(val, NULL, 0); if (errno) { pr_err(-errno, "Could not read %s: %m\n", match->long_opt); return -1; } break; case PROG_ARG_LONG: long_ptr = match->long_ptr.ptr; errno = 0; *long_ptr = strtol(val, NULL, 0); if (errno) { pr_err(-errno, "Could not read %s: %m\n", match->long_opt); return -1; } break; case PROG_ARG_IP: ip_ptr = match->ip_ptr.ptr; rc = ip_addr_from_string(val, ip_ptr); if (rc) return rc; break; case PROG_ARG_TIME: time = match->time; rc = get_time_from_string(time.clkid, time.ns, val); if (rc < 0) { pr_err(rc, "Could not read base time: %m\n"); return -1; } break; case PROG_ARG_BOOL: boolean_ptr = match->boolean_ptr.ptr; *boolean_ptr = true; break; case PROG_ARG_FILEPATH: filepath = match->filepath; if (strlen(val) >= filepath.size) { fprintf(stderr, "File path \"%s\" too large, please limit to %zu bytes\n", val, filepath.size); return -ERANGE; } strcpy(filepath.buf, val); break; case PROG_ARG_IFNAME: ifname = match->ifname; if (strlen(val) >= ifname.size) { fprintf(stderr, "Interface name \"%s\" too large, please limit to %zu bytes\n", val, ifname.size); return -ERANGE; } strcpy(ifname.buf, val); break; case PROG_ARG_STRING: string = match->string; if (strlen(val) >= string.size) { fprintf(stderr, "String \"%s\" too large, please limit to %zu bytes\n", val, string.size); return -ERANGE; } strcpy(string.buf, val); break; case PROG_ARG_HELP: help_ptr = match->help_ptr.ptr; *help_ptr = true; break; default: fprintf(stderr, "Unknown argument type %d\n", match->type); return -EINVAL; } return 0; } /* Parse non-positional arguments and return the number of * arguments consumed. Return on first positional argument * found. */ int prog_parse_np_args(int argc, char **argv, struct prog_arg *prog_args, int prog_args_size) { bool help_requested = false; int rc, i, parsed = 0; bool *parsed_arr; parsed_arr = calloc(sizeof(bool), prog_args_size); if (!parsed_arr) return -ENOMEM; while (argc) { char *arg = argv[0], *val = NULL; char *equals = NULL; equals = strchr(arg, '='); if (equals) { *equals = 0; val = equals + 1; } else if (argc >= 2) { val = argv[1]; } for (i = 0; i < prog_args_size; i++) { if (strcmp(arg, prog_args[i].short_opt) && strcmp(arg, prog_args[i].long_opt)) continue; /* Consume argument specifier */ parsed++; argc--; argv++; if (!val && argc < required_args[prog_args[i].type]) { fprintf(stderr, "Value expected after %s\n", arg); free(parsed_arr); return -EINVAL; } rc = prog_parse_one_arg(val, &prog_args[i]); if (rc) { free(parsed_arr); return rc; } if (prog_args[i].type == PROG_ARG_HELP) help_requested = true; /* Consume actual argument value, unless it was * separated from the argument string by an "=" sign, * case in which it's really the same string */ if (!equals) { parsed += required_args[prog_args[i].type]; argc -= required_args[prog_args[i].type]; argv += required_args[prog_args[i].type]; } parsed_arr[i] = true; /* Success, stop searching */ break; } if (i == prog_args_size) { fprintf(stderr, "Unrecognized option %s\n", arg); free(parsed_arr); return -EINVAL; } } for (i = 0; i < prog_args_size; i++) { /* Mandatory arguments are only mandatory if the user doesn't * specify --help. */ if (!prog_args[i].optional && !parsed_arr[i] && !help_requested) { fprintf(stderr, "Please specify %s\n", prog_args[i].long_opt); free(parsed_arr); return -EINVAL; } } free(parsed_arr); return parsed; } isochron-0.9/src/argparser.h000066400000000000000000000037351431034026500161420ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2019-2021 NXP */ #ifndef _ISOCHRON_ARGPARSER_H #define _ISOCHRON_ARGPARSER_H #include #include #include #include #include #include #include #include struct ip_address { int family; union { struct in_addr addr; struct in6_addr addr6; }; char bound_if_name[IFNAMSIZ]; }; enum prog_arg_type { PROG_ARG_MAC_ADDR, PROG_ARG_UNSIGNED, PROG_ARG_LONG, PROG_ARG_TIME, PROG_ARG_STRING, PROG_ARG_IFNAME, PROG_ARG_FILEPATH, PROG_ARG_BOOL, PROG_ARG_IP, PROG_ARG_HELP, }; struct prog_arg_filepath { char *buf; size_t size; }; struct prog_arg_ifname { char *buf; size_t size; }; struct prog_arg_string { char *buf; size_t size; }; struct prog_arg_time { clockid_t clkid; __s64 *ns; }; struct prog_arg_unsigned { unsigned long *ptr; }; struct prog_arg_long { long *ptr; }; struct prog_arg_mac_addr { unsigned char *buf; }; struct prog_arg_boolean { bool *ptr; }; struct prog_arg_ip { struct ip_address *ptr; }; struct prog_arg_help { bool *ptr; }; struct prog_arg { const char *short_opt; const char *long_opt; bool optional; enum prog_arg_type type; union { struct prog_arg_string string; struct prog_arg_ifname ifname; struct prog_arg_filepath filepath; struct prog_arg_time time; struct prog_arg_unsigned unsigned_ptr; struct prog_arg_long long_ptr; struct prog_arg_mac_addr mac; struct prog_arg_boolean boolean_ptr; struct prog_arg_ip ip_ptr; struct prog_arg_help help_ptr; }; }; int prog_parse_np_args(int argc, char **argv, struct prog_arg *prog_args, int prog_args_size); void prog_usage(const char *prog_name, struct prog_arg *prog_args, int prog_args_size); int string_replace_escape_sequences(char *str); char *string_trim_whitespaces(char *str); char *string_trim_comments(char *str); int ip_addr_from_string(const char *string, struct ip_address *ip); #endif isochron-0.9/src/common.c000066400000000000000000000132261431034026500154330ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright 2019 NXP */ /* This file contains code snippets from: * - The Linux kernel * - The linuxptp project */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* For va_start and va_end */ #include #include "common.h" #include "rtnl.h" void pr_err(int rc, const char *fmt, ...) { va_list ap; errno = -rc; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } ssize_t read_exact(int fd, void *buf, size_t count) { size_t total_read = 0; ssize_t ret; do { ret = read(fd, buf + total_read, count - total_read); if (ret <= 0) return ret; total_read += ret; } while (total_read != count); return total_read; } ssize_t write_exact(int fd, const void *buf, size_t count) { size_t written = 0; ssize_t ret; do { ret = write(fd, buf + written, count - written); if (ret <= 0) return ret; written += ret; } while (written != count); return written; } void mac_addr_sprintf(char *buf, unsigned char *addr) { snprintf(buf, MACADDR_BUFSIZ, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); } __s64 timespec_to_ns(const struct timespec *ts) { return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec; } struct timespec ns_to_timespec(__s64 ns) { return (struct timespec) { .tv_sec = ns / NSEC_PER_SEC, .tv_nsec = llabs(ns) % NSEC_PER_SEC, }; } void ns_sprintf(char *buf, __s64 ns) { struct timespec ts = ns_to_timespec(ns); snprintf(buf, TIMESPEC_BUFSIZ, "%ld.%09ld", ts.tv_sec, ts.tv_nsec); } static const char * const trace_marker_paths[] = { "/sys/kernel/debug/tracing/trace_marker", "/debug/tracing/trace_marker", "/debugfs/tracing/trace_marker", }; int trace_mark_open(void) { unsigned int i; int fd; for (i = 0; i < ARRAY_SIZE(trace_marker_paths); i++) { fd = open(trace_marker_paths[i], O_WRONLY); if (fd < 0) continue; return fd; } return -1; } void trace_mark_close(int fd) { close(fd); } int set_utc_tai_offset(int offset) { struct timex tx; memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_TAI; tx.constant = offset; return adjtimex(&tx); } int get_utc_tai_offset(void) { struct timex tx; memset(&tx, 0, sizeof(tx)); adjtimex(&tx); return tx.tai; } void isochron_fixup_kernel_utc_offset(int ptp_utc_offset) { int kernel_offset = get_utc_tai_offset(); if (ptp_utc_offset == kernel_offset) return; printf("Kernel UTC-TAI offset of %d seems out of date, updating it to %d\n", kernel_offset, ptp_utc_offset); set_utc_tai_offset(ptp_utc_offset); } static void ptpmon_print_tried_ports(const char *real_ifname, char **tried_ports, int tries) { int i; fprintf(stderr, "Interface %s not found amount %d ports reported by ptp4l: ", real_ifname, tries); for (i = 0; i < tries; i++) fprintf(stderr, "%s", tried_ports[i]); fprintf(stderr, "\n"); } static void ptpmon_free_tried_ports(char **tried_ports, int tries) { int i; for (i = 0; i < tries; i++) free(tried_ports[i]); free(tried_ports); } int ptpmon_query_port_state_by_name(struct ptpmon *ptpmon, const char *iface, struct mnl_socket *rtnl, enum port_state *port_state) { struct default_ds default_ds; char real_ifname[IFNAMSIZ]; char **tried_ports, *dup; int portnum, num_ports; int tries = 0; int rc; rc = vlan_resolve_real_dev(rtnl, iface, real_ifname); if (rc) return rc; rc = ptpmon_query_clock_mid(ptpmon, MID_DEFAULT_DATA_SET, &default_ds, sizeof(default_ds)); if (rc) { pr_err(rc, "Failed to query DEFAULT_DATA_SET: %m\n"); return rc; } num_ports = __be16_to_cpu(default_ds.number_ports); tried_ports = calloc(num_ports, sizeof(char *)); if (!tried_ports) { printf("Failed to allocate memory for port names\n"); return -ENOMEM; } for (portnum = 1; portnum <= num_ports; portnum++) { __u8 buf[sizeof(struct port_properties_np) + MAX_IFACE_LEN] = {0}; struct port_properties_np *port_properties_np; char real_port_ifname[IFNAMSIZ]; struct port_identity portid; portid_set(&portid, &default_ds.clock_identity, portnum); rc = ptpmon_query_port_mid_extra(ptpmon, &portid, MID_PORT_PROPERTIES_NP, buf, sizeof(struct port_properties_np), MAX_IFACE_LEN); if (rc) { ptpmon_free_tried_ports(tried_ports, tries); return rc; } port_properties_np = (struct port_properties_np *)buf; rc = vlan_resolve_real_dev(rtnl, port_properties_np->iface, real_port_ifname); if (rc) goto out; if (strcmp(real_port_ifname, real_ifname)) { /* Skipping ptp4l port, save the name for later to * inform the user in case we found nothing. */ dup = strdup(real_port_ifname); if (!dup) { rc = -ENOMEM; goto out; } tried_ports[tries++] = dup; continue; } *port_state = port_properties_np->port_state; rc = 0; goto out; } /* Nothing found */ rc = -ENODEV; ptpmon_print_tried_ports(real_ifname, tried_ports, tries); out: ptpmon_free_tried_ports(tried_ports, tries); return rc; } int if_name_copy(char dest[IFNAMSIZ], const char src[IFNAMSIZ]) { char buf[IFNAMSIZ + 1]; memcpy(buf, src, IFNAMSIZ); buf[IFNAMSIZ] = 0; if (strlen(buf) == IFNAMSIZ) return -EINVAL; strcpy(dest, buf); return 0; } int uds_copy(char dest[UNIX_PATH_MAX], const char src[UNIX_PATH_MAX]) { char buf[UNIX_PATH_MAX + 1]; memcpy(buf, src, UNIX_PATH_MAX); buf[UNIX_PATH_MAX] = 0; if (strlen(buf) == UNIX_PATH_MAX) return -EINVAL; strcpy(dest, buf); return 0; } isochron-0.9/src/common.h000066400000000000000000000154441431034026500154440ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2019 NXP */ /* This file contains code snippets from: * - libnfnetlink.h * - The Linux kernel * - The linuxptp project */ #ifndef _COMMON_H #define _COMMON_H #include #include #include #include #include #include #include #include #include #include #include #include "endian.h" #include "ptpmon.h" #define min(a,b) \ ({ \ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a < _b ? _a : _b; \ }) #define max(a,b) \ ({ \ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ _a > _b ? _a : _b; \ }) #define BIT(nr) (1UL << (nr)) struct sched_attr { __u32 size; /* Size of this structure */ __u32 sched_policy; /* Policy (SCHED_*) */ __u64 sched_flags; /* Flags */ __s32 sched_nice; /* Nice value (SCHED_OTHER, SCHED_BATCH) */ __u32 sched_priority; /* Static priority (SCHED_FIFO, SCHED_RR) */ /* Remaining fields are for SCHED_DEADLINE */ __u64 sched_runtime; __u64 sched_deadline; __u64 sched_period; }; static inline int sched_setattr(pid_t pid, const struct sched_attr *attr, unsigned int flags) { return syscall(SYS_sched_setattr, pid, attr, flags); } #ifndef SO_TXTIME #define SO_TXTIME 61 #define SCM_TXTIME SO_TXTIME struct sock_txtime { clockid_t clockid; uint16_t flags; }; enum txtime_flags { SOF_TXTIME_DEADLINE_MODE = (1 << 0), SOF_TXTIME_REPORT_ERRORS = (1 << 1), SOF_TXTIME_FLAGS_LAST = SOF_TXTIME_REPORT_ERRORS, SOF_TXTIME_FLAGS_MASK = (SOF_TXTIME_FLAGS_LAST - 1) | SOF_TXTIME_FLAGS_LAST }; #endif #ifndef PACKET_TX_TIMESTAMP #define PACKET_TX_TIMESTAMP 16 #endif #ifndef SO_EE_ORIGIN_TXTIME #define SO_EE_ORIGIN_TXTIME 6 #define SO_EE_CODE_TXTIME_INVALID_PARAM 1 #define SO_EE_CODE_TXTIME_MISSED 2 #endif struct isochron_header { __be64 scheduled; __be64 wakeup; __be32 seqid; } __attribute__((packed)); #define NSEC_PER_SEC 1000000000LL #define MSEC_PER_SEC 1000L #define ETH_P_ISOCHRON 0xdead /* Error margin for all that is unknown or uncalculable */ #define TIME_MARGIN (NSEC_PER_SEC / 2) #define TIMESPEC_BUFSIZ 22 #define MACADDR_BUFSIZ 18 /* From include/uapi/linux/net_tstamp.h */ #ifndef HAVE_TX_SWHW enum { SOF_TIMESTAMPING_OPT_TX_SWHW = (1<<14), }; #endif #define ARRAY_SIZE(array) \ (sizeof(array) / sizeof(*array)) #ifndef LIST_FOREACH_SAFE #define LIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = LIST_FIRST((head)); \ (var) && ((tvar) = LIST_NEXT((var), field), 1); \ (var) = (tvar)) #endif /** * struct vlan_ethhdr - vlan ethernet header (ethhdr + vlan_hdr) * @h_dest: destination ethernet address * @h_source: source ethernet address * @h_vlan_proto: ethernet protocol * @h_vlan_TCI: priority and VLAN ID * @h_vlan_encapsulated_proto: packet type ID or len */ struct vlan_ethhdr { unsigned char h_dest[ETH_ALEN]; unsigned char h_source[ETH_ALEN]; __be16 h_vlan_proto; __be16 h_vlan_TCI; __be16 h_vlan_encapsulated_proto; }; #define VLAN_PRIO_MASK 0xe000 /* Priority Code Point */ #define VLAN_PRIO_SHIFT 13 #define VLAN_CFI_MASK 0x1000 /* Canonical Format Indicator / Drop Eligible Indicator */ #define VLAN_VID_MASK 0x0fff /* VLAN Identifier */ #define VLAN_N_VID 4096 ssize_t read_exact(int fd, void *buf, size_t count); ssize_t write_exact(int fd, const void *buf, size_t count); __s64 timespec_to_ns(const struct timespec *ts); struct timespec ns_to_timespec(__s64 ns); void mac_addr_sprintf(char *buf, unsigned char *addr); void ns_sprintf(char *buf, __s64 ns); /** * ether_addr_to_u64 - Convert an Ethernet address into a u64 value. * @addr: Pointer to a six-byte array containing the Ethernet address * * Return a u64 value of the address */ static inline __u64 ether_addr_to_u64(const unsigned char *addr) { __u64 u = 0; int i; for (i = 0; i < ETH_ALEN; i++) u = u << 8 | addr[i]; return u; } /** * ether_addr_copy - Copy an Ethernet address * @dst: Pointer to a six-byte array Ethernet address destination * @src: Pointer to a six-byte array Ethernet address source * * Please note: dst & src must both be aligned to u16. */ static inline void ether_addr_copy(unsigned char *dst, const unsigned char *src) { *(__u32 *)dst = *(const __u32 *)src; *(__u16 *)(dst + 4) = *(const __u16 *)(src + 4); } /** * is_zero_ether_addr - Determine if give Ethernet address is all zeros. * @addr: Pointer to a six-byte array containing the Ethernet address * * Return true if the address is all zeroes. * * Please note: addr must be aligned to u16. */ static inline bool is_zero_ether_addr(const unsigned char *addr) { return ((*(const __u32 *)addr) | (*(const __u16 *)(addr + 4))) == 0; } /** * is_multicast_ether_addr - Determine if the Ethernet address is a multicast. * @addr: Pointer to a six-byte array containing the Ethernet address * * Return true if the address is a multicast address. * By definition the broadcast address is also a multicast address. */ static inline bool is_multicast_ether_addr(const unsigned char *addr) { return addr[0] & 0x01; } /** * ether_addr_equal - Compare two Ethernet addresses * @a: Pointer to a six-byte array containing the Ethernet address * @b: Pointer other six-byte array containing the Ethernet address * * Compare two Ethernet addresses, returns true if equal * * Please note: a & b must both be aligned to u16. */ static inline bool ether_addr_equal(const unsigned char *a, const unsigned char *b) { __u32 fold = ((*(const __u32 *)a) ^ (*(const __u32 *)b)) | ((*(const __u16 *)(a + 4)) ^ (*(const __u16 *)(b + 4))); return fold == 0; } int trace_mark_open(void); void trace_mark_close(int fd); int set_utc_tai_offset(int offset); int get_utc_tai_offset(void); void isochron_fixup_kernel_utc_offset(int ptp_utc_offset); static inline __s64 utc_to_tai(__s64 utc, __s64 offset) { return utc + offset * NSEC_PER_SEC; } static inline __s64 master_offset_from_current_ds(const struct current_ds *current_ds) { return (__s64 )(__be64_to_cpu(current_ds->offset_from_master)) >> 16; } int ptpmon_query_port_state_by_name(struct ptpmon *ptpmon, const char *iface, struct mnl_socket *rtnl, enum port_state *port_state); void pr_err(int rc, const char *fmt, ...); /* Calculate the first base_time in the future that satisfies this * relationship: * * future_base_time = base_time + N x cycle_time >= now, or * * now - base_time * N >= --------------- * cycle_time */ static inline __s64 future_base_time(__s64 base_time, __s64 cycle_time, __s64 now) { __s64 n; if (base_time >= now) return base_time; n = (now - base_time) / cycle_time; return base_time + (n + 1) * cycle_time; } int if_name_copy(char dest[IFNAMSIZ], const char src[IFNAMSIZ]); int uds_copy(char dest[UNIX_PATH_MAX], const char src[UNIX_PATH_MAX]); #endif isochron-0.9/src/daemon.c000066400000000000000000001003221431034026500154000ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright 2022 NXP */ #include #include #include #include #include #include #include #include #include #include "argparser.h" #include "common.h" #include "isochron.h" #include "management.h" #include "ptpmon.h" #include "rtnl.h" #include "send.h" #include "sk.h" #include "sysmon.h" struct isochron_daemon { struct ip_address stats_addr; long stats_port; char pid_filename[PATH_MAX]; char log_filename[PATH_MAX]; struct isochron_mgmt_handler *mgmt_handler; struct sk *mgmt_listen_sock; struct sk *mgmt_sock; bool have_client; struct isochron_send *send; struct mnl_socket *rtnl; bool session_active; }; static int prog_check_admin_state(struct isochron_daemon *prog) { const char *if_name = prog->send->if_name; bool up; int rc; rc = rtnl_query_admin_state(prog->rtnl, if_name, &up); if (rc) { pr_err(rc, "Failed to query port %s admin state: %m\n", if_name); return rc; } if (!up) { fprintf(stderr, "Interface %s is administratively down\n", if_name); return -ENETDOWN; } return 0; } static int prog_prepare_send_session(struct isochron_daemon *prog) { struct isochron_send *send = prog->send; int rc; /* Hack to suppress local log output to the filesystem, since * isochron_send_interpret_args() makes the default output_file * isochron.dat, and this triggers a validation error when a sender * session is restarted. */ send->output_file[0] = 0; rc = isochron_send_interpret_args(send); if (rc) return rc; rc = isochron_send_init_data_sock(send); if (rc) goto err_init_data_sock; isochron_send_init_data_packet(send); isochron_send_init_thread_state(send); rc = isochron_log_init(&send->log, send->iterations * sizeof(struct isochron_send_pkt_data)); if (rc) goto err_log_init; rc = isochron_send_update_session_start_time(send); if (rc) { pr_err(rc, "Failed to update session start time: %m\n"); goto err_update_session_start; } rc = isochron_send_start_threads(send); if (rc) goto err_start_threads; prog->session_active = true; return 0; err_start_threads: err_update_session_start: isochron_log_teardown(&send->log); err_log_init: isochron_send_teardown_data_sock(send); err_init_data_sock: return rc; } static void prog_teardown_send_session(struct isochron_daemon *prog) { struct isochron_send *send = prog->send; prog->session_active = false; isochron_send_stop_threads(send); isochron_log_teardown(&send->log); isochron_send_teardown_data_sock(send); } static void isochron_teardown_sender(struct isochron_daemon *prog) { struct isochron_send *send = prog->send; if (!send) return; if (prog->session_active) prog_teardown_send_session(prog); if (send->ptpmon) isochron_send_teardown_ptpmon(send); if (send->sysmon) isochron_send_teardown_sysmon(send); free(send); prog->send = NULL; } static void prog_close_client_stats_session(struct isochron_daemon *prog) { isochron_teardown_sender(prog); sk_close(prog->mgmt_sock); prog->have_client = false; } static int prog_client_connect_event(struct isochron_daemon *prog) { int rc; rc = sk_accept(prog->mgmt_listen_sock, &prog->mgmt_sock); if (rc) return rc; prog->have_client = true; return 0; } static int prog_update_role(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_node_role *r = ptr; struct isochron_send *send; if (__be32_to_cpu(r->role) != ISOCHRON_ROLE_SEND) { mgmt_extack(extack, "Unexpected node role %d", __be32_to_cpu(r->role)); return -EINVAL; } send = calloc(1, sizeof(*send)); if (!send) { mgmt_extack(extack, "failed to allocate memory for new sender"); return -ENOMEM; } isochron_send_prepare_default_args(send); isochron_teardown_sender(prog); prog->send = send; return 0; } static int prog_update_utc_offset(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_utc_offset *u = ptr; int offset; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } offset = __be16_to_cpu(u->offset); isochron_fixup_kernel_utc_offset(offset); prog->send->utc_tai_offset = offset; return 0; } static int prog_update_packet_count(void *priv, void *ptr, char *extack) { struct isochron_packet_count *p = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->iterations = __be64_to_cpu(p->count); return 0; } static int prog_update_packet_size(void *priv, void *ptr, char *extack) { struct isochron_packet_size *p = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->tx_len = __be32_to_cpu(p->size); return 0; } static int prog_update_destination_mac(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_mac_addr *m = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } ether_addr_copy(prog->send->dest_mac, m->addr); return 0; } static int prog_update_source_mac(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_mac_addr *m = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } ether_addr_copy(prog->send->src_mac, m->addr); return 0; } static int prog_update_if_name(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; struct isochron_if_name *n = ptr; int rc; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } rc = if_name_copy(send->if_name, n->name); if (rc) { mgmt_extack(extack, "Truncation while copying string"); return rc; } return 0; } static int prog_update_priority(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_priority *p = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->priority = __be32_to_cpu(p->priority); return 0; } static int prog_update_stats_port(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_port *p = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->stats_port = __be16_to_cpu(p->port); return 0; } static int prog_update_base_time(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_time *t = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->base_time = (__s64)__be64_to_cpu(t->time); return 0; } static int prog_update_advance_time(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_time *t = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->advance_time = (__s64)__be64_to_cpu(t->time); return 0; } static int prog_update_shift_time(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_time *t = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->shift_time = (__s64)__be64_to_cpu(t->time); return 0; } static int prog_update_cycle_time(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_time *t = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->cycle_time = (__s64)__be64_to_cpu(t->time); return 0; } static int prog_update_window_size(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_time *t = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->window_size = (__s64)__be64_to_cpu(t->time); return 0; } static int prog_update_sysmon_enabled(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } if (f->enabled) { if (prog->send->sysmon) { fprintf(stderr, "sysmon already enabled\n"); return -EINVAL; } return isochron_send_init_sysmon(prog->send); } else { if (!prog->send->sysmon) { fprintf(stderr, "sysmon not enabled\n"); return -EINVAL; } isochron_send_teardown_sysmon(prog->send); } return 0; } static int prog_update_ptpmon_enabled(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } if (f->enabled) { if (prog->send->ptpmon) { mgmt_extack(extack, "ptpmon already enabled"); return -EINVAL; } return isochron_send_init_ptpmon(prog->send); } else { if (!prog->send->ptpmon) { mgmt_extack(extack, "ptpmon not enabled"); return -EINVAL; } isochron_send_teardown_ptpmon(prog->send); } return 0; } static int prog_update_uds(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; struct isochron_uds *u = ptr; int rc; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } rc = uds_copy(send->uds_remote, u->name); if (rc) { mgmt_extack(extack, "Truncation while copying string"); return rc; } return 0; } static int prog_update_domain_number(void *priv, void *ptr, char *extack) { struct isochron_domain_number *d = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->domain_number = d->domain_number; return 0; } static int prog_update_transport_specific(void *priv, void *ptr, char *extack) { struct isochron_transport_specific *t = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->transport_specific = t->transport_specific; return 0; } static int prog_update_num_readings(void *priv, void *ptr, char *extack) { struct isochron_num_readings *n = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->num_readings = __be32_to_cpu(n->num_readings); return 0; } static int prog_update_ts_enabled(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->do_ts = f->enabled; return 0; } static int prog_update_vid(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_vid *v = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->vid = __be16_to_cpu(v->vid); return 0; } static int prog_update_ethertype(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_ethertype *e = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->etype = (__s16)__be16_to_cpu(e->ethertype); return 0; } static int prog_update_quiet_enabled(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->quiet = f->enabled; return 0; } static int prog_update_taprio_enabled(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->taprio = f->enabled; return 0; } static int prog_update_txtime_enabled(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->txtime = f->enabled; return 0; } static int prog_update_deadline_enabled(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->deadline = f->enabled; return 0; } static int prog_update_ip_destination(void *priv, void *ptr, char *extack) { struct isochron_ip_address *i = ptr; struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; int rc; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } send->ip_destination.family = __be32_to_cpu(i->family); rc = if_name_copy(send->ip_destination.bound_if_name, i->bound_if_name); if (rc) { mgmt_extack(extack, "Truncation while copying string"); return rc; } return 0; } static int prog_update_l2_enabled(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->l2 = f->enabled; return 0; } static int prog_update_l4_enabled(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->l4 = f->enabled; return 0; } static int prog_update_data_port(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_port *p = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->data_port = __be16_to_cpu(p->port); return 0; } static int prog_update_sched_fifo(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->sched_fifo = f->enabled; return 0; } static int prog_update_sched_rr(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->sched_rr = f->enabled; return 0; } static int prog_update_sched_priority(void *priv, void *ptr, char *extack) { struct isochron_sched_priority *s = ptr; struct isochron_daemon *prog = priv; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->sched_priority = __be32_to_cpu(s->sched_priority); return 0; } static int prog_update_cpu_mask(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_cpu_mask *c = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->cpumask = __be64_to_cpu(c->cpu_mask); return 0; } static int prog_update_test_state(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_test_state *s = ptr; int rc; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } if (s->test_state == ISOCHRON_TEST_STATE_IDLE) { if (!prog->session_active) { mgmt_extack(extack, "Sender already idle"); return -EINVAL; } prog_teardown_send_session(prog); } else if (s->test_state == ISOCHRON_TEST_STATE_RUNNING) { if (prog->session_active) { mgmt_extack(extack, "Sender already running a test"); return -EINVAL; } rc = prog_check_admin_state(prog); if (rc) return rc; rc = prog_prepare_send_session(prog); if (rc) return rc; } return 0; } static int prog_update_sync_monitor_enabled(void *priv, void *ptr, char *extack) { struct isochron_daemon *prog = priv; struct isochron_feature_enabled *f = ptr; if (!prog->send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } prog->send->omit_sync = !f->enabled; return 0; } static int prog_forward_isochron_log(void *priv, char *extack) { struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; int rc; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } if (!prog->session_active) { mgmt_extack(extack, "Log exists only while session is active"); return -EINVAL; } rc = isochron_send_tlv(prog->mgmt_sock, ISOCHRON_RESPONSE, ISOCHRON_MID_LOG, isochron_log_buf_tlv_size(&send->log)); if (rc) return rc; isochron_log_xmit(&send->log, prog->mgmt_sock); isochron_log_teardown(&send->log); return isochron_log_init(&send->log, send->iterations * sizeof(struct isochron_send_pkt_data)); } static int prog_forward_sysmon_offset(void *priv, char *extack) { struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } if (!send->sysmon) { mgmt_extack(extack, "Sender sysmon not instantiated"); return -EINVAL; } return isochron_forward_sysmon_offset(prog->mgmt_sock, send->sysmon, extack); } static int prog_forward_ptpmon_offset(void *priv, char *extack) { struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } if (!send->ptpmon) { mgmt_extack(extack, "Sender ptpmon not instantiated"); return -EINVAL; } return isochron_forward_ptpmon_offset(prog->mgmt_sock, send->ptpmon, extack); } static int prog_forward_utc_offset(void *priv, char *extack) { struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; int rc, utc_offset; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } if (!send->ptpmon) { mgmt_extack(extack, "Sender ptpmon not instantiated"); return -EINVAL; } rc = isochron_forward_utc_offset(prog->mgmt_sock, send->ptpmon, &utc_offset, extack); if (rc) return rc; isochron_fixup_kernel_utc_offset(utc_offset); prog->send->utc_tai_offset = utc_offset; return 0; } static int prog_forward_port_state(void *priv, char *extack) { struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } if (!send->ptpmon) { mgmt_extack(extack, "Sender ptpmon not instantiated"); return -EINVAL; } if (!strlen(send->if_name)) { mgmt_extack(extack, "Sender interface not specified"); return -EINVAL; } return isochron_forward_port_state(prog->mgmt_sock, send->ptpmon, send->if_name, prog->rtnl, extack); } static int prog_forward_port_link_state(void *priv, char *extack) { struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } return isochron_forward_port_link_state(prog->mgmt_sock, send->if_name, prog->rtnl, extack); } static int prog_forward_gm_clock_identity(void *priv, char *extack) { struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } if (!send->ptpmon) { mgmt_extack(extack, "Sender ptpmon not instantiated"); return -EINVAL; } return isochron_forward_gm_clock_identity(prog->mgmt_sock, send->ptpmon, extack); } static int prog_forward_test_state(void *priv, char *extack) { struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; enum test_state test_state; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } if (!prog->session_active) { test_state = ISOCHRON_TEST_STATE_IDLE; } else if (send->tx_tstamp_tid_stopped) { test_state = ISOCHRON_TEST_STATE_IDLE; if (send->tx_timestamp_tid_rc) { mgmt_extack(extack, "TX timestamping thread failed"); test_state = ISOCHRON_TEST_STATE_FAILED; } if (send->send_tid_rc) { mgmt_extack(extack, "Sender thread failed"); test_state = ISOCHRON_TEST_STATE_FAILED; } } else { test_state = ISOCHRON_TEST_STATE_RUNNING; } return isochron_forward_test_state(prog->mgmt_sock, test_state, extack); } static int prog_forward_current_clock_tai(void *priv, char *extack) { struct isochron_daemon *prog = priv; return isochron_forward_current_clock_tai(prog->mgmt_sock, extack); } static int prog_forward_oper_base_time(void *priv, char *extack) { struct isochron_daemon *prog = priv; struct isochron_send *send = prog->send; struct isochron_time t = {}; int rc; if (!send) { mgmt_extack(extack, "Sender role not instantiated"); return -EINVAL; } t.time = __cpu_to_be64(send->oper_base_time); rc = isochron_send_tlv(prog->mgmt_sock, ISOCHRON_RESPONSE, ISOCHRON_MID_OPER_BASE_TIME, sizeof(t)); if (rc) return rc; sk_send(prog->mgmt_sock, &t, sizeof(t)); return 0; } static const struct isochron_mgmt_ops daemon_mgmt_ops[__ISOCHRON_MID_MAX] = { [ISOCHRON_MID_LOG] = { .get = prog_forward_isochron_log, }, [ISOCHRON_MID_SYSMON_OFFSET] = { .get = prog_forward_sysmon_offset, }, [ISOCHRON_MID_PTPMON_OFFSET] = { .get = prog_forward_ptpmon_offset, }, [ISOCHRON_MID_UTC_OFFSET] = { .get = prog_forward_utc_offset, .set = prog_update_utc_offset, .struct_size = sizeof(struct isochron_utc_offset), }, [ISOCHRON_MID_PORT_STATE] = { .get = prog_forward_port_state, }, [ISOCHRON_MID_PORT_LINK_STATE] = { .get = prog_forward_port_link_state, }, [ISOCHRON_MID_GM_CLOCK_IDENTITY] = { .get = prog_forward_gm_clock_identity, }, [ISOCHRON_MID_TEST_STATE] = { .get = prog_forward_test_state, .set = prog_update_test_state, .struct_size = sizeof(struct isochron_test_state), }, [ISOCHRON_MID_CURRENT_CLOCK_TAI] = { .get = prog_forward_current_clock_tai, }, [ISOCHRON_MID_OPER_BASE_TIME] = { .get = prog_forward_oper_base_time, }, [ISOCHRON_MID_NODE_ROLE] = { .set = prog_update_role, .struct_size = sizeof(struct isochron_node_role), }, [ISOCHRON_MID_PACKET_COUNT] = { .struct_size = sizeof(struct isochron_packet_count), .set = prog_update_packet_count, }, [ISOCHRON_MID_PACKET_SIZE] = { .set = prog_update_packet_size, .struct_size = sizeof(struct isochron_packet_size), }, [ISOCHRON_MID_DESTINATION_MAC] = { .set = prog_update_destination_mac, .struct_size = sizeof(struct isochron_mac_addr), }, [ISOCHRON_MID_SOURCE_MAC] = { .set = prog_update_source_mac, .struct_size = sizeof(struct isochron_mac_addr), }, [ISOCHRON_MID_IF_NAME] = { .set = prog_update_if_name, .struct_size = sizeof(struct isochron_if_name), }, [ISOCHRON_MID_PRIORITY] = { .set = prog_update_priority, .struct_size = sizeof(struct isochron_priority), }, [ISOCHRON_MID_STATS_PORT] = { .set = prog_update_stats_port, .struct_size = sizeof(struct isochron_port), }, [ISOCHRON_MID_BASE_TIME] = { .set = prog_update_base_time, .struct_size = sizeof(struct isochron_time), }, [ISOCHRON_MID_ADVANCE_TIME] = { .set = prog_update_advance_time, .struct_size = sizeof(struct isochron_time), }, [ISOCHRON_MID_SHIFT_TIME] = { .set = prog_update_shift_time, .struct_size = sizeof(struct isochron_time), }, [ISOCHRON_MID_CYCLE_TIME] = { .set = prog_update_cycle_time, .struct_size = sizeof(struct isochron_time), }, [ISOCHRON_MID_WINDOW_SIZE] = { .set = prog_update_window_size, .struct_size = sizeof(struct isochron_time), }, [ISOCHRON_MID_SYSMON_ENABLED] = { .set = prog_update_sysmon_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_PTPMON_ENABLED] = { .set = prog_update_ptpmon_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_UDS] = { .set = prog_update_uds, .struct_size = sizeof(struct isochron_uds), }, [ISOCHRON_MID_DOMAIN_NUMBER] = { .set = prog_update_domain_number, .struct_size = sizeof(struct isochron_domain_number), }, [ISOCHRON_MID_TRANSPORT_SPECIFIC] = { .set = prog_update_transport_specific, .struct_size = sizeof(struct isochron_transport_specific), }, [ISOCHRON_MID_NUM_READINGS] = { .set = prog_update_num_readings, .struct_size = sizeof(struct isochron_num_readings), }, [ISOCHRON_MID_TS_ENABLED] = { .set = prog_update_ts_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_VID] = { .set = prog_update_vid, .struct_size = sizeof(struct isochron_vid), }, [ISOCHRON_MID_ETHERTYPE] = { .set = prog_update_ethertype, .struct_size = sizeof(struct isochron_ethertype), }, [ISOCHRON_MID_QUIET_ENABLED] = { .set = prog_update_quiet_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_TAPRIO_ENABLED] = { .set = prog_update_taprio_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_TXTIME_ENABLED] = { .set = prog_update_txtime_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_DEADLINE_ENABLED] = { .set = prog_update_deadline_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_IP_DESTINATION] = { .set = prog_update_ip_destination, .struct_size = sizeof(struct isochron_ip_address), }, [ISOCHRON_MID_L2_ENABLED] = { .set = prog_update_l2_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_L4_ENABLED] = { .set = prog_update_l4_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_DATA_PORT] = { .set = prog_update_data_port, .struct_size = sizeof(struct isochron_port), }, [ISOCHRON_MID_SCHED_FIFO_ENABLED] = { .set = prog_update_sched_fifo, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_SCHED_RR_ENABLED] = { .set = prog_update_sched_rr, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_SCHED_PRIORITY] = { .set = prog_update_sched_priority, .struct_size = sizeof(struct isochron_sched_priority), }, [ISOCHRON_MID_CPU_MASK] = { .set = prog_update_cpu_mask, .struct_size = sizeof(struct isochron_cpu_mask), }, [ISOCHRON_MID_SYNC_MONITOR_ENABLED] = { .set = prog_update_sync_monitor_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, }; static int prog_mgmt_loop(struct isochron_daemon *prog) { struct pollfd pfd[1] = { [0] = { /* .fd to be filled in dynamically */ .events = POLLIN | POLLERR | POLLPRI, }, }; int rc = 0; int cnt; do { if (prog->have_client) pfd[0].fd = sk_fd(prog->mgmt_sock); else pfd[0].fd = sk_fd(prog->mgmt_listen_sock); cnt = poll(pfd, ARRAY_SIZE(pfd), -1); if (cnt < 0) { if (errno == EINTR) { break; } else { perror("poll failed"); rc = -errno; break; } } else if (!cnt) { printf("poll returned 0\n"); break; } if (pfd[0].revents & (POLLIN | POLLERR | POLLPRI)) { if (prog->have_client) { rc = isochron_mgmt_event(prog->mgmt_sock, prog->mgmt_handler, prog); if (sk_closed(prog->mgmt_sock)) prog_close_client_stats_session(prog); else if (rc) break; } else { rc = prog_client_connect_event(prog); if (rc) break; } } if (signal_received) break; } while (1); if (prog->have_client) prog_close_client_stats_session(prog); return rc; } static int prog_init_mgmt_listen_sock(struct isochron_daemon *prog) { int rc; prog->mgmt_handler = isochron_mgmt_handler_create(daemon_mgmt_ops); if (!prog->mgmt_handler) return -ENOMEM; rc = sk_listen_tcp(&prog->stats_addr, prog->stats_port, 1, &prog->mgmt_listen_sock); if (rc) isochron_mgmt_handler_destroy(prog->mgmt_handler); return rc; } static void prog_teardown_mgmt_listen_sock(struct isochron_daemon *prog) { sk_close(prog->mgmt_listen_sock); isochron_mgmt_handler_destroy(prog->mgmt_handler); } static int prog_rtnl_open(struct isochron_daemon *prog) { struct mnl_socket *nl; nl = mnl_socket_open(NETLINK_ROUTE); if (!nl) { perror("mnl_socket_open"); return -errno; } if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { perror("mnl_socket_bind"); mnl_socket_close(nl); return -errno; } prog->rtnl = nl; return 0; } static void prog_rtnl_close(struct isochron_daemon *prog) { struct mnl_socket *nl = prog->rtnl; prog->rtnl = NULL; mnl_socket_close(nl); } static int prog_redirect_output(struct isochron_daemon *prog) { FILE *log_file; int dev_null; dev_null = open("/dev/null", O_RDONLY); if (dev_null < 0) { perror("open /dev/null"); return -errno; } dup2(dev_null, STDIN_FILENO); close(dev_null); if (!strlen(prog->log_filename)) { /* Redirect stdout and stderr to /dev/null too */ dup2(STDIN_FILENO, STDOUT_FILENO); dup2(STDIN_FILENO, STDERR_FILENO); return 0; } log_file = freopen(prog->log_filename, "w", stdout); if (!log_file) { perror("freopen"); return -errno; } setlinebuf(log_file); dup2(STDOUT_FILENO, STDERR_FILENO); return 0; } static int prog_daemonize(struct isochron_daemon *prog) { FILE *pid_file = NULL; pid_t pid; if (strlen(prog->pid_filename)) { pid_file = fopen(prog->pid_filename, "w"); if (!pid_file) { perror("pid file open"); goto err_pid_file_open; } } pid = fork(); if (pid < 0) { perror("fork"); goto err_fork; } if (pid > 0) { /* Parent */ if (pid_file) { fprintf(pid_file, "%d", pid); fclose(pid_file); } prog_teardown_mgmt_listen_sock(prog); prog_rtnl_close(prog); exit(EXIT_SUCCESS); } if (pid_file) fclose(pid_file); /* Child */ if (setsid() < 0) return -errno; return prog_redirect_output(prog); err_fork: if (pid_file) fclose(pid_file); err_pid_file_open: return -errno; } static int prog_init(struct isochron_daemon *prog) { int rc; rc = prog_rtnl_open(prog); if (rc) goto err_rtnl; rc = prog_init_mgmt_listen_sock(prog); if (rc) goto err_mgmt_sock; rc = prog_daemonize(prog); if (rc) { fprintf(stderr, "prog_daemonize returned %d\n", rc); goto err_daemonize; } rc = mlockall(MCL_CURRENT | MCL_FUTURE); if (rc < 0) { perror("mlockall failed"); goto err_mlockall; } return 0; err_mlockall: err_daemonize: prog_teardown_mgmt_listen_sock(prog); err_mgmt_sock: prog_rtnl_close(prog); err_rtnl: return rc; } static void prog_teardown(struct isochron_daemon *prog) { munlockall(); prog_teardown_mgmt_listen_sock(prog); prog_rtnl_close(prog); } static int prog_parse_args(int argc, char **argv, struct isochron_daemon *prog) { bool help = false; struct prog_arg args[] = { { .short_opt = "-h", .long_opt = "--help", .type = PROG_ARG_HELP, .help_ptr = { .ptr = &help, }, .optional = true, }, { .short_opt = "-p", .long_opt = "--pid-file", .type = PROG_ARG_FILEPATH, .filepath = { .buf = prog->pid_filename, .size = PATH_MAX - 1, }, .optional = true, }, { .short_opt = "-l", .long_opt = "--log-file", .type = PROG_ARG_FILEPATH, .filepath = { .buf = prog->log_filename, .size = PATH_MAX - 1, }, .optional = true, }, { .short_opt = "-P", .long_opt = "--stats-port", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->stats_port, }, .optional = true, }, { .short_opt = "-S", .long_opt = "--stats-address", .type = PROG_ARG_IP, .ip_ptr = { .ptr = &prog->stats_addr, }, .optional = true, }, }; int rc; rc = prog_parse_np_args(argc, argv, args, ARRAY_SIZE(args)); /* Non-positional arguments left unconsumed */ if (rc < 0) { pr_err(rc, "argument parsing failed: %m\n"); return rc; } else if (rc < argc) { fprintf(stderr, "%d unconsumed arguments. First: %s\n", argc - rc, argv[rc]); prog_usage("isochron-rcv", args, ARRAY_SIZE(args)); return -1; } if (help) { prog_usage("isochron-daemon", args, ARRAY_SIZE(args)); return -1; } if (!prog->stats_port) prog->stats_port = ISOCHRON_STATS_PORT; return 0; } int isochron_daemon_main(int argc, char *argv[]) { struct isochron_daemon prog = {0}; int rc; rc = prog_parse_args(argc, argv, &prog); if (rc < 0) return rc; rc = prog_init(&prog); if (rc < 0) return rc; rc = prog_mgmt_loop(&prog); prog_teardown(&prog); return rc; } isochron-0.9/src/endian.h000066400000000000000000000042651431034026500154110ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2019-2021 NXP */ /* This file contains code snippets from: * - The Linux kernel * - libnfnetlink.h */ #ifndef _ISOCHRON_ENDIAN_H /* _ENDIAN_H name is taken :-/ */ #define _ISOCHRON_ENDIAN_H /* Copied from libnfnetlink.h */ /* Pablo: What is the equivalence of be64_to_cpu in userspace? * * Harald: Good question. I don't think there's a standard way [yet?], * so I'd suggest manually implementing it by "#if little endian" bitshift * operations in C (at least for now). * * All the payload of any nfattr will always be in network byte order. * This would allow easy transport over a real network in the future * (e.g. jamal's netlink2). * * Pablo: I've called it __be64_to_cpu instead of be64_to_cpu, since maybe * there will one in the userspace headers someday. We don't want to * pollute POSIX space naming, */ #include #ifdef __CHECKER__ #define __force __attribute__((force)) #else # define __force #endif /* __CHECKER__ */ #if __BYTE_ORDER == __BIG_ENDIAN # ifndef __be16_to_cpu # define __be16_to_cpu(x) ((__force __u16)(__be16)(x)) # endif # ifndef __cpu_to_be16 # define __cpu_to_be16(x) ((__force __be16)(__u16)(x)) # endif # ifndef __be32_to_cpu # define __be32_to_cpu(x) ((__force __u32)(__be32)(x)) # endif # ifndef __cpu_to_be32 # define __cpu_to_be32(x) ((__force __be32)(__u32)(x)) # endif # ifndef __be64_to_cpu # define __be64_to_cpu(x) ((__force __u64)(__be64)(x)) # endif # ifndef __cpu_to_be64 # define __cpu_to_be64(x) ((__force __be64)(__u64)(x)) # endif # else # if __BYTE_ORDER == __LITTLE_ENDIAN # ifndef __be16_to_cpu # define __be16_to_cpu(x) __bswap_16((__force __u16)(__be16)(x)) # endif # ifndef __cpu_to_be16 # define __cpu_to_be16(x) ((__force __be16)__bswap_16((x))) # endif # ifndef __be32_to_cpu # define __be32_to_cpu(x) __bswap_32((__force __u32)(__be32)(x)) # endif # ifndef __cpu_to_be32 # define __cpu_to_be32(x) ((__force __be32)__bswap_32((x))) # endif # ifndef __be64_to_cpu # define __be64_to_cpu(x) __bswap_64((__force __u64)(__be64)(x)) # endif # ifndef __cpu_to_be64 # define __cpu_to_be64(x) ((__force __be64)__bswap_64((x))) # endif # endif #endif #endif isochron-0.9/src/isochron.c000066400000000000000000000061351431034026500157700ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright 2020 NXP */ #include #include #include #include #include #include "common.h" #include "isochron.h" static const struct isochron_prog progs[] = { { .prog_name = "isochron-daemon", .prog_func = "daemon", .main = isochron_daemon_main, }, { .prog_name = "isochron-orchestrate", .prog_func = "orchestrate", .main = isochron_orchestrate_main, }, { .prog_name = "isochron-send", .prog_func = "send", .main = isochron_send_main, }, { .prog_name = "isochron-rcv", .prog_func = "rcv", .main = isochron_rcv_main, }, { .prog_name = "isochron-report", .prog_func = "report", .main = isochron_report_main, }, }; bool signal_received; static void isochron_signal_handler(int signo) { switch (signo) { case SIGTERM: case SIGINT: signal_received = true; break; default: break; } } static void isochron_usage(void) { size_t i; fprintf(stderr, "isochron usage:\n"); for (i = 0; i < ARRAY_SIZE(progs); i++) fprintf(stderr, "isochron %s ...\n", progs[i].prog_func); fprintf(stderr, "Run "); for (i = 0; i < ARRAY_SIZE(progs); i++) fprintf(stderr, "\"isochron %s --help\", ", progs[i].prog_func); fprintf(stderr, "for more details.\n"); } int isochron_parse_args(int *argc, char ***argv, const struct isochron_prog **prog) { char *prog_name; char *prog_func; size_t i; if (*argc < 2) { isochron_usage(); return -EINVAL; } /* First try to match on program name */ prog_name = *argv[0]; (*argv)++; (*argc)--; for (i = 0; i < ARRAY_SIZE(progs); i++) { if (strcmp(prog_name, progs[i].prog_name) == 0) { *prog = &progs[i]; return 0; } } /* Next try to match on function name */ prog_func = (*argv)[0]; (*argv)++; (*argc)--; if (!strcmp(prog_func, "-V") || !strcmp(prog_func, "--version")) { fprintf(stderr, "%s\n", VERSION); return -EINVAL; } if (!strcmp(prog_func, "-h") || !strcmp(prog_func, "--help")) { isochron_usage(); return -EINVAL; } for (i = 0; i < ARRAY_SIZE(progs); i++) { if (strcmp(prog_func, progs[i].prog_func) == 0) { *prog = &progs[i]; return 0; } } fprintf(stderr, "%s: unknown function %s, expected one of ", prog_name, prog_func); for (i = 0; i < ARRAY_SIZE(progs); i++) fprintf(stderr, "\"%s\", ", progs[i].prog_func); fprintf(stderr, "\n"); return -EINVAL; } static int isochron_handle_signals(void (*handler)(int signo)) { struct sigaction sa; int rc; sa.sa_handler = handler; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); rc = sigaction(SIGTERM, &sa, NULL); if (rc < 0) { perror("can't catch SIGTERM"); return -errno; } rc = sigaction(SIGINT, &sa, NULL); if (rc < 0) { perror("can't catch SIGINT"); return -errno; } rc = sigaction(SIGPIPE, &sa, NULL); if (rc < 0) { perror("can't catch SIGPIPE"); return -errno; } return 0; } int main(int argc, char *argv[]) { const struct isochron_prog *prog; int rc; rc = isochron_handle_signals(isochron_signal_handler); if (rc) return rc; rc = isochron_parse_args(&argc, &argv, &prog); if (rc) return -rc; return prog->main(argc, argv); } isochron-0.9/src/isochron.h000066400000000000000000000012521431034026500157700ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2020-2021 NXP */ #ifndef _ISOCHRON_H #define _ISOCHRON_H #include typedef int isochron_prog_main_func_t(int argc, char *argv[]); struct isochron_prog { const char *prog_name; const char *prog_func; isochron_prog_main_func_t *main; }; int isochron_daemon_main(int argc, char *argv[]); int isochron_orchestrate_main(int argc, char *argv[]); int isochron_send_main(int argc, char *argv[]); int isochron_rcv_main(int argc, char *argv[]); int isochron_report_main(int argc, char *argv[]); int isochron_parse_args(int *argc, char ***argv, const struct isochron_prog **prog); extern bool signal_received; #endif isochron-0.9/src/log.c000066400000000000000000001016341431034026500147250ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright 2019-2021 NXP */ #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "endian.h" #include "log.h" #define ISOCHRON_LOG_VERSION 4 #define FILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) /*0660*/ #define ISOCHRON_FLAG_OMIT_SYNC BIT(0) #define ISOCHRON_FLAG_DO_TS BIT(1) #define ISOCHRON_FLAG_TAPRIO BIT(2) #define ISOCHRON_FLAG_TXTIME BIT(3) #define ISOCHRON_FLAG_DEADLINE BIT(4) static const char *isochron_magic = "ISOCHRON"; struct isochron_log_file_header { char magic[8]; __be32 version; __be32 packet_count; __be16 frame_size; __be16 flags; __be32 reserved; __be64 base_time; __be64 advance_time; __be64 shift_time; __be64 cycle_time; __be64 window_size; __be64 send_log_start; __be64 rcv_log_start; __be32 send_log_size; __be32 rcv_log_size; __be64 reserved2; } __attribute((packed)); struct isochron_packet_metrics { LIST_ENTRY(isochron_packet_metrics) list; __s64 wakeup_to_hw_ts; __s64 hw_rx_deadline_delta; __s64 latency_budget; __s64 path_delay; __s64 wakeup_latency; __s64 sender_latency; __s64 driver_latency; __s64 arrival_latency; __u32 seqid; }; struct isochron_stats { LIST_HEAD(stats_head, isochron_packet_metrics) entries; int frame_count; int hw_tx_deadline_misses; double tx_sync_offset_mean; double rx_sync_offset_mean; double path_delay_mean; }; struct isochron_metric_stats { int seqid_of_min; int seqid_of_max; __s64 min; __s64 max; double mean; double stddev; }; #define ISOCHRON_FMT_TIME BIT(0) #define ISOCHRON_FMT_SIGNED BIT(1) #define ISOCHRON_FMT_UNSIGNED BIT(2) #define ISOCHRON_FMT_HEX BIT(3) struct isochron_printf_variables { __s64 advance_time; /* A */ __s64 base_time; /* B */ __s64 cycle_time; /* C */ __s64 shift_time; /* H */ __s64 window_size; /* W */ __s64 tx_scheduled; /* S */ __s64 tx_wakeup; /* w */ __s64 tx_hwts; /* T */ __s64 tx_swts; /* t */ __s64 tx_sched; /* s */ __u32 seqid; /* q */ __s64 arrival; /* a */ __s64 rx_hwts; /* R */ __s64 rx_swts; /* r */ }; struct isochron_variable_code { size_t offset; size_t size; unsigned long valid_formats; }; static const struct isochron_variable_code variable_codes[256] = { ['A'] = { .offset = offsetof(struct isochron_printf_variables, advance_time), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['B'] = { .offset = offsetof(struct isochron_printf_variables, base_time), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['C'] = { .offset = offsetof(struct isochron_printf_variables, cycle_time), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['H'] = { .offset = offsetof(struct isochron_printf_variables, shift_time), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['W'] = { .offset = offsetof(struct isochron_printf_variables, window_size), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['S'] = { .offset = offsetof(struct isochron_printf_variables, tx_scheduled), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['w'] = { .offset = offsetof(struct isochron_printf_variables, tx_wakeup), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['T'] = { .offset = offsetof(struct isochron_printf_variables, tx_hwts), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['t'] = { .offset = offsetof(struct isochron_printf_variables, tx_swts), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['s'] = { .offset = offsetof(struct isochron_printf_variables, tx_sched), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['q'] = { .offset = offsetof(struct isochron_printf_variables, seqid), .size = sizeof(__u32), .valid_formats = ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['a'] = { .offset = offsetof(struct isochron_printf_variables, arrival), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['R'] = { .offset = offsetof(struct isochron_printf_variables, rx_hwts), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, ['r'] = { .offset = offsetof(struct isochron_printf_variables, rx_swts), .size = sizeof(__s64), .valid_formats = ISOCHRON_FMT_TIME | ISOCHRON_FMT_SIGNED | ISOCHRON_FMT_UNSIGNED | ISOCHRON_FMT_HEX, }, }; size_t isochron_log_buf_tlv_size(struct isochron_log *log) { return sizeof(__be32) + /* log_version */ sizeof(__be32) + /* buf_len */ log->size; } /* Get a reference to an existing log entry */ void *isochron_log_get_entry(struct isochron_log *log, size_t entry_size, int index) { if (index * entry_size > log->size) return NULL; return log->buf + entry_size * index; } int isochron_log_send_pkt(struct isochron_log *log, const struct isochron_send_pkt_data *send_pkt) { __u32 index = __be32_to_cpu(send_pkt->seqid) - 1; struct isochron_send_pkt_data *dest; dest = isochron_log_get_entry(log, sizeof(*send_pkt), index); if (!dest) { fprintf(stderr, "cannot log send packet with index %u\n", index); return -EINVAL; } memcpy(dest, send_pkt, sizeof(*send_pkt)); return 0; } int isochron_log_rcv_pkt(struct isochron_log *log, const struct isochron_rcv_pkt_data *rcv_pkt) { __u32 index = __be32_to_cpu(rcv_pkt->seqid) - 1; struct isochron_rcv_pkt_data *dest; dest = isochron_log_get_entry(log, sizeof(*rcv_pkt), index); if (!dest) { fprintf(stderr, "cannot log rcv packet with index %u\n", index); return -EINVAL; } memcpy(dest, rcv_pkt, sizeof(*rcv_pkt)); return 0; } int isochron_log_xmit(struct isochron_log *log, struct sk *sock) { __be32 log_version = __cpu_to_be32(ISOCHRON_LOG_VERSION); __be32 buf_len = __cpu_to_be32(log->size); int rc; rc = sk_send(sock, &log_version, sizeof(log_version)); if (rc) { sk_err(sock, rc, "Failed to write log version to socket: %m\n"); return -errno; } rc = sk_send(sock, &buf_len, sizeof(buf_len)); if (rc) { sk_err(sock, rc, "Failed to write log length to socket: %m\n"); return -errno; } if (log->size) { rc = sk_send(sock, log->buf, log->size); if (rc) { sk_err(sock, rc, "Failed to write log to socket: %m\n"); return -errno; } } return 0; } int isochron_log_recv(struct isochron_log *log, struct sk *sock) { __be32 log_version; __be32 buf_len; int rc; rc = sk_recv(sock, &log_version, sizeof(log_version), 0); if (rc) { sk_err(sock, rc, "could not read log version: %m\n"); return rc; } if (__be32_to_cpu(log_version) != ISOCHRON_LOG_VERSION) { fprintf(stderr, "incompatible isochron log version %d, expected %d, exiting\n", __be32_to_cpu(log_version), ISOCHRON_LOG_VERSION); return -EINVAL; } rc = sk_recv(sock, &buf_len, sizeof(buf_len), 0); if (rc) { sk_err(sock, rc, "could not read buffer length: %m\n"); return rc; } rc = isochron_log_init(log, __be32_to_cpu(buf_len)); if (rc) return rc; log->size = __be32_to_cpu(buf_len); if (log->size) { rc = sk_recv(sock, log->buf, log->size, 0); if (rc) { sk_err(sock, rc, "could not read log: %m"); isochron_log_teardown(log); return rc; } } return 0; } void isochron_rcv_log_print(struct isochron_log *log) { char *log_buf_end = log->buf + log->size; struct isochron_rcv_pkt_data *rcv_pkt; for (rcv_pkt = (struct isochron_rcv_pkt_data *)log->buf; (char *)rcv_pkt < log_buf_end; rcv_pkt++) { __s64 rx_hwts = (__s64 )__be64_to_cpu(rcv_pkt->hwts); __s64 rx_swts = (__s64 )__be64_to_cpu(rcv_pkt->swts); __u32 seqid = __be32_to_cpu(rcv_pkt->seqid); /* Print packet */ if (rcv_pkt->hwts) { char hwts_buf[TIMESPEC_BUFSIZ]; char swts_buf[TIMESPEC_BUFSIZ]; ns_sprintf(hwts_buf, rx_hwts); ns_sprintf(swts_buf, rx_swts); printf("seqid %u rxtstamp %s swts %s\n", seqid, hwts_buf, swts_buf); } else if (seqid) { printf("seqid %u\n", seqid); } } } void isochron_send_log_print(struct isochron_log *log) { char *log_buf_end = log->buf + log->size; struct isochron_send_pkt_data *send_pkt; for (send_pkt = (struct isochron_send_pkt_data *)log->buf; (char *)send_pkt < log_buf_end; send_pkt++) { __s64 tx_scheduled = (__s64 )__be64_to_cpu(send_pkt->scheduled); __s64 tx_hwts = (__s64 )__be64_to_cpu(send_pkt->hwts); __s64 tx_swts = (__s64 )__be64_to_cpu(send_pkt->swts); char scheduled_buf[TIMESPEC_BUFSIZ]; char hwts_buf[TIMESPEC_BUFSIZ]; char swts_buf[TIMESPEC_BUFSIZ]; ns_sprintf(scheduled_buf, tx_scheduled); ns_sprintf(hwts_buf, tx_hwts); ns_sprintf(swts_buf, tx_swts); printf("[%s] seqid %d txtstamp %s swts %s\n", scheduled_buf, __be32_to_cpu(send_pkt->seqid), hwts_buf, swts_buf); } } static struct isochron_rcv_pkt_data *isochron_rcv_log_find(struct isochron_log *rcv_log, __be32 seqid) { struct isochron_rcv_pkt_data *rcv_pkt; __u32 index; index = __be32_to_cpu(seqid) - 1; rcv_pkt = isochron_log_get_entry(rcv_log, sizeof(*rcv_pkt), index); if (!rcv_pkt) return NULL; if (rcv_pkt->seqid != seqid) return NULL; return rcv_pkt; } static void isochron_printf_vars_get(const struct isochron_send_pkt_data *send_pkt, const struct isochron_rcv_pkt_data *rcv_pkt, __s64 base_time, __s64 advance_time, __s64 shift_time, __s64 cycle_time, __s64 window_size, struct isochron_printf_variables *v) { v->base_time = base_time; v->advance_time = advance_time; v->shift_time = shift_time; v->cycle_time = cycle_time; v->window_size = window_size; v->tx_scheduled = (__s64 )__be64_to_cpu(send_pkt->scheduled); v->tx_wakeup = (__s64 )__be64_to_cpu(send_pkt->wakeup); v->tx_hwts = (__s64 )__be64_to_cpu(send_pkt->hwts); v->tx_swts = (__s64 )__be64_to_cpu(send_pkt->swts); v->tx_sched = (__s64 )__be64_to_cpu(send_pkt->sched_ts); v->rx_hwts = (__s64 )__be64_to_cpu(rcv_pkt->hwts); v->rx_swts = (__s64 )__be64_to_cpu(rcv_pkt->swts); v->arrival = (__s64 )__be64_to_cpu(rcv_pkt->arrival); v->seqid = (__u32 )__be32_to_cpu(send_pkt->seqid); } static int isochron_printf_signed_int(char *buf, const char *buf_end_ptr, const struct isochron_variable_code *vc, const struct isochron_printf_variables *v) { __s64 *var64 = (__s64 *)((char *)v + vc->offset); __s32 *var32 = (__s32 *)((char *)v + vc->offset); char tmp[30]; size_t size; switch (vc->size) { case sizeof(__s64): sprintf(tmp, "%lld", *var64); break; case sizeof(__s32): sprintf(tmp, "%d", *var32); break; default: fprintf(stderr, "Unrecognized variable size %zu for a signed int\n", vc->size); return -EINVAL; } size = strlen(tmp); if (buf + size >= buf_end_ptr) { fprintf(stderr, "Destination buffer not large enough to print signed int\n"); return -EINVAL; } strcpy(buf, tmp); return size; } static int isochron_printf_unsigned_int(char *buf, const char *buf_end_ptr, const struct isochron_variable_code *vc, const struct isochron_printf_variables *v) { __u64 *var64 = (__u64 *)((char *)v + vc->offset); __u32 *var32 = (__u32 *)((char *)v + vc->offset); char tmp[30]; size_t size; switch (vc->size) { case sizeof(__u64): sprintf(tmp, "%llu", *var64); break; case sizeof(__u32): sprintf(tmp, "%u", *var32); break; default: fprintf(stderr, "Unrecognized variable size %zu for a signed int\n", vc->size); return -EINVAL; } size = strlen(tmp); if (buf + size >= buf_end_ptr) { fprintf(stderr, "Destination buffer not large enough to print unsigned int\n"); return -EINVAL; } strcpy(buf, tmp); return size; } static int isochron_printf_hex_int(char *buf, const char *buf_end_ptr, const struct isochron_variable_code *vc, const struct isochron_printf_variables *v) { __u64 *var64 = (__u64 *)((char *)v + vc->offset); __u32 *var32 = (__u32 *)((char *)v + vc->offset); char tmp[30]; size_t size; switch (vc->size) { case sizeof(__u64): sprintf(tmp, "%llx", *var64); break; case sizeof(__u32): sprintf(tmp, "%x", *var32); break; default: fprintf(stderr, "Unrecognized variable size %zu for a signed int\n", vc->size); return -EINVAL; } size = strlen(tmp); if (buf + size >= buf_end_ptr) { fprintf(stderr, "Destination buffer not large enough to print hex int\n"); return -EINVAL; } strcpy(buf, tmp); return size; } static int isochron_printf_time(char *buf, const char *buf_end_ptr, const struct isochron_variable_code *vc, const struct isochron_printf_variables *v) { __u64 *var64 = (__u64 *)((char *)v + vc->offset); char tmp[TIMESPEC_BUFSIZ]; if (vc->size != sizeof(__s64)) { fprintf(stderr, "Unexpected size %zu for a time format\n", vc->size); return -EINVAL; } ns_sprintf(tmp, *var64); if (buf + strlen(tmp) >= buf_end_ptr) { fprintf(stderr, "Destination buffer not large enough to print time\n"); return -EINVAL; } strcpy(buf, tmp); return strlen(tmp); } static int isochron_printf_one_var(char *buf_ptr, const char *buf_end_ptr, const struct isochron_printf_variables *v, char var_code, char printf_code) { const struct isochron_variable_code *vc = &variable_codes[(__u8 )var_code]; if (!vc->valid_formats) { fprintf(stderr, "Unknown variable code '%c'\n", var_code); return -EINVAL; } switch (printf_code) { case 'd': if (!(vc->valid_formats & ISOCHRON_FMT_SIGNED)) { fprintf(stderr, "Variable '%c' cannot be printed as signed int\n", var_code); return -EINVAL; } return isochron_printf_signed_int(buf_ptr, buf_end_ptr, vc, v); case 'u': if (!(vc->valid_formats & ISOCHRON_FMT_UNSIGNED)) { fprintf(stderr, "Variable '%c' cannot be printed as signed int\n", var_code); return -EINVAL; } return isochron_printf_unsigned_int(buf_ptr, buf_end_ptr, vc, v); case 'x': if (!(vc->valid_formats & ISOCHRON_FMT_HEX)) { fprintf(stderr, "Variable '%c' cannot be printed as hexadecimal\n", var_code); return -EINVAL; } return isochron_printf_hex_int(buf_ptr, buf_end_ptr, vc, v); case 'T': if (!(vc->valid_formats & ISOCHRON_FMT_TIME)) { fprintf(stderr, "Variable '%c' cannot be printed as time\n", var_code); return -EINVAL; } return isochron_printf_time(buf_ptr, buf_end_ptr, vc, v); default: fprintf(stderr, "Unknown printf code '%c'\n", printf_code); return -EINVAL; } } static int buf_copy_verbatim(char *dest, const char *dest_end, const char *src, size_t size) { if (dest + size >= dest_end) { fprintf(stderr, "Buffer not large enough for printf format\n"); return -EINVAL; } memcpy(dest, src, size); return size; } static int isochron_printf_one_packet(const struct isochron_printf_variables *v, const char *printf_fmt, const char *printf_args) { const char *fmt_end_ptr = printf_fmt + strlen(printf_fmt); char buf[ISOCHRON_LOG_PRINTF_BUF_SIZE]; const char *args_ptr = printf_args; const char *fmt_ptr = printf_fmt; const char *args_end_ptr; char *buf_ptr = buf; char *percent, code; char *buf_end_ptr; int rc; buf_end_ptr = buf + ISOCHRON_LOG_PRINTF_BUF_SIZE - 1; args_end_ptr = printf_args + strlen(printf_args); do { percent = strchr(fmt_ptr, '%'); if (!percent) { rc = buf_copy_verbatim(buf_ptr, buf_end_ptr, fmt_ptr, strlen(fmt_ptr)); if (rc < 0) return rc; buf_ptr += rc; } else { if (percent + 1 >= fmt_end_ptr) { fprintf(stderr, "Illegal percent placement at the end of the printf format\n"); return -EINVAL; } code = *(percent + 1); /* Escaped %% */ if (code == '%') { /* Copy up to and including the first percent */ rc = buf_copy_verbatim(buf_ptr, buf_end_ptr, fmt_ptr, percent + 1 - fmt_ptr); if (rc < 0) return rc; buf_ptr += rc; /* Jump past both percent signs */ fmt_ptr = percent + 2; continue; } if (args_ptr >= args_end_ptr) { fprintf(stderr, "Not enough arguments for format\n"); return -EINVAL; } /* First copy verbatim up to the percent sign */ rc = buf_copy_verbatim(buf_ptr, buf_end_ptr, fmt_ptr, (percent - fmt_ptr)); if (rc < 0) return rc; buf_ptr += rc; rc = isochron_printf_one_var(buf_ptr, buf_end_ptr, v, *args_ptr, code); if (rc < 0) return rc; /* Advance past what we've just printed */ buf_ptr += rc; /* Jump past the percent and past the code character */ fmt_ptr = percent + 2; /* Consume one argument */ args_ptr++; } } while (percent); /* Avoid uselessly memsetting the whole buffer to zero, * just make sure it is NULL-terminated */ *buf_ptr = 0; if (args_ptr < args_end_ptr) { fprintf(stderr, "printf arguments left unconsumed\n"); return -EINVAL; } if (buf[0]) fputs(buf, stdout); return 0; } static void isochron_process_stat(const struct isochron_printf_variables *v, struct isochron_stats *stats, bool taprio, bool txtime) { struct isochron_packet_metrics *entry; entry = calloc(1, sizeof(*entry)); if (!entry) return; entry->seqid = v->seqid; entry->wakeup_to_hw_ts = v->tx_hwts - v->tx_wakeup; entry->hw_rx_deadline_delta = v->rx_hwts - v->tx_scheduled; /* When tc-taprio or tc-etf offload is enabled, we know that the * MAC TX timestamp will be larger than the gate event, because the * application's schedule should be the same as the NIC's schedule. * The NIC will buffer that packet until the gate opens, something * which does not happen normally. So when we operate on a NIC without * tc-taprio offload, the reported deadline delta will be negative, * i.e. the packet will be received before the deadline expired, * precisely because isochron actually sends the packet in advance of * the deadline. * Avoid printing negative values and interpret this delta as either a * positive "deadline delta" when we have tc-taprio (this should give * us the latency of the hardware), or as a (still positive) latency * budget, i.e. "how much we could still reduce the cycle time without * losing deadlines". */ if (taprio || txtime) entry->latency_budget = v->tx_hwts - v->tx_scheduled; else entry->latency_budget = v->tx_scheduled - v->tx_hwts; entry->path_delay = v->rx_hwts - v->tx_hwts; entry->wakeup_latency = v->tx_wakeup - (v->tx_scheduled - v->advance_time); entry->sender_latency = v->tx_swts - v->tx_wakeup; entry->driver_latency = v->tx_swts - v->tx_sched; entry->arrival_latency = v->arrival - v->rx_hwts; if (v->tx_hwts > v->tx_scheduled) stats->hw_tx_deadline_misses++; stats->frame_count++; stats->tx_sync_offset_mean += v->tx_hwts - v->tx_swts; stats->rx_sync_offset_mean += v->rx_hwts - v->rx_swts; stats->path_delay_mean += entry->path_delay; LIST_INSERT_HEAD(&stats->entries, entry, list); } /* For a given metric, iterate through the list of metric structures of each * packet and calculate minimum, maximum, average, standard deviation. */ static void isochron_metric_compute_stats(const struct isochron_stats *stats, struct isochron_metric_stats *ms, int metric_offset, bool interpret_in_reverse) { struct isochron_packet_metrics *entry; double sumsqr = 0; ms->seqid_of_max = 1; ms->seqid_of_min = 1; ms->min = LONG_MAX; ms->max = LONG_MIN; ms->mean = 0; LIST_FOREACH(entry, &stats->entries, list) { __s64 *metric = (__s64 *)((char *)entry + metric_offset); __s64 val = interpret_in_reverse ? -(*metric) : *metric; if (val < ms->min) { ms->min = val; ms->seqid_of_min = entry->seqid; } if (val > ms->max) { ms->max = val; ms->seqid_of_max = entry->seqid; } ms->mean += val; } ms->mean /= (double)stats->frame_count; LIST_FOREACH(entry, &stats->entries, list) { __s64 *metric = (__s64 *)((char *)entry + metric_offset); __s64 val = interpret_in_reverse ? -(*metric) : *metric; double deviation = (double)val - ms->mean; sumsqr += deviation * deviation; } ms->stddev = sqrt(sumsqr / (double)stats->frame_count); } static void isochron_print_metric_stats(const char *name, const struct isochron_metric_stats *ms) { printf("%s: min %lld max %lld mean %.3lf stddev %.3lf, " "min at seqid %d, max at seqid %d\n", name, ms->min, ms->max, ms->mean, ms->stddev, ms->seqid_of_min, ms->seqid_of_max); } int isochron_print_stats(struct isochron_log *send_log, struct isochron_log *rcv_log, const char *printf_fmt, const char *printf_args, unsigned long start, unsigned long stop, bool summary, bool omit_sync, bool taprio, bool txtime, __s64 base_time, __s64 advance_time, __s64 shift_time, __s64 cycle_time, __s64 window_size) { struct isochron_rcv_pkt_data dummy_rcv_pkt = {}; struct isochron_metric_stats sender_latency_ms; struct isochron_metric_stats wakeup_latency_ms; struct isochron_metric_stats driver_latency_ms; struct isochron_packet_metrics *entry, *tmp; struct isochron_send_pkt_data *pkt_arr; struct isochron_stats stats = {0}; struct isochron_metric_stats ms; __u64 not_tx_timestamped = 0; __u64 not_received = 0; size_t pkt_arr_size; __u32 seqid; int rc = 0; LIST_INIT(&stats.entries); pkt_arr = (struct isochron_send_pkt_data *)send_log->buf; pkt_arr_size = send_log->size / sizeof(*pkt_arr); if (start == 0 || start > pkt_arr_size || stop == 0 || stop > pkt_arr_size) { fprintf(stderr, "Trying to index an out-of-bounds element\n"); return -ERANGE; } for (seqid = start; seqid <= stop; seqid++) { struct isochron_send_pkt_data *send_pkt = &pkt_arr[seqid - 1]; struct isochron_rcv_pkt_data *rcv_pkt; struct isochron_printf_variables v; bool missing = false; if (seqid != __be32_to_cpu(send_pkt->seqid)) /* Incomplete log, send_pkt->seqid is 0, exit */ break; if (!__be64_to_cpu(send_pkt->swts) || !__be64_to_cpu(send_pkt->sched_ts) || !__be64_to_cpu(send_pkt->hwts)) { not_tx_timestamped++; missing = true; } /* For packets that didn't reach the receiver, at least report * the TX timestamps and seqid for debugging purposes, and use * a dummy received packet with all RX timestamps set to zero */ rcv_pkt = isochron_rcv_log_find(rcv_log, send_pkt->seqid); if (!rcv_pkt) { rcv_pkt = &dummy_rcv_pkt; missing = true; not_received++; } isochron_printf_vars_get(send_pkt, rcv_pkt, base_time, advance_time, shift_time, cycle_time, window_size, &v); rc = isochron_printf_one_packet(&v, printf_fmt, printf_args); if (rc) goto out; if (summary && !missing) isochron_process_stat(&v, &stats, taprio, txtime); } if (!summary) return 0; if (not_tx_timestamped) { printf("Packets not completely TX timestamped: %llu (%.3lf%%)\n", not_tx_timestamped, 100.0f * not_tx_timestamped / pkt_arr_size); } if (not_received) { printf("Packets not received: %llu (%.3lf%%)\n", not_received, 100.0f * not_received / pkt_arr_size); } if (!stats.frame_count) { printf("Could not calculate statistics, no packets were received\n"); return 0; } stats.tx_sync_offset_mean /= stats.frame_count; stats.rx_sync_offset_mean /= stats.frame_count; stats.path_delay_mean /= stats.frame_count; if (llabs((long long)stats.tx_sync_offset_mean) > NSEC_PER_SEC && !omit_sync) { printf("Sender PHC not synchronized (mean PHC to system time " "diff %.3lf ns larger than 1 second)\n", stats.tx_sync_offset_mean); } if (llabs((long long)stats.rx_sync_offset_mean) > NSEC_PER_SEC && !omit_sync) { printf("Receiver PHC not synchronized (mean PHC to system time " "diff %.3lf ns larger than 1 second)\n", stats.rx_sync_offset_mean); } if (llabs((long long)stats.path_delay_mean) > NSEC_PER_SEC && !omit_sync) { printf("Sender and receiver not synchronized (mean path delay " "%.3lf ns larger than 1 second)\n", stats.path_delay_mean); } printf("Summary:\n"); /* Path delay */ isochron_metric_compute_stats(&stats, &ms, offsetof(struct isochron_packet_metrics, path_delay), false); isochron_print_metric_stats("Path delay", &ms); /* Wakeup to HW TX timestamp */ isochron_metric_compute_stats(&stats, &ms, offsetof(struct isochron_packet_metrics, wakeup_to_hw_ts), false); isochron_print_metric_stats("Wakeup to HW TX timestamp", &ms); /* HW RX deadline delta (TX time to HW RX timestamp) */ isochron_metric_compute_stats(&stats, &ms, offsetof(struct isochron_packet_metrics, hw_rx_deadline_delta), false); if (ms.mean > 0) { isochron_print_metric_stats("Packets arrived later than scheduled. TX time to HW RX timestamp", &ms); } else { isochron_metric_compute_stats(&stats, &ms, offsetof(struct isochron_packet_metrics, hw_rx_deadline_delta), true); isochron_print_metric_stats("Packets arrived earlier than scheduled. HW RX timestamp to TX time", &ms); } /* Latency budget, interpreted differently depending on testing mode */ isochron_metric_compute_stats(&stats, &ms, offsetof(struct isochron_packet_metrics, latency_budget), false); if (taprio || txtime) isochron_print_metric_stats("MAC latency", &ms); else isochron_print_metric_stats("Application latency budget", &ms); isochron_metric_compute_stats(&stats, &ms, offsetof(struct isochron_packet_metrics, sender_latency), false); isochron_print_metric_stats("Sender latency", &ms); sender_latency_ms = ms; /* Wakeup latency */ isochron_metric_compute_stats(&stats, &ms, offsetof(struct isochron_packet_metrics, wakeup_latency), false); wakeup_latency_ms = ms; isochron_print_metric_stats("Wakeup latency", &ms); /* Driver latency */ isochron_metric_compute_stats(&stats, &ms, offsetof(struct isochron_packet_metrics, driver_latency), false); driver_latency_ms = ms; isochron_print_metric_stats("Driver latency", &ms); /* Arrival latency */ isochron_metric_compute_stats(&stats, &ms, offsetof(struct isochron_packet_metrics, arrival_latency), false); isochron_print_metric_stats("Arrival latency", &ms); printf("Sending one packet takes on average %.3lf%% of the cycle time (min %.3lf%% max %.3lf%%)\n", 100.0f * sender_latency_ms.mean / cycle_time, 100.0f * sender_latency_ms.min / cycle_time, 100.0f * sender_latency_ms.max / cycle_time); printf("Waking up takes on average %.3lf%% of the cycle time (min %.3lf%% max %.3lf%%)\n", 100.0f * wakeup_latency_ms.mean / cycle_time, 100.0f * wakeup_latency_ms.min / cycle_time, 100.0f * wakeup_latency_ms.max / cycle_time); printf("Driver takes on average %.3lf%% of the cycle time to send a packet (min %.3lf%% max %.3lf%%)\n", 100.0f * driver_latency_ms.mean / cycle_time, 100.0f * driver_latency_ms.min / cycle_time, 100.0f * driver_latency_ms.max / cycle_time); /* HW TX deadline misses */ if (!taprio && !txtime) printf("HW TX deadline misses: %d (%.3lf%%)\n", stats.hw_tx_deadline_misses, 100.0f * stats.hw_tx_deadline_misses / stats.frame_count); out: LIST_FOREACH_SAFE(entry, &stats.entries, list, tmp) { LIST_REMOVE(entry, list); free(entry); } return rc; } int isochron_log_init(struct isochron_log *log, size_t size) { log->buf = calloc(sizeof(char), size); if (!log->buf) return -ENOMEM; log->size = size; return 0; } void isochron_log_teardown(struct isochron_log *log) { free(log->buf); } int isochron_log_for_each_pkt(struct isochron_log *log, size_t pkt_size, void *priv, isochron_log_walk_cb_t cb) { size_t i, pkt_arr_size = log->size / pkt_size; int rc; for (i = 0; i < pkt_arr_size; i++) { void *pkt = (void *)((__u8 *)log->buf + i * pkt_size); rc = cb(priv, pkt); if (rc) return rc; } return 0; } int isochron_log_load(const char *file, struct isochron_log *send_log, struct isochron_log *rcv_log, long *packet_count, long *frame_size, bool *omit_sync, bool *do_ts, bool *taprio, bool *txtime, bool *deadline, __s64 *base_time, __s64 *advance_time, __s64 *shift_time, __s64 *cycle_time, __s64 *window_size) { struct isochron_log_file_header header; size_t len; int fd, rc; int flags; fd = open(file, O_RDONLY); if (fd < 0) { fprintf(stderr, "Failed to open file %s: %m\n", file); rc = fd; goto out; } len = read_exact(fd, &header, sizeof(header)); if (len <= 0) { perror("Failed to read log header from file"); rc = len; goto out_close; } if (memcmp(header.magic, isochron_magic, strlen(isochron_magic))) { fprintf(stderr, "Unrecognized file format\n"); rc = -EINVAL; goto out_close; } flags = __be16_to_cpu(header.flags); *omit_sync = !!(flags & ISOCHRON_FLAG_OMIT_SYNC); *do_ts = !!(flags & ISOCHRON_FLAG_DO_TS); *taprio = !!(flags & ISOCHRON_FLAG_TAPRIO); *txtime = !!(flags & ISOCHRON_FLAG_TXTIME); *deadline = !!(flags & ISOCHRON_FLAG_DEADLINE); *packet_count = __be32_to_cpu(header.packet_count); *frame_size = __be16_to_cpu(header.frame_size); *base_time = (__s64 )__be64_to_cpu(header.base_time); *advance_time = (__s64 )__be64_to_cpu(header.advance_time); *shift_time = (__s64 )__be64_to_cpu(header.shift_time); *cycle_time = (__s64 )__be64_to_cpu(header.cycle_time); *window_size = (__s64 )__be64_to_cpu(header.window_size); if (lseek(fd, __be64_to_cpu(header.send_log_start), SEEK_SET) < 0) { perror("Failed to seek to the sender log"); rc = -errno; goto out_close; } rc = isochron_log_init(send_log, __be32_to_cpu(header.send_log_size)); if (rc) { fprintf(stderr, "failed to allocate memory for send log\n"); goto out_close; } len = read_exact(fd, send_log->buf, send_log->size); if (len <= 0) { perror("Failed to read sender log"); rc = len; goto out_send_log_teardown; } if (lseek(fd, __be64_to_cpu(header.rcv_log_start), SEEK_SET) < 0) { perror("Failed to seek to the receiver log"); rc = -errno; goto out_send_log_teardown; } rc = isochron_log_init(rcv_log, __be32_to_cpu(header.rcv_log_size)); if (rc) { fprintf(stderr, "failed to allocate memory for rcv log\n"); goto out_send_log_teardown; } len = read_exact(fd, rcv_log->buf, rcv_log->size); if (len <= 0) { perror("Failed to read receiver log"); rc = len; goto out_rcv_log_teardown; } close(fd); return 0; out_rcv_log_teardown: isochron_log_teardown(rcv_log); out_send_log_teardown: isochron_log_teardown(send_log); out_close: close(fd); out: return rc; } int isochron_log_save(const char *file, const struct isochron_log *send_log, const struct isochron_log *rcv_log, long packet_count, long frame_size, bool omit_sync, bool do_ts, bool taprio, bool txtime, bool deadline, __s64 base_time, __s64 advance_time, __s64 shift_time, __s64 cycle_time, __s64 window_size) { struct isochron_log_file_header header = { .version = __cpu_to_be32(ISOCHRON_LOG_VERSION), .packet_count = __cpu_to_be32(packet_count), .frame_size = __cpu_to_be16(frame_size), .base_time = __cpu_to_be64(base_time), .advance_time = __cpu_to_be64(advance_time), .shift_time = __cpu_to_be64(shift_time), .cycle_time = __cpu_to_be64(cycle_time), .window_size = __cpu_to_be64(window_size), }; int flags = 0; size_t len; int fd; if (omit_sync) flags |= ISOCHRON_FLAG_OMIT_SYNC; if (do_ts) flags |= ISOCHRON_FLAG_DO_TS; if (taprio) flags |= ISOCHRON_FLAG_TAPRIO; if (txtime) flags |= ISOCHRON_FLAG_TXTIME; if (deadline) flags |= ISOCHRON_FLAG_DEADLINE; memcpy(header.magic, isochron_magic, strlen(isochron_magic)); header.flags = __cpu_to_be16(flags); header.send_log_start = __cpu_to_be64(sizeof(header)); header.send_log_size = __cpu_to_be32(send_log->size); header.rcv_log_start = __cpu_to_be64(sizeof(header) + send_log->size); header.rcv_log_size = __cpu_to_be32(rcv_log->size); fd = open(file, O_CREAT | O_WRONLY | O_TRUNC, FILEMODE); if (fd < 0) { perror("open"); return fd; } len = write_exact(fd, &header, sizeof(header)); if (len <= 0) { perror("Failed to write log header to file"); close(fd); return len; } len = write_exact(fd, send_log->buf, send_log->size); if (len <= 0) { perror("Failed to write send log to file"); close(fd); return len; } len = write_exact(fd, rcv_log->buf, rcv_log->size); if (len <= 0) { perror("Failed to write receive log to file"); close(fd); return len; } close(fd); return 0; } isochron-0.9/src/log.h000066400000000000000000000051571431034026500147350ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2019-2021 NXP */ #ifndef _ISOCHRON_LOG_H #define _ISOCHRON_LOG_H #include #include #include #include #include #include "sk.h" #define ISOCHRON_LOG_PRINTF_MAX_NUM_ARGS 256 #define ISOCHRON_LOG_PRINTF_BUF_SIZE 4096 struct isochron_send_pkt_data { __be32 seqid; __be32 reserved; __be64 scheduled; __be64 wakeup; __be64 hwts; __be64 swts; __be64 sched_ts; } __attribute((packed)); struct isochron_rcv_pkt_data { __be32 seqid; __be32 reserved; __be64 arrival; __be64 hwts; __be64 swts; } __attribute((packed)); struct isochron_log { size_t size; char *buf; }; int isochron_log_init(struct isochron_log *log, size_t size); void *isochron_log_get_entry(struct isochron_log *log, size_t entry_size, int index); int isochron_log_xmit(struct isochron_log *log, struct sk *sock); int isochron_log_recv(struct isochron_log *log, struct sk *sock); void isochron_log_teardown(struct isochron_log *log); void isochron_rcv_log_print(struct isochron_log *log); void isochron_send_log_print(struct isochron_log *log); typedef int isochron_log_walk_cb_t(void *priv, void *pkt); int isochron_log_for_each_pkt(struct isochron_log *log, size_t pkt_size, void *priv, isochron_log_walk_cb_t cb); int isochron_log_send_pkt(struct isochron_log *log, const struct isochron_send_pkt_data *send_pkt); int isochron_log_rcv_pkt(struct isochron_log *log, const struct isochron_rcv_pkt_data *rcv_pkt); int isochron_print_stats(struct isochron_log *send_log, struct isochron_log *rcv_log, const char *printf_fmt, const char *printf_args, unsigned long start, unsigned long stop, bool summary, bool omit_sync, bool taprio, bool txtime, __s64 base_time, __s64 advance_time, __s64 shift_time, __s64 cycle_time, __s64 window_size); size_t isochron_log_buf_tlv_size(struct isochron_log *log); int isochron_log_load(const char *file, struct isochron_log *send_log, struct isochron_log *rcv_log, long *packet_count, long *frame_size, bool *omit_sync, bool *do_ts, bool *taprio, bool *txtime, bool *deadline, __s64 *base_time, __s64 *advance_time, __s64 *shift_time, __s64 *cycle_time, __s64 *window_size); int isochron_log_save(const char *file, const struct isochron_log *send_log, const struct isochron_log *rcv_log, long packet_count, long frame_size, bool omit_sync, bool do_ts, bool taprio, bool txtime, bool deadline, __s64 base_time, __s64 advance_time, __s64 shift_time, __s64 cycle_time, __s64 window_size); #endif isochron-0.9/src/management.c000066400000000000000000001054301431034026500162560ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2021 NXP */ #include #include #include #include #include "argparser.h" #include "common.h" #include "management.h" #include "ptpmon.h" #include "rtnl.h" #include "sysmon.h" struct isochron_mgmt_handler { const struct isochron_mgmt_ops *ops; struct isochron_error *error_table; }; const char *mid_to_string(enum isochron_management_id mid) { switch (mid) { case ISOCHRON_MID_LOG: return "LOG"; case ISOCHRON_MID_SYSMON_OFFSET: return "SYSMON_OFFSET"; case ISOCHRON_MID_PTPMON_OFFSET: return "PTPMON_OFFSET"; case ISOCHRON_MID_UTC_OFFSET: return "UTC_OFFSET"; case ISOCHRON_MID_PORT_STATE: return "PORT_STATE"; case ISOCHRON_MID_GM_CLOCK_IDENTITY: return "GM_CLOCK_IDENTITY"; case ISOCHRON_MID_PACKET_COUNT: return "PACKET_COUNT"; case ISOCHRON_MID_DESTINATION_MAC: return "DESTINATION_MAC"; case ISOCHRON_MID_SOURCE_MAC: return "SOURCE_MAC"; case ISOCHRON_MID_NODE_ROLE: return "NODE_ROLE"; case ISOCHRON_MID_PACKET_SIZE: return "PACKET_SIZE"; case ISOCHRON_MID_IF_NAME: return "IF_NAME"; case ISOCHRON_MID_PRIORITY: return "PRIORITY"; case ISOCHRON_MID_STATS_PORT: return "STATS_PORT"; case ISOCHRON_MID_BASE_TIME: return "BASE_TIME"; case ISOCHRON_MID_ADVANCE_TIME: return "ADVANCE_TIME"; case ISOCHRON_MID_SHIFT_TIME: return "SHIFT_TIME"; case ISOCHRON_MID_CYCLE_TIME: return "CYCLE_TIME"; case ISOCHRON_MID_WINDOW_SIZE: return "WINDOW_SIZE"; case ISOCHRON_MID_SYSMON_ENABLED: return "SYSMON_ENABLED"; case ISOCHRON_MID_PTPMON_ENABLED: return "PTPMON_ENABLED"; case ISOCHRON_MID_UDS: return "UDS"; case ISOCHRON_MID_DOMAIN_NUMBER: return "DOMAIN_NUMBER"; case ISOCHRON_MID_TRANSPORT_SPECIFIC: return "TRANSPORT_SPECIFIC"; case ISOCHRON_MID_NUM_READINGS: return "NUM_READINGS"; case ISOCHRON_MID_TS_ENABLED: return "TS_ENABLED"; case ISOCHRON_MID_VID: return "VID"; case ISOCHRON_MID_ETHERTYPE: return "ETHERTYPE"; case ISOCHRON_MID_QUIET_ENABLED: return "QUIET_ENABLED"; case ISOCHRON_MID_TAPRIO_ENABLED: return "TAPRIO_ENABLED"; case ISOCHRON_MID_TXTIME_ENABLED: return "TXTIME_ENABLED"; case ISOCHRON_MID_DEADLINE_ENABLED: return "DEADLINE_ENABLED"; case ISOCHRON_MID_IP_DESTINATION: return "IP_DESTINATION"; case ISOCHRON_MID_L2_ENABLED: return "L2_ENABLED"; case ISOCHRON_MID_L4_ENABLED: return "L4_ENABLED"; case ISOCHRON_MID_DATA_PORT: return "DATA_PORT"; case ISOCHRON_MID_SCHED_FIFO_ENABLED: return "SCHED_FIFO_ENABLED"; case ISOCHRON_MID_SCHED_RR_ENABLED: return "SCHED_RR_ENABLED"; case ISOCHRON_MID_SCHED_PRIORITY: return "SCHED_PRIORITY"; case ISOCHRON_MID_CPU_MASK: return "CPU_MASK"; case ISOCHRON_MID_TEST_STATE: return "TEST_STATE"; case ISOCHRON_MID_SYNC_MONITOR_ENABLED: return "SYNC_MONITOR_ENABLED"; case ISOCHRON_MID_PORT_LINK_STATE: return "PORT_LINK_STATE"; case ISOCHRON_MID_CURRENT_CLOCK_TAI: return "CURRENT_CLOCK_TAI"; case ISOCHRON_MID_OPER_BASE_TIME: return "OPER_BASE_TIME"; default: return "UNKNOWN"; } }; int isochron_send_tlv(struct sk *sock, enum isochron_management_action action, enum isochron_management_id mid, size_t size) { struct isochron_management_message *msg; unsigned char buf[BUFSIZ]; struct isochron_tlv *tlv; memset(buf, 0, sizeof(*msg) + sizeof(*tlv)); msg = (struct isochron_management_message *)buf; msg->version = ISOCHRON_MANAGEMENT_VERSION; msg->action = action; msg->payload_length = __cpu_to_be32(sizeof(*tlv) + size); tlv = (struct isochron_tlv *)(msg + 1); tlv->tlv_type = __cpu_to_be16(ISOCHRON_TLV_MANAGEMENT); tlv->management_id = __cpu_to_be16(mid); tlv->length_field = __cpu_to_be32(size); return sk_send(sock, buf, sizeof(*msg) + sizeof(*tlv)); } static void isochron_send_empty_tlv(struct sk *sock, enum isochron_management_id mid) { isochron_send_tlv(sock, ISOCHRON_RESPONSE, mid, 0); } int isochron_collect_rcv_log(struct sk *sock, struct isochron_log *rcv_log) { struct isochron_management_message msg; struct isochron_tlv tlv; int rc; rc = isochron_send_tlv(sock, ISOCHRON_GET, ISOCHRON_MID_LOG, 0); if (rc) return rc; rc = sk_recv(sock, &msg, sizeof(msg), 0); if (rc) { sk_err(sock, rc, "Failed to receive GET response message header for log: %m\n"); return rc; } rc = sk_recv(sock, &tlv, sizeof(tlv), 0); if (rc) { sk_err(sock, rc, "Failed to receive GET response TLV for log: %m\n"); return rc; } if (msg.version != ISOCHRON_MANAGEMENT_VERSION || msg.action != ISOCHRON_RESPONSE || __be16_to_cpu(tlv.tlv_type) != ISOCHRON_TLV_MANAGEMENT || __be16_to_cpu(tlv.management_id) != ISOCHRON_MID_LOG) { fprintf(stderr, "Unexpected reply from isochron receiver\n"); return -EBADMSG; } return isochron_log_recv(rcv_log, sock); } static int isochron_drain_sk(struct sk *sock, size_t len) { unsigned char junk[BUFSIZ]; int rc; while (len) { size_t count = min(len, (size_t)BUFSIZ); rc = sk_recv(sock, junk, count, 0); if (rc) { sk_err(sock, rc, "Error while draining %zu bytes from socket: %m\n", len); return rc; } len -= count; }; return 0; } int isochron_query_mid_error(struct sk *sock, enum isochron_management_id mid, struct isochron_error *err) { struct isochron_management_message msg; size_t payload_length, tlv_length; struct isochron_tlv tlv; __be32 rc_be; int rc; rc = isochron_send_tlv(sock, ISOCHRON_GET_ERROR, mid, 0); if (rc) return rc; rc = sk_recv(sock, &msg, sizeof(msg), 0); if (rc) { sk_err(sock, rc, "Failed to receive GET_ERR response message header: %m\n"); return rc; } if (msg.version != ISOCHRON_MANAGEMENT_VERSION) { fprintf(stderr, "Failed to get error for MID %s: unexpected message version %d in response\n", mid_to_string(mid), msg.version); return -EBADMSG; } if (msg.action != ISOCHRON_RESPONSE) { fprintf(stderr, "Failed to get error for MID %s: unexpected action %d in response\n", mid_to_string(mid), msg.action); return -EBADMSG; } payload_length = __be32_to_cpu(msg.payload_length); if (payload_length < sizeof(tlv)) { fprintf(stderr, "Failed to get error for MID %s: TLV header length %zu shorter than expected\n", mid_to_string(mid), payload_length); rc = isochron_drain_sk(sock, payload_length); if (rc) return rc; return -EBADMSG; } rc = sk_recv(sock, &tlv, sizeof(tlv), 0); if (rc) { sk_err(sock, rc, "Failed to receive GET_ERR response TLV: %m\n"); return rc; } payload_length -= sizeof(tlv); tlv_length = __be32_to_cpu(tlv.length_field); if (tlv_length < sizeof(rc_be)) { fprintf(stderr, "Failed to get error for MID %s: expected TLV length at least %zu in response, got %zu\n", mid_to_string(mid), sizeof(rc_be), tlv_length); rc = isochron_drain_sk(sock, tlv_length); if (rc) return rc; return -EBADMSG; } if (__be16_to_cpu(tlv.tlv_type) != ISOCHRON_TLV_MANAGEMENT) { fprintf(stderr, "Failed to get error for MID %s: unexpected TLV type %d in response\n", mid_to_string(mid), __be16_to_cpu(tlv.tlv_type)); rc = isochron_drain_sk(sock, tlv_length); if (rc) return rc; return -EBADMSG; } if (__be16_to_cpu(tlv.management_id) != mid) { fprintf(stderr, "Failed to get error for MID %s: response for unexpected MID %s\n", mid_to_string(mid), mid_to_string(__be16_to_cpu(tlv.management_id))); rc = isochron_drain_sk(sock, tlv_length); if (rc) return rc; return -EBADMSG; } rc = sk_recv(sock, &rc_be, sizeof(rc_be), 0); if (rc) { sk_err(sock, rc, "Failed to receive error code for MID %s: %m\n", mid_to_string(mid)); return rc; } err->rc = (int)__be32_to_cpu(rc_be); memset(err->extack, 0, ISOCHRON_EXTACK_SIZE); payload_length -= sizeof(rc_be); if (payload_length >= ISOCHRON_EXTACK_SIZE) { fprintf(stderr, "extack message too long, discarding\n"); rc = isochron_drain_sk(sock, tlv_length); if (rc) return rc; } if (payload_length) { rc = sk_recv(sock, err->extack, payload_length, 0); if (rc) { sk_err(sock, rc, "Failed to receive extack for MID %s: %m\n", mid_to_string(mid)); return rc; } } return 0; } static void isochron_print_mid_error(struct sk *sock, enum isochron_management_id mid) { struct isochron_error err; if (isochron_query_mid_error(sock, mid, &err)) return; if (strlen(err.extack)) fprintf(stderr, "Remote error %d: %s\n", err.rc, err.extack); else pr_err(err.rc, "Remote error %d: %m\n", err.rc); } int isochron_query_mid(struct sk *sock, enum isochron_management_id mid, void *data, size_t data_len) { struct isochron_management_message msg; size_t payload_length, tlv_length; struct isochron_tlv tlv; int rc; rc = isochron_send_tlv(sock, ISOCHRON_GET, mid, 0); if (rc) { sk_err(sock, rc, "Failed to send GET message for MID %s: %m\n", mid_to_string(mid)); return rc; } rc = sk_recv(sock, &msg, sizeof(msg), 0); if (rc) { sk_err(sock, rc, "Failed to receive response message header for MID %s: %m\n", mid_to_string(mid)); return rc; } if (msg.version != ISOCHRON_MANAGEMENT_VERSION) { fprintf(stderr, "Failed to query MID %s: unexpected message version %d in response\n", mid_to_string(mid), msg.version); isochron_print_mid_error(sock, mid); return -EBADMSG; } if (msg.action != ISOCHRON_RESPONSE) { fprintf(stderr, "Failed to query MID %s: unexpected action %d in response\n", mid_to_string(mid), msg.action); isochron_print_mid_error(sock, mid); return -EBADMSG; } payload_length = __be32_to_cpu(msg.payload_length); if (payload_length != data_len + sizeof(tlv)) { if (data_len == sizeof(tlv)) { fprintf(stderr, "Failed to query MID %s: received empty payload in response\n", mid_to_string(mid)); } else { fprintf(stderr, "Failed to query MID %s: expected payload length %zu in response, got %zu\n", mid_to_string(mid), data_len + sizeof(tlv), payload_length); } rc = isochron_drain_sk(sock, payload_length); if (rc) return rc; isochron_print_mid_error(sock, mid); return -EBADMSG; } rc = sk_recv(sock, &tlv, sizeof(tlv), 0); if (rc) { sk_err(sock, rc, "Failed to receive TLV header for MID %s: %m\n", mid_to_string(mid)); return rc; } tlv_length = __be32_to_cpu(tlv.length_field); if (tlv_length != data_len) { fprintf(stderr, "Failed to query MID %s: expected TLV length %zu in response, got %zu\n", mid_to_string(mid), data_len, tlv_length); rc = isochron_drain_sk(sock, tlv_length); if (rc) return rc; isochron_print_mid_error(sock, mid); return -EBADMSG; } if (__be16_to_cpu(tlv.tlv_type) != ISOCHRON_TLV_MANAGEMENT) { fprintf(stderr, "Failed to query MID %s: unexpected TLV type %d in response\n", mid_to_string(mid), __be16_to_cpu(tlv.tlv_type)); rc = isochron_drain_sk(sock, tlv_length); if (rc) return rc; isochron_print_mid_error(sock, mid); return -EBADMSG; } if (__be16_to_cpu(tlv.management_id) != mid) { fprintf(stderr, "Failed to query MID %s: response for unexpected MID %s\n", mid_to_string(mid), mid_to_string(__be16_to_cpu(tlv.management_id))); rc = isochron_drain_sk(sock, tlv_length); if (rc) return rc; isochron_print_mid_error(sock, mid); return -EBADMSG; } if (data_len) { rc = sk_recv(sock, data, data_len, 0); if (rc) { sk_err(sock, rc, "Failed to receive management data for MID %s: %m\n", mid_to_string(mid)); return rc; } } return 0; } void mgmt_extack(char *extack, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsnprintf(extack, ISOCHRON_EXTACK_SIZE - 1, fmt, ap); va_end(ap); /* Print to the local error output as well, for posterity */ fprintf(stderr, "%s\n", extack); } static void isochron_mgmt_tlv_get(struct sk *sock, void *priv, enum isochron_management_id mid, const struct isochron_mgmt_ops *ops, struct isochron_error *err) { int rc; if (!ops->get) { mgmt_extack(err->extack, "Unhandled GET for MID %s", mid_to_string(mid)); err->rc = -EOPNOTSUPP; goto error; } *err->extack = 0; rc = ops->get(priv, err->extack); err->rc = rc; if (rc) goto error; return; error: isochron_send_empty_tlv(sock, mid); } static void isochron_mgmt_tlv_set(struct sk *sock, struct isochron_tlv *tlv, void *priv, enum isochron_management_id mid, const struct isochron_mgmt_ops *ops, struct isochron_error *err) { size_t tlv_len = __be32_to_cpu(tlv->length_field); int rc; if (!ops->set) { mgmt_extack(err->extack, "Unhandled SET for MID %s", mid_to_string(mid)); err->rc = -EOPNOTSUPP; goto error; } if (tlv_len != ops->struct_size) { mgmt_extack(err->extack, "Expected %zu bytes for SET of MID %s, got %zu", ops->struct_size, mid_to_string(mid), tlv_len); err->rc = -EINVAL; goto error; } *err->extack = 0; rc = ops->set(priv, isochron_tlv_data(tlv), err->extack); err->rc = rc; if (rc) goto error; /* Echo back the TLV data as ack */ rc = isochron_send_tlv(sock, ISOCHRON_RESPONSE, mid, ops->struct_size); if (rc) { mgmt_extack(err->extack, "Failed to send TLV response"); err->rc = rc; goto error; } sk_send(sock, isochron_tlv_data(tlv), ops->struct_size); return; error: isochron_send_empty_tlv(sock, mid); } static void isochron_forward_mgmt_err(struct sk *sock, enum isochron_management_id mid, const struct isochron_error *err) { size_t len = strlen(err->extack); __be32 err_be; int rc; rc = isochron_send_tlv(sock, ISOCHRON_RESPONSE, mid, sizeof(__be32) + len); if (rc) goto error; err_be = __cpu_to_be32(err->rc); sk_send(sock, &err_be, sizeof(err_be)); if (len) sk_send(sock, err->extack, len); return; error: isochron_send_empty_tlv(sock, mid); } static int isochron_update_mid(struct sk *sock, enum isochron_management_id mid, void *data, size_t data_len) { struct isochron_management_message msg; size_t payload_length, tlv_length; struct isochron_tlv tlv; unsigned char *tmp_buf; int rc; tmp_buf = malloc(data_len); if (!tmp_buf) return -ENOMEM; rc = isochron_send_tlv(sock, ISOCHRON_SET, mid, data_len); if (rc) goto out; rc = sk_send(sock, data, data_len); if (rc) goto out; rc = sk_recv(sock, &msg, sizeof(msg), 0); if (rc) goto out; if (msg.version != ISOCHRON_MANAGEMENT_VERSION) { fprintf(stderr, "Failed to update MID %s: unexpected message version %d in response\n", mid_to_string(mid), msg.version); isochron_print_mid_error(sock, mid); rc = -EBADMSG; goto out; } if (msg.action != ISOCHRON_RESPONSE) { fprintf(stderr, "Failed to update MID %s: unexpected action %d in response\n", mid_to_string(mid), msg.action); isochron_print_mid_error(sock, mid); rc = -EBADMSG; goto out; } payload_length = __be32_to_cpu(msg.payload_length); if (payload_length != data_len + sizeof(tlv)) { if (payload_length == sizeof(tlv)) { fprintf(stderr, "Failed to update MID %s: received empty payload in response\n", mid_to_string(mid)); } else { fprintf(stderr, "Failed to update MID %s: expected payload length %zu in response, got %zu\n", mid_to_string(mid), data_len + sizeof(tlv), payload_length); } rc = isochron_drain_sk(sock, payload_length); if (rc) goto out; isochron_print_mid_error(sock, mid); rc = -EBADMSG; goto out; } rc = sk_recv(sock, &tlv, sizeof(tlv), 0); if (rc) goto out; tlv_length = __be32_to_cpu(tlv.length_field); if (tlv_length != data_len) { fprintf(stderr, "Failed to update MID %s: expected TLV length %zu in response, got %zu\n", mid_to_string(mid), data_len, tlv_length); rc = isochron_drain_sk(sock, tlv_length); if (rc) goto out; isochron_print_mid_error(sock, mid); rc = -EBADMSG; goto out; } if (__be16_to_cpu(tlv.tlv_type) != ISOCHRON_TLV_MANAGEMENT) { fprintf(stderr, "Failed to update MID %s: unexpected TLV type %d in response\n", mid_to_string(mid), __be16_to_cpu(tlv.tlv_type)); rc = isochron_drain_sk(sock, tlv_length); if (rc) goto out; isochron_print_mid_error(sock, mid); rc = -EBADMSG; goto out; } if (__be16_to_cpu(tlv.management_id) != mid) { fprintf(stderr, "Failed to update MID %s: response for unexpected MID %s\n", mid_to_string(mid), mid_to_string(__be16_to_cpu(tlv.management_id))); rc = isochron_drain_sk(sock, tlv_length); if (rc) goto out; isochron_print_mid_error(sock, mid); rc = -EBADMSG; goto out; } rc = sk_recv(sock, tmp_buf, data_len, 0); if (rc) goto out; if (memcmp(tmp_buf, data, data_len)) { fprintf(stderr, "Failed to update MID %s: unexpected reply contents\n", mid_to_string(mid)); isochron_print_mid_error(sock, mid); rc = -EBADMSG; goto out; } out: free(tmp_buf); return rc; } int isochron_update_packet_count(struct sk *sock, long count) { struct isochron_packet_count packet_count = { .count = __cpu_to_be64(count), }; return isochron_update_mid(sock, ISOCHRON_MID_PACKET_COUNT, &packet_count, sizeof(packet_count)); } int isochron_update_packet_size(struct sk *sock, int size) { struct isochron_packet_size p = { .size = __cpu_to_be32(size), }; return isochron_update_mid(sock, ISOCHRON_MID_PACKET_SIZE, &p, sizeof(p)); } int isochron_update_destination_mac(struct sk *sock, unsigned char *addr) { struct isochron_mac_addr mac = {}; ether_addr_copy(mac.addr, addr); return isochron_update_mid(sock, ISOCHRON_MID_DESTINATION_MAC, &mac, sizeof(mac)); } int isochron_update_source_mac(struct sk *sock, unsigned char *addr) { struct isochron_mac_addr mac = {}; ether_addr_copy(mac.addr, addr); return isochron_update_mid(sock, ISOCHRON_MID_SOURCE_MAC, &mac, sizeof(mac)); } int isochron_update_node_role(struct sk *sock, enum isochron_role role) { struct isochron_node_role r = { .role = __cpu_to_be32(role), }; return isochron_update_mid(sock, ISOCHRON_MID_NODE_ROLE, &r, sizeof(r)); } int isochron_update_if_name(struct sk *sock, const char if_name[IFNAMSIZ]) { struct isochron_if_name ifn = {}; int rc; rc = if_name_copy(ifn.name, if_name); if (rc) { fprintf(stderr, "Truncation while copying string\n"); return rc; } return isochron_update_mid(sock, ISOCHRON_MID_IF_NAME, &ifn, sizeof(ifn)); } int isochron_update_priority(struct sk *sock, int priority) { struct isochron_priority p = { .priority = __cpu_to_be32(priority), }; return isochron_update_mid(sock, ISOCHRON_MID_PRIORITY, &p, sizeof(p)); } int isochron_update_stats_port(struct sk *sock, __u16 port) { struct isochron_port p = { .port = __cpu_to_be16(port), }; return isochron_update_mid(sock, ISOCHRON_MID_STATS_PORT, &p, sizeof(p)); } int isochron_update_base_time(struct sk *sock, __u64 base_time) { struct isochron_time t = { .time = __cpu_to_be64(base_time), }; return isochron_update_mid(sock, ISOCHRON_MID_BASE_TIME, &t, sizeof(t)); } int isochron_update_advance_time(struct sk *sock, __u64 advance_time) { struct isochron_time t = { .time = __cpu_to_be64(advance_time), }; return isochron_update_mid(sock, ISOCHRON_MID_ADVANCE_TIME, &t, sizeof(t)); } int isochron_update_shift_time(struct sk *sock, __u64 shift_time) { struct isochron_time t = { .time = __cpu_to_be64(shift_time), }; return isochron_update_mid(sock, ISOCHRON_MID_SHIFT_TIME, &t, sizeof(t)); } int isochron_update_cycle_time(struct sk *sock, __u64 cycle_time) { struct isochron_time t = { .time = __cpu_to_be64(cycle_time), }; return isochron_update_mid(sock, ISOCHRON_MID_CYCLE_TIME, &t, sizeof(t)); } int isochron_update_window_size(struct sk *sock, __u64 window_time) { struct isochron_time t = { .time = __cpu_to_be64(window_time), }; return isochron_update_mid(sock, ISOCHRON_MID_WINDOW_SIZE, &t, sizeof(t)); } int isochron_update_domain_number(struct sk *sock, int domain_number) { struct isochron_domain_number d = { .domain_number = domain_number, }; return isochron_update_mid(sock, ISOCHRON_MID_DOMAIN_NUMBER, &d, sizeof(d)); } int isochron_update_transport_specific(struct sk *sock, int transport_specific) { struct isochron_transport_specific t = { .transport_specific = transport_specific, }; return isochron_update_mid(sock, ISOCHRON_MID_TRANSPORT_SPECIFIC, &t, sizeof(t)); } int isochron_update_uds(struct sk *sock, const char uds_remote[UNIX_PATH_MAX]) { struct isochron_uds u = {}; int rc; rc = uds_copy(u.name, uds_remote); if (rc) { fprintf(stderr, "Truncation while copying string\n"); return rc; } return isochron_update_mid(sock, ISOCHRON_MID_UDS, &u, sizeof(u)); } int isochron_update_num_readings(struct sk *sock, int num_readings) { struct isochron_num_readings n = { .num_readings = __cpu_to_be32(num_readings), }; return isochron_update_mid(sock, ISOCHRON_MID_NUM_READINGS, &n, sizeof(n)); } int isochron_update_sysmon_enabled(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_SYSMON_ENABLED, &f, sizeof(f)); } int isochron_update_ptpmon_enabled(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_PTPMON_ENABLED, &f, sizeof(f)); } int isochron_update_ts_enabled(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_TS_ENABLED, &f, sizeof(f)); } int isochron_update_vid(struct sk *sock, __u16 vid) { struct isochron_vid v = { .vid = __cpu_to_be16(vid), }; return isochron_update_mid(sock, ISOCHRON_MID_VID, &v, sizeof(v)); } int isochron_update_ethertype(struct sk *sock, __u16 ethertype) { struct isochron_ethertype e = { .ethertype = __cpu_to_be16(ethertype), }; return isochron_update_mid(sock, ISOCHRON_MID_ETHERTYPE, &e, sizeof(e)); } int isochron_update_quiet_enabled(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_QUIET_ENABLED, &f, sizeof(f)); } int isochron_update_taprio_enabled(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_TAPRIO_ENABLED, &f, sizeof(f)); } int isochron_update_txtime_enabled(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_TXTIME_ENABLED, &f, sizeof(f)); } int isochron_update_deadline_enabled(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_DEADLINE_ENABLED, &f, sizeof(f)); } int isochron_update_utc_offset(struct sk *sock, int offset) { struct isochron_utc_offset u = { .offset = __cpu_to_be16(offset), }; return isochron_update_mid(sock, ISOCHRON_MID_UTC_OFFSET, &u, sizeof(u)); } int isochron_update_ip_destination(struct sk *sock, struct ip_address *addr) { struct isochron_ip_address i; int rc; i.family = __cpu_to_be32(addr->family); memcpy(i.addr, &addr->addr6, 16); rc = if_name_copy(i.bound_if_name, addr->bound_if_name); if (rc) { fprintf(stderr, "Truncation while copying string\n"); return rc; } return isochron_update_mid(sock, ISOCHRON_MID_IP_DESTINATION, &i, sizeof(i)); } int isochron_update_l2_enabled(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_L2_ENABLED, &f, sizeof(f)); } int isochron_update_l4_enabled(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_L4_ENABLED, &f, sizeof(f)); } int isochron_update_data_port(struct sk *sock, __u16 port) { struct isochron_port p = { .port = __cpu_to_be16(port), }; return isochron_update_mid(sock, ISOCHRON_MID_DATA_PORT, &p, sizeof(p)); } int isochron_update_sched_fifo(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_SCHED_FIFO_ENABLED, &f, sizeof(f)); } int isochron_update_sched_rr(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_SCHED_RR_ENABLED, &f, sizeof(f)); } int isochron_update_sched_priority(struct sk *sock, int priority) { struct isochron_sched_priority p = { .sched_priority = __cpu_to_be32(priority), }; return isochron_update_mid(sock, ISOCHRON_MID_SCHED_PRIORITY, &p, sizeof(p)); } int isochron_update_cpu_mask(struct sk *sock, unsigned long cpumask) { struct isochron_cpu_mask c = { .cpu_mask = __cpu_to_be64(cpumask), }; return isochron_update_mid(sock, ISOCHRON_MID_CPU_MASK, &c, sizeof(c)); } int isochron_update_test_state(struct sk *sock, enum test_state state) { struct isochron_test_state t = { .test_state = state, }; return isochron_update_mid(sock, ISOCHRON_MID_TEST_STATE, &t, sizeof(t)); } int isochron_update_sync_monitor_enabled(struct sk *sock, bool enabled) { struct isochron_feature_enabled f = { .enabled = enabled, }; return isochron_update_mid(sock, ISOCHRON_MID_SYNC_MONITOR_ENABLED, &f, sizeof(f)); } static void isochron_tlv_next(struct isochron_tlv **tlv, size_t *len) { size_t tlv_size_bytes; tlv_size_bytes = __be32_to_cpu((*tlv)->length_field) + sizeof(**tlv); *len += tlv_size_bytes; *tlv = (struct isochron_tlv *)((unsigned char *)tlv + tlv_size_bytes); } int isochron_mgmt_event(struct sk *sock, struct isochron_mgmt_handler *handler, void *priv) { struct isochron_management_message msg; const struct isochron_mgmt_ops *ops; enum isochron_management_id mid; struct isochron_error *err; unsigned char buf[BUFSIZ]; struct isochron_tlv *tlv; size_t parsed_len = 0; size_t len; int rc; rc = sk_recv(sock, &msg, sizeof(msg), 0); if (rc) { sk_err(sock, rc, "Failed to receive message header: %m\n"); return rc; } if (msg.version != ISOCHRON_MANAGEMENT_VERSION) { fprintf(stderr, "Expected management version %d, got %d\n", ISOCHRON_MANAGEMENT_VERSION, msg.version); return 0; } switch (msg.action) { case ISOCHRON_GET: case ISOCHRON_SET: case ISOCHRON_GET_ERROR: break; default: fprintf(stderr, "Unexpected action %d\n", msg.action); return 0; } len = __be32_to_cpu(msg.payload_length); if (len >= BUFSIZ) { fprintf(stderr, "GET message too large at %zd, max %d\n", len, BUFSIZ); return 0; } rc = sk_recv(sock, buf, len, 0); if (rc) { sk_err(sock, rc, "Failed to receive message body: %m\n"); return rc; } tlv = (struct isochron_tlv *)buf; while (parsed_len < (size_t)len) { if (__be16_to_cpu(tlv->tlv_type) != ISOCHRON_TLV_MANAGEMENT) goto next; mid = __be16_to_cpu(tlv->management_id); if (mid < 0 || mid >= __ISOCHRON_MID_MAX) { fprintf(stderr, "Unrecognized MID %d\n", mid); isochron_send_empty_tlv(sock, mid); goto next; } ops = &handler->ops[mid]; err = &handler->error_table[mid]; switch (msg.action) { case ISOCHRON_GET: isochron_mgmt_tlv_get(sock, priv, mid, ops, err); break; case ISOCHRON_SET: isochron_mgmt_tlv_set(sock, tlv, priv, mid, ops, err); break; case ISOCHRON_GET_ERROR: isochron_forward_mgmt_err(sock, mid, err); default: break; } next: isochron_tlv_next(&tlv, &parsed_len); } return 0; } int isochron_forward_log(struct sk *sock, struct isochron_log *log, size_t size, char *extack) { int rc; rc = isochron_send_tlv(sock, ISOCHRON_RESPONSE, ISOCHRON_MID_LOG, isochron_log_buf_tlv_size(log)); if (rc) return rc; isochron_log_xmit(log, sock); isochron_log_teardown(log); return isochron_log_init(log, size); } int isochron_forward_sysmon_offset(struct sk *sock, struct sysmon *sysmon, char *extack) { __s64 sysmon_offset, sysmon_delay; struct isochron_sysmon_offset so; __u64 sysmon_ts; int rc; rc = sysmon_get_offset(sysmon, &sysmon_offset, &sysmon_ts, &sysmon_delay); if (rc) { mgmt_extack(extack, "Failed to read sysmon offset: %m"); return rc; } so.offset = __cpu_to_be64(sysmon_offset); so.time = __cpu_to_be64(sysmon_ts); so.delay = __cpu_to_be64(sysmon_delay); rc = isochron_send_tlv(sock, ISOCHRON_RESPONSE, ISOCHRON_MID_SYSMON_OFFSET, sizeof(so)); if (rc) return rc; sk_send(sock, &so, sizeof(so)); return 0; } int isochron_forward_ptpmon_offset(struct sk *sock, struct ptpmon *ptpmon, char *extack) { struct isochron_ptpmon_offset po; struct current_ds current_ds; __s64 ptpmon_offset; int rc; rc = ptpmon_query_clock_mid(ptpmon, MID_CURRENT_DATA_SET, ¤t_ds, sizeof(current_ds)); if (rc) { mgmt_extack(extack, "Failed to read ptpmon offset: %m"); return rc; } ptpmon_offset = master_offset_from_current_ds(¤t_ds); po.offset = __cpu_to_be64(ptpmon_offset); rc = isochron_send_tlv(sock, ISOCHRON_RESPONSE, ISOCHRON_MID_PTPMON_OFFSET, sizeof(po)); if (rc) return rc; sk_send(sock, &po, sizeof(po)); return 0; } int isochron_forward_utc_offset(struct sk *sock, struct ptpmon *ptpmon, int *utc_offset, char *extack) { struct time_properties_ds time_properties_ds; struct isochron_utc_offset utc; int rc; rc = ptpmon_query_clock_mid(ptpmon, MID_TIME_PROPERTIES_DATA_SET, &time_properties_ds, sizeof(time_properties_ds)); if (rc) { mgmt_extack(extack, "Failed to read ptpmon UTC offset: %m"); return rc; } utc.offset = time_properties_ds.current_utc_offset; rc = isochron_send_tlv(sock, ISOCHRON_RESPONSE, ISOCHRON_MID_UTC_OFFSET, sizeof(utc)); if (rc) return 0; sk_send(sock, &utc, sizeof(utc)); *utc_offset = __be16_to_cpu(utc.offset); return 0; } int isochron_forward_port_state(struct sk *sock, struct ptpmon *ptpmon, const char *if_name, struct mnl_socket *rtnl, char *extack) { struct isochron_port_state state; enum port_state port_state; int rc; rc = ptpmon_query_port_state_by_name(ptpmon, if_name, rtnl, &port_state); if (rc) { mgmt_extack(extack, "Failed to read ptpmon port state: %m"); return rc; } state.state = port_state; rc = isochron_send_tlv(sock, ISOCHRON_RESPONSE, ISOCHRON_MID_PORT_STATE, sizeof(state)); if (rc) return rc; sk_send(sock, &state, sizeof(state)); return 0; } int isochron_forward_test_state(struct sk *sock, enum test_state state, char *extack) { struct isochron_test_state test_state = { .test_state = state, }; int rc; rc = isochron_send_tlv(sock, ISOCHRON_RESPONSE, ISOCHRON_MID_TEST_STATE, sizeof(test_state)); if (rc) return rc; sk_send(sock, &test_state, sizeof(test_state)); return 0; } int isochron_forward_port_link_state(struct sk *sock, const char *if_name, struct mnl_socket *rtnl, char *extack) { struct isochron_port_link_state s = { .link_state = PORT_LINK_STATE_UNKNOWN, }; bool running; int rc; rc = rtnl_query_link_state(rtnl, if_name, &running); if (rc) { mgmt_extack(extack, "Failed to query port %s link state", if_name); } else { s.link_state = running ? PORT_LINK_STATE_RUNNING : PORT_LINK_STATE_DOWN; } rc = isochron_send_tlv(sock, ISOCHRON_RESPONSE, ISOCHRON_MID_PORT_LINK_STATE, sizeof(s)); if (rc) return rc; sk_send(sock, &s, sizeof(s)); return 0; } int isochron_forward_gm_clock_identity(struct sk *sock, struct ptpmon *ptpmon, char *extack) { struct isochron_gm_clock_identity gm; struct parent_data_set parent_ds; int rc; rc = ptpmon_query_clock_mid(ptpmon, MID_PARENT_DATA_SET, &parent_ds, sizeof(parent_ds)); if (rc) { mgmt_extack(extack, "Failed to read ptpmon GM clockID: %m"); return rc; } memcpy(&gm.clock_identity, &parent_ds.grandmaster_identity, sizeof(gm.clock_identity)); rc = isochron_send_tlv(sock, ISOCHRON_RESPONSE, ISOCHRON_MID_GM_CLOCK_IDENTITY, sizeof(gm)); if (rc) return 0; sk_send(sock, &gm, sizeof(gm)); return 0; } int isochron_forward_current_clock_tai(struct sk *sock, char *extack) { struct isochron_time t = {}; struct timespec now_ts; __s64 now; int rc; clock_gettime(CLOCK_TAI, &now_ts); now = timespec_to_ns(&now_ts); t.time = __cpu_to_be64(now); rc = isochron_send_tlv(sock, ISOCHRON_RESPONSE, ISOCHRON_MID_CURRENT_CLOCK_TAI, sizeof(t)); if (rc) return rc; sk_send(sock, &t, sizeof(t)); return 0; } int isochron_collect_sync_stats(struct sk *sock, __s64 *sysmon_offset, __s64 *ptpmon_offset, int *utc_offset, enum port_state *port_state, struct clock_identity *gm_clkid) { struct isochron_gm_clock_identity gm; struct isochron_sysmon_offset sysmon; struct isochron_ptpmon_offset ptpmon; struct isochron_port_state state; struct isochron_utc_offset utc; int rc; rc = isochron_query_mid(sock, ISOCHRON_MID_SYSMON_OFFSET, &sysmon, sizeof(sysmon)); if (rc) { fprintf(stderr, "sysmon offset missing from mgmt reply\n"); return rc; } rc = isochron_query_mid(sock, ISOCHRON_MID_PTPMON_OFFSET, &ptpmon, sizeof(ptpmon)); if (rc) { fprintf(stderr, "ptpmon offset missing from mgmt reply\n"); return rc; } rc = isochron_query_mid(sock, ISOCHRON_MID_UTC_OFFSET, &utc, sizeof(utc)); if (rc) { fprintf(stderr, "UTC offset missing from mgmt reply\n"); return rc; } rc = isochron_query_mid(sock, ISOCHRON_MID_PORT_STATE, &state, sizeof(state)); if (rc) { fprintf(stderr, "port state missing from mgmt reply\n"); return rc; } rc = isochron_query_mid(sock, ISOCHRON_MID_GM_CLOCK_IDENTITY, &gm, sizeof(gm)); if (rc) { fprintf(stderr, "GM clock identity missing from mgmt reply: %d\n", rc); return rc; } *sysmon_offset = __be64_to_cpu(sysmon.offset); *ptpmon_offset = __be64_to_cpu(ptpmon.offset); *utc_offset = __be16_to_cpu(utc.offset); *port_state = state.state; memcpy(gm_clkid, &gm.clock_identity, sizeof(*gm_clkid)); return 0; } int isochron_query_current_clock_tai(struct sk *sock, __s64 *clock_tai) { struct isochron_time t = {}; int rc; rc = isochron_query_mid(sock, ISOCHRON_MID_CURRENT_CLOCK_TAI, &t, sizeof(t)); if (rc) { fprintf(stderr, "Current CLOCK_TAI missing from mgmt reply\n"); return rc; } *clock_tai = __be64_to_cpu(t.time); return 0; } int isochron_query_oper_base_time(struct sk *sock, __s64 *base_time) { struct isochron_time t = {}; int rc; rc = isochron_query_mid(sock, ISOCHRON_MID_OPER_BASE_TIME, &t, sizeof(t)); if (rc) { fprintf(stderr, "OPER_BASE_TIME missing from mgmt reply\n"); return rc; } *base_time = __be64_to_cpu(t.time); return 0; } struct isochron_mgmt_handler * isochron_mgmt_handler_create(const struct isochron_mgmt_ops *ops) { struct isochron_mgmt_handler *handler; struct isochron_error *error_table; handler = calloc(1, sizeof(*handler)); if (!handler) return NULL; error_table = calloc(__ISOCHRON_MID_MAX, sizeof(*error_table)); if (!error_table) { free(handler); return NULL; } handler->ops = ops; handler->error_table = error_table; return handler; } void isochron_mgmt_handler_destroy(struct isochron_mgmt_handler *handler) { free(handler->error_table); free(handler); } isochron-0.9/src/management.h000066400000000000000000000255321431034026500162670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright 2021 NXP */ #ifndef _ISOCHRON_MANAGEMENT_H #define _ISOCHRON_MANAGEMENT_H #include #include #include #include #include "log.h" #include "ptpmon.h" #include "sk.h" #include "sysmon.h" #define ISOCHRON_STATS_PORT 5000 /* TCP */ #define ISOCHRON_DATA_PORT 6000 /* UDP */ #define ISOCHRON_MANAGEMENT_VERSION 2 #define ISOCHRON_EXTACK_SIZE 1020 /* Don't forget to update mid_to_string() when adding new members */ enum isochron_management_id { ISOCHRON_MID_LOG, ISOCHRON_MID_SYSMON_OFFSET, ISOCHRON_MID_PTPMON_OFFSET, ISOCHRON_MID_UTC_OFFSET, ISOCHRON_MID_PORT_STATE, ISOCHRON_MID_GM_CLOCK_IDENTITY, ISOCHRON_MID_PACKET_COUNT, ISOCHRON_MID_DESTINATION_MAC, ISOCHRON_MID_SOURCE_MAC, ISOCHRON_MID_NODE_ROLE, ISOCHRON_MID_PACKET_SIZE, ISOCHRON_MID_IF_NAME, ISOCHRON_MID_PRIORITY, ISOCHRON_MID_STATS_PORT, ISOCHRON_MID_BASE_TIME, ISOCHRON_MID_ADVANCE_TIME, ISOCHRON_MID_SHIFT_TIME, ISOCHRON_MID_CYCLE_TIME, ISOCHRON_MID_WINDOW_SIZE, ISOCHRON_MID_SYSMON_ENABLED, ISOCHRON_MID_PTPMON_ENABLED, ISOCHRON_MID_UDS, ISOCHRON_MID_DOMAIN_NUMBER, ISOCHRON_MID_TRANSPORT_SPECIFIC, ISOCHRON_MID_NUM_READINGS, ISOCHRON_MID_TS_ENABLED, ISOCHRON_MID_VID, ISOCHRON_MID_ETHERTYPE, ISOCHRON_MID_QUIET_ENABLED, ISOCHRON_MID_TAPRIO_ENABLED, ISOCHRON_MID_TXTIME_ENABLED, ISOCHRON_MID_DEADLINE_ENABLED, ISOCHRON_MID_IP_DESTINATION, ISOCHRON_MID_L2_ENABLED, ISOCHRON_MID_L4_ENABLED, ISOCHRON_MID_DATA_PORT, ISOCHRON_MID_SCHED_FIFO_ENABLED, ISOCHRON_MID_SCHED_RR_ENABLED, ISOCHRON_MID_SCHED_PRIORITY, ISOCHRON_MID_CPU_MASK, ISOCHRON_MID_TEST_STATE, ISOCHRON_MID_SYNC_MONITOR_ENABLED, ISOCHRON_MID_PORT_LINK_STATE, ISOCHRON_MID_CURRENT_CLOCK_TAI, ISOCHRON_MID_OPER_BASE_TIME, __ISOCHRON_MID_MAX, }; enum isochron_management_action { ISOCHRON_GET = 0, ISOCHRON_SET, ISOCHRON_RESPONSE, ISOCHRON_GET_ERROR, }; enum isochron_role { ISOCHRON_ROLE_SEND, ISOCHRON_ROLE_RCV, }; enum test_state { ISOCHRON_TEST_STATE_IDLE, ISOCHRON_TEST_STATE_RUNNING, ISOCHRON_TEST_STATE_FAILED, }; enum port_link_state { PORT_LINK_STATE_UNKNOWN, PORT_LINK_STATE_DOWN, PORT_LINK_STATE_RUNNING, }; enum isochron_tlv_type { ISOCHRON_TLV_MANAGEMENT = 0, }; struct isochron_management_message { __u8 version; __u8 action; __be16 reserved; __be32 payload_length; /* TLVs follow */ } __attribute((packed)); struct isochron_tlv { __be16 tlv_type; __be16 management_id; __be32 length_field; } __attribute((packed)); /* ISOCHRON_MID_SYSMON_OFFSET */ struct isochron_sysmon_offset { __be64 offset; __be64 time; __be64 delay; } __attribute((packed)); /* ISOCHRON_MID_PTPMON_OFFSET */ struct isochron_ptpmon_offset { __be64 offset; } __attribute((packed)); /* ISOCHRON_MID_UTC_OFFSET */ struct isochron_utc_offset { __be16 offset; } __attribute((packed)); /* ISOCHRON_MID_PORT_STATE */ struct isochron_port_state { __u8 state; } __attribute((packed)); /* ISOCHRON_MID_GM_CLOCK_IDENTITY */ struct isochron_gm_clock_identity { struct clock_identity clock_identity; } __attribute((packed)); /* ISOCHRON_MID_PACKET_COUNT */ struct isochron_packet_count { __be64 count; }; /* ISOCHRON_MID_DESTINATION_MAC */ /* ISOCHRON_MID_SOURCE_MAC */ struct isochron_mac_addr { unsigned char addr[ETH_ALEN]; __u8 reserved[2]; } __attribute((packed)); /* ISOCHRON_MID_NODE_ROLE */ struct isochron_node_role { __be32 role; } __attribute((packed)); /* ISOCHRON_MID_PACKET_SIZE */ struct isochron_packet_size { __be32 size; } __attribute((packed)); /* ISOCHRON_MID_IF_NAME */ struct isochron_if_name { char name[IFNAMSIZ]; } __attribute((packed)); /* ISOCHRON_MID_PRIORITY */ struct isochron_priority { __be32 priority; } __attribute((packed)); /* ISOCHRON_MID_STATS_PORT */ /* ISOCHRON_MID_DATA_PORT */ struct isochron_port { __be16 port; } __attribute((packed)); /* ISOCHRON_MID_BASE_TIME */ /* ISOCHRON_MID_ADVANCE_TIME */ /* ISOCHRON_MID_SHIFT_TIME */ /* ISOCHRON_MID_CYCLE_TIME */ /* ISOCHRON_MID_WINDOW_SIZE */ /* ISOCHRON_MID_CURRENT_CLOCK_TAI */ /* ISOCHRON_MID_OPER_BASE_TIME */ struct isochron_time { __be64 time; } __attribute((packed)); /* ISOCHRON_MID_SYSMON_ENABLED */ /* ISOCHRON_MID_PTPMON_ENABLED */ /* ISOCHRON_MID_TS_ENABLED */ /* ISOCHRON_MID_QUIET_ENABLED */ /* ISOCHRON_MID_TAPRIO_ENABLED */ /* ISOCHRON_MID_TXTIME_ENABLED */ /* ISOCHRON_MID_DEADLINE_ENABLED */ /* ISOCHRON_MID_L2_ENABLED */ /* ISOCHRON_MID_L4_ENABLED */ /* ISOCHRON_MID_SCHED_FIFO_ENABLED */ /* ISOCHRON_MID_SCHED_RR_ENABLED */ struct isochron_feature_enabled { __u8 enabled; __u8 reserved[3]; } __attribute((packed)); /* ISOCHRON_MID_UDS */ struct isochron_uds { char name[UNIX_PATH_MAX]; } __attribute((packed)); /* ISOCHRON_MID_DOMAIN_NUMBER */ struct isochron_domain_number { __u8 domain_number; __u8 reserved[3]; } __attribute((packed)); /* ISOCHRON_MID_TRANSPORT_SPECIFIC */ struct isochron_transport_specific { __u8 transport_specific; __u8 reserved[3]; } __attribute((packed)); /* ISOCHRON_MID_NUM_READINGS */ struct isochron_num_readings { __be32 num_readings; } __attribute((packed)); /* ISOCHRON_MID_VID */ struct isochron_vid { __be16 vid; __u8 reserved[2]; } __attribute((packed)); /* ISOCHRON_MID_ETHERTYPE */ struct isochron_ethertype { __be16 ethertype; __u8 reserved[2]; } __attribute((packed)); /* ISOCHRON_MID_IP_DESTINATION */ struct isochron_ip_address { __be32 family; __u8 addr[16]; char bound_if_name[IFNAMSIZ]; } __attribute((packed)); /* ISOCHRON_MID_SCHED_PRIORITY */ struct isochron_sched_priority { __be32 sched_priority; } __attribute((packed)); /* ISOCHRON_MID_CPU_MASK */ struct isochron_cpu_mask { __be64 cpu_mask; } __attribute((packed)); /* ISOCHRON_MID_TEST_STATE */ struct isochron_test_state { __u8 test_state; __u8 reserved[3]; } __attribute((packed)); /* ISOCHRON_MID_PORT_LINK_STATE */ struct isochron_port_link_state { __u8 link_state; __u8 reserved[3]; } __attribute((packed)); const char *mid_to_string(enum isochron_management_id mid); int isochron_send_tlv(struct sk *sock, enum isochron_management_action action, enum isochron_management_id mid, size_t size); int isochron_collect_rcv_log(struct sk *sock, struct isochron_log *rcv_log); int isochron_query_mid(struct sk *sock, enum isochron_management_id mid, void *data, size_t data_len); int isochron_update_packet_count(struct sk *sock, long count); int isochron_update_packet_size(struct sk *sock, int size); int isochron_update_destination_mac(struct sk *sock, unsigned char *addr); int isochron_update_source_mac(struct sk *sock, unsigned char *addr); int isochron_update_node_role(struct sk *sock, enum isochron_role role); int isochron_update_if_name(struct sk *sock, const char if_name[IFNAMSIZ]); int isochron_update_priority(struct sk *sock, int priority); int isochron_update_stats_port(struct sk *sock, __u16 port); int isochron_update_base_time(struct sk *sock, __u64 base_time); int isochron_update_advance_time(struct sk *sock, __u64 advance_time); int isochron_update_shift_time(struct sk *sock, __u64 shift_time); int isochron_update_cycle_time(struct sk *sock, __u64 cycle_time); int isochron_update_window_size(struct sk *sock, __u64 window_time); int isochron_update_domain_number(struct sk *sock, int domain_number); int isochron_update_transport_specific(struct sk *sock, int transport_specific); int isochron_update_uds(struct sk *sock, const char uds_remote[UNIX_PATH_MAX]); int isochron_update_num_readings(struct sk *sock, int num_readings); int isochron_update_sysmon_enabled(struct sk *sock, bool enabled); int isochron_update_ptpmon_enabled(struct sk *sock, bool enabled); int isochron_update_sync_monitor_enabled(struct sk *sock, bool enabled); int isochron_update_ts_enabled(struct sk *sock, bool enabled); int isochron_update_vid(struct sk *sock, __u16 vid); int isochron_update_ethertype(struct sk *sock, __u16 etype); int isochron_update_quiet_enabled(struct sk *sock, bool enabled); int isochron_update_taprio_enabled(struct sk *sock, bool enabled); int isochron_update_txtime_enabled(struct sk *sock, bool enabled); int isochron_update_deadline_enabled(struct sk *sock, bool enabled); int isochron_update_utc_offset(struct sk *sock, int offset); int isochron_update_ip_destination(struct sk *sock, struct ip_address *addr); int isochron_update_l2_enabled(struct sk *sock, bool enabled); int isochron_update_l4_enabled(struct sk *sock, bool enabled); int isochron_update_data_port(struct sk *sock, __u16 port); int isochron_update_sched_fifo(struct sk *sock, bool enabled); int isochron_update_sched_rr(struct sk *sock, bool enabled); int isochron_update_sched_priority(struct sk *sock, int priority); int isochron_update_cpu_mask(struct sk *sock, unsigned long cpumask); int isochron_update_test_state(struct sk *sock, enum test_state state); static inline void *isochron_tlv_data(struct isochron_tlv *tlv) { return tlv + 1; } typedef int isochron_tlv_cb_t(void *priv, struct isochron_tlv *tlv); typedef int isochron_mgmt_tlv_set_cb_t(void *priv, void *ptr); int isochron_forward_log(struct sk *sock, struct isochron_log *log, size_t size, char *extack); int isochron_forward_sysmon_offset(struct sk *sock, struct sysmon *sysmon, char *extack); int isochron_forward_ptpmon_offset(struct sk *sock, struct ptpmon *ptpmon, char *extack); int isochron_forward_utc_offset(struct sk *sock, struct ptpmon *ptpmon, int *utc_offset, char *extack); int isochron_forward_port_state(struct sk *sock, struct ptpmon *ptpmon, const char *if_name, struct mnl_socket *rtnl, char *extack); int isochron_forward_test_state(struct sk *sock, enum test_state state, char *extack); int isochron_forward_port_link_state(struct sk *sock, const char *if_name, struct mnl_socket *rtnl, char *extack); int isochron_forward_gm_clock_identity(struct sk *sock, struct ptpmon *ptpmon, char *extack); int isochron_forward_current_clock_tai(struct sk *sock, char *extack); int isochron_collect_sync_stats(struct sk *sock, __s64 *sysmon_offset, __s64 *ptpmon_offset, int *utc_offset, enum port_state *port_state, struct clock_identity *gm_clkid); int isochron_query_current_clock_tai(struct sk *sock, __s64 *clock_tai); int isochron_query_oper_base_time(struct sk *sock, __s64 *base_time); struct isochron_error { int rc; char extack[ISOCHRON_EXTACK_SIZE]; }; void mgmt_extack(char *extack, const char *fmt, ...); struct isochron_mgmt_ops { int (*get)(void *priv, char *extack); int (*set)(void *priv, void *ptr, char *extack); size_t struct_size; }; struct isochron_mgmt_handler; struct isochron_mgmt_handler * isochron_mgmt_handler_create(const struct isochron_mgmt_ops *ops); void isochron_mgmt_handler_destroy(struct isochron_mgmt_handler *handler); int isochron_mgmt_event(struct sk *sock, struct isochron_mgmt_handler *handler, void *priv); int isochron_query_mid_error(struct sk *sock, enum isochron_management_id mid, struct isochron_error *err); #endif isochron-0.9/src/missing.h000066400000000000000000000036101431034026500156150ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2011 Richard Cochran * Copyright 2021 NXP */ /* This file contains code snippets from: * - The Linux kernel * - The linuxptp project */ #ifndef _MISSING_H #define _MISSING_H #ifndef PTP_MAX_SAMPLES #define PTP_MAX_SAMPLES 25 /* Maximum allowed offset measurement samples. */ #endif /* PTP_MAX_SAMPLES */ #ifndef PTP_SYS_OFFSET #define PTP_SYS_OFFSET _IOW(PTP_CLK_MAGIC, 5, struct ptp_sys_offset) struct ptp_sys_offset { unsigned int n_samples; /* Desired number of measurements. */ unsigned int rsv[3]; /* Reserved for future use. */ /* * Array of interleaved system/phc time stamps. The kernel * will provide 2*n_samples + 1 time stamps, with the last * one as a system time stamp. */ struct ptp_clock_time ts[2 * PTP_MAX_SAMPLES + 1]; }; #endif /* PTP_SYS_OFFSET */ #ifndef PTP_SYS_OFFSET_PRECISE #define PTP_SYS_OFFSET_PRECISE \ _IOWR(PTP_CLK_MAGIC, 8, struct ptp_sys_offset_precise) struct ptp_sys_offset_precise { struct ptp_clock_time device; struct ptp_clock_time sys_realtime; struct ptp_clock_time sys_monoraw; unsigned int rsv[4]; /* Reserved for future use. */ }; #endif /* PTP_SYS_OFFSET_PRECISE */ #ifndef PTP_SYS_OFFSET_EXTENDED #define PTP_SYS_OFFSET_EXTENDED \ _IOWR(PTP_CLK_MAGIC, 9, struct ptp_sys_offset_extended) struct ptp_sys_offset_extended { unsigned int n_samples; /* Desired number of measurements. */ unsigned int rsv[3]; /* Reserved for future use. */ /* * Array of [system, phc, system] time stamps. The kernel will provide * 3*n_samples time stamps. */ struct ptp_clock_time ts[PTP_MAX_SAMPLES][3]; }; #endif /* PTP_SYS_OFFSET_EXTENDED */ #define CLOCKFD 3 #define FD_TO_CLOCKID(fd) ((clockid_t) ((((unsigned int) ~fd) << 3) | CLOCKFD)) #define CLOCKID_TO_FD(clk) ((unsigned int) ~((clk) >> 3)) #ifndef CLOCK_INVALID #define CLOCK_INVALID -1 #endif #endif isochron-0.9/src/orchestrate.c000066400000000000000000001016231431034026500164650ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright 2022 NXP */ #include #include #include #include #include #include #include #include #include #include "argparser.h" #include "common.h" #include "isochron.h" #include "management.h" #include "send.h" #include "sk.h" #include "syncmon.h" struct isochron_orch_node; struct isochron_orch_node { enum isochron_role role; LIST_ENTRY(isochron_orch_node) list; char name[BUFSIZ]; struct ip_address addr; unsigned long port; struct sk *mgmt_sock; long sync_threshold; bool collect_sync_stats; union { /* ISOCHRON_ROLE_SEND */ struct { struct isochron_send *send; enum test_state test_state; char exec[BUFSIZ]; __s64 oper_base_time; size_t num_rtt_measurements; __s64 rtt; __s64 max_rtt; }; /* ISOCHRON_ROLE_RCV */ struct { struct isochron_orch_node *sender; }; }; }; struct isochron_orch { LIST_HEAD(nodes_head, isochron_orch_node) nodes; char input_filename[PATH_MAX]; struct syncmon *syncmon; }; static void isochron_node_rtt_init(struct isochron_orch_node *node) { node->max_rtt = 0; node->num_rtt_measurements = 0; } static void isochron_node_rtt_before(struct isochron_orch_node *node) { struct timespec now_ts; clock_gettime(CLOCK_MONOTONIC, &now_ts); node->rtt = -timespec_to_ns(&now_ts); } static void isochron_node_rtt_after(struct isochron_orch_node *node) { struct timespec now_ts; clock_gettime(CLOCK_MONOTONIC, &now_ts); node->rtt += timespec_to_ns(&now_ts); node->num_rtt_measurements++; if (node->max_rtt < node->rtt) node->max_rtt = node->rtt; } static void isochron_node_rtt_finalize(struct isochron_orch_node *node) { printf("Max TCP round trip time to node %s over %zu measurements is %lld ns\n", node->name, node->num_rtt_measurements, node->max_rtt); } static int prog_query_test_state(struct isochron_orch_node *node, enum test_state *state) { struct isochron_test_state t; int rc; rc = isochron_query_mid(node->mgmt_sock, ISOCHRON_MID_TEST_STATE, &t, sizeof(t)); if (rc) { fprintf(stderr, "Test state missing from node %s reply\n", node->name); return rc; } *state = t.test_state; return 0; } static bool prog_all_senders_stopped(void *priv) { struct isochron_orch *prog = priv; struct isochron_orch_node *node; int rc; LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; if (node->test_state != ISOCHRON_TEST_STATE_RUNNING) continue; rc = prog_query_test_state(node, &node->test_state); if (rc) return false; if (node->test_state == ISOCHRON_TEST_STATE_RUNNING) return false; } return true; } static struct isochron_orch_node * prog_find_synchronized_sender(struct isochron_orch *prog) { struct isochron_orch_node *node; LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; if (node->collect_sync_stats) return node; } return NULL; } static int prog_calculate_oper_base_times(struct isochron_orch *prog) { struct isochron_orch_node *node, *ref, *tmp; struct isochron_orch_node **sorted_senders; struct isochron_send *send; size_t num_senders = 0; size_t i, j, k = 0; __s64 now; int rc; ref = prog_find_synchronized_sender(prog); if (!ref) { /* All senders are unsynchronized, use their base time as-is */ LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; node->oper_base_time = node->send->base_time; } return 0; } LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; num_senders++; } sorted_senders = calloc(num_senders, sizeof(*sorted_senders)); if (!sorted_senders) { rc = -ENOMEM; goto out; } LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; sorted_senders[k] = node; k++; } /* Sort the sender list by their original base times */ for (i = 0; i < num_senders; i++) { for (j = i + 1; j < num_senders; j++) { if (sorted_senders[i]->send->base_time > sorted_senders[j]->send->base_time) { tmp = sorted_senders[i]; sorted_senders[i] = sorted_senders[j]; sorted_senders[j] = tmp; } } } /* Since the nodes should be synchronized by now, it's enough to * pick the current CLOCK_TAI of only one "reference" sender */ rc = isochron_query_current_clock_tai(ref->mgmt_sock, &now); if (rc) goto out_free_sorted_senders; /* The retrieved CLOCK_TAI value was taken half an RTT ago, * update it with one full RTT to be on the safe side. */ now += ref->max_rtt; LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; /* Adjust for the time it takes to update * ISOCHRON_MID_BASE_TIME and ISOCHRON_MID_TEST_STATE * for all senders */ now += 2 * node->max_rtt; /* Every node's PHC time is within +/- a margin of its PTP * master, and its CLOCK_TAI is within +/- a margin of its PHC * time. Furthermore, we don't really know the offset to the * GM. So assume the worst case - a PTP linear topology - and * account for twice the sync threshold for every node. */ if (node->send->sync_threshold) now += 2 * node->send->sync_threshold; else now += 2 * ref->send->sync_threshold; } now += TIME_MARGIN; /* Finally advance each sender's base time into the operational value * in the common future, according to its own cycle time. */ LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; send = node->send; /* Each sender takes its sweet time to ensure the first wakeup * time is at least TIME_MARGIN into the future. We need to * overcome that extra time and make sure the time we're * passing it into the future by even more than that, to * prevent the sender from auto-advancing it and make it use * what we provided as-is. */ send->session_start = now; node->oper_base_time = isochron_send_first_base_time(send); } /* Produce equivalently sorted numbers by advancing the operational * base times again such that the sort order by original base times * (given by their position in the array) is preserved. * Every node's oper base time needs to be higher than its previous * element from the sorted list. */ for (i = 1; i < num_senders; i++) { node = sorted_senders[i]; tmp = sorted_senders[i - 1]; node->oper_base_time = future_base_time(node->oper_base_time, node->send->cycle_time, tmp->oper_base_time); } out_free_sorted_senders: free(sorted_senders); out: return rc; } static int prog_update_sender_base_times(struct isochron_orch *prog) { struct isochron_orch_node *node; int rc; rc = prog_calculate_oper_base_times(prog); if (rc) return rc; LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; rc = isochron_update_base_time(node->mgmt_sock, node->oper_base_time); if (rc) { fprintf(stderr, "failed to update base time for node %s\n", node->name); return rc; } } return 0; } static void prog_print_sender_base_times(struct isochron_orch *prog) { char oper_base_time_buf[TIMESPEC_BUFSIZ]; char cycle_time_buf[TIMESPEC_BUFSIZ]; char base_time_buf[TIMESPEC_BUFSIZ]; struct isochron_orch_node *node; LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; ns_sprintf(oper_base_time_buf, node->oper_base_time); ns_sprintf(cycle_time_buf, node->send->cycle_time); ns_sprintf(base_time_buf, node->send->base_time); printf("Node %s: base time %s (operational %s), cycle time %s\n", node->name, base_time_buf, oper_base_time_buf, cycle_time_buf); } } static bool prog_validate_oper_base_time(struct isochron_orch_node *node) { char their_oper_base_time_buf[TIMESPEC_BUFSIZ]; char our_oper_base_time_buf[TIMESPEC_BUFSIZ]; __s64 oper_base_time; int rc; if (node->oper_base_time == node->send->base_time) return true; /* Node had its base time auto-advanced by us, check if it * actually used that for the test or if the margin was too low */ rc = isochron_query_oper_base_time(node->mgmt_sock, &oper_base_time); if (rc) { pr_err(rc, "Failed to read back node %s operational base time: %m\n", node->name); return false; } if (oper_base_time == node->oper_base_time) return true; ns_sprintf(our_oper_base_time_buf, node->oper_base_time); ns_sprintf(their_oper_base_time_buf, oper_base_time); printf("Node %s had to advance our operational base time %s to %s, repeating test\n", node->name, our_oper_base_time_buf, their_oper_base_time_buf); return false; } static bool prog_validate_test_exit_code(struct isochron_orch_node *node) { struct isochron_error err; int rc; if (node->test_state != ISOCHRON_TEST_STATE_FAILED) return true; rc = isochron_query_mid_error(node->mgmt_sock, ISOCHRON_MID_TEST_STATE, &err); if (rc) { pr_err(rc, "Failed to retrieve test exit code for node %s: %m\n", node->name); return false; } fprintf(stderr, "Node %s failed, repeating test: %s\n", node->name, err.extack); return false; } static bool prog_validate_test(struct isochron_orch *prog) { struct isochron_orch_node *node; LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; if (!prog_validate_oper_base_time(node)) return false; if (!prog_validate_test_exit_code(node)) return false; } return true; } static int prog_start_senders(struct isochron_orch *prog) { struct isochron_orch_node *node; int rc; rc = prog_update_sender_base_times(prog); if (rc) return rc; LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; rc = isochron_update_test_state(node->mgmt_sock, ISOCHRON_TEST_STATE_RUNNING); if (rc) { pr_err(rc, "Failed to start node %s: %m\n", node->name); return rc; } node->test_state = ISOCHRON_TEST_STATE_RUNNING; } prog_print_sender_base_times(prog); return 0; } static int prog_stop_senders(struct isochron_orch *prog) { struct isochron_orch_node *node; int rc; LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; rc = isochron_update_test_state(node->mgmt_sock, ISOCHRON_TEST_STATE_IDLE); if (rc) { pr_err(rc, "Failed to stop node %s: %m\n", node->name); return rc; } } return 0; } static struct syncmon_node * prog_add_syncmon_sender(struct syncmon *syncmon, struct isochron_orch_node *node) { struct isochron_send *send = node->send; if (node->collect_sync_stats) return syncmon_add_remote_sender(syncmon, node->name, node->mgmt_sock, send->iterations, send->cycle_time, send->sync_threshold); else return syncmon_add_remote_sender_no_sync(syncmon, node->name, node->mgmt_sock, send->iterations, send->cycle_time); } static struct syncmon_node * prog_add_syncmon_receiver(struct syncmon *syncmon, struct isochron_orch_node *node, struct syncmon_node *pair) { if (node->collect_sync_stats) return syncmon_add_remote_receiver(syncmon, node->name, node->mgmt_sock, pair, node->sync_threshold); else return syncmon_add_remote_receiver_no_sync(syncmon, node->name, node->mgmt_sock, pair); } static int prog_init_syncmon(struct isochron_orch *prog) { struct isochron_orch_node *node; struct syncmon *syncmon; struct syncmon_node *sn; syncmon = syncmon_create(); if (!syncmon) return -ENOMEM; LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_RCV) continue; sn = prog_add_syncmon_sender(syncmon, node->sender); if (!sn) { syncmon_destroy(syncmon); return -ENOMEM; } sn = prog_add_syncmon_receiver(syncmon, node, sn); if (!sn) { syncmon_destroy(syncmon); return -ENOMEM; } } prog->syncmon = syncmon; return 0; } static int prog_collect_logs(struct isochron_orch *prog) { struct isochron_orch_node *node, *sender; struct isochron_log send_log, rcv_log; struct isochron_send *send; int rc; LIST_FOREACH(node, &prog->nodes, list) { if (node->role != ISOCHRON_ROLE_RCV) continue; sender = node->sender; send = sender->send; printf("Collecting stats from %s\n", sender->name); rc = isochron_collect_rcv_log(sender->mgmt_sock, &send_log); if (rc) { pr_err(rc, "Failed to collect sender stats: %m\n"); return rc; } printf("Collecting stats from %s\n", node->name); rc = isochron_collect_rcv_log(node->mgmt_sock, &rcv_log); if (rc) { pr_err(rc, "Failed to collect receiver stats: %m\n"); isochron_log_teardown(&send_log); return rc; } rc = isochron_log_save(send->output_file, &send_log, &rcv_log, send->iterations, send->tx_len, send->omit_sync, send->do_ts, send->taprio, send->txtime, send->deadline, send->base_time, send->advance_time, send->shift_time, send->cycle_time, send->window_size); isochron_log_teardown(&send_log); isochron_log_teardown(&rcv_log); if (rc) { pr_err(rc, "Failed to save log: %m\n"); return rc; } } return 0; } static int prog_run_test(struct isochron_orch *prog) { bool sync_ok, test_valid; int rc; rc = prog_init_syncmon(prog); if (rc) return rc; do { /* Adapt sync check intervals to new realities */ syncmon_init(prog->syncmon); rc = syncmon_wait_until_ok(prog->syncmon); if (rc) { pr_err(rc, "Failed to check sync status: %m\n"); goto out; } rc = prog_start_senders(prog); if (rc) goto out; sync_ok = syncmon_monitor(prog->syncmon, prog_all_senders_stopped, prog); test_valid = prog_validate_test(prog); if (sync_ok && test_valid) { rc = prog_collect_logs(prog); if (rc) goto out; } rc = prog_stop_senders(prog); if (rc) goto out; if (signal_received) { rc = -EINTR; goto out; } } while (!sync_ok || !test_valid); out: syncmon_destroy(prog->syncmon); return rc; } static int prog_marshall_data_to_receiver(struct isochron_orch_node *node) { struct isochron_orch_node *sender = node->sender; return isochron_prepare_receiver(sender->send, node->mgmt_sock); } static int prog_marshall_data_to_sender(struct isochron_orch_node *node) { struct isochron_send *send = node->send; const char *if_name = send->if_name; struct sk *sock = node->mgmt_sock; int rc; isochron_node_rtt_init(node); isochron_node_rtt_before(node); rc = isochron_update_node_role(sock, ISOCHRON_ROLE_SEND); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update role for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_if_name(sock, if_name); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update interface name for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_destination_mac(sock, send->dest_mac); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update MAC DA for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_source_mac(sock, send->src_mac); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update MAC SA for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_priority(sock, send->priority); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update priority for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_stats_port(sock, send->stats_port); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update stats port for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_advance_time(sock, send->advance_time); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update advance time for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_shift_time(sock, send->shift_time); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update shift time for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_cycle_time(sock, send->cycle_time); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update cycle time for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_window_size(sock, send->window_size); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update window size for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_domain_number(sock, send->domain_number); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update domain number for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_transport_specific(sock, send->transport_specific); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update transport specific for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_uds(sock, send->uds_remote); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update UDS for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_num_readings(sock, send->num_readings); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update number of readings for node %s\n", node->name); return rc; } if (!send->omit_sync) { isochron_node_rtt_before(node); rc = isochron_update_sysmon_enabled(sock, true); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to enable sysmon for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_ptpmon_enabled(sock, true); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to enable ptpmon for node %s\n", node->name); return rc; } } isochron_node_rtt_before(node); rc = isochron_update_sync_monitor_enabled(sock, false); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to disable local sync monitoring for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_packet_size(sock, send->tx_len); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update packet size for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_ts_enabled(sock, send->do_ts); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to enable timestamping for node %s\n", node->name); return rc; } if (send->vid >= 0) { isochron_node_rtt_before(node); rc = isochron_update_vid(sock, send->vid); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update VLAN ID for node %s\n", node->name); return rc; } } if (send->etype >= 0) { isochron_node_rtt_before(node); rc = isochron_update_ethertype(sock, send->etype); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update EtherType for node %s\n", node->name); return rc; } } isochron_node_rtt_before(node); rc = isochron_update_quiet_enabled(sock, send->quiet); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to make node %s quiet\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_taprio_enabled(sock, send->taprio); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to enable taprio for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_txtime_enabled(sock, send->txtime); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to enable txtime for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_deadline_enabled(sock, send->deadline); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to enable deadline for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_packet_count(sock, send->iterations); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update packet count for node %s\n", node->name); return rc; } if (send->utc_tai_offset >= 0) { isochron_node_rtt_before(node); rc = isochron_update_utc_offset(sock, send->utc_tai_offset); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update UTC offset for node %s\n", node->name); return rc; } } if (send->ip_destination.family) { isochron_node_rtt_before(node); rc = isochron_update_ip_destination(sock, &send->ip_destination); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update IP destination for node %s\n", node->name); return rc; } } isochron_node_rtt_before(node); rc = isochron_update_l2_enabled(sock, send->l2); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to enable L2 transport for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_l4_enabled(sock, send->l4); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to enable L4 transport for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_data_port(sock, send->data_port); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to set data port for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_sched_fifo(sock, send->sched_fifo); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to enable SCHED_FIFO for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_sched_rr(sock, send->sched_rr); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to enable SCHED_RR for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_sched_priority(sock, send->sched_priority); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to update sched priority for node %s\n", node->name); return rc; } isochron_node_rtt_before(node); rc = isochron_update_cpu_mask(sock, send->cpumask); isochron_node_rtt_after(node); if (rc) { fprintf(stderr, "failed to set CPU mask for node %s\n", node->name); return rc; } isochron_node_rtt_finalize(node); return 0; } static int prog_query_receiver_mac_address(struct isochron_orch_node *node) { struct isochron_orch_node *sender = node->sender; struct isochron_mac_addr mac; char mac_buf[MACADDR_BUFSIZ]; int rc; if (!sender->send->l2) return 0; if (!is_zero_ether_addr(sender->send->dest_mac)) return 0; rc = isochron_query_mid(node->mgmt_sock, ISOCHRON_MID_DESTINATION_MAC, &mac, sizeof(mac)); if (rc) { fprintf(stderr, "Destination MAC missing from node %s reply\n", node->name); return rc; } ether_addr_copy(sender->send->dest_mac, mac.addr); mac_addr_sprintf(mac_buf, sender->send->dest_mac); printf("Destination MAC address of %s is %s\n", node->name, mac_buf); return 0; } static int prog_drain_receiver_log(struct isochron_orch_node *node) { struct isochron_log rcv_log; int rc; rc = isochron_collect_rcv_log(node->mgmt_sock, &rcv_log); if (rc) return rc; isochron_log_teardown(&rcv_log); return 0; } static int prog_marshall_data_from_receiver(struct isochron_orch_node *node) { int rc; rc = prog_query_receiver_mac_address(node); if (rc) return rc; rc = prog_drain_receiver_log(node); if (rc) return rc; return 0; } static int prog_marshall_data_from_nodes(struct isochron_orch *prog) { struct isochron_orch_node *node; int rc; LIST_FOREACH(node, &prog->nodes, list) { if (node->role == ISOCHRON_ROLE_RCV) { rc = prog_marshall_data_from_receiver(node); if (rc) return rc; } } return 0; } static int prog_marshall_data_to_nodes(struct isochron_orch *prog) { struct isochron_orch_node *node; int rc; LIST_FOREACH(node, &prog->nodes, list) { if (node->role == ISOCHRON_ROLE_SEND) { rc = prog_marshall_data_to_sender(node); if (rc) return rc; } else if (node->role == ISOCHRON_ROLE_RCV) { rc = prog_marshall_data_to_receiver(node); if (rc) return rc; } } return 0; } static int prog_open_node_connection(struct isochron_orch_node *node) { int rc; if (!node->port) node->port = ISOCHRON_STATS_PORT; if (node->addr.family != AF_INET && node->addr.family != AF_INET6) { fprintf(stderr, "Node %s missing a \"host\" property\n", node->name); return -EINVAL; } rc = sk_connect_tcp(&node->addr, node->port, &node->mgmt_sock); if (rc) { fprintf(stderr, "Failed to connect to node %s: %m\n", node->name); return rc; } printf("Connected to node %s\n", node->name); return 0; } static int prog_validate_node(struct isochron_orch_node *node) { if (!strlen(node->exec)) { fprintf(stderr, "exec line missing from node %s\n", node->name); return -EINVAL; } return 0; } static int prog_init_sender_nodes(struct isochron_orch *prog) { struct isochron_orch_node *node; int rc; LIST_FOREACH(node, &prog->nodes, list) { rc = prog_validate_node(node); if (rc) return rc; node->collect_sync_stats = !node->send->omit_sync; node->sync_threshold = node->send->sync_threshold; } return 0; } static int prog_init_receiver_nodes(struct isochron_orch *prog) { struct isochron_orch_node *node, *rcv_node, *tmp; struct isochron_send *send; int rc; LIST_FOREACH_SAFE(node, &prog->nodes, list, tmp) { if (node->role != ISOCHRON_ROLE_SEND) continue; send = node->send; if (!send->stats_srv.family) continue; rcv_node = calloc(sizeof(*rcv_node), 1); if (!rcv_node) return -ENOMEM; rc = snprintf(rcv_node->name, BUFSIZ, "%s's receiver", node->name); if (rc >= BUFSIZ) { fprintf(stderr, "Truncation while parsing node \"%s\"'s name\n", node->name); free(rcv_node); return -EINVAL; } rcv_node->addr = send->stats_srv; rcv_node->port = send->stats_port; rcv_node->collect_sync_stats = !send->omit_sync && !send->omit_remote_sync; rcv_node->sync_threshold = send->sync_threshold; rcv_node->role = ISOCHRON_ROLE_RCV; rcv_node->sender = node; LIST_INSERT_HEAD(&prog->nodes, rcv_node, list); } return 0; } static void prog_close_node_connection(struct isochron_orch_node *node) { if (!node->mgmt_sock) return; sk_close(node->mgmt_sock); } static int prog_open_node_connections(struct isochron_orch *prog) { struct isochron_orch_node *node; int rc; LIST_FOREACH(node, &prog->nodes, list) { rc = prog_open_node_connection(node); if (rc) return rc; } return 0; } static void prog_teardown(struct isochron_orch *prog) { struct isochron_orch_node *node, *tmp; LIST_FOREACH_SAFE(node, &prog->nodes, list, tmp) { prog_close_node_connection(node); if (node->role == ISOCHRON_ROLE_SEND) free(node->send); LIST_REMOVE(node, list); free(node); } } static int prog_exec_node_argparser(struct isochron_orch_node *node, int argc, char **argv, const char *value) { const struct isochron_prog *prog; int rc; rc = isochron_parse_args(&argc, &argv, &prog); if (rc) return rc; if (prog->main == isochron_send_main) { rc = isochron_send_parse_args(argc, argv, node->send); } else { fprintf(stderr, "Unsupported exec line \"%s\" for node %s\n", value, node->name); rc = -EINVAL; } return rc; } static int prog_parse_exec_line(struct isochron_orch_node *node, const char *value) { size_t i, len = strlen(value); bool curr_char_is_quote; int argc = 0, k = 0; char last_quote = 0; char prev = 0; char **argv; int rc; for (i = 0; i < len; i++) { curr_char_is_quote = (value[i] == '\'' || value[i] == '"'); if (last_quote && value[i] == last_quote) /* unquote */ last_quote = 0; else if (curr_char_is_quote) /* quote and remember starting character */ last_quote = value[i]; if (curr_char_is_quote || (!last_quote && isspace(value[i]))) node->exec[i] = 0; else node->exec[i] = value[i]; if (node->exec[i] && !prev) argc++; prev = node->exec[i]; } node->exec[len] = 0; argv = calloc(argc, sizeof(char *)); if (!argv) { fprintf(stderr, "low memory\n"); return -ENOMEM; } prev = 0; for (i = 0; i < len; i++) { if (node->exec[i] && !prev) argv[k++] = &node->exec[i]; prev = node->exec[i]; } rc = prog_exec_node_argparser(node, argc, argv, value); free(argv); return rc; } static int prog_parse_key_value(struct isochron_orch_node *node, const char *key, const char *value) { int rc; if (strcmp(key, "host") == 0) { rc = ip_addr_from_string(value, &node->addr); if (rc) { fprintf(stderr, "Invalid IP address \"%s\" for host %s\n", value, node->name); return rc; } } else if (strcmp(key, "port") == 0) { errno = 0; node->port = strtoul(value, NULL, 0); if (errno) { fprintf(stderr, "Invalid port \"%s\" for host %s\n", value, node->name); return -errno; } } else if (strcmp(key, "exec") == 0) { rc = prog_parse_exec_line(node, value); if (rc) return rc; } else { fprintf(stderr, "Unrecognized key %s\n", key); } return 0; } static int prog_parse_input_file_linewise(struct isochron_orch *prog, char *buf) { struct isochron_orch_node *curr_node = NULL; char *end, *equal, *key, *value; char *line = strtok(buf, "\n"); struct isochron_send *send; size_t len; int rc = 0; LIST_INIT(&prog->nodes); while (line) { line = string_trim_comments(line); line = string_trim_whitespaces(line); len = strlen(line); if (!len) goto next; if (len >= BUFSIZ) { fprintf(stderr, "Line too long: \"%s\"\n", line); rc = -EINVAL; break; } end = line + len - 1; if (*line == '[') { if (*end != ']') { fprintf(stderr, "Unterminated section header on line: \"%s\"\n", buf); rc = -EINVAL; break; } curr_node = calloc(sizeof(*curr_node), 1); if (!curr_node) { rc = -ENOMEM; break; } send = calloc(sizeof(*send), 1); if (!send) { free(curr_node); rc = -ENOMEM; break; } memcpy(curr_node->name, line + 1, len - 2); LIST_INSERT_HEAD(&prog->nodes, curr_node, list); curr_node->role = ISOCHRON_ROLE_SEND; curr_node->send = send; goto next; } if (!curr_node) { fprintf(stderr, "Unexpected line \"%s\" belonging to no section\n", buf); rc = -EINVAL; break; } equal = line; strsep(&equal, "="); if (!equal) { fprintf(stderr, "Invalid format for line \"%s\", expected \" = \"\n", line); rc = -EINVAL; break; } key = string_trim_whitespaces(line); value = string_trim_whitespaces(equal); rc = prog_parse_key_value(curr_node, key, value); if (rc) break; next: line = strtok(NULL, "\n"); } return rc; } static int prog_parse_input_file(struct isochron_orch *prog) { struct stat sb; int rc, fd; char *buf; fd = open(prog->input_filename, O_RDONLY); if (fd < 0) { perror("open"); rc = -errno; goto err_open; } rc = fstat(fd, &sb); if (rc) { perror("fstat"); rc = -errno; goto err_fstat; } /* Allow multi-line statements by mmapping the entire file * and replacing escape sequences first */ buf = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); if (buf == MAP_FAILED) { perror("mmap"); rc = -errno; goto err_mmap; } string_replace_escape_sequences(buf); rc = prog_parse_input_file_linewise(prog, buf); munmap(buf, sb.st_size); err_mmap: err_fstat: close(fd); err_open: return rc; } static int prog_parse_args(int argc, char **argv, struct isochron_orch *prog) { bool help = false; struct prog_arg args[] = { { .short_opt = "-h", .long_opt = "--help", .type = PROG_ARG_HELP, .help_ptr = { .ptr = &help, }, .optional = true, }, { .short_opt = "-F", .long_opt = "--input-file", .type = PROG_ARG_FILEPATH, .filepath = { .buf = prog->input_filename, .size = PATH_MAX - 1, }, }, }; int rc; rc = prog_parse_np_args(argc, argv, args, ARRAY_SIZE(args)); /* Non-positional arguments left unconsumed */ if (rc < 0) { pr_err(rc, "argument parsing failed: %m\n"); return rc; } else if (rc < argc) { fprintf(stderr, "%d unconsumed arguments. First: %s\n", argc - rc, argv[rc]); prog_usage("isochron-rcv", args, ARRAY_SIZE(args)); return -1; } if (help) { prog_usage("isochron-orchestrate", args, ARRAY_SIZE(args)); return -1; } return 0; } int isochron_orchestrate_main(int argc, char *argv[]) { struct isochron_orch prog = {0}; int rc; rc = prog_parse_args(argc, argv, &prog); if (rc < 0) return rc; rc = prog_parse_input_file(&prog); if (rc) goto out; rc = prog_init_sender_nodes(&prog); if (rc) goto out; rc = prog_init_receiver_nodes(&prog); if (rc) goto out; rc = prog_open_node_connections(&prog); if (rc) goto out; rc = prog_marshall_data_from_nodes(&prog); if (rc) goto out; rc = prog_marshall_data_to_nodes(&prog); if (rc) goto out; rc = prog_run_test(&prog); out: prog_teardown(&prog); return rc; } isochron-0.9/src/ptpmon.c000066400000000000000000000357101431034026500154620ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2021 NXP */ /* This file contains code snippets from: * - The Linux kernel * - The linuxptp project */ #include #include #include #include #include #include #include #include #include #include "endian.h" #include "ptpmon.h" #define ARRAY_SIZE(array) \ (sizeof(array) / sizeof(*array)) /* Version definition for IEEE 1588-2019 */ #define PTP_MAJOR_VERSION 2 #define PTP_MINOR_VERSION 1 #define PTP_VERSION (PTP_MINOR_VERSION << 4 | PTP_MAJOR_VERSION) #define PTP_MSGSIZE 1500 #define UDS_FILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) /*0660*/ enum ptp_message_type { PTP_MSGTYPE_SYNC = 0x0, PTP_MSGTYPE_DELAY_REQ = 0x1, PTP_MSGTYPE_PDELAY_REQ = 0x2, PTP_MSGTYPE_PDELAY_RESP = 0x3, PTP_MSGTYPE_MANAGEMENT = 0xd, }; enum control_field { CTL_SYNC, CTL_DELAY_REQ, CTL_FOLLOW_UP, CTL_DELAY_RESP, CTL_MANAGEMENT, CTL_OTHER, }; struct ptp_header { __u8 tsmt; /* transportSpecific | messageType */ __u8 ver; /* reserved | versionPTP */ __be16 message_length; __u8 domain_number; __u8 reserved1; __u8 flag_field[2]; __be64 correction; __be32 reserved2; struct port_identity source_port_identity; __be16 sequence_id; __u8 control; __u8 log_message_interval; } __attribute((packed)); enum management_action { GET, SET, RESPONSE, COMMAND, ACKNOWLEDGE, }; enum ptp_tlv_type { TLV_MANAGEMENT = 0x0001, TLV_MANAGEMENT_ERROR_STATUS = 0x0002, }; enum ptp_management_error_id { MID_RESPONSE_TOO_BIG = 0x0001, MID_NO_SUCH_ID = 0x0002, MID_WRONG_LENGTH = 0x0003, MID_WRONG_VALUE = 0x0004, MID_NOT_SETABLE = 0x0005, MID_NOT_SUPPORTED = 0x0006, MID_GENERAL_ERROR = 0xFFFE, }; struct management_error_status { __be16 type; __be16 length; __be16 error; __be16 id; __u8 reserved[4]; } __attribute((packed)); struct management_tlv_datum { __u8 val; __u8 reserved; } __attribute((packed)); struct ptp_message { unsigned char buf[PTP_MSGSIZE]; size_t len; }; struct ptp_management_header { struct ptp_header hdr; struct port_identity target_port_identity; __u8 starting_boundary_hops; __u8 boundary_hops; __u8 flags; /* reserved | actionField */ __u8 reserved; } __attribute((packed)); struct ptp_tlv { __be16 tlv_type; __be16 length_field; __be16 management_id; } __attribute((packed)); struct ptpmon { char uds_remote[UNIX_PATH_MAX]; char uds_local[UNIX_PATH_MAX]; struct port_identity port_identity; int transport_specific; int domain_number; int fd; __u16 sequence_id; struct default_ds dds; }; typedef int ptpmon_tlv_cb_t(void *priv, struct ptp_tlv *tlv, const void *tlv_data); static const struct port_identity target_all_ports = { .clock_identity = { .id = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, }, .port_number = (__force __be16 )0xffff, }; const char *port_state_to_string(enum port_state state) { switch (state) { case PS_INITIALIZING: return "INITIALIZING"; case PS_FAULTY: return "FAULTY"; case PS_DISABLED: return "DISABLED"; case PS_LISTENING: return "LISTENING"; case PS_PRE_MASTER: return "PRE_MASTER"; case PS_MASTER: return "MASTER"; case PS_PASSIVE: return "PASSIVE"; case PS_UNCALIBRATED: return "UNCALIBRATED"; case PS_SLAVE: return "SLAVE"; case PS_GRAND_MASTER: return "GRAND_MASTER"; default: return "NONE"; } } static void ptp_message_clear(struct ptp_message *msg) { memset(&msg->buf, 0, PTP_MSGSIZE); } static struct ptp_header *ptp_message_header(struct ptp_message *msg) { return (struct ptp_header *)msg->buf; } static struct ptp_management_header *ptp_management_header(struct ptp_message *msg) { return (struct ptp_management_header *)msg->buf; } static void *ptp_management_suffix(struct ptp_message *msg) { return ptp_management_header(msg) + 1; } static enum ptp_message_type ptp_message_type(struct ptp_message *msg) { struct ptp_header *header = ptp_message_header(msg); return header->tsmt & 0x0f; } static enum management_action ptp_management_action(struct ptp_message *msg) { struct ptp_management_header *mgmt = ptp_management_header(msg); return mgmt->flags & 0x0f; } static void *ptp_management_tlv_data(struct ptp_tlv *tlv) { return tlv + 1; } static const char *mgt_err_code_to_string(enum ptp_management_error_id err) { switch (err) { case MID_RESPONSE_TOO_BIG: return "response too big"; case MID_NO_SUCH_ID: return "no such ID"; case MID_WRONG_LENGTH: return "wrong length"; case MID_WRONG_VALUE: return "wrong value"; case MID_NOT_SETABLE: return "not settable"; case MID_NOT_SUPPORTED: return "not supported"; case MID_GENERAL_ERROR: return "general error"; default: return "unknown"; } } static int ptpmon_management_error(struct management_error_status *mgt) { enum ptp_management_error_id err; enum ptp_management_id mid; mid = __be16_to_cpu(mgt->id); err = __be16_to_cpu(mgt->error); fprintf(stderr, "Server returned error code %d (%s) for MID %d\n", err, mgt_err_code_to_string(err), mid); return err; } static void *ptp_message_add_management_tlv(struct ptp_message *msg, enum ptp_management_id mid, size_t mid_size) { struct ptp_header *header = ptp_message_header(msg); struct ptp_tlv *tlv = ptp_management_suffix(msg); if (msg->len + sizeof(*tlv) + mid_size >= PTP_MSGSIZE) return NULL; tlv->tlv_type = __cpu_to_be16(TLV_MANAGEMENT); tlv->length_field = __cpu_to_be16(2 + mid_size); tlv->management_id = __cpu_to_be16(mid); msg->len += sizeof(*tlv) + mid_size; header->message_length = __cpu_to_be16(msg->len); return ptp_management_tlv_data(tlv); } static int uds_send(int fd, const char uds_remote[UNIX_PATH_MAX], void *buf, int buflen) { struct sockaddr_un sun; int cnt; memset(&sun, 0, sizeof(sun)); sun.sun_family = AF_LOCAL; strcpy(sun.sun_path, uds_remote); cnt = sendto(fd, buf, buflen, 0, (struct sockaddr *)&sun, sizeof(sun)); if (cnt < 1) return -errno; return cnt; } static int uds_recv(int fd, const char uds_remote[UNIX_PATH_MAX], void *buf, int buflen) { struct sockaddr_un sun; socklen_t len = sizeof(sun); memset(&sun, 0, sizeof(sun)); sun.sun_family = AF_LOCAL; strcpy(sun.sun_path, uds_remote); return recvfrom(fd, buf, buflen, 0, (struct sockaddr *)&sun, &len); } static int uds_bind(const char uds_local[UNIX_PATH_MAX]) { struct sockaddr_un sun; int fd, err; fd = socket(AF_LOCAL, SOCK_DGRAM, 0); if (fd < 0) return -errno; memset(&sun, 0, sizeof(sun)); sun.sun_family = AF_LOCAL; strcpy(sun.sun_path, uds_local); unlink(uds_local); err = bind(fd, (struct sockaddr *)&sun, sizeof(sun)); if (err < 0) { close(fd); return -errno; } err = chmod(uds_local, UDS_FILEMODE); if (err) { fprintf(stderr, "Failed to change mode of %s to %o: %m\n", uds_local, UDS_FILEMODE); close(fd); return -errno; } return fd; } static void uds_close(int fd) { struct sockaddr_un sun; socklen_t len = sizeof(sun); int err; err = getsockname(fd, (struct sockaddr *)&sun, &len); if (err) return; if (sun.sun_family == AF_LOCAL) unlink(sun.sun_path); close(fd); } static int ptpmon_send(struct ptpmon *ptpmon, struct ptp_message *msg) { int len; len = uds_send(ptpmon->fd, ptpmon->uds_remote, msg->buf, msg->len); return len < 0 ? len : 0; } static int ptpmon_recv(struct ptpmon *ptpmon, struct ptp_message *msg) { int len; len = uds_recv(ptpmon->fd, ptpmon->uds_remote, msg->buf, PTP_MSGSIZE); if (len >= 0) msg->len = len; return len < 0 ? len : 0; } static void ptpmon_message_init(struct ptpmon *ptpmon, struct ptp_message *msg, enum management_action action, const struct port_identity *target_port_identity) { struct ptp_management_header *mgmt = ptp_management_header(msg); struct ptp_header *header = ptp_message_header(msg); msg->len = sizeof(*mgmt); header->tsmt = PTP_MSGTYPE_MANAGEMENT | (ptpmon->transport_specific << 4); header->ver = PTP_VERSION; header->message_length = __cpu_to_be16(msg->len); header->domain_number = ptpmon->domain_number; header->source_port_identity = ptpmon->port_identity; header->sequence_id = __cpu_to_be16(ptpmon->sequence_id++); header->control = CTL_MANAGEMENT; header->log_message_interval = 0x7f; memcpy(&mgmt->target_port_identity, target_port_identity, sizeof(*target_port_identity)); mgmt->starting_boundary_hops = 0; mgmt->boundary_hops = 0; mgmt->flags = action; } static void ptp_management_tlv_next(struct ptp_tlv **tlv, size_t *len) { size_t tlv_size_bytes = __be16_to_cpu((*tlv)->length_field) + sizeof(**tlv); *len += tlv_size_bytes; *tlv = (struct ptp_tlv *)((unsigned char *)tlv + tlv_size_bytes); } struct ptpmon_tlv_parse_priv { enum ptp_management_id mid; void *dest; size_t dest_len; }; static int ptpmon_copy_data_set(void *priv, struct ptp_tlv *tlv, const void *tlv_data) { enum ptp_management_id mid = __be16_to_cpu(tlv->management_id); size_t tlv_length = __be16_to_cpu(tlv->length_field); struct ptpmon_tlv_parse_priv *parse = priv; if (mid != parse->mid) { fprintf(stderr, "unknown management id 0x%x, expected 0x%x\n", mid, parse->mid); return -EINVAL; } if (tlv_length != parse->dest_len + 2) { fprintf(stderr, "unexpected TLV length %zu for management id %d, expected %zu\n", tlv_length, parse->mid, parse->dest_len + 2); return -EINVAL; } memcpy(parse->dest, tlv_data, parse->dest_len); return 0; } static __u8 *ptp_management_tlv_extra_len_field(__u8 *tlv_data, size_t dest_len) { return tlv_data + dest_len - 1; } static int ptpmon_copy_variable_len_data_set(void *priv, struct ptp_tlv *tlv, const void *tlv_data) { enum ptp_management_id mid = __be16_to_cpu(tlv->management_id); size_t tlv_length = __be16_to_cpu(tlv->length_field); struct ptpmon_tlv_parse_priv *parse = priv; size_t extra_len, expected_len; if (mid != parse->mid) { fprintf(stderr, "unknown management id 0x%x, expected 0x%x\n", mid, parse->mid); return -EINVAL; } extra_len = *ptp_management_tlv_extra_len_field((__u8 *)tlv_data, parse->dest_len); expected_len = parse->dest_len + 2 + extra_len; /* PTP messages are padded to even lengths */ if (expected_len & 1) expected_len++; if (tlv_length != expected_len) { fprintf(stderr, "unexpected TLV length %zu for management id %d, expected %zu\n", tlv_length, parse->mid, expected_len); return -EINVAL; } memcpy(parse->dest, tlv_data, parse->dest_len + extra_len); return 0; } static int ptp_management_message_for_each_tlv(struct ptp_message *msg, ptpmon_tlv_cb_t cb, void *priv) { size_t len = sizeof(struct ptp_management_header); struct ptp_tlv *tlv = ptp_management_suffix(msg); int err = -EBADMSG; while (len < msg->len) { enum ptp_tlv_type tlv_type = __be16_to_cpu(tlv->tlv_type); switch (tlv_type) { case TLV_MANAGEMENT: err = cb(priv, tlv, ptp_management_tlv_data(tlv)); if (err) return err; break; case TLV_MANAGEMENT_ERROR_STATUS: return ptpmon_management_error(ptp_management_tlv_data(tlv)); default: printf("unknown TLV type %d\n", tlv_type); } ptp_management_tlv_next(&tlv, &len); } return err; } static int ptp_message_parse_reply(struct ptp_message *msg, ptpmon_tlv_cb_t cb, void *priv) { enum ptp_message_type msgtype = ptp_message_type(msg); enum management_action action; if (msg->len < sizeof(struct ptp_management_header)) { fprintf(stderr, "Buffer too short to be a management message\n"); return -EBADMSG; } if (msgtype != PTP_MSGTYPE_MANAGEMENT) { fprintf(stderr, "Expected MANAGEMENT PTP message, got 0x%x\n", msgtype); return -EBADMSG; } action = ptp_management_action(msg); if (action != RESPONSE) { printf("expected RESPONSE action, got %d\n", action); return -EBADMSG; } return ptp_management_message_for_each_tlv(msg, cb, priv); } static void ptp_management_message_update_extra_len(struct ptp_message *msg, size_t dest_len, size_t extra_len) { struct ptp_tlv *tlv = ptp_management_suffix(msg); void *tlv_data = ptp_management_tlv_data(tlv); *ptp_management_tlv_extra_len_field(tlv_data, dest_len) = extra_len; } int ptpmon_query_port_mid_extra(struct ptpmon *ptpmon, const struct port_identity *target_port_identity, enum ptp_management_id mid, void *dest, size_t dest_len, size_t extra_len) { struct ptpmon_tlv_parse_priv parse = { .mid = mid, .dest = dest, .dest_len = dest_len, }; struct ptp_message msg; int err; ptp_message_clear(&msg); ptpmon_message_init(ptpmon, &msg, GET, target_port_identity); if (!ptp_message_add_management_tlv(&msg, mid, dest_len + extra_len)) return -ERANGE; ptp_management_message_update_extra_len(&msg, dest_len, extra_len); err = ptpmon_send(ptpmon, &msg); if (err) return err; err = ptpmon_recv(ptpmon, &msg); if (err) return err; return ptp_message_parse_reply(&msg, ptpmon_copy_variable_len_data_set, &parse); } int ptpmon_query_clock_mid_extra(struct ptpmon *ptpmon, enum ptp_management_id mid, void *dest, size_t dest_len, size_t extra_len) { return ptpmon_query_port_mid_extra(ptpmon, &target_all_ports, mid, dest, dest_len, extra_len); } int ptpmon_query_port_mid(struct ptpmon *ptpmon, const struct port_identity *target_port_identity, enum ptp_management_id mid, void *dest, size_t dest_len) { struct ptpmon_tlv_parse_priv parse = { .mid = mid, .dest = dest, .dest_len = dest_len, }; struct ptp_message msg; int err; ptp_message_clear(&msg); ptpmon_message_init(ptpmon, &msg, GET, target_port_identity); if (!ptp_message_add_management_tlv(&msg, mid, dest_len)) return -ERANGE; err = ptpmon_send(ptpmon, &msg); if (err) return err; err = ptpmon_recv(ptpmon, &msg); if (err) return err; return ptp_message_parse_reply(&msg, ptpmon_copy_data_set, &parse); } int ptpmon_query_clock_mid(struct ptpmon *ptpmon, enum ptp_management_id mid, void *dest, size_t dest_len) { return ptpmon_query_port_mid(ptpmon, &target_all_ports, mid, dest, dest_len); } int ptpmon_open(struct ptpmon *ptpmon) { int fd; fd = uds_bind(ptpmon->uds_local); if (fd < 0) return fd; ptpmon->fd = fd; return 0; } void ptpmon_close(struct ptpmon *ptpmon) { uds_close(ptpmon->fd); } struct ptpmon *ptpmon_create(int domain_number, int transport_specific, const char uds_local[UNIX_PATH_MAX], const char uds_remote[UNIX_PATH_MAX]) { struct ptpmon *ptpmon; if (strlen(uds_local) >= UNIX_PATH_MAX) { fprintf(stderr, "Local UDS path \"%s\" too long, would truncate\n", uds_local); return NULL; } if (strlen(uds_remote) >= UNIX_PATH_MAX) { fprintf(stderr, "Remote UDS path \"%s\" too long, would truncate\n", uds_remote); return NULL; } ptpmon = calloc(1, sizeof(*ptpmon)); if (!ptpmon) return NULL; ptpmon->domain_number = domain_number; ptpmon->transport_specific = transport_specific; strcpy(ptpmon->uds_local, uds_local); strcpy(ptpmon->uds_remote, uds_remote); ptpmon->port_identity.port_number = __cpu_to_be16(getpid()); return ptpmon; } void ptpmon_destroy(struct ptpmon *ptpmon) { free(ptpmon); } isochron-0.9/src/ptpmon.h000066400000000000000000000156461431034026500154750ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2021 NXP */ /* This file contains code snippets from: * - The Linux kernel * - The linuxptp project */ #ifndef _PTPMON_H #define _PTPMON_H #include #include #include #include #include #include #include "endian.h" #define MAX_IFACE_LEN 255 enum ptp_management_id { /* Clock management ID values */ MID_USER_DESCRIPTION = 0x0002, MID_SAVE_IN_NON_VOLATILE_STORAGE = 0x0003, MID_RESET_NON_VOLATILE_STORAGE = 0x0004, MID_INITIALIZE = 0x0005, MID_FAULT_LOG = 0x0006, MID_FAULT_LOG_RESET = 0x0007, MID_DEFAULT_DATA_SET = 0x2000, MID_CURRENT_DATA_SET = 0x2001, MID_PARENT_DATA_SET = 0x2002, MID_TIME_PROPERTIES_DATA_SET = 0x2003, MID_PRIORITY1 = 0x2005, MID_PRIORITY2 = 0x2006, MID_DOMAIN = 0x2007, MID_SLAVE_ONLY = 0x2008, MID_TIME = 0x200F, MID_CLOCK_ACCURACY = 0x2010, MID_UTC_PROPERTIES = 0x2011, MID_TRACEABILITY_PROPERTIES = 0x2012, MID_TIMESCALE_PROPERTIES = 0x2013, MID_PATH_TRACE_LIST = 0x2015, MID_PATH_TRACE_ENABLE = 0x2016, MID_GRANDMASTER_CLUSTER_TABLE = 0x2017, MID_ACCEPTABLE_MASTER_TABLE = 0x201A, MID_ACCEPTABLE_MASTER_MAX_TABLE_SIZE = 0x201C, MID_ALTERNATE_TIME_OFFSET_ENABLE = 0x201E, MID_ALTERNATE_TIME_OFFSET_NAME = 0x201F, MID_ALTERNATE_TIME_OFFSET_MAX_KEY = 0x2020, MID_ALTERNATE_TIME_OFFSET_PROPERTIES = 0x2021, MID_EXTERNAL_PORT_CONFIGURATION_ENABLED = 0x3000, MID_HOLDOVER_UPGRADE_ENABLE = 0x3002, MID_TRANSPARENT_CLOCK_DEFAULT_DATA_SET = 0x4000, MID_PRIMARY_DOMAIN = 0x4002, MID_TIME_STATUS_NP = 0xC000, MID_GRANDMASTER_SETTINGS_NP = 0xC001, MID_SUBSCRIBE_EVENTS_NP = 0xC003, MID_SYNCHRONIZATION_UNCERTAIN_NP = 0xC006, /* Port management ID values */ MID_NULL_MANAGEMENT = 0x0000, MID_CLOCK_DESCRIPTION = 0x0001, MID_PORT_DATA_SET = 0x2004, MID_LOG_ANNOUNCE_INTERVAL = 0x2009, MID_ANNOUNCE_RECEIPT_TIMEOUT = 0x200A, MID_LOG_SYNC_INTERVAL = 0x200B, MID_VERSION_NUMBER = 0x200C, MID_ENABLE_PORT = 0x200D, MID_DISABLE_PORT = 0x200E, MID_UNICAST_NEGOTIATION_ENABLE = 0x2014, MID_UNICAST_MASTER_TABLE = 0x2018, MID_UNICAST_MASTER_MAX_TABLE_SIZE = 0x2019, MID_ACCEPTABLE_MASTER_TABLE_ENABLED = 0x201B, MID_ALTERNATE_MASTER = 0x201D, MID_MASTER_ONLY = 0x3001, MID_EXT_PORT_CONFIG_PORT_DATA_SET = 0x3003, MID_TRANSPARENT_CLOCK_PORT_DATA_SET = 0x4001, MID_DELAY_MECHANISM = 0x6000, MID_LOG_MIN_PDELAY_REQ_INTERVAL = 0x6001, MID_PORT_DATA_SET_NP = 0xC002, MID_PORT_PROPERTIES_NP = 0xC004, MID_PORT_STATS_NP = 0xC005, }; struct clock_quality { __u8 clock_class; __u8 clock_accuracy; __be16 offset_scaled_log_variance; } __attribute((packed)); struct clock_identity { __u8 id[8]; } __attribute((packed)); struct port_identity { struct clock_identity clock_identity; __be16 port_number; } __attribute((packed)); /* MID_DEFAULT_DATA_SET */ struct default_ds { __u8 flags; __u8 reserved1; __be16 number_ports; __u8 priority1; struct clock_quality clock_quality; __u8 priority2; struct clock_identity clock_identity; __u8 domain_number; __u8 reserved2; } __attribute((packed)); /* MID_PORT_DATA_SET */ struct port_ds { struct port_identity port_identity; __u8 port_state; __u8 log_min_delay_req_interval; __be64 peer_mean_path_delay; __u8 log_announce_interval; __u8 announce_receipt_timeout; __u8 log_sync_interval; __u8 delay_mechanism; __u8 log_min_pdelay_req_interval; __u8 version_number; } __attribute((packed)); /* MID_CURRENT_DATA_SET */ struct current_ds { __be16 steps_removed; __be64 offset_from_master; __be64 mean_path_delay; } __attribute((packed)); /* MID_TIME_PROPERTIES_DATA_SET */ struct time_properties_ds { __be16 current_utc_offset; __u8 flags; __u8 time_source; } __attribute((packed)); /* MID_PARENT_DATA_SET */ struct parent_data_set { struct port_identity parent_port_identity; __u8 parent_stats; __u8 reserved; __be16 observed_parent_offset_scaled_log_variance; __be32 observed_parent_clock_phase_change_rate; __u8 grandmaster_priority1; struct clock_quality grandmaster_clock_quality; __u8 grandmaster_priority2; struct clock_identity grandmaster_identity; } __attribute((packed)); /* MID_PORT_PROPERTIES_NP */ struct port_properties_np { struct port_identity port_identity; __u8 port_state; __u8 timestamping; __u8 iface_len; char iface[0]; /* up to MAX_IFACE_LEN */ } __attribute((packed)); /** Defines the state of a port. */ enum port_state { PS_INITIALIZING = 1, PS_FAULTY, PS_DISABLED, PS_LISTENING, PS_PRE_MASTER, PS_MASTER, PS_PASSIVE, PS_UNCALIBRATED, PS_SLAVE, PS_GRAND_MASTER, /*non-standard extension*/ }; static inline void portid_set(struct port_identity *portid, const struct clock_identity *clockid, unsigned int port_number) { memcpy(&portid->clock_identity, clockid, sizeof(*clockid)); portid->port_number = __cpu_to_be16(port_number); } #define CLOCKID_BUFSIZE 64 #define PORTID_BUFSIZE 64 static inline void clockid_to_string(const struct clock_identity *clockid, char buf[CLOCKID_BUFSIZE]) { const unsigned char *ptr = clockid->id; snprintf(buf, CLOCKID_BUFSIZE, "%02x%02x%02x.%02x%02x.%02x%02x%02x", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7]); } static inline void portid_to_string(const struct port_identity *portid, char buf[PORTID_BUFSIZE]) { const unsigned char *ptr = portid->clock_identity.id; snprintf(buf, PORTID_BUFSIZE, "%02x%02x%02x.%02x%02x.%02x%02x%02x-%hu", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7], ntohs(portid->port_number)); } static inline bool portid_eq(const struct port_identity *a, const struct port_identity *b) { return memcmp(a, b, sizeof(*a)) == 0; } static inline bool clockid_eq(const struct clock_identity *a, const struct clock_identity *b) { return memcmp(a, b, sizeof(*a)) == 0; } const char *port_state_to_string(enum port_state state); struct ptpmon; struct ptpmon *ptpmon_create(int domain_number, int transport_specific, const char uds_local[UNIX_PATH_MAX], const char uds_remote[UNIX_PATH_MAX]); void ptpmon_destroy(struct ptpmon *ptpmon); int ptpmon_open(struct ptpmon *ptpmon); void ptpmon_close(struct ptpmon *ptpmon); int ptpmon_query_port_mid(struct ptpmon *ptpmon, const struct port_identity *target_port_identity, enum ptp_management_id mid, void *dest, size_t dest_len); int ptpmon_query_clock_mid(struct ptpmon *ptpmon, enum ptp_management_id mid, void *dest, size_t dest_len); int ptpmon_query_port_mid_extra(struct ptpmon *ptpmon, const struct port_identity *target_port_identity, enum ptp_management_id mid, void *dest, size_t dest_len, size_t extra_len); int ptpmon_query_clock_mid_extra(struct ptpmon *ptpmon, enum ptp_management_id mid, void *dest, size_t dest_len, size_t extra_len); #endif isochron-0.9/src/rcv.c000066400000000000000000000571641431034026500147460ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright 2019 NXP */ /* This file contains code snippets from: * - The Linux kernel * - The linuxptp project * Initial prototype based on: * - https://gist.github.com/austinmarton/2862515 */ #include #include #include #include #include #include #include #include #include #include #include "argparser.h" #include "common.h" #include "isochron.h" #include "log.h" #include "management.h" #include "ptpmon.h" #include "rtnl.h" #include "sk.h" #include "sysmon.h" #define BUF_SIZ 10000 struct isochron_rcv { char if_name[IFNAMSIZ]; unsigned char dest_mac[ETH_ALEN]; char uds_remote[UNIX_PATH_MAX]; unsigned int if_index; __u8 rcvbuf[BUF_SIZ]; struct isochron_log log; clockid_t clkid; struct ptpmon *ptpmon; struct sysmon *sysmon; struct mnl_socket *rtnl; struct isochron_mgmt_handler *mgmt_handler; struct sk *mgmt_listen_sock; struct sk *mgmt_sock; struct sk *l4_sock; struct sk *l2_sock; int l2_data_fd; int l4_data_fd; int data_timeout_fd; bool have_client; bool client_waiting_for_log; bool data_fd_timed_out; bool quiet; long etype; long stats_port; struct ip_address stats_addr; unsigned long iterations; unsigned long received_pkt_count; bool sched_fifo; bool sched_rr; long sched_priority; long utc_tai_offset; bool l2; bool l4; long data_port; long domain_number; long transport_specific; long sync_threshold; long num_readings; }; static int prog_rearm_data_timeout_fd(struct isochron_rcv *prog) { struct itimerspec timeout = { .it_value = { .tv_sec = 5, .tv_nsec = 0, }, .it_interval = { .tv_sec = 0, .tv_nsec = 0, }, }; if (timerfd_settime(prog->data_timeout_fd, 0, &timeout, NULL) < 0) { perror("timerfd_settime"); return -errno; } return 0; } static void prog_disarm_data_timeout_fd(struct isochron_rcv *prog) { struct itimerspec timeout = {}; timerfd_settime(prog->data_timeout_fd, 0, &timeout, NULL); } static bool prog_received_all_packets(struct isochron_rcv *prog) { return prog->received_pkt_count == prog->iterations; } static int prog_forward_isochron_log(struct isochron_rcv *prog) { int rc; rc = isochron_send_tlv(prog->mgmt_sock, ISOCHRON_RESPONSE, ISOCHRON_MID_LOG, isochron_log_buf_tlv_size(&prog->log)); if (rc) return 0; isochron_log_xmit(&prog->log, prog->mgmt_sock); isochron_log_teardown(&prog->log); return isochron_log_init(&prog->log, prog->iterations * sizeof(struct isochron_rcv_pkt_data)); } static int app_loop(struct isochron_rcv *prog, __u8 *rcvbuf, size_t len, bool l2, const struct isochron_timestamp *tstamp) { struct isochron_rcv_pkt_data rcv_pkt = {0}; struct timespec now_ts; __u32 seqid; __s64 now; int rc; clock_gettime(prog->clkid, &now_ts); rc = prog_rearm_data_timeout_fd(prog); if (rc) return rc; now = timespec_to_ns(&now_ts); rcv_pkt.arrival = __cpu_to_be64(now); if (l2) { struct ethhdr *eth_hdr = (struct ethhdr *)rcvbuf; struct isochron_header *hdr = (struct isochron_header *)(eth_hdr + 1); if (len < sizeof(*eth_hdr) + sizeof(*hdr)) { if (!prog->quiet) printf("Packet too short (%zu bytes)\n", len); return 0; } rcv_pkt.seqid = hdr->seqid; rcv_pkt.hwts = __cpu_to_be64(timespec_to_ns(&tstamp->hw)); rcv_pkt.swts = __cpu_to_be64(utc_to_tai(timespec_to_ns(&tstamp->sw), prog->utc_tai_offset)); } else { struct isochron_header *hdr = (struct isochron_header *)rcvbuf; if (len < sizeof(*hdr)) { if (!prog->quiet) printf("Packet too short (%zu bytes)\n", len); return 0; } rcv_pkt.seqid = hdr->seqid; rcv_pkt.hwts = __cpu_to_be64(timespec_to_ns(&tstamp->hw)); rcv_pkt.swts = __cpu_to_be64(utc_to_tai(timespec_to_ns(&tstamp->sw), prog->utc_tai_offset)); } seqid = __be32_to_cpu(rcv_pkt.seqid); if (seqid > prog->iterations) { if (!prog->quiet) printf("Discarding seqid %u\n", seqid); return 0; } rc = isochron_log_rcv_pkt(&prog->log, &rcv_pkt); if (rc) return rc; prog->received_pkt_count++; /* Expedite the log transmission if we're late */ if (prog->client_waiting_for_log && prog_received_all_packets(prog)) return prog_forward_isochron_log(prog); return 0; } static int prog_init_l2_sock(struct isochron_rcv *prog) { int fd, rc; if (!prog->l2) return 0; if (is_zero_ether_addr(prog->dest_mac)) { rc = sk_get_ether_addr(prog->if_name, prog->dest_mac); if (rc) return rc; } rc = sk_bind_l2(prog->dest_mac, prog->etype, prog->if_name, &prog->l2_sock); if (rc) return rc; fd = sk_fd(prog->l2_sock); if (is_multicast_ether_addr(prog->dest_mac)) { rc = sk_multicast_listen(prog->l2_sock, prog->if_index, prog->dest_mac, true); if (rc) goto out; } rc = sk_timestamping_init(prog->l2_sock, prog->if_name, true); if (rc) goto out; prog->l2_data_fd = fd; return 0; out: sk_close(prog->l2_sock); return rc; } static int prog_init_l4_sock(struct isochron_rcv *prog) { struct ip_address any = {}; int rc; if (!prog->l4) return 0; if_name_copy(any.bound_if_name, prog->if_name); rc = sk_bind_udp(&any, prog->data_port, &prog->l4_sock); if (rc) return rc; rc = sk_timestamping_init(prog->l4_sock, prog->if_name, true); if (rc) { errno = -rc; goto out; } prog->l4_data_fd = sk_fd(prog->l4_sock); return 0; out: sk_close(prog->l4_sock); return -errno; } static void prog_teardown_l2_sock(struct isochron_rcv *prog) { if (!prog->l2_sock) return; if (is_multicast_ether_addr(prog->dest_mac)) sk_multicast_listen(prog->l2_sock, prog->if_index, prog->dest_mac, false); sk_close(prog->l2_sock); prog->l2_sock = NULL; } static void prog_teardown_l4_sock(struct isochron_rcv *prog) { if (!prog->l4_sock) return; sk_close(prog->l4_sock); prog->l4_sock = NULL; } static int prog_data_event(struct isochron_rcv *prog, struct sk *sock, bool l2) { struct ethhdr *eth_hdr = (struct ethhdr *)prog->rcvbuf; struct isochron_timestamp tstamp = {0}; ssize_t len; len = sk_recvmsg(sock, prog->rcvbuf, BUF_SIZ, &tstamp, 0, 0); /* Suppress "Interrupted system call" message */ if (len < 0 && errno != EINTR) { perror("recvfrom failed"); return -errno; } if (l2 && !ether_addr_equal(prog->dest_mac, eth_hdr->h_dest)) return 0; return app_loop(prog, prog->rcvbuf, len, l2, &tstamp); } static int prog_data_fd_timeout(struct isochron_rcv *prog) { prog->data_fd_timed_out = true; if (!prog->client_waiting_for_log) return 0; /* Ok, ok, time is up, let's send what we've got so far. */ prog->client_waiting_for_log = false; prog_disarm_data_timeout_fd(prog); fprintf(stderr, "Timed out waiting for data packets, received %lu out of %lu expected\n", prog->received_pkt_count, prog->iterations); return prog_forward_isochron_log(prog); } static void prog_close_client_stats_session(struct isochron_rcv *prog) { prog_disarm_data_timeout_fd(prog); sk_close(prog->mgmt_sock); prog->have_client = false; prog->data_fd_timed_out = false; prog->client_waiting_for_log = false; prog->received_pkt_count = 0; prog->iterations = 0; } static int prog_client_connect_event(struct isochron_rcv *prog) { int rc; rc = sk_accept(prog->mgmt_listen_sock, &prog->mgmt_sock); if (rc) return rc; prog->have_client = true; return 0; } static int prog_get_packet_log(void *priv, char *extack) { struct isochron_rcv *prog = priv; /* Keep the client on hold */ if (!prog_received_all_packets(prog) && !prog->data_fd_timed_out) { prog->client_waiting_for_log = true; return 0; } return prog_forward_isochron_log(prog); } static int prog_forward_sysmon_offset(void *priv, char *extack) { struct isochron_rcv *prog = priv; return isochron_forward_sysmon_offset(prog->mgmt_sock, prog->sysmon, extack); } static int prog_forward_ptpmon_offset(void *priv, char *extack) { struct isochron_rcv *prog = priv; return isochron_forward_ptpmon_offset(prog->mgmt_sock, prog->ptpmon, extack); } static int prog_forward_utc_offset(void *priv, char *extack) { struct isochron_rcv *prog = priv; int rc, utc_offset; rc = isochron_forward_utc_offset(prog->mgmt_sock, prog->ptpmon, &utc_offset, extack); if (rc) return rc; isochron_fixup_kernel_utc_offset(utc_offset); prog->utc_tai_offset = utc_offset; return 0; } static int prog_forward_port_state(void *priv, char *extack) { struct isochron_rcv *prog = priv; return isochron_forward_port_state(prog->mgmt_sock, prog->ptpmon, prog->if_name, prog->rtnl, extack); } static int prog_forward_port_link_state(void *priv, char *extack) { struct isochron_rcv *prog = priv; return isochron_forward_port_link_state(prog->mgmt_sock, prog->if_name, prog->rtnl, extack); } static int prog_forward_gm_clock_identity(void *priv, char *extack) { struct isochron_rcv *prog = priv; return isochron_forward_gm_clock_identity(prog->mgmt_sock, prog->ptpmon, extack); } static int prog_forward_destination_mac(void *priv, char *extack) { struct isochron_rcv *prog = priv; struct isochron_mac_addr mac; int rc; memset(&mac, 0, sizeof(mac)); ether_addr_copy(mac.addr, prog->dest_mac); rc = isochron_send_tlv(prog->mgmt_sock, ISOCHRON_RESPONSE, ISOCHRON_MID_DESTINATION_MAC, sizeof(mac)); if (rc) return rc; sk_send(prog->mgmt_sock, &mac, sizeof(mac)); return 0; } static int prog_forward_current_clock_tai(void *priv, char *extack) { struct isochron_rcv *prog = priv; return isochron_forward_current_clock_tai(prog->mgmt_sock, extack); } static int prog_set_packet_count(void *priv, void *ptr, char *extack) { struct isochron_packet_count *packet_count = ptr; struct isochron_rcv *prog = priv; size_t iterations; int rc; iterations = __be64_to_cpu(packet_count->count); isochron_log_teardown(&prog->log); rc = isochron_log_init(&prog->log, iterations * sizeof(struct isochron_rcv_pkt_data)); if (rc) { mgmt_extack(extack, "Could not allocate log for %zu iterations", iterations); return rc; } prog->iterations = iterations; /* Clock is ticking! */ rc = prog_rearm_data_timeout_fd(prog); if (rc) { mgmt_extack(extack, "Could not arm timeout timer"); return rc; } return 0; } static int prog_update_l2_enabled(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_rcv *prog = priv; int rc = 0; if (prog->l2 == f->enabled) return 0; prog->l2 = f->enabled; if (prog->l2) rc = prog_init_l2_sock(prog); else prog_teardown_l2_sock(prog); return rc; } static int prog_update_l4_enabled(void *priv, void *ptr, char *extack) { struct isochron_feature_enabled *f = ptr; struct isochron_rcv *prog = priv; int rc = 0; if (prog->l4 == f->enabled) return 0; prog->l4 = f->enabled; if (prog->l4) rc = prog_init_l4_sock(prog); else prog_teardown_l4_sock(prog); return rc; } static const struct isochron_mgmt_ops rcv_mgmt_ops[__ISOCHRON_MID_MAX] = { [ISOCHRON_MID_PACKET_COUNT] = { .set = prog_set_packet_count, .struct_size = sizeof(struct isochron_packet_count), }, [ISOCHRON_MID_L2_ENABLED] = { .set = prog_update_l2_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_L4_ENABLED] = { .set = prog_update_l4_enabled, .struct_size = sizeof(struct isochron_feature_enabled), }, [ISOCHRON_MID_LOG] = { .get = prog_get_packet_log, }, [ISOCHRON_MID_SYSMON_OFFSET] = { .get = prog_forward_sysmon_offset, }, [ISOCHRON_MID_PTPMON_OFFSET] = { .get = prog_forward_ptpmon_offset, }, [ISOCHRON_MID_UTC_OFFSET] = { .get = prog_forward_utc_offset, }, [ISOCHRON_MID_PORT_STATE] = { .get = prog_forward_port_state, }, [ISOCHRON_MID_PORT_LINK_STATE] = { .get = prog_forward_port_link_state, }, [ISOCHRON_MID_GM_CLOCK_IDENTITY] = { .get = prog_forward_gm_clock_identity, }, [ISOCHRON_MID_DESTINATION_MAC] = { .get = prog_forward_destination_mac, }, [ISOCHRON_MID_CURRENT_CLOCK_TAI] = { .get = prog_forward_current_clock_tai, }, }; enum pollfd_type { PFD_MGMT, PFD_DATA_TIMEOUT, PFD_DATA1, PFD_DATA2, __PFD_MAX, }; static void prog_fill_dynamic_pfds(struct isochron_rcv *prog, struct pollfd *pfd, int *pfd_num, int *l2_pfd, int *l4_pfd) { *pfd_num = PFD_DATA1; *l2_pfd = -1; *l4_pfd = -1; if (prog->have_client) pfd[PFD_MGMT].fd = sk_fd(prog->mgmt_sock); else pfd[PFD_MGMT].fd = sk_fd(prog->mgmt_listen_sock); if (prog->l2) { *l2_pfd = *pfd_num; pfd[(*pfd_num)++].fd = prog->l2_data_fd; } if (prog->l4) { *l4_pfd = *pfd_num; pfd[(*pfd_num)++].fd = prog->l4_data_fd; } } static int server_loop(struct isochron_rcv *prog) { struct pollfd pfd[__PFD_MAX] = { [PFD_MGMT] = { /* .fd to be filled in dynamically */ .events = POLLIN | POLLERR | POLLPRI, }, [PFD_DATA_TIMEOUT] = { .fd = prog->data_timeout_fd, .events = POLLIN | POLLERR | POLLPRI, }, [PFD_DATA1] = { /* .fd to be filled in dynamically */ .events = POLLIN | POLLERR | POLLPRI, }, [PFD_DATA2] = { /* .fd to be filled in dynamically */ .events = POLLIN | POLLERR | POLLPRI, }, }; __u32 sched_policy = SCHED_OTHER; int l2_pfd, l4_pfd; int pfd_num; int rc = 0; int cnt; if (prog->sched_fifo) sched_policy = SCHED_FIFO; if (prog->sched_rr) sched_policy = SCHED_RR; if (sched_policy != SCHED_OTHER) { struct sched_attr attr = { .size = sizeof(struct sched_attr), .sched_policy = sched_policy, .sched_priority = prog->sched_priority, }; if (sched_setattr(getpid(), &attr, 0)) { perror("sched_setattr failed"); return -errno; } } do { prog_fill_dynamic_pfds(prog, pfd, &pfd_num, &l2_pfd, &l4_pfd); cnt = poll(pfd, pfd_num, -1); if (cnt < 0) { if (errno == EINTR) { break; } else { perror("poll failed"); rc = -errno; break; } } else if (!cnt) { break; } if (l2_pfd >= 0 && pfd[l2_pfd].revents & (POLLIN | POLLERR | POLLPRI)) { rc = prog_data_event(prog, prog->l2_sock, true); if (rc) break; } if (l4_pfd >= 0 && pfd[l4_pfd].revents & (POLLIN | POLLERR | POLLPRI)) { rc = prog_data_event(prog, prog->l4_sock, false); if (rc) break; } if (pfd[PFD_MGMT].revents & (POLLIN | POLLERR | POLLPRI)) { if (prog->have_client) { rc = isochron_mgmt_event(prog->mgmt_sock, prog->mgmt_handler, prog); if (sk_closed(prog->mgmt_sock)) prog_close_client_stats_session(prog); else if (rc) break; } else { rc = prog_client_connect_event(prog); if (rc) break; } } if (pfd[PFD_DATA_TIMEOUT].revents & (POLLIN | POLLERR | POLLPRI)) { __u64 expiry_count; rc = read_exact(prog->data_timeout_fd, &expiry_count, sizeof(expiry_count)); if (rc < 0) break; rc = prog_data_fd_timeout(prog); if (rc) break; } if (signal_received) break; } while (1); if (prog->have_client) prog_close_client_stats_session(prog); /* Restore scheduling policy */ if (sched_policy != SCHED_OTHER) { struct sched_attr attr = { .size = sizeof(struct sched_attr), .sched_policy = SCHED_OTHER, .sched_priority = 0, }; if (sched_setattr(getpid(), &attr, 0)) { perror("sched_setattr failed"); return -errno; } } return rc; } static int prog_init_ptpmon(struct isochron_rcv *prog) { char uds_local[UNIX_PATH_MAX]; int rc; snprintf(uds_local, sizeof(uds_local), "/var/run/isochron.%d", getpid()); prog->ptpmon = ptpmon_create(prog->domain_number, prog->transport_specific, uds_local, prog->uds_remote); if (!prog->ptpmon) return -ENOMEM; rc = ptpmon_open(prog->ptpmon); if (rc) { pr_err(rc, "failed to open ptpmon: %m\n"); goto out_destroy; } return 0; out_destroy: ptpmon_destroy(prog->ptpmon); prog->ptpmon = NULL; return rc; } static void prog_teardown_ptpmon(struct isochron_rcv *prog) { ptpmon_close(prog->ptpmon); ptpmon_destroy(prog->ptpmon); } static int prog_init_sysmon(struct isochron_rcv *prog) { prog->sysmon = sysmon_create(prog->if_name, prog->num_readings); if (!prog->sysmon) return -ENOMEM; sysmon_print_method(prog->sysmon); return 0; } static void prog_teardown_sysmon(struct isochron_rcv *prog) { sysmon_destroy(prog->sysmon); } static int prog_init_mgmt_listen_sock(struct isochron_rcv *prog) { int rc; prog->mgmt_handler = isochron_mgmt_handler_create(rcv_mgmt_ops); if (!prog->mgmt_handler) return -ENOMEM; rc = sk_listen_tcp(&prog->stats_addr, prog->stats_port, 1, &prog->mgmt_listen_sock); if (rc) isochron_mgmt_handler_destroy(prog->mgmt_handler); return rc; } static void prog_teardown_mgmt_listen_sock(struct isochron_rcv *prog) { sk_close(prog->mgmt_listen_sock); isochron_mgmt_handler_destroy(prog->mgmt_handler); } static int prog_init_data_timeout_fd(struct isochron_rcv *prog) { int fd; fd = timerfd_create(CLOCK_MONOTONIC, 0); if (fd < 0) { perror("timerfd_create"); return -errno; } prog->data_timeout_fd = fd; return 0; } static void prog_teardown_data_timeout_fd(struct isochron_rcv *prog) { prog_disarm_data_timeout_fd(prog); close(prog->data_timeout_fd); } static int prog_rtnl_open(struct isochron_rcv *prog) { struct mnl_socket *nl; nl = mnl_socket_open(NETLINK_ROUTE); if (!nl) { perror("mnl_socket_open"); return -errno; } if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { perror("mnl_socket_bind"); mnl_socket_close(nl); return -errno; } prog->rtnl = nl; return 0; } static void prog_rtnl_close(struct isochron_rcv *prog) { struct mnl_socket *nl = prog->rtnl; prog->rtnl = NULL; mnl_socket_close(nl); } static int prog_check_admin_state(struct isochron_rcv *prog) { bool up; int rc; rc = rtnl_query_admin_state(prog->rtnl, prog->if_name, &up); if (rc) { pr_err(rc, "Failed to query port %s admin state: %m\n", prog->if_name); return rc; } if (!up) { fprintf(stderr, "Interface %s is administratively down\n", prog->if_name); return -ENETDOWN; } return 0; } static int prog_init(struct isochron_rcv *prog) { int rc; rc = prog_rtnl_open(prog); if (rc) return rc; rc = prog_check_admin_state(prog); if (rc) goto out_close_rtnl; rc = sk_validate_ts_info(prog->if_name); if (rc) goto out_close_rtnl; rc = prog_init_ptpmon(prog); if (rc) goto out_close_rtnl; rc = prog_init_sysmon(prog); if (rc) goto out_teardown_ptpmon; prog->clkid = CLOCK_TAI; prog->if_index = if_nametoindex(prog->if_name); if (!prog->if_index) { perror("if_nametoindex failed"); rc = -errno; goto out_teardown_sysmon; } rc = prog_init_mgmt_listen_sock(prog); if (rc) goto out_teardown_sysmon; rc = prog_init_l2_sock(prog); if (rc) goto out_teardown_mgmt_listen_sock; rc = prog_init_l4_sock(prog); if (rc) goto out_teardown_l2_sock; rc = prog_init_data_timeout_fd(prog); if (rc) goto out_teardown_l4_sock; return 0; out_teardown_l4_sock: prog_teardown_l4_sock(prog); out_teardown_l2_sock: prog_teardown_l2_sock(prog); out_teardown_mgmt_listen_sock: prog_teardown_mgmt_listen_sock(prog); out_teardown_sysmon: prog_teardown_sysmon(prog); out_teardown_ptpmon: prog_teardown_ptpmon(prog); out_close_rtnl: prog_rtnl_close(prog); return rc; } static int prog_parse_args(int argc, char **argv, struct isochron_rcv *prog) { bool help = false; struct prog_arg args[] = { { .short_opt = "-h", .long_opt = "--help", .type = PROG_ARG_HELP, .help_ptr = { .ptr = &help, }, .optional = true, }, { .short_opt = "-i", .long_opt = "--interface", .type = PROG_ARG_IFNAME, .ifname = { .buf = prog->if_name, .size = IFNAMSIZ - 1, }, }, { .short_opt = "-d", .long_opt = "--dmac", .type = PROG_ARG_MAC_ADDR, .mac = { .buf = prog->dest_mac, }, .optional = true, }, { .short_opt = "-q", .long_opt = "--quiet", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->quiet, }, .optional = true, }, { .short_opt = "-e", .long_opt = "--etype", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->etype, }, .optional = true, }, { .short_opt = "-S", .long_opt = "--stats-address", .type = PROG_ARG_IP, .ip_ptr = { .ptr = &prog->stats_addr, }, .optional = true, }, { .short_opt = "-P", .long_opt = "--stats-port", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->stats_port, }, .optional = true, }, { .short_opt = "-H", .long_opt = "--sched-priority", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->sched_priority, }, .optional = true, }, { .short_opt = "-f", .long_opt = "--sched-fifo", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->sched_fifo, }, .optional = true, }, { .short_opt = "-r", .long_opt = "--sched-rr", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->sched_rr, }, .optional = true, }, { .short_opt = "-O", .long_opt = "--utc-tai-offset", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->utc_tai_offset, }, .optional = true, }, { .short_opt = "-2", .long_opt = "--l2", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->l2, }, .optional = true, }, { .short_opt = "-4", .long_opt = "--l4", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->l4, }, .optional = true, }, { .short_opt = "-N", .long_opt = "--domain-number", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->domain_number, }, .optional = true, }, { .short_opt = "-t", .long_opt = "--transport-specific", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->transport_specific, }, .optional = true, }, { .short_opt = "-U", .long_opt = "--unix-domain-socket", .type = PROG_ARG_FILEPATH, .filepath = { .buf = prog->uds_remote, .size = UNIX_PATH_MAX - 1, }, .optional = true, }, { .short_opt = "-R", .long_opt = "--num-readings", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->num_readings, }, .optional = true, }, }; int rc; prog->utc_tai_offset = -1; rc = prog_parse_np_args(argc, argv, args, ARRAY_SIZE(args)); /* Non-positional arguments left unconsumed */ if (rc < 0) { pr_err(rc, "argument parsing failed: %m\n"); return rc; } else if (rc < argc) { fprintf(stderr, "%d unconsumed arguments. First: %s\n", argc - rc, argv[rc]); prog_usage("isochron-rcv", args, ARRAY_SIZE(args)); return -1; } if (help) { prog_usage("isochron-rcv", args, ARRAY_SIZE(args)); return -1; } if (prog->sched_fifo && prog->sched_rr) { fprintf(stderr, "cannot have SCHED_FIFO and SCHED_RR at the same time\n"); return -EINVAL; } if (!prog->stats_port) prog->stats_port = ISOCHRON_STATS_PORT; if (!prog->l2 && !prog->l4) prog->l2 = true; if (!prog->etype) prog->etype = ETH_P_ISOCHRON; if (!prog->data_port) prog->data_port = ISOCHRON_DATA_PORT; if (!prog->num_readings) prog->num_readings = 5; if (strlen(prog->uds_remote) == 0) sprintf(prog->uds_remote, "/var/run/ptp4l"); if (prog->utc_tai_offset == -1) { prog->utc_tai_offset = get_utc_tai_offset(); fprintf(stderr, "Using the kernel UTC-TAI offset which is %ld\n", prog->utc_tai_offset); } else { rc = set_utc_tai_offset(prog->utc_tai_offset); if (rc == -1) { perror("set_utc_tai_offset"); return -errno; } } return 0; } static void prog_teardown(struct isochron_rcv *prog) { if (!prog->quiet) isochron_rcv_log_print(&prog->log); isochron_log_teardown(&prog->log); prog_teardown_data_timeout_fd(prog); prog_teardown_l4_sock(prog); prog_teardown_l2_sock(prog); prog_teardown_mgmt_listen_sock(prog); prog_teardown_sysmon(prog); prog_teardown_ptpmon(prog); prog_rtnl_close(prog); } int isochron_rcv_main(int argc, char *argv[]) { struct isochron_rcv prog = {0}; int rc; rc = prog_parse_args(argc, argv, &prog); if (rc < 0) return rc; rc = prog_init(&prog); if (rc < 0) return rc; rc = server_loop(&prog); prog_teardown(&prog); return rc; } isochron-0.9/src/report.c000066400000000000000000000074241431034026500154610ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright 2021 NXP */ #include #include #include #include #include "argparser.h" #include "common.h" #include "isochron.h" #include "log.h" struct isochron_report { struct isochron_log send_log; struct isochron_log rcv_log; long packet_count; long frame_size; bool omit_sync; bool do_ts; bool txtime; bool taprio; bool deadline; __s64 base_time; __s64 advance_time; __s64 shift_time; __s64 cycle_time; __s64 window_size; bool summary; unsigned long start; unsigned long stop; char input_file[PATH_MAX]; char printf_fmt[ISOCHRON_LOG_PRINTF_BUF_SIZE]; char printf_args[ISOCHRON_LOG_PRINTF_MAX_NUM_ARGS]; }; static int prog_parse_args(int argc, char **argv, struct isochron_report *prog) { bool help = false; struct prog_arg args[] = { { .short_opt = "-h", .long_opt = "--help", .type = PROG_ARG_HELP, .help_ptr = { .ptr = &help, }, .optional = true, }, { .short_opt = "-F", .long_opt = "--input-file", .type = PROG_ARG_FILEPATH, .filepath = { .buf = prog->input_file, .size = PATH_MAX - 1, }, .optional = true, }, { .short_opt = "-m", .long_opt = "--summary", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->summary, }, .optional = true, }, { .short_opt = "-s", .long_opt = "--start", .type = PROG_ARG_UNSIGNED, .unsigned_ptr = { .ptr = &prog->start, }, .optional = true, }, { .short_opt = "-S", .long_opt = "--stop", .type = PROG_ARG_UNSIGNED, .unsigned_ptr = { .ptr = &prog->stop, }, .optional = true, }, { .short_opt = "-f", .long_opt = "--printf-format", .type = PROG_ARG_STRING, .string = { .buf = prog->printf_fmt, .size = ISOCHRON_LOG_PRINTF_BUF_SIZE - 1, }, .optional = true, }, { .short_opt = "-a", .long_opt = "--printf-args", .type = PROG_ARG_STRING, .string = { .buf = prog->printf_args, .size = ISOCHRON_LOG_PRINTF_MAX_NUM_ARGS - 1, }, .optional = true, }, }; int rc; rc = prog_parse_np_args(argc, argv, args, ARRAY_SIZE(args)); /* Non-positional arguments left unconsumed */ if (rc < 0) { pr_err(rc, "argument parsing failed: %m\n"); return rc; } else if (rc < argc) { fprintf(stderr, "%d unconsumed arguments. First: %s\n", argc - rc, argv[rc]); prog_usage("isochron-report", args, ARRAY_SIZE(args)); return -1; } if (help) { prog_usage("isochron-report", args, ARRAY_SIZE(args)); return -1; } if (!strlen(prog->input_file)) sprintf(prog->input_file, "isochron.dat"); return 0; } int isochron_report_main(int argc, char *argv[]) { struct isochron_report prog = {0}; int rc; rc = prog_parse_args(argc, argv, &prog); if (rc) return rc; /* Replacing the escape sequences does not need to be done per packet, * so do it once before passing the printf format specifier to the log. */ rc = string_replace_escape_sequences(prog.printf_fmt); if (rc) return rc; rc = isochron_log_load(prog.input_file, &prog.send_log, &prog.rcv_log, &prog.packet_count, &prog.frame_size, &prog.omit_sync, &prog.do_ts, &prog.taprio, &prog.txtime, &prog.deadline, &prog.base_time, &prog.advance_time, &prog.shift_time, &prog.cycle_time, &prog.window_size); if (rc) return rc; if (!prog.start) prog.start = 1; if (!prog.stop) prog.stop = prog.packet_count; rc = isochron_print_stats(&prog.send_log, &prog.rcv_log, prog.printf_fmt, prog.printf_args, prog.start, prog.stop, prog.summary, prog.omit_sync, prog.taprio, prog.txtime, prog.base_time, prog.advance_time, prog.shift_time, prog.cycle_time, prog.window_size); isochron_log_teardown(&prog.send_log); isochron_log_teardown(&prog.rcv_log); return 0; } isochron-0.9/src/rtnl.c000066400000000000000000000150421431034026500151200ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright 2022 NXP */ #include #include #include #include #include #include "common.h" #include "rtnl.h" #define SOCKET_BUFFER_SIZE 8192 struct vlan_info { const char *kind; __u32 link_ifindex; }; struct ifname_info { char ifname[IFNAMSIZ]; }; struct ifindex_info { int ifindex; }; struct if_admin_info { bool up; }; struct if_oper_info { bool running; }; static int rtnl_parse_vlan_linkinfo(const struct nlattr *attr, void *data) { __u16 type = mnl_attr_get_type(attr); struct vlan_info *v = data; /* skip unsupported attributes */ if (mnl_attr_type_valid(attr, IFLA_INFO_MAX) < 0) return MNL_CB_OK; switch (type) { case IFLA_INFO_KIND: if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) { perror("mnl_attr_validate"); return MNL_CB_ERROR; } v->kind = mnl_attr_get_str(attr); break; } return MNL_CB_OK; } static int rtnl_parse_vlan_attr(const struct nlattr *attr, void *data) { struct vlan_info *v = data; /* skip unsupported attributes */ if (mnl_attr_type_valid(attr, IFLA_MAX) < 0) return MNL_CB_OK; switch (mnl_attr_get_type(attr)) { case IFLA_LINK: if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) { perror("mnl_attr_validate"); return MNL_CB_ERROR; } v->link_ifindex = mnl_attr_get_u32(attr); break; case IFLA_LINKINFO: mnl_attr_parse_nested(attr, rtnl_parse_vlan_linkinfo, v); break; } return MNL_CB_OK; } static int rtnl_parse_vlan_nlh(const struct nlmsghdr *nlh, void *data) { struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh); struct vlan_info *v = data; mnl_attr_parse(nlh, sizeof(*ifm), rtnl_parse_vlan_attr, v); return MNL_CB_STOP; } static int rtnl_parse_ifname_attr(const struct nlattr *attr, void *data) { struct ifname_info *i = data; const char *ifname; /* skip unsupported attributes */ if (mnl_attr_type_valid(attr, IFLA_MAX) < 0) return MNL_CB_OK; switch (mnl_attr_get_type(attr)) { case IFLA_IFNAME: if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) { perror("mnl_attr_validate"); return MNL_CB_ERROR; } ifname = mnl_attr_get_str(attr); if (strlen(ifname) >= IFNAMSIZ) { fprintf(stderr, "rtnetlink returned IFLA_IFNAME %s too long\n", ifname); return MNL_CB_ERROR; } strcpy(i->ifname, ifname); break; } return MNL_CB_OK; } static int rtnl_parse_ifname_nlh(const struct nlmsghdr *nlh, void *data) { struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh); struct ifname_info *i = data; mnl_attr_parse(nlh, sizeof(*ifm), rtnl_parse_ifname_attr, i); return MNL_CB_STOP; } static int rtnl_parse_ifindex_nlh(const struct nlmsghdr *nlh, void *data) { struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh); struct ifindex_info *i = data; i->ifindex = ifm->ifi_index; return MNL_CB_STOP; } static int rtnl_parse_if_up_nlh(const struct nlmsghdr *nlh, void *data) { struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh); struct if_admin_info *i = data; i->up = !!(ifm->ifi_flags & IFF_UP); return MNL_CB_STOP; } static int rtnl_parse_if_running_nlh(const struct nlmsghdr *nlh, void *data) { struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh); struct if_oper_info *i = data; i->running = !!(ifm->ifi_flags & IFF_RUNNING); return MNL_CB_STOP; } static int rtnl_getlink_by_ifname(struct mnl_socket *rtnl, const char *ifname, mnl_cb_t cb, void *data) { char buf[SOCKET_BUFFER_SIZE]; struct ifinfomsg *ifm; struct nlmsghdr *nlh; __u32 seq, portid; int rc; portid = mnl_socket_get_portid(rtnl); nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = RTM_GETLINK; nlh->nlmsg_flags = NLM_F_REQUEST; nlh->nlmsg_seq = seq = time(NULL); ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); mnl_attr_put_str(nlh, IFLA_IFNAME, ifname); if (mnl_socket_sendto(rtnl, nlh, nlh->nlmsg_len) < 0) { perror("mnl_socket_sendto"); return -errno; } rc = mnl_socket_recvfrom(rtnl, buf, sizeof(buf)); if (rc < 0) { perror("mnl_socket_recvfrom"); return -errno; } rc = mnl_cb_run(buf, rc, seq, portid, cb, data); if (rc < 0) { perror("mnl_cb_run"); return -errno; } return 0; } static int rtnl_getlink_by_ifindex(struct mnl_socket *rtnl, int ifindex, mnl_cb_t cb, void *data) { char buf[SOCKET_BUFFER_SIZE]; struct ifinfomsg *ifm; struct nlmsghdr *nlh; __u32 seq, portid; int rc; portid = mnl_socket_get_portid(rtnl); nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = RTM_GETLINK; nlh->nlmsg_flags = NLM_F_REQUEST; nlh->nlmsg_seq = seq = time(NULL); ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); ifm->ifi_index = ifindex; if (mnl_socket_sendto(rtnl, nlh, nlh->nlmsg_len) < 0) { perror("mnl_socket_sendto"); return -errno; } rc = mnl_socket_recvfrom(rtnl, buf, sizeof(buf)); if (rc < 0) { perror("mnl_socket_recvfrom"); return -errno; } rc = mnl_cb_run(buf, rc, seq, portid, cb, data); if (rc < 0) { perror("mnl_cb_run"); return -errno; } return 0; } static int rtnl_fill_vlan_info(struct mnl_socket *rtnl, int ifindex, struct vlan_info *v) { return rtnl_getlink_by_ifindex(rtnl, ifindex, rtnl_parse_vlan_nlh, v); } static int rtnl_fill_ifindex_name(struct mnl_socket *rtnl, int ifindex, struct ifname_info *i) { return rtnl_getlink_by_ifindex(rtnl, ifindex, rtnl_parse_ifname_nlh, i); } static int rtnl_fill_ifname_ifindex(struct mnl_socket *rtnl, const char *ifname, struct ifindex_info *i) { return rtnl_getlink_by_ifname(rtnl, ifname, rtnl_parse_ifindex_nlh, i); } int vlan_resolve_real_dev(struct mnl_socket *rtnl, const char *vlan_ifname, char *real_ifname) { struct ifindex_info ifindex = {}; struct ifname_info ifname = {}; struct vlan_info v = {}; int rc; rc = rtnl_fill_ifname_ifindex(rtnl, vlan_ifname, &ifindex); if (rc) return rc; do { rc = rtnl_fill_vlan_info(rtnl, ifindex.ifindex, &v); if (rc) return rc; if (!v.kind || strcmp(v.kind, "vlan")) break; ifindex.ifindex = v.link_ifindex; } while (true); rc = rtnl_fill_ifindex_name(rtnl, ifindex.ifindex, &ifname); if (rc) return rc; strcpy(real_ifname, ifname.ifname); return 0; } int rtnl_query_admin_state(struct mnl_socket *rtnl, const char *if_name, bool *up) { struct if_admin_info i = {}; int rc; rc = rtnl_getlink_by_ifname(rtnl, if_name, rtnl_parse_if_up_nlh, &i); if (rc) return rc; *up = i.up; return 0; } int rtnl_query_link_state(struct mnl_socket *rtnl, const char *if_name, bool *running) { struct if_oper_info i = {}; int rc; rc = rtnl_getlink_by_ifname(rtnl, if_name, rtnl_parse_if_running_nlh, &i); if (rc) return rc; *running = i.running; return 0; } isochron-0.9/src/rtnl.h000066400000000000000000000006051431034026500151240ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2022 NXP */ #ifndef _RTNL_H #define _RTNL_H int vlan_resolve_real_dev(struct mnl_socket *rtnl, const char *vlan_ifname, char *real_ifname); int rtnl_query_admin_state(struct mnl_socket *rtnl, const char *if_name, bool *up); int rtnl_query_link_state(struct mnl_socket *rtnl, const char *if_name, bool *running); #endif isochron-0.9/src/send.c000066400000000000000000001205111431034026500150700ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright 2019 NXP */ /* This file contains code snippets from: * - The Linux kernel * - The linuxptp project * Initial prototype based on: * https://gist.github.com/austinmarton/1922600 * https://sourceforge.net/p/linuxptp/mailman/message/31998404/ */ #define _GNU_SOURCE #include #include #include #include #include #include #include /* For va_start and va_end */ #include #include #include #include #include #include #include #include #include #include #include #include "argparser.h" #include "common.h" #include "isochron.h" #include "log.h" #include "management.h" #include "send.h" #include "rtnl.h" #include #define TIME_FMT_LEN 27 /* "[%s] " */ struct isochron_txtime_postmortem_priv { struct isochron_send *prog; __u64 txtime; }; static void trace(struct isochron_send *prog, const char *fmt, ...) { char now_buf[TIMESPEC_BUFSIZ]; struct timespec now_ts; int len = TIME_FMT_LEN; va_list ap; __s64 now; if (!prog->trace_mark) return; clock_gettime(prog->clkid, &now_ts); now = timespec_to_ns(&now_ts); ns_sprintf(now_buf, now); /* The first 24 is for the minimum string width (for padding). * The second .24 is for precision (maximum string length). */ snprintf(prog->tracebuf, TIME_FMT_LEN + 1, "[%24.24s] ", now_buf); prog->tracebuf[TIME_FMT_LEN] = ' '; va_start(ap, fmt); len += vsnprintf(prog->tracebuf + TIME_FMT_LEN + 1, BUF_SIZ - TIME_FMT_LEN - 1, fmt, ap); va_end(ap); if (write(prog->trace_mark_fd, prog->tracebuf, len) < 0) { perror("trace"); exit(EXIT_FAILURE); } } static int prog_init_stats_socket(struct isochron_send *prog) { if (!prog->stats_srv.family) return 0; return sk_connect_tcp(&prog->stats_srv, prog->stats_port, &prog->mgmt_sock); } static void prog_teardown_stats_socket(struct isochron_send *prog) { if (!prog->stats_srv.family) return; sk_close(prog->mgmt_sock); } __s64 isochron_send_first_base_time(struct isochron_send *prog) { __s64 base_time = prog->base_time + prog->shift_time; /* Make sure we get enough sleep at the beginning */ return future_base_time(base_time, prog->cycle_time, prog->session_start + TIME_MARGIN); } static int isochron_txtime_pkt_dump(void *priv, void *pkt) { struct isochron_txtime_postmortem_priv *postmortem = priv; struct isochron_send_pkt_data *send_pkt = pkt; struct isochron_send *prog = postmortem->prog; char ideal_wakeup_buf[TIMESPEC_BUFSIZ]; char scheduled_buf[TIMESPEC_BUFSIZ]; char latency_buf[TIMESPEC_BUFSIZ]; char wakeup_buf[TIMESPEC_BUFSIZ]; __s64 ideal_wakeup, latency; __u64 wakeup, scheduled; if (postmortem->txtime != __be64_to_cpu(send_pkt->scheduled)) return 0; scheduled = __be64_to_cpu(send_pkt->scheduled); wakeup = __be64_to_cpu(send_pkt->wakeup); ideal_wakeup = scheduled - prog->advance_time; latency = wakeup - ideal_wakeup; ns_sprintf(scheduled_buf, scheduled); ns_sprintf(wakeup_buf, wakeup); ns_sprintf(ideal_wakeup_buf, ideal_wakeup); ns_sprintf(latency_buf, latency); fprintf(stderr, "TXTIME postmortem: seqid %d scheduled for %s, wakeup at %s, ideal wakeup at %s, wakeup latency %s\n", __be32_to_cpu(send_pkt->seqid), scheduled_buf, wakeup_buf, ideal_wakeup_buf, latency_buf); return 1; } static void prog_late_txtime_pkt_postmortem(struct isochron_send *prog, __u64 txtime) { struct isochron_txtime_postmortem_priv postmortem = { .prog = prog, .txtime = txtime, }; int rc; rc = isochron_log_for_each_pkt(&prog->log, sizeof(struct isochron_send_pkt_data), &postmortem, isochron_txtime_pkt_dump); if (!rc) { char txtime_buf[TIMESPEC_BUFSIZ]; ns_sprintf(txtime_buf, postmortem.txtime); fprintf(stderr, "don't recognize packet with txtime %s\n", txtime_buf); } } /* Timestamps will come later */ static int prog_log_packet_no_tstamp(struct isochron_send *prog, const struct isochron_header *hdr) { struct isochron_send_pkt_data *send_pkt; __u32 index; /* Don't log if we're running indefinitely, there's no point */ if (!prog->iterations) return 0; index = __be32_to_cpu(hdr->seqid) - 1; send_pkt = isochron_log_get_entry(&prog->log, sizeof(*send_pkt), index); if (!send_pkt) { fprintf(stderr, "Could not log send packet at index %u\n", index); return -EINVAL; } if (send_pkt->seqid) { fprintf(stderr, "There already exists a packet logged at index %u\n", index); return -EINVAL; } send_pkt->scheduled = hdr->scheduled; send_pkt->wakeup = hdr->wakeup; send_pkt->seqid = hdr->seqid; send_pkt->sched_ts = 0; send_pkt->swts = 0; send_pkt->hwts = 0; return 0; } static bool isochron_pkt_fully_timestamped(struct isochron_send_pkt_data *send_pkt) { return send_pkt->hwts && send_pkt->swts && send_pkt->sched_ts; } static int prog_validate_premature_tx(__u32 seqid, __s64 hwts, __s64 scheduled, bool deadline) { char scheduled_buf[TIMESPEC_BUFSIZ]; char hwts_buf[TIMESPEC_BUFSIZ]; /* When deadline_mode is set, premature transmissions are expected */ if (deadline || hwts >= scheduled) return 0; ns_sprintf(scheduled_buf, scheduled); ns_sprintf(hwts_buf, hwts); fprintf(stderr, "Premature transmission detected for seqid %u scheduled for %s: TX hwts %s\n", seqid, scheduled_buf, hwts_buf); return -EINVAL; } static int prog_validate_late_tx(__u32 seqid, __s64 hwts, __s64 scheduled, __s64 cycle_time, bool deadline) { int num_extra_cycles = (hwts - scheduled) / cycle_time; char scheduled_buf[TIMESPEC_BUFSIZ]; char hwts_buf[TIMESPEC_BUFSIZ]; bool late; if (deadline) late = (hwts > scheduled); else late = (num_extra_cycles > 0); if (!late) return 0; ns_sprintf(scheduled_buf, scheduled); ns_sprintf(hwts_buf, hwts); fprintf(stderr, "Late transmission by %d cycles detected for seqid %u scheduled for %s: TX hwts %s\n", num_extra_cycles, seqid, scheduled_buf, hwts_buf); return -EINVAL; } static int prog_validate_tx_hwts(struct isochron_send *prog, const struct isochron_send_pkt_data *send_pkt) { __s64 hwts, scheduled; __u32 seqid; int rc; if (!prog->txtime && !prog->taprio) return 0; seqid = __be32_to_cpu(send_pkt->seqid); hwts = __be64_to_cpu(send_pkt->hwts); scheduled = __be64_to_cpu(send_pkt->scheduled); rc = prog_validate_premature_tx(seqid, hwts, scheduled, prog->deadline); if (rc) return rc; rc = prog_validate_late_tx(seqid, hwts, scheduled, prog->cycle_time, prog->deadline); if (rc) return rc; return 0; } /* Propagates the return code from sk_recvmsg, i.e. the number of bytes * read from the socket (if we could successfully read a timestamp), * or a negative error code. */ static int prog_poll_txtstamps(struct isochron_send *prog, int timeout) { struct isochron_send_pkt_data *send_pkt; struct isochron_timestamp tstamp = {}; __be64 hwts, swts, swts_utc; __u8 err_pkt[BUF_SIZ]; __u64 txtime; int len, rc; len = sk_recvmsg(prog->data_sock, err_pkt, BUF_SIZ, &tstamp, MSG_ERRQUEUE, timeout); if (len <= 0) return len; txtime = timespec_to_ns(&tstamp.txtime); if (txtime) { prog_late_txtime_pkt_postmortem(prog, txtime); return -EINVAL; } /* Since we log the packets in the same order as the kernel keeps * track of timestamps using SOF_TIMESTAMPING_OPT_ID on the data * socket, finding packets by timestamp key and patching their * timestamp is a cheap hack to avoid a linear lookup. */ send_pkt = isochron_log_get_entry(&prog->log, sizeof(*send_pkt), tstamp.tskey); if (!send_pkt) { fprintf(stderr, "received timestamp for unknown key %u\n", tstamp.tskey); return -EINVAL; } if (isochron_pkt_fully_timestamped(send_pkt)) { fprintf(stderr, "received duplicate timestamp for packet key %u already fully timestamped\n", tstamp.tskey); return -EINVAL; } swts = __cpu_to_be64(utc_to_tai(timespec_to_ns(&tstamp.sw), prog->utc_tai_offset)); swts_utc = __cpu_to_be64(timespec_to_ns(&tstamp.sw)); hwts = __cpu_to_be64(timespec_to_ns(&tstamp.hw)); switch (tstamp.tstype) { case SCM_TSTAMP_SCHED: if (swts_utc) send_pkt->sched_ts = swts; break; case SCM_TSTAMP_SND: if (swts_utc) send_pkt->swts = swts; break; default: break; } if (hwts) { send_pkt->hwts = hwts; rc = prog_validate_tx_hwts(prog, send_pkt); if (rc) return rc; } if (isochron_pkt_fully_timestamped(send_pkt)) prog->timestamped++; return len; } static int do_work(struct isochron_send *prog, int iteration, __s64 scheduled, struct isochron_header *hdr) { struct timespec now_ts; __s64 now; int rc; clock_gettime(prog->clkid, &now_ts); now = timespec_to_ns(&now_ts); trace(prog, "send seqid %d start\n", iteration); hdr->scheduled = __cpu_to_be64(scheduled); hdr->wakeup = __cpu_to_be64(now); hdr->seqid = __cpu_to_be32(iteration); if (prog->txtime) *((__u64 *)CMSG_DATA(prog->txtime_cmsg)) = (__u64)(scheduled); rc = prog_log_packet_no_tstamp(prog, hdr); if (rc) return rc; /* Send packet */ rc = sk_sendmsg(prog->data_sock, prog->msg, 0); if (rc < 0) { perror("Failed to send data packet"); return -errno; } trace(prog, "send seqid %d end\n", iteration); return 0; } static int isochron_missing_txts_dump(void *priv, void *pkt) { struct isochron_send_pkt_data *send_pkt = pkt; __u32 seqid = __be32_to_cpu(send_pkt->seqid); bool missing_sched_ts = false; bool missing_hwts = false; bool missing_swts = false; if (!seqid) return 1; if (!send_pkt->hwts) missing_hwts = true; if (!send_pkt->swts) missing_swts = true; if (!send_pkt->sched_ts) missing_sched_ts = true; if (!missing_hwts && !missing_swts && !missing_sched_ts) return 0; fprintf(stderr, "seqid %u missing timestamps: %s%s%s\n", __be32_to_cpu(send_pkt->seqid), missing_hwts ? "hw, " : "", missing_swts ? "sw, " : "", missing_sched_ts ? "sched, " : ""); return 0; } static void prog_print_missing_timestamps(struct isochron_send *prog) { if (prog->quiet) return; isochron_log_for_each_pkt(&prog->log, sizeof(struct isochron_send_pkt_data), prog, isochron_missing_txts_dump); } static int wait_for_txtimestamps(struct isochron_send *prog) { int timeout_ms = 2 * MSEC_PER_SEC; int rc; while (prog->timestamped < prog->iterations) { rc = prog_poll_txtstamps(prog, timeout_ms); if (rc <= 0) { fprintf(stderr, "Timed out waiting for TX timestamps, %ld timestamps unacknowledged\n", prog->iterations - prog->timestamped); prog_print_missing_timestamps(prog); return rc; } } return 0; } static int run_nanosleep(struct isochron_send *prog) { char cycle_time_buf[TIMESPEC_BUFSIZ]; char base_time_buf[TIMESPEC_BUFSIZ]; char wakeup_buf[TIMESPEC_BUFSIZ]; char now_buf[TIMESPEC_BUFSIZ]; struct isochron_header *hdr; __s64 wakeup, scheduled; unsigned long i; int rc; if (prog->l2) { hdr = (struct isochron_header *)(prog->sendbuf + prog->l2_header_len); } else { hdr = (struct isochron_header *)prog->sendbuf; } wakeup = prog->oper_base_time - prog->advance_time; ns_sprintf(now_buf, prog->session_start); ns_sprintf(base_time_buf, prog->oper_base_time); ns_sprintf(cycle_time_buf, prog->cycle_time); ns_sprintf(wakeup_buf, wakeup); fprintf(stderr, "%12s: %*s\n", "Now", TIMESPEC_BUFSIZ, now_buf); fprintf(stderr, "%12s: %*s\n", "First wakeup", TIMESPEC_BUFSIZ, wakeup_buf); fprintf(stderr, "%12s: %*s\n", "Base time", TIMESPEC_BUFSIZ, base_time_buf); fprintf(stderr, "%12s: %*s\n", "Cycle time", TIMESPEC_BUFSIZ, cycle_time_buf); /* Play nice with awk's array indexing */ for (i = 1; !prog->iterations || i <= prog->iterations; i++) { struct timespec wakeup_ts = ns_to_timespec(wakeup); rc = clock_nanosleep(prog->clkid, TIMER_ABSTIME, &wakeup_ts, NULL); switch (rc) { case 0: scheduled = wakeup + prog->advance_time; rc = do_work(prog, i, scheduled, hdr); if (rc < 0) return rc; wakeup += prog->cycle_time; break; case EINTR: continue; default: pr_err(-rc, "clock_nanosleep failed: %m\n"); break; } if (signal_received || prog->send_tid_should_stop) break; } return 0; } static void *prog_send_thread(void *arg) { struct isochron_send *prog = arg; prog->send_tid_rc = run_nanosleep(prog); prog->send_tid_stopped = true; return &prog->send_tid_rc; } static void *prog_tx_timestamp_thread(void *arg) { struct isochron_send *prog = arg; struct timespec wakeup_ts; __s64 wakeup; wakeup = prog->oper_base_time - prog->advance_time; wakeup_ts = ns_to_timespec(wakeup); /* Sync with the sender thread before polling for timestamps */ clock_nanosleep(prog->clkid, TIMER_ABSTIME, &wakeup_ts, NULL); prog->tx_timestamp_tid_rc = wait_for_txtimestamps(prog); prog->tx_tstamp_tid_stopped = true; return &prog->tx_timestamp_tid_rc; } static int prog_init_syncmon(struct isochron_send *prog) { struct syncmon_node *sn, *remote_sn; struct syncmon *syncmon; syncmon = syncmon_create(); if (!syncmon) { fprintf(stderr, "Failed to create syncmon\n"); return -ENOMEM; } if (prog->omit_sync) { sn = syncmon_add_local_sender_no_sync(syncmon, "local", prog->rtnl, prog->if_name, prog->iterations, prog->cycle_time); } else { sn = syncmon_add_local_sender(syncmon, "local", prog->rtnl, prog->if_name, prog->iterations, prog->cycle_time, prog->ptpmon, prog->sysmon, prog->sync_threshold); } if (!sn) { syncmon_destroy(syncmon); return -ENOMEM; } /* Instantiate syncmon node for the remote end only if --client * was specified, otherwise we don't know how to reach it. */ if (prog->mgmt_sock) { if (prog->omit_sync || prog->omit_remote_sync) { remote_sn = syncmon_add_remote_receiver_no_sync(syncmon, "remote", prog->mgmt_sock, sn); } else { remote_sn = syncmon_add_remote_receiver(syncmon, "remote", prog->mgmt_sock, sn, prog->sync_threshold); } if (!remote_sn) { syncmon_destroy(syncmon); return -ENOMEM; } } syncmon_init(syncmon); prog->syncmon = syncmon; return 0; } static void prog_teardown_syncmon(struct isochron_send *prog) { syncmon_destroy(prog->syncmon); } static int prog_query_utc_offset(struct isochron_send *prog) { struct time_properties_ds time_properties_ds; int ptp_utc_offset; int rc; rc = ptpmon_query_clock_mid(prog->ptpmon, MID_TIME_PROPERTIES_DATA_SET, &time_properties_ds, sizeof(time_properties_ds)); if (rc) { pr_err(rc, "ptpmon failed to query TIME_PROPERTIES_DATA_SET: %m\n"); return rc; } ptp_utc_offset = __be16_to_cpu(time_properties_ds.current_utc_offset); isochron_fixup_kernel_utc_offset(ptp_utc_offset); prog->utc_tai_offset = ptp_utc_offset; return 0; } static int prog_query_dest_mac(struct isochron_send *prog) { struct isochron_mac_addr mac; char mac_buf[MACADDR_BUFSIZ]; int rc; if (!prog->l2) return 0; if (!is_zero_ether_addr(prog->dest_mac)) return 0; if (!prog->stats_srv.family) { fprintf(stderr, "Destination MAC address is only optional with --client\n"); return -EINVAL; } rc = isochron_query_mid(prog->mgmt_sock, ISOCHRON_MID_DESTINATION_MAC, &mac, sizeof(mac)); if (rc) { fprintf(stderr, "destination MAC missing from receiver reply\n"); return rc; } ether_addr_copy(prog->dest_mac, mac.addr); mac_addr_sprintf(mac_buf, prog->dest_mac); printf("Destination MAC address is %s\n", mac_buf); return 0; } int isochron_prepare_receiver(struct isochron_send *prog, struct sk *mgmt_sock) { int rc; rc = isochron_update_l2_enabled(mgmt_sock, prog->l2); if (rc) return rc; rc = isochron_update_l4_enabled(mgmt_sock, prog->l4); if (rc) return rc; return isochron_update_packet_count(mgmt_sock, prog->iterations); } static int prog_prepare_receiver(struct isochron_send *prog) { if (!prog->stats_srv.family) return 0; return isochron_prepare_receiver(prog, prog->mgmt_sock); } int isochron_send_update_session_start_time(struct isochron_send *prog) { struct timespec now_ts; int rc; rc = clock_gettime(prog->clkid, &now_ts); if (rc < 0) { perror("clock_gettime failed"); rc = -errno; return rc; } prog->session_start = timespec_to_ns(&now_ts); prog->oper_base_time = isochron_send_first_base_time(prog); return 0; } static int prog_send_thread_create(struct isochron_send *prog) { int sched_policy = SCHED_OTHER; pthread_attr_t attr; int rc; rc = pthread_attr_init(&attr); if (rc) { pr_err(-rc, "failed to init sender pthread attrs: %m\n"); return rc; } if (prog->sched_fifo) sched_policy = SCHED_FIFO; if (prog->sched_rr) sched_policy = SCHED_RR; if (sched_policy != SCHED_OTHER) { struct sched_param sched_param = { .sched_priority = prog->sched_priority, }; rc = pthread_attr_setschedpolicy(&attr, sched_policy); if (rc) { pr_err(-rc, "failed to set sender pthread sched policy: %m\n"); goto err_destroy_attr; } rc = pthread_attr_setschedparam(&attr, &sched_param); if (rc) { pr_err(-rc, "failed to set sender pthread sched priority: %m\n"); goto err_destroy_attr; } } if (prog->cpumask) { int cpu, num_cpus = sysconf(_SC_NPROCESSORS_ONLN); cpu_set_t cpus; CPU_ZERO(&cpus); for (cpu = 0; cpu < num_cpus; cpu++) if (prog->cpumask & BIT(cpu)) CPU_SET(cpu, &cpus); rc = pthread_attr_setaffinity_np(&attr, sizeof(cpus), &cpus); if (rc) { pr_err(-rc, "failed to set sender pthread cpu affinity: %m\n"); goto err_destroy_attr; } } rc = pthread_create(&prog->send_tid, &attr, prog_send_thread, prog); if (rc) { pr_err(-rc, "failed to create sender pthread: %m\n"); goto err_destroy_attr; } err_destroy_attr: pthread_attr_destroy(&attr); return rc; } static void prog_send_thread_destroy(struct isochron_send *prog) { void *res; int rc; prog->send_tid_should_stop = true; rc = pthread_join(prog->send_tid, &res); if (rc) { pr_err(-rc, "failed to join with sender thread: %m\n"); return; } rc = *((int *)res); if (rc) pr_err(rc, "sender thread failed: %m\n"); } static int prog_tx_timestamp_thread_create(struct isochron_send *prog) { pthread_attr_t attr; int rc; if (!prog->do_ts) return 0; rc = pthread_attr_init(&attr); if (rc) { pr_err(-rc, "failed to init tx timestamp pthread attrs: %m\n"); return rc; } rc = pthread_create(&prog->tx_timestamp_tid, &attr, prog_tx_timestamp_thread, prog); if (rc) { pr_err(-rc, "failed to create tx timestamp pthread: %m\n"); goto err_destroy_attr; } err_destroy_attr: pthread_attr_destroy(&attr); return rc; } static void prog_tx_timestamp_thread_destroy(struct isochron_send *prog) { void *res; int rc; if (!prog->do_ts) return; rc = pthread_join(prog->tx_timestamp_tid, &res); if (rc) { pr_err(-rc, "failed to join with tx timestamp thread: %m\n"); return; } rc = *((int *)res); if (rc) pr_err(rc, "tx timestamp thread failed: %m\n"); } int isochron_send_start_threads(struct isochron_send *prog) { int rc; rc = prog_tx_timestamp_thread_create(prog); if (rc) return rc; rc = prog_send_thread_create(prog); if (rc) { prog_tx_timestamp_thread_destroy(prog); return rc; } return 0; } void isochron_send_stop_threads(struct isochron_send *prog) { prog_send_thread_destroy(prog); prog_tx_timestamp_thread_destroy(prog); } void isochron_send_init_thread_state(struct isochron_send *prog) { prog->timestamped = 0; prog->send_tid_should_stop = false; prog->send_tid_stopped = false; prog->tx_tstamp_tid_stopped = false; } static int prog_prepare_session(struct isochron_send *prog) { int rc; isochron_send_init_thread_state(prog); rc = isochron_log_init(&prog->log, prog->iterations * sizeof(struct isochron_send_pkt_data)); if (rc) return rc; rc = syncmon_wait_until_ok(prog->syncmon); if (rc) { pr_err(rc, "Failed to check sync status: %m\n"); goto out_teardown_log; } rc = prog_prepare_receiver(prog); if (rc) { pr_err(rc, "Failed to prepare receiver for the test: %m\n"); goto out_teardown_log; } rc = isochron_send_update_session_start_time(prog); if (rc) { pr_err(rc, "Failed to update session start time: %m\n"); goto out_teardown_log; } rc = isochron_send_start_threads(prog); if (rc) goto out_teardown_log; return 0; out_teardown_log: isochron_log_teardown(&prog->log); return rc; } static int prog_end_session(struct isochron_send *prog, bool save_log) { struct isochron_log rcv_log; int rc = 0; isochron_send_stop_threads(prog); if (!prog->stats_srv.family && !prog->quiet) isochron_send_log_print(&prog->log); if (!prog->stats_srv.family) goto skip_collecting_rcv_log; printf("Collecting receiver stats\n"); rc = isochron_collect_rcv_log(prog->mgmt_sock, &rcv_log); if (rc) { pr_err(rc, "Failed to collect receiver stats: %m\n"); return rc; } if (save_log && strlen(prog->output_file)) { rc = isochron_log_save(prog->output_file, &prog->log, &rcv_log, prog->iterations, prog->tx_len, prog->omit_sync, prog->do_ts, prog->taprio, prog->txtime, prog->deadline, prog->base_time, prog->advance_time, prog->shift_time, prog->cycle_time, prog->window_size); } isochron_log_teardown(&rcv_log); skip_collecting_rcv_log: isochron_log_teardown(&prog->log); return rc; } int isochron_send_init_ptpmon(struct isochron_send *prog) { char uds_local[UNIX_PATH_MAX]; int retries = 3; int rc; if (prog->omit_sync) return 0; snprintf(uds_local, sizeof(uds_local), "/var/run/isochron.%d", getpid()); prog->ptpmon = ptpmon_create(prog->domain_number, prog->transport_specific, uds_local, prog->uds_remote); if (!prog->ptpmon) return -ENOMEM; rc = ptpmon_open(prog->ptpmon); if (rc) { pr_err(rc, "failed to open ptpmon: %m\n"); goto out_destroy; } while (prog_query_utc_offset(prog) == -ENOENT) { if (signal_received) { rc = -EINTR; goto out_close; } if (!retries--) { fprintf(stderr, "Timed out waiting for ptp4l\n"); rc = -ETIMEDOUT; goto out_close; } printf("waiting for ptp4l\n"); sleep(1); } return 0; out_close: ptpmon_close(prog->ptpmon); out_destroy: ptpmon_destroy(prog->ptpmon); prog->ptpmon = NULL; return rc; } void isochron_send_teardown_ptpmon(struct isochron_send *prog) { if (!prog->ptpmon) return; ptpmon_close(prog->ptpmon); ptpmon_destroy(prog->ptpmon); prog->ptpmon = NULL; } int isochron_send_init_sysmon(struct isochron_send *prog) { if (prog->omit_sync) return 0; prog->sysmon = sysmon_create(prog->if_name, prog->num_readings); if (!prog->sysmon) return -ENOMEM; sysmon_print_method(prog->sysmon); return 0; } void isochron_send_teardown_sysmon(struct isochron_send *prog) { if (!prog->sysmon) return; sysmon_destroy(prog->sysmon); } int isochron_send_init_data_sock(struct isochron_send *prog) { int rc; /* Open socket to send on */ if (prog->l2) rc = sk_bind_l2(prog->dest_mac, prog->etype, prog->if_name, &prog->data_sock); else rc = sk_udp(&prog->ip_destination, prog->data_port, &prog->data_sock); if (rc) goto out; rc = sk_set_priority(prog->data_sock, prog->priority); if (rc) goto out_close; if (is_zero_ether_addr(prog->src_mac)) { rc = sk_get_ether_addr(prog->if_name, prog->src_mac); if (rc) goto out_close; } if (prog->txtime) { rc = sk_enable_txtime(prog->data_sock, prog->deadline); if (rc) goto out_close; } if (prog->do_ts) { rc = sk_validate_ts_info(prog->if_name); if (rc) goto out_close; rc = sk_timestamping_init(prog->data_sock, prog->if_name, true); if (rc < 0) goto out_close; } prog->msg = sk_msg_create(prog->data_sock, prog->sendbuf, prog->tx_len, CMSG_SPACE(sizeof(__s64))); if (!prog->msg) { rc = -ENOMEM; goto out_close; } if (prog->txtime) prog->txtime_cmsg = sk_msg_add_cmsg(prog->msg, SOL_SOCKET, SCM_TXTIME, CMSG_LEN(sizeof(__u64))); return 0; out_close: sk_close(prog->data_sock); out: return rc; } void isochron_send_teardown_data_sock(struct isochron_send *prog) { sk_msg_destroy(prog->msg); sk_close(prog->data_sock); } void isochron_send_init_data_packet(struct isochron_send *prog) { int i; /* Construct the Ethernet header */ memset(prog->sendbuf, 0, BUF_SIZ); /* Ethernet header */ if (prog->do_vlan) { struct vlan_ethhdr *hdr = (struct vlan_ethhdr *)prog->sendbuf; ether_addr_copy(hdr->h_source, prog->src_mac); ether_addr_copy(hdr->h_dest, prog->dest_mac); hdr->h_vlan_proto = __cpu_to_be16(ETH_P_8021Q); /* Ethertype field */ hdr->h_vlan_encapsulated_proto = __cpu_to_be16(prog->etype); hdr->h_vlan_TCI = __cpu_to_be16((prog->priority << VLAN_PRIO_SHIFT) | (prog->vid & VLAN_VID_MASK)); } else { struct ethhdr *hdr = (struct ethhdr *)prog->sendbuf; ether_addr_copy(hdr->h_source, prog->src_mac); ether_addr_copy(hdr->h_dest, prog->dest_mac); hdr->h_proto = __cpu_to_be16(prog->etype); } if (prog->ip_destination.family == AF_INET) prog->l4_header_len = sizeof(struct iphdr) + sizeof(struct udphdr); else if (prog->ip_destination.family == AF_INET6) prog->l4_header_len = sizeof(struct ip6_hdr) + sizeof(struct udphdr); if (prog->l4) prog->tx_len -= sizeof(struct ethhdr) + prog->l4_header_len; i = sizeof(struct isochron_header) + prog->l2_header_len; /* Packet data */ while (i < prog->tx_len) { prog->sendbuf[i++] = 0xde; prog->sendbuf[i++] = 0xad; prog->sendbuf[i++] = 0xbe; prog->sendbuf[i++] = 0xef; } } static int prog_init_trace_mark(struct isochron_send *prog) { int fd; if (!prog->trace_mark) return 0; fd = trace_mark_open(); if (fd < 0) { perror("trace_mark_open"); return -errno; } memset(prog->tracebuf, ' ', TIME_FMT_LEN + 1); prog->tracebuf[0] = '['; prog->tracebuf[TIME_FMT_LEN - 2] = ']'; prog->trace_mark_fd = fd; return 0; } static void prog_teardown_trace_mark(struct isochron_send *prog) { if (!prog->trace_mark) return; trace_mark_close(prog->trace_mark_fd); } static int prog_rtnl_open(struct isochron_send *prog) { struct mnl_socket *nl; nl = mnl_socket_open(NETLINK_ROUTE); if (!nl) { perror("mnl_socket_open"); return -errno; } if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { perror("mnl_socket_bind"); mnl_socket_close(nl); return -errno; } prog->rtnl = nl; return 0; } static void prog_rtnl_close(struct isochron_send *prog) { struct mnl_socket *nl = prog->rtnl; prog->rtnl = NULL; mnl_socket_close(nl); } static int prog_check_admin_state(struct isochron_send *prog) { bool up; int rc; rc = rtnl_query_admin_state(prog->rtnl, prog->if_name, &up); if (rc) { pr_err(rc, "Failed to query port %s admin state: %m\n", prog->if_name); return rc; } if (!up) { fprintf(stderr, "Interface %s is administratively down\n", prog->if_name); return -ENETDOWN; } return 0; } static int prog_init(struct isochron_send *prog) { int rc; rc = prog_rtnl_open(prog); if (rc) goto out; rc = prog_check_admin_state(prog); if (rc) goto out_close_rtnl; rc = prog_init_stats_socket(prog); if (rc) goto out_close_rtnl; rc = prog_query_dest_mac(prog); if (rc) goto out_stats_socket_teardown; rc = isochron_send_init_data_sock(prog); if (rc) goto out_stats_socket_teardown; isochron_send_init_data_packet(prog); rc = prog_init_trace_mark(prog); if (rc) goto out_close_data_sock; /* Prevent the process's virtual memory from being swapped out, by * locking all current and future pages */ rc = mlockall(MCL_CURRENT | MCL_FUTURE); if (rc < 0) { perror("mlockall failed"); goto out_close_trace_mark_fd; } rc = isochron_send_init_ptpmon(prog); if (rc) goto out_munlock; rc = isochron_send_init_sysmon(prog); if (rc) goto out_teardown_ptpmon; rc = prog_init_syncmon(prog); if (rc) goto out_teardown_sysmon; /* Drain potentially old packets from the isochron receiver */ if (prog->stats_srv.family) { struct isochron_log rcv_log; rc = isochron_collect_rcv_log(prog->mgmt_sock, &rcv_log); if (rc) goto out_teardown_syncmon; isochron_log_teardown(&rcv_log); } return rc; out_teardown_syncmon: prog_teardown_syncmon(prog); out_teardown_sysmon: isochron_send_teardown_sysmon(prog); out_teardown_ptpmon: isochron_send_teardown_ptpmon(prog); out_munlock: munlockall(); out_close_trace_mark_fd: prog_teardown_trace_mark(prog); out_close_data_sock: isochron_send_teardown_data_sock(prog); out_stats_socket_teardown: prog_teardown_stats_socket(prog); out_close_rtnl: prog_rtnl_close(prog); out: return rc; } static void prog_teardown(struct isochron_send *prog) { prog_teardown_syncmon(prog); isochron_send_teardown_sysmon(prog); isochron_send_teardown_ptpmon(prog); munlockall(); prog_teardown_trace_mark(prog); isochron_send_teardown_data_sock(prog); prog_teardown_stats_socket(prog); prog_rtnl_close(prog); } void isochron_send_prepare_default_args(struct isochron_send *prog) { prog->clkid = CLOCK_TAI; prog->vid = -1; prog->utc_tai_offset = -1; prog->sync_threshold = -1; prog->num_readings = 5; prog->etype = ETH_P_ISOCHRON; prog->data_port = ISOCHRON_DATA_PORT; prog->stats_port = ISOCHRON_STATS_PORT; sprintf(prog->uds_remote, "/var/run/ptp4l"); } int isochron_send_interpret_args(struct isochron_send *prog) { int rc; /* No point in leaving this one's default to zero, if we know that * means it will always be late for its gate event. So set the implicit * advance time to be one full cycle early, but make sure to avoid * premature transmission by delaying with the window size. I.e. * don't send here: * * base-time - cycle-time base-time * |------|--------------------------|------|--------------------------| * ^<-------------------------------> window-size * | advance-time <----> * | * here * * but here: * * base-time - cycle-time base-time * |------|--------------------------|------|--------------------------| * ^<------------------------> window-size * | advance-time <----> * | * here */ if (!prog->advance_time) prog->advance_time = prog->cycle_time - prog->window_size; if (prog->advance_time > prog->cycle_time) { fprintf(stderr, "Advance time cannot be higher than cycle time\n"); return -EINVAL; } if (prog->shift_time > prog->cycle_time) { fprintf(stderr, "Shift time cannot be higher than cycle time\n"); return -EINVAL; } if (prog->window_size > prog->cycle_time) { fprintf(stderr, "Window size cannot be higher than cycle time\n"); return -EINVAL; } if (prog->txtime && prog->taprio) { fprintf(stderr, "Cannot enable txtime and taprio mode at the same time\n"); return -EINVAL; } if (prog->deadline && !prog->txtime) { fprintf(stderr, "Deadline mode supported only with txtime\n"); return -EINVAL; } if (prog->sched_fifo && prog->sched_rr) { fprintf(stderr, "cannot have SCHED_FIFO and SCHED_RR at the same time\n"); return -EINVAL; } if (prog->tx_len > BUF_SIZ) { fprintf(stderr, "Frame size cannot exceed %d octets\n", BUF_SIZ); return -EINVAL; } if (strlen(prog->output_file) && !prog->stats_srv.family) { fprintf(stderr, "--client is mandatory when --output-file is used\n"); return -EINVAL; } if (prog->l4 && !prog->ip_destination.family) { fprintf(stderr, "--ip-destination is mandatory with --l4\n"); return -EINVAL; } if (!prog->l4 && prog->ip_destination.family) { fprintf(stderr, "--ip-destination provided, but --l4 not specified\n"); return -EINVAL; } if (!strlen(prog->output_file)) sprintf(prog->output_file, "isochron.dat"); if (prog->sync_threshold < 0 && !prog->omit_sync) { fprintf(stderr, "--sync-threshold is mandatory unless --omit-sync is used\n"); return -EINVAL; } if (prog->l2 && prog->l4) { fprintf(stderr, "Choose transport as either L2 or L4!\n"); return -EINVAL; } if (!prog->l2 && !prog->l4) prog->l2 = true; /* If we have a connection to the receiver, we can query it for the * destination MAC for this test */ if (prog->l2 && is_zero_ether_addr(prog->dest_mac) && !prog->stats_srv.family) { fprintf(stderr, "Please specify destination MAC address\n"); return -EINVAL; } if (prog->l2) { if (prog->vid == -1) { prog->do_vlan = false; prog->l2_header_len = sizeof(struct ethhdr); } else { prog->do_vlan = true; prog->l2_header_len = sizeof(struct vlan_ethhdr); } } else if (prog->vid != -1) { fprintf(stderr, "Cannot insert VLAN header over IP socket\n"); return -EINVAL; } if (prog->do_ts && !prog->iterations) { fprintf(stderr, "cannot take timestamps if running indefinitely\n"); return -EINVAL; } if (prog->utc_tai_offset == -1) { /* If we're using the ptpmon, we'll get the UTC offset * from the PTP daemon. */ if (prog->omit_sync) { prog->utc_tai_offset = get_utc_tai_offset(); fprintf(stderr, "Using the kernel UTC-TAI offset which is %ld\n", prog->utc_tai_offset); } } else { rc = set_utc_tai_offset(prog->utc_tai_offset); if (rc == -1) { perror("set_utc_tai_offset"); return -errno; } } return 0; } int isochron_send_parse_args(int argc, char **argv, struct isochron_send *prog) { bool help = false; struct prog_arg args[] = { { .short_opt = "-h", .long_opt = "--help", .type = PROG_ARG_HELP, .help_ptr = { .ptr = &help, }, .optional = true, }, { .short_opt = "-i", .long_opt = "--interface", .type = PROG_ARG_IFNAME, .ifname = { .buf = prog->if_name, .size = IFNAMSIZ - 1, }, }, { .short_opt = "-d", .long_opt = "--dmac", .type = PROG_ARG_MAC_ADDR, .mac = { .buf = prog->dest_mac, }, .optional = true, }, { .short_opt = "-A", .long_opt = "--smac", .type = PROG_ARG_MAC_ADDR, .mac = { .buf = prog->src_mac, }, .optional = true, }, { .short_opt = "-p", .long_opt = "--priority", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->priority, }, .optional = true, }, { .short_opt = "-P", .long_opt = "--stats-port", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->stats_port, }, .optional = true, }, { .short_opt = "-b", .long_opt = "--base-time", .type = PROG_ARG_TIME, .time = { .clkid = CLOCK_TAI, .ns = &prog->base_time, }, .optional = true, }, { .short_opt = "-a", .long_opt = "--advance-time", .type = PROG_ARG_TIME, .time = { .clkid = CLOCK_TAI, .ns = &prog->advance_time, }, .optional = true, }, { .short_opt = "-S", .long_opt = "--shift-time", .type = PROG_ARG_TIME, .time = { .clkid = CLOCK_TAI, .ns = &prog->shift_time, }, .optional = true, }, { .short_opt = "-c", .long_opt = "--cycle-time", .type = PROG_ARG_TIME, .time = { .clkid = CLOCK_TAI, .ns = &prog->cycle_time, }, }, { .short_opt = "-w", .long_opt = "--window-size", .type = PROG_ARG_TIME, .time = { .clkid = CLOCK_TAI, .ns = &prog->window_size, }, .optional = true, }, { .short_opt = "-n", .long_opt = "--num-frames", .type = PROG_ARG_UNSIGNED, .unsigned_ptr = { .ptr = &prog->iterations, }, .optional = true, }, { .short_opt = "-s", .long_opt = "--frame-size", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->tx_len, }, }, { .short_opt = "-T", .long_opt = "--no-ts", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->do_ts, }, .optional = true, }, { .short_opt = "-v", .long_opt = "--vid", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->vid, }, .optional = true, }, { .short_opt = "-C", .long_opt = "--client", .type = PROG_ARG_IP, .ip_ptr = { .ptr = &prog->stats_srv, }, .optional = true, }, { .short_opt = "-q", .long_opt = "--quiet", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->quiet, }, .optional = true, }, { .short_opt = "-e", .long_opt = "--etype", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->etype, }, .optional = true, }, { .short_opt = "-o", .long_opt = "--omit-sync", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->omit_sync, }, .optional = true, }, { .short_opt = "-y", .long_opt = "--omit-remote-sync", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->omit_remote_sync, }, .optional = true, }, { .short_opt = "-m", .long_opt = "--tracemark", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->trace_mark, }, .optional = true, }, { .short_opt = "-Q", .long_opt = "--taprio", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->taprio, }, .optional = true, }, { .short_opt = "-x", .long_opt = "--txtime", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->txtime, }, .optional = true, }, { .short_opt = "-D", .long_opt = "--deadline", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->deadline, }, .optional = true, }, { .short_opt = "-H", .long_opt = "--sched-priority", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->sched_priority, }, .optional = true, }, { .short_opt = "-f", .long_opt = "--sched-fifo", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->sched_fifo, }, .optional = true, }, { .short_opt = "-r", .long_opt = "--sched-rr", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->sched_rr, }, .optional = true, }, { .short_opt = "-O", .long_opt = "--utc-tai-offset", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->utc_tai_offset, }, .optional = true, }, { .short_opt = "-J", .long_opt = "--ip-destination", .type = PROG_ARG_IP, .ip_ptr = { .ptr = &prog->ip_destination, }, .optional = true, }, { .short_opt = "-2", .long_opt = "--l2", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->l2, }, .optional = true, }, { .short_opt = "-4", .long_opt = "--l4", .type = PROG_ARG_BOOL, .boolean_ptr = { .ptr = &prog->l4, }, .optional = true, }, { .short_opt = "-W", .long_opt = "--data-port", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->data_port, }, .optional = true, }, { .short_opt = "-N", .long_opt = "--domain-number", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->domain_number, }, .optional = true, }, { .short_opt = "-t", .long_opt = "--transport-specific", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->transport_specific, }, .optional = true, }, { .short_opt = "-U", .long_opt = "--unix-domain-socket", .type = PROG_ARG_FILEPATH, .filepath = { .buf = prog->uds_remote, .size = UNIX_PATH_MAX - 1, }, .optional = true, }, { .short_opt = "-X", .long_opt = "--sync-threshold", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->sync_threshold, }, .optional = true, }, { .short_opt = "-R", .long_opt = "--num-readings", .type = PROG_ARG_LONG, .long_ptr = { .ptr = &prog->num_readings, }, .optional = true, }, { .short_opt = "-F", .long_opt = "--output-file", .type = PROG_ARG_FILEPATH, .filepath = { .buf = prog->output_file, .size = PATH_MAX - 1, }, .optional = true, }, { .short_opt = "-M", .long_opt = "--cpu-mask", .type = PROG_ARG_UNSIGNED, .unsigned_ptr = { .ptr = &prog->cpumask, }, .optional = true, }, }; int rc; isochron_send_prepare_default_args(prog); rc = prog_parse_np_args(argc, argv, args, ARRAY_SIZE(args)); /* Non-positional arguments left unconsumed */ if (rc < 0) { pr_err(rc, "argument parsing failed: %m\n"); return rc; } else if (rc < argc) { fprintf(stderr, "%d unconsumed arguments. First: %s\n", argc - rc, argv[rc]); prog_usage("isochron-send", args, ARRAY_SIZE(args)); return -1; } if (help) { prog_usage("isochron-send", args, ARRAY_SIZE(args)); return -1; } /* Convert negative logic from cmdline to positive */ prog->do_ts = !prog->do_ts; return isochron_send_interpret_args(prog); } static bool prog_stop_syncmon(void *priv) { struct isochron_send *prog = priv; return prog->send_tid_stopped; } int isochron_send_main(int argc, char *argv[]) { struct isochron_send prog = {0}; bool sync_ok; int rc; rc = isochron_send_parse_args(argc, argv, &prog); if (rc < 0) return rc; do { rc = prog_init(&prog); if (rc) return rc; rc = prog_prepare_session(&prog); if (rc) break; sync_ok = syncmon_monitor(prog.syncmon, prog_stop_syncmon, &prog); rc = prog_end_session(&prog, sync_ok); if (rc) break; prog_teardown(&prog); } while (!sync_ok); if (rc) prog_teardown(&prog); return rc; } isochron-0.9/src/send.h000066400000000000000000000056211431034026500151010ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2022 NXP */ #ifndef _ISOCHRON_SEND_H #define _ISOCHRON_SEND_H #include #include #include #include #include "log.h" #include "ptpmon.h" #include "syncmon.h" #include "sysmon.h" #define BUF_SIZ 10000 struct isochron_send { volatile bool send_tid_should_stop; volatile bool send_tid_stopped; volatile bool tx_tstamp_tid_stopped; unsigned char dest_mac[ETH_ALEN]; unsigned char src_mac[ETH_ALEN]; char if_name[IFNAMSIZ]; char uds_remote[UNIX_PATH_MAX]; __u8 sendbuf[BUF_SIZ]; struct ptpmon *ptpmon; struct sysmon *sysmon; struct mnl_socket *rtnl; enum port_link_state link_state; enum port_state last_local_port_state; enum port_state last_remote_port_state; struct cmsghdr *txtime_cmsg; struct sk_msg *msg; struct sk_addr *sa; struct ip_address stats_srv; struct isochron_log log; unsigned long timestamped; unsigned long iterations; clockid_t clkid; __s64 session_start; __s64 advance_time; __s64 shift_time; __s64 cycle_time; __s64 base_time; __s64 oper_base_time; __s64 window_size; long priority; long tx_len; struct sk *data_sock; struct sk *mgmt_sock; long vid; bool do_ts; bool quiet; long etype; bool omit_sync; bool omit_remote_sync; bool trace_mark; int trace_mark_fd; char tracebuf[BUF_SIZ]; long stats_port; bool taprio; bool txtime; bool deadline; bool do_vlan; int l2_header_len; int l4_header_len; bool sched_fifo; bool sched_rr; long sched_priority; long utc_tai_offset; struct ip_address ip_destination; bool l2; bool l4; long data_port; long domain_number; long transport_specific; long sync_threshold; long num_readings; char output_file[PATH_MAX]; pthread_t send_tid; pthread_t tx_timestamp_tid; int send_tid_rc; int tx_timestamp_tid_rc; unsigned long cpumask; struct syncmon *syncmon; }; int isochron_send_parse_args(int argc, char **argv, struct isochron_send *prog); void isochron_send_prepare_default_args(struct isochron_send *prog); int isochron_send_interpret_args(struct isochron_send *prog); void isochron_send_init_thread_state(struct isochron_send *prog); void isochron_send_init_data_packet(struct isochron_send *prog); int isochron_send_init_data_sock(struct isochron_send *prog); void isochron_send_teardown_data_sock(struct isochron_send *prog); int isochron_send_init_sysmon(struct isochron_send *prog); int isochron_send_init_ptpmon(struct isochron_send *prog); void isochron_send_teardown_sysmon(struct isochron_send *prog); void isochron_send_teardown_ptpmon(struct isochron_send *prog); int isochron_send_update_session_start_time(struct isochron_send *prog); int isochron_send_start_threads(struct isochron_send *prog); void isochron_send_stop_threads(struct isochron_send *prog); int isochron_prepare_receiver(struct isochron_send *prog, struct sk *mgmt_sock); __s64 isochron_send_first_base_time(struct isochron_send *prog); #endif isochron-0.9/src/sk.c000066400000000000000000000526061431034026500145650ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2022 NXP */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "sk.h" struct sk_addr { union { struct sockaddr_ll l2; struct sockaddr_in udp4; struct sockaddr_in6 udp6; } u; size_t sockaddr_size; }; struct sk_msg { struct iovec iov; struct msghdr msghdr; struct cmsghdr *last_cmsg; char *msg_control; }; struct sk { int family; int fd; struct sk_addr *sa; bool closed; }; static int __sk_bind_ipv4(int fd, const struct in_addr *a, int port) { struct sockaddr_in s = { .sin_family = AF_INET, .sin_port = htons(port), }; memcpy(&s.sin_addr, a, sizeof(*a)); return bind(fd, (struct sockaddr *)&s, sizeof(s)); } static int sk_bind_ipv4_any(int fd, int port) { struct in_addr a = { .s_addr = htonl(INADDR_ANY), }; return __sk_bind_ipv4(fd, &a, port); } static int sk_bind_ipv4(int fd, const struct ip_address *ip, int port) { if (ip->family != AF_INET) return sk_bind_ipv4_any(fd, port); return __sk_bind_ipv4(fd, &ip->addr, port); } static int __sk_bind_ipv6(int fd, const struct in6_addr *a, int port) { struct sockaddr_in6 s = { .sin6_family = AF_INET6, .sin6_port = htons(port), }; memcpy(&s.sin6_addr, a, sizeof(*a)); return bind(fd, (struct sockaddr *)&s, sizeof(s)); } static int sk_bind_ipv6_any(int fd, int port) { struct in6_addr a = in6addr_any; return __sk_bind_ipv6(fd, &a, port); } static int sk_bind_ipv6(int fd, const struct ip_address *ip, int port) { if (ip->family != AF_INET6) return sk_bind_ipv6_any(fd, port); return __sk_bind_ipv6(fd, &ip->addr6, port); } int sk_listen_tcp(const struct ip_address *ip, int port, int backlog, struct sk **listen_sock) { bool ipv4_fallback = false; int sockopt = 1; int fd, rc; *listen_sock = calloc(1, sizeof(struct sk)); if (!(*listen_sock)) return -ENOMEM; fd = socket(PF_INET6, SOCK_STREAM, 0); if (fd < 0) { /* Linux kernel is dual stack and allows IPv4-mapped IPv6 * addresses, but it may be compiled with CONFIG_IPV6=n, case * in which we need to explicitly fall back to PF_INET sockets. */ fd = socket(PF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("Failed to create IPv6 or IPv4 socket"); goto out; } ipv4_fallback = true; } /* Allow the socket to be reused, in case the connection * is closed prematurely */ rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(int)); if (rc < 0) { perror("Failed to setsockopt(SO_REUSEADDR)"); goto out_close; } if (strlen(ip->bound_if_name)) { rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ip->bound_if_name, IFNAMSIZ - 1); if (rc < 0) { fprintf(stderr, "Failed to bind socket to device %s: %m\n", ip->bound_if_name); goto out_close; } } if (ipv4_fallback) rc = sk_bind_ipv4(fd, ip, port); else rc = sk_bind_ipv6(fd, ip, port); if (rc < 0) { fprintf(stderr, "Failed to bind to TCP port %d: %m\n", port); goto out_close; } rc = listen(fd, backlog); if (rc < 0) { fprintf(stderr, "Failed to listen on TCP port %d: %m\n", port); goto out_close; } (*listen_sock)->fd = fd; (*listen_sock)->family = ipv4_fallback ? PF_INET : PF_INET6; return 0; out_close: close(fd); out: free(*listen_sock); *listen_sock = NULL; return -errno; } static int sk_accept_ipv6(const struct sk *listen_sock, struct sk *sock, char *client_addr) { socklen_t addr_len = sizeof(struct sockaddr_in6); struct sockaddr_in6 sa; int fd; fd = accept(listen_sock->fd, (struct sockaddr *)&sa, &addr_len); if (fd < 0) { if (errno != EINTR) perror("Failed to accept connection from socket"); return -errno; } if (!inet_ntop(AF_INET6, &sa.sin6_addr, client_addr, INET6_ADDRSTRLEN)) { perror("Failed to convert IPv6 address to text"); close(fd); return -errno; } sock->family = PF_INET6; sock->fd = fd; return 0; } static int sk_accept_ipv4(const struct sk *listen_sock, struct sk *sock, char *client_addr) { socklen_t addr_len = sizeof(struct sockaddr_in); struct sockaddr_in sa; int fd; fd = accept(listen_sock->fd, (struct sockaddr *)&sa, &addr_len); if (fd < 0) { if (errno != EINTR) perror("Failed to accept connection from socket"); return -errno; } if (!inet_ntop(AF_INET, &sa.sin_addr.s_addr, client_addr, INET_ADDRSTRLEN)) { perror("Failed to convert IPv4 address to text"); close(fd); return -errno; } sock->family = PF_INET6; sock->fd = fd; return 0; } int sk_accept(const struct sk *listen_sock, struct sk **sock) { char client_addr[INET6_ADDRSTRLEN]; int rc; if (listen_sock->family != PF_INET && listen_sock->family != PF_INET6) return -EINVAL; *sock = calloc(1, sizeof(struct sk)); if (!(*sock)) return -ENOMEM; if (listen_sock->family == PF_INET6) rc = sk_accept_ipv6(listen_sock, *sock, client_addr); else rc = sk_accept_ipv4(listen_sock, *sock, client_addr); if (rc) { free(*sock); return rc; } printf("Accepted connection from %s\n", client_addr); return 0; } int sk_connect_tcp(const struct ip_address *ip, int port, struct sk **sock) { struct sockaddr_in6 sa6; struct sockaddr_in sa4; struct sockaddr *sa; int fd, size, rc; if (ip->family == AF_INET) { sa = (struct sockaddr *)&sa4; sa4.sin_addr = ip->addr; sa4.sin_port = htons(port); sa4.sin_family = AF_INET; size = sizeof(struct sockaddr_in); } else if (ip->family == AF_INET6) { sa = (struct sockaddr *)&sa6; sa6.sin6_addr = ip->addr6; sa6.sin6_port = htons(port); sa6.sin6_family = AF_INET6; size = sizeof(struct sockaddr_in6); } else { return -EINVAL; } *sock = calloc(1, sizeof(struct sk)); if (!(*sock)) return -ENOMEM; fd = socket(ip->family, SOCK_STREAM, 0); if (fd < 0) { perror("Failed to create TCP socket"); goto err; } if (strlen(ip->bound_if_name)) { rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ip->bound_if_name, IFNAMSIZ - 1); if (rc < 0) { fprintf(stderr, "Failed to bind TCP socket to device %s: %m\n", ip->bound_if_name); goto err_close; } } rc = connect(fd, sa, size); if (rc < 0) { fprintf(stderr, "Failed to connect to TCP port %d: %m\n", port); goto err_close; } (*sock)->fd = fd; (*sock)->family = ip->family; return 0; err_close: close(fd); err: free(*sock); *sock = NULL; return -errno; } static void sk_addr_destroy(struct sk_addr *sa) { free(sa); } void sk_close(struct sk *sock) { if (sock->sa) sk_addr_destroy(sock->sa); close(sock->fd); free(sock); } int sk_recv(struct sk *sock, void *buf, size_t len, int flags) { size_t received = 0; ssize_t ret; do { ret = recv(sock->fd, buf + received, len - received, flags); if (ret <= 0) { sock->closed = ret == 0; return ret ? -errno : -ECONNRESET; } received += ret; } while (received != len); return 0; } int sk_send(struct sk *sock, const void *buf, size_t count) { size_t sent = 0; ssize_t ret; do { ret = send(sock->fd, buf + sent, count - sent, 0); if (ret <= 0) { sock->closed = ret == 0; return ret ? -errno : -ECONNRESET; } sent += ret; } while (sent != count); return 0; } int sk_fd(const struct sk *sock) { return sock->fd; } bool sk_closed(const struct sk *sock) { return sock->closed; } static struct sk_addr *sk_addr_create_l2(const unsigned char addr[ETH_ALEN], __u16 ethertype, const char *if_name) { int ifindex = if_nametoindex(if_name); struct sk_addr *sa; if (!ifindex) { fprintf(stderr, "Could not determine ifindex of %s\n", if_name); return NULL; } sa = calloc(1, sizeof(struct sk_addr)); if (!sa) return NULL; sa->u.l2.sll_protocol = __cpu_to_be16(ethertype); sa->u.l2.sll_ifindex = ifindex; sa->u.l2.sll_halen = ETH_ALEN; sa->u.l2.sll_family = AF_PACKET; ether_addr_copy(sa->u.l2.sll_addr, addr); sa->sockaddr_size = sizeof(struct sockaddr_ll); return sa; } static struct sk_addr *sk_addr_create_udp(const struct ip_address *ip, int port) { struct sk_addr *sa; sa = calloc(1, sizeof(struct sk_addr)); if (!sa) return NULL; if (ip->family == AF_INET) { sa->u.udp4.sin_addr = ip->addr; sa->u.udp4.sin_port = htons(port); sa->u.udp4.sin_family = AF_INET; sa->sockaddr_size = sizeof(struct sockaddr_in); } else { sa->u.udp6.sin6_addr = ip->addr6; sa->u.udp6.sin6_port = htons(port); sa->u.udp6.sin6_family = AF_INET6; sa->sockaddr_size = sizeof(struct sockaddr_in6); } return sa; } struct sk_msg *sk_msg_create(const struct sk *sock, void *buf, size_t len, size_t cmsg_len) { struct sk_addr *sa = sock->sa; struct sk_msg *msg; if (!sa) return NULL; msg = calloc(1, sizeof(struct sk_msg)); if (!msg) return NULL; if (cmsg_len) { msg->msg_control = calloc(1, cmsg_len); if (!msg->msg_control) { free(msg); return NULL; } } msg->iov.iov_base = buf; msg->iov.iov_len = len; msg->msghdr.msg_name = (struct sockaddr *)&sa->u; msg->msghdr.msg_namelen = sa->sockaddr_size; msg->msghdr.msg_iov = &msg->iov; msg->msghdr.msg_iovlen = 1; msg->msghdr.msg_control = msg->msg_control; return msg; } void sk_msg_destroy(struct sk_msg *msg) { if (msg->msg_control) free(msg->msg_control); free(msg); } struct cmsghdr *sk_msg_add_cmsg(struct sk_msg *msg, int level, int type, size_t len) { struct cmsghdr *cmsg; msg->msghdr.msg_controllen += len; if (msg->last_cmsg) cmsg = CMSG_NXTHDR(&msg->msghdr, msg->last_cmsg); else cmsg = CMSG_FIRSTHDR(&msg->msghdr); cmsg->cmsg_level = level; cmsg->cmsg_type = type; cmsg->cmsg_len = len; msg->last_cmsg = cmsg; return cmsg; } int sk_sendmsg(struct sk *sock, const struct sk_msg *msg, int flags) { return sendmsg(sock->fd, &msg->msghdr, flags); } int sk_bind_l2(const unsigned char addr[ETH_ALEN], __u16 ethertype, const char *if_name, struct sk **sock) { struct sk_addr *sa; int fd, rc; *sock = calloc(1, sizeof(struct sk)); if (!(*sock)) return -ENOMEM; sa = sk_addr_create_l2(addr, ethertype, if_name); if (!sa) goto out_free_sock; fd = socket(PF_PACKET, SOCK_RAW, htons(ethertype)); if (fd < 0) { perror("Failed to create PF_PACKET socket"); goto out_free_sa; } /* Bind to device */ rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, if_name, IFNAMSIZ - 1); if (rc < 0) { fprintf(stderr, "Failed to bind L2 socket to device %s: %m", if_name); goto out_close; } rc = bind(fd, (struct sockaddr *)&sa->u.l2, sa->sockaddr_size); if (rc) goto out_close; (*sock)->fd = fd; (*sock)->family = PF_PACKET; (*sock)->sa = sa; return 0; out_close: close(fd); out_free_sa: free(sa); out_free_sock: free(*sock); *sock = NULL; return -errno; } int sk_udp(const struct ip_address *dest, int port, struct sk **sock) { bool ipv4_fallback = false; struct sk_addr *sa; int fd, rc; *sock = calloc(1, sizeof(struct sk)); if (!(*sock)) return -ENOMEM; sa = sk_addr_create_udp(dest, port); if (!sa) { errno = -ENOMEM; goto out_free_sock; } if (dest->family) { fd = socket(dest->family, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) { perror("Failed to create UDP socket"); goto out_free_sa; } } else { fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) { fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) { perror("Failed to create IPv6 or IPv4 UDP socket"); goto out_free_sa; } ipv4_fallback = true; } } if (strlen(dest->bound_if_name)) { rc = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, dest->bound_if_name, IFNAMSIZ - 1); if (rc < 0) { fprintf(stderr, "Failed to bind socket to device %s: %m\n", dest->bound_if_name); goto out_close; } } (*sock)->fd = fd; (*sock)->family = dest->family ? : ipv4_fallback ? PF_INET : PF_INET6; (*sock)->sa = sa; return 0; out_close: close(fd); out_free_sa: free(sa); out_free_sock: free(*sock); *sock = NULL; return -errno; } int sk_bind_udp(const struct ip_address *dest, int port, struct sk **sock) { int rc; rc = sk_udp(dest, port, sock); if (rc) return rc; if ((*sock)->family == AF_INET) rc = sk_bind_ipv4((*sock)->fd, dest, port); else rc = sk_bind_ipv6((*sock)->fd, dest, port); if (rc) { fprintf(stderr, "Failed to bind to UDP port %d: %m\n", port); sk_close(*sock); *sock = NULL; } return rc; } static void init_ifreq(struct ifreq *ifreq, struct hwtstamp_config *cfg, const char if_name[IFNAMSIZ]) { memset(ifreq, 0, sizeof(*ifreq)); memset(cfg, 0, sizeof(*cfg)); strcpy(ifreq->ifr_name, if_name); ifreq->ifr_data = (void *) cfg; } static int hwts_init(int fd, const char if_name[IFNAMSIZ], int rx_filter, int tx_type) { struct hwtstamp_config cfg; struct ifreq ifreq; int rc; init_ifreq(&ifreq, &cfg, if_name); cfg.tx_type = tx_type; cfg.rx_filter = rx_filter; rc = ioctl(fd, SIOCSHWTSTAMP, &ifreq); if (rc < 0) { perror("ioctl SIOCSHWTSTAMP failed"); return -errno; } if (cfg.tx_type != tx_type) fprintf(stderr, "tx_type %d not %d\n", cfg.tx_type, tx_type); if (cfg.rx_filter != rx_filter) fprintf(stderr, "rx_filter %d not %d\n", cfg.rx_filter, rx_filter); if (cfg.tx_type != tx_type || cfg.rx_filter != rx_filter) fprintf(stderr, "The current filter does not match the required\n"); return 0; } int sk_timestamping_init(struct sk *sock, const char if_name[IFNAMSIZ], bool on) { int rc, filter, flags, tx_type; int fd = sock->fd; if (strlen(if_name) >= IFNAMSIZ) { fprintf(stderr, "Interface name %s too long\n", if_name); return -EINVAL; } flags = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_TX_SCHED | SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RAW_HARDWARE | SOF_TIMESTAMPING_OPT_TX_SWHW | SOF_TIMESTAMPING_OPT_ID; filter = HWTSTAMP_FILTER_ALL; if (on) tx_type = HWTSTAMP_TX_ON; else tx_type = HWTSTAMP_TX_OFF; rc = hwts_init(fd, if_name, filter, tx_type); if (rc) return rc; rc = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)); if (rc < 0) { perror("ioctl SO_TIMESTAMPING failed"); return -1; } flags = 1; rc = setsockopt(fd, SOL_SOCKET, SO_SELECT_ERR_QUEUE, &flags, sizeof(flags)); if (rc < 0) { perror("SO_SELECT_ERR_QUEUE failed"); return rc; } return 0; } int sk_recvmsg(struct sk *sock, void *buf, int buflen, struct isochron_timestamp *tstamp, int flags, int timeout) { struct iovec iov = { buf, buflen }; struct timespec *ts; struct cmsghdr *cm; struct msghdr msg; char control[256]; int fd = sock->fd; ssize_t len; int rc = 0; memset(control, 0, sizeof(control)); memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = sizeof(control); if (flags == MSG_ERRQUEUE) { struct pollfd pfd = { fd, POLLPRI, 0 }; rc = poll(&pfd, 1, timeout); if (rc == 0) { return 0; } else if (rc < 0) { perror("poll for tx timestamp failed"); return rc; } else if (!(pfd.revents & POLLPRI)) { fprintf(stderr, "poll woke up on non ERR event\n"); return -1; } /* On success a positive number is returned */ } len = recvmsg(fd, &msg, flags); /* Suppress "Interrupted system call" message */ if (len < 1 && errno != EINTR) perror("recvmsg failed"); for (cm = CMSG_FIRSTHDR(&msg); cm != NULL; cm = CMSG_NXTHDR(&msg, cm)) { int level = cm->cmsg_level; int type = cm->cmsg_type; if (level == SOL_SOCKET && type == SCM_TIMESTAMPING) { struct scm_timestamping *tss; if (cm->cmsg_len < sizeof(*ts) * 3) { fprintf(stderr, "short SO_TIMESTAMPING message\n"); return -1; } tss = (struct scm_timestamping *)CMSG_DATA(cm); if (tstamp) { tstamp->sw = tss->ts[0]; tstamp->hw = tss->ts[2]; } } else if ((level == SOL_PACKET && type == PACKET_TX_TIMESTAMP) || (level == SOL_IP && type == IP_RECVERR) || (level == SOL_IPV6 && type == IPV6_RECVERR)) { struct sock_extended_err *sock_err; char txtime_buf[TIMESPEC_BUFSIZ]; __u64 txtime; sock_err = (struct sock_extended_err *)CMSG_DATA(cm); if (!sock_err) continue; switch (sock_err->ee_origin) { case SO_EE_ORIGIN_TIMESTAMPING: if (!tstamp) break; tstamp->tskey = sock_err->ee_data; tstamp->tstype = sock_err->ee_info; break; case SO_EE_ORIGIN_TXTIME: txtime = ((__u64)sock_err->ee_data << 32) + sock_err->ee_info; if (tstamp) tstamp->txtime = ns_to_timespec(txtime); ns_sprintf(txtime_buf, txtime); switch (sock_err->ee_code) { case SO_EE_CODE_TXTIME_INVALID_PARAM: fprintf(stderr, "packet with txtime %s dropped due to invalid params\n", txtime_buf); break; case SO_EE_CODE_TXTIME_MISSED: fprintf(stderr, "packet with txtime %s dropped due to missed deadline\n", txtime_buf); break; default: return -1; } break; default: pr_err(-sock_err->ee_errno, "unknown socket error %d, origin %d code %d: %m\n", sock_err->ee_errno, sock_err->ee_origin, sock_err->ee_code); break; } } else { fprintf(stderr, "unknown cmsg level %d type %d\n", level, type); } } return len; } int sk_set_priority(const struct sk *sock, int priority) { if (setsockopt(sock->fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(int))) { perror("setsockopt(SO_PRIORITY) failed"); return -errno; } return 0; } int sk_enable_txtime(const struct sk *sock, bool deadline) { static struct sock_txtime sk_txtime = { .clockid = CLOCK_TAI, .flags = SOF_TXTIME_REPORT_ERRORS, }; if (deadline) sk_txtime.flags |= SOF_TXTIME_DEADLINE_MODE; if (setsockopt(sock->fd, SOL_SOCKET, SO_TXTIME, &sk_txtime, sizeof(sk_txtime))) { perror("setsockopt(SO_TXTIME) failed"); return -errno; } return 0; } /* Borrowed from raw_configure in linuxptp */ int sk_multicast_listen(const struct sk *sock, unsigned int if_index, unsigned char *macaddr, bool enable) { struct packet_mreq mreq; int rc, option; if (enable) option = PACKET_ADD_MEMBERSHIP; else option = PACKET_DROP_MEMBERSHIP; memset(&mreq, 0, sizeof(mreq)); mreq.mr_ifindex = if_index; mreq.mr_type = PACKET_MR_MULTICAST; mreq.mr_alen = ETH_ALEN; ether_addr_copy(mreq.mr_address, macaddr); rc = setsockopt(sock->fd, SOL_PACKET, option, &mreq, sizeof(mreq)); if (!rc) return 0; perror("setsockopt PACKET_MR_MULTICAST failed"); mreq.mr_ifindex = if_index; mreq.mr_type = PACKET_MR_ALLMULTI; mreq.mr_alen = 0; rc = setsockopt(sock->fd, SOL_PACKET, option, &mreq, sizeof(mreq)); if (!rc) return 0; perror("setsockopt PACKET_MR_ALLMULTI failed"); mreq.mr_ifindex = if_index; mreq.mr_type = PACKET_MR_PROMISC; mreq.mr_alen = 0; rc = setsockopt(sock->fd, SOL_PACKET, option, &mreq, sizeof(mreq)); if (!rc) return 0; perror("setsockopt PACKET_MR_PROMISC failed"); fprintf(stderr, "all socket options failed\n"); return -1; } int sk_get_ts_info(const char name[IFNAMSIZ], struct sk_ts_info *sk_info) { struct ethtool_ts_info info; struct ifreq ifr; int fd, err; if (strlen(name) >= IFNAMSIZ) { fprintf(stderr, "Interface name %s too long\n", name); return -EINVAL; } memset(sk_info, 0, sizeof(struct sk_ts_info)); memset(&ifr, 0, sizeof(ifr)); memset(&info, 0, sizeof(info)); info.cmd = ETHTOOL_GET_TS_INFO; strcpy(ifr.ifr_name, name); ifr.ifr_data = (char *) &info; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { fprintf(stderr, "socket failed: %m\n"); return -errno; } err = ioctl(fd, SIOCETHTOOL, &ifr); close(fd); if (err < 0) { fprintf(stderr, "ioctl SIOCETHTOOL failed: %m\n"); return -errno; } /* copy the necessary data to sk_info */ sk_info->valid = true; sk_info->phc_index = info.phc_index; sk_info->so_timestamping = info.so_timestamping; sk_info->tx_types = info.tx_types; sk_info->rx_filters = info.rx_filters; return 0; } int sk_validate_ts_info(const char if_name[IFNAMSIZ]) { struct sk_ts_info ts_info; int rc; /* check if device is a valid ethernet device */ rc = sk_get_ts_info(if_name, &ts_info); if (rc) return rc; if (!ts_info.valid) return -EINVAL; if (!(ts_info.so_timestamping & SOF_TIMESTAMPING_TX_HARDWARE)) { fprintf(stderr, "Driver not capable of SOF_TIMESTAMPING_TX_HARDWARE, continuing anyway\n"); } if (!(ts_info.so_timestamping & SOF_TIMESTAMPING_RX_HARDWARE)) { fprintf(stderr, "Driver not capable of SOF_TIMESTAMPING_RX_HARDWARE, continuing anyway\n"); } if (!(ts_info.so_timestamping & SOF_TIMESTAMPING_TX_SOFTWARE)) { fprintf(stderr, "Driver not capable of SOF_TIMESTAMPING_TX_SOFTWARE, continuing anyway\n"); } if (!(ts_info.so_timestamping & SOF_TIMESTAMPING_RX_SOFTWARE)) { fprintf(stderr, "Driver not capable of SOF_TIMESTAMPING_RX_SOFTWARE, continuing anyway\n"); } if (!(ts_info.so_timestamping & SOF_TIMESTAMPING_SOFTWARE)) { fprintf(stderr, "Driver not capable of SOF_TIMESTAMPING_SOFTWARE, continuing anyway\n"); } return 0; } int sk_get_ether_addr(const char if_name[IFNAMSIZ], unsigned char *addr) { struct ifreq if_mac; int fd, rc; if (strlen(if_name) >= IFNAMSIZ) { fprintf(stderr, "Interface name %s too long\n", if_name); return -EINVAL; } fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("Failed to open socket"); return -errno; } memset(&if_mac, 0, sizeof(struct ifreq)); strcpy(if_mac.ifr_name, if_name); rc = ioctl(fd, SIOCGIFHWADDR, &if_mac); close(fd); if (rc < 0) { perror("SIOCGIFHWADDR"); return -errno; } ether_addr_copy(addr, (unsigned char *)if_mac.ifr_hwaddr.sa_data); return 0; } isochron-0.9/src/sk.h000066400000000000000000000054071431034026500145670ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2022 NXP */ #ifndef _ISOCHRON_SK_H #define _ISOCHRON_SK_H #include #include "argparser.h" struct isochron_timestamp { struct timespec hw; struct timespec sw; struct timespec sched; struct timespec txtime; __u32 tskey; __u32 tstype; }; /** * Contains timestamping information returned by the GET_TS_INFO ioctl. * @valid: set to non-zero when the info struct contains valid data. * @phc_index: index of the PHC device. * @so_timestamping: supported time stamping modes. * @tx_types: driver level transmit options for the HWTSTAMP ioctl. * @rx_filters: driver level receive options for the HWTSTAMP ioctl. */ struct sk_ts_info { bool valid; int phc_index; unsigned int so_timestamping; unsigned int tx_types; unsigned int rx_filters; }; struct sk; struct sk_msg; /* Connection-oriented */ int sk_listen_tcp(const struct ip_address *ip, int port, int backlog, struct sk **listen_sock); int sk_accept(const struct sk *listen_sock, struct sk **sock); int sk_connect_tcp(const struct ip_address *ip, int port, struct sk **sock); int sk_recv(struct sk *sock, void *buf, size_t len, int flags); int sk_send(struct sk *sock, const void *buf, size_t count); bool sk_closed(const struct sk *sock); /* Connection-less */ int sk_udp(const struct ip_address *dest, int port, struct sk **sock); int sk_bind_udp(const struct ip_address *dest, int port, struct sk **sock); int sk_bind_l2(const unsigned char addr[ETH_ALEN], __u16 ethertype, const char *if_name, struct sk **sock); struct sk_msg *sk_msg_create(const struct sk *sock, void *buf, size_t len, size_t cmsg_len); void sk_msg_destroy(struct sk_msg *msg); struct cmsghdr *sk_msg_add_cmsg(struct sk_msg *msg, int level, int type, size_t len); int sk_sendmsg(struct sk *sock, const struct sk_msg *msg, int flags); int sk_recvmsg(struct sk *sock, void *buf, int buflen, struct isochron_timestamp *tstamp, int flags, int timeout); int sk_timestamping_init(struct sk *sock, const char if_name[IFNAMSIZ], bool on); int sk_set_priority(const struct sk *sock, int priority); int sk_enable_txtime(const struct sk *sock, bool deadline); int sk_multicast_listen(const struct sk *sock, unsigned int if_index, unsigned char *macaddr, bool enable); /* Common */ void sk_close(struct sk *sock); int sk_fd(const struct sk *sock); void sk_err(const struct sk *sock, int rc, const char *fmt, ...); /* Others */ int sk_get_ts_info(const char name[IFNAMSIZ], struct sk_ts_info *sk_info); int sk_validate_ts_info(const char if_name[IFNAMSIZ]); int sk_get_ether_addr(const char if_name[IFNAMSIZ], unsigned char *addr); #define sk_err(sock, rc, ...) \ do { \ if (!sk_closed(sock)) { \ pr_err(rc, __VA_ARGS__); \ } \ } while (0); #endif isochron-0.9/src/syncmon.c000066400000000000000000000377011431034026500156350ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2022 NXP */ #include #include #include #include #include #include #include #include #include #include "common.h" #include "isochron.h" #include "management.h" #include "rtnl.h" #include "syncmon.h" #define NUM_SYNC_CHECKS 3 struct syncmon_node { bool remote; union { /* remote */ struct { struct sk *mgmt_sock; }; /* local */ struct { struct mnl_socket *rtnl; const char *if_name; struct ptpmon *ptpmon; struct sysmon *sysmon; }; }; const char *name; enum isochron_role role; struct syncmon_node *pair; size_t num_pkts; __s64 cycle_time; __s64 sync_threshold; bool collect_sync_stats; bool gm_warned; bool ptpmon_sync_done; bool sysmon_sync_done; bool transient_port_state; struct clock_identity gm_clkid; enum port_link_state link_state; enum port_state port_state; __s64 ptpmon_offset; __s64 sysmon_offset; LIST_ENTRY(syncmon_node) list; }; struct syncmon { struct timespec ts; size_t num_checks; __s64 monitor_interval; __s64 initial_interval; bool same_gm; LIST_HEAD(nodes_head, syncmon_node) nodes; }; static const struct clock_identity clockid_zero; static int syncmon_node_query_link_state_remote(struct syncmon_node *node, enum port_link_state *link_state) { struct isochron_port_link_state s; int rc; rc = isochron_query_mid(node->mgmt_sock, ISOCHRON_MID_PORT_LINK_STATE, &s, sizeof(s)); if (rc) { fprintf(stderr, "Port link state missing from node %s reply\n", node->name); return rc; } *link_state = s.link_state; return 0; } static int syncmon_node_query_link_state_local(struct syncmon_node *node, enum port_link_state *link_state) { bool running; int rc; rc = rtnl_query_link_state(node->rtnl, node->if_name, &running); if (rc) { pr_err(rc, "Failed to query port %s link state: %m\n", node->if_name); return rc; } *link_state = running ? PORT_LINK_STATE_RUNNING : PORT_LINK_STATE_DOWN; return 0; } static int syncmon_node_update_link_state(struct syncmon_node *node) { enum port_link_state link_state; int rc; if (node->remote) rc = syncmon_node_query_link_state_remote(node, &link_state); else rc = syncmon_node_query_link_state_local(node, &link_state); if (rc) return rc; if (node->link_state == link_state) return 0; node->link_state = link_state; if (node->link_state == PORT_LINK_STATE_RUNNING) printf("Link state of node %s is running\n", node->name); if (node->link_state == PORT_LINK_STATE_DOWN) printf("Link state of node %s is down\n", node->name); return 0; } static int syncmon_node_query_sync_local(struct syncmon_node *node, __s64 *sysmon_offset, __s64 *ptpmon_offset, int *utc_offset, enum port_state *port_state, struct clock_identity *gm_clkid) { struct time_properties_ds time_properties_ds; struct parent_data_set parent_ds; struct current_ds current_ds; __s64 sysmon_delay; __u64 sysmon_ts; int rc; rc = ptpmon_query_port_state_by_name(node->ptpmon, node->if_name, node->rtnl, port_state); if (rc) { pr_err(rc, "ptpmon failed to query port state: %m\n"); return rc; } rc = ptpmon_query_clock_mid(node->ptpmon, MID_PARENT_DATA_SET, &parent_ds, sizeof(parent_ds)); if (rc) { pr_err(rc, "ptpmon failed to query grandmaster clock id: %m\n"); return rc; } rc = ptpmon_query_clock_mid(node->ptpmon, MID_CURRENT_DATA_SET, ¤t_ds, sizeof(current_ds)); if (rc) { pr_err(rc, "ptpmon failed to query CURRENT_DATA_SET: %m\n"); return rc; } rc = ptpmon_query_clock_mid(node->ptpmon, MID_TIME_PROPERTIES_DATA_SET, &time_properties_ds, sizeof(time_properties_ds)); if (rc) { pr_err(rc, "ptpmon failed to query TIME_PROPERTIES_DATA_SET: %m\n"); return rc; } rc = sysmon_get_offset(node->sysmon, sysmon_offset, &sysmon_ts, &sysmon_delay); if (rc) { pr_err(rc, "Failed to query current sysmon offset: %m\n"); return rc; } *ptpmon_offset = master_offset_from_current_ds(¤t_ds); *utc_offset = __be16_to_cpu(time_properties_ds.current_utc_offset); *gm_clkid = parent_ds.grandmaster_identity; return 0; } static int syncmon_node_query_sync_remote(struct syncmon_node *node, __s64 *sysmon_offset, __s64 *ptpmon_offset, int *utc_offset, enum port_state *port_state, struct clock_identity *gm_clkid) { return isochron_collect_sync_stats(node->mgmt_sock, sysmon_offset, ptpmon_offset, utc_offset, port_state, gm_clkid); } static void syncmon_warn_different_grandmasters(struct syncmon_node *node1, struct syncmon_node *node2) { char node1_gm[CLOCKID_BUFSIZE]; char node2_gm[CLOCKID_BUFSIZE]; if (clockid_eq(&node1->gm_clkid, &clockid_zero) || clockid_eq(&node2->gm_clkid, &clockid_zero)) return; if (node1->gm_warned && node2->gm_warned) return; clockid_to_string(&node1->gm_clkid, node1_gm); clockid_to_string(&node2->gm_clkid, node2_gm); printf("Nodes not synchronized to the same grandmaster, %s has %s, %s has %s\n", node1->name, node1_gm, node2->name, node2_gm); node1->gm_warned = true; node2->gm_warned = true; } static void syncmon_compare_node_grandmasters(struct syncmon *syncmon) { struct syncmon_node *node1, *node2; syncmon->same_gm = true; LIST_FOREACH(node1, &syncmon->nodes, list) { if (!node1->collect_sync_stats) continue; LIST_FOREACH(node2, &syncmon->nodes, list) { if (!node2->collect_sync_stats) continue; if (node1 == node2) continue; if (!clockid_eq(&node1->gm_clkid, &node2->gm_clkid)) { syncmon->same_gm = false; syncmon_warn_different_grandmasters(node1, node2); break; } } } } static int syncmon_node_update_sync(struct syncmon *syncmon, struct syncmon_node *node) { struct clock_identity gm_clkid; enum port_state port_state; int utc_offset; int rc; if (node->remote) rc = syncmon_node_query_sync_remote(node, &node->sysmon_offset, &node->ptpmon_offset, &utc_offset, &port_state, &gm_clkid); else rc = syncmon_node_query_sync_local(node, &node->sysmon_offset, &node->ptpmon_offset, &utc_offset, &port_state, &gm_clkid); if (rc) return rc; node->sysmon_offset += NSEC_PER_SEC * utc_offset; node->transient_port_state = port_state != PS_MASTER && port_state != PS_SLAVE; node->ptpmon_sync_done = !!(llabs(node->ptpmon_offset) <= node->sync_threshold); node->sysmon_sync_done = !!(llabs(node->sysmon_offset) <= node->sync_threshold); if (port_state != node->port_state) { printf("Node %s port changed state to %s\n", node->name, port_state_to_string(port_state)); node->port_state = port_state; } if (!clockid_eq(&gm_clkid, &node->gm_clkid)) { char gm[CLOCKID_BUFSIZE]; clockid_to_string(&gm_clkid, gm); printf("Node %s changed GM to %s\n", node->name, gm); node->gm_clkid = gm_clkid; node->gm_warned = false; syncmon_compare_node_grandmasters(syncmon); } return 0; } static void syncmon_next(struct syncmon *syncmon, __s64 interval) { __s64 next = timespec_to_ns(&syncmon->ts) + interval; syncmon->ts = ns_to_timespec(next); clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &syncmon->ts, NULL); } static bool syncmon_all_nodes_within_3x_threshold(struct syncmon *syncmon) { struct syncmon_node *node; LIST_FOREACH(node, &syncmon->nodes, list) { if (!node->collect_sync_stats) continue; if (node->transient_port_state) return false; if (llabs(node->ptpmon_offset) > 3 * node->sync_threshold) return false; if (llabs(node->sysmon_offset) > 3 * node->sync_threshold) return false; } return true; } static void syncmon_print_sync_stats_double(struct syncmon_node *send, struct syncmon_node *rcv) { char now_buf[TIMESPEC_BUFSIZ]; struct timespec now_ts; __s64 now; clock_gettime(CLOCK_TAI, &now_ts); now = timespec_to_ns(&now_ts); ns_sprintf(now_buf, now); printf("isochron[%s]: %s ptpmon %10lld sysmon %10lld receiver ptpmon %10lld sysmon %10lld\n", now_buf, send->name, send->ptpmon_offset, send->sysmon_offset, rcv->ptpmon_offset, rcv->sysmon_offset); } static void syncmon_print_sync_stats_single(struct syncmon_node *send) { char now_buf[TIMESPEC_BUFSIZ]; struct timespec now_ts; __s64 now; clock_gettime(CLOCK_TAI, &now_ts); now = timespec_to_ns(&now_ts); ns_sprintf(now_buf, now); /* In case --omit-remote-sync is used */ printf("isochron[%s]: %s ptpmon %10lld sysmon %10lld\n", now_buf, send->name, send->ptpmon_offset, send->sysmon_offset); } static bool syncmon_sync_ok(struct syncmon *syncmon) { bool any_port_transient_state = false; bool all_ptpmon_sync_done = true; bool all_sysmon_sync_done = true; bool any_link_down = false; struct syncmon_node *node; int rc; LIST_FOREACH(node, &syncmon->nodes, list) { rc = syncmon_node_update_link_state(node); if (rc) return false; if (node->link_state != PORT_LINK_STATE_RUNNING) any_link_down = true; if (!node->collect_sync_stats) continue; rc = syncmon_node_update_sync(syncmon, node); if (rc) return false; if (node->transient_port_state) any_port_transient_state = true; if (!node->ptpmon_sync_done) all_ptpmon_sync_done = false; if (!node->sysmon_sync_done) all_sysmon_sync_done = false; } LIST_FOREACH(node, &syncmon->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; if (!node->collect_sync_stats) continue; if (node->pair && node->pair->collect_sync_stats) syncmon_print_sync_stats_double(node, node->pair); else syncmon_print_sync_stats_single(node); } return !any_link_down && !any_port_transient_state && syncmon->same_gm && all_ptpmon_sync_done && all_sysmon_sync_done; } static void syncmon_init_num_checks(struct syncmon *syncmon) { bool have_sync_stats = false; struct syncmon_node *node; LIST_FOREACH(node, &syncmon->nodes, list) { if (node->collect_sync_stats) { have_sync_stats = true; break; } } /* No point for checking link state more than once if * we don't have a ptpmon. */ syncmon->num_checks = have_sync_stats ? NUM_SYNC_CHECKS : 1; /* Bypass GM check if we won't have ptpmon */ if (!have_sync_stats) syncmon->same_gm = true; } static void syncmon_init_initial_interval(struct syncmon *syncmon) { /* Accelerate initial sync checks if we're already in sync. */ if (syncmon_sync_ok(syncmon)) syncmon->initial_interval = NSEC_PER_SEC / 10; else if (syncmon_all_nodes_within_3x_threshold(syncmon)) syncmon->initial_interval = NSEC_PER_SEC / 2; else syncmon->initial_interval = NSEC_PER_SEC; } static void syncmon_init_monitor_interval(struct syncmon *syncmon) { __s64 duration, shortest_duration = INT64_MAX; struct syncmon_node *node; LIST_FOREACH(node, &syncmon->nodes, list) { if (node->role != ISOCHRON_ROLE_SEND) continue; duration = node->num_pkts * node->cycle_time; if (shortest_duration > duration) shortest_duration = duration; } /* Make sure that short tests have sync checks frequent enough to * actually detect a sync loss and have the time to stop it. */ syncmon->monitor_interval = shortest_duration / syncmon->num_checks; if (syncmon->monitor_interval > NSEC_PER_SEC) syncmon->monitor_interval = NSEC_PER_SEC; } void syncmon_init(struct syncmon *syncmon) { syncmon_init_num_checks(syncmon); syncmon_init_initial_interval(syncmon); syncmon_init_monitor_interval(syncmon); } static void syncmon_init_ts(struct syncmon *syncmon) { clock_gettime(CLOCK_MONOTONIC, &syncmon->ts); } int syncmon_wait_until_ok(struct syncmon *syncmon) { int sync_checks_to_go = syncmon->num_checks; syncmon_init_ts(syncmon); while (1) { if (signal_received) return -EINTR; if (syncmon_sync_ok(syncmon)) sync_checks_to_go--; if (!sync_checks_to_go) break; syncmon_next(syncmon, syncmon->initial_interval); } return 0; } bool syncmon_monitor(struct syncmon *syncmon, syncmon_stop_fn_t stop, void *priv) { int sync_checks_to_go = syncmon->num_checks; syncmon_init_ts(syncmon); while (!stop(priv)) { if (signal_received) return false; if (!syncmon_sync_ok(syncmon)) sync_checks_to_go--; if (!sync_checks_to_go) { fprintf(stderr, "Sync lost during the test, repeating\n"); return false; } syncmon_next(syncmon, syncmon->monitor_interval); } return true; } struct syncmon_node * syncmon_add_local_sender_no_sync(struct syncmon *syncmon, const char *name, struct mnl_socket *rtnl, const char *if_name, size_t num_pkts, __s64 cycle_time) { struct syncmon_node *node; node = calloc(1, sizeof(*node)); if (!node) return NULL; node->role = ISOCHRON_ROLE_SEND; node->name = name; node->rtnl = rtnl; node->if_name = if_name; node->num_pkts = num_pkts; node->cycle_time = cycle_time; LIST_INSERT_HEAD(&syncmon->nodes, node, list); return node; } struct syncmon_node *syncmon_add_local_sender(struct syncmon *syncmon, const char *name, struct mnl_socket *rtnl, const char *if_name, size_t num_pkts, __s64 cycle_time, struct ptpmon *ptpmon, struct sysmon *sysmon, __s64 sync_threshold) { struct syncmon_node *node; node = calloc(1, sizeof(*node)); if (!node) return NULL; node->role = ISOCHRON_ROLE_SEND; node->name = name; node->rtnl = rtnl; node->if_name = if_name; node->num_pkts = num_pkts; node->cycle_time = cycle_time; node->ptpmon = ptpmon; node->sysmon = sysmon; node->sync_threshold = sync_threshold; node->collect_sync_stats = true; LIST_INSERT_HEAD(&syncmon->nodes, node, list); return node; } struct syncmon_node * syncmon_add_remote_sender_no_sync(struct syncmon *syncmon, const char *name, struct sk *mgmt_sock, size_t num_pkts, __s64 cycle_time) { struct syncmon_node *node; node = calloc(1, sizeof(*node)); if (!node) return NULL; node->remote = true; node->role = ISOCHRON_ROLE_SEND; node->name = name; node->mgmt_sock = mgmt_sock; node->num_pkts = num_pkts; node->cycle_time = cycle_time; LIST_INSERT_HEAD(&syncmon->nodes, node, list); return node; } struct syncmon_node *syncmon_add_remote_sender(struct syncmon *syncmon, const char *name, struct sk *mgmt_sock, size_t num_pkts, __s64 cycle_time, __s64 sync_threshold) { struct syncmon_node *node; node = calloc(1, sizeof(*node)); if (!node) return NULL; node->remote = true; node->role = ISOCHRON_ROLE_SEND; node->name = name; node->mgmt_sock = mgmt_sock; node->num_pkts = num_pkts; node->cycle_time = cycle_time; node->sync_threshold = sync_threshold; node->collect_sync_stats = true; LIST_INSERT_HEAD(&syncmon->nodes, node, list); return node; } struct syncmon_node * syncmon_add_remote_receiver_no_sync(struct syncmon *syncmon, const char *name, struct sk *mgmt_sock, struct syncmon_node *pair) { struct syncmon_node *node; node = calloc(1, sizeof(*node)); if (!node) return NULL; node->remote = true; node->role = ISOCHRON_ROLE_RCV; node->name = name; node->mgmt_sock = mgmt_sock; node->pair = pair; pair->pair = node; LIST_INSERT_HEAD(&syncmon->nodes, node, list); return node; } struct syncmon_node *syncmon_add_remote_receiver(struct syncmon *syncmon, const char *name, struct sk *mgmt_sock, struct syncmon_node *pair, __s64 sync_threshold) { struct syncmon_node *node; node = calloc(1, sizeof(*node)); if (!node) return NULL; node->remote = true; node->role = ISOCHRON_ROLE_RCV; node->name = name; node->mgmt_sock = mgmt_sock; node->pair = pair; pair->pair = node; node->sync_threshold = sync_threshold; node->collect_sync_stats = true; LIST_INSERT_HEAD(&syncmon->nodes, node, list); return node; } struct syncmon *syncmon_create(void) { struct syncmon *syncmon; syncmon = calloc(1, sizeof(*syncmon)); if (!syncmon) return NULL; LIST_INIT(&syncmon->nodes); return syncmon; } void syncmon_destroy(struct syncmon *syncmon) { struct syncmon_node *node, *tmp; LIST_FOREACH_SAFE(node, &syncmon->nodes, list, tmp) { LIST_REMOVE(node, list); free(node); } free(syncmon); } isochron-0.9/src/syncmon.h000066400000000000000000000035441431034026500156400ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2022 NXP */ #ifndef _ISOCHRON_SYNCMON_H #define _ISOCHRON_SYNCMON_H #include #include #include #include #include "ptpmon.h" #include "sk.h" #include "sysmon.h" struct syncmon; struct syncmon_node; typedef bool syncmon_stop_fn_t(void *priv); struct syncmon *syncmon_create(void); void syncmon_destroy(struct syncmon *syncmon); void syncmon_init(struct syncmon *syncmon); int syncmon_wait_until_ok(struct syncmon *syncmon); bool syncmon_monitor(struct syncmon *syncmon, syncmon_stop_fn_t stop, void *priv); struct syncmon_node * syncmon_add_local_sender_no_sync(struct syncmon *syncmon, const char *name, struct mnl_socket *rtnl, const char *if_name, size_t num_pkts, __s64 cycle_time); struct syncmon_node *syncmon_add_local_sender(struct syncmon *syncmon, const char *name, struct mnl_socket *rtnl, const char *if_name, size_t num_pkts, __s64 cycle_time, struct ptpmon *ptpmon, struct sysmon *sysmon, __s64 sync_threshold); struct syncmon_node * syncmon_add_remote_sender_no_sync(struct syncmon *syncmon, const char *name, struct sk *mgmt_sock, size_t num_pkts, __s64 cycle_time); struct syncmon_node *syncmon_add_remote_sender(struct syncmon *syncmon, const char *name, struct sk *mgmt_sock, size_t num_pkts, __s64 cycle_time, __s64 sync_threshold); struct syncmon_node * syncmon_add_remote_receiver_no_sync(struct syncmon *syncmon, const char *name, struct sk *mgmt_sock, struct syncmon_node *pair); struct syncmon_node *syncmon_add_remote_receiver(struct syncmon *syncmon, const char *name, struct sk *mgmt_sock, struct syncmon_node *pair, __s64 sync_threshold); #endif isochron-0.9/src/sysmon.c000066400000000000000000000202311431034026500154650ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2012 Richard Cochran * Copyright 2021 NXP */ /* This file contains code snippets from the linuxptp project */ #define _GNU_SOURCE /* for asprintf() */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "missing.h" #include "sk.h" #include "sysmon.h" #define NSEC_PER_SEC 1000000000LL enum sysoff_method { SYSOFF_RUN_TIME_MISSING = -1, SYSOFF_PRECISE, SYSOFF_EXTENDED, SYSOFF_BASIC, SYSOFF_LAST, }; struct sysmon { clockid_t clkid; char *name; int phc_index; int num_readings; enum sysoff_method sysoff_method; }; static __s64 pct_to_ns(struct ptp_clock_time *t) { return t->sec * NSEC_PER_SEC + t->nsec; } static clockid_t phc_open(const char *iface, int *phc_index) { struct sk_ts_info ts_info; char phc_device[19]; struct timespec ts; clockid_t clkid; int fd; /* check if device is a valid ethernet device */ if (sk_get_ts_info(iface, &ts_info) || !ts_info.valid) { fprintf(stderr, "unknown clock %s: %m\n", iface); return CLOCK_INVALID; } if (ts_info.phc_index < 0) { fprintf(stderr, "interface %s does not have a PHC\n", iface); return CLOCK_INVALID; } snprintf(phc_device, sizeof(phc_device), "/dev/ptp%d", ts_info.phc_index); fd = open(phc_device, O_RDWR); if (fd < 0) { fprintf(stderr, "cannot open %s: %m\n", phc_device); return CLOCK_INVALID; } clkid = FD_TO_CLOCKID(fd); /* check if clkid is valid */ if (clock_gettime(clkid, &ts)) { close(fd); return CLOCK_INVALID; } *phc_index = ts_info.phc_index; return clkid; } static void phc_close(clockid_t clkid) { close(CLOCKID_TO_FD(clkid)); } static int sysoff_precise(int fd, __s64 *result, __u64 *ts) { struct ptp_sys_offset_precise pso; memset(&pso, 0, sizeof(pso)); if (ioctl(fd, PTP_SYS_OFFSET_PRECISE, &pso)) return SYSOFF_RUN_TIME_MISSING; *result = pct_to_ns(&pso.sys_realtime) - pct_to_ns(&pso.device); *ts = pct_to_ns(&pso.sys_realtime); return SYSOFF_PRECISE; } static __s64 sysoff_estimate(struct ptp_clock_time *pct, int extended, int n_samples, __u64 *ts, __s64 *delay) { __s64 shortest_interval, best_timestamp, best_offset; __s64 interval, timestamp, offset; __s64 t1, t2, tp; int i = 0; if (extended) { t1 = pct_to_ns(&pct[3*i]); tp = pct_to_ns(&pct[3*i+1]); t2 = pct_to_ns(&pct[3*i+2]); } else { t1 = pct_to_ns(&pct[2*i]); tp = pct_to_ns(&pct[2*i+1]); t2 = pct_to_ns(&pct[2*i+2]); } shortest_interval = t2 - t1; best_timestamp = (t2 + t1) / 2; best_offset = best_timestamp - tp; for (i = 1; i < n_samples; i++) { if (extended) { t1 = pct_to_ns(&pct[3*i]); tp = pct_to_ns(&pct[3*i+1]); t2 = pct_to_ns(&pct[3*i+2]); } else { t1 = pct_to_ns(&pct[2*i]); tp = pct_to_ns(&pct[2*i+1]); t2 = pct_to_ns(&pct[2*i+2]); } interval = t2 - t1; timestamp = (t2 + t1) / 2; offset = timestamp - tp; if (interval < shortest_interval) { shortest_interval = interval; best_timestamp = timestamp; best_offset = offset; } } *ts = best_timestamp; *delay = shortest_interval; return best_offset; } static int sysoff_extended(int fd, int n_samples, __s64 *result, __u64 *ts, __s64 *delay) { struct ptp_sys_offset_extended pso; memset(&pso, 0, sizeof(pso)); pso.n_samples = n_samples; if (ioctl(fd, PTP_SYS_OFFSET_EXTENDED, &pso)) return SYSOFF_RUN_TIME_MISSING; *result = sysoff_estimate(&pso.ts[0][0], 1, n_samples, ts, delay); return SYSOFF_EXTENDED; } static int sysoff_basic(int fd, int n_samples, __s64 *result, __u64 *ts, __s64 *delay) { struct ptp_sys_offset pso; memset(&pso, 0, sizeof(pso)); pso.n_samples = n_samples; if (ioctl(fd, PTP_SYS_OFFSET, &pso)) return SYSOFF_RUN_TIME_MISSING; *result = sysoff_estimate(pso.ts, 0, n_samples, ts, delay); return SYSOFF_BASIC; } /** * Measure the offset between a PHC and the system time. * @param fd An open file descriptor to a PHC device. * @param method A non-negative SYSOFF_ value returned by sysoff_probe(). * @param n_samples The number of consecutive readings to make. * @param result The estimated offset in nanoseconds. * @param ts The system time corresponding to the 'result'. * @param delay The delay in reading of the clock in nanoseconds. * @return One of the SYSOFF_ enumeration values. */ static int sysoff_measure(int fd, enum sysoff_method method, int n_samples, __s64 *result, __u64 *ts, __s64 *delay) { switch (method) { case SYSOFF_PRECISE: *delay = 0; return sysoff_precise(fd, result, ts); case SYSOFF_EXTENDED: return sysoff_extended(fd, n_samples, result, ts, delay); case SYSOFF_BASIC: return sysoff_basic(fd, n_samples, result, ts, delay); default: return SYSOFF_RUN_TIME_MISSING; } } /** * Check to see if a PTP_SYS_OFFSET ioctl is supported. * @param fd An open file descriptor to a PHC device. * @return One of the SYSOFF_ enumeration values. */ static enum sysoff_method sysoff_probe(int fd, int n_samples) { __s64 junk, delay; __u64 ts; int i; if (n_samples > PTP_MAX_SAMPLES) { fprintf(stderr, "warning: %d exceeds kernel max readings %d\n", n_samples, PTP_MAX_SAMPLES); fprintf(stderr, "falling back to clock_gettime method\n"); return SYSOFF_RUN_TIME_MISSING; } for (i = 0; i < SYSOFF_LAST; i++) { if (sysoff_measure(fd, i, n_samples, &junk, &ts, &delay) < 0) continue; return i; } return SYSOFF_RUN_TIME_MISSING; } static int read_phc(clockid_t clkid, clockid_t sysclk, int readings, __s64 *offset, __u64 *ts, __s64 *delay) { __s64 interval, best_interval = INT64_MAX; struct timespec t_dst1, t_dst2, t_src; int i; /* Pick the quickest clkid reading. */ for (i = 0; i < readings; i++) { if (clock_gettime(sysclk, &t_dst1) || clock_gettime(clkid, &t_src) || clock_gettime(sysclk, &t_dst2)) { fprintf(stderr, "failed to read clock: %m\n"); return 0; } interval = (t_dst2.tv_sec - t_dst1.tv_sec) * NSEC_PER_SEC + t_dst2.tv_nsec - t_dst1.tv_nsec; if (best_interval > interval) { best_interval = interval; *offset = (t_dst1.tv_sec - t_src.tv_sec) * NSEC_PER_SEC + t_dst1.tv_nsec - t_src.tv_nsec + interval / 2; *ts = t_dst2.tv_sec * NSEC_PER_SEC + t_dst2.tv_nsec; } } *delay = best_interval; return 1; } int sysmon_get_offset(struct sysmon *sysmon, __s64 *offset, __u64 *ts, __s64 *delay) { if (sysmon->sysoff_method != SYSOFF_RUN_TIME_MISSING) { if (sysoff_measure(CLOCKID_TO_FD(sysmon->clkid), sysmon->sysoff_method, sysmon->num_readings, offset, ts, delay) < 0) return -1; } else { if (!read_phc(sysmon->clkid, CLOCK_REALTIME, sysmon->num_readings, offset, ts, delay)) return -1; } return 0; } void sysmon_print_method(struct sysmon *sysmon) { switch (sysmon->sysoff_method) { case SYSOFF_BASIC: printf("Using PTP_SYS_OFFSET for measuring the offset from %s to CLOCK_REALTIME\n", sysmon->name); break; case SYSOFF_EXTENDED: printf("Using PTP_SYS_OFFSET_EXTENDED for measuring the offset from %s to CLOCK_REALTIME\n", sysmon->name); break; case SYSOFF_PRECISE: printf("Using PTP_SYS_OFFSET_PRECISE for measuring the offset from %s to CLOCK_REALTIME\n", sysmon->name); break; default: break; } } struct sysmon *sysmon_create(const char *iface, int num_readings) { struct sysmon *sysmon; int phc_index = -1; clockid_t clkid; int err; sysmon = calloc(1, sizeof(*sysmon)); if (!sysmon) return NULL; clkid = phc_open(iface, &phc_index); if (clkid == CLOCK_INVALID) { free(sysmon); return NULL; } sysmon->clkid = clkid; sysmon->phc_index = phc_index; sysmon->num_readings = num_readings; sysmon->sysoff_method = sysoff_probe(CLOCKID_TO_FD(clkid), num_readings); err = asprintf(&sysmon->name, "/dev/ptp%d", phc_index); if (err < 0) { phc_close(clkid); free(sysmon); return NULL; } return sysmon; } void sysmon_destroy(struct sysmon *sysmon) { phc_close(sysmon->clkid); free(sysmon->name); free(sysmon); } isochron-0.9/src/sysmon.h000066400000000000000000000006251431034026500154770ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright 2021 NXP */ #ifndef _SYSMON_H #define _SYSMON_H #include struct sysmon; struct sysmon *sysmon_create(const char *iface, int num_readings); void sysmon_destroy(struct sysmon *sysmon); int sysmon_get_offset(struct sysmon *sysmon, __s64 *offset, __u64 *ts, __s64 *delay); void sysmon_print_method(struct sysmon *sysmon); #endif isochron-0.9/toolchain_deps.sh000077500000000000000000000004511431034026500165360ustar00rootroot00000000000000#!/bin/bash CC="$1" CFLAGS="$2" EXTRA_CFLAGS="" ${CC} ${CFLAGS} -x c -c -o $(mktemp) - > /dev/null 2>&1 << EOF #include int main(void) { return SOF_TIMESTAMPING_OPT_TX_SWHW; } EOF if [ $? = 0 ]; then EXTRA_CFLAGS="${EXTRA_CFLAGS} -DHAVE_TX_SWHW" fi echo ${EXTRA_CFLAGS}