pax_global_header00006660000000000000000000000064125772701070014522gustar00rootroot0000000000000052 comment=b9aa4d783e1c5fe7be4173a7a7e8c8bba7276652 linuxptp-1.6/000077500000000000000000000000001257727010700132535ustar00rootroot00000000000000linuxptp-1.6/.gitignore000066400000000000000000000001131257727010700152360ustar00rootroot00000000000000/*.d /*.o /.version /hwstamp_ctl /phc2sys /pmc /ptp4l /phc_ctl /timemaster linuxptp-1.6/CODING_STYLE.org000066400000000000000000000061471257727010700157170ustar00rootroot00000000000000 * General For this project, we are using the Linux kernel coding style, with a bit of latitude, as described below. The kernel style can be quickly summarized like this: - text width is 80 columns - indentation by tabs - tab width is 8 spaces - identifiers are lower case with underscores - macros are upper case - braces and spacing follow K&R style * Using checkpatch.pl You can use the Linux kernel's checkpatch.pl script to sanity check your work. Sometimes that script warns about code which is in fact fine, so use your good taste. I use the following code as my pre-commit hook. #+BEGIN_EXAMPLE if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi git diff --cached $against -- | ../linux/scripts/checkpatch.pl \ --ignore BRACES \ --ignore PREFER_PR_LEVEL \ --no-signoff \ - #+END_EXAMPLE * Exceptions ** Use of lowerCamelCase and UpperCamelCase The messages and data sets in the PTP and gPTP standards are full of stuff that looks a bit like C-language pseudo code, and all this is unfortunately in camel case. In those cases where the linuxptp code is closely related to these fields, we retain the camel case, even though it hurts our eyes to look at. The alternative would have been to convert the field names into lower case underscore, but that would have over extended the already ridiculously long names, such as logMinPdelayReqInterval and observedParentOffsetScaledLogVariance. The exception permitting CamelCase applies primarily to the declarations found in the following header files. - ddt.h - ds.h - msg.h - pdt.h - tlv.h ** Braces around single if-else bodies In the Linux kernel style, if-else bodies containing just a single line are not placed within curly braces. Therefore you will often see code like the following example. #+BEGIN_EXAMPLE if (!x) do_zero(); else do_nonzero(); #+END_EXAMPLE In this situation we still like to see the braces, the rationale being that the if-else bodies tend to accumulate new statements over time, especially on the error path. Using the strict kernel style thus results in patches (and annotated views) that show a bogus change in the test expression, and this requires extra mental effort when reviewing patches, just to realize that no logical change has occurred. Additionally, having the braces hardly uses up more vertical space than not having them, so we generally include them as shown in the following example. #+BEGIN_EXAMPLE if (!x) { do_zero(); } else { do_nonzero(); } #+END_EXAMPLE ** Line lengths We try to keep the line length to 80 characters wide. However, sometimes it happens that, no matter how hard we try, we find ourselves going a bit over that limit. This is especially true in connection with the long CamelCase identifiers mentioned above. Breaking a statement over two lines can look even worse than having one line too long, so please exercise judgement when applying the line length rule. linuxptp-1.6/COPYING000066400000000000000000000432541257727010700143160ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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 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) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) year 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 Lesser General Public License instead of this License. linuxptp-1.6/README.org000066400000000000000000000262641257727010700147330ustar00rootroot00000000000000 * Introduction This software is an implementation of the Precision Time Protocol (PTP) according to IEEE standard 1588 for Linux. The dual design goals are to provide a robust implementation of the standard and to use the most relevant and modern Application Programming Interfaces (API) offered by the Linux kernel. Supporting legacy APIs and other platforms is not a goal. * License The software is copyrighted by the authors and is licensed under the GNU General Public License. See the file, COPYING, for details of the license terms. * Features - Supports hardware and software time stamping via the Linux SO_TIMESTAMPING socket option. - Supports the Linux PTP Hardware Clock (PHC) subsystem by using the clock_gettime family of calls, including the new clock_adjtimex system call. - Implements Boundary Clock (BC) and Ordinary Clock (OC). - Transport over UDP/IPv4, UDP/IPv6, and raw Ethernet (Layer 2). - Supports IEEE 802.1AS-2011 in the role of end station. - Modular design allowing painless addition of new transports and clock servos. * Getting the Code You can download the latest released version at Source Forge. http://sourceforge.net/projects/linuxptp/files/latest/download The source code is managed using the git version control system. To get your own copy of the project sources, use the following command. #+BEGIN_EXAMPLE git clone git://git.code.sf.net/p/linuxptp/code linuxptp #+END_EXAMPLE If the git protocol is blocked by your local area network, then you can use the alternative HTTP protocol instead. #+BEGIN_EXAMPLE git clone http://git.code.sf.net/p/linuxptp/code linuxptp #+END_EXAMPLE * System Requirements In order to run this software, you need Linux kernel version 3.0 or newer, and the kernel header files must available at compile time. In addition, you will also need to have either: 1. A supported Ethernet MAC device. 2. A supported PHY device paired with a MAC that allows time stamping in the PHY (indicated by PHY=Y in the table below). ** Linux Kernel Support In order to support PTP, the operating system needs to provide two services: network packet time stamping and clock control. In 2009, Patrick Ohly added a new socket option called SO_TIMESTAMPING for packet time stamping, especially for PTP. This work appeared in Linux version 2.6.30. In July of 2011, the PTP Hardware Clock (PHC) subsystem was merged into Linux version 3.0. The PHC code provides a driver framework and the user space API for clock control. ** Ethtool Support Starting with version 3.5 of the Linux kernel, you can query the time stamping capabilities of a network interface using the ETHTOOL_GET_TS_INFO ioctl. Using ethtool version 3.4 or later, you can check your system's time stamping support as shown in the following example. #+BEGIN_EXAMPLE ethtool -T eth0 #+END_EXAMPLE If the ethtool ioctl is available, then the ptp4l program will use it in order to discover the proper PHC device. ** Driver Support Matrix The following two tables list the drivers that support the PHC subsystem and the Linux kernel version when they first appeared. These drivers will create a PHC device for controlling the hardware clock. *** Hardware Timestamping - PHY |---------+-------------------------------+---------| | Driver | Hardware | Version | |---------+-------------------------------+---------| | dp83640 | National Semiconductor PHYTER | 3.0 | |---------+-------------------------------+---------| *** Hardware Timestamping - MAC |------------+--------------------------+---------| | Driver | Hardware | Version | |------------+--------------------------+---------| | amd-xgbe | AMD 10GbE Ethernet Soc | 3.17 | | bfin_mac | Analog Blackfin | 3.8 | | bnx2x | Broadcom NetXtremeII 10G | 3.18 | | cpts | Texas Instruments am335x | 3.8 | | e1000e | Intel 82574, 82583 | 3.9 | | fm10k | Intel FM10000 | 3.18 | | fec | Freescale i.mx6 | 3.8 | | gianfar | Freescale eTSEC PowerPC | 3.0 | | i40e | Intel XL710 Family | 3.14 | | igb | Intel 82576, 82580 | 3.5 | | ixgbe | Intel 82599 | 3.5 | | mlx4 | Mellanox 40G PCI | 3.14 | | ptp_ixp46x | Intel IXP465 | 3.0 | | ptp_phc | Lapis EG20T PCH | 3.5 | | sfc | Solarflare SFC9000 | 3.7 | | stmmac | STM Synopsys IP Core | 3.10 | | tg3 | Broadcom Tigon3 PCI | 3.8 | | tilegx | Tilera GBE/XGBE | 3.12 | |------------+--------------------------+---------| *** Software Timestamping The table below shows the Linux drivers that support software time stamping. In addition, the 'PHY' column indicates whether the Ethernet MAC driver can support a PTP Hardware Clock in an external PHY. The letter 'Y' in this column means that if you design a mother board that combines such a MAC with a PTP capable PHY, then it will work with the Linux PHC subsystem. |--------------+--------------------------+---------+-----| | Driver | Hardware | Version | PHY | |--------------+--------------------------+---------+-----| | 3c59x | 3Com EtherLink PCI | 3.14 | N | | altera_tse | Altera Triple-Speed MAC | 3.15 | Y | | bna | Brocade 1010/1020 10Gb | 3.14 | N | | bnx2x | Broadcom Everest | 3.5 | N | | davinci_emac | TI DaVinci, Sitara | 3.1 | Y | | dnet | Dave Ethernet MAC | 3.1 | Y | | e100 | Intel PRO/100 | 3.5 | N | | e1000 | Intel PRO/1000 PCI/PCI-X | 3.5 | N | | e1000e | Intel PRO/1000 PCIe | 3.5 | N | | emaclite | Xilinx Ethernet Lite | 3.1 | Y | | ethoc | OpenCores 10/100 MAC | 3.1 | Y | | fec | Freescale Coldfire | 3.1 | Y | | fec_mpc52xx | Freescale MPC5200 | 3.1 | Y | | forcedeth | NVIDIA nForce | 3.5 | N | | fs_enet | Freescale MPC512x | 3.1 | Y | | genet | Broadcom GENET | 3.15 | Y | | ixp4xx_eth | Intel IXP4xx | 3.0 | Y | | lib8390 | Asix AX88796 | 3.1 | Y | | lib8390 | Various 8390 based HW | 3.1 | N | | ll_temac | Xilinx LL TEMAC | 3.1 | Y | | macb | Atmel AT32, AT91 | 3.1 | Y | | mv643xx_eth | Marvell Discovery, Orion | 3.1 | Y | | pxa168_eth | Marvell pxa168 | 3.1 | Y | | r6040 | RDC Ethernet MAC | 3.1 | Y | | r8169 | Realtek 8169/8168/8101 | 3.4 | N | | samsun-sxgbe | Samsung SXGBE 10G | 3.15 | Y | | smsc911x | SMSC LAN911x, LAN921x | 3.1 | Y | | smsc9420 | SMSC LAN9420 PCI | 3.1 | Y | | stmmac | STM Synopsys IP Core | 3.1 | Y | | tg3 | Broadcom Tigon3 PCI | 3.1 | Y | | ucc_geth | Freescale QE Gigabit | 3.1 | Y | | usbnet | USB network devices | 3.2 | Y/N | | xgene-enet | APM X-Gene SoC | 3.17 | Y | |--------------+--------------------------+---------+-----| * Installation ** Linux kernel There are many ways of getting a precompiled Linux kernel or compiling your own, so this section is only meant as an example. It is important to have the kernel headers available when compiling the Linux PTP stack. #+BEGIN_EXAMPLE export ARCH=x86 export CROSS_COMPILE= export KBUILD_OUTPUT=/home/richard/kernel/ptp_debian mkdir -p $KBUILD_OUTPUT cp /boot/config-2.6.38-bpo.2-686 $KBUILD_OUTPUT/.config make oldnoconfig make menuconfig time make -j4 make headers_install #+END_EXAMPLE Here is a table of kernel configuration options needed for PTP support. In addtion to these, you should enable the specific Ethernet MAC and PHY drivers for your hardware. |---------------------------------+-----------------------------| | Option | Description | |---------------------------------+-----------------------------| | CONFIG_PPS | Required | | CONFIG_NETWORK_PHY_TIMESTAMPING | Timestamping in PHY devices | | PTP_1588_CLOCK | PTP clock support | |---------------------------------+-----------------------------| ** PTP stack 1. Just type 'make' 2. If you compiled your own kernel (and the headers are not installed into the system path), then you should set the KBUILD_OUTPUT environment variable as in the example, above. 3. In order to install the programs and man pages into /usr/local, run the 'make install' target. You can change the installation directories by setttings the variables prefix, sbindir, mandir, and man8dir on the make command line. * Getting Involved The software development is hosted at Source Forge. https://sourceforge.net/projects/linuxptp/ ** Reporting Bugs Please report any bugs or other issues with the software to the linuxptp-users mailing list. https://lists.sourceforge.net/lists/listinfo/linuxptp-users ** Development If you would like to get involved in improving the software, please join the linuxptp-devel mailing list. https://lists.sourceforge.net/lists/listinfo/linuxptp-devel *** Submitting Patches 1. Before submitting patches, please make sure that you are starting your work on the *current HEAD* of the git repository. 2. Please checkout the ~CODING_STYLE.org~ file for guidelines on how to properly format your code. 3. Describe your changes. Each patch will be reviewed, and the reviewers need to understand why you did what you did. 4. *Sign-Off* each commit, so the changes can be properly attributed to you and you explicitely give your agreement for distribution under linuxptp's license. Signing-off is as simple as: #+BEGIN_EXAMPLE git commit -s #+END_EXAMPLE or by adding the following line (replace your real name and email) to your patch: #+BEGIN_EXAMPLE Signed-off-by: Random J Developer #+END_EXAMPLE 5. Finally, send your patches via email to the linuxptp-devel mailing list, where they will be reviewed, and eventually be included in the official code base. #+BEGIN_EXAMPLE git send-email --to linuxptp-devel@lists.sourceforge.net origin/master #+END_EXAMPLE * Thanks Thanks to AudioScience Inc for sponsoring the 8021.AS support. - http://www.audioscience.com Thanks to Intel Corporation for donating four NICs, the 82574, 82580, 82599, and the i210. - http://www.intel.com - http://e1000.sourceforge.net For testing I use an OTMC 100 grandmaster clock donated by OMICRON Lab. - http://www.omicron-lab.com/ptp linuxptp-1.6/address.h000066400000000000000000000023241257727010700150520ustar00rootroot00000000000000/** * @file address.h * @brief Definition of a structure to hold an address. * @note Copyright (C) 2014 Red Hat, Inc., Jiri Benc * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_ADDRESS_H #define HAVE_ADDRESS_H #include #include #include #include struct address { socklen_t len; union { struct sockaddr_storage ss; struct sockaddr_ll sll; struct sockaddr_in sin; struct sockaddr_in6 sin6; struct sockaddr_un sun; struct sockaddr sa; }; }; #endif linuxptp-1.6/bmc.c000066400000000000000000000073241257727010700141660ustar00rootroot00000000000000/** * @file bmc.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include "bmc.h" #include "ds.h" #define A_BETTER_TOPO 2 #define A_BETTER 1 #define B_BETTER -1 #define B_BETTER_TOPO -2 static int dscmp2(struct dataset *a, struct dataset *b) { int diff; unsigned int A = a->stepsRemoved, B = b->stepsRemoved; if (A + 1 < B) return A_BETTER; if (B + 1 < A) return B_BETTER; /* * We ignore the "error-1" conditions mentioned in the * standard, since there is nothing we can do about it anyway. */ if (A < B) { diff = memcmp(&b->receiver, &b->sender, sizeof(b->receiver)); if (diff < 0) return A_BETTER; if (diff > 0) return A_BETTER_TOPO; /* error-1 */ return 0; } if (A > B) { diff = memcmp(&a->receiver, &a->sender, sizeof(a->receiver)); if (diff < 0) return B_BETTER; if (diff > 0) return B_BETTER_TOPO; /* error-1 */ return 0; } diff = memcmp(&a->sender, &b->sender, sizeof(a->sender)); if (diff < 0) return A_BETTER_TOPO; if (diff > 0) return B_BETTER_TOPO; if (a->receiver.portNumber < b->receiver.portNumber) return A_BETTER_TOPO; if (a->receiver.portNumber > b->receiver.portNumber) return B_BETTER_TOPO; /* * If we got this far, it means "error-2" has occured. */ return 0; } int dscmp(struct dataset *a, struct dataset *b) { int diff; if (a == b) return 0; if (a && !b) return A_BETTER; if (b && !a) return B_BETTER; diff = memcmp(&a->identity, &b->identity, sizeof(a->identity)); if (!diff) return dscmp2(a, b); if (a->priority1 < b->priority1) return A_BETTER; if (a->priority1 > b->priority1) return B_BETTER; if (a->quality.clockClass < b->quality.clockClass) return A_BETTER; if (a->quality.clockClass > b->quality.clockClass) return B_BETTER; if (a->quality.clockAccuracy < b->quality.clockAccuracy) return A_BETTER; if (a->quality.clockAccuracy > b->quality.clockAccuracy) return B_BETTER; if (a->quality.offsetScaledLogVariance < b->quality.offsetScaledLogVariance) return A_BETTER; if (a->quality.offsetScaledLogVariance > b->quality.offsetScaledLogVariance) return B_BETTER; if (a->priority2 < b->priority2) return A_BETTER; if (a->priority2 > b->priority2) return B_BETTER; return diff < 0 ? A_BETTER : B_BETTER; } enum port_state bmc_state_decision(struct clock *c, struct port *r) { struct dataset *clock_ds, *clock_best, *port_best; enum port_state ps; clock_ds = clock_default_ds(c); clock_best = clock_best_foreign(c); port_best = port_best_foreign(r); ps = port_state(r); if (!port_best && PS_LISTENING == ps) return ps; if (clock_class(c) <= 127) { if (dscmp(clock_ds, port_best) > 0) { return PS_GRAND_MASTER; /*M1*/ } else { return PS_PASSIVE; /*P1*/ } } if (dscmp(clock_ds, clock_best) > 0) { return PS_GRAND_MASTER; /*M2*/ } if (clock_best_port(c) == r) { return PS_SLAVE; /*S1*/ } if (dscmp(clock_best, port_best) == A_BETTER_TOPO) { return PS_PASSIVE; /*P2*/ } else { return PS_MASTER; /*M3*/ } } linuxptp-1.6/bmc.h000066400000000000000000000027661257727010700142000ustar00rootroot00000000000000/** * @file bmc.h * @brief Best master clock algorithm * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_BMC_H #define HAVE_BMC_H #include "clock.h" #include "port.h" #include "fsm.h" /** * BMC state decision algorithm. * @param c The local clock. * @param r The port in question. * @return A @ref port_state value as the recommended state. */ enum port_state bmc_state_decision(struct clock *c, struct port *r); /** * Compare two data sets. * @param a A dataset to compare. * @param b A dataset to compare. * @return An integer less than, equal to, or greater than zero * if the dataset @a a is found, respectively, to be * less than, to match, or be greater than @a b. */ int dscmp(struct dataset *a, struct dataset *b); #endif linuxptp-1.6/clock.c000066400000000000000000001177651257727010700145330ustar00rootroot00000000000000/** * @file clock.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "address.h" #include "bmc.h" #include "clock.h" #include "clockadj.h" #include "clockcheck.h" #include "foreign.h" #include "filter.h" #include "missing.h" #include "msg.h" #include "phc.h" #include "port.h" #include "servo.h" #include "stats.h" #include "print.h" #include "tlv.h" #include "tsproc.h" #include "uds.h" #include "util.h" #define N_CLOCK_PFD (N_POLLFD + 1) /* one extra per port, for the fault timer */ #define POW2_41 ((double)(1ULL << 41)) struct port { LIST_ENTRY(port) list; }; struct freq_estimator { tmv_t origin1; tmv_t ingress1; unsigned int max_count; unsigned int count; }; struct clock_stats { struct stats *offset; struct stats *freq; struct stats *delay; unsigned int max_count; }; struct clock_subscriber { LIST_ENTRY(clock_subscriber) list; uint8_t events[EVENT_BITMASK_CNT]; struct PortIdentity targetPortIdentity; struct address addr; UInteger16 sequenceId; time_t expiration; }; struct clock { struct config *config; clockid_t clkid; struct servo *servo; enum servo_type servo_type; struct defaultDS dds; struct dataset default_dataset; struct currentDS cur; struct parent_ds dad; struct timePropertiesDS tds; struct ClockIdentity ptl[PATH_TRACE_MAX]; struct foreign_clock *best; struct ClockIdentity best_id; LIST_HEAD(ports_head, port) ports; struct port *uds_port; struct pollfd *pollfd; int pollfd_valid; int nports; /* does not include the UDS port */ int last_port_number; int free_running; int freq_est_interval; int grand_master_capable; /* for 802.1AS only */ int utc_timescale; int utc_offset_set; int leap_set; int kernel_leap; int utc_offset; /* grand master role */ int time_flags; /* grand master role */ int time_source; /* grand master role */ enum servo_state servo_state; tmv_t master_offset; tmv_t path_delay; tmv_t ingress_ts; struct tsproc *tsproc; struct freq_estimator fest; struct time_status_np status; double nrr; struct clock_description desc; struct clock_stats stats; int stats_interval; struct clockcheck *sanity_check; struct interface uds_interface; LIST_HEAD(clock_subscribers_head, clock_subscriber) subscribers; }; struct clock the_clock; static void handle_state_decision_event(struct clock *c); static int clock_resize_pollfd(struct clock *c, int new_nports); static void clock_remove_port(struct clock *c, struct port *p); static int cid_eq(struct ClockIdentity *a, struct ClockIdentity *b) { return 0 == memcmp(a, b, sizeof(*a)); } #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 static void remove_subscriber(struct clock_subscriber *s) { LIST_REMOVE(s, list); free(s); } static void clock_update_subscription(struct clock *c, struct ptp_message *req, uint8_t *bitmask, uint16_t duration) { struct clock_subscriber *s; int i, remove = 1; struct timespec now; for (i = 0; i < EVENT_BITMASK_CNT; i++) { if (bitmask[i]) { remove = 0; break; } } LIST_FOREACH(s, &c->subscribers, list) { if (!memcmp(&s->targetPortIdentity, &req->header.sourcePortIdentity, sizeof(struct PortIdentity))) { /* Found, update the transport address and event * mask. */ if (!remove) { s->addr = req->address; memcpy(s->events, bitmask, EVENT_BITMASK_CNT); clock_gettime(CLOCK_MONOTONIC, &now); s->expiration = now.tv_sec + duration; } else { remove_subscriber(s); } return; } } if (remove) return; /* Not present yet, add the subscriber. */ s = malloc(sizeof(*s)); if (!s) { pr_err("failed to allocate memory for a subscriber"); return; } s->targetPortIdentity = req->header.sourcePortIdentity; s->addr = req->address; memcpy(s->events, bitmask, EVENT_BITMASK_CNT); clock_gettime(CLOCK_MONOTONIC, &now); s->expiration = now.tv_sec + duration; s->sequenceId = 0; LIST_INSERT_HEAD(&c->subscribers, s, list); } static void clock_get_subscription(struct clock *c, struct ptp_message *req, uint8_t *bitmask, uint16_t *duration) { struct clock_subscriber *s; struct timespec now; LIST_FOREACH(s, &c->subscribers, list) { if (!memcmp(&s->targetPortIdentity, &req->header.sourcePortIdentity, sizeof(struct PortIdentity))) { memcpy(bitmask, s->events, EVENT_BITMASK_CNT); clock_gettime(CLOCK_MONOTONIC, &now); if (s->expiration < now.tv_sec) *duration = 0; else *duration = s->expiration - now.tv_sec; return; } } /* A client without entry means the client has no subscriptions. */ memset(bitmask, 0, EVENT_BITMASK_CNT); *duration = 0; } static void clock_flush_subscriptions(struct clock *c) { struct clock_subscriber *s, *tmp; LIST_FOREACH_SAFE(s, &c->subscribers, list, tmp) { remove_subscriber(s); } } static void clock_prune_subscriptions(struct clock *c) { struct clock_subscriber *s, *tmp; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); LIST_FOREACH_SAFE(s, &c->subscribers, list, tmp) { if (s->expiration <= now.tv_sec) { pr_info("subscriber %s timed out", pid2str(&s->targetPortIdentity)); remove_subscriber(s); } } } void clock_send_notification(struct clock *c, struct ptp_message *msg, int msglen, enum notification event) { unsigned int event_pos = event / 8; uint8_t mask = 1 << (event % 8); struct port *uds = c->uds_port; struct clock_subscriber *s; LIST_FOREACH(s, &c->subscribers, list) { if (!(s->events[event_pos] & mask)) continue; /* send event */ msg->header.sequenceId = htons(s->sequenceId); s->sequenceId++; msg->management.targetPortIdentity.clockIdentity = s->targetPortIdentity.clockIdentity; msg->management.targetPortIdentity.portNumber = htons(s->targetPortIdentity.portNumber); msg->address = s->addr; port_forward_to(uds, msg); } } void clock_destroy(struct clock *c) { struct port *p, *tmp; clock_flush_subscriptions(c); LIST_FOREACH_SAFE(p, &c->ports, list, tmp) { clock_remove_port(c, p); } port_close(c->uds_port); free(c->pollfd); if (c->clkid != CLOCK_REALTIME) { phc_close(c->clkid); } servo_destroy(c->servo); tsproc_destroy(c->tsproc); stats_destroy(c->stats.offset); stats_destroy(c->stats.freq); stats_destroy(c->stats.delay); if (c->sanity_check) clockcheck_destroy(c->sanity_check); memset(c, 0, sizeof(*c)); msg_cleanup(); } static int clock_fault_timeout(struct port *port, int set) { struct fault_interval i; if (!set) { pr_debug("clearing fault on port %d", port_number(port)); return port_set_fault_timer_lin(port, 0); } fault_interval(port, last_fault_type(port), &i); if (i.type == FTMO_LINEAR_SECONDS) { pr_debug("waiting %d seconds to clear fault on port %d", i.val, port_number(port)); return port_set_fault_timer_lin(port, i.val); } else if (i.type == FTMO_LOG2_SECONDS) { pr_debug("waiting 2^{%d} seconds to clear fault on port %d", i.val, port_number(port)); return port_set_fault_timer_log(port, 1, i.val); } pr_err("Unsupported fault interval type %d", i.type); return -1; } static void clock_freq_est_reset(struct clock *c) { c->fest.origin1 = tmv_zero(); c->fest.ingress1 = tmv_zero(); c->fest.count = 0; }; static void clock_management_send_error(struct port *p, struct ptp_message *msg, int error_id) { if (port_management_error(port_identity(p), p, msg, error_id)) pr_err("failed to send management error status"); } /* The 'p' and 'req' paremeters are needed for the GET actions that operate * on per-client datasets. If such actions do not apply to the caller, it is * allowed to pass both of them as NULL. */ static int clock_management_fill_response(struct clock *c, struct port *p, struct ptp_message *req, struct ptp_message *rsp, int id) { int datalen = 0, respond = 0; struct management_tlv *tlv; struct management_tlv_datum *mtd; struct time_status_np *tsn; struct grandmaster_settings_np *gsn; struct subscribe_events_np *sen; struct PTPText *text; tlv = (struct management_tlv *) rsp->management.suffix; tlv->type = TLV_MANAGEMENT; tlv->id = id; switch (id) { case TLV_USER_DESCRIPTION: text = (struct PTPText *) tlv->data; text->length = c->desc.userDescription.length; memcpy(text->text, c->desc.userDescription.text, text->length); datalen = 1 + text->length; respond = 1; break; case TLV_DEFAULT_DATA_SET: memcpy(tlv->data, &c->dds, sizeof(c->dds)); datalen = sizeof(c->dds); respond = 1; break; case TLV_CURRENT_DATA_SET: memcpy(tlv->data, &c->cur, sizeof(c->cur)); datalen = sizeof(c->cur); respond = 1; break; case TLV_PARENT_DATA_SET: memcpy(tlv->data, &c->dad.pds, sizeof(c->dad.pds)); datalen = sizeof(c->dad.pds); respond = 1; break; case TLV_TIME_PROPERTIES_DATA_SET: memcpy(tlv->data, &c->tds, sizeof(c->tds)); datalen = sizeof(c->tds); respond = 1; break; case TLV_PRIORITY1: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->dds.priority1; datalen = sizeof(*mtd); respond = 1; break; case TLV_PRIORITY2: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->dds.priority2; datalen = sizeof(*mtd); respond = 1; break; case TLV_DOMAIN: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->dds.domainNumber; datalen = sizeof(*mtd); respond = 1; break; case TLV_SLAVE_ONLY: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->dds.flags & DDS_SLAVE_ONLY; datalen = sizeof(*mtd); respond = 1; break; case TLV_CLOCK_ACCURACY: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->dds.clockQuality.clockAccuracy; datalen = sizeof(*mtd); respond = 1; break; case TLV_TRACEABILITY_PROPERTIES: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->tds.flags & (TIME_TRACEABLE|FREQ_TRACEABLE); datalen = sizeof(*mtd); respond = 1; break; case TLV_TIMESCALE_PROPERTIES: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->tds.flags & PTP_TIMESCALE; datalen = sizeof(*mtd); respond = 1; break; case TLV_TIME_STATUS_NP: tsn = (struct time_status_np *) tlv->data; tsn->master_offset = c->master_offset; tsn->ingress_time = tmv_to_nanoseconds(c->ingress_ts); tsn->cumulativeScaledRateOffset = (Integer32) (c->status.cumulativeScaledRateOffset + c->nrr * POW2_41 - POW2_41); tsn->scaledLastGmPhaseChange = c->status.scaledLastGmPhaseChange; tsn->gmTimeBaseIndicator = c->status.gmTimeBaseIndicator; tsn->lastGmPhaseChange = c->status.lastGmPhaseChange; if (cid_eq(&c->dad.pds.grandmasterIdentity, &c->dds.clockIdentity)) tsn->gmPresent = 0; else tsn->gmPresent = 1; tsn->gmIdentity = c->dad.pds.grandmasterIdentity; datalen = sizeof(*tsn); respond = 1; break; case TLV_GRANDMASTER_SETTINGS_NP: gsn = (struct grandmaster_settings_np *) tlv->data; gsn->clockQuality = c->dds.clockQuality; gsn->utc_offset = c->utc_offset; gsn->time_flags = c->time_flags; gsn->time_source = c->time_source; datalen = sizeof(*gsn); respond = 1; break; case TLV_SUBSCRIBE_EVENTS_NP: if (p != c->uds_port) { /* Only the UDS port allowed. */ break; } sen = (struct subscribe_events_np *)tlv->data; clock_get_subscription(c, req, sen->bitmask, &sen->duration); respond = 1; break; } if (respond) { if (datalen % 2) { tlv->data[datalen] = 0; datalen++; } tlv->length = sizeof(tlv->id) + datalen; rsp->header.messageLength += sizeof(*tlv) + datalen; rsp->tlv_count = 1; } return respond; } static int clock_management_get_response(struct clock *c, struct port *p, int id, struct ptp_message *req) { struct PortIdentity pid = port_identity(p); struct ptp_message *rsp; int respond; rsp = port_management_reply(pid, p, req); if (!rsp) { return 0; } respond = clock_management_fill_response(c, p, req, rsp, id); if (respond) port_prepare_and_send(p, rsp, 0); msg_put(rsp); return respond; } static int clock_management_set(struct clock *c, struct port *p, int id, struct ptp_message *req, int *changed) { int respond = 0; struct management_tlv *tlv; struct management_tlv_datum *mtd; struct grandmaster_settings_np *gsn; struct subscribe_events_np *sen; tlv = (struct management_tlv *) req->management.suffix; switch (id) { case TLV_PRIORITY1: mtd = (struct management_tlv_datum *) tlv->data; c->dds.priority1 = mtd->val; *changed = 1; respond = 1; break; case TLV_PRIORITY2: mtd = (struct management_tlv_datum *) tlv->data; c->dds.priority2 = mtd->val; *changed = 1; respond = 1; break; case TLV_GRANDMASTER_SETTINGS_NP: gsn = (struct grandmaster_settings_np *) tlv->data; c->dds.clockQuality = gsn->clockQuality; c->utc_offset = gsn->utc_offset; c->time_flags = gsn->time_flags; c->time_source = gsn->time_source; *changed = 1; respond = 1; break; case TLV_SUBSCRIBE_EVENTS_NP: sen = (struct subscribe_events_np *)tlv->data; clock_update_subscription(c, req, sen->bitmask, sen->duration); respond = 1; break; } if (respond && !clock_management_get_response(c, p, id, req)) pr_err("failed to send management set response"); return respond ? 1 : 0; } static void clock_stats_update(struct clock_stats *s, int64_t offset, double freq) { struct stats_result offset_stats, freq_stats, delay_stats; stats_add_value(s->offset, offset); stats_add_value(s->freq, freq); if (stats_get_num_values(s->offset) < s->max_count) return; stats_get_result(s->offset, &offset_stats); stats_get_result(s->freq, &freq_stats); /* Path delay stats are updated separately, they may be empty. */ if (!stats_get_result(s->delay, &delay_stats)) { pr_info("rms %4.0f max %4.0f " "freq %+6.0f +/- %3.0f " "delay %5.0f +/- %3.0f", offset_stats.rms, offset_stats.max_abs, freq_stats.mean, freq_stats.stddev, delay_stats.mean, delay_stats.stddev); } else { pr_info("rms %4.0f max %4.0f " "freq %+6.0f +/- %3.0f", offset_stats.rms, offset_stats.max_abs, freq_stats.mean, freq_stats.stddev); } stats_reset(s->offset); stats_reset(s->freq); stats_reset(s->delay); } static enum servo_state clock_no_adjust(struct clock *c, tmv_t ingress, tmv_t origin) { double fui; double ratio, freq; struct freq_estimator *f = &c->fest; enum servo_state state = SERVO_UNLOCKED; /* * The ratio of the local clock freqency to the master clock * is estimated by: * * (ingress_2 - ingress_1) / (origin_2 - origin_1) * * Both of the origin time estimates include the path delay, * but we assume that the path delay is in fact constant. * By leaving out the path delay altogther, we can avoid the * error caused by our imperfect path delay measurement. */ if (!f->ingress1) { f->ingress1 = ingress; f->origin1 = origin; return state; } f->count++; if (f->count < f->max_count) { return state; } if (tmv_eq(ingress, f->ingress1)) { pr_warning("bad timestamps in rate ratio calculation"); return state; } ratio = tmv_dbl(tmv_sub(origin, f->origin1)) / tmv_dbl(tmv_sub(ingress, f->ingress1)); freq = (1.0 - ratio) * 1e9; if (c->stats.max_count > 1) { clock_stats_update(&c->stats, tmv_to_nanoseconds(c->master_offset), freq); } else { pr_info("master offset %10" PRId64 " s%d freq %+7.0f " "path delay %9" PRId64, tmv_to_nanoseconds(c->master_offset), state, freq, tmv_to_nanoseconds(c->path_delay)); } fui = 1.0 + (c->status.cumulativeScaledRateOffset + 0.0) / POW2_41; pr_debug("peer/local %.9f", c->nrr); pr_debug("fup_info %.9f", fui); pr_debug("product %.9f", fui * c->nrr); pr_debug("sum-1 %.9f", fui + c->nrr - 1.0); pr_debug("master/local %.9f", ratio); pr_debug("diff %+.9f", ratio - (fui + c->nrr - 1.0)); f->ingress1 = ingress; f->origin1 = origin; f->count = 0; return state; } static void clock_update_grandmaster(struct clock *c) { struct parentDS *pds = &c->dad.pds; memset(&c->cur, 0, sizeof(c->cur)); memset(c->ptl, 0, sizeof(c->ptl)); pds->parentPortIdentity.clockIdentity = c->dds.clockIdentity; pds->parentPortIdentity.portNumber = 0; pds->grandmasterIdentity = c->dds.clockIdentity; pds->grandmasterClockQuality = c->dds.clockQuality; pds->grandmasterPriority1 = c->dds.priority1; pds->grandmasterPriority2 = c->dds.priority2; c->dad.path_length = 0; c->tds.currentUtcOffset = c->utc_offset; c->tds.flags = c->time_flags; c->tds.timeSource = c->time_source; } static void clock_update_slave(struct clock *c) { struct parentDS *pds = &c->dad.pds; struct ptp_message *msg = TAILQ_FIRST(&c->best->messages); c->cur.stepsRemoved = 1 + c->best->dataset.stepsRemoved; pds->parentPortIdentity = c->best->dataset.sender; pds->grandmasterIdentity = msg->announce.grandmasterIdentity; pds->grandmasterClockQuality = msg->announce.grandmasterClockQuality; pds->grandmasterPriority1 = msg->announce.grandmasterPriority1; pds->grandmasterPriority2 = msg->announce.grandmasterPriority2; c->tds.currentUtcOffset = msg->announce.currentUtcOffset; c->tds.flags = msg->header.flagField[1]; c->tds.timeSource = msg->announce.timeSource; if (!(c->tds.flags & PTP_TIMESCALE)) { pr_warning("foreign master not using PTP timescale"); } if (c->tds.currentUtcOffset < CURRENT_UTC_OFFSET) { pr_warning("running in a temporal vortex"); } } static int clock_utc_correct(struct clock *c, tmv_t ingress) { struct timespec offset; int utc_offset, leap, clock_leap; uint64_t ts; if (!c->utc_timescale) return 0; if (c->tds.flags & UTC_OFF_VALID && c->tds.flags & TIME_TRACEABLE) { utc_offset = c->tds.currentUtcOffset; } else if (c->tds.currentUtcOffset > CURRENT_UTC_OFFSET) { utc_offset = c->tds.currentUtcOffset; } else { utc_offset = CURRENT_UTC_OFFSET; } if (c->tds.flags & LEAP_61) { leap = 1; } else if (c->tds.flags & LEAP_59) { leap = -1; } else { leap = 0; } /* Handle leap seconds. */ if ((leap || c->leap_set) && c->clkid == CLOCK_REALTIME) { /* If the clock will be stepped, the time stamp has to be the target time. Ignore possible 1 second error in utc_offset. */ if (c->servo_state == SERVO_UNLOCKED) { ts = tmv_to_nanoseconds(tmv_sub(ingress, c->master_offset)); if (c->tds.flags & PTP_TIMESCALE) ts -= utc_offset * NS_PER_SEC; } else { ts = tmv_to_nanoseconds(ingress); } /* Suspend clock updates in the last second before midnight. */ if (is_utc_ambiguous(ts)) { pr_info("clock update suspended due to leap second"); return -1; } clock_leap = leap_second_status(ts, c->leap_set, &leap, &utc_offset); if (c->leap_set != clock_leap) { if (c->kernel_leap) sysclk_set_leap(clock_leap); else servo_leap(c->servo, clock_leap); c->leap_set = clock_leap; } } /* Update TAI-UTC offset of the system clock if valid and traceable. */ if (c->tds.flags & UTC_OFF_VALID && c->tds.flags & TIME_TRACEABLE && c->utc_offset_set != utc_offset && c->clkid == CLOCK_REALTIME) { sysclk_set_tai_offset(utc_offset); c->utc_offset_set = utc_offset; } if (!(c->tds.flags & PTP_TIMESCALE)) return 0; offset.tv_sec = utc_offset; offset.tv_nsec = 0; /* Local clock is UTC, but master is TAI. */ c->master_offset = tmv_add(c->master_offset, timespec_to_tmv(offset)); return 0; } static int forwarding(struct clock *c, struct port *p) { enum port_state ps = port_state(p); switch (ps) { case PS_MASTER: case PS_GRAND_MASTER: case PS_SLAVE: case PS_UNCALIBRATED: case PS_PRE_MASTER: return 1; default: break; } if (p == c->uds_port && ps != PS_FAULTY) { return 1; } return 0; } /* public methods */ UInteger8 clock_class(struct clock *c) { return c->dds.clockQuality.clockClass; } struct config *clock_config(struct clock *c) { return c->config; } static int clock_add_port(struct clock *c, int phc_index, enum timestamp_type timestamping, struct interface *iface) { struct port *p, *piter, *lastp = NULL; if (clock_resize_pollfd(c, c->nports + 1)) return -1; p = port_open(phc_index, timestamping, ++c->last_port_number, iface, c); if (!p) { /* No need to shrink pollfd */ return -1; } LIST_FOREACH(piter, &c->ports, list) lastp = piter; if (lastp) LIST_INSERT_AFTER(lastp, p, list); else LIST_INSERT_HEAD(&c->ports, p, list); c->nports++; clock_fda_changed(c); return 0; } static void clock_remove_port(struct clock *c, struct port *p) { /* Do not call clock_resize_pollfd, it's pointless to shrink * the allocated memory at this point, clock_destroy will free * it all anyway. This function is usable from other parts of * the code, but even then we don't mind if pollfd is larger * than necessary. */ LIST_REMOVE(p, list); c->nports--; clock_fda_changed(c); port_close(p); } struct clock *clock_create(struct config *config, int phc_index, struct interfaces_head *ifaces, struct default_ds *dds) { enum timestamp_type timestamping = config_get_int(config, NULL, "time_stamping"); int fadj = 0, max_adj = 0, sw_ts = timestamping == TS_SOFTWARE ? 1 : 0; enum servo_type servo = config_get_int(config, NULL, "clock_servo"); struct clock *c = &the_clock; struct port *p; char phc[32]; struct interface *udsif = &c->uds_interface; struct interface *iface; struct timespec ts; int sfl; clock_gettime(CLOCK_REALTIME, &ts); srandom(ts.tv_sec ^ ts.tv_nsec); if (c->nports) clock_destroy(c); snprintf(udsif->name, sizeof(udsif->name), "%s", config_get_string(config, NULL, "uds_address")); if (config_set_section_int(config, udsif->name, "delay_mechanism", DM_AUTO)) { return NULL; } if (config_set_section_int(config, udsif->name, "network_transport", TRANS_UDS)) { return NULL; } if (config_set_section_int(config, udsif->name, "delay_filter_length", 1)) { return NULL; } c->config = config; c->free_running = config_get_int(config, NULL, "free_running"); c->freq_est_interval = config_get_int(config, NULL, "freq_est_interval"); c->grand_master_capable = config_get_int(config, NULL, "gmCapable"); c->kernel_leap = config_get_int(config, NULL, "kernel_leap"); c->utc_offset = CURRENT_UTC_OFFSET; c->time_source = config_get_int(config, NULL, "timeSource"); c->desc = dds->clock_desc; if (c->free_running) { c->clkid = CLOCK_INVALID; if (timestamping == TS_SOFTWARE || timestamping == TS_LEGACY_HW) { c->utc_timescale = 1; } } else if (phc_index >= 0) { snprintf(phc, 31, "/dev/ptp%d", phc_index); c->clkid = phc_open(phc); if (c->clkid == CLOCK_INVALID) { pr_err("Failed to open %s: %m", phc); return NULL; } max_adj = phc_max_adj(c->clkid); if (!max_adj) { pr_err("clock is not adjustable"); return NULL; } clockadj_init(c->clkid); } else { c->clkid = CLOCK_REALTIME; c->utc_timescale = 1; clockadj_init(c->clkid); max_adj = sysclk_max_freq(); sysclk_set_leap(0); } c->utc_offset_set = 0; c->leap_set = 0; c->time_flags = c->utc_timescale ? 0 : PTP_TIMESCALE; if (c->clkid != CLOCK_INVALID) { fadj = (int) clockadj_get_freq(c->clkid); /* Due to a bug in older kernels, the reading may silently fail and return 0. Set the frequency back to make sure fadj is the actual frequency of the clock. */ clockadj_set_freq(c->clkid, fadj); } c->servo = servo_create(c->config, servo, -fadj, max_adj, sw_ts); if (!c->servo) { pr_err("Failed to create clock servo"); return NULL; } c->servo_state = SERVO_UNLOCKED; c->servo_type = servo; c->tsproc = tsproc_create(config_get_int(config, NULL, "tsproc_mode"), config_get_int(config, NULL, "delay_filter"), config_get_int(config, NULL, "delay_filter_length")); if (!c->tsproc) { pr_err("Failed to create time stamp processor"); return NULL; } c->nrr = 1.0; c->stats_interval = config_get_int(config, NULL, "summary_interval"); c->stats.offset = stats_create(); c->stats.freq = stats_create(); c->stats.delay = stats_create(); if (!c->stats.offset || !c->stats.freq || !c->stats.delay) { pr_err("failed to create stats"); return NULL; } sfl = config_get_int(config, NULL, "sanity_freq_limit"); if (sfl) { c->sanity_check = clockcheck_create(sfl); if (!c->sanity_check) { pr_err("Failed to create clock sanity check"); return NULL; } } c->dds = dds->dds; /* Initialize the parentDS. */ clock_update_grandmaster(c); c->dad.pds.parentStats = 0; c->dad.pds.observedParentOffsetScaledLogVariance = 0xffff; c->dad.pds.observedParentClockPhaseChangeRate = 0x7fffffff; c->dad.ptl = c->ptl; clock_sync_interval(c, 0); LIST_INIT(&c->subscribers); LIST_INIT(&c->ports); c->last_port_number = 0; /* * Create the UDS interface. */ if (clock_resize_pollfd(c, 0)) { pr_err("failed to allocate pollfd"); return NULL; } c->uds_port = port_open(phc_index, timestamping, 0, udsif, c); if (!c->uds_port) { pr_err("failed to open the UDS port"); return NULL; } clock_fda_changed(c); /* Create the ports. */ STAILQ_FOREACH(iface, ifaces, list) { if (clock_add_port(c, phc_index, timestamping, iface)) { pr_err("failed to open port %s", iface->name); return NULL; } } c->dds.numberPorts = c->nports; LIST_FOREACH(p, &c->ports, list) { port_dispatch(p, EV_INITIALIZE, 0); } port_dispatch(c->uds_port, EV_INITIALIZE, 0); return c; } struct dataset *clock_best_foreign(struct clock *c) { return c->best ? &c->best->dataset : NULL; } struct port *clock_best_port(struct clock *c) { return c->best ? c->best->port : NULL; } struct dataset *clock_default_ds(struct clock *c) { struct dataset *out = &c->default_dataset; struct defaultDS *in = &c->dds; out->priority1 = in->priority1; out->identity = in->clockIdentity; out->quality = in->clockQuality; out->priority2 = in->priority2; out->stepsRemoved = 0; out->sender.clockIdentity = in->clockIdentity; out->sender.portNumber = 0; out->receiver.clockIdentity = in->clockIdentity; out->receiver.portNumber = 0; return out; } UInteger8 clock_domain_number(struct clock *c) { return c->dds.domainNumber; } void clock_follow_up_info(struct clock *c, struct follow_up_info_tlv *f) { c->status.cumulativeScaledRateOffset = f->cumulativeScaledRateOffset; c->status.scaledLastGmPhaseChange = f->scaledLastGmPhaseChange; c->status.gmTimeBaseIndicator = f->gmTimeBaseIndicator; memcpy(&c->status.lastGmPhaseChange, &f->lastGmPhaseChange, sizeof(c->status.lastGmPhaseChange)); } int clock_gm_capable(struct clock *c) { return c->grand_master_capable; } struct ClockIdentity clock_identity(struct clock *c) { return c->dds.clockIdentity; } static int clock_resize_pollfd(struct clock *c, int new_nports) { struct pollfd *new_pollfd; /* Need to allocate one extra block of fds for uds */ new_pollfd = realloc(c->pollfd, (new_nports + 1) * N_CLOCK_PFD * sizeof(struct pollfd)); if (!new_pollfd) return -1; c->pollfd = new_pollfd; return 0; } static void clock_fill_pollfd(struct pollfd *dest, struct port *p) { struct fdarray *fda; int i; fda = port_fda(p); for (i = 0; i < N_POLLFD; i++) { dest[i].fd = fda->fd[i]; dest[i].events = POLLIN|POLLPRI; } dest[i].fd = port_fault_fd(p); dest[i].events = POLLIN|POLLPRI; } static void clock_check_pollfd(struct clock *c) { struct port *p; struct pollfd *dest = c->pollfd; if (c->pollfd_valid) return; LIST_FOREACH(p, &c->ports, list) { clock_fill_pollfd(dest, p); dest += N_CLOCK_PFD; } clock_fill_pollfd(dest, c->uds_port); c->pollfd_valid = 1; } void clock_fda_changed(struct clock *c) { c->pollfd_valid = 0; } static int clock_do_forward_mgmt(struct clock *c, struct port *in, struct port *out, struct ptp_message *msg, int *pre_sent) { if (in == out || !forwarding(c, out)) return 0; if (!*pre_sent) { /* delay calling msg_pre_send until * actually forwarding */ msg_pre_send(msg); *pre_sent = 1; } return port_forward(out, msg); } static void clock_forward_mgmt_msg(struct clock *c, struct port *p, struct ptp_message *msg) { struct port *piter; int pdulen = 0, msg_ready = 0; if (forwarding(c, p) && msg->management.boundaryHops) { pdulen = msg->header.messageLength; msg->management.boundaryHops--; LIST_FOREACH(piter, &c->ports, list) { if (clock_do_forward_mgmt(c, p, piter, msg, &msg_ready)) pr_err("port %d: management forward failed", port_number(piter)); } if (clock_do_forward_mgmt(c, p, c->uds_port, msg, &msg_ready)) pr_err("uds port: management forward failed"); if (msg_ready) { msg_post_recv(msg, pdulen); msg->management.boundaryHops++; } } } int clock_manage(struct clock *c, struct port *p, struct ptp_message *msg) { int changed = 0, res, answers; struct port *piter; struct management_tlv *mgt; struct ClockIdentity *tcid, wildcard = { {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} }; /* Forward this message out all eligible ports. */ clock_forward_mgmt_msg(c, p, msg); /* Apply this message to the local clock and ports. */ tcid = &msg->management.targetPortIdentity.clockIdentity; if (!cid_eq(tcid, &wildcard) && !cid_eq(tcid, &c->dds.clockIdentity)) { return changed; } if (msg->tlv_count != 1) { return changed; } mgt = (struct management_tlv *) msg->management.suffix; /* The correct length according to the management ID is checked in tlv.c, but management TLVs with empty bodies are also received successfully to support GETs and CMDs. At this point the TLV either has the correct length or length 2. */ switch (management_action(msg)) { case GET: if (clock_management_get_response(c, p, mgt->id, msg)) return changed; break; case SET: if (mgt->length == 2 && mgt->id != TLV_NULL_MANAGEMENT) { clock_management_send_error(p, msg, TLV_WRONG_LENGTH); return changed; } if (p != c->uds_port) { /* Sorry, only allowed on the UDS port. */ clock_management_send_error(p, msg, TLV_NOT_SUPPORTED); return changed; } if (clock_management_set(c, p, mgt->id, msg, &changed)) return changed; break; case COMMAND: break; default: return changed; } switch (mgt->id) { case TLV_PORT_PROPERTIES_NP: if (p != c->uds_port) { /* Only the UDS port allowed. */ clock_management_send_error(p, msg, TLV_NOT_SUPPORTED); return 0; } } switch (mgt->id) { case TLV_USER_DESCRIPTION: case TLV_SAVE_IN_NON_VOLATILE_STORAGE: case TLV_RESET_NON_VOLATILE_STORAGE: case TLV_INITIALIZE: case TLV_FAULT_LOG: case TLV_FAULT_LOG_RESET: case TLV_DEFAULT_DATA_SET: case TLV_CURRENT_DATA_SET: case TLV_PARENT_DATA_SET: case TLV_TIME_PROPERTIES_DATA_SET: case TLV_PRIORITY1: case TLV_PRIORITY2: case TLV_DOMAIN: case TLV_SLAVE_ONLY: case TLV_TIME: case TLV_CLOCK_ACCURACY: case TLV_UTC_PROPERTIES: case TLV_TRACEABILITY_PROPERTIES: case TLV_TIMESCALE_PROPERTIES: case TLV_PATH_TRACE_LIST: case TLV_PATH_TRACE_ENABLE: case TLV_GRANDMASTER_CLUSTER_TABLE: case TLV_ACCEPTABLE_MASTER_TABLE: case TLV_ACCEPTABLE_MASTER_MAX_TABLE_SIZE: case TLV_ALTERNATE_TIME_OFFSET_ENABLE: case TLV_ALTERNATE_TIME_OFFSET_NAME: case TLV_ALTERNATE_TIME_OFFSET_MAX_KEY: case TLV_ALTERNATE_TIME_OFFSET_PROPERTIES: case TLV_TRANSPARENT_CLOCK_DEFAULT_DATA_SET: case TLV_PRIMARY_DOMAIN: case TLV_TIME_STATUS_NP: case TLV_GRANDMASTER_SETTINGS_NP: case TLV_SUBSCRIBE_EVENTS_NP: clock_management_send_error(p, msg, TLV_NOT_SUPPORTED); break; default: answers = 0; LIST_FOREACH(piter, &c->ports, list) { res = port_manage(piter, p, msg); if (res < 0) return changed; if (res > 0) answers++; } if (!answers) { /* IEEE 1588 Interpretation #21 suggests to use * TLV_WRONG_VALUE for ports that do not exist */ clock_management_send_error(p, msg, TLV_WRONG_VALUE); } break; } return changed; } void clock_notify_event(struct clock *c, enum notification event) { struct port *uds = c->uds_port; struct PortIdentity pid = port_identity(uds); struct ptp_message *msg; UInteger16 msg_len; int id; switch (event) { /* set id */ default: return; } /* targetPortIdentity and sequenceId will be filled by * clock_send_notification */ msg = port_management_notify(pid, uds); if (!msg) return; if (!clock_management_fill_response(c, NULL, NULL, msg, id)) goto err; msg_len = msg->header.messageLength; if (msg_pre_send(msg)) goto err; clock_send_notification(c, msg, msg_len, event); err: msg_put(msg); } struct parent_ds *clock_parent_ds(struct clock *c) { return &c->dad; } struct PortIdentity clock_parent_identity(struct clock *c) { return c->dad.pds.parentPortIdentity; } int clock_poll(struct clock *c) { int cnt, err, i, sde = 0; enum fsm_event event; struct pollfd *cur; struct port *p; clock_check_pollfd(c); cnt = poll(c->pollfd, (c->nports + 1) * N_CLOCK_PFD, -1); if (cnt < 0) { if (EINTR == errno) { return 0; } else { pr_emerg("poll failed"); return -1; } } else if (!cnt) { return 0; } cur = c->pollfd; LIST_FOREACH(p, &c->ports, list) { /* Let the ports handle their events. */ for (i = err = 0; i < N_POLLFD && !err; i++) { if (cur[i].revents & (POLLIN|POLLPRI)) { event = port_event(p, i); if (EV_STATE_DECISION_EVENT == event) sde = 1; if (EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES == event) sde = 1; err = port_dispatch(p, event, 0); /* Clear any fault after a little while. */ if (PS_FAULTY == port_state(p)) { clock_fault_timeout(p, 1); break; } } } /* Check the fault timer. */ if (cur[N_POLLFD].revents & (POLLIN|POLLPRI)) { clock_fault_timeout(p, 0); port_dispatch(p, EV_FAULT_CLEARED, 0); } cur += N_CLOCK_PFD; } /* Check the UDS port. */ for (i = 0; i < N_POLLFD; i++) { if (cur[i].revents & (POLLIN|POLLPRI)) { event = port_event(c->uds_port, i); if (EV_STATE_DECISION_EVENT == event) sde = 1; } } if (sde) handle_state_decision_event(c); clock_prune_subscriptions(c); return 0; } void clock_path_delay(struct clock *c, tmv_t req, tmv_t rx) { tsproc_up_ts(c->tsproc, req, rx); if (tsproc_update_delay(c->tsproc, &c->path_delay)) return; c->cur.meanPathDelay = tmv_to_TimeInterval(c->path_delay); if (c->stats.delay) stats_add_value(c->stats.delay, tmv_to_nanoseconds(c->path_delay)); } void clock_peer_delay(struct clock *c, tmv_t ppd, tmv_t req, tmv_t rx, double nrr) { c->path_delay = ppd; c->nrr = nrr; tsproc_set_delay(c->tsproc, ppd); tsproc_up_ts(c->tsproc, req, rx); if (c->stats.delay) stats_add_value(c->stats.delay, tmv_to_nanoseconds(ppd)); } int clock_slave_only(struct clock *c) { return c->dds.flags & DDS_SLAVE_ONLY; } UInteger16 clock_steps_removed(struct clock *c) { return c->cur.stepsRemoved; } int clock_switch_phc(struct clock *c, int phc_index) { struct servo *servo; int fadj, max_adj; clockid_t clkid; char phc[32]; snprintf(phc, 31, "/dev/ptp%d", phc_index); clkid = phc_open(phc); if (clkid == CLOCK_INVALID) { pr_err("Switching PHC, failed to open %s: %m", phc); return -1; } max_adj = phc_max_adj(clkid); if (!max_adj) { pr_err("Switching PHC, clock is not adjustable"); phc_close(clkid); return -1; } fadj = (int) clockadj_get_freq(clkid); clockadj_set_freq(clkid, fadj); servo = servo_create(c->config, c->servo_type, -fadj, max_adj, 0); if (!servo) { pr_err("Switching PHC, failed to create clock servo"); phc_close(clkid); return -1; } phc_close(c->clkid); servo_destroy(c->servo); c->clkid = clkid; c->servo = servo; c->servo_state = SERVO_UNLOCKED; return 0; } enum servo_state clock_synchronize(struct clock *c, tmv_t ingress, tmv_t origin) { double adj, weight; enum servo_state state = SERVO_UNLOCKED; c->ingress_ts = ingress; tsproc_down_ts(c->tsproc, origin, ingress); if (tsproc_update_offset(c->tsproc, &c->master_offset, &weight)) return state; if (clock_utc_correct(c, ingress)) return c->servo_state; c->cur.offsetFromMaster = tmv_to_TimeInterval(c->master_offset); if (c->free_running) return clock_no_adjust(c, ingress, origin); adj = servo_sample(c->servo, tmv_to_nanoseconds(c->master_offset), tmv_to_nanoseconds(ingress), weight, &state); c->servo_state = state; if (c->stats.max_count > 1) { clock_stats_update(&c->stats, tmv_to_nanoseconds(c->master_offset), adj); } else { pr_info("master offset %10" PRId64 " s%d freq %+7.0f " "path delay %9" PRId64, tmv_to_nanoseconds(c->master_offset), state, adj, tmv_to_nanoseconds(c->path_delay)); } tsproc_set_clock_rate_ratio(c->tsproc, clock_rate_ratio(c)); switch (state) { case SERVO_UNLOCKED: break; case SERVO_JUMP: clockadj_set_freq(c->clkid, -adj); clockadj_step(c->clkid, -tmv_to_nanoseconds(c->master_offset)); c->ingress_ts = tmv_zero(); if (c->sanity_check) { clockcheck_set_freq(c->sanity_check, -adj); clockcheck_step(c->sanity_check, -tmv_to_nanoseconds(c->master_offset)); } tsproc_reset(c->tsproc, 0); break; case SERVO_LOCKED: clockadj_set_freq(c->clkid, -adj); if (c->clkid == CLOCK_REALTIME) sysclk_set_sync(); if (c->sanity_check) clockcheck_set_freq(c->sanity_check, -adj); break; } return state; } void clock_sync_interval(struct clock *c, int n) { int shift; shift = c->freq_est_interval - n; if (shift < 0) shift = 0; else if (shift >= sizeof(int) * 8) { shift = sizeof(int) * 8 - 1; pr_warning("freq_est_interval is too long"); } c->fest.max_count = (1 << shift); shift = c->stats_interval - n; if (shift < 0) shift = 0; else if (shift >= sizeof(int) * 8) { shift = sizeof(int) * 8 - 1; pr_warning("summary_interval is too long"); } c->stats.max_count = (1 << shift); servo_sync_interval(c->servo, n < 0 ? 1.0 / (1 << -n) : 1 << n); } struct timePropertiesDS *clock_time_properties(struct clock *c) { return &c->tds; } void clock_update_time_properties(struct clock *c, struct timePropertiesDS tds) { c->tds = tds; } static void handle_state_decision_event(struct clock *c) { struct foreign_clock *best = NULL, *fc; struct ClockIdentity best_id; struct port *piter; int fresh_best = 0; LIST_FOREACH(piter, &c->ports, list) { fc = port_compute_best(piter); if (!fc) continue; if (!best || dscmp(&fc->dataset, &best->dataset) > 0) best = fc; } if (best) { best_id = best->dataset.identity; } else { best_id = c->dds.clockIdentity; } pr_notice("selected best master clock %s", cid2str(&best_id)); if (!cid_eq(&best_id, &c->best_id)) { clock_freq_est_reset(c); tsproc_reset(c->tsproc, 1); c->ingress_ts = tmv_zero(); c->path_delay = 0; c->nrr = 1.0; fresh_best = 1; } c->best = best; c->best_id = best_id; LIST_FOREACH(piter, &c->ports, list) { enum port_state ps; enum fsm_event event; ps = bmc_state_decision(c, piter); switch (ps) { case PS_LISTENING: event = EV_NONE; break; case PS_GRAND_MASTER: pr_notice("assuming the grand master role"); clock_update_grandmaster(c); event = EV_RS_GRAND_MASTER; break; case PS_MASTER: event = EV_RS_MASTER; break; case PS_PASSIVE: event = EV_RS_PASSIVE; break; case PS_SLAVE: clock_update_slave(c); event = EV_RS_SLAVE; break; default: event = EV_FAULT_DETECTED; break; } port_dispatch(piter, event, fresh_best); } } struct clock_description *clock_description(struct clock *c) { return &c->desc; } int clock_num_ports(struct clock *c) { return c->nports; } void clock_check_ts(struct clock *c, struct timespec ts) { if (c->sanity_check && clockcheck_sample(c->sanity_check, ts.tv_sec * NS_PER_SEC + ts.tv_nsec)) { servo_reset(c->servo); } } double clock_rate_ratio(struct clock *c) { return servo_rate_ratio(c->servo); } linuxptp-1.6/clock.h000066400000000000000000000223361257727010700145250ustar00rootroot00000000000000/** * @file clock.h * @brief Implements a PTP clock. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_CLOCK_H #define HAVE_CLOCK_H #include "dm.h" #include "ds.h" #include "config.h" #include "notification.h" #include "servo.h" #include "tlv.h" #include "tmv.h" #include "transport.h" struct ptp_message; /*forward declaration*/ /** Opaque type. */ struct clock; /** * Obtains a reference to the best foreign master of a clock. * @param c The clock instance. * @return A pointer to the data set of the foreign master, * or NULL if none has been yet discovered. */ struct dataset *clock_best_foreign(struct clock *c); /** * Obtains a reference to the port with the best foreign master. * @param c The clock instance. * @return A pointer to the port with the best foreign master, * or NULL if none has been yet discovered. */ struct port *clock_best_port(struct clock *c); /** * Obtain the clockClass attribute from a clock. * @param c The clock instance. * @return The value of the clock's class. */ UInteger8 clock_class(struct clock *c); /** * Obtains a reference to the configuration database. * @param c The clock instance. * @return A pointer to the configuration, without fail. */ struct config *clock_config(struct clock *c); /** * Create a clock instance. There can only be one clock in any system, * so subsequent calls will destroy the previous clock instance. * * @param config Pointer to the configuration database. * @param phc_index PTP hardware clock device to use. * Pass -1 to select CLOCK_REALTIME. * @param ifaces A queue of network interfaces. * @param dds A pointer to a default data set for the clock. * @return A pointer to the single global clock instance. */ struct clock *clock_create(struct config *config, int phc_index, struct interfaces_head *ifaces, struct default_ds *dds); /** * Obtains a clock's default data set. * @param c The clock instance. * @return A pointer to the data set of the clock. */ struct dataset *clock_default_ds(struct clock *c); /** * Free all of the resources associated with a clock. * @param c The clock instance. */ void clock_destroy(struct clock *c); /** * Obtain the domain number from a clock's default data set. * @param c The clock instance. * @return The PTP domain number. */ UInteger8 clock_domain_number(struct clock *c); /** * Provide the follow_up info TLV from a slave port. * @param c The clock instance. * @param f Pointer to the TLV. */ void clock_follow_up_info(struct clock *c, struct follow_up_info_tlv *f); /** * Obtain the gmCapable flag from a clock's default data set. * This function is specific to the 802.1AS standard. * @param c The clock instance. * @return One if the clock is capable of becoming grand master, zero otherwise. */ int clock_gm_capable(struct clock *c); /** * Obtain a clock's identity from its default data set. * @param c The clock instance. * @return The clock's identity. */ struct ClockIdentity clock_identity(struct clock *c); /** * Informs clock that a file descriptor of one of its ports changed. The * clock will rebuild its array of file descriptors to poll. * @param c The clock instance. */ void clock_fda_changed(struct clock *c); /** * Manage the clock according to a given message. * @param c The clock instance. * @param p The port on which the message arrived. * @param msg A management message. * @return One if the management action caused a change that * implies a state decision event, zero otherwise. */ int clock_manage(struct clock *c, struct port *p, struct ptp_message *msg); /** * Send notification about an event to all subscribers. * @param c The clock instance. * @param msg The PTP message to send, in network byte order. * @param msglen The length of the message in bytes. * @param event The event that occured. */ void clock_send_notification(struct clock *c, struct ptp_message *msg, int msglen, enum notification event); /** * Construct and send notification to subscribers about an event that * occured on the clock. * @param c The clock instance. * @param event The identification of the event. */ void clock_notify_event(struct clock *c, enum notification event); /** * Obtain a clock's parent data set. * @param c The clock instance. * @return A pointer to the parent data set of the clock. */ struct parent_ds *clock_parent_ds(struct clock *c); /** * Obtain the parent port identity from a clock's parent data set. * @param c The clock instance. * @return The parent port identity. */ struct PortIdentity clock_parent_identity(struct clock *c); /** * Provide a data point to estimate the path delay. * @param c The clock instance. * @param req The transmission time of the delay request message. * @param rx The reception time of the delay request message, * as reported in the delay response message, including * correction. */ void clock_path_delay(struct clock *c, tmv_t req, tmv_t rx); /** * Provide the estimated peer delay from a slave port. * @param c The clock instance. * @param ppd The peer delay as measured on a slave port. * @param req The transmission time of the pdelay request message. * @param rx The reception time of the pdelay request message. * @param nrr The neighbor rate ratio as measured on a slave port. */ void clock_peer_delay(struct clock *c, tmv_t ppd, tmv_t req, tmv_t rx, double nrr); /** * Poll for events and dispatch them. * @param c A pointer to a clock instance obtained with clock_create(). * @return Zero on success, non-zero otherwise. */ int clock_poll(struct clock *c); /** * Obtain the slave-only flag from a clock's default data set. * @param c The clock instance. * @return The value of the clock's slave-only flag. */ int clock_slave_only(struct clock *c); /** * Obtain the steps removed field from a clock's current data set. * @param c The clock instance. * @return The value of the clock's steps removed field. */ UInteger16 clock_steps_removed(struct clock *c); /** * Switch to a new PTP Hardware Clock, for use with the "jbod" mode. * @param c The clock instance. * @param phc_index The index of the PHC device to use. * @return Zero on success, non-zero otherwise. */ int clock_switch_phc(struct clock *c, int phc_index); /** * Provide a data point to synchronize the clock. * @param c The clock instance to synchronize. * @param ingress The ingress time stamp on the sync message. * @param origin The reported transmission time of the sync message, including any corrections. * @param correction1 The correction field of the sync message. * @param correction2 The correction field of the follow up message. * Pass zero in the case of one step operation. * @return The state of the clock's servo. */ enum servo_state clock_synchronize(struct clock *c, tmv_t ingress, tmv_t origin); /** * Inform a slaved clock about the master's sync interval. * @param c The clock instance. * @param n The logarithm base two of the sync interval. */ void clock_sync_interval(struct clock *c, int n); /** * Obtain a clock's time properties data set. * @param c The clock instance. * @return A pointer to the time properties data set of the clock. */ struct timePropertiesDS *clock_time_properties(struct clock *c); /** * Update a clock's time properties data set. * @param c The clock instance. * @param tds The new time properties data set for the clock. */ void clock_update_time_properties(struct clock *c, struct timePropertiesDS tds); /** * Obtain a clock's description. * @param c The clock instance. * @return A pointer to the clock_description of the clock. */ struct clock_description *clock_description(struct clock *c); /** * Obtain the number of ports a clock has, excluding the UDS port. * @param c The clock instance. * @return The number of ports. */ int clock_num_ports(struct clock *c); /** * Perform a sanity check on a time stamp made by a clock. * @param c The clock instance. * @param ts The time stamp. */ void clock_check_ts(struct clock *c, struct timespec ts); /** * Obtain ratio between master's frequency and current clock frequency. * @param c The clock instance. * @return The rate ratio, 1.0 is returned when not known. */ double clock_rate_ratio(struct clock *c); #endif linuxptp-1.6/clockadj.c000066400000000000000000000112421257727010700151710ustar00rootroot00000000000000/** * @file clockadj.c * @note Copyright (C) 2013 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "clockadj.h" #include "missing.h" #include "print.h" #define NS_PER_SEC 1000000000LL static int realtime_leap_bit; static long realtime_hz; static long realtime_nominal_tick; void clockadj_init(clockid_t clkid) { #ifdef _SC_CLK_TCK if (clkid == CLOCK_REALTIME) { /* This is USER_HZ in the kernel. */ realtime_hz = sysconf(_SC_CLK_TCK); if (realtime_hz > 0) { /* This is TICK_USEC in the kernel. */ realtime_nominal_tick = (1000000 + realtime_hz / 2) / realtime_hz; } } #endif } void clockadj_set_freq(clockid_t clkid, double freq) { struct timex tx; memset(&tx, 0, sizeof(tx)); /* With system clock set also the tick length. */ if (clkid == CLOCK_REALTIME && realtime_nominal_tick) { tx.modes |= ADJ_TICK; tx.tick = round(freq / 1e3 / realtime_hz) + realtime_nominal_tick; freq -= 1e3 * realtime_hz * (tx.tick - realtime_nominal_tick); } tx.modes |= ADJ_FREQUENCY; tx.freq = (long) (freq * 65.536); if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to adjust the clock: %m"); } double clockadj_get_freq(clockid_t clkid) { double f = 0.0; struct timex tx; memset(&tx, 0, sizeof(tx)); if (clock_adjtime(clkid, &tx) < 0) { pr_err("failed to read out the clock frequency adjustment: %m"); } else { f = tx.freq / 65.536; if (clkid == CLOCK_REALTIME && realtime_nominal_tick) f += 1e3 * realtime_hz * (tx.tick - realtime_nominal_tick); } return f; } void clockadj_step(clockid_t clkid, int64_t step) { struct timex tx; int sign = 1; if (step < 0) { sign = -1; step *= -1; } memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_SETOFFSET | ADJ_NANO; tx.time.tv_sec = sign * (step / NS_PER_SEC); tx.time.tv_usec = sign * (step % NS_PER_SEC); /* * The value of a timeval is the sum of its fields, but the * field tv_usec must always be non-negative. */ if (tx.time.tv_usec < 0) { tx.time.tv_sec -= 1; tx.time.tv_usec += 1000000000; } if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to step clock: %m"); } void sysclk_set_leap(int leap) { clockid_t clkid = CLOCK_REALTIME; struct timex tx; const char *m = NULL; memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_STATUS; switch (leap) { case -1: tx.status = STA_DEL; m = "clock set to delete leap second at midnight (UTC)"; break; case 1: tx.status = STA_INS; m = "clock set to insert leap second at midnight (UTC)"; break; default: tx.status = 0; } if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to set the clock status: %m"); else if (m) pr_notice("%s", m); realtime_leap_bit = tx.status; } void sysclk_set_tai_offset(int offset) { clockid_t clkid = CLOCK_REALTIME; struct timex tx; memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_TAI; tx.constant = offset; if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to set TAI offset: %m"); } int sysclk_max_freq(void) { clockid_t clkid = CLOCK_REALTIME; int f = 0; struct timex tx; memset(&tx, 0, sizeof(tx)); if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to read out the clock maximum adjustment: %m"); else f = tx.tolerance / 65.536; if (!f) f = 500000; /* The kernel allows the tick length to be adjusted up to 10%. But use it only if the overall frequency of the clock can be adjusted continuously with the tick and freq fields (i.e. hz <= 1000). */ if (realtime_nominal_tick && 2 * f >= 1000 * realtime_hz) f = realtime_nominal_tick / 10 * 1000 * realtime_hz; return f; } void sysclk_set_sync(void) { clockid_t clkid = CLOCK_REALTIME; struct timex tx; memset(&tx, 0, sizeof(tx)); /* Clear the STA_UNSYNC flag from the status and keep the maxerror value (which is increased automatically by 500 ppm) below 16 seconds to avoid getting the STA_UNSYNC flag back. */ tx.modes = ADJ_STATUS | ADJ_MAXERROR; tx.status = realtime_leap_bit; if (clock_adjtime(clkid, &tx) < 0) pr_err("failed to set clock status and maximum error: %m"); } linuxptp-1.6/clockadj.h000066400000000000000000000047111257727010700152010ustar00rootroot00000000000000/** * @file clockadj.h * @brief Wraps clock_adjtime functionality. * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_CLOCKADJ_H #define HAVE_CLOCKADJ_H #include #include /** * Initialize state needed when adjusting or reading the clock. * @param clkid A clock ID obtained using phc_open() or CLOCK_REALTIME. */ void clockadj_init(clockid_t clkid); /** * Set clock's frequency offset. * @param clkid A clock ID obtained using phc_open() or CLOCK_REALTIME. * @param freq The frequency offset in parts per billion (ppb). */ void clockadj_set_freq(clockid_t clkid, double freq); /** * Read clock's frequency offset. * @param clkid A clock ID obtained using phc_open() or CLOCK_REALTIME. * @return The frequency offset in parts per billion (ppb). */ double clockadj_get_freq(clockid_t clkid); /** * Step clock's time. * @param clkid A clock ID obtained using phc_open() or CLOCK_REALTIME. * @param step The time step in nanoseconds. */ void clockadj_step(clockid_t clkid, int64_t step); /** * Set the system clock to insert/delete leap second at midnight. * @param leap +1 to insert leap second, -1 to delete leap second, * 0 to reset the leap state. */ void sysclk_set_leap(int leap); /** * Set the TAI offset of the system clock to have correct CLOCK_TAI. * @param offset The TAI-UTC offset in seconds. */ void sysclk_set_tai_offset(int offset); /** * Read maximum frequency adjustment of the system clock (CLOCK_REALTIME). * @return The maximum frequency adjustment in parts per billion (ppb). */ int sysclk_max_freq(void); /** * Mark the system clock as synchronized to let the kernel synchronize * the real-time clock (RTC) to it. */ void sysclk_set_sync(void); #endif linuxptp-1.6/clockcheck.c000066400000000000000000000064551257727010700155220ustar00rootroot00000000000000/** * @file clockcheck.c * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "clockcheck.h" #include "print.h" #define CHECK_MIN_INTERVAL 100000000 #define CHECK_MAX_FREQ 900000000 struct clockcheck { /* Sanity frequency limit */ int freq_limit; /* Frequency was set at least once */ int freq_known; /* Current frequency */ int current_freq; /* Maximum and minimum frequency since last update */ int max_freq; int min_freq; uint64_t last_ts; uint64_t last_mono_ts; }; struct clockcheck *clockcheck_create(int freq_limit) { struct clockcheck *cc; cc = calloc(1, sizeof(*cc)); if (!cc) return NULL; cc->freq_limit = freq_limit; cc->max_freq = -CHECK_MAX_FREQ; cc->min_freq = CHECK_MAX_FREQ; return cc; } int clockcheck_sample(struct clockcheck *cc, uint64_t ts) { uint64_t mono_ts; int64_t interval, mono_interval; double max_foffset, min_foffset; struct timespec now; int ret = 0; /* Check the sanity of the synchronized clock by comparing its uncorrected frequency with the system monotonic clock. If the synchronized clock is the system clock, the measured frequency offset will be the current frequency correction of the system clock. */ if (!cc->freq_known) return ret; interval = (int64_t)ts - cc->last_ts; if (interval >= 0 && interval < CHECK_MIN_INTERVAL) return ret; clock_gettime(CLOCK_MONOTONIC, &now); mono_ts = now.tv_sec * 1000000000LL + now.tv_nsec; mono_interval = (int64_t)mono_ts - cc->last_mono_ts; if (mono_interval < CHECK_MIN_INTERVAL) return ret; if (cc->last_ts && cc->max_freq <= CHECK_MAX_FREQ) { max_foffset = 1e9 * (interval / (1.0 + cc->min_freq / 1e9) / mono_interval - 1.0); min_foffset = 1e9 * (interval / (1.0 + cc->max_freq / 1e9) / mono_interval - 1.0); if (min_foffset > cc->freq_limit) { pr_warning("clockcheck: clock jumped forward or" " running faster than expected!"); ret = 1; } else if (max_foffset < -cc->freq_limit) { pr_warning("clockcheck: clock jumped backward or" " running slower than expected!"); ret = 1; } } cc->last_mono_ts = mono_ts; cc->last_ts = ts; cc->max_freq = cc->min_freq = cc->current_freq; return ret; } void clockcheck_set_freq(struct clockcheck *cc, int freq) { if (cc->max_freq < freq) cc->max_freq = freq; if (cc->min_freq > freq) cc->min_freq = freq; cc->current_freq = freq; cc->freq_known = 1; } void clockcheck_step(struct clockcheck *cc, int64_t step) { if (cc->last_ts) cc->last_ts += step; } void clockcheck_destroy(struct clockcheck *cc) { free(cc); } linuxptp-1.6/clockcheck.h000066400000000000000000000044241257727010700155210ustar00rootroot00000000000000/** * @file clockcheck.h * @brief Implements clock sanity checking. * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_CLOCKCHECK_H #define HAVE_CLOCKCHECK_H #include /** Opaque type */ struct clockcheck; /** * Create a new instance of a clock sanity check. * @param freq_limit The maximum allowed frequency offset between uncorrected * clock and the system monotonic clock in ppb. * @return A pointer to a new clock check on success, NULL otherwise. */ struct clockcheck *clockcheck_create(int freq_limit); /** * Perform the sanity check on a time stamp. * @param cc Pointer to a clock check obtained via @ref clockcheck_create(). * @param ts Time stamp made by the clock in nanoseconds. * @return Zero if ts passed the check, non-zero otherwise. */ int clockcheck_sample(struct clockcheck *cc, uint64_t ts); /** * Inform clock check about changes in current frequency of the clock. * @param cc Pointer to a clock check obtained via @ref clockcheck_create(). * @param freq Frequency correction applied to the clock in ppb. */ void clockcheck_set_freq(struct clockcheck *cc, int freq); /** * Inform clock check that the clock was stepped. * @param cc Pointer to a clock check obtained via @ref clockcheck_create(). * @param step Step correction applied to the clock in nanoseconds. */ void clockcheck_step(struct clockcheck *cc, int64_t step); /** * Destroy a clock check. * @param cc Pointer to a clock check obtained via @ref clockcheck_create(). */ void clockcheck_destroy(struct clockcheck *cc); #endif linuxptp-1.6/config.c000066400000000000000000000512271257727010700146730ustar00rootroot00000000000000/** * @file config.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "config.h" #include "ether.h" #include "hash.h" #include "print.h" #include "util.h" enum config_section { GLOBAL_SECTION, PORT_SECTION, UNKNOWN_SECTION, }; enum config_type { CFG_TYPE_INT, CFG_TYPE_DOUBLE, CFG_TYPE_ENUM, CFG_TYPE_STRING, }; struct config_enum { const char *label; int value; }; typedef union { int i; double d; char *s; } any_t; #define CONFIG_LABEL_SIZE 32 #define CFG_ITEM_STATIC (1 << 0) /* statically allocated, not to be freed */ #define CFG_ITEM_LOCKED (1 << 1) /* command line value, may not be changed */ #define CFG_ITEM_PORT (1 << 2) /* item may appear in port sections */ #define CFG_ITEM_DYNSTR (1 << 4) /* string value dynamically allocated */ struct config_item { char label[CONFIG_LABEL_SIZE]; enum config_type type; struct config_enum *tab; unsigned int flags; any_t val; any_t min; any_t max; }; #define N_CONFIG_ITEMS (sizeof(config_tab) / sizeof(config_tab[0])) #define CONFIG_ITEM_DBL(_label, _port, _default, _min, _max) { \ .label = _label, \ .type = CFG_TYPE_DOUBLE, \ .flags = _port ? CFG_ITEM_PORT : 0, \ .val.d = _default, \ .min.d = _min, \ .max.d = _max, \ } #define CONFIG_ITEM_ENUM(_label, _port, _default, _table) { \ .label = _label, \ .type = CFG_TYPE_ENUM, \ .flags = _port ? CFG_ITEM_PORT : 0, \ .tab = _table, \ .val.i = _default, \ } #define CONFIG_ITEM_INT(_label, _port, _default, _min, _max) { \ .label = _label, \ .type = CFG_TYPE_INT, \ .flags = _port ? CFG_ITEM_PORT : 0, \ .val.i = _default, \ .min.i = _min, \ .max.i = _max, \ } #define CONFIG_ITEM_STRING(_label, _port, _default) { \ .label = _label, \ .type = CFG_TYPE_STRING, \ .flags = _port ? CFG_ITEM_PORT : 0, \ .val.s = _default, \ } #define GLOB_ITEM_DBL(label, _default, min, max) \ CONFIG_ITEM_DBL(label, 0, _default, min, max) #define GLOB_ITEM_ENU(label, _default, table) \ CONFIG_ITEM_ENUM(label, 0, _default, table) #define GLOB_ITEM_INT(label, _default, min, max) \ CONFIG_ITEM_INT(label, 0, _default, min, max) #define GLOB_ITEM_STR(label, _default) \ CONFIG_ITEM_STRING(label, 0, _default) #define PORT_ITEM_DBL(label, _default, min, max) \ CONFIG_ITEM_DBL(label, 1, _default, min, max) #define PORT_ITEM_ENU(label, _default, table) \ CONFIG_ITEM_ENUM(label, 1, _default, table) #define PORT_ITEM_INT(label, _default, min, max) \ CONFIG_ITEM_INT(label, 1, _default, min, max) #define PORT_ITEM_STR(label, _default) \ CONFIG_ITEM_STRING(label, 1, _default) static struct config_enum clock_servo_enu[] = { { "pi", CLOCK_SERVO_PI }, { "linreg", CLOCK_SERVO_LINREG }, { "ntpshm", CLOCK_SERVO_NTPSHM }, { "nullf", CLOCK_SERVO_NULLF }, { NULL, 0 }, }; static struct config_enum delay_filter_enu[] = { { "moving_average", FILTER_MOVING_AVERAGE }, { "moving_median", FILTER_MOVING_MEDIAN }, { NULL, 0 }, }; static struct config_enum delay_mech_enu[] = { { "Auto", DM_AUTO }, { "E2E", DM_E2E }, { "P2P", DM_P2P }, { NULL, 0 }, }; static struct config_enum nw_trans_enu[] = { { "L2", TRANS_IEEE_802_3 }, { "UDPv4", TRANS_UDP_IPV4 }, { "UDPv6", TRANS_UDP_IPV6 }, { NULL, 0 }, }; static struct config_enum timestamping_enu[] = { { "hardware", TS_HARDWARE }, { "software", TS_SOFTWARE }, { "legacy", TS_LEGACY_HW }, { NULL, 0 }, }; static struct config_enum tsproc_enu[] = { { "filter", TSPROC_FILTER }, { "raw", TSPROC_RAW }, { "filter_weight", TSPROC_FILTER_WEIGHT }, { "raw_weight", TSPROC_RAW_WEIGHT }, { NULL, 0 }, }; struct config_item config_tab[] = { PORT_ITEM_INT("announceReceiptTimeout", 3, 2, UINT8_MAX), GLOB_ITEM_INT("assume_two_step", 0, 0, 1), PORT_ITEM_INT("boundary_clock_jbod", 0, 0, 1), GLOB_ITEM_INT("check_fup_sync", 0, 0, 1), GLOB_ITEM_INT("clockAccuracy", 0xfe, 0, UINT8_MAX), GLOB_ITEM_INT("clockClass", 248, 0, UINT8_MAX), GLOB_ITEM_ENU("clock_servo", CLOCK_SERVO_PI, clock_servo_enu), PORT_ITEM_INT("delayAsymmetry", 0, INT_MIN, INT_MAX), PORT_ITEM_ENU("delay_filter", FILTER_MOVING_MEDIAN, delay_filter_enu), PORT_ITEM_INT("delay_filter_length", 10, 1, INT_MAX), PORT_ITEM_ENU("delay_mechanism", DM_E2E, delay_mech_enu), GLOB_ITEM_INT("domainNumber", 0, 0, 127), PORT_ITEM_INT("egressLatency", 0, INT_MIN, INT_MAX), PORT_ITEM_INT("fault_badpeernet_interval", 16, INT32_MIN, INT32_MAX), PORT_ITEM_INT("fault_reset_interval", 4, INT8_MIN, INT8_MAX), GLOB_ITEM_DBL("first_step_threshold", 0.00002, 0.0, DBL_MAX), PORT_ITEM_INT("follow_up_info", 0, 0, 1), GLOB_ITEM_INT("free_running", 0, 0, 1), PORT_ITEM_INT("freq_est_interval", 1, 0, INT_MAX), GLOB_ITEM_INT("gmCapable", 1, 0, 1), PORT_ITEM_INT("hybrid_e2e", 0, 0, 1), PORT_ITEM_INT("ingressLatency", 0, INT_MIN, INT_MAX), GLOB_ITEM_INT("kernel_leap", 1, 0, 1), PORT_ITEM_INT("logAnnounceInterval", 1, INT8_MIN, INT8_MAX), PORT_ITEM_INT("logMinDelayReqInterval", 0, INT8_MIN, INT8_MAX), PORT_ITEM_INT("logMinPdelayReqInterval", 0, INT8_MIN, INT8_MAX), PORT_ITEM_INT("logSyncInterval", 0, INT8_MIN, INT8_MAX), GLOB_ITEM_INT("logging_level", LOG_INFO, PRINT_LEVEL_MIN, PRINT_LEVEL_MAX), GLOB_ITEM_STR("manufacturerIdentity", "00:00:00"), GLOB_ITEM_INT("max_frequency", 900000000, 0, INT_MAX), PORT_ITEM_INT("min_neighbor_prop_delay", -20000000, INT_MIN, -1), PORT_ITEM_INT("neighborPropDelayThresh", 20000000, 0, INT_MAX), PORT_ITEM_ENU("network_transport", TRANS_UDP_IPV4, nw_trans_enu), GLOB_ITEM_INT("ntpshm_segment", 0, INT_MIN, INT_MAX), GLOB_ITEM_INT("offsetScaledLogVariance", 0xffff, 0, UINT16_MAX), PORT_ITEM_INT("path_trace_enabled", 0, 0, 1), GLOB_ITEM_DBL("pi_integral_const", 0.0, 0.0, DBL_MAX), GLOB_ITEM_DBL("pi_integral_exponent", 0.4, -DBL_MAX, DBL_MAX), GLOB_ITEM_DBL("pi_integral_norm_max", 0.3, DBL_MIN, 2.0), GLOB_ITEM_DBL("pi_integral_scale", 0.0, 0.0, DBL_MAX), GLOB_ITEM_DBL("pi_proportional_const", 0.0, 0.0, DBL_MAX), GLOB_ITEM_DBL("pi_proportional_exponent", -0.3, -DBL_MAX, DBL_MAX), GLOB_ITEM_DBL("pi_proportional_norm_max", 0.7, DBL_MIN, 1.0), GLOB_ITEM_DBL("pi_proportional_scale", 0.0, 0.0, DBL_MAX), GLOB_ITEM_INT("priority1", 128, 0, UINT8_MAX), GLOB_ITEM_INT("priority2", 128, 0, UINT8_MAX), GLOB_ITEM_STR("productDescription", ";;"), PORT_ITEM_STR("ptp_dst_mac", "01:1B:19:00:00:00"), PORT_ITEM_STR("p2p_dst_mac", "01:80:C2:00:00:0E"), GLOB_ITEM_STR("revisionData", ";;"), GLOB_ITEM_INT("sanity_freq_limit", 200000000, 0, INT_MAX), GLOB_ITEM_INT("slaveOnly", 0, 0, 1), GLOB_ITEM_DBL("step_threshold", 0.0, 0.0, DBL_MAX), GLOB_ITEM_INT("summary_interval", 0, INT_MIN, INT_MAX), PORT_ITEM_INT("syncReceiptTimeout", 0, 0, UINT8_MAX), GLOB_ITEM_INT("timeSource", INTERNAL_OSCILLATOR, 0x10, 0xfe), GLOB_ITEM_ENU("time_stamping", TS_HARDWARE, timestamping_enu), PORT_ITEM_INT("transportSpecific", 0, 0, 0x0F), PORT_ITEM_ENU("tsproc_mode", TSPROC_FILTER, tsproc_enu), GLOB_ITEM_INT("twoStepFlag", 1, 0, 1), GLOB_ITEM_INT("tx_timestamp_timeout", 1, 1, INT_MAX), PORT_ITEM_INT("udp_ttl", 1, 1, 255), PORT_ITEM_INT("udp6_scope", 0x0E, 0x00, 0x0F), GLOB_ITEM_STR("uds_address", "/var/run/ptp4l"), GLOB_ITEM_INT("use_syslog", 1, 0, 1), GLOB_ITEM_STR("userDescription", ""), GLOB_ITEM_INT("verbose", 0, 0, 1), }; static enum parser_result parse_fault_interval(struct config *cfg, const char *section, const char *option, const char *value); static struct config_item *config_section_item(struct config *cfg, const char *section, const char *name) { char buf[CONFIG_LABEL_SIZE + MAX_IFNAME_SIZE]; snprintf(buf, sizeof(buf), "%s.%s", section, name); return hash_lookup(cfg->htab, buf); } static struct config_item *config_global_item(struct config *cfg, const char *name) { return config_section_item(cfg, "global", name); } static struct config_item *config_find_item(struct config *cfg, const char *section, const char *name) { struct config_item *ci; if (section) { ci = config_section_item(cfg, section, name); if (ci) { return ci; } } return config_global_item(cfg, name); } static struct config_item *config_item_alloc(struct config *cfg, const char *section, const char *name, enum config_type type) { struct config_item *ci; char buf[CONFIG_LABEL_SIZE + MAX_IFNAME_SIZE]; ci = calloc(1, sizeof(*ci)); if (!ci) { fprintf(stderr, "low memory\n"); return NULL; } strncpy(ci->label, name, CONFIG_LABEL_SIZE - 1); ci->type = type; snprintf(buf, sizeof(buf), "%s.%s", section, ci->label); if (hash_insert(cfg->htab, buf, ci)) { fprintf(stderr, "low memory or duplicate item %s\n", name); free(ci); return NULL; } return ci; } static void config_item_free(void *ptr) { struct config_item *ci = ptr; if (ci->type == CFG_TYPE_STRING && ci->flags & CFG_ITEM_DYNSTR) free(ci->val.s); if (ci->flags & CFG_ITEM_STATIC) return; free(ci); } static enum parser_result parse_section_line(char *s, enum config_section *section) { if (!strcasecmp(s, "[global]")) { *section = GLOBAL_SECTION; } else if (s[0] == '[') { char c; *section = PORT_SECTION; /* Replace square brackets with white space. */ while (0 != (c = *s)) { if (c == '[' || c == ']') *s = ' '; s++; } } else return NOT_PARSED; return PARSED_OK; } static enum parser_result parse_item(struct config *cfg, const char *section, const char *option, const char *value) { enum parser_result r; struct config_item *cgi, *dst; struct config_enum *cte; double df; int val; r = parse_fault_interval(cfg, section, option, value); if (r != NOT_PARSED) return r; r = BAD_VALUE; /* If there is no default value, then the option is bogus. */ cgi = config_global_item(cfg, option); if (!cgi) { return NOT_PARSED; } switch (cgi->type) { case CFG_TYPE_INT: r = get_ranged_int(value, &val, cgi->min.i, cgi->max.i); break; case CFG_TYPE_DOUBLE: r = get_ranged_double(value, &df, cgi->min.d, cgi->max.d); break; case CFG_TYPE_ENUM: for (cte = cgi->tab; cte->label; cte++) { if (!strcasecmp(cte->label, value)) { val = cte->value; r = PARSED_OK; break; } } break; case CFG_TYPE_STRING: r = PARSED_OK; break; } if (r != PARSED_OK) { return r; } if (section) { if (!(cgi->flags & CFG_ITEM_PORT)) { return NOT_PARSED; } /* Create or update this port specific item. */ dst = config_section_item(cfg, section, option); if (!dst) { dst = config_item_alloc(cfg, section, option, cgi->type); if (!dst) { return NOT_PARSED; } } } else if (cgi->flags & CFG_ITEM_LOCKED) { /* This global option was set on the command line. */ return PARSED_OK; } else { /* Update the global default value. */ dst = cgi; } switch (dst->type) { case CFG_TYPE_INT: case CFG_TYPE_ENUM: dst->val.i = val; break; case CFG_TYPE_DOUBLE: dst->val.d = df; break; case CFG_TYPE_STRING: if (dst->flags & CFG_ITEM_DYNSTR) { free(dst->val.s); } dst->val.s = strdup(value); if (!dst->val.s) { pr_err("low memory"); return NOT_PARSED; } dst->flags |= CFG_ITEM_DYNSTR; break; } return PARSED_OK; } static enum parser_result parse_fault_interval(struct config *cfg, const char *section, const char *option, const char *value) { int i, val; const char *str, *fault_options[2] = { "fault_badpeernet_interval", "fault_reset_interval", }; int fault_values[2] = { 0, FRI_ASAP, }; if (strcasecmp("ASAP", value)) { return NOT_PARSED; } for (i = 0; i < 2; i++) { str = fault_options[i]; val = fault_values[i]; if (!strcmp(option, str)) { if (config_set_section_int(cfg, section, str, val)) { pr_err("bug: failed to set option %s!", option); exit(-1); } return PARSED_OK; } } return NOT_PARSED; } static enum parser_result parse_setting_line(char *line, const char **option, const char **value) { *option = line; while (!isspace(line[0])) { if (line[0] == '\0') return NOT_PARSED; line++; } while (isspace(line[0])) { line[0] = '\0'; line++; } *value = line; return PARSED_OK; } static void check_deprecated_options(const char **option) { const char *new_option = NULL; if (!strcmp(*option, "pi_offset_const")) { new_option = "step_threshold"; } else if (!strcmp(*option, "pi_f_offset_const")) { new_option = "first_step_threshold"; } else if (!strcmp(*option, "pi_max_frequency")) { new_option = "max_frequency"; } if (new_option) { fprintf(stderr, "option %s is deprecated, please use %s instead\n", *option, new_option); *option = new_option; } } int config_read(char *name, struct config *cfg) { enum config_section current_section = UNKNOWN_SECTION; enum parser_result parser_res; FILE *fp; char buf[1024], *line, *c; const char *option, *value; struct interface *current_port = NULL; int line_num; fp = 0 == strncmp(name, "-", 2) ? stdin : fopen(name, "r"); if (!fp) { fprintf(stderr, "failed to open configuration file %s: %m\n", name); return -1; } for (line_num = 1; fgets(buf, sizeof(buf), fp); line_num++) { c = buf; /* skip whitespace characters */ while (isspace(*c)) c++; /* ignore empty lines and comments */ if (*c == '#' || *c == '\n' || *c == '\0') continue; line = c; /* remove trailing whitespace characters and \n */ c += strlen(line) - 1; while (c > line && (*c == '\n' || isspace(*c))) *c-- = '\0'; if (parse_section_line(line, ¤t_section) == PARSED_OK) { if (current_section == PORT_SECTION) { char port[17]; if (1 != sscanf(line, " %16s", port)) { fprintf(stderr, "could not parse port name on line %d\n", line_num); goto parse_error; } current_port = config_create_interface(port, cfg); if (!current_port) goto parse_error; config_init_interface(current_port, cfg); } continue; } if (current_section == UNKNOWN_SECTION) { fprintf(stderr, "line %d is not in a section\n", line_num); goto parse_error; } if (parse_setting_line(line, &option, &value)) { fprintf(stderr, "could not parse line %d in %s section\n", line_num, current_section == GLOBAL_SECTION ? "global" : current_port->name); goto parse_error; } check_deprecated_options(&option); parser_res = parse_item(cfg, current_section == GLOBAL_SECTION ? NULL : current_port->name, option, value); switch (parser_res) { case PARSED_OK: break; case NOT_PARSED: fprintf(stderr, "unknown option %s at line %d in %s section\n", option, line_num, current_section == GLOBAL_SECTION ? "global" : current_port->name); goto parse_error; case BAD_VALUE: fprintf(stderr, "%s is a bad value for option %s at line %d\n", value, option, line_num); goto parse_error; case MALFORMED: fprintf(stderr, "%s is a malformed value for option %s at line %d\n", value, option, line_num); goto parse_error; case OUT_OF_RANGE: fprintf(stderr, "%s is an out of range value for option %s at line %d\n", value, option, line_num); goto parse_error; } } fclose(fp); return 0; parse_error: fprintf(stderr, "failed to parse configuration file %s\n", name); fclose(fp); return -2; } struct interface *config_create_interface(char *name, struct config *cfg) { struct interface *iface; /* only create each interface once (by name) */ STAILQ_FOREACH(iface, &cfg->interfaces, list) { if (0 == strncmp(name, iface->name, MAX_IFNAME_SIZE)) return iface; } iface = calloc(1, sizeof(struct interface)); if (!iface) { fprintf(stderr, "cannot allocate memory for a port\n"); return NULL; } strncpy(iface->name, name, MAX_IFNAME_SIZE); STAILQ_INSERT_TAIL(&cfg->interfaces, iface, list); return iface; } void config_init_interface(struct interface *iface, struct config *cfg) { sk_get_ts_info(iface->name, &iface->ts_info); } struct config *config_create(void) { char buf[CONFIG_LABEL_SIZE + 8]; struct config_item *ci; struct config *cfg; int i; cfg = calloc(1, sizeof(*cfg)); if (!cfg) { return NULL; } STAILQ_INIT(&cfg->interfaces); cfg->htab = hash_create(); if (!cfg->htab) { free(cfg); return NULL; } /* Populate the hash table with global defaults. */ for (i = 0; i < N_CONFIG_ITEMS; i++) { ci = &config_tab[i]; ci->flags |= CFG_ITEM_STATIC; snprintf(buf, sizeof(buf), "global.%s", ci->label); if (hash_insert(cfg->htab, buf, ci)) { fprintf(stderr, "duplicate item %s\n", ci->label); goto fail; } } /* Perform a Built In Self Test.*/ for (i = 0; i < N_CONFIG_ITEMS; i++) { ci = &config_tab[i]; ci = config_global_item(cfg, ci->label); if (ci != &config_tab[i]) { fprintf(stderr, "config BIST failed at %s\n", config_tab[i].label); goto fail; } } return cfg; fail: hash_destroy(cfg->htab, NULL); free(cfg); return NULL; } void config_destroy(struct config *cfg) { struct interface *iface; while ((iface = STAILQ_FIRST(&cfg->interfaces))) { STAILQ_REMOVE_HEAD(&cfg->interfaces, list); free(iface); } hash_destroy(cfg->htab, config_item_free); free(cfg); } double config_get_double(struct config *cfg, const char *section, const char *option) { struct config_item *ci = config_find_item(cfg, section, option); if (!ci || ci->type != CFG_TYPE_DOUBLE) { pr_err("bug: config option %s missing or invalid!", option); exit(-1); } pr_debug("config item %s.%s is %f", section, option, ci->val.d); return ci->val.d; } int config_get_int(struct config *cfg, const char *section, const char *option) { struct config_item *ci = config_find_item(cfg, section, option); if (!ci) { pr_err("bug: config option %s missing!", option); exit(-1); } switch (ci->type) { case CFG_TYPE_DOUBLE: case CFG_TYPE_STRING: pr_err("bug: config option %s type mismatch!", option); exit(-1); case CFG_TYPE_INT: case CFG_TYPE_ENUM: break; } pr_debug("config item %s.%s is %d", section, option, ci->val.i); return ci->val.i; } char *config_get_string(struct config *cfg, const char *section, const char *option) { struct config_item *ci = config_find_item(cfg, section, option); if (!ci || ci->type != CFG_TYPE_STRING) { pr_err("bug: config option %s missing or invalid!", option); exit(-1); } pr_debug("config item %s.%s is '%s'", section, option, ci->val.s); return ci->val.s; } int config_set_double(struct config *cfg, const char *option, double val) { struct config_item *ci = config_find_item(cfg, NULL, option); if (!ci || ci->type != CFG_TYPE_DOUBLE) { pr_err("bug: config option %s missing or invalid!", option); return -1; } ci->flags |= CFG_ITEM_LOCKED; ci->val.d = val; pr_debug("locked item global.%s as %f", option, ci->val.d); return 0; } int config_set_section_int(struct config *cfg, const char *section, const char *option, int val) { struct config_item *cgi, *dst; cgi = config_find_item(cfg, NULL, option); if (!cgi) { pr_err("bug: config option %s missing!", option); return -1; } switch (cgi->type) { case CFG_TYPE_DOUBLE: case CFG_TYPE_STRING: pr_err("bug: config option %s type mismatch!", option); return -1; case CFG_TYPE_INT: case CFG_TYPE_ENUM: break; } if (!section) { cgi->flags |= CFG_ITEM_LOCKED; cgi->val.i = val; pr_debug("locked item global.%s as %d", option, cgi->val.i); return 0; } /* Create or update this port specific item. */ dst = config_section_item(cfg, section, option); if (!dst) { dst = config_item_alloc(cfg, section, option, cgi->type); if (!dst) { return -1; } } dst->val.i = val; pr_debug("section item %s.%s now %d", section, option, dst->val.i); return 0; } int config_set_string(struct config *cfg, const char *option, const char *val) { struct config_item *ci = config_find_item(cfg, NULL, option); if (!ci || ci->type != CFG_TYPE_STRING) { pr_err("bug: config option %s missing or invalid!", option); return -1; } ci->flags |= CFG_ITEM_LOCKED; if (ci->flags & CFG_ITEM_DYNSTR) { free(ci->val.s); } ci->val.s = strdup(val); if (!ci->val.s) { pr_err("low memory"); return -1; } ci->flags |= CFG_ITEM_DYNSTR; pr_debug("locked item global.%s as '%s'", option, ci->val.s); return 0; } linuxptp-1.6/config.h000066400000000000000000000045621257727010700147000ustar00rootroot00000000000000/** * @file config.h * @brief Configuration file code * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_CONFIG_H #define HAVE_CONFIG_H #include #include "ds.h" #include "dm.h" #include "filter.h" #include "transport.h" #include "servo.h" #include "sk.h" #define MAX_IFNAME_SIZE 108 /* = UNIX_PATH_MAX */ /** Defines a network interface, with PTP options. */ struct interface { STAILQ_ENTRY(interface) list; char name[MAX_IFNAME_SIZE + 1]; struct sk_ts_info ts_info; }; struct config { /* configured interfaces */ STAILQ_HEAD(interfaces_head, interface) interfaces; /* hash of all non-legacy items */ struct hash *htab; }; int config_read(char *name, struct config *cfg); struct interface *config_create_interface(char *name, struct config *cfg); void config_init_interface(struct interface *iface, struct config *cfg); void config_destroy(struct config *cfg); /* New, hash table based methods: */ struct config *config_create(void); double config_get_double(struct config *cfg, const char *section, const char *option); int config_get_int(struct config *cfg, const char *section, const char *option); char *config_get_string(struct config *cfg, const char *section, const char *option); int config_set_double(struct config *cfg, const char *option, double val); int config_set_section_int(struct config *cfg, const char *section, const char *option, int val); static inline int config_set_int(struct config *cfg, const char *option, int val) { return config_set_section_int(cfg, NULL, option, val); } int config_set_string(struct config *cfg, const char *option, const char *val); #endif linuxptp-1.6/contain.h000066400000000000000000000022131257727010700150550ustar00rootroot00000000000000/** * @file contain.h * @brief Implements pseudo object oriented features. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_CONTAIN_H #define HAVE_CONTAIN_H #include /* * This macro borrowed from the Linux kernel. */ #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type, member) ); \ }) #endif linuxptp-1.6/ddt.h000066400000000000000000000051661257727010700142070ustar00rootroot00000000000000/** * @file ddt.h * @brief Derived data types * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_DDT_H #define HAVE_DDT_H #include "pdt.h" #define PACKED __attribute__((packed)) typedef Integer64 TimeInterval; /* nanoseconds << 16 */ /** On the wire time stamp format. */ struct Timestamp { uint16_t seconds_msb; /* 16 bits + */ uint32_t seconds_lsb; /* 32 bits = 48 bits*/ UInteger32 nanoseconds; } PACKED; /** Internal binary time stamp format. */ struct timestamp { uint64_t sec; UInteger32 nsec; }; struct ClockIdentity { Octet id[8]; }; struct PortIdentity { struct ClockIdentity clockIdentity; UInteger16 portNumber; } PACKED; struct PortAddress { Enumeration16 networkProtocol; UInteger16 addressLength; Octet address[0]; } PACKED; struct PhysicalAddress { UInteger16 length; Octet address[0]; } PACKED; struct ClockQuality { UInteger8 clockClass; Enumeration8 clockAccuracy; UInteger16 offsetScaledLogVariance; } PACKED; struct TLV { Enumeration16 type; UInteger16 length; /* must be even */ Octet value[0]; } PACKED; struct PTPText { UInteger8 length; Octet text[0]; } PACKED; /* A static_ptp_text is like a PTPText but includes space to store the * text inside the struct. The text array must always be * null-terminated. Also tracks a maximum number of symbols. Note in * UTF-8, # symbols != # bytes. */ #define MAX_PTP_OCTETS 255 struct static_ptp_text { /* null-terminated array of UTF-8 symbols */ Octet text[MAX_PTP_OCTETS + 1]; /* number of used bytes in text, not including trailing null */ int length; /* max number of UTF-8 symbols that can be in text */ int max_symbols; }; struct FaultRecord { UInteger16 faultRecordLength; struct Timestamp faultTime; Enumeration8 severityCode; struct PTPText faultName; struct PTPText faultValue; struct PTPText faultDescription; }; #endif linuxptp-1.6/default.cfg000066400000000000000000000030021257727010700153530ustar00rootroot00000000000000[global] # # Default Data Set # twoStepFlag 1 slaveOnly 0 priority1 128 priority2 128 domainNumber 0 clockClass 248 clockAccuracy 0xFE offsetScaledLogVariance 0xFFFF free_running 0 freq_est_interval 1 # # Port Data Set # logAnnounceInterval 1 logSyncInterval 0 logMinDelayReqInterval 0 logMinPdelayReqInterval 0 announceReceiptTimeout 3 syncReceiptTimeout 0 delayAsymmetry 0 fault_reset_interval 4 neighborPropDelayThresh 20000000 # # Run time options # assume_two_step 0 logging_level 6 path_trace_enabled 0 follow_up_info 0 hybrid_e2e 0 tx_timestamp_timeout 1 use_syslog 1 verbose 0 summary_interval 0 kernel_leap 1 check_fup_sync 0 # # Servo Options # pi_proportional_const 0.0 pi_integral_const 0.0 pi_proportional_scale 0.0 pi_proportional_exponent -0.3 pi_proportional_norm_max 0.7 pi_integral_scale 0.0 pi_integral_exponent 0.4 pi_integral_norm_max 0.3 step_threshold 0.0 first_step_threshold 0.00002 max_frequency 900000000 clock_servo pi sanity_freq_limit 200000000 ntpshm_segment 0 # # Transport options # transportSpecific 0x0 ptp_dst_mac 01:1B:19:00:00:00 p2p_dst_mac 01:80:C2:00:00:0E udp_ttl 1 udp6_scope 0x0E uds_address /var/run/ptp4l # # Default interface options # network_transport UDPv4 delay_mechanism E2E time_stamping hardware tsproc_mode filter delay_filter moving_median delay_filter_length 10 egressLatency 0 ingressLatency 0 boundary_clock_jbod 0 # # Clock description # productDescription ;; revisionData ;; manufacturerIdentity 00:00:00 userDescription ; timeSource 0xA0 linuxptp-1.6/dm.h000066400000000000000000000021671257727010700140320ustar00rootroot00000000000000/** * @file dm.h * @brief Enumerates the delay mechanisms. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_DM_H #define HAVE_DM_H /** * Defines the possible delay mechanisms. */ enum delay_mechanism { /** Start as E2E, but switch to P2P if a peer is detected. */ DM_AUTO, /** Delay request-response mechanism. */ DM_E2E, /** Peer delay mechanism. */ DM_P2P, }; #endif linuxptp-1.6/ds.h000066400000000000000000000062261257727010700140400ustar00rootroot00000000000000/** * @file ds.h * @brief Data sets * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_DS_H #define HAVE_DS_H #include "ddt.h" #include "fault.h" #include "filter.h" #include "tsproc.h" /* clock data sets */ #define DDS_TWO_STEP_FLAG (1<<0) #define DDS_SLAVE_ONLY (1<<1) struct defaultDS { UInteger8 flags; UInteger8 reserved1; UInteger16 numberPorts; UInteger8 priority1; struct ClockQuality clockQuality; UInteger8 priority2; struct ClockIdentity clockIdentity; UInteger8 domainNumber; UInteger8 reserved2; } PACKED; #define OUI_LEN 3 struct clock_description { struct static_ptp_text productDescription; struct static_ptp_text revisionData; struct static_ptp_text userDescription; Octet manufacturerIdentity[OUI_LEN]; }; struct default_ds { struct defaultDS dds; struct clock_description clock_desc; }; struct dataset { UInteger8 priority1; struct ClockIdentity identity; struct ClockQuality quality; UInteger8 priority2; UInteger16 stepsRemoved; struct PortIdentity sender; struct PortIdentity receiver; }; struct currentDS { UInteger16 stepsRemoved; TimeInterval offsetFromMaster; TimeInterval meanPathDelay; } PACKED; struct parentDS { struct PortIdentity parentPortIdentity; UInteger8 parentStats; UInteger8 reserved; UInteger16 observedParentOffsetScaledLogVariance; Integer32 observedParentClockPhaseChangeRate; UInteger8 grandmasterPriority1; struct ClockQuality grandmasterClockQuality; UInteger8 grandmasterPriority2; struct ClockIdentity grandmasterIdentity; } PACKED; struct parent_ds { struct parentDS pds; struct ClockIdentity *ptl; unsigned int path_length; }; #define CURRENT_UTC_OFFSET 36 /* 1 Jul 2015 */ #define INTERNAL_OSCILLATOR 0xA0 struct timePropertiesDS { Integer16 currentUtcOffset; UInteger8 flags; Enumeration8 timeSource; } PACKED; struct portDS { struct PortIdentity portIdentity; Enumeration8 portState; Integer8 logMinDelayReqInterval; TimeInterval peerMeanPathDelay; Integer8 logAnnounceInterval; UInteger8 announceReceiptTimeout; Integer8 logSyncInterval; Enumeration8 delayMechanism; Integer8 logMinPdelayReqInterval; UInteger8 versionNumber; } PACKED; #define FRI_ASAP (-128) #endif linuxptp-1.6/ether.h000066400000000000000000000024151257727010700145350ustar00rootroot00000000000000/** * @file ether.h * @brief Provides definitions useful when working with Ethernet packets. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_ETHER_H #define HAVE_ETHER_H #include #define MAC_LEN 6 typedef uint8_t eth_addr[MAC_LEN]; struct eth_hdr { eth_addr dst; eth_addr src; uint16_t type; } __attribute__((packed)); #define VLAN_HLEN 4 struct vlan_hdr { eth_addr dst; eth_addr src; uint16_t tpid; uint16_t tci; uint16_t type; } __attribute__((packed)); #define OFF_ETYPE (2 * sizeof(eth_addr)) #endif linuxptp-1.6/fault.c000066400000000000000000000020741257727010700145350ustar00rootroot00000000000000/** * @file fault.c * @note Copyright (C) 2013 Delio Brignoli * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "fault.h" static const char *fault_type_str[FT_CNT] = { "FT_UNSPECIFIED", "FT_BAD_PEER_NETWORK", "FT_SWITCH_PHC", }; const char *ft_str(enum fault_type ft) { if (ft < 0 || ft >= FT_CNT) return "INVALID_FAULT_TYPE_ENUM"; return fault_type_str[ft]; } linuxptp-1.6/fault.h000066400000000000000000000021401257727010700145340ustar00rootroot00000000000000/** * @file fault.h * @note Copyright (C) 2013 Delio Brignoli * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include enum fault_type { FT_UNSPECIFIED = 0, FT_BAD_PEER_NETWORK, FT_SWITCH_PHC, FT_CNT, }; enum fault_tmo_type { FTMO_LINEAR_SECONDS = 0, FTMO_LOG2_SECONDS, FTMO_CNT, }; struct fault_interval { enum fault_tmo_type type; int32_t val; }; const char *ft_str(enum fault_type ft); linuxptp-1.6/fd.h000066400000000000000000000021761257727010700140230ustar00rootroot00000000000000/** * @file fd.h * @brief Defines a array of file descriptors, useful for polling. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_FD_H #define HAVE_FD_H #define N_TIMER_FDS 6 enum { FD_EVENT, FD_GENERAL, FD_ANNOUNCE_TIMER, FD_SYNC_RX_TIMER, FD_DELAY_TIMER, FD_QUALIFICATION_TIMER, FD_MANNO_TIMER, FD_SYNC_TX_TIMER, N_POLLFD, }; struct fdarray { int fd[N_POLLFD]; }; #endif linuxptp-1.6/filter.c000066400000000000000000000025211257727010700147040ustar00rootroot00000000000000/** * @file filter.c * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "filter_private.h" #include "mave.h" #include "mmedian.h" struct filter *filter_create(enum filter_type type, int length) { switch (type) { case FILTER_MOVING_AVERAGE: return mave_create(length); case FILTER_MOVING_MEDIAN: return mmedian_create(length); default: return NULL; } } void filter_destroy(struct filter *filter) { filter->destroy(filter); } tmv_t filter_sample(struct filter *filter, tmv_t sample) { return filter->sample(filter, sample); } void filter_reset(struct filter *filter) { filter->reset(filter); } linuxptp-1.6/filter.h000066400000000000000000000035451257727010700147200ustar00rootroot00000000000000/** * @file filter.h * @brief Implements a generic filter interface. * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_FILTER_H #define HAVE_FILTER_H #include "tmv.h" /** Opaque type */ struct filter; /** * Defines the available filters. */ enum filter_type { FILTER_MOVING_AVERAGE, FILTER_MOVING_MEDIAN, }; /** * Create a new instance of a filter. * @param type The type of the filter to create. * @param length The filter's length. * @return A pointer to a new filter on success, NULL otherwise. */ struct filter *filter_create(enum filter_type type, int length); /** * Destroy an instance of a filter. * @param filter Pointer to a filter obtained via @ref filter_create(). */ void filter_destroy(struct filter *filter); /** * Feed a sample into a filter. * @param filter Pointer to a filter obtained via @ref filter_create(). * @param sample The input sample. * @return The output value. */ tmv_t filter_sample(struct filter *filter, tmv_t sample); /** * Reset a filter. * @param filter Pointer to a filter obtained via @ref filter_create(). */ void filter_reset(struct filter *filter); #endif linuxptp-1.6/filter_private.h000066400000000000000000000020751257727010700164470ustar00rootroot00000000000000/** * @file filter_private.h * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_FILTER_PRIVATE_H #define HAVE_FILTER_PRIVATE_H #include "tmv.h" #include "contain.h" struct filter { void (*destroy)(struct filter *filter); tmv_t (*sample)(struct filter *filter, tmv_t sample); void (*reset)(struct filter *filter); }; #endif linuxptp-1.6/foreign.h000066400000000000000000000032001257727010700150500ustar00rootroot00000000000000/** * @file foreign.h * @brief Defines a foreign clock record. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_FOREIGN_H #define HAVE_FOREIGN_H #include #include "ds.h" #include "port.h" #define FOREIGN_MASTER_THRESHOLD 2 struct foreign_clock { /** * Pointer to next foreign_clock in list. */ LIST_ENTRY(foreign_clock) list; /** * A list of received announce messages. * * The data set field, foreignMasterPortIdentity, is the * sourcePortIdentity of the first message. */ TAILQ_HEAD(messages, ptp_message) messages; /** * Number of elements in the message list, * aka foreignMasterAnnounceMessages. */ unsigned int n_messages; /** * Pointer to the associated port. */ struct port *port; /** * Contains the information from the latest announce message * in a form suitable for comparision in the BMCA. */ struct dataset dataset; }; #endif linuxptp-1.6/fsm.c000066400000000000000000000137401257727010700142110ustar00rootroot00000000000000/** * @file fsm.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "fsm.h" enum port_state ptp_fsm(enum port_state state, enum fsm_event event, int mdiff) { enum port_state next = state; if (EV_INITIALIZE == event || EV_POWERUP == event) return PS_INITIALIZING; switch (state) { case PS_INITIALIZING: next = PS_LISTENING; break; case PS_FAULTY: switch (event) { case EV_DESIGNATED_DISABLED: next = PS_DISABLED; break; case EV_FAULT_CLEARED: next = PS_INITIALIZING; break; default: break; } break; case PS_DISABLED: if (EV_DESIGNATED_ENABLED == event) next = PS_INITIALIZING; break; case PS_LISTENING: switch (event) { case EV_DESIGNATED_DISABLED: next = PS_DISABLED; break; case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES: next = PS_MASTER; break; case EV_RS_MASTER: next = PS_PRE_MASTER; break; case EV_RS_GRAND_MASTER: next = PS_GRAND_MASTER; break; case EV_RS_SLAVE: next = PS_UNCALIBRATED; break; case EV_RS_PASSIVE: next = PS_PASSIVE; break; default: break; } break; case PS_PRE_MASTER: switch (event) { case EV_DESIGNATED_DISABLED: next = PS_DISABLED; break; case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_QUALIFICATION_TIMEOUT_EXPIRES: next = PS_MASTER; break; case EV_RS_SLAVE: next = PS_UNCALIBRATED; break; case EV_RS_PASSIVE: next = PS_PASSIVE; break; default: break; } break; case PS_MASTER: case PS_GRAND_MASTER: switch (event) { case EV_DESIGNATED_DISABLED: next = PS_DISABLED; break; case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_RS_SLAVE: next = PS_UNCALIBRATED; break; case EV_RS_PASSIVE: next = PS_PASSIVE; break; default: break; } break; case PS_PASSIVE: switch (event) { case EV_DESIGNATED_DISABLED: next = PS_DISABLED; break; case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES: next = PS_MASTER; break; case EV_RS_MASTER: next = PS_PRE_MASTER; break; case EV_RS_GRAND_MASTER: next = PS_GRAND_MASTER; break; case EV_RS_SLAVE: next = PS_UNCALIBRATED; break; default: break; } break; case PS_UNCALIBRATED: switch (event) { case EV_DESIGNATED_DISABLED: next = PS_DISABLED; break; case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES: next = PS_MASTER; break; case EV_MASTER_CLOCK_SELECTED: next = PS_SLAVE; break; case EV_RS_MASTER: next = PS_PRE_MASTER; break; case EV_RS_GRAND_MASTER: next = PS_GRAND_MASTER; break; case EV_RS_SLAVE: next = PS_UNCALIBRATED; break; case EV_RS_PASSIVE: next = PS_PASSIVE; break; default: break; } break; case PS_SLAVE: switch (event) { case EV_DESIGNATED_DISABLED: next = PS_DISABLED; break; case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES: next = PS_MASTER; break; case EV_SYNCHRONIZATION_FAULT: next = PS_UNCALIBRATED; break; case EV_RS_MASTER: next = PS_PRE_MASTER; break; case EV_RS_GRAND_MASTER: next = PS_GRAND_MASTER; break; case EV_RS_SLAVE: if (mdiff) next = PS_UNCALIBRATED; break; case EV_RS_PASSIVE: next = PS_PASSIVE; break; default: break; } break; } return next; } enum port_state ptp_slave_fsm(enum port_state state, enum fsm_event event, int mdiff) { enum port_state next = state; if (EV_INITIALIZE == event || EV_POWERUP == event) return PS_INITIALIZING; switch (state) { case PS_INITIALIZING: next = PS_LISTENING; break; case PS_FAULTY: switch (event) { case EV_DESIGNATED_DISABLED: next = PS_DISABLED; break; case EV_FAULT_CLEARED: next = PS_INITIALIZING; break; default: break; } break; case PS_DISABLED: if (EV_DESIGNATED_ENABLED == event) next = PS_INITIALIZING; break; case PS_LISTENING: switch (event) { case EV_DESIGNATED_DISABLED: next = PS_DISABLED; break; case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES: case EV_RS_MASTER: case EV_RS_GRAND_MASTER: case EV_RS_PASSIVE: next = PS_LISTENING; break; case EV_RS_SLAVE: next = PS_UNCALIBRATED; break; default: break; } break; case PS_UNCALIBRATED: switch (event) { case EV_DESIGNATED_DISABLED: next = PS_DISABLED; break; case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES: case EV_RS_MASTER: case EV_RS_GRAND_MASTER: case EV_RS_PASSIVE: next = PS_LISTENING; break; case EV_MASTER_CLOCK_SELECTED: next = PS_SLAVE; break; default: break; } break; case PS_SLAVE: switch (event) { case EV_DESIGNATED_DISABLED: next = PS_DISABLED; break; case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES: case EV_RS_MASTER: case EV_RS_GRAND_MASTER: case EV_RS_PASSIVE: next = PS_LISTENING; break; case EV_SYNCHRONIZATION_FAULT: next = PS_UNCALIBRATED; break; case EV_RS_SLAVE: if (mdiff) next = PS_UNCALIBRATED; break; default: break; } break; default: break; } return next; } linuxptp-1.6/fsm.h000066400000000000000000000043411257727010700142130ustar00rootroot00000000000000/** * @file fsm.h * @brief The finite state machine for ports on boundary and ordinary clocks. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_FSM_H #define HAVE_FSM_H /** 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*/ }; /** Defines the events for the port state machine. */ enum fsm_event { EV_NONE, EV_POWERUP, EV_INITIALIZE, EV_DESIGNATED_ENABLED, EV_DESIGNATED_DISABLED, EV_FAULT_CLEARED, EV_FAULT_DETECTED, EV_STATE_DECISION_EVENT, EV_QUALIFICATION_TIMEOUT_EXPIRES, EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES, EV_SYNCHRONIZATION_FAULT, EV_MASTER_CLOCK_SELECTED, EV_RS_MASTER, EV_RS_GRAND_MASTER, EV_RS_SLAVE, EV_RS_PASSIVE, }; /** * Run the state machine for a BC or OC port. * @param state The current state of the port. * @param event The event to be processed. * @param mdiff Whether a new master has been selected. * @return The new state for the port. */ enum port_state ptp_fsm(enum port_state state, enum fsm_event event, int mdiff); /** * Run the state machine for a slave only clock. * @param state The current state of the port. * @param event The event to be processed. * @param mdiff Whether a new master has been selected. * @return The new state for the port. */ enum port_state ptp_slave_fsm(enum port_state state, enum fsm_event event, int mdiff); #endif linuxptp-1.6/gPTP.cfg000066400000000000000000000025461257727010700145550ustar00rootroot00000000000000[global] # # Default Data Set # twoStepFlag 1 gmCapable 1 priority1 248 priority2 248 domainNumber 0 clockClass 248 clockAccuracy 0xFE offsetScaledLogVariance 0xFFFF free_running 0 freq_est_interval 1 # # Port Data Set # logAnnounceInterval 1 logSyncInterval -3 logMinPdelayReqInterval 0 announceReceiptTimeout 3 syncReceiptTimeout 3 delayAsymmetry 0 fault_reset_interval 4 neighborPropDelayThresh 800 min_neighbor_prop_delay -20000000 # # Run time options # assume_two_step 1 logging_level 6 path_trace_enabled 1 follow_up_info 1 hybrid_e2e 0 tx_timestamp_timeout 1 use_syslog 1 verbose 0 summary_interval 0 kernel_leap 1 check_fup_sync 0 # # Servo options # pi_proportional_const 0.0 pi_integral_const 0.0 pi_proportional_scale 0.0 pi_proportional_exponent -0.3 pi_proportional_norm_max 0.7 pi_integral_scale 0.0 pi_integral_exponent 0.4 pi_integral_norm_max 0.3 step_threshold 0.0 first_step_threshold 0.00002 max_frequency 900000000 clock_servo pi sanity_freq_limit 200000000 ntpshm_segment 0 # # Transport options # transportSpecific 0x1 ptp_dst_mac 01:80:C2:00:00:0E p2p_dst_mac 01:80:C2:00:00:0E uds_address /var/run/ptp4l # # Default interface options # network_transport L2 delay_mechanism P2P time_stamping hardware tsproc_mode filter delay_filter moving_median delay_filter_length 10 egressLatency 0 ingressLatency 0 boundary_clock_jbod 0 linuxptp-1.6/hash.c000066400000000000000000000044131257727010700143440ustar00rootroot00000000000000/** * @file hash.c * @brief Implements a simple hash table. * @note Copyright (C) 2015 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "hash.h" #define HASH_TABLE_SIZE 200 struct node { char *key; void *data; struct node *next; }; struct hash { struct node *table[HASH_TABLE_SIZE]; }; static unsigned int hash_function(const char* s) { unsigned int i; for (i = 0; *s; s++) { i = 131 * i + *s; } return i % HASH_TABLE_SIZE; } struct hash *hash_create(void) { struct hash *ht = calloc(1, sizeof(*ht)); return ht; } void hash_destroy(struct hash *ht, void (*func)(void *)) { unsigned int i; struct node *n, *next, **table = ht->table; for (i = 0; i < HASH_TABLE_SIZE; i++) { for (n = table[i] ; n; n = next) { next = n->next; if (func) { func(n->data); } free(n->key); free(n); } } free(ht); } int hash_insert(struct hash *ht, const char* key, void *data) { unsigned int h; struct node *n, **table = ht->table; h = hash_function(key); for (n = table[h] ; n; n = n->next) { if (!strcmp(n->key, key)) { /* reject duplicate keys */ return -1; } } n = calloc(1, sizeof(*n)); if (!n) { return -1; } n->key = strdup(key); if (!n->key) { free(n); return -1; } n->data = data; n->next = table[h]; table[h] = n; return 0; } void *hash_lookup(struct hash *ht, const char* key) { unsigned int h; struct node *n, **table = ht->table; h = hash_function(key); for (n = table[h] ; n; n = n->next) { if (!strcmp(n->key, key)) { return n->data; } } return NULL; } linuxptp-1.6/hash.h000066400000000000000000000037631257727010700143600ustar00rootroot00000000000000/** * @file hash.h * @brief Implements a simple hash table. * @note Copyright (C) 2015 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_HASH_H #define HAVE_HASH_H struct hash; /** * Create a new hash table. * @return A pointer to a new hash table on success, NULL otherwise. */ struct hash *hash_create(void); /** * Destroy an instance of a hash table. * @param ht Pointer to a hash table obtained via @ref hash_create(). * @param func Callback function, possibly NULL, to apply to the * data of each element in the table. */ void hash_destroy(struct hash *ht, void (*func)(void *)); /** * Inserts an element into a hash table. * @param ht Hash table into which the element is to be stored. * @param key Key that identifies the element. * @param data Pointer to the user data to be stored. * @return Zero on success and non-zero on error. Attempting to * insert a duplicate key will fail with an error. */ int hash_insert(struct hash *ht, const char* key, void *data); /** * Looks up an element from the hash table. * @param ht Hash table to consult. * @param key Key identifying the element of interest. * @return Pointer to the element's data, or NULL if the key is not found. */ void *hash_lookup(struct hash *ht, const char* key); #endif linuxptp-1.6/hwstamp_ctl.8000066400000000000000000000027471257727010700157030ustar00rootroot00000000000000.TH HWSTAMP_CTL 8 "June 2014" "linuxptp" .SH NAME hwstamp_ctl \- set time stamping policy at the driver level .SH SYNOPSIS .B hwstamp_ctl .BI \-i " interface" [ .BI \-r " rx-filter" ] [ .BI \-t " tx-type" ] [ .B \-v ] .SH DESCRIPTION .B hwstamp_ctl is a program used to set and get the hardware time stamping policy at the network driver level with the .B SIOCSHWTSTAMP .BR ioctl (2). The .I tx-type and .I rx-filter values are hints to the driver what it is expected to do. If the requested fine-grained filtering for incoming packets is not supported, the driver may time stamp more than just the requested types of packets. If neither .I tx-type nor .I rx-filter values are passed to the program, it will use the .B SIOCGHWTSTAMP .BR ioctl(2) to non-destructively read the current hardware time stamping policy. This program is a debugging tool. The .BR ptp4l (8) program does not need this program to function, it will set the policy automatically as appropriate. .SH OPTIONS .TP .BI \-i " interface" Specify the network interface of which the policy should be changed. .TP .BI \-r " rx-filter" Specify which types of incoming packets should be time stamped, .I rx-filter is an integer value. .TP .BI \-t " tx-type" Enable or disable hardware time stamping for outgoing packets, .I tx-type is an integer value. .TP .BI \-h Display a help message and list of possible values for .I rx-filter and .IR tx-type . .TP .B \-v Prints the software version and exits. .SH SEE ALSO .BR ioctl (2), .BR ptp4l (8) linuxptp-1.6/hwstamp_ctl.c000066400000000000000000000122731257727010700157510ustar00rootroot00000000000000/** * @file hwstamp_ctl.c * @brief Utility program to set time stamping policy at the driver level. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include "version.h" #include "missing.h" static void usage(char *progname) { fprintf(stderr, "\n" "usage: %s [options]\n\n" " -h prints this message and exits\n" " -i [device] interface device to use, for example 'eth0'\n" " -r [%d..%d] select receive time stamping:\n" "\t\t%2d time stamp no incoming packet at all\n" "\t\t%2d time stamp any incoming packet\n" "\t\t%2d (reserved value)\n" "\t\t%2d PTP v1, UDP, any kind of event packet\n" "\t\t%2d PTP v1, UDP, Sync packet\n" "\t\t%2d PTP v1, UDP, Delay_req packet\n" "\t\t%2d PTP v2, UDP, any kind of event packet\n" "\t\t%2d PTP v2, UDP, Sync packet\n" "\t\t%2d PTP v2, UDP, Delay_req packet\n" "\t\t%2d 802.AS1, Ethernet, any kind of event packet\n" "\t\t%2d 802.AS1, Ethernet, Sync packet\n" "\t\t%2d 802.AS1, Ethernet, Delay_req packet\n" "\t\t%2d PTP v2/802.AS1, any layer, any kind of event packet\n" "\t\t%2d PTP v2/802.AS1, any layer, Sync packet\n" "\t\t%2d PTP v2/802.AS1, any layer, Delay_req packet\n" " -t [%d|%d] disable or enable transmit time stamping\n" " -v prints the software version and exits\n" "\n", progname, HWTSTAMP_FILTER_NONE, HWTSTAMP_FILTER_PTP_V2_DELAY_REQ, HWTSTAMP_FILTER_NONE, HWTSTAMP_FILTER_ALL, HWTSTAMP_FILTER_SOME, HWTSTAMP_FILTER_PTP_V1_L4_EVENT, HWTSTAMP_FILTER_PTP_V1_L4_SYNC, HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ, HWTSTAMP_FILTER_PTP_V2_L4_EVENT, HWTSTAMP_FILTER_PTP_V2_L4_SYNC, HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ, HWTSTAMP_FILTER_PTP_V2_L2_EVENT, HWTSTAMP_FILTER_PTP_V2_L2_SYNC, HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ, HWTSTAMP_FILTER_PTP_V2_EVENT, HWTSTAMP_FILTER_PTP_V2_SYNC, HWTSTAMP_FILTER_PTP_V2_DELAY_REQ, HWTSTAMP_TX_OFF, HWTSTAMP_TX_ON); } int main(int argc, char *argv[]) { struct ifreq ifreq; struct hwtstamp_config cfg; char *device = NULL, *progname; int c, err, fd, rxopt = HWTSTAMP_FILTER_NONE, txopt = HWTSTAMP_TX_OFF; int setrx = 0, settx = 0; /* Process the command line arguments. */ progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; while (EOF != (c = getopt(argc, argv, "hi:r:t:v"))) { switch (c) { case 'i': device = optarg; break; case 'r': setrx = 1; rxopt = atoi(optarg); break; case 't': settx = 1; txopt = atoi(optarg); break; case 'v': version_show(stdout); return 0; case 'h': usage(progname); return 0; case '?': default: usage(progname); return -1; } } if (!device) { usage(progname); return -1; } if (rxopt < HWTSTAMP_FILTER_NONE || rxopt > HWTSTAMP_FILTER_PTP_V2_DELAY_REQ || txopt < HWTSTAMP_TX_OFF || txopt > HWTSTAMP_TX_ON) { usage(progname); return -1; } memset(&ifreq, 0, sizeof(ifreq)); memset(&cfg, 0, sizeof(cfg)); strncpy(ifreq.ifr_name, device, sizeof(ifreq.ifr_name) - 1); ifreq.ifr_data = (void *) &cfg; fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) { perror("socket"); return -1; } /* First, attempt to get the current settings. */ err = ioctl(fd, SIOCGHWTSTAMP, &ifreq); if (err < 0) { err = errno; if (err == ENOTTY) fprintf(stderr, "Kernel does not have support " "for non-destructive SIOCGHWTSTAMP.\n"); else if (err == EOPNOTSUPP) fprintf(stderr, "Device driver does not have support " "for non-destructive SIOCGHWTSTAMP.\n"); else perror("SIOCGHWTSTAMP failed"); } else { printf("current settings:\n" "tx_type %d\n" "rx_filter %d\n", cfg.tx_type, cfg.rx_filter); } /* Now, attempt to set values. Only change the values actually * requested by user, rather than blindly resetting th zero if * unrequested. */ if (settx || setrx) { if (settx) cfg.tx_type = txopt; if (setrx) cfg.rx_filter = rxopt; err = ioctl(fd, SIOCSHWTSTAMP, &ifreq); if (err < 0) { err = errno; perror("SIOCSHWTSTAMP failed"); if (err == ERANGE) fprintf(stderr, "The requested time stamping mode is " "not supported by the hardware.\n"); } else { printf("new settings:\n" "tx_type %d\n" "rx_filter %d\n", cfg.tx_type, cfg.rx_filter); } } return err; } linuxptp-1.6/incdefs.sh000077500000000000000000000045511257727010700152320ustar00rootroot00000000000000#!/bin/sh # # Discover the CFLAGS to use during compilation. # # Copyright (C) 2013 Richard Cochran # # 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., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # Look for functional prototypes in the C library. # user_flags() { # Needed for vasprintf(). printf " -D_GNU_SOURCE" # Get list of directories searched for header files. dirs=$(echo "" | ${CROSS_COMPILE}cpp -Wp,-v 2>&1 >/dev/null | grep ^" /") # Look for clock_adjtime(). for d in $dirs; do files=$(find $d -type f -name time.h) for f in $files; do if grep -q clock_adjtime $f; then printf " -DHAVE_CLOCK_ADJTIME" break 2 fi done done # Look for posix_spawn(). for d in $dirs; do files=$(find $d -type f -name spawn.h) for f in $files; do if grep -q posix_spawn $f; then printf " -DHAVE_POSIX_SPAWN" break 2 fi done done } # # Find the most appropriate kernel header for the SIOCSHWTSTAMP ioctl. # # 1. custom kernel or cross build using KBUILD_OUTPUT # 2. sanitized headers installed under /lib/modules/`uname -r`/build # 3. normal build using standard system headers # kernel_flags() { prefix="" tstamp=/usr/include/linux/net_tstamp.h if [ "x$KBUILD_OUTPUT" != "x" ]; then # With KBUILD_OUTPUT set, we are building against # either a custom kernel or a cross compiled kernel. build=${KBUILD_OUTPUT} else # If the currently running kernel is a custom build # with the headers installed, then we should use them. build=/lib/modules/`uname -r`/build fi if [ -f ${build}${tstamp} ]; then prefix=${build} printf " -I%s/usr/include" $prefix fi if grep -q HWTSTAMP_TX_ONESTEP_SYNC ${prefix}${tstamp}; then printf " -DHAVE_ONESTEP_SYNC" fi } flags="$(user_flags)$(kernel_flags)" echo "$flags" linuxptp-1.6/linreg.c000066400000000000000000000223601257727010700147020ustar00rootroot00000000000000/** * @file linreg.c * @brief Implements an adaptive servo based on linear regression. * @note Copyright (C) 2014 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "linreg.h" #include "print.h" #include "servo_private.h" /* Maximum and minimum number of points used in regression, defined as a power of 2 */ #define MAX_SIZE 6 #define MIN_SIZE 2 #define MAX_POINTS (1 << MAX_SIZE) /* Smoothing factor used for long-term prediction error */ #define ERR_SMOOTH 0.02 /* Number of updates used for initialization */ #define ERR_INITIAL_UPDATES 10 /* Maximum ratio of two err values to be considered equal */ #define ERR_EQUALS 1.05 /* Uncorrected local time vs remote time */ struct point { uint64_t x; uint64_t y; double w; }; struct result { /* Slope and intercept from latest regression */ double slope; double intercept; /* Exponential moving average of prediction error */ double err; /* Number of initial err updates */ int err_updates; }; struct linreg_servo { struct servo servo; /* Circular buffer of points */ struct point points[MAX_POINTS]; /* Current time in x, y */ struct point reference; /* Number of stored points */ unsigned int num_points; /* Index of the newest point */ unsigned int last_point; /* Remainder from last update of reference.x */ double x_remainder; /* Local time stamp of last update */ uint64_t last_update; /* Regression results for all sizes */ struct result results[MAX_SIZE - MIN_SIZE + 1]; /* Selected size */ unsigned int size; /* Current frequency offset of the clock */ double clock_freq; /* Expected interval between updates */ double update_interval; /* Current ratio between remote and local frequency */ double frequency_ratio; /* Upcoming leap second */ int leap; }; static void linreg_destroy(struct servo *servo) { struct linreg_servo *s = container_of(servo, struct linreg_servo, servo); free(s); } static void move_reference(struct linreg_servo *s, int64_t x, int64_t y) { struct result *res; unsigned int i; s->reference.x += x; s->reference.y += y; /* Update intercepts for new reference */ for (i = MIN_SIZE; i <= MAX_SIZE; i++) { res = &s->results[i - MIN_SIZE]; res->intercept += x * res->slope - y; } } static void update_reference(struct linreg_servo *s, uint64_t local_ts) { double x_interval; int64_t y_interval; if (s->last_update) { y_interval = local_ts - s->last_update; /* Remove current frequency correction from the interval */ x_interval = y_interval / (1.0 + s->clock_freq / 1e9); x_interval += s->x_remainder; s->x_remainder = x_interval - (int64_t)x_interval; move_reference(s, (int64_t)x_interval, y_interval); } s->last_update = local_ts; } static void add_sample(struct linreg_servo *s, int64_t offset, double weight) { s->last_point = (s->last_point + 1) % MAX_POINTS; s->points[s->last_point].x = s->reference.x; s->points[s->last_point].y = s->reference.y - offset; s->points[s->last_point].w = weight; if (s->num_points < MAX_POINTS) s->num_points++; } static void regress(struct linreg_servo *s) { double x, y, y0, e, x_sum, y_sum, xy_sum, x2_sum, w, w_sum; unsigned int i, l, n, size; struct result *res; x_sum = 0.0, y_sum = 0.0, xy_sum = 0.0, x2_sum = 0.0; w_sum = 0.0; i = 0; y0 = (int64_t)(s->points[s->last_point].y - s->reference.y); for (size = MIN_SIZE; size <= MAX_SIZE; size++) { n = 1 << size; if (n > s->num_points) /* Not enough points for this size */ break; res = &s->results[size - MIN_SIZE]; /* Update moving average of the prediction error */ if (res->slope) { e = fabs(res->intercept - y0); if (res->err_updates < ERR_INITIAL_UPDATES) { res->err *= res->err_updates; res->err += e; res->err_updates++; res->err /= res->err_updates; } else { res->err += ERR_SMOOTH * (e - res->err); } } for (; i < n; i++) { /* Iterate points from newest to oldest */ l = (MAX_POINTS + s->last_point - i) % MAX_POINTS; x = (int64_t)(s->points[l].x - s->reference.x); y = (int64_t)(s->points[l].y - s->reference.y); w = s->points[l].w; x_sum += x * w; y_sum += y * w; xy_sum += x * y * w; x2_sum += x * x * w; w_sum += w; } /* Get new intercept and slope */ res->slope = (xy_sum - x_sum * y_sum / w_sum) / (x2_sum - x_sum * x_sum / w_sum); res->intercept = (y_sum - res->slope * x_sum) / w_sum; } } static void update_size(struct linreg_servo *s) { struct result *res; double best_err; int size, best_size; /* Find largest size with smallest prediction error */ best_size = 0; best_err = 0.0; for (size = MIN_SIZE; size <= MAX_SIZE; size++) { res = &s->results[size - MIN_SIZE]; if ((!best_size && res->slope) || (best_err * ERR_EQUALS > res->err && res->err_updates >= ERR_INITIAL_UPDATES)) { best_size = size; best_err = res->err; } } s->size = best_size; } static double linreg_sample(struct servo *servo, int64_t offset, uint64_t local_ts, double weight, enum servo_state *state) { struct linreg_servo *s = container_of(servo, struct linreg_servo, servo); struct result *res; int corr_interval; /* * The current time and the time when will be the frequency of the * clock actually updated is assumed here to be equal to local_ts * (which is the time stamp of the received sync message). As long as * the differences are smaller than the update interval, the loop * should be robust enough to handle this simplification. */ update_reference(s, local_ts); add_sample(s, offset, weight); regress(s); update_size(s); if (s->size < MIN_SIZE) { /* Not enough points, wait for more */ *state = SERVO_UNLOCKED; return -s->clock_freq; } res = &s->results[s->size - MIN_SIZE]; pr_debug("linreg: points %d slope %.9f intercept %.0f err %.0f", 1 << s->size, res->slope, res->intercept, res->err); if ((servo->first_update && servo->first_step_threshold && servo->first_step_threshold < fabs(res->intercept)) || (servo->step_threshold && servo->step_threshold < fabs(res->intercept))) { /* The clock will be stepped by offset */ move_reference(s, 0, -offset); s->last_update -= offset; *state = SERVO_JUMP; } else { *state = SERVO_LOCKED; } /* Set clock frequency to the slope */ s->clock_freq = 1e9 * (res->slope - 1.0); /* * Adjust the frequency to correct the time offset. Use longer * correction interval with larger sizes to reduce the frequency error. * The update interval is assumed to be not affected by the frequency * adjustment. If it is (e.g. phc2sys controlling the system clock), a * correction slowing down the clock will result in an overshoot. With * the system clock's maximum adjustment of 10% that's acceptable. */ corr_interval = s->size <= 4 ? 1 : s->size / 2; s->clock_freq += res->intercept / s->update_interval / corr_interval; /* Clamp the frequency to the allowed maximum */ if (s->clock_freq > servo->max_frequency) s->clock_freq = servo->max_frequency; else if (s->clock_freq < -servo->max_frequency) s->clock_freq = -servo->max_frequency; s->frequency_ratio = res->slope / (1.0 + s->clock_freq / 1e9); return -s->clock_freq; } static void linreg_sync_interval(struct servo *servo, double interval) { struct linreg_servo *s = container_of(servo, struct linreg_servo, servo); s->update_interval = interval; } static void linreg_reset(struct servo *servo) { struct linreg_servo *s = container_of(servo, struct linreg_servo, servo); unsigned int i; s->num_points = 0; s->last_update = 0; s->size = 0; s->frequency_ratio = 1.0; for (i = MIN_SIZE; i <= MAX_SIZE; i++) { s->results[i - MIN_SIZE].slope = 0.0; s->results[i - MIN_SIZE].err_updates = 0; } } static double linreg_rate_ratio(struct servo *servo) { struct linreg_servo *s = container_of(servo, struct linreg_servo, servo); return s->frequency_ratio; } static void linreg_leap(struct servo *servo, int leap) { struct linreg_servo *s = container_of(servo, struct linreg_servo, servo); /* * Move reference when leap second is applied to the reference * time as if the clock was stepped in the opposite direction */ if (s->leap && !leap) move_reference(s, 0, s->leap * 1000000000); s->leap = leap; } struct servo *linreg_servo_create(int fadj) { struct linreg_servo *s; s = calloc(1, sizeof(*s)); if (!s) return NULL; s->servo.destroy = linreg_destroy; s->servo.sample = linreg_sample; s->servo.sync_interval = linreg_sync_interval; s->servo.reset = linreg_reset; s->servo.rate_ratio = linreg_rate_ratio; s->servo.leap = linreg_leap; s->clock_freq = -fadj; s->frequency_ratio = 1.0; return &s->servo; } linuxptp-1.6/linreg.h000066400000000000000000000016431257727010700147100ustar00rootroot00000000000000/** * @file linreg.h * @note Copyright (C) 2014 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_LINREG_H #define HAVE_LINREG_H #include "servo.h" struct servo *linreg_servo_create(int fadj); #endif linuxptp-1.6/makefile000066400000000000000000000057211257727010700147600ustar00rootroot00000000000000# # Copyright (C) 2011 Richard Cochran # # 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., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. KBUILD_OUTPUT = DEBUG = CC = $(CROSS_COMPILE)gcc VER = -DVER=$(version) CFLAGS = -Wall $(VER) $(incdefs) $(DEBUG) $(EXTRA_CFLAGS) LDLIBS = -lm -lrt $(EXTRA_LDFLAGS) PRG = ptp4l pmc phc2sys hwstamp_ctl phc_ctl timemaster OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o fault.o \ filter.o fsm.o hash.o linreg.o mave.o mmedian.o msg.o ntpshm.o nullf.o phc.o \ pi.o port.o print.o ptp4l.o raw.o servo.o sk.o stats.o tlv.o \ transport.o tsproc.o udp.o udp6.o uds.o util.o version.o OBJECTS = $(OBJ) hwstamp_ctl.o phc2sys.o phc_ctl.o pmc.o pmc_common.o \ sysoff.o timemaster.o SRC = $(OBJECTS:.o=.c) DEPEND = $(OBJECTS:.o=.d) srcdir := $(dir $(lastword $(MAKEFILE_LIST))) incdefs := $(shell $(srcdir)/incdefs.sh) version := $(shell $(srcdir)/version.sh $(srcdir)) VPATH = $(srcdir) prefix = /usr/local sbindir = $(prefix)/sbin mandir = $(prefix)/man man8dir = $(mandir)/man8 all: $(PRG) ptp4l: $(OBJ) pmc: config.o hash.o msg.o pmc.o pmc_common.o print.o raw.o sk.o tlv.o \ transport.o udp.o udp6.o uds.o util.o version.o phc2sys: clockadj.o clockcheck.o config.o hash.o linreg.o msg.o ntpshm.o \ nullf.o phc.o phc2sys.o pi.o pmc_common.o print.o raw.o servo.o sk.o stats.o \ sysoff.o tlv.o transport.o udp.o udp6.o uds.o util.o version.o hwstamp_ctl: hwstamp_ctl.o version.o phc_ctl: phc_ctl.o phc.o sk.o util.o clockadj.o sysoff.o print.o version.o timemaster: print.o sk.o timemaster.o util.o version.o version.o: .version version.sh $(filter-out version.d,$(DEPEND)) .version: force @echo $(version) > .version.new; \ cmp -s .version .version.new || cp .version.new .version; \ rm -f .version.new; force: install: $(PRG) install -p -m 755 -d $(DESTDIR)$(sbindir) $(DESTDIR)$(man8dir) install $(PRG) $(DESTDIR)$(sbindir) install -p -m 644 -t $(DESTDIR)$(man8dir) $(PRG:%=%.8) clean: rm -f $(OBJECTS) $(DEPEND) distclean: clean rm -f $(PRG) rm -f .version # Implicit rule to generate a C source file's dependencies. %.d: %.c @echo DEPEND $<; \ rm -f $@; \ $(CC) -MM $(CPPFLAGS) $(CFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$ ifneq ($(MAKECMDGOALS), clean) ifneq ($(MAKECMDGOALS), distclean) -include $(DEPEND) endif endif .PHONY: all force clean distclean linuxptp-1.6/mave.c000066400000000000000000000037571257727010700143630ustar00rootroot00000000000000/** * @file mave.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "mave.h" #include "filter_private.h" struct mave { struct filter filter; int cnt; int len; int index; tmv_t sum; tmv_t *val; }; static void mave_destroy(struct filter *filter) { struct mave *m = container_of(filter, struct mave, filter); free(m->val); free(m); } static tmv_t mave_accumulate(struct filter *filter, tmv_t val) { struct mave *m = container_of(filter, struct mave, filter); m->sum = tmv_sub(m->sum, m->val[m->index]); m->val[m->index] = val; m->index = (1 + m->index) % m->len; m->sum = tmv_add(m->sum, val); if (m->cnt < m->len) { m->cnt++; } return tmv_div(m->sum, m->cnt); } static void mave_reset(struct filter *filter) { struct mave *m = container_of(filter, struct mave, filter); m->cnt = 0; m->index = 0; m->sum = 0; memset(m->val, 0, m->len * sizeof(*m->val)); } struct filter *mave_create(int length) { struct mave *m; m = calloc(1, sizeof(*m)); if (!m) { return NULL; } m->filter.destroy = mave_destroy; m->filter.sample = mave_accumulate; m->filter.reset = mave_reset; m->val = calloc(1, length * sizeof(*m->val)); if (!m->val) { free(m); return NULL; } m->len = length; return &m->filter; } linuxptp-1.6/mave.h000066400000000000000000000017041257727010700143560ustar00rootroot00000000000000/** * @file mave.h * @brief Implements a moving average. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_MAVE_H #define HAVE_MAVE_H #include "filter.h" struct filter *mave_create(int length); #endif linuxptp-1.6/missing.h000066400000000000000000000044501257727010700151000ustar00rootroot00000000000000/** * @file missing.h * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* * When glibc offers the syscall, this will go away. */ #ifndef HAVE_MISSING_H #define HAVE_MISSING_H #include #include #include #include #ifndef ADJ_TAI #define ADJ_TAI 0x0080 #endif #ifndef ADJ_NANO #define ADJ_NANO 0x2000 #endif #ifndef ADJ_SETOFFSET #define ADJ_SETOFFSET 0x0100 #endif #ifndef CLOCK_INVALID #define CLOCK_INVALID -1 #endif #define CLOCKFD 3 #define FD_TO_CLOCKID(fd) ((~(clockid_t) (fd) << 3) | CLOCKFD) #define CLOCKID_TO_FD(clk) ((unsigned int) ~((clk) >> 3)) #ifndef HAVE_ONESTEP_SYNC enum _missing_hwtstamp_tx_types { HWTSTAMP_TX_ONESTEP_SYNC = 2, }; #endif #ifndef SIOCGHWTSTAMP #define SIOCGHWTSTAMP 0x89b1 #endif #ifndef SO_SELECT_ERR_QUEUE #define SO_SELECT_ERR_QUEUE 45 #endif #ifndef HAVE_CLOCK_ADJTIME static inline int clock_adjtime(clockid_t id, struct timex *tx) { return syscall(__NR_clock_adjtime, id, tx); } #endif #ifndef __uClinux__ #include #else static inline int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *request, struct timespec *remain) { return syscall(__NR_clock_nanosleep, clock_id, flags, request, remain); } static inline int timerfd_create(int clockid, int flags) { return syscall(__NR_timerfd_create, clockid, flags); } static inline int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value) { return syscall(__NR_timerfd_settime, fd, flags, new_value, old_value); } #endif #endif linuxptp-1.6/mmedian.c000066400000000000000000000053231257727010700150340ustar00rootroot00000000000000/** * @file mmedian.c * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "mmedian.h" #include "filter_private.h" struct mmedian { struct filter filter; int cnt; int len; int index; /* Indices sorted by value. */ int *order; /* Values stored in circular buffer. */ tmv_t *samples; }; static void mmedian_destroy(struct filter *filter) { struct mmedian *m = container_of(filter, struct mmedian, filter); free(m->order); free(m->samples); free(m); } static tmv_t mmedian_sample(struct filter *filter, tmv_t sample) { struct mmedian *m = container_of(filter, struct mmedian, filter); int i; m->samples[m->index] = sample; if (m->cnt < m->len) { m->cnt++; } else { /* Remove index of the replaced value from order. */ for (i = 0; i < m->cnt; i++) if (m->order[i] == m->index) break; for (; i + 1 < m->cnt; i++) m->order[i] = m->order[i + 1]; } /* Insert index of the new value to order. */ for (i = m->cnt - 1; i > 0; i--) { if (m->samples[m->order[i - 1]] <= m->samples[m->index]) break; m->order[i] = m->order[i - 1]; } m->order[i] = m->index; m->index = (1 + m->index) % m->len; if (m->cnt % 2) return m->samples[m->order[m->cnt / 2]]; else return tmv_div(tmv_add(m->samples[m->order[m->cnt / 2 - 1]], m->samples[m->order[m->cnt / 2]]), 2); } static void mmedian_reset(struct filter *filter) { struct mmedian *m = container_of(filter, struct mmedian, filter); m->cnt = 0; m->index = 0; } struct filter *mmedian_create(int length) { struct mmedian *m; if (length < 1) return NULL; m = calloc(1, sizeof(*m)); if (!m) return NULL; m->filter.destroy = mmedian_destroy; m->filter.sample = mmedian_sample; m->filter.reset = mmedian_reset; m->order = calloc(1, length * sizeof(*m->order)); if (!m->order) { free(m); return NULL; } m->samples = calloc(1, length * sizeof(*m->samples)); if (!m->samples) { free(m->order); free(m); return NULL; } m->len = length; return &m->filter; } linuxptp-1.6/mmedian.h000066400000000000000000000017131257727010700150400ustar00rootroot00000000000000/** * @file mmedian.h * @brief Implements a moving median. * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_MMEDIAN_H #define HAVE_MMEDIAN_H #include "filter.h" struct filter *mmedian_create(int length); #endif linuxptp-1.6/msg.c000066400000000000000000000246311257727010700142130ustar00rootroot00000000000000/** * @file msg.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "contain.h" #include "msg.h" #include "print.h" #include "tlv.h" #define VERSION_MASK 0x0f #define VERSION 0x02 /* * Head room fits a VLAN Ethernet header, and 'msg' is 64 bit aligned. */ #define MSG_HEADROOM 24 struct message_storage { unsigned char reserved[MSG_HEADROOM]; struct ptp_message msg; } PACKED; static TAILQ_HEAD(msg_pool, ptp_message) msg_pool; static struct { int total; int count; } pool_stats; #ifdef DEBUG_POOL static void pool_debug(const char *str, void *addr) { fprintf(stderr, "*** %p %10s total %d count %d used %d\n", addr, str, pool_stats.total, pool_stats.count, pool_stats.total - pool_stats.count); } #else static void pool_debug(const char *str, void *addr) { } #endif static void announce_pre_send(struct announce_msg *m) { m->currentUtcOffset = htons(m->currentUtcOffset); m->grandmasterClockQuality.offsetScaledLogVariance = htons(m->grandmasterClockQuality.offsetScaledLogVariance); m->stepsRemoved = htons(m->stepsRemoved); } static void announce_post_recv(struct announce_msg *m) { m->currentUtcOffset = ntohs(m->currentUtcOffset); m->grandmasterClockQuality.offsetScaledLogVariance = ntohs(m->grandmasterClockQuality.offsetScaledLogVariance); m->stepsRemoved = ntohs(m->stepsRemoved); } int64_t host2net64(int64_t val) { return __cpu_to_be64(val); } int64_t net2host64(int64_t val) { return __be64_to_cpu(val); } static int hdr_post_recv(struct ptp_header *m) { if ((m->ver & VERSION_MASK) != VERSION) return -EPROTO; m->messageLength = ntohs(m->messageLength); m->correction = net2host64(m->correction); m->sourcePortIdentity.portNumber = ntohs(m->sourcePortIdentity.portNumber); m->sequenceId = ntohs(m->sequenceId); return 0; } static int hdr_pre_send(struct ptp_header *m) { m->messageLength = htons(m->messageLength); m->correction = host2net64(m->correction); m->sourcePortIdentity.portNumber = htons(m->sourcePortIdentity.portNumber); m->sequenceId = htons(m->sequenceId); return 0; } static void port_id_post_recv(struct PortIdentity *pid) { pid->portNumber = ntohs(pid->portNumber); } static void port_id_pre_send(struct PortIdentity *pid) { pid->portNumber = htons(pid->portNumber); } static int suffix_post_recv(uint8_t *ptr, int len, struct tlv_extra *last) { int cnt, err; struct TLV *tlv; if (!ptr) return 0; for (cnt = 0; len > sizeof(struct TLV); cnt++) { tlv = (struct TLV *) ptr; tlv->type = ntohs(tlv->type); tlv->length = ntohs(tlv->length); if (tlv->length % 2) { return -EBADMSG; } len -= sizeof(struct TLV); ptr += sizeof(struct TLV); if (tlv->length > len) { return -EBADMSG; } len -= tlv->length; ptr += tlv->length; err = tlv_post_recv(tlv, len ? NULL : last); if (err) return err; } return cnt; } static void suffix_pre_send(uint8_t *ptr, int cnt, struct tlv_extra *last) { int i; struct TLV *tlv; if (!ptr) return; for (i = 0; i < cnt; i++) { tlv = (struct TLV *) ptr; tlv_pre_send(tlv, i == cnt - 1 ? last : NULL); ptr += sizeof(struct TLV) + tlv->length; tlv->type = htons(tlv->type); tlv->length = htons(tlv->length); } } static void timestamp_post_recv(struct ptp_message *m, struct Timestamp *ts) { uint32_t lsb = ntohl(ts->seconds_lsb); uint16_t msb = ntohs(ts->seconds_msb); m->ts.pdu.sec = ((uint64_t)lsb) | (((uint64_t)msb) << 32); m->ts.pdu.nsec = ntohl(ts->nanoseconds); } static void timestamp_pre_send(struct Timestamp *ts) { ts->seconds_lsb = htonl(ts->seconds_lsb); ts->seconds_msb = htons(ts->seconds_msb); ts->nanoseconds = htonl(ts->nanoseconds); } /* public methods */ struct ptp_message *msg_allocate(void) { struct message_storage *s; struct ptp_message *m = TAILQ_FIRST(&msg_pool); if (m) { TAILQ_REMOVE(&msg_pool, m, list); pool_stats.count--; pool_debug("dequeue", m); } else { s = malloc(sizeof(*s)); if (s) { m = &s->msg; pool_stats.total++; pool_debug("allocate", m); } } if (m) { memset(m, 0, sizeof(*m)); m->refcnt = 1; } return m; } void msg_cleanup(void) { struct message_storage *s; struct ptp_message *m; while ((m = TAILQ_FIRST(&msg_pool)) != NULL) { TAILQ_REMOVE(&msg_pool, m, list); s = container_of(m, struct message_storage, msg); free(s); } } void msg_get(struct ptp_message *m) { m->refcnt++; } int msg_post_recv(struct ptp_message *m, int cnt) { int pdulen, type, err; uint8_t *suffix = NULL; if (cnt < sizeof(struct ptp_header)) return -EBADMSG; err = hdr_post_recv(&m->header); if (err) return err; type = msg_type(m); switch (type) { case SYNC: pdulen = sizeof(struct sync_msg); break; case DELAY_REQ: pdulen = sizeof(struct delay_req_msg); break; case PDELAY_REQ: pdulen = sizeof(struct pdelay_req_msg); break; case PDELAY_RESP: pdulen = sizeof(struct pdelay_resp_msg); break; case FOLLOW_UP: pdulen = sizeof(struct follow_up_msg); break; case DELAY_RESP: pdulen = sizeof(struct delay_resp_msg); break; case PDELAY_RESP_FOLLOW_UP: pdulen = sizeof(struct pdelay_resp_fup_msg); break; case ANNOUNCE: pdulen = sizeof(struct announce_msg); break; case SIGNALING: pdulen = sizeof(struct signaling_msg); break; case MANAGEMENT: pdulen = sizeof(struct management_msg); break; default: return -EBADMSG; } if (cnt < pdulen) return -EBADMSG; switch (type) { case SYNC: timestamp_post_recv(m, &m->sync.originTimestamp); break; case DELAY_REQ: break; case PDELAY_REQ: break; case PDELAY_RESP: timestamp_post_recv(m, &m->pdelay_resp.requestReceiptTimestamp); port_id_post_recv(&m->pdelay_resp.requestingPortIdentity); break; case FOLLOW_UP: timestamp_post_recv(m, &m->follow_up.preciseOriginTimestamp); suffix = m->follow_up.suffix; break; case DELAY_RESP: timestamp_post_recv(m, &m->delay_resp.receiveTimestamp); suffix = m->delay_resp.suffix; break; case PDELAY_RESP_FOLLOW_UP: timestamp_post_recv(m, &m->pdelay_resp_fup.responseOriginTimestamp); port_id_post_recv(&m->pdelay_resp_fup.requestingPortIdentity); suffix = m->pdelay_resp_fup.suffix; break; case ANNOUNCE: clock_gettime(CLOCK_MONOTONIC, &m->ts.host); timestamp_post_recv(m, &m->announce.originTimestamp); announce_post_recv(&m->announce); suffix = m->announce.suffix; break; case SIGNALING: suffix = m->signaling.suffix; break; case MANAGEMENT: port_id_post_recv(&m->management.targetPortIdentity); suffix = m->management.suffix; break; } if (msg_sots_missing(m)) return -ETIME; m->tlv_count = suffix_post_recv(suffix, cnt - pdulen, &m->last_tlv); if (m->tlv_count < 0) return m->tlv_count; return 0; } int msg_pre_send(struct ptp_message *m) { int type; uint8_t *suffix = NULL; if (hdr_pre_send(&m->header)) return -1; type = msg_type(m); switch (type) { case SYNC: break; case DELAY_REQ: break; case PDELAY_REQ: break; case PDELAY_RESP: timestamp_pre_send(&m->pdelay_resp.requestReceiptTimestamp); port_id_pre_send(&m->pdelay_resp.requestingPortIdentity); break; case FOLLOW_UP: timestamp_pre_send(&m->follow_up.preciseOriginTimestamp); suffix = m->follow_up.suffix; break; case DELAY_RESP: timestamp_pre_send(&m->delay_resp.receiveTimestamp); m->delay_resp.requestingPortIdentity.portNumber = htons(m->delay_resp.requestingPortIdentity.portNumber); suffix = m->delay_resp.suffix; break; case PDELAY_RESP_FOLLOW_UP: timestamp_pre_send(&m->pdelay_resp_fup.responseOriginTimestamp); port_id_pre_send(&m->pdelay_resp_fup.requestingPortIdentity); suffix = m->pdelay_resp_fup.suffix; break; case ANNOUNCE: announce_pre_send(&m->announce); suffix = m->announce.suffix; break; case SIGNALING: suffix = m->signaling.suffix; break; case MANAGEMENT: port_id_pre_send(&m->management.targetPortIdentity); suffix = m->management.suffix; break; default: return -1; } suffix_pre_send(suffix, m->tlv_count, &m->last_tlv); return 0; } const char *msg_type_string(int type) { switch (type) { case SYNC: return "SYNC"; case DELAY_REQ: return "DELAY_REQ"; case PDELAY_REQ: return "PDELAY_REQ"; case PDELAY_RESP: return "PDELAY_RESP"; case FOLLOW_UP: return "FOLLOW_UP"; case DELAY_RESP: return "DELAY_RESP"; case PDELAY_RESP_FOLLOW_UP: return "PDELAY_RESP_FOLLOW_UP"; case ANNOUNCE: return "ANNOUNCE"; case SIGNALING: return "SIGNALING"; case MANAGEMENT: return "MANAGEMENT"; } return "unknown"; } void msg_print(struct ptp_message *m, FILE *fp) { fprintf(fp, "\t" "%-10s " // "versionPTP 0x%02X " // "messageLength %hu " // "domainNumber %u " // "reserved1 0x%02X " // "flagField 0x%02X%02X " // "correction %lld " // "reserved2 %u " // "sourcePortIdentity ... " "sequenceId %4hu " // "control %u " // "logMessageInterval %d " , msg_type_string(msg_type(m)), // m->header.ver, // m->header.messageLength, // m->header.domainNumber, // m->header.reserved1, // m->header.flagField[0], // m->header.flagField[1], // m->header.correction, // m->header.reserved2, // m->header.sourcePortIdentity, m->header.sequenceId // m->header.control, // m->header.logMessageInterval ); fprintf(fp, "\n"); } void msg_put(struct ptp_message *m) { m->refcnt--; if (!m->refcnt) { pool_stats.count++; pool_debug("recycle", m); TAILQ_INSERT_HEAD(&msg_pool, m, list); } } int msg_sots_missing(struct ptp_message *m) { int type = msg_type(m); switch (type) { case SYNC: case DELAY_REQ: case PDELAY_REQ: case PDELAY_RESP: break; case FOLLOW_UP: case DELAY_RESP: case PDELAY_RESP_FOLLOW_UP: case ANNOUNCE: case SIGNALING: case MANAGEMENT: default: return 0; } return msg_sots_valid(m) ? 0 : 1; } linuxptp-1.6/msg.h000066400000000000000000000236241257727010700142210ustar00rootroot00000000000000/** * @file msg.h * @brief Implements the various PTP message types. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_MSG_H #define HAVE_MSG_H #include #include #include #include "address.h" #include "ddt.h" #include "tlv.h" #define PTP_VERSION 2 /* Values for the messageType field */ #define SYNC 0x0 #define DELAY_REQ 0x1 #define PDELAY_REQ 0x2 #define PDELAY_RESP 0x3 #define FOLLOW_UP 0x8 #define DELAY_RESP 0x9 #define PDELAY_RESP_FOLLOW_UP 0xA #define ANNOUNCE 0xB #define SIGNALING 0xC #define MANAGEMENT 0xD /* Bits for flagField[0] */ #define ALT_MASTER (1<<0) #define TWO_STEP (1<<1) #define UNICAST (1<<2) /* Bits for flagField[1] */ #define LEAP_61 (1<<0) #define LEAP_59 (1<<1) #define UTC_OFF_VALID (1<<2) #define PTP_TIMESCALE (1<<3) #define TIME_TRACEABLE (1<<4) #define FREQ_TRACEABLE (1<<5) enum timestamp_type { TS_SOFTWARE, TS_HARDWARE, TS_LEGACY_HW, TS_ONESTEP, }; struct hw_timestamp { enum timestamp_type type; struct timespec ts; struct timespec sw; }; enum controlField { CTL_SYNC, CTL_DELAY_REQ, CTL_FOLLOW_UP, CTL_DELAY_RESP, CTL_MANAGEMENT, CTL_OTHER, }; struct ptp_header { uint8_t tsmt; /* transportSpecific | messageType */ uint8_t ver; /* reserved | versionPTP */ UInteger16 messageLength; UInteger8 domainNumber; Octet reserved1; Octet flagField[2]; Integer64 correction; UInteger32 reserved2; struct PortIdentity sourcePortIdentity; UInteger16 sequenceId; UInteger8 control; Integer8 logMessageInterval; } PACKED; struct announce_msg { struct ptp_header hdr; struct Timestamp originTimestamp; Integer16 currentUtcOffset; Octet reserved; UInteger8 grandmasterPriority1; struct ClockQuality grandmasterClockQuality; UInteger8 grandmasterPriority2; struct ClockIdentity grandmasterIdentity; UInteger16 stepsRemoved; Enumeration8 timeSource; uint8_t suffix[0]; } PACKED; struct sync_msg { struct ptp_header hdr; struct Timestamp originTimestamp; } PACKED; struct delay_req_msg { struct ptp_header hdr; struct Timestamp originTimestamp; } PACKED; struct follow_up_msg { struct ptp_header hdr; struct Timestamp preciseOriginTimestamp; uint8_t suffix[0]; } PACKED; struct delay_resp_msg { struct ptp_header hdr; struct Timestamp receiveTimestamp; struct PortIdentity requestingPortIdentity; uint8_t suffix[0]; } PACKED; struct pdelay_req_msg { struct ptp_header hdr; struct Timestamp originTimestamp; struct PortIdentity reserved; } PACKED; struct pdelay_resp_msg { struct ptp_header hdr; struct Timestamp requestReceiptTimestamp; struct PortIdentity requestingPortIdentity; } PACKED; struct pdelay_resp_fup_msg { struct ptp_header hdr; struct Timestamp responseOriginTimestamp; struct PortIdentity requestingPortIdentity; uint8_t suffix[0]; } PACKED; struct signaling_msg { struct ptp_header hdr; struct PortIdentity targetPortIdentity; uint8_t suffix[0]; } PACKED; struct management_msg { struct ptp_header hdr; struct PortIdentity targetPortIdentity; UInteger8 startingBoundaryHops; UInteger8 boundaryHops; uint8_t flags; /* reserved | actionField */ uint8_t reserved; uint8_t suffix[0]; } PACKED; struct message_data { uint8_t buffer[1500]; } PACKED; struct ptp_message { union { struct ptp_header header; struct announce_msg announce; struct sync_msg sync; struct delay_req_msg delay_req; struct follow_up_msg follow_up; struct delay_resp_msg delay_resp; struct pdelay_req_msg pdelay_req; struct pdelay_resp_msg pdelay_resp; struct pdelay_resp_fup_msg pdelay_resp_fup; struct signaling_msg signaling; struct management_msg management; struct message_data data; } PACKED; /**/ int tail_room; int refcnt; TAILQ_ENTRY(ptp_message) list; struct { /** * Contains the time stamp from the packet data in a * native binary format for the host machine. The * exact source of the time stamp's value depends on * the message type: * * - announce originTimestamp * - follow_up preciseOriginTimestamp * - sync originTimestamp * - delay_req originTimestamp * - pdelay_resp requestReceiptTimestamp * - pdelay_resp_fup responseOriginTimestamp */ struct timestamp pdu; /** * Approximate ingress time stamp using the relative * CLOCK_MONOTONIC. Used to determine when announce * messages have expired. */ struct timespec host; } ts; /** * Contains the ingress time stamp obtained by the * SO_TIMESTAMPING socket option. */ struct hw_timestamp hwts; /** * Contains the address this message was received from or should be * sent to. */ struct address address; /** * Contains the number of TLVs in the suffix. */ int tlv_count; /** * Used to hold the data of the last TLV in the message when * the layout of the TLV makes it difficult to access the data * directly from the message's buffer. */ struct tlv_extra last_tlv; }; /** * Obtain the action field from a management message. * @param m A management message. * @return The value of the action field. */ static inline uint8_t management_action(struct ptp_message *m) { return m->management.flags & 0x0f; } /** * Test a given bit in a message's flag field. * @param m Message to test. * @param index Index into flag field, either 0 or 1. * @param bit Bit mask of one bit to test. * @return One if bit is set, zero otherwise. */ static inline Boolean field_is_set(struct ptp_message *m, int index, Octet bit) { return m->header.flagField[index] & bit ? TRUE : FALSE; } /** * Obtain the transportSpecific field from a message. * @param m Message to test. * @return The value of the transportSpecific field. Note that the * value is returned unshifted, in the upper nibble. */ static inline UInteger8 msg_transport_specific(struct ptp_message *m) { return m->header.tsmt & 0xf0; } /** * Obtain the message type. * @param m Message to test. * @return The value of the messageType field. */ static inline int msg_type(struct ptp_message *m) { return m->header.tsmt & 0x0f; } /** * Allocate a new message instance. * * Messages are reference counted, and newly allocated messages have a * reference count of one. Allocated messages are freed using the * function @ref msg_put(). * * @return Pointer to a message on success, NULL otherwise. */ struct ptp_message *msg_allocate(void); /** * Release all of the memory in the message cache. */ void msg_cleanup(void); /** * Obtain a reference to a message, increasing its reference count by one. * @param m A message obtained using @ref msg_allocate(). */ void msg_get(struct ptp_message *m); /** * Process messages after reception. * @param m A message obtained using @ref msg_allocate(). * @param cnt The size of 'm' in bytes. * @return Zero on success, non-zero if the message is invalid. */ int msg_post_recv(struct ptp_message *m, int cnt); /** * Prepare messages for transmission. * @param m A message obtained using @ref msg_allocate(). * @return Zero on success, non-zero if the message is invalid. */ int msg_pre_send(struct ptp_message *m); /** * Print messages for debugging purposes. * @param type Value of the messageType field as returned by @ref msg_type(). * @return String describing the message type. */ const char *msg_type_string(int type); /** * Print messages for debugging purposes. * @param m A message obtained using @ref msg_allocate(). * @param fp An open file pointer. */ void msg_print(struct ptp_message *m, FILE *fp); /** * Release a reference to a message, decreasing its reference count by one. * @param m A message obtained using @ref msg_allocate(). */ void msg_put(struct ptp_message *m); /** * Test whether an event message received a valid SO_TIMESTAMPING time stamp. * @param m Message to test. * @return One if the message is an event without a time stamp, zero otherwise. */ int msg_sots_missing(struct ptp_message *m); /** * Test whether a message has a valid SO_TIMESTAMPING time stamp. * @param m Message to test. * @return One if the message has a valid time stamp, zero otherwise. */ static inline int msg_sots_valid(struct ptp_message *m) { return (m->hwts.ts.tv_sec || m->hwts.ts.tv_nsec) ? 1 : 0; } /** * Work around buggy 802.1AS switches. */ extern int assume_two_step; /** * Test whether a message is one-step message. * @param m Message to test. * @return One if the message is a one-step, zero otherwise. */ static inline Boolean one_step(struct ptp_message *m) { if (assume_two_step) return 0; return !field_is_set(m, 0, TWO_STEP); } /** * Convert a 64 bit word into network byte order. */ int64_t host2net64(int64_t val); /** * Convert a 64 bit word into host byte order. */ int64_t net2host64(int64_t val); #endif linuxptp-1.6/notification.h000066400000000000000000000017321257727010700161150ustar00rootroot00000000000000/** * @file notification.h * @brief Definitions for the notification framework. * @note Copyright (C) 2014 Red Hat, Inc., Jiri Benc * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_NOTIFICATION_H #define HAVE_NOTIFICATION_H enum notification { NOTIFY_PORT_STATE, }; #endif linuxptp-1.6/ntpshm.c000066400000000000000000000101031257727010700147230ustar00rootroot00000000000000/** * @file ntpshm.c * @brief Implements a servo providing the NTP SHM reference clock to * send the samples to another process. * @note Copyright (C) 2014 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "config.h" #include "print.h" #include "ntpshm.h" #include "servo_private.h" /* NTP leap values */ #define LEAP_NORMAL 0x0 #define LEAP_INSERT 0x1 #define LEAP_DELETE 0x2 /* Key of the first SHM segment */ #define SHMKEY 0x4e545030 /* Declaration of the SHM segment from ntp (ntpd/refclock_shm.c) */ struct shmTime { int mode; /* 0 - if valid set * use values, * clear valid * 1 - if valid set * if count before and after read of values is equal, * use values * clear valid */ volatile int count; time_t clockTimeStampSec; int clockTimeStampUSec; time_t receiveTimeStampSec; int receiveTimeStampUSec; int leap; int precision; int nsamples; volatile int valid; int clockTimeStampNSec; int receiveTimeStampNSec; int dummy[8]; }; struct ntpshm_servo { struct servo servo; struct shmTime *shm; int leap; }; static void ntpshm_destroy(struct servo *servo) { struct ntpshm_servo *s = container_of(servo, struct ntpshm_servo, servo); shmdt(s->shm); free(s); } static double ntpshm_sample(struct servo *servo, int64_t offset, uint64_t local_ts, double weight, enum servo_state *state) { struct ntpshm_servo *s = container_of(servo, struct ntpshm_servo, servo); uint64_t clock_ts = local_ts - offset; s->shm->mode = 1; s->shm->count++; s->shm->valid = 0; /* TODO: write memory barrier */ s->shm->clockTimeStampSec = clock_ts / NS_PER_SEC; s->shm->clockTimeStampNSec = clock_ts % NS_PER_SEC; s->shm->clockTimeStampUSec = s->shm->clockTimeStampNSec / 1000; s->shm->receiveTimeStampSec = local_ts / NS_PER_SEC; s->shm->receiveTimeStampNSec = local_ts % NS_PER_SEC; s->shm->receiveTimeStampUSec = s->shm->receiveTimeStampNSec / 1000; s->shm->precision = -30; /* 1 nanosecond */ switch (s->leap) { case -1: s->shm->leap = LEAP_DELETE; break; case 1: s->shm->leap = LEAP_INSERT; break; default: s->shm->leap = LEAP_NORMAL; } /* TODO: write memory barrier */ s->shm->count++; s->shm->valid = 1; *state = SERVO_UNLOCKED; return 0.0; } static void ntpshm_sync_interval(struct servo *servo, double interval) { } static void ntpshm_reset(struct servo *servo) { } static void ntpshm_leap(struct servo *servo, int leap) { struct ntpshm_servo *s = container_of(servo, struct ntpshm_servo, servo); s->leap = leap; } struct servo *ntpshm_servo_create(struct config *cfg) { struct ntpshm_servo *s; int ntpshm_segment = config_get_int(cfg, NULL, "ntpshm_segment"); int shmid; s = calloc(1, sizeof(*s)); if (!s) return NULL; s->servo.destroy = ntpshm_destroy; s->servo.sample = ntpshm_sample; s->servo.sync_interval = ntpshm_sync_interval; s->servo.reset = ntpshm_reset; s->servo.leap = ntpshm_leap; shmid = shmget(SHMKEY + ntpshm_segment, sizeof (struct shmTime), IPC_CREAT | 0600); if (shmid == -1) { pr_err("ntpshm: shmget failed: %m"); free(s); return NULL; } s->shm = (struct shmTime *)shmat(shmid, 0, 0); if (s->shm == (void *)-1) { pr_err("ntpshm: shmat failed: %m"); free(s); return NULL; } return &s->servo; } linuxptp-1.6/ntpshm.h000066400000000000000000000016551257727010700147440ustar00rootroot00000000000000/** * @file ntpshm.h * @note Copyright (C) 2014 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_NTPSHM_H #define HAVE_NTPSHM_H #include "servo.h" struct servo *ntpshm_servo_create(struct config *cfg); #endif linuxptp-1.6/nullf.c000066400000000000000000000040101257727010700145320ustar00rootroot00000000000000/** * @file nullf.c * @brief Implements a clock servo that always set the frequency offset to zero. * @note Copyright (C) 2015 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "nullf.h" #include "print.h" #include "servo_private.h" struct nullf_servo { struct servo servo; }; static void nullf_destroy(struct servo *servo) { struct nullf_servo *s = container_of(servo, struct nullf_servo, servo); free(s); } static double nullf_sample(struct servo *servo, int64_t offset, uint64_t local_ts, double weight, enum servo_state *state) { if (!offset) { *state = SERVO_LOCKED; return 0.0; } if ((servo->first_update && servo->first_step_threshold && servo->first_step_threshold < fabs(offset)) || (servo->step_threshold && servo->step_threshold < fabs(offset))) { *state = SERVO_JUMP; } else { *state = SERVO_UNLOCKED; } return 0.0; } static void nullf_sync_interval(struct servo *servo, double interval) { } static void nullf_reset(struct servo *servo) { } struct servo *nullf_servo_create(void) { struct nullf_servo *s; s = calloc(1, sizeof(*s)); if (!s) return NULL; s->servo.destroy = nullf_destroy; s->servo.sample = nullf_sample; s->servo.sync_interval = nullf_sync_interval; s->servo.reset = nullf_reset; return &s->servo; } linuxptp-1.6/nullf.h000066400000000000000000000016371257727010700145530ustar00rootroot00000000000000/** * @file nullf.h * @note Copyright (C) 2015 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_NULLF_H #define HAVE_NULLF_H #include "servo.h" struct servo *nullf_servo_create(void); #endif linuxptp-1.6/pdt.h000066400000000000000000000023471257727010700142210ustar00rootroot00000000000000/** * @file pdt.h * @brief Primitive data types * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_PDT_H #define HAVE_PDT_H #include enum {FALSE, TRUE}; typedef int Boolean; typedef uint8_t Enumeration8; typedef uint16_t Enumeration16; typedef int8_t Integer8; typedef uint8_t UInteger8; typedef int16_t Integer16; typedef uint16_t UInteger16; typedef int32_t Integer32; typedef uint32_t UInteger32; typedef int64_t Integer64; typedef uint8_t Octet; #endif linuxptp-1.6/phc.c000066400000000000000000000045161257727010700141770ustar00rootroot00000000000000/** * @file phc.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include "phc.h" /* * On 32 bit platforms, the PHC driver's maximum adjustment (type * 'int' in units of ppb) can overflow the timex.freq field (type * 'long'). So in this case we clamp the maximum to the largest * possible adjustment that fits into a 32 bit long. */ #define BITS_PER_LONG (sizeof(long)*8) #define MAX_PPB_32 32767999 /* 2^31 - 1 / 65.536 */ static int phc_get_caps(clockid_t clkid, struct ptp_clock_caps *caps); clockid_t phc_open(char *phc) { clockid_t clkid; struct ptp_clock_caps caps; int fd = open(phc, O_RDWR); if (fd < 0) return CLOCK_INVALID; clkid = FD_TO_CLOCKID(fd); /* check if clkid is valid */ if (phc_get_caps(clkid, &caps)) { close(fd); return CLOCK_INVALID; } return clkid; } void phc_close(clockid_t clkid) { if (clkid == CLOCK_INVALID) return; close(CLOCKID_TO_FD(clkid)); } static int phc_get_caps(clockid_t clkid, struct ptp_clock_caps *caps) { int fd = CLOCKID_TO_FD(clkid), err; err = ioctl(fd, PTP_CLOCK_GETCAPS, caps); if (err) perror("PTP_CLOCK_GETCAPS"); return err; } int phc_max_adj(clockid_t clkid) { int max; struct ptp_clock_caps caps; if (phc_get_caps(clkid, &caps)) return 0; max = caps.max_adj; if (BITS_PER_LONG == 32 && max > MAX_PPB_32) max = MAX_PPB_32; return max; } int phc_has_pps(clockid_t clkid) { struct ptp_clock_caps caps; if (phc_get_caps(clkid, &caps)) return 0; return caps.pps; } linuxptp-1.6/phc.h000066400000000000000000000032641257727010700142030ustar00rootroot00000000000000/** * @file phc.h * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_PHC_H #define HAVE_PHC_H #include "missing.h" /** * Opens a PTP hardware clock device. * * @param phc The device to open. * * @return A valid clock ID on success, CLOCK_INVALID otherwise. */ clockid_t phc_open(char *phc); /** * Closes a PTP hardware clock device. * * @param clkid A clock ID obtained using phc_open(). */ void phc_close(clockid_t clkid); /** * Query the maximum frequency adjustment of a PTP hardware clock device. * * @param clkid A clock ID obtained using phc_open(). * * @return The clock's maximum frequency adjustment in parts per billion. */ int phc_max_adj(clockid_t clkid); /** * Checks whether the given PTP hardware clock device supports PPS output. * * @param clkid A clock ID obtained using phc_open(). * * @return Zero if PPS output is not supported by the clock, non-zero * otherwise. */ int phc_has_pps(clockid_t clkid); #endif linuxptp-1.6/phc2sys.8000066400000000000000000000214721257727010700147450ustar00rootroot00000000000000.TH PHC2SYS 8 "November 2012" "linuxptp" .SH NAME phc2sys \- synchronize two or more clocks .SH SYNOPSIS .B phc2sys \-a [ .B \-r ] [ .B \-r ] [ options ] .br .B phc2sys [ .BI \-d " pps-device" ] [ .BI \-s " device" ] [ .BI \-c " device" ] [ .BI \-O " offset" ] [ .BI \-w ] [ options ] .SH DESCRIPTION .B phc2sys is a program which synchronizes two or more clocks in the system. Typically, it is used to synchronize the system clock to a PTP hardware clock (PHC), which itself is synchronized by the .BR ptp4l (8) program. With the .B \-a option, the clocks to synchronize are fetched from the running .B ptp4l daemon and the direction of synchronization automatically follows changes of the PTP port states. Manual configuration is also possible. When using manual configuration, two synchronization modes are supported, one uses a pulse per second (PPS) signal provided by the source clock and the other mode reads time from the source clock directly. Some clocks can be used in both modes, the mode which will synchronize the slave clock with better accuracy depends on hardware and driver implementation. .SH OPTIONS .TP .BI \-a Read the clocks to synchronize from running .B ptp4l and follow changes in the port states, adjusting the synchronization direction automatically. The system clock (CLOCK_REALTIME) is not synchronized, unless the .B \-r option is also specified. .TP .BI \-r Only valid together with the .B \-a option. Instructs .B phc2sys to also synchronize the system clock (CLOCK_REALTIME). By default, the system clock is not considered as a possible time source. If you want the system clock to be eligible to become a time source, specify the .B \-r option twice. .TP .BI \-d " pps-device" Specify the PPS device of the master clock (e.g. /dev/pps0). With this option the PPS synchronization mode is used instead of the direct mode. As the PPS signal does not specify time and only marks start of a second, the slave clock should be already close to the correct time before .B phc2sys is started or the .B \-s option should be used too. With the .B \-s option the PPS signal of the master clock is enabled automatically, otherwise it has to be enabled before .B phc2sys is started (e.g. by running \f(CWecho 1 > /sys/class/ptp/ptp0/pps_enable\fP). This option can be used only with the system clock as the slave clock. Not compatible with the .B \-a option. .TP .BI \-s " device" Specify the master clock by device (e.g. /dev/ptp0) or interface (e.g. eth0) or by name (e.g. CLOCK_REALTIME for the system clock). When this option is used together with the .B \-d option, the master clock is used only to correct the offset by whole number of seconds, which cannot be fixed with PPS alone. Not compatible with the .B \-a option. .TP .BI \-i " interface" Performs the exact same function as .B \-s for compatibility reasons. Previously enabled specifying master clock by network interface. However, this can now be done using .B \-s and this option is no longer necessary. As such it has been deprecated, and should no longer be used. .TP .BI \-c " device" Specify the slave clock by device (e.g. /dev/ptp1) or interface (e.g. eth1) or by name. The default is CLOCK_REALTIME (the system clock). Not compatible with the .B \-a option. .TP .BI \-E " servo" Specify which clock servo should be used. Valid values are pi for a PI controller, linreg for an adaptive controller using linear regression, and ntpshm for the NTP SHM reference clock to allow another process to synchronize the local clock. The default is pi. .TP .BI \-P " kp" Specify the proportional constant of the PI controller. The default is 0.7. .TP .BI \-I " ki" Specify the integral constant of the PI controller. The default is 0.3. .TP .BI \-S " step" Specify the step threshold of the servo. It is the maximum offset that the servo corrects by changing the clock frequency instead of stepping the clock. The clock is stepped on start regardless of the option if the offset is larger than 20 microseconds (unless the .BI \-F option is used). It's specified in seconds. The value of 0.0 disables stepping after the start. The default is 0.0. .TP .BI \-F " step" Specify the step threshold applied only on the first update. It is the maximum offset that is corrected by adjusting clock. It's specified in seconds. The value of 0.0 disables stepping on start. The default is 0.00002 (20 microseconds). .TP .BI \-R " update-rate" Specify the slave clock update rate when running in the direct synchronization mode. The default is 1 per second. .TP .BI \-N " phc-num" Specify the number of master clock readings per one slave clock update. Only the fastest reading is used to update the slave clock, this is useful to minimize the error caused by random delays in scheduling and bus utilization. The default is 5. .TP .BI \-O " offset" Specify the offset between the slave and master times in seconds. Not compatible with the .B \-a option. See .SM .B TIME SCALE USAGE below. .TP .BI \-L " freq-limit" The maximum allowed frequency offset between uncorrected clock and the system monotonic clock in parts per billion (ppb). This is used as a sanity check of the synchronized clock. When a larger offset is measured, a warning message will be printed and the servo will be reset. When set to 0, the sanity check is disabled. The default is 200000000 (20%). .TP .BI \-M " segment" The number of the SHM segment used by ntpshm servo. The default is 0. .TP .BI \-u " summary-updates" Specify the number of clock updates included in summary statistics. The statistics include offset root mean square (RMS), maximum absolute offset, frequency offset mean and standard deviation, and mean of the delay in clock readings and standard deviation. The units are nanoseconds and parts per billion (ppb). If zero, the individual samples are printed instead of the statistics. The messages are printed at the LOG_INFO level. The default is 0 (disabled). .TP .B \-w Wait until ptp4l is in a synchronized state. If the .B \-O option is not used, also keep the offset between the slave and master times updated according to the currentUtcOffset value obtained from ptp4l and the direction of the clock synchronization. Not compatible with the .B \-a option. .TP .BI \-n " domain-number" Specify the domain number used by ptp4l. The default is 0. .TP .B \-x When a leap second is announced, don't apply it in the kernel by stepping the clock, but let the servo correct the one-second offset slowly by changing the clock frequency (unless the .B \-S option is used). .TP .BI \-z " uds-address" Specifies the address of the server's UNIX domain socket. The default is /var/run/ptp4l. .TP .BI \-l " print-level" Set the maximum syslog level of messages which should be printed or sent to the system logger. The default is 6 (LOG_INFO). .TP .B \-m Print messages to the standard output. .TP .B \-q Don't send messages to the system logger. .TP .BI \-h Display a help message. .TP .B \-v Prints the software version and exits. .SH TIME SCALE USAGE .B Ptp4l uses either PTP time scale or UTC (Coordinated Universal Time) time scale. PTP time scale is continuous and shifted against UTC by a few tens of seconds as PTP time scale does not apply leap seconds. In hardware time stamping mode, .B ptp4l announces use of PTP time scale and PHC is used for the stamps. That means PHC must follow PTP time scale while system clock follows UTC. Time offset between these two is maintained by .BR phc2sys . .B Phc2sys acquires the offset value either by reading it from ptp4l when .B \-a or .B \-w is in effect or from command line when .B \-O is supplied. Failure to maintain the correct offset can result in local system clock being off some seconds to domain master system clock when in slave mode, or incorect PTP time announced to the network in case the host is the domain master. .SH EXAMPLES Synchronize time automatically according to the current .B ptp4l state, synchronize the system clock to the remote master. .RS \f(CWphc2sys \-a \-r\fP .RE Same as above, but when the host becomes the domain master, synchronize time in the domain to its system clock. .RS \f(CWphc2sys \-a \-rr\fP .RE The host is a domain master, PTP clock is synchronized to system clock and the time offset is obtained from .BR ptp4l . .B Phc2sys waits for .B ptp4l to get at least one port in master or slave mode before starting the synchronization. .RS \f(CWphc2sys \-c /dev/ptp0 \-s CLOCK_REALTIME \-w\fP .RE Same as above, time offset is provided on command line and .B phc2sys does not wait for .BR ptp4l . .RS \f(CWphc2sys \-c /dev/ptp0 \-s CLOCK_REALTIME \-O 35\fP .RE The host is in slave mode, system clock is synchronized from PTP clock, .B phc2sys waits for .B ptp4l and the offset is set automatically. .RS \f(CWphc2sys \-s /dev/ptp0 \-w\fP .RE Same as above, PTP clock id is read from the network interface, the offset is provided on command line .B phc2sys does not wait. .RS \f(CWphc2sys \-s eth0 \-O \-35\fP .RE .SH SEE ALSO .BR ptp4l (8) linuxptp-1.6/phc2sys.c000066400000000000000000001053041257727010700150150ustar00rootroot00000000000000/** * @file phc2sys.c * @brief Utility program to synchronize two clocks via a PPS. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clockadj.h" #include "clockcheck.h" #include "ds.h" #include "fsm.h" #include "missing.h" #include "notification.h" #include "ntpshm.h" #include "phc.h" #include "pi.h" #include "pmc_common.h" #include "print.h" #include "servo.h" #include "sk.h" #include "stats.h" #include "sysoff.h" #include "tlv.h" #include "uds.h" #include "util.h" #include "version.h" #define KP 0.7 #define KI 0.3 #define NS_PER_SEC 1000000000LL #define PHC_PPS_OFFSET_LIMIT 10000000 #define PMC_UPDATE_INTERVAL (60 * NS_PER_SEC) #define PMC_SUBSCRIBE_DURATION 180 /* 3 minutes */ /* Note that PMC_SUBSCRIBE_DURATION has to be longer than * PMC_UPDATE_INTERVAL otherwise subscription will time out before it is * renewed. */ struct clock { LIST_ENTRY(clock) list; clockid_t clkid; int sysoff_supported; int is_utc; int dest_only; int state; int new_state; int sync_offset; int leap_set; int utc_offset_set; struct servo *servo; enum servo_state servo_state; char *device; const char *source_label; struct stats *offset_stats; struct stats *freq_stats; struct stats *delay_stats; struct clockcheck *sanity_check; }; struct port { LIST_ENTRY(port) list; unsigned int number; int state; struct clock *clock; }; struct node { unsigned int stats_max_count; int sanity_freq_limit; enum servo_type servo_type; int phc_readings; double phc_interval; int sync_offset; int forced_sync_offset; int utc_offset_traceable; int leap; int kernel_leap; struct pmc *pmc; int pmc_ds_requested; uint64_t pmc_last_update; int state_changed; int clock_identity_set; struct ClockIdentity clock_identity; LIST_HEAD(port_head, port) ports; LIST_HEAD(clock_head, clock) clocks; struct clock *master; }; static struct config *phc2sys_config; static int update_pmc(struct node *node, int subscribe); static int clock_handle_leap(struct node *node, struct clock *clock, int64_t offset, uint64_t ts); static int run_pmc_get_utc_offset(struct node *node, int timeout); static void run_pmc_events(struct node *node); static clockid_t clock_open(char *device) { struct sk_ts_info ts_info; char phc_device[16]; int clkid; /* check if device is CLOCK_REALTIME */ if (!strcasecmp(device, "CLOCK_REALTIME")) return CLOCK_REALTIME; /* check if device is valid phc device */ clkid = phc_open(device); if (clkid != CLOCK_INVALID) return clkid; /* check if device is a valid ethernet device */ if (sk_get_ts_info(device, &ts_info) || !ts_info.valid) { fprintf(stderr, "unknown clock %s: %m\n", device); return CLOCK_INVALID; } if (ts_info.phc_index < 0) { fprintf(stderr, "interface %s does not have a PHC\n", device); return CLOCK_INVALID; } sprintf(phc_device, "/dev/ptp%d", ts_info.phc_index); clkid = phc_open(phc_device); if (clkid == CLOCK_INVALID) fprintf(stderr, "cannot open %s: %m\n", device); return clkid; } static struct clock *clock_add(struct node *node, char *device) { struct clock *c; clockid_t clkid = CLOCK_INVALID; int max_ppb; double ppb; if (device) { clkid = clock_open(device); if (clkid == CLOCK_INVALID) return NULL; } c = calloc(1, sizeof(*c)); if (!c) { pr_err("failed to allocate memory for a clock"); return NULL; } c->clkid = clkid; c->servo_state = SERVO_UNLOCKED; c->device = strdup(device); if (c->clkid == CLOCK_REALTIME) { c->source_label = "sys"; c->is_utc = 1; } else { c->source_label = "phc"; } if (node->stats_max_count > 0) { c->offset_stats = stats_create(); c->freq_stats = stats_create(); c->delay_stats = stats_create(); if (!c->offset_stats || !c->freq_stats || !c->delay_stats) { pr_err("failed to create stats"); return NULL; } } if (node->sanity_freq_limit) { c->sanity_check = clockcheck_create(node->sanity_freq_limit); if (!c->sanity_check) { pr_err("failed to create clock check"); return NULL; } } clockadj_init(c->clkid); ppb = clockadj_get_freq(c->clkid); /* The reading may silently fail and return 0, reset the frequency to make sure ppb is the actual frequency of the clock. */ clockadj_set_freq(c->clkid, ppb); if (c->clkid == CLOCK_REALTIME) { sysclk_set_leap(0); max_ppb = sysclk_max_freq(); } else { max_ppb = phc_max_adj(c->clkid); if (!max_ppb) { pr_err("clock is not adjustable"); return NULL; } } c->servo = servo_create(phc2sys_config, node->servo_type, -ppb, max_ppb, 0); servo_sync_interval(c->servo, node->phc_interval); if (clkid != CLOCK_REALTIME) c->sysoff_supported = (SYSOFF_SUPPORTED == sysoff_probe(CLOCKID_TO_FD(clkid), node->phc_readings)); LIST_INSERT_HEAD(&node->clocks, c, list); return c; } static struct port *port_get(struct node *node, unsigned int number) { struct port *p; LIST_FOREACH(p, &node->ports, list) { if (p->number == number) return p; } return NULL; } static struct port *port_add(struct node *node, unsigned int number, char *device) { struct port *p; struct clock *c = NULL, *tmp; p = port_get(node, number); if (p) return p; /* port is a new one, look whether we have the device already on * a different port */ LIST_FOREACH(tmp, &node->clocks, list) { if (!strcmp(tmp->device, device)) { c = tmp; break; } } if (!c) { c = clock_add(node, device); if (!c) return NULL; } p = malloc(sizeof(*p)); if (!p) { pr_err("failed to allocate memory for a port"); return NULL; } p->number = number; p->clock = c; LIST_INSERT_HEAD(&node->ports, p, list); return p; } static void clock_reinit(struct clock *clock) { servo_reset(clock->servo); clock->servo_state = SERVO_UNLOCKED; if (clock->offset_stats) { stats_reset(clock->offset_stats); stats_reset(clock->freq_stats); stats_reset(clock->delay_stats); } } static void reconfigure(struct node *node) { struct clock *c, *rt = NULL, *src = NULL, *last = NULL; int src_cnt = 0, dst_cnt = 0; pr_info("reconfiguring after port state change"); node->state_changed = 0; LIST_FOREACH(c, &node->clocks, list) { if (c->clkid == CLOCK_REALTIME) { rt = c; continue; } if (c->new_state) { if (c->new_state == PS_MASTER) clock_reinit(c); c->state = c->new_state; c->new_state = 0; } switch (c->state) { case PS_FAULTY: case PS_DISABLED: case PS_LISTENING: case PS_PRE_MASTER: case PS_MASTER: case PS_PASSIVE: pr_info("selecting %s for synchronization", c->device); dst_cnt++; break; case PS_UNCALIBRATED: src_cnt++; break; case PS_SLAVE: src = c; src_cnt++; break; } last = c; } if (dst_cnt > 1 && !src) { if (!rt || rt->dest_only) { node->master = last; /* Reset to original state in next reconfiguration. */ node->master->new_state = node->master->state; node->master->state = PS_SLAVE; if (rt) rt->state = PS_SLAVE; pr_info("no source, selecting %s as the default clock", last->device); return; } } if (src_cnt > 1) { pr_info("multiple master clocks available, postponing sync..."); node->master = NULL; return; } if (src_cnt > 0 && !src) { pr_info("master clock not ready, waiting..."); node->master = NULL; return; } if (!src_cnt && !dst_cnt) { pr_info("no PHC ready, waiting..."); node->master = NULL; return; } if ((!src_cnt && (!rt || rt->dest_only)) || (!dst_cnt && !rt)) { pr_info("nothing to synchronize"); node->master = NULL; return; } if (!src_cnt) { src = rt; rt->state = PS_SLAVE; } else if (rt) { if (rt->state != PS_MASTER) { rt->state = PS_MASTER; clock_reinit(rt); } pr_info("selecting %s for synchronization", rt->device); } node->master = src; pr_info("selecting %s as the master clock", src->device); } static int read_phc(clockid_t clkid, clockid_t sysclk, int readings, int64_t *offset, uint64_t *ts, int64_t *delay) { struct timespec tdst1, tdst2, tsrc; int i; int64_t interval, best_interval = INT64_MAX; /* Pick the quickest clkid reading. */ for (i = 0; i < readings; i++) { if (clock_gettime(sysclk, &tdst1) || clock_gettime(clkid, &tsrc) || clock_gettime(sysclk, &tdst2)) { pr_err("failed to read clock: %m"); return 0; } interval = (tdst2.tv_sec - tdst1.tv_sec) * NS_PER_SEC + tdst2.tv_nsec - tdst1.tv_nsec; if (best_interval > interval) { best_interval = interval; *offset = (tdst1.tv_sec - tsrc.tv_sec) * NS_PER_SEC + tdst1.tv_nsec - tsrc.tv_nsec + interval / 2; *ts = tdst2.tv_sec * NS_PER_SEC + tdst2.tv_nsec; } } *delay = best_interval; return 1; } static int64_t get_sync_offset(struct node *node, struct clock *dst) { int direction = node->forced_sync_offset; if (!direction) direction = dst->is_utc - node->master->is_utc; return (int64_t)dst->sync_offset * NS_PER_SEC * direction; } static void update_clock_stats(struct clock *clock, unsigned int max_count, int64_t offset, double freq, int64_t delay) { struct stats_result offset_stats, freq_stats, delay_stats; stats_add_value(clock->offset_stats, offset); stats_add_value(clock->freq_stats, freq); if (delay >= 0) stats_add_value(clock->delay_stats, delay); if (stats_get_num_values(clock->offset_stats) < max_count) return; stats_get_result(clock->offset_stats, &offset_stats); stats_get_result(clock->freq_stats, &freq_stats); if (!stats_get_result(clock->delay_stats, &delay_stats)) { pr_info("rms %4.0f max %4.0f " "freq %+6.0f +/- %3.0f " "delay %5.0f +/- %3.0f", offset_stats.rms, offset_stats.max_abs, freq_stats.mean, freq_stats.stddev, delay_stats.mean, delay_stats.stddev); } else { pr_info("rms %4.0f max %4.0f " "freq %+6.0f +/- %3.0f", offset_stats.rms, offset_stats.max_abs, freq_stats.mean, freq_stats.stddev); } stats_reset(clock->offset_stats); stats_reset(clock->freq_stats); stats_reset(clock->delay_stats); } static void update_clock(struct node *node, struct clock *clock, int64_t offset, uint64_t ts, int64_t delay) { enum servo_state state; double ppb; if (clock_handle_leap(node, clock, offset, ts)) return; offset += get_sync_offset(node, clock); if (clock->sanity_check && clockcheck_sample(clock->sanity_check, ts)) servo_reset(clock->servo); ppb = servo_sample(clock->servo, offset, ts, 1.0, &state); clock->servo_state = state; switch (state) { case SERVO_UNLOCKED: break; case SERVO_JUMP: clockadj_step(clock->clkid, -offset); if (clock->sanity_check) clockcheck_step(clock->sanity_check, -offset); /* Fall through. */ case SERVO_LOCKED: clockadj_set_freq(clock->clkid, -ppb); if (clock->clkid == CLOCK_REALTIME) sysclk_set_sync(); if (clock->sanity_check) clockcheck_set_freq(clock->sanity_check, -ppb); break; } if (clock->offset_stats) { update_clock_stats(clock, node->stats_max_count, offset, ppb, delay); } else { if (delay >= 0) { pr_info("%s offset %9" PRId64 " s%d freq %+7.0f " "delay %6" PRId64, node->master->source_label, offset, state, ppb, delay); } else { pr_info("%s offset %9" PRId64 " s%d freq %+7.0f", node->master->source_label, offset, state, ppb); } } } static void enable_pps_output(clockid_t src) { int enable = 1; if (!phc_has_pps(src)) return; if (ioctl(CLOCKID_TO_FD(src), PTP_ENABLE_PPS, enable) < 0) pr_warning("failed to enable PPS output"); } static int read_pps(int fd, int64_t *offset, uint64_t *ts) { struct pps_fdata pfd; pfd.timeout.sec = 10; pfd.timeout.nsec = 0; pfd.timeout.flags = ~PPS_TIME_INVALID; if (ioctl(fd, PPS_FETCH, &pfd)) { pr_err("failed to fetch PPS: %m"); return 0; } *ts = pfd.info.assert_tu.sec * NS_PER_SEC; *ts += pfd.info.assert_tu.nsec; *offset = *ts % NS_PER_SEC; if (*offset > NS_PER_SEC / 2) *offset -= NS_PER_SEC; return 1; } static int do_pps_loop(struct node *node, struct clock *clock, int fd) { int64_t pps_offset, phc_offset, phc_delay; uint64_t pps_ts, phc_ts; clockid_t src = node->master->clkid; node->master->source_label = "pps"; if (src == CLOCK_INVALID) { /* The sync offset can't be applied with PPS alone. */ node->sync_offset = 0; } else { enable_pps_output(node->master->clkid); } while (is_running()) { if (!read_pps(fd, &pps_offset, &pps_ts)) { continue; } /* If a PHC is available, use it to get the whole number of seconds in the offset and PPS for the rest. */ if (src != CLOCK_INVALID) { if (!read_phc(src, clock->clkid, node->phc_readings, &phc_offset, &phc_ts, &phc_delay)) return -1; /* Convert the time stamp to the PHC time. */ phc_ts -= phc_offset; /* Check if it is close to the start of the second. */ if (phc_ts % NS_PER_SEC > PHC_PPS_OFFSET_LIMIT) { pr_warning("PPS is not in sync with PHC" " (0.%09lld)", phc_ts % NS_PER_SEC); continue; } phc_ts = phc_ts / NS_PER_SEC * NS_PER_SEC; pps_offset = pps_ts - phc_ts; } if (update_pmc(node, 0) < 0) continue; update_clock(node, clock, pps_offset, pps_ts, -1); } close(fd); return 0; } static int update_needed(struct clock *c) { switch (c->state) { case PS_FAULTY: case PS_DISABLED: case PS_LISTENING: case PS_PRE_MASTER: case PS_MASTER: case PS_PASSIVE: return 1; case PS_UNCALIBRATED: case PS_SLAVE: break; } return 0; } static int do_loop(struct node *node, int subscriptions) { struct timespec interval; struct clock *clock; uint64_t ts; int64_t offset, delay; interval.tv_sec = node->phc_interval; interval.tv_nsec = (node->phc_interval - interval.tv_sec) * 1e9; while (is_running()) { clock_nanosleep(CLOCK_MONOTONIC, 0, &interval, NULL); if (update_pmc(node, subscriptions) < 0) continue; if (subscriptions) { run_pmc_events(node); if (node->state_changed) { /* force getting offset, as it may have * changed after the port state change */ if (run_pmc_get_utc_offset(node, 1000) <= 0) { pr_err("failed to get UTC offset"); continue; } reconfigure(node); } } if (!node->master) continue; LIST_FOREACH(clock, &node->clocks, list) { if (!update_needed(clock)) continue; if (clock->clkid == CLOCK_REALTIME && node->master->sysoff_supported) { /* use sysoff */ if (sysoff_measure(CLOCKID_TO_FD(node->master->clkid), node->phc_readings, &offset, &ts, &delay)) return -1; } else { /* use phc */ if (!read_phc(node->master->clkid, clock->clkid, node->phc_readings, &offset, &ts, &delay)) continue; } update_clock(node, clock, offset, ts, delay); } } return 0; } static int check_clock_identity(struct node *node, struct ptp_message *msg) { if (!node->clock_identity_set) return 1; return !memcmp(&node->clock_identity, &msg->header.sourcePortIdentity.clockIdentity, sizeof(struct ClockIdentity)); } static int is_msg_mgt(struct ptp_message *msg) { struct TLV *tlv; if (msg_type(msg) != MANAGEMENT) return 0; if (management_action(msg) != RESPONSE) return 0; if (msg->tlv_count != 1) return 0; tlv = (struct TLV *) msg->management.suffix; if (tlv->type == TLV_MANAGEMENT) return 1; if (tlv->type == TLV_MANAGEMENT_ERROR_STATUS) return -1; return 0; } static int get_mgt_id(struct ptp_message *msg) { struct management_tlv *mgt = (struct management_tlv *) msg->management.suffix; return mgt->id; } static void *get_mgt_data(struct ptp_message *msg) { struct management_tlv *mgt = (struct management_tlv *) msg->management.suffix; return mgt->data; } static int get_mgt_err_id(struct ptp_message *msg) { struct management_error_status *mgt; mgt = (struct management_error_status *)msg->management.suffix; return mgt->id; } static int normalize_state(int state) { if (state != PS_MASTER && state != PS_SLAVE && state != PS_PRE_MASTER && state != PS_UNCALIBRATED) { /* treat any other state as "not a master nor a slave" */ state = PS_DISABLED; } return state; } static int clock_compute_state(struct node *node, struct clock *clock) { struct port *p; int state = PS_DISABLED; LIST_FOREACH(p, &node->ports, list) { if (p->clock != clock) continue; /* PS_SLAVE takes the highest precedence, PS_UNCALIBRATED * after that, PS_MASTER is third, PS_PRE_MASTER fourth and * all of that overrides PS_DISABLED, which corresponds * nicely with the numerical values */ if (p->state > state) state = p->state; } return state; } static int recv_subscribed(struct node *node, struct ptp_message *msg, int excluded) { int mgt_id, state; struct portDS *pds; struct port *port; struct clock *clock; mgt_id = get_mgt_id(msg); if (mgt_id == excluded) return 0; switch (mgt_id) { case TLV_PORT_DATA_SET: pds = get_mgt_data(msg); port = port_get(node, pds->portIdentity.portNumber); if (!port) { pr_info("received data for unknown port %s", pid2str(&pds->portIdentity)); return 1; } state = normalize_state(pds->portState); if (port->state != state) { pr_info("port %s changed state", pid2str(&pds->portIdentity)); port->state = state; clock = port->clock; state = clock_compute_state(node, clock); if (clock->state != state) { clock->new_state = state; node->state_changed = 1; } } return 1; } return 0; } static void send_subscription(struct node *node) { struct subscribe_events_np sen; memset(&sen, 0, sizeof(sen)); sen.duration = PMC_SUBSCRIBE_DURATION; sen.bitmask[0] = 1 << NOTIFY_PORT_STATE; pmc_send_set_action(node->pmc, TLV_SUBSCRIBE_EVENTS_NP, &sen, sizeof(sen)); } static int init_pmc(struct config *cfg, struct node *node, int domain_number) { char uds_local[MAX_IFNAME_SIZE + 1]; snprintf(uds_local, sizeof(uds_local), "/var/run/phc2sys.%d", getpid()); node->pmc = pmc_create(cfg, TRANS_UDS, uds_local, 0, domain_number, 0, 1); if (!node->pmc) { pr_err("failed to create pmc"); return -1; } return 0; } /* Return values: * 1: success * 0: timeout * -1: error reported by the other side * -2: local error, fatal */ static int run_pmc(struct node *node, int timeout, int ds_id, struct ptp_message **msg) { #define N_FD 1 struct pollfd pollfd[N_FD]; int cnt, res; while (1) { pollfd[0].fd = pmc_get_transport_fd(node->pmc); pollfd[0].events = POLLIN|POLLPRI; if (!node->pmc_ds_requested && ds_id >= 0) pollfd[0].events |= POLLOUT; cnt = poll(pollfd, N_FD, timeout); if (cnt < 0) { pr_err("poll failed"); return -2; } if (!cnt) { /* Request the data set again in the next run. */ node->pmc_ds_requested = 0; return 0; } /* Send a new request if there are no pending messages. */ if ((pollfd[0].revents & POLLOUT) && !(pollfd[0].revents & (POLLIN|POLLPRI))) { switch (ds_id) { case TLV_SUBSCRIBE_EVENTS_NP: send_subscription(node); break; default: pmc_send_get_action(node->pmc, ds_id); break; } node->pmc_ds_requested = 1; } if (!(pollfd[0].revents & (POLLIN|POLLPRI))) continue; *msg = pmc_recv(node->pmc); if (!*msg) continue; if (!check_clock_identity(node, *msg)) { msg_put(*msg); *msg = NULL; continue; } res = is_msg_mgt(*msg); if (res < 0 && get_mgt_err_id(*msg) == ds_id) { node->pmc_ds_requested = 0; return -1; } if (res <= 0 || recv_subscribed(node, *msg, ds_id) || get_mgt_id(*msg) != ds_id) { msg_put(*msg); *msg = NULL; continue; } node->pmc_ds_requested = 0; return 1; } } static int run_pmc_wait_sync(struct node *node, int timeout) { struct ptp_message *msg; int res; void *data; Enumeration8 portState; while (1) { res = run_pmc(node, timeout, TLV_PORT_DATA_SET, &msg); if (res <= 0) return res; data = get_mgt_data(msg); portState = ((struct portDS *)data)->portState; msg_put(msg); switch (portState) { case PS_MASTER: case PS_SLAVE: return 1; } /* try to get more data sets (for other ports) */ node->pmc_ds_requested = 1; } } static int run_pmc_get_utc_offset(struct node *node, int timeout) { struct ptp_message *msg; int res; struct timePropertiesDS *tds; res = run_pmc(node, timeout, TLV_TIME_PROPERTIES_DATA_SET, &msg); if (res <= 0) return res; tds = (struct timePropertiesDS *)get_mgt_data(msg); if (tds->flags & PTP_TIMESCALE) { node->sync_offset = tds->currentUtcOffset; if (tds->flags & LEAP_61) node->leap = 1; else if (tds->flags & LEAP_59) node->leap = -1; else node->leap = 0; node->utc_offset_traceable = tds->flags & UTC_OFF_VALID && tds->flags & TIME_TRACEABLE; } else { node->sync_offset = 0; node->leap = 0; node->utc_offset_traceable = 0; } msg_put(msg); return 1; } static int run_pmc_get_number_ports(struct node *node, int timeout) { struct ptp_message *msg; int res; struct defaultDS *dds; res = run_pmc(node, timeout, TLV_DEFAULT_DATA_SET, &msg); if (res <= 0) return res; dds = (struct defaultDS *)get_mgt_data(msg); res = dds->numberPorts; msg_put(msg); return res; } static int run_pmc_subscribe(struct node *node, int timeout) { struct ptp_message *msg; int res; res = run_pmc(node, timeout, TLV_SUBSCRIBE_EVENTS_NP, &msg); if (res <= 0) return res; msg_put(msg); return 1; } static void run_pmc_events(struct node *node) { struct ptp_message *msg; run_pmc(node, 0, -1, &msg); } static int run_pmc_port_properties(struct node *node, int timeout, unsigned int port, int *state, int *tstamping, char *iface) { struct ptp_message *msg; int res, len; struct port_properties_np *ppn; pmc_target_port(node->pmc, port); while (1) { res = run_pmc(node, timeout, TLV_PORT_PROPERTIES_NP, &msg); if (res <= 0) goto out; ppn = get_mgt_data(msg); if (ppn->portIdentity.portNumber != port) { msg_put(msg); continue; } *state = ppn->port_state; *tstamping = ppn->timestamping; len = ppn->interface.length; if (len > IFNAMSIZ - 1) len = IFNAMSIZ - 1; memcpy(iface, ppn->interface.text, len); iface[len] = '\0'; msg_put(msg); res = 1; break; } out: pmc_target_all(node->pmc); return res; } static int run_pmc_clock_identity(struct node *node, int timeout) { struct ptp_message *msg; struct defaultDS *dds; int res; res = run_pmc(node, timeout, TLV_DEFAULT_DATA_SET, &msg); if (res <= 0) return res; dds = (struct defaultDS *)get_mgt_data(msg); memcpy(&node->clock_identity, &dds->clockIdentity, sizeof(struct ClockIdentity)); node->clock_identity_set = 1; msg_put(msg); return 1; } static void close_pmc(struct node *node) { pmc_destroy(node->pmc); node->pmc = NULL; } static int auto_init_ports(struct node *node, int add_rt) { struct port *port; struct clock *clock; int number_ports, res; unsigned int i; int state, timestamping; char iface[IFNAMSIZ]; while (1) { res = run_pmc_clock_identity(node, 1000); if (res < 0) return -1; if (res > 0) break; /* res == 0, timeout */ pr_notice("Waiting for ptp4l..."); } number_ports = run_pmc_get_number_ports(node, 1000); if (number_ports <= 0) { pr_err("failed to get number of ports"); return -1; } res = run_pmc_subscribe(node, 1000); if (res <= 0) { pr_err("failed to subscribe"); return -1; } for (i = 1; i <= number_ports; i++) { res = run_pmc_port_properties(node, 1000, i, &state, ×tamping, iface); if (res == -1) { /* port does not exist, ignore the port */ continue; } if (res <= 0) { pr_err("failed to get port properties"); return -1; } if (timestamping == TS_SOFTWARE) { /* ignore ports with software time stamping */ continue; } port = port_add(node, i, iface); if (!port) return -1; port->state = normalize_state(state); } if (LIST_EMPTY(&node->clocks)) { pr_err("no suitable ports available"); return -1; } LIST_FOREACH(clock, &node->clocks, list) { clock->new_state = clock_compute_state(node, clock); } node->state_changed = 1; if (add_rt) { clock = clock_add(node, "CLOCK_REALTIME"); if (!clock) return -1; if (add_rt == 1) clock->dest_only = 1; } /* get initial offset */ if (run_pmc_get_utc_offset(node, 1000) <= 0) { pr_err("failed to get UTC offset"); return -1; } return 0; } /* Returns: -1 in case of error, 0 otherwise */ static int update_pmc(struct node *node, int subscribe) { struct timespec tp; uint64_t ts; if (clock_gettime(CLOCK_MONOTONIC, &tp)) { pr_err("failed to read clock: %m"); return -1; } ts = tp.tv_sec * NS_PER_SEC + tp.tv_nsec; if (node->pmc && !(ts > node->pmc_last_update && ts - node->pmc_last_update < PMC_UPDATE_INTERVAL)) { if (subscribe) run_pmc_subscribe(node, 0); if (run_pmc_get_utc_offset(node, 0) > 0) node->pmc_last_update = ts; } return 0; } /* Returns: non-zero to skip clock update */ static int clock_handle_leap(struct node *node, struct clock *clock, int64_t offset, uint64_t ts) { int clock_leap, node_leap = node->leap; clock->sync_offset = node->sync_offset; if ((node_leap || clock->leap_set) && clock->is_utc != node->master->is_utc) { /* If the master clock is in UTC, get a time stamp from it, as it is the clock which will include the leap second. */ if (node->master->is_utc) { struct timespec tp; if (clock_gettime(node->master->clkid, &tp)) { pr_err("failed to read clock: %m"); return -1; } ts = tp.tv_sec * NS_PER_SEC + tp.tv_nsec; } /* If the clock will be stepped, the time stamp has to be the new time. Ignore possible 1 second error in UTC offset. */ if (clock->is_utc && clock->servo_state == SERVO_UNLOCKED) ts -= offset + get_sync_offset(node, clock); /* Suspend clock updates in the last second before midnight. */ if (is_utc_ambiguous(ts)) { pr_info("clock update suspended due to leap second"); return 1; } clock_leap = leap_second_status(ts, clock->leap_set, &node_leap, &clock->sync_offset); if (clock->leap_set != clock_leap) { /* Only the system clock can leap. */ if (clock->clkid == CLOCK_REALTIME && node->kernel_leap) sysclk_set_leap(clock_leap); else servo_leap(clock->servo, clock_leap); clock->leap_set = clock_leap; } } if (node->utc_offset_traceable && clock->utc_offset_set != clock->sync_offset) { if (clock->clkid == CLOCK_REALTIME) sysclk_set_tai_offset(clock->sync_offset); clock->utc_offset_set = clock->sync_offset; } return 0; } static void usage(char *progname) { fprintf(stderr, "\n" "usage: %s [options]\n\n" "\n" " automatic configuration:\n" " -a turn on autoconfiguration\n" " -r synchronize system (realtime) clock\n" " repeat -r to consider it also as a time source\n" " manual configuration:\n" " -c [dev|name] slave clock (CLOCK_REALTIME)\n" " -d [dev] master PPS device\n" " -s [dev|name] master clock\n" " -O [offset] slave-master time offset (0)\n" " -w wait for ptp4l\n" " common options:\n" " -E [pi|linreg] clock servo (pi)\n" " -P [kp] proportional constant (0.7)\n" " -I [ki] integration constant (0.3)\n" " -S [step] step threshold (disabled)\n" " -F [step] step threshold only on start (0.00002)\n" " -R [rate] slave clock update rate in HZ (1.0)\n" " -N [num] number of master clock readings per update (5)\n" " -L [limit] sanity frequency limit in ppb (200000000)\n" " -M [num] NTP SHM segment number (0)\n" " -u [num] number of clock updates in summary stats (0)\n" " -n [num] domain number (0)\n" " -x apply leap seconds by servo instead of kernel\n" " -z [path] server address for UDS (/var/run/ptp4l)\n" " -l [num] set the logging level to 'num' (6)\n" " -m print messages to stdout\n" " -q do not print messages to the syslog\n" " -v prints the software version and exits\n" " -h prints this message and exits\n" "\n", progname); } int main(int argc, char *argv[]) { char *progname; char *src_name = NULL, *dst_name = NULL; struct clock *src, *dst; struct config *cfg; int autocfg = 0, rt = 0; int c, domain_number = 0, pps_fd = -1; int r = -1, wait_sync = 0; int print_level = LOG_INFO, use_syslog = 1, verbose = 0; int ntpshm_segment; double phc_rate, tmp; struct node node = { .sanity_freq_limit = 200000000, .servo_type = CLOCK_SERVO_PI, .phc_readings = 5, .phc_interval = 1.0, .kernel_leap = 1, }; handle_term_signals(); cfg = phc2sys_config = config_create(); if (!cfg) { return -1; } config_set_double(cfg, "pi_proportional_const", KP); config_set_double(cfg, "pi_integral_const", KI); /* Process the command line arguments. */ progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; while (EOF != (c = getopt(argc, argv, "arc:d:s:E:P:I:S:F:R:N:O:L:M:i:u:wn:xz:l:mqvh"))) { switch (c) { case 'a': autocfg = 1; break; case 'r': rt++; break; case 'c': dst_name = strdup(optarg); break; case 'd': pps_fd = open(optarg, O_RDONLY); if (pps_fd < 0) { fprintf(stderr, "cannot open '%s': %m\n", optarg); goto end; } break; case 'i': fprintf(stderr, "'-i' has been deprecated. please use '-s' instead.\n"); case 's': src_name = strdup(optarg); break; case 'E': if (!strcasecmp(optarg, "pi")) { node.servo_type = CLOCK_SERVO_PI; } else if (!strcasecmp(optarg, "linreg")) { node.servo_type = CLOCK_SERVO_LINREG; } else if (!strcasecmp(optarg, "ntpshm")) { node.servo_type = CLOCK_SERVO_NTPSHM; } else { fprintf(stderr, "invalid servo name %s\n", optarg); goto end; } break; case 'P': if (get_arg_val_d(c, optarg, &tmp, 0.0, DBL_MAX) || config_set_double(cfg, "pi_proportional_const", tmp)) goto end; break; case 'I': if (get_arg_val_d(c, optarg, &tmp, 0.0, DBL_MAX) || config_set_double(cfg, "pi_integral_const", tmp)) goto end; break; case 'S': if (get_arg_val_d(c, optarg, &tmp, 0.0, DBL_MAX) || config_set_double(cfg, "step_threshold", tmp)) goto end; break; case 'F': if (get_arg_val_d(c, optarg, &tmp, 0.0, DBL_MAX) || config_set_double(cfg, "first_step_threshold", tmp)) goto end; break; case 'R': if (get_arg_val_d(c, optarg, &phc_rate, 1e-9, DBL_MAX)) goto end; node.phc_interval = 1.0 / phc_rate; break; case 'N': if (get_arg_val_i(c, optarg, &node.phc_readings, 1, INT_MAX)) goto end; break; case 'O': if (get_arg_val_i(c, optarg, &node.sync_offset, INT_MIN, INT_MAX)) goto end; node.forced_sync_offset = -1; break; case 'L': if (get_arg_val_i(c, optarg, &node.sanity_freq_limit, 0, INT_MAX)) goto end; break; case 'M': if (get_arg_val_i(c, optarg, &ntpshm_segment, INT_MIN, INT_MAX) || config_set_int(cfg, "ntpshm_segment", ntpshm_segment)) goto end; break; case 'u': if (get_arg_val_ui(c, optarg, &node.stats_max_count, 0, UINT_MAX)) goto end; break; case 'w': wait_sync = 1; break; case 'n': if (get_arg_val_i(c, optarg, &domain_number, 0, 255)) goto end; break; case 'x': node.kernel_leap = 0; break; case 'z': if (strlen(optarg) > MAX_IFNAME_SIZE) { fprintf(stderr, "path %s too long, max is %d\n", optarg, MAX_IFNAME_SIZE); goto end; } if (config_set_string(cfg, "uds_address", optarg)) { goto end; } break; case 'l': if (get_arg_val_i(c, optarg, &print_level, PRINT_LEVEL_MIN, PRINT_LEVEL_MAX)) goto end; break; case 'm': verbose = 1; break; case 'q': use_syslog = 0; break; case 'v': version_show(stdout); config_destroy(cfg); return 0; case 'h': usage(progname); config_destroy(cfg); return 0; default: goto bad_usage; } } if (autocfg && (src_name || dst_name || pps_fd >= 0 || wait_sync || node.forced_sync_offset)) { fprintf(stderr, "autoconfiguration cannot be mixed with manual config options.\n"); goto bad_usage; } if (!autocfg && pps_fd < 0 && !src_name) { fprintf(stderr, "autoconfiguration or valid source clock must be selected.\n"); goto bad_usage; } if (!autocfg && !wait_sync && !node.forced_sync_offset) { fprintf(stderr, "time offset must be specified using -w or -O\n"); goto bad_usage; } if (node.servo_type == CLOCK_SERVO_NTPSHM) { node.kernel_leap = 0; node.sanity_freq_limit = 0; } print_set_progname(progname); print_set_verbose(verbose); print_set_syslog(use_syslog); print_set_level(print_level); if (autocfg) { if (init_pmc(cfg, &node, domain_number)) goto end; if (auto_init_ports(&node, rt) < 0) goto end; r = do_loop(&node, 1); goto end; } src = clock_add(&node, src_name); free(src_name); if (!src) { fprintf(stderr, "valid source clock must be selected.\n"); goto bad_usage; } src->state = PS_SLAVE; node.master = src; dst = clock_add(&node, dst_name ? dst_name : "CLOCK_REALTIME"); free(dst_name); if (!dst) { fprintf(stderr, "valid destination clock must be selected.\n"); goto bad_usage; } dst->state = PS_MASTER; if (pps_fd >= 0 && dst->clkid != CLOCK_REALTIME) { fprintf(stderr, "cannot use a pps device unless destination is CLOCK_REALTIME\n"); goto bad_usage; } r = -1; if (wait_sync) { if (init_pmc(cfg, &node, domain_number)) goto end; while (is_running()) { r = run_pmc_wait_sync(&node, 1000); if (r < 0) goto end; if (r > 0) break; else pr_notice("Waiting for ptp4l..."); } if (!node.forced_sync_offset) { r = run_pmc_get_utc_offset(&node, 1000); if (r <= 0) { pr_err("failed to get UTC offset"); goto end; } } if (node.forced_sync_offset || (src->clkid != CLOCK_REALTIME && dst->clkid != CLOCK_REALTIME) || src->clkid == CLOCK_INVALID) close_pmc(&node); } if (pps_fd >= 0) { /* only one destination clock allowed with PPS until we * implement a mean to specify PTP port to PPS mapping */ servo_sync_interval(dst->servo, 1.0); r = do_pps_loop(&node, dst, pps_fd); } else { r = do_loop(&node, 0); } end: if (node.pmc) close_pmc(&node); config_destroy(cfg); return r; bad_usage: usage(progname); config_destroy(cfg); return -1; } linuxptp-1.6/phc_ctl.8000066400000000000000000000060141257727010700147610ustar00rootroot00000000000000.TH PHC_CTL 8 "June 2014" "linuxptp" .SH NAME phc_ctl \- directly control PHC device clock .SH SYNOPSIS .B phc_ctl [ options ] [ commands ] .SH DESCRIPTION .B phc_ctl is a program which can be used to directly control a PHC clock device. Typically, it is used for debugging purposes, and has little use for general control of the device. For general control of PHC clock devices, .B phc2sys (8) should be preferred. may be either CLOCK_REALTIME, any /dev/ptpX device, or any ethernet device which supports ethtool's get_ts_info ioctl. .SH OPTIONS .TP .BI \-l " print-level" Set the maximum syslog level of messages which should be printed or sent to the system logger. The default is 6 (LOG_INFO). .TP .BI \-q Do not send messages to syslog. By default messages will be sent. .TP .BI \-Q Do not print messages to standard output. By default messages will be printed. .TP .BI \-h Display a help message. .TP .B \-v Prints the software version and exits. .SH COMMANDS .B phc_ctl is controlled by passing commands which take either an optional or required parameter. The commands (outlined below) will control aspects of the PHC clock device. These commands may be useful for inspecting or debugging the PHC driver, but may have adverse side effects on running instances of .B ptp4l (8) or .B phc2sys (8) .TP .BI set " seconds" Set the PHC clock time to the value specified in seconds. Defaults to reading CLOCK_REALTIME if no value is provided. .TP .BI get Get the current time of the PHC clock device. .TP .BI adj " seconds" Adjust the PHC clock by an amount of seconds provided. This argument is required. .TP .BI freq " ppb" Adjust the frequency of the PHC clock by the specified parts per billion. If no argument is provided, it will attempt to read the current frequency and report it. .TP .BI cmp Compare the PHC clock device to CLOCK_REALTIME, using the best method available. .TP .BI caps Display the device capabiltiies. This is the default command if no commands are provided. .TP .BI wait " seconds" Sleep the process for the specified period of time, waking up and resuming afterwards. This command may be useful for sanity checking whether the PHC clock is running as expected. The arguments specified in seconds are read as double precision floating point values, and will scale to nanoseconds. This means providing a value of 5.5 means 5 and one half seconds. This allows specifying fairly precise values for time. .SH EXAMPLES Read the current clock time from the device .RS \f(CWphc_ctl /dev/ptp0 get\fP .RE Set the PHC clock time to CLOCK_REALTIME .RS \f(CWphc_ctl /dev/ptp0 set\fP .RE Set PHC clock time to 0 (seconds since Epoch) .RS \f(CWphc_ctl /dev/ptp0 set 0.0\fP .RE Quickly sanity check frequency slewing by setting slewing frequency by positive 10%, resetting clock to 0.0 time, waiting for 10 seconds, and then reading time. The time read back should be (roughly) 11 seconds, since the clock was slewed 10% faster. .RS \f(CWphc_ctl /dev/ptp0 freq 100000000 set 0.0 wait 10.0 get .RE .SH SEE ALSO .BR ptp4l (8) .BR phc2sys (8) linuxptp-1.6/phc_ctl.c000066400000000000000000000326041257727010700150400ustar00rootroot00000000000000/* * @file phc_ctl.c * @brief Utility program to directly control and debug a PHC device. * @note Copyright (C) 2014 Jacob Keller * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clockadj.h" #include "missing.h" #include "phc.h" #include "print.h" #include "sk.h" #include "sysoff.h" #include "util.h" #include "version.h" #define NSEC2SEC 1000000000.0 /* trap the alarm signal so that pause() will wake up on receipt */ static void handle_alarm(int s) { return; } static void double_to_timespec(double d, struct timespec *ts) { double fraction, whole; fraction = modf(d, &whole); /* cast the whole value to a time_t to store as seconds */ ts->tv_sec = (time_t)whole; /* tv_nsec is a long, so we multiply the nanoseconds per second double * value by our fractional component. This results in a correct * timespec from the double representing seconds. */ ts->tv_nsec = (long)(NSEC2SEC * fraction); } static int install_handler(int signum, void(*handler)(int)) { struct sigaction action; sigset_t mask; /* Unblock the signal */ sigemptyset(&mask); sigaddset(&mask, signum); sigprocmask(SIG_UNBLOCK, &mask, NULL); /* Install the signal handler */ action.sa_handler = handler; action.sa_flags = 0; sigemptyset(&action.sa_mask); sigaction(signum, &action, NULL); return 0; } static int64_t calculate_offset(struct timespec *ts1, struct timespec *rt, struct timespec *ts2) { int64_t interval; int64_t offset; #define NSEC_PER_SEC 1000000000ULL /* calculate interval between clock realtime */ interval = (ts2->tv_sec - ts1->tv_sec) * NSEC_PER_SEC; interval += ts2->tv_nsec - ts1->tv_nsec; /* assume PHC read occured half way between CLOCK_REALTIME reads */ offset = (rt->tv_sec - ts1->tv_sec) * NSEC_PER_SEC; offset += (rt->tv_nsec - ts1->tv_nsec) - (interval / 2); return offset; } static clockid_t clock_open(char *device) { struct sk_ts_info ts_info; char phc_device[16]; int clkid; /* check if device is CLOCK_REALTIME */ if (!strcasecmp(device, "CLOCK_REALTIME")) return CLOCK_REALTIME; /* check if device is valid phc device */ clkid = phc_open(device); if (clkid != CLOCK_INVALID) return clkid; /* check if device is a valid ethernet device */ if (sk_get_ts_info(device, &ts_info) || !ts_info.valid) { pr_err("unknown clock %s: %m", device); return CLOCK_INVALID; } if (ts_info.phc_index < 0) { pr_err("interface %s does not have a PHC", device); return CLOCK_INVALID; } sprintf(phc_device, "/dev/ptp%d", ts_info.phc_index); clkid = phc_open(phc_device); if (clkid == CLOCK_INVALID) pr_err("cannot open %s for %s: %m", phc_device, device); return clkid; } static void usage(const char *progname) { fprintf(stderr, "\n" "usage: %s [options] -- [command]\n\n" " device ethernet or ptp clock device" "\n" " options\n" " -l [num] set the logging level to 'num'\n" " -q do not print messages to the syslog\n" " -Q do not print messages to stdout\n" " -v prints the software version and exits\n" " -h prints this message and exits\n" "\n" " commands\n" " specify commands with arguments. Can specify multiple\n" " commands to be executed in order. Seconds are read as\n" " double precision floating point values.\n" " set [seconds] set PHC time (defaults to time on CLOCK_REALTIME)\n" " get get PHC time\n" " adj adjust PHC time by offset\n" " freq [ppb] adjust PHC frequency (default returns current offset)\n" " cmp compare PHC offset to CLOCK_REALTIME\n" " caps display device capabilities (default if no command given)\n" " wait pause between commands\n" "\n", progname); } typedef int (*cmd_func_t)(clockid_t, int, char *[]); struct cmd_t { const char *name; const cmd_func_t function; }; static cmd_func_t get_command_function(const char *name); static inline int name_is_a_command(const char *name); static int do_set(clockid_t clkid, int cmdc, char *cmdv[]) { struct timespec ts; double time_arg = 0; int args_to_eat = 0; enum parser_result r; memset(&ts, 0, sizeof(ts)); /* if we have no more arguments, or the next argument is the ";" * separator, then we run set as default parameter mode */ if (cmdc < 1 || name_is_a_command(cmdv[0])) { clock_gettime(CLOCK_REALTIME, &ts); /* since we aren't using the options, we can simply ensure * that we don't eat any arguments */ args_to_eat = 0; } else { /* parse the double */ r = get_ranged_double(cmdv[0], &time_arg, 0.0, DBL_MAX); switch (r) { case PARSED_OK: break; case MALFORMED: pr_err("set: '%s' is not a valid double", cmdv[0]); return -2; case OUT_OF_RANGE: pr_err("set: '%s' is out of range", cmdv[0]); return -2; default: pr_err("set: couldn't process '%s'", cmdv[0]); return -2; } double_to_timespec(time_arg, &ts); /* in order for processing to work, we need to ensure the * run_cmds loop eats the optional set argument */ args_to_eat = 1; } if (clock_settime(clkid, &ts)) { pr_err("set: failed to set clock time: %s", strerror(errno)); return -1; } else { pr_notice("set clock time to %ld.%09ld or %s", ts.tv_sec, ts.tv_nsec, ctime(&ts.tv_sec)); } return args_to_eat; } static int do_get(clockid_t clkid, int cmdc, char *cmdv[]) { struct timespec ts; memset(&ts, 0, sizeof(ts)); if (clock_gettime(clkid, &ts)) { pr_err("get: failed to get clock time: %s", strerror(errno)); return -1; } else { pr_notice("clock time is %ld.%09lu or %s", ts.tv_sec, ts.tv_nsec, ctime(&ts.tv_sec)); } /* get operation does not require any arguments */ return 0; } static int do_adj(clockid_t clkid, int cmdc, char *cmdv[]) { double time_arg; int64_t nsecs; enum parser_result r; if (cmdc < 1 || name_is_a_command(cmdv[0])) { pr_err("adj: missing required time argument"); return -2; } /* parse the double time offset argument */ r = get_ranged_double(cmdv[0], &time_arg, DBL_MIN, DBL_MAX); switch (r) { case PARSED_OK: break; case MALFORMED: pr_err("adj: '%s' is not a valid double", cmdv[0]); return -2; case OUT_OF_RANGE: pr_err("adj: '%s' is out of range.", cmdv[0]); return -2; default: pr_err("adj: couldn't process '%s'", cmdv[0]); return -2; } nsecs = (int64_t)(NSEC2SEC * time_arg); clockadj_init(clkid); clockadj_step(clkid, nsecs); pr_notice("adjusted clock by %lf seconds", time_arg); /* adjustment always consumes one argument */ return 1; } static int do_freq(clockid_t clkid, int cmdc, char *cmdv[]) { double ppb; enum parser_result r; clockadj_init(clkid); if (cmdc < 1 || name_is_a_command(cmdv[0])) { ppb = clockadj_get_freq(clkid); pr_err("clock frequency offset is %lfppb", ppb); /* no argument was used */ return 0; } /* parse the double ppb argument */ r = get_ranged_double(cmdv[0], &ppb, -NSEC2SEC, NSEC2SEC); switch (r) { case PARSED_OK: break; case MALFORMED: pr_err("freq: '%s' is not a valid double", cmdv[0]); return -2; case OUT_OF_RANGE: pr_err("freq: '%s' is out of range.", cmdv[0]); return -2; default: pr_err("freq: couldn't process '%s'", cmdv[0]); return -2; } clockadj_set_freq(clkid, ppb); pr_err("adjusted clock frequency offset to %lfppb", ppb); /* consumed one argument to determine the frequency adjustment value */ return 1; } static int do_caps(clockid_t clkid, int cmdc, char *cmdv[]) { struct ptp_clock_caps caps; if (clkid == CLOCK_REALTIME) { pr_warning("CLOCK_REALTIME is not a PHC device."); return 0; } if (ioctl(CLOCKID_TO_FD(clkid), PTP_CLOCK_GETCAPS, &caps)) { pr_err("get capabilities failed: %s", strerror(errno)); return -1; } pr_notice("\n" "capabilities:\n" " %d maximum frequency adjustment (ppb)\n" " %d programable alarms\n" " %d external time stamp channels\n" " %d programmable periodic signals\n" " %s pulse per second support", caps.max_adj, caps.n_alarm, caps.n_ext_ts, caps.n_per_out, caps.pps ? "has" : "doesn't have"); return 0; } static int do_cmp(clockid_t clkid, int cmdc, char *cmdv[]) { struct timespec ts, rta, rtb; int64_t sys_offset, delay = 0, offset; uint64_t sys_ts; if (SYSOFF_SUPPORTED == sysoff_measure(CLOCKID_TO_FD(clkid), 9, &sys_offset, &sys_ts, &delay)) { pr_notice( "offset from CLOCK_REALTIME is %"PRId64"ns\n", sys_offset); return 0; } memset(&ts, 0, sizeof(ts)); memset(&ts, 0, sizeof(rta)); memset(&ts, 0, sizeof(rtb)); if (clock_gettime(CLOCK_REALTIME, &rta) || clock_gettime(clkid, &ts) || clock_gettime(CLOCK_REALTIME, &rtb)) { pr_err("cmp: failed clock reads: %s\n", strerror(errno)); return -1; } offset = calculate_offset(&rta, &ts, &rtb); pr_notice( "offset from CLOCK_REALTIME is approximately %"PRId64"ns\n", offset); return 0; } static int do_wait(clockid_t clkid, int cmdc, char *cmdv[]) { double time_arg; struct timespec ts; struct itimerval timer; enum parser_result r; if (cmdc < 1 || name_is_a_command(cmdv[0])) { pr_err("wait: requires sleep duration argument\n"); return -2; } memset(&timer, 0, sizeof(timer)); /* parse the double time offset argument */ r = get_ranged_double(cmdv[0], &time_arg, 0.0, DBL_MAX); switch (r) { case PARSED_OK: break; case MALFORMED: pr_err("wait: '%s' is not a valid double", cmdv[0]); return -2; case OUT_OF_RANGE: pr_err("wait: '%s' is out of range.", cmdv[0]); return -2; default: pr_err("wait: couldn't process '%s'", cmdv[0]); return -2; } double_to_timespec(time_arg, &ts); timer.it_value.tv_sec = ts.tv_sec; timer.it_value.tv_usec = ts.tv_nsec / 1000; setitimer(ITIMER_REAL, &timer, NULL); pause(); /* the SIGALRM is already trapped during initialization, so we will * wake up here once the alarm is handled. */ pr_notice( "process slept for %lf seconds\n", time_arg); return 1; } static const struct cmd_t all_commands[] = { { "set", &do_set }, { "get", &do_get }, { "adj", &do_adj }, { "freq", &do_freq }, { "cmp", &do_cmp }, { "caps", &do_caps }, { "wait", &do_wait }, { 0, 0 } }; static cmd_func_t get_command_function(const char *name) { int i; cmd_func_t cmd = NULL; for (i = 0; all_commands[i].name != NULL; i++) { if (!strncmp(name, all_commands[i].name, strlen(all_commands[i].name))) cmd = all_commands[i].function; } return cmd; } static inline int name_is_a_command(const char *name) { return get_command_function(name) != NULL; } static int run_cmds(clockid_t clkid, int cmdc, char *cmdv[]) { int i = 0, result = 0; cmd_func_t action = NULL; while (i < cmdc) { char *arg = cmdv[i]; /* increment now to remove the command argument */ i++; action = get_command_function(arg); if (action) result = action(clkid, cmdc - i, &cmdv[i]); else pr_err("unknown command %s.", arg); /* result is how many arguments were used up by the command, * not including the ";". We will increment the loop counter * to avoid processing the arguments as commands. */ if (result < 0) return result; else i += result; } return 0; } int main(int argc, char *argv[]) { const char *progname; char **cmdv, *default_cmdv[] = { "caps" }; int c, result, cmdc; int print_level = LOG_INFO, verbose = 1, use_syslog = 1; clockid_t clkid; install_handler(SIGALRM, handle_alarm); /* Process the command line arguments. */ progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; while (EOF != (c = getopt(argc, argv, "l:qQvh"))) { switch (c) { case 'l': if (get_arg_val_i(c, optarg, &print_level, PRINT_LEVEL_MIN, PRINT_LEVEL_MAX)) return -1; break; case 'q': use_syslog = 0; break; case 'Q': verbose = 0; break; case 'v': version_show(stdout); return 0; case 'h': usage(progname); return 0; default: usage(progname); return -1; } } print_set_progname(progname); print_set_verbose(verbose); print_set_syslog(use_syslog); print_set_level(print_level); if ((argc - optind) < 1) { usage(progname); return -1; } if ((argc - optind) == 1) { cmdv = default_cmdv; cmdc = 1; } else { cmdv = &argv[optind+1]; cmdc = argc - optind - 1; } clkid = clock_open(argv[optind]); if (clkid == CLOCK_INVALID) return -1; /* pass the remaining arguments to the run_cmds loop */ result = run_cmds(clkid, cmdc, cmdv); if (result < -1) { /* show usage when command fails */ usage(progname); return result; } return 0; } linuxptp-1.6/pi.c000066400000000000000000000150321257727010700140300ustar00rootroot00000000000000/** * @file pi.c * @brief Implements a Proportional Integral clock servo. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "config.h" #include "pi.h" #include "print.h" #include "servo_private.h" #define HWTS_KP_SCALE 0.7 #define HWTS_KI_SCALE 0.3 #define SWTS_KP_SCALE 0.1 #define SWTS_KI_SCALE 0.001 #define MAX_KP_NORM_MAX 1.0 #define MAX_KI_NORM_MAX 2.0 #define FREQ_EST_MARGIN 0.001 struct pi_servo { struct servo servo; int64_t offset[2]; uint64_t local[2]; double drift; double kp; double ki; double last_freq; int count; /* configuration: */ double configured_pi_kp; double configured_pi_ki; double configured_pi_kp_scale; double configured_pi_kp_exponent; double configured_pi_kp_norm_max; double configured_pi_ki_scale; double configured_pi_ki_exponent; double configured_pi_ki_norm_max; }; static void pi_destroy(struct servo *servo) { struct pi_servo *s = container_of(servo, struct pi_servo, servo); free(s); } static double pi_sample(struct servo *servo, int64_t offset, uint64_t local_ts, double weight, enum servo_state *state) { struct pi_servo *s = container_of(servo, struct pi_servo, servo); double ki_term, ppb = s->last_freq; double freq_est_interval, localdiff; switch (s->count) { case 0: s->offset[0] = offset; s->local[0] = local_ts; *state = SERVO_UNLOCKED; s->count = 1; break; case 1: s->offset[1] = offset; s->local[1] = local_ts; /* Make sure the first sample is older than the second. */ if (s->local[0] >= s->local[1]) { *state = SERVO_UNLOCKED; s->count = 0; break; } /* Wait long enough before estimating the frequency offset. */ localdiff = (s->local[1] - s->local[0]) / 1e9; localdiff += localdiff * FREQ_EST_MARGIN; freq_est_interval = 0.016 / s->ki; if (freq_est_interval > 1000.0) { freq_est_interval = 1000.0; } if (localdiff < freq_est_interval) { *state = SERVO_UNLOCKED; break; } /* Adjust drift by the measured frequency offset. */ s->drift += (1e9 - s->drift) * (s->offset[1] - s->offset[0]) / (s->local[1] - s->local[0]); if (s->drift < -servo->max_frequency) s->drift = -servo->max_frequency; else if (s->drift > servo->max_frequency) s->drift = servo->max_frequency; if ((servo->first_update && servo->first_step_threshold && servo->first_step_threshold < fabs(offset)) || (servo->step_threshold && servo->step_threshold < fabs(offset))) *state = SERVO_JUMP; else *state = SERVO_LOCKED; ppb = s->drift; s->count = 2; break; case 2: /* * reset the clock servo when offset is greater than the max * offset value. Note that the clock jump will be performed in * step 1, so it is not necessary to have clock jump * immediately. This allows re-calculating drift as in initial * clock startup. */ if (servo->step_threshold && servo->step_threshold < fabs(offset)) { *state = SERVO_UNLOCKED; s->count = 0; break; } ki_term = s->ki * offset * weight; ppb = s->kp * offset * weight + s->drift + ki_term; if (ppb < -servo->max_frequency) { ppb = -servo->max_frequency; } else if (ppb > servo->max_frequency) { ppb = servo->max_frequency; } else { s->drift += ki_term; } *state = SERVO_LOCKED; break; } s->last_freq = ppb; return ppb; } static void pi_sync_interval(struct servo *servo, double interval) { struct pi_servo *s = container_of(servo, struct pi_servo, servo); s->kp = s->configured_pi_kp_scale * pow(interval, s->configured_pi_kp_exponent); if (s->kp > s->configured_pi_kp_norm_max / interval) s->kp = s->configured_pi_kp_norm_max / interval; s->ki = s->configured_pi_ki_scale * pow(interval, s->configured_pi_ki_exponent); if (s->ki > s->configured_pi_ki_norm_max / interval) s->ki = s->configured_pi_ki_norm_max / interval; pr_debug("PI servo: sync interval %.3f kp %.3f ki %.6f", interval, s->kp, s->ki); } static void pi_reset(struct servo *servo) { struct pi_servo *s = container_of(servo, struct pi_servo, servo); s->count = 0; } struct servo *pi_servo_create(struct config *cfg, int fadj, int sw_ts) { struct pi_servo *s; s = calloc(1, sizeof(*s)); if (!s) return NULL; s->servo.destroy = pi_destroy; s->servo.sample = pi_sample; s->servo.sync_interval = pi_sync_interval; s->servo.reset = pi_reset; s->drift = fadj; s->last_freq = fadj; s->kp = 0.0; s->ki = 0.0; s->configured_pi_kp = config_get_double(cfg, NULL, "pi_proportional_const"); s->configured_pi_ki = config_get_double(cfg, NULL, "pi_integral_const"); s->configured_pi_kp_scale = config_get_double(cfg, NULL, "pi_proportional_scale"); s->configured_pi_kp_exponent = config_get_double(cfg, NULL, "pi_proportional_exponent"); s->configured_pi_kp_norm_max = config_get_double(cfg, NULL, "pi_proportional_norm_max"); s->configured_pi_ki_scale = config_get_double(cfg, NULL, "pi_integral_scale"); s->configured_pi_ki_exponent = config_get_double(cfg, NULL, "pi_integral_exponent"); s->configured_pi_ki_norm_max = config_get_double(cfg, NULL, "pi_integral_norm_max"); if (s->configured_pi_kp && s->configured_pi_ki) { /* Use the constants as configured by the user without adjusting for sync interval unless they make the servo unstable. */ s->configured_pi_kp_scale = s->configured_pi_kp; s->configured_pi_ki_scale = s->configured_pi_ki; s->configured_pi_kp_exponent = 0.0; s->configured_pi_ki_exponent = 0.0; s->configured_pi_kp_norm_max = MAX_KP_NORM_MAX; s->configured_pi_ki_norm_max = MAX_KI_NORM_MAX; } else if (!s->configured_pi_kp_scale || !s->configured_pi_ki_scale) { if (sw_ts) { s->configured_pi_kp_scale = SWTS_KP_SCALE; s->configured_pi_ki_scale = SWTS_KI_SCALE; } else { s->configured_pi_kp_scale = HWTS_KP_SCALE; s->configured_pi_ki_scale = HWTS_KI_SCALE; } } return &s->servo; } linuxptp-1.6/pi.h000066400000000000000000000016661257727010700140450ustar00rootroot00000000000000/** * @file pi.h * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_PI_H #define HAVE_PI_H #include "servo.h" struct servo *pi_servo_create(struct config *cfg, int fadj, int sw_ts); #endif linuxptp-1.6/pmc.8000066400000000000000000000055431257727010700141320ustar00rootroot00000000000000.TH PMC 8 "October 2013" "linuxptp" .SH NAME pmc \- PTP management client .SH SYNOPSIS .B pmc [ .B \-2 | .B \-4 | .B \-6 | .B \-u ] [ .BI \-b " boundary-hops" ] [ .BI \-d " domain-number" ] [ .BI \-i " interface" ] [ .BI \-s " uds-address" ] [ .BI \-t " transport-specific-field" ] [ .B \-v ] [ .B \-z ] [ command ] ... .SH DESCRIPTION .B pmc is a program which implements a PTP management client according to IEEE standard 1588. The program reads from the standard input or from the command line actions specified by name and management ID, sends them over the selected transport and prints any received replies. There are three actions supported: .B GET retrieves the specified information, .B SET updates the specified information and .B CMD (or .BR COMMAND ) initiates the specified event. By default the management commands are addressed to all ports. The .B TARGET command can be used to select a particular clock and port for the subsequent messages. Command .B help can be used to get a list of supported actions and management IDs. .SH OPTIONS .TP .B \-2 Select the IEEE 802.3 network transport. .TP .B \-4 Select the UDP IPv4 network transport. This is the default transport. .TP .B \-6 Select the UDP IPv6 network transport. .TP .B \-u Select the Unix Domain Socket transport. .TP .BI \-b " boundary-hops" Specify the boundary hops value in sent messages. The default is 1. .TP .BI \-d " domain-number" Specify the domain number in sent messages. The default is 0. .TP .BI \-i " interface" Specify the network interface. The default is /var/run/pmc.$pid for the Unix Domain Socket transport and eth0 for the other transports. .TP .BI \-s " uds-address" Specifies the address of the server's UNIX domain socket. The default is /var/run/ptp4l. .TP .BI \-t " transport-specific-field" Specify the transport specific field in sent messages as a hexadecimal number. The default is 0x0. .TP .B \-h Display a help message. .TP .B \-v Prints the software version and exits. .TP .B \-z The official interpretation of the 1588 standard mandates sending GET actions with valid (but meaningless) TLV values. Therefore the pmc program normally sends GET requests with properly formed TLV values. This option enables the legacy option of sending zero length TLV values instead. .SH MANAGEMENT IDS .TP .B ANNOUNCE_RECEIPT_TIMEOUT .TP .B CLOCK_ACCURACY .TP .B CLOCK_DESCRIPTION .TP .B CURRENT_DATA_SET .TP .B DEFAULT_DATA_SET .TP .B DELAY_MECHANISM .TP .B DOMAIN .TP .B GRANDMASTER_SETTINGS_NP .TP .B LOG_ANNOUNCE_INTERVAL .TP .B LOG_MIN_PDELAY_REQ_INTERVAL .TP .B LOG_SYNC_INTERVAL .TP .B NULL_MANAGEMENT .TP .B PARENT_DATA_SET .TP .B PORT_DATA_SET .TP .B PORT_DATA_SET_NP .TP .B PRIORITY1 .TP .B PRIORITY2 .TP .B SLAVE_ONLY .TP .B TIMESCALE_PROPERTIES .TP .B TIME_PROPERTIES_DATA_SET .TP .B TIME_STATUS_NP .TP .B TRACEABILITY_PROPERTIES .TP .B USER_DESCRIPTION .TP .B VERSION_NUMBER .SH SEE ALSO .BR ptp4l (8) linuxptp-1.6/pmc.c000066400000000000000000000646561257727010700142170ustar00rootroot00000000000000/** * @file pmc.c * @brief PTP management client program * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include "ds.h" #include "fsm.h" #include "pmc_common.h" #include "print.h" #include "tlv.h" #include "uds.h" #include "util.h" #include "version.h" #define BAD_ACTION -1 #define BAD_ID -1 #define AMBIGUOUS_ID -2 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define P41 ((double)(1ULL << 41)) static struct pmc *pmc; static void do_get_action(int action, int index, char *str); static void do_set_action(int action, int index, char *str); static void not_supported(int action, int index, char *str); static void null_management(int action, int index, char *str); struct management_id { char name[64]; int code; void (*func)(int action, int index, char *str); }; struct management_id idtab[] = { /* Clock management ID values */ { "USER_DESCRIPTION", TLV_USER_DESCRIPTION, do_get_action }, { "SAVE_IN_NON_VOLATILE_STORAGE", TLV_SAVE_IN_NON_VOLATILE_STORAGE, not_supported }, { "RESET_NON_VOLATILE_STORAGE", TLV_RESET_NON_VOLATILE_STORAGE, not_supported }, { "INITIALIZE", TLV_INITIALIZE, not_supported }, { "FAULT_LOG", TLV_FAULT_LOG, not_supported }, { "FAULT_LOG_RESET", TLV_FAULT_LOG_RESET, not_supported }, { "DEFAULT_DATA_SET", TLV_DEFAULT_DATA_SET, do_get_action }, { "CURRENT_DATA_SET", TLV_CURRENT_DATA_SET, do_get_action }, { "PARENT_DATA_SET", TLV_PARENT_DATA_SET, do_get_action }, { "TIME_PROPERTIES_DATA_SET", TLV_TIME_PROPERTIES_DATA_SET, do_get_action }, { "PRIORITY1", TLV_PRIORITY1, do_set_action }, { "PRIORITY2", TLV_PRIORITY2, do_set_action }, { "DOMAIN", TLV_DOMAIN, do_get_action }, { "SLAVE_ONLY", TLV_SLAVE_ONLY, do_get_action }, { "TIME", TLV_TIME, not_supported }, { "CLOCK_ACCURACY", TLV_CLOCK_ACCURACY, do_get_action }, { "UTC_PROPERTIES", TLV_UTC_PROPERTIES, not_supported }, { "TRACEABILITY_PROPERTIES", TLV_TRACEABILITY_PROPERTIES, do_get_action }, { "TIMESCALE_PROPERTIES", TLV_TIMESCALE_PROPERTIES, do_get_action }, { "PATH_TRACE_LIST", TLV_PATH_TRACE_LIST, not_supported }, { "PATH_TRACE_ENABLE", TLV_PATH_TRACE_ENABLE, not_supported }, { "GRANDMASTER_CLUSTER_TABLE", TLV_GRANDMASTER_CLUSTER_TABLE, not_supported }, { "ACCEPTABLE_MASTER_TABLE", TLV_ACCEPTABLE_MASTER_TABLE, not_supported }, { "ACCEPTABLE_MASTER_MAX_TABLE_SIZE", TLV_ACCEPTABLE_MASTER_MAX_TABLE_SIZE, not_supported }, { "ALTERNATE_TIME_OFFSET_ENABLE", TLV_ALTERNATE_TIME_OFFSET_ENABLE, not_supported }, { "ALTERNATE_TIME_OFFSET_NAME", TLV_ALTERNATE_TIME_OFFSET_NAME, not_supported }, { "ALTERNATE_TIME_OFFSET_MAX_KEY", TLV_ALTERNATE_TIME_OFFSET_MAX_KEY, not_supported }, { "ALTERNATE_TIME_OFFSET_PROPERTIES", TLV_ALTERNATE_TIME_OFFSET_PROPERTIES, not_supported }, { "TRANSPARENT_CLOCK_DEFAULT_DATA_SET", TLV_TRANSPARENT_CLOCK_DEFAULT_DATA_SET, not_supported }, { "PRIMARY_DOMAIN", TLV_PRIMARY_DOMAIN, not_supported }, { "TIME_STATUS_NP", TLV_TIME_STATUS_NP, do_get_action }, { "GRANDMASTER_SETTINGS_NP", TLV_GRANDMASTER_SETTINGS_NP, do_set_action }, /* Port management ID values */ { "NULL_MANAGEMENT", TLV_NULL_MANAGEMENT, null_management }, { "CLOCK_DESCRIPTION", TLV_CLOCK_DESCRIPTION, do_get_action }, { "PORT_DATA_SET", TLV_PORT_DATA_SET, do_get_action }, { "LOG_ANNOUNCE_INTERVAL", TLV_LOG_ANNOUNCE_INTERVAL, do_get_action }, { "ANNOUNCE_RECEIPT_TIMEOUT", TLV_ANNOUNCE_RECEIPT_TIMEOUT, do_get_action }, { "LOG_SYNC_INTERVAL", TLV_LOG_SYNC_INTERVAL, do_get_action }, { "VERSION_NUMBER", TLV_VERSION_NUMBER, do_get_action }, { "ENABLE_PORT", TLV_ENABLE_PORT, not_supported }, { "DISABLE_PORT", TLV_DISABLE_PORT, not_supported }, { "UNICAST_NEGOTIATION_ENABLE", TLV_UNICAST_NEGOTIATION_ENABLE, not_supported }, { "UNICAST_MASTER_TABLE", TLV_UNICAST_MASTER_TABLE, not_supported }, { "UNICAST_MASTER_MAX_TABLE_SIZE", TLV_UNICAST_MASTER_MAX_TABLE_SIZE, not_supported }, { "ACCEPTABLE_MASTER_TABLE_ENABLED", TLV_ACCEPTABLE_MASTER_TABLE_ENABLED, not_supported }, { "ALTERNATE_MASTER", TLV_ALTERNATE_MASTER, not_supported }, { "TRANSPARENT_CLOCK_PORT_DATA_SET", TLV_TRANSPARENT_CLOCK_PORT_DATA_SET, not_supported }, { "DELAY_MECHANISM", TLV_DELAY_MECHANISM, do_get_action }, { "LOG_MIN_PDELAY_REQ_INTERVAL", TLV_LOG_MIN_PDELAY_REQ_INTERVAL, do_get_action }, { "PORT_DATA_SET_NP", TLV_PORT_DATA_SET_NP, do_set_action }, }; static const char *action_string[] = { "GET", "SET", "RESPONSE", "COMMAND", "ACKNOWLEDGE", }; #define IFMT "\n\t\t" static char *text2str(struct PTPText *text) { static struct static_ptp_text s; s.max_symbols = -1; static_ptp_text_copy(&s, text); return (char*)(s.text); } #define MAX_PRINT_BYTES 16 #define BIN_BUF_SIZE (MAX_PRINT_BYTES * 3 + 1) static char *bin2str_impl(Octet *data, int len, char *buf, int buf_len) { int i, offset = 0; if (len > MAX_PRINT_BYTES) len = MAX_PRINT_BYTES; buf[0] = '\0'; if (!data) return buf; if (len) offset += snprintf(buf, buf_len, "%02hhx", data[0]); for (i = 1; i < len; i++) { if (offset >= buf_len) /* truncated output */ break; offset += snprintf(buf + offset, buf_len - offset, ":%02hhx", data[i]); } return buf; } static char *bin2str(Octet *data, int len) { static char buf[BIN_BUF_SIZE]; return bin2str_impl(data, len, buf, sizeof(buf)); } static uint16_t align16(uint16_t *p) { uint16_t v; memcpy(&v, p, sizeof(v)); return v; } static char *portaddr2str(struct PortAddress *addr) { static char buf[BIN_BUF_SIZE]; switch(align16(&addr->networkProtocol)) { case TRANS_UDP_IPV4: if (align16(&addr->addressLength) == 4 && inet_ntop(AF_INET, addr->address, buf, sizeof(buf))) return buf; break; case TRANS_UDP_IPV6: if (align16(&addr->addressLength) == 16 && inet_ntop(AF_INET6, addr->address, buf, sizeof(buf))) return buf; break; } bin2str_impl(addr->address, align16(&addr->addressLength), buf, sizeof(buf)); return buf; } static void pmc_show(struct ptp_message *msg, FILE *fp) { int action; struct TLV *tlv; struct management_tlv *mgt; struct management_tlv_datum *mtd; struct defaultDS *dds; struct currentDS *cds; struct parentDS *pds; struct timePropertiesDS *tp; struct time_status_np *tsn; struct grandmaster_settings_np *gsn; struct mgmt_clock_description *cd; struct portDS *p; struct port_ds_np *pnp; if (msg_type(msg) != MANAGEMENT) { return; } action = management_action(msg); if (action < GET || action > ACKNOWLEDGE) { return; } fprintf(fp, "\t%s seq %hu %s ", pid2str(&msg->header.sourcePortIdentity), msg->header.sequenceId, action_string[action]); if (msg->tlv_count != 1) { goto out; } tlv = (struct TLV *) msg->management.suffix; if (tlv->type == TLV_MANAGEMENT) { fprintf(fp, "MANAGEMENT "); } else if (tlv->type == TLV_MANAGEMENT_ERROR_STATUS) { fprintf(fp, "MANAGEMENT_ERROR_STATUS "); goto out; } else { fprintf(fp, "unknown-tlv "); } mgt = (struct management_tlv *) msg->management.suffix; if (mgt->length == 2 && mgt->id != TLV_NULL_MANAGEMENT) { fprintf(fp, "empty-tlv "); goto out; } switch (mgt->id) { case TLV_CLOCK_DESCRIPTION: cd = &msg->last_tlv.cd; fprintf(fp, "CLOCK_DESCRIPTION " IFMT "clockType 0x%hx" IFMT "physicalLayerProtocol %s" IFMT "physicalAddress %s" IFMT "protocolAddress %hu %s", align16(cd->clockType), text2str(cd->physicalLayerProtocol), bin2str(cd->physicalAddress->address, align16(&cd->physicalAddress->length)), align16(&cd->protocolAddress->networkProtocol), portaddr2str(cd->protocolAddress)); fprintf(fp, IFMT "manufacturerId %s" IFMT "productDescription %s", bin2str(cd->manufacturerIdentity, OUI_LEN), text2str(cd->productDescription)); fprintf(fp, IFMT "revisionData %s", text2str(cd->revisionData)); fprintf(fp, IFMT "userDescription %s" IFMT "profileId %s", text2str(cd->userDescription), bin2str(cd->profileIdentity, PROFILE_ID_LEN)); break; case TLV_USER_DESCRIPTION: fprintf(fp, "USER_DESCRIPTION " IFMT "userDescription %s", text2str(msg->last_tlv.cd.userDescription)); break; case TLV_DEFAULT_DATA_SET: dds = (struct defaultDS *) mgt->data; fprintf(fp, "DEFAULT_DATA_SET " IFMT "twoStepFlag %d" IFMT "slaveOnly %d" IFMT "numberPorts %hu" IFMT "priority1 %hhu" IFMT "clockClass %hhu" IFMT "clockAccuracy 0x%02hhx" IFMT "offsetScaledLogVariance 0x%04hx" IFMT "priority2 %hhu" IFMT "clockIdentity %s" IFMT "domainNumber %hhu", dds->flags & DDS_TWO_STEP_FLAG ? 1 : 0, dds->flags & DDS_SLAVE_ONLY ? 1 : 0, dds->numberPorts, dds->priority1, dds->clockQuality.clockClass, dds->clockQuality.clockAccuracy, dds->clockQuality.offsetScaledLogVariance, dds->priority2, cid2str(&dds->clockIdentity), dds->domainNumber); break; case TLV_CURRENT_DATA_SET: cds = (struct currentDS *) mgt->data; fprintf(fp, "CURRENT_DATA_SET " IFMT "stepsRemoved %hd" IFMT "offsetFromMaster %.1f" IFMT "meanPathDelay %.1f", cds->stepsRemoved, cds->offsetFromMaster / 65536.0, cds->meanPathDelay / 65536.0); break; case TLV_PARENT_DATA_SET: pds = (struct parentDS *) mgt->data; fprintf(fp, "PARENT_DATA_SET " IFMT "parentPortIdentity %s" IFMT "parentStats %hhu" IFMT "observedParentOffsetScaledLogVariance 0x%04hx" IFMT "observedParentClockPhaseChangeRate 0x%08x" IFMT "grandmasterPriority1 %hhu" IFMT "gm.ClockClass %hhu" IFMT "gm.ClockAccuracy 0x%02hhx" IFMT "gm.OffsetScaledLogVariance 0x%04hx" IFMT "grandmasterPriority2 %hhu" IFMT "grandmasterIdentity %s", pid2str(&pds->parentPortIdentity), pds->parentStats, pds->observedParentOffsetScaledLogVariance, pds->observedParentClockPhaseChangeRate, pds->grandmasterPriority1, pds->grandmasterClockQuality.clockClass, pds->grandmasterClockQuality.clockAccuracy, pds->grandmasterClockQuality.offsetScaledLogVariance, pds->grandmasterPriority2, cid2str(&pds->grandmasterIdentity)); break; case TLV_TIME_PROPERTIES_DATA_SET: tp = (struct timePropertiesDS *) mgt->data; fprintf(fp, "TIME_PROPERTIES_DATA_SET " IFMT "currentUtcOffset %hd" IFMT "leap61 %d" IFMT "leap59 %d" IFMT "currentUtcOffsetValid %d" IFMT "ptpTimescale %d" IFMT "timeTraceable %d" IFMT "frequencyTraceable %d" IFMT "timeSource 0x%02hhx", tp->currentUtcOffset, tp->flags & LEAP_61 ? 1 : 0, tp->flags & LEAP_59 ? 1 : 0, tp->flags & UTC_OFF_VALID ? 1 : 0, tp->flags & PTP_TIMESCALE ? 1 : 0, tp->flags & TIME_TRACEABLE ? 1 : 0, tp->flags & FREQ_TRACEABLE ? 1 : 0, tp->timeSource); break; case TLV_PRIORITY1: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "PRIORITY1 " IFMT "priority1 %hhu", mtd->val); break; case TLV_PRIORITY2: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "PRIORITY2 " IFMT "priority2 %hhu", mtd->val); break; case TLV_DOMAIN: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "DOMAIN " IFMT "domainNumber %hhu", mtd->val); break; case TLV_SLAVE_ONLY: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "SLAVE_ONLY " IFMT "slaveOnly %d", mtd->val & DDS_SLAVE_ONLY ? 1 : 0); break; case TLV_CLOCK_ACCURACY: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "CLOCK_ACCURACY " IFMT "clockAccuracy 0x%02hhx", mtd->val); break; case TLV_TRACEABILITY_PROPERTIES: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "TRACEABILITY_PROPERTIES " IFMT "timeTraceable %d" IFMT "frequencyTraceable %d", mtd->val & TIME_TRACEABLE ? 1 : 0, mtd->val & FREQ_TRACEABLE ? 1 : 0); break; case TLV_TIMESCALE_PROPERTIES: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "TIMESCALE_PROPERTIES " IFMT "ptpTimescale %d", mtd->val & PTP_TIMESCALE ? 1 : 0); break; case TLV_TIME_STATUS_NP: tsn = (struct time_status_np *) mgt->data; fprintf(fp, "TIME_STATUS_NP " IFMT "master_offset %" PRId64 IFMT "ingress_time %" PRId64 IFMT "cumulativeScaledRateOffset %+.9f" IFMT "scaledLastGmPhaseChange %d" IFMT "gmTimeBaseIndicator %hu" IFMT "lastGmPhaseChange 0x%04hx'%016" PRIx64 ".%04hx" IFMT "gmPresent %s" IFMT "gmIdentity %s", tsn->master_offset, tsn->ingress_time, (tsn->cumulativeScaledRateOffset + 0.0) / P41, tsn->scaledLastGmPhaseChange, tsn->gmTimeBaseIndicator, tsn->lastGmPhaseChange.nanoseconds_msb, tsn->lastGmPhaseChange.nanoseconds_lsb, tsn->lastGmPhaseChange.fractional_nanoseconds, tsn->gmPresent ? "true" : "false", cid2str(&tsn->gmIdentity)); break; case TLV_GRANDMASTER_SETTINGS_NP: gsn = (struct grandmaster_settings_np *) mgt->data; fprintf(fp, "GRANDMASTER_SETTINGS_NP " IFMT "clockClass %hhu" IFMT "clockAccuracy 0x%02hhx" IFMT "offsetScaledLogVariance 0x%04hx" IFMT "currentUtcOffset %hd" IFMT "leap61 %d" IFMT "leap59 %d" IFMT "currentUtcOffsetValid %d" IFMT "ptpTimescale %d" IFMT "timeTraceable %d" IFMT "frequencyTraceable %d" IFMT "timeSource 0x%02hhx", gsn->clockQuality.clockClass, gsn->clockQuality.clockAccuracy, gsn->clockQuality.offsetScaledLogVariance, gsn->utc_offset, gsn->time_flags & LEAP_61 ? 1 : 0, gsn->time_flags & LEAP_59 ? 1 : 0, gsn->time_flags & UTC_OFF_VALID ? 1 : 0, gsn->time_flags & PTP_TIMESCALE ? 1 : 0, gsn->time_flags & TIME_TRACEABLE ? 1 : 0, gsn->time_flags & FREQ_TRACEABLE ? 1 : 0, gsn->time_source); break; case TLV_PORT_DATA_SET: p = (struct portDS *) mgt->data; if (p->portState > PS_SLAVE) { p->portState = 0; } fprintf(fp, "PORT_DATA_SET " IFMT "portIdentity %s" IFMT "portState %s" IFMT "logMinDelayReqInterval %hhd" IFMT "peerMeanPathDelay %" PRId64 IFMT "logAnnounceInterval %hhd" IFMT "announceReceiptTimeout %hhu" IFMT "logSyncInterval %hhd" IFMT "delayMechanism %hhu" IFMT "logMinPdelayReqInterval %hhd" IFMT "versionNumber %hhu", pid2str(&p->portIdentity), ps_str[p->portState], p->logMinDelayReqInterval, p->peerMeanPathDelay >> 16, p->logAnnounceInterval, p->announceReceiptTimeout, p->logSyncInterval, p->delayMechanism, p->logMinPdelayReqInterval, p->versionNumber); break; case TLV_PORT_DATA_SET_NP: pnp = (struct port_ds_np *) mgt->data; fprintf(fp, "PORT_DATA_SET_NP " IFMT "neighborPropDelayThresh %u" IFMT "asCapable %d", pnp->neighborPropDelayThresh, pnp->asCapable ? 1 : 0); break; case TLV_LOG_ANNOUNCE_INTERVAL: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "LOG_ANNOUNCE_INTERVAL " IFMT "logAnnounceInterval %hhd", mtd->val); break; case TLV_ANNOUNCE_RECEIPT_TIMEOUT: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "ANNOUNCE_RECEIPT_TIMEOUT " IFMT "announceReceiptTimeout %hhu", mtd->val); break; case TLV_LOG_SYNC_INTERVAL: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "ANNOUNCE_RECEIPT_TIMEOUT " IFMT "logSyncInterval %hhd", mtd->val); break; case TLV_VERSION_NUMBER: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "VERSION_NUMBER " IFMT "versionNumber %hhu", mtd->val); break; case TLV_DELAY_MECHANISM: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "DELAY_MECHANISM " IFMT "delayMechanism %hhu", mtd->val); break; case TLV_LOG_MIN_PDELAY_REQ_INTERVAL: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "LOG_MIN_PDELAY_REQ_INTERVAL " IFMT "logMinPdelayReqInterval %hhd", mtd->val); break; } out: fprintf(fp, "\n"); fflush(fp); } static void do_get_action(int action, int index, char *str) { if (action == GET) pmc_send_get_action(pmc, idtab[index].code); else fprintf(stderr, "%s only allows GET\n", idtab[index].name); } static void do_set_action(int action, int index, char *str) { struct grandmaster_settings_np gsn; struct management_tlv_datum mtd; struct port_ds_np pnp; int cnt, code = idtab[index].code; int leap_61, leap_59, utc_off_valid; int ptp_timescale, time_traceable, freq_traceable; switch (action) { case GET: pmc_send_get_action(pmc, code); return; case SET: break; case RESPONSE: case COMMAND: case ACKNOWLEDGE: default: fprintf(stderr, "%s only allows GET or SET\n", idtab[index].name); return; } switch (code) { case TLV_PRIORITY1: cnt = sscanf(str, " %*s %*s %hhu", &mtd.val); if (cnt != 1) { fprintf(stderr, "%s SET needs 1 value\n", idtab[index].name); break; } pmc_send_set_action(pmc, code, &mtd, sizeof(mtd)); break; case TLV_PRIORITY2: cnt = sscanf(str, " %*s %*s %hhu", &mtd.val); if (cnt != 1) { fprintf(stderr, "%s SET needs 1 value\n", idtab[index].name); break; } pmc_send_set_action(pmc, code, &mtd, sizeof(mtd)); break; case TLV_GRANDMASTER_SETTINGS_NP: cnt = sscanf(str, " %*s %*s " "clockClass %hhu " "clockAccuracy %hhx " "offsetScaledLogVariance %hx " "currentUtcOffset %hd " "leap61 %d " "leap59 %d " "currentUtcOffsetValid %d " "ptpTimescale %d " "timeTraceable %d " "frequencyTraceable %d " "timeSource %hhx ", &gsn.clockQuality.clockClass, &gsn.clockQuality.clockAccuracy, &gsn.clockQuality.offsetScaledLogVariance, &gsn.utc_offset, &leap_61, &leap_59, &utc_off_valid, &ptp_timescale, &time_traceable, &freq_traceable, &gsn.time_source); if (cnt != 11) { fprintf(stderr, "%s SET needs 11 values\n", idtab[index].name); break; } gsn.time_flags = 0; if (leap_61) gsn.time_flags |= LEAP_61; if (leap_59) gsn.time_flags |= LEAP_59; if (utc_off_valid) gsn.time_flags |= UTC_OFF_VALID; if (ptp_timescale) gsn.time_flags |= PTP_TIMESCALE; if (time_traceable) gsn.time_flags |= TIME_TRACEABLE; if (freq_traceable) gsn.time_flags |= FREQ_TRACEABLE; pmc_send_set_action(pmc, code, &gsn, sizeof(gsn)); break; case TLV_PORT_DATA_SET_NP: cnt = sscanf(str, " %*s %*s " "neighborPropDelayThresh %u " "asCapable %d ", &pnp.neighborPropDelayThresh, &pnp.asCapable); if (cnt != 2) { fprintf(stderr, "%s SET needs 2 values\n", idtab[index].name); break; } pmc_send_set_action(pmc, code, &pnp, sizeof(pnp)); break; } } static void not_supported(int action, int index, char *str) { fprintf(stdout, "sorry, %s not supported yet\n", idtab[index].name); } static void null_management(int action, int index, char *str) { if (action == GET) pmc_send_get_action(pmc, idtab[index].code); else puts("non-get actions still todo"); } static int parse_action(char *s) { int len = strlen(s); if (0 == strncasecmp(s, "GET", len)) return GET; else if (0 == strncasecmp(s, "SET", len)) return SET; else if (0 == strncasecmp(s, "CMD", len)) return COMMAND; else if (0 == strncasecmp(s, "COMMAND", len)) return COMMAND; return BAD_ACTION; } static int parse_id(char *s) { int i, index = BAD_ID, len = strlen(s); /* check for exact match */ for (i = 0; i < ARRAY_SIZE(idtab); i++) { if (strcasecmp(s, idtab[i].name) == 0) { return i; } } /* look for a unique prefix match */ for (i = 0; i < ARRAY_SIZE(idtab); i++) { if (0 == strncasecmp(s, idtab[i].name, len)) { if (index == BAD_ID) index = i; else return AMBIGUOUS_ID; } } return index; } static int parse_target(const char *str) { struct PortIdentity pid; if (str[0] == '*') { memset(&pid, 0xff, sizeof(pid)); } else if (str2pid(str, &pid)) { return -1; } return pmc_target(pmc, &pid); } static void print_help(FILE *fp) { int i; fprintf(fp, "\n"); for (i = 0; i < ARRAY_SIZE(idtab); i++) { if (idtab[i].func != not_supported) fprintf(fp, "\t[action] %s\n", idtab[i].name); } fprintf(fp, "\n"); fprintf(fp, "\tThe [action] can be GET, SET, CMD, or COMMAND\n"); fprintf(fp, "\tCommands are case insensitive and may be abbreviated.\n"); fprintf(fp, "\n"); fprintf(fp, "\tTARGET [portIdentity]\n"); fprintf(fp, "\tTARGET *\n"); fprintf(fp, "\n"); } static int do_command(char *str) { int action, id; char action_str[10+1] = {0}, id_str[64+1] = {0}; if (0 == strncasecmp(str, "HELP", strlen(str))) { print_help(stdout); return 0; } if (2 != sscanf(str, " %10s %64s", action_str, id_str)) return -1; if (0 == strncasecmp(action_str, "TARGET", strlen(action_str))) return parse_target(id_str); action = parse_action(action_str); id = parse_id(id_str); if (action == BAD_ACTION || id == BAD_ID) return -1; if (id == AMBIGUOUS_ID) { fprintf(stdout, "id %s is too ambiguous\n", id_str); return 0; } fprintf(stdout, "sending: %s %s\n", action_string[action], idtab[id].name); idtab[id].func(action, id, str); return 0; } static void usage(char *progname) { fprintf(stderr, "\nusage: %s [options] [commands]\n\n" " Network Transport\n\n" " -2 IEEE 802.3\n" " -4 UDP IPV4 (default)\n" " -6 UDP IPV6\n" " -u UDS local\n\n" " Other Options\n\n" " -b [num] boundary hops, default 1\n" " -d [num] domain number, default 0\n" " -h prints this message and exits\n" " -i [dev] interface device to use, default 'eth0'\n" " for network and '/var/run/pmc.$pid' for UDS.\n" " -s [path] server address for UDS, default '/var/run/ptp4l'.\n" " -t [hex] transport specific field, default 0x0\n" " -v prints the software version and exits\n" " -z send zero length TLV values with the GET actions\n" "\n", progname); } int main(int argc, char *argv[]) { const char *iface_name = NULL; char *progname; int c, cnt, length, tmo = -1, batch_mode = 0, zero_datalen = 0; int ret = 0; char line[1024], *command = NULL, uds_local[MAX_IFNAME_SIZE + 1]; enum transport_type transport_type = TRANS_UDP_IPV4; UInteger8 boundary_hops = 1, domain_number = 0, transport_specific = 0; struct ptp_message *msg; struct config *cfg; #define N_FD 2 struct pollfd pollfd[N_FD]; handle_term_signals(); cfg = config_create(); if (!cfg) { return -1; } /* Process the command line arguments. */ progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; while (EOF != (c = getopt(argc, argv, "246u""b:d:hi:s:t:vz"))) { switch (c) { case '2': transport_type = TRANS_IEEE_802_3; break; case '4': transport_type = TRANS_UDP_IPV4; break; case '6': transport_type = TRANS_UDP_IPV6; break; case 'u': transport_type = TRANS_UDS; break; case 'b': boundary_hops = atoi(optarg); break; case 'd': domain_number = atoi(optarg); break; case 'i': iface_name = optarg; break; case 's': if (strlen(optarg) > MAX_IFNAME_SIZE) { fprintf(stderr, "path %s too long, max is %d\n", optarg, MAX_IFNAME_SIZE); config_destroy(cfg); return -1; } if (config_set_string(cfg, "uds_address", optarg)) { config_destroy(cfg); return -1; } break; case 't': if (1 == sscanf(optarg, "%x", &c)) transport_specific = c << 4; break; case 'v': version_show(stdout); config_destroy(cfg); return 0; case 'z': zero_datalen = 1; break; case 'h': usage(progname); config_destroy(cfg); return 0; case '?': default: usage(progname); config_destroy(cfg); return -1; } } if (!iface_name) { if (transport_type == TRANS_UDS) { snprintf(uds_local, sizeof(uds_local), "/var/run/pmc.%d", getpid()); iface_name = uds_local; } else { iface_name = "eth0"; } } if (optind < argc) { batch_mode = 1; } print_set_progname(progname); print_set_syslog(1); print_set_verbose(1); pmc = pmc_create(cfg, transport_type, iface_name, boundary_hops, domain_number, transport_specific, zero_datalen); if (!pmc) { fprintf(stderr, "failed to create pmc\n"); config_destroy(cfg); return -1; } pollfd[0].fd = batch_mode ? -1 : STDIN_FILENO; pollfd[1].fd = pmc_get_transport_fd(pmc); while (is_running()) { if (batch_mode && !command) { if (optind < argc) { command = argv[optind++]; } else { /* No more commands, wait a bit for any outstanding replies and exit. */ tmo = 100; } } pollfd[0].events = 0; pollfd[1].events = POLLIN | POLLPRI; if (!batch_mode && !command) pollfd[0].events |= POLLIN | POLLPRI; if (command) pollfd[1].events |= POLLOUT; cnt = poll(pollfd, N_FD, tmo); if (cnt < 0) { if (EINTR == errno) { continue; } else { pr_emerg("poll failed"); ret = -1; break; } } else if (!cnt) { break; } if (pollfd[0].revents & POLLHUP) { if (tmo == -1) { /* Wait a bit longer for outstanding replies. */ tmo = 100; pollfd[0].fd = -1; pollfd[0].events = 0; } else { break; } } if (pollfd[0].revents & (POLLIN|POLLPRI)) { if (!fgets(line, sizeof(line), stdin)) { break; } length = strlen(line); if (length < 2) { continue; } line[length - 1] = 0; command = line; } if (pollfd[1].revents & POLLOUT) { if (do_command(command)) { fprintf(stderr, "bad command: %s\n", command); } command = NULL; } if (pollfd[1].revents & (POLLIN|POLLPRI)) { msg = pmc_recv(pmc); if (msg) { pmc_show(msg, stdout); msg_put(msg); } } } pmc_destroy(pmc); msg_cleanup(); config_destroy(cfg); return ret; } linuxptp-1.6/pmc_common.c000066400000000000000000000205171257727010700155530ustar00rootroot00000000000000/** * @file pmc_common.c * @note Copyright (C) 2012 Richard Cochran * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "print.h" #include "tlv.h" #include "transport.h" #include "util.h" #include "pmc_common.h" /* Field Len Type -------------------------------------------------------- clockType 2 physicalLayerProtocol 1 PTPText physicalAddressLength 2 UInteger16 physicalAddress 0 protocolAddress 4 Enumeration16 + UInteger16 manufacturerIdentity 3 reserved 1 productDescription 1 PTPText revisionData 1 PTPText userDescription 1 PTPText profileIdentity 6 -------------------------------------------------------- TOTAL 22 */ #define EMPTY_CLOCK_DESCRIPTION 22 /* Includes one extra byte to make length even. */ #define EMPTY_PTP_TEXT 2 struct pmc { UInteger16 sequence_id; UInteger8 boundary_hops; UInteger8 domain_number; UInteger8 transport_specific; struct PortIdentity port_identity; struct PortIdentity target; struct transport *transport; struct fdarray fdarray; int zero_length_gets; }; struct pmc *pmc_create(struct config *cfg, enum transport_type transport_type, const char *iface_name, UInteger8 boundary_hops, UInteger8 domain_number, UInteger8 transport_specific, int zero_datalen) { struct pmc *pmc; pmc = calloc(1, sizeof *pmc); if (!pmc) return NULL; if (transport_type != TRANS_UDS && generate_clock_identity(&pmc->port_identity.clockIdentity, iface_name)) { pr_err("failed to generate a clock identity"); goto failed; } pmc->port_identity.portNumber = 1; pmc_target_all(pmc); pmc->boundary_hops = boundary_hops; pmc->domain_number = domain_number; pmc->transport_specific = transport_specific; pmc->transport = transport_create(cfg, transport_type); if (!pmc->transport) { pr_err("failed to create transport"); goto failed; } if (transport_open(pmc->transport, iface_name, &pmc->fdarray, TS_SOFTWARE)) { pr_err("failed to open transport"); goto failed; } pmc->zero_length_gets = zero_datalen ? 1 : 0; return pmc; failed: if (pmc->transport) transport_destroy(pmc->transport); free(pmc); return NULL; } void pmc_destroy(struct pmc *pmc) { transport_close(pmc->transport, &pmc->fdarray); transport_destroy(pmc->transport); free(pmc); } static struct ptp_message *pmc_message(struct pmc *pmc, uint8_t action) { struct ptp_message *msg; int pdulen; msg = msg_allocate(); if (!msg) return NULL; pdulen = sizeof(struct management_msg); msg->hwts.type = TS_SOFTWARE; msg->header.tsmt = MANAGEMENT | pmc->transport_specific; msg->header.ver = PTP_VERSION; msg->header.messageLength = pdulen; msg->header.domainNumber = pmc->domain_number; msg->header.sourcePortIdentity = pmc->port_identity; msg->header.sequenceId = pmc->sequence_id++; msg->header.control = CTL_MANAGEMENT; msg->header.logMessageInterval = 0x7f; msg->management.targetPortIdentity = pmc->target; msg->management.startingBoundaryHops = pmc->boundary_hops; msg->management.boundaryHops = pmc->boundary_hops; msg->management.flags = action; return msg; } static int pmc_send(struct pmc *pmc, struct ptp_message *msg, int pdulen) { int err; err = msg_pre_send(msg); if (err) { pr_err("msg_pre_send failed"); return -1; } return transport_send(pmc->transport, &pmc->fdarray, 0, msg); } static int pmc_tlv_datalen(struct pmc *pmc, int id) { int len = 0; if (pmc->zero_length_gets) return len; switch (id) { case TLV_USER_DESCRIPTION: len += EMPTY_PTP_TEXT; break; case TLV_DEFAULT_DATA_SET: len += sizeof(struct defaultDS); break; case TLV_CURRENT_DATA_SET: len += sizeof(struct currentDS); break; case TLV_PARENT_DATA_SET: len += sizeof(struct parentDS); break; case TLV_TIME_PROPERTIES_DATA_SET: len += sizeof(struct timePropertiesDS); break; case TLV_PRIORITY1: case TLV_PRIORITY2: case TLV_DOMAIN: case TLV_SLAVE_ONLY: case TLV_CLOCK_ACCURACY: case TLV_TRACEABILITY_PROPERTIES: case TLV_TIMESCALE_PROPERTIES: len += sizeof(struct management_tlv_datum); break; case TLV_TIME_STATUS_NP: len += sizeof(struct time_status_np); break; case TLV_GRANDMASTER_SETTINGS_NP: len += sizeof(struct grandmaster_settings_np); break; case TLV_NULL_MANAGEMENT: break; case TLV_CLOCK_DESCRIPTION: len += EMPTY_CLOCK_DESCRIPTION; break; case TLV_PORT_DATA_SET: len += sizeof(struct portDS); break; case TLV_PORT_DATA_SET_NP: len += sizeof(struct port_ds_np); break; case TLV_LOG_ANNOUNCE_INTERVAL: case TLV_ANNOUNCE_RECEIPT_TIMEOUT: case TLV_LOG_SYNC_INTERVAL: case TLV_VERSION_NUMBER: case TLV_DELAY_MECHANISM: case TLV_LOG_MIN_PDELAY_REQ_INTERVAL: len += sizeof(struct management_tlv_datum); break; } return len; } int pmc_get_transport_fd(struct pmc *pmc) { return pmc->fdarray.fd[FD_GENERAL]; } int pmc_send_get_action(struct pmc *pmc, int id) { int datalen, pdulen; struct ptp_message *msg; struct management_tlv *mgt; msg = pmc_message(pmc, GET); if (!msg) { return -1; } mgt = (struct management_tlv *) msg->management.suffix; mgt->type = TLV_MANAGEMENT; datalen = pmc_tlv_datalen(pmc, id); mgt->length = 2 + datalen; mgt->id = id; pdulen = msg->header.messageLength + sizeof(*mgt) + datalen; msg->header.messageLength = pdulen; msg->tlv_count = 1; if (id == TLV_CLOCK_DESCRIPTION && !pmc->zero_length_gets) { /* * Make sure the tlv_extra pointers dereferenced in * mgt_pre_send() do point to something. */ struct mgmt_clock_description *cd = &msg->last_tlv.cd; uint8_t *buf = mgt->data; cd->clockType = (UInteger16 *) buf; buf += sizeof(*cd->clockType); cd->physicalLayerProtocol = (struct PTPText *) buf; buf += sizeof(struct PTPText) + cd->physicalLayerProtocol->length; cd->physicalAddress = (struct PhysicalAddress *) buf; buf += sizeof(struct PhysicalAddress) + 0; cd->protocolAddress = (struct PortAddress *) buf; } pmc_send(pmc, msg, pdulen); msg_put(msg); return 0; } int pmc_send_set_action(struct pmc *pmc, int id, void *data, int datasize) { int pdulen; struct ptp_message *msg; struct management_tlv *mgt; msg = pmc_message(pmc, SET); if (!msg) { return -1; } mgt = (struct management_tlv *) msg->management.suffix; mgt->type = TLV_MANAGEMENT; mgt->length = 2 + datasize; mgt->id = id; memcpy(mgt->data, data, datasize); pdulen = msg->header.messageLength + sizeof(*mgt) + datasize; msg->header.messageLength = pdulen; msg->tlv_count = 1; pmc_send(pmc, msg, pdulen); msg_put(msg); return 0; } struct ptp_message *pmc_recv(struct pmc *pmc) { struct ptp_message *msg; int cnt, err; msg = msg_allocate(); if (!msg) { pr_err("low memory"); return NULL; } msg->hwts.type = TS_SOFTWARE; cnt = transport_recv(pmc->transport, pmc_get_transport_fd(pmc), msg); if (cnt <= 0) { pr_err("recv message failed"); goto failed; } err = msg_post_recv(msg, cnt); if (err) { switch (err) { case -EBADMSG: pr_err("bad message"); break; case -ETIME: pr_err("received %s without timestamp", msg_type_string(msg_type(msg))); break; case -EPROTO: pr_debug("ignoring message"); } goto failed; } return msg; failed: msg_put(msg); return NULL; } int pmc_target(struct pmc *pmc, struct PortIdentity *pid) { pmc->target = *pid; return 0; } void pmc_target_port(struct pmc *pmc, UInteger16 portNumber) { pmc->target.portNumber = portNumber; } void pmc_target_all(struct pmc *pmc) { memset(&pmc->target, 0xff, sizeof(pmc->target)); } linuxptp-1.6/pmc_common.h000066400000000000000000000031641257727010700155570ustar00rootroot00000000000000/** * @file pmc_common.h * @brief Code shared between PTP management clients. * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_PMC_COMMON_H #define HAVE_PMC_COMMON_H #include "config.h" #include "msg.h" #include "transport.h" struct pmc; struct pmc *pmc_create(struct config *cfg, enum transport_type transport_type, const char *iface_name, UInteger8 boundary_hops, UInteger8 domain_number, UInteger8 transport_specific, int zero_datalen); void pmc_destroy(struct pmc *pmc); int pmc_get_transport_fd(struct pmc *pmc); int pmc_send_get_action(struct pmc *pmc, int id); int pmc_send_set_action(struct pmc *pmc, int id, void *data, int datasize); struct ptp_message *pmc_recv(struct pmc *pmc); int pmc_target(struct pmc *pmc, struct PortIdentity *pid); void pmc_target_port(struct pmc *pmc, UInteger16 portNumber); void pmc_target_all(struct pmc *pmc); #endif linuxptp-1.6/port.c000066400000000000000000002023011257727010700144010ustar00rootroot00000000000000/** * @file port.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include "bmc.h" #include "clock.h" #include "filter.h" #include "missing.h" #include "msg.h" #include "phc.h" #include "port.h" #include "print.h" #include "sk.h" #include "tlv.h" #include "tmv.h" #include "tsproc.h" #include "util.h" #define ALLOWED_LOST_RESPONSES 3 #define ANNOUNCE_SPAN 1 enum syfu_state { SF_EMPTY, SF_HAVE_SYNC, SF_HAVE_FUP, }; enum syfu_event { SYNC_MISMATCH, SYNC_MATCH, FUP_MISMATCH, FUP_MATCH, }; struct nrate_estimator { double ratio; tmv_t origin1; tmv_t ingress1; unsigned int max_count; unsigned int count; int ratio_valid; }; struct port { LIST_ENTRY(port) list; char *name; struct clock *clock; struct transport *trp; enum timestamp_type timestamping; struct fdarray fda; int fault_fd; int phc_index; int jbod; struct foreign_clock *best; enum syfu_state syfu; struct ptp_message *last_syncfup; struct ptp_message *delay_req; struct ptp_message *peer_delay_req; struct ptp_message *peer_delay_resp; struct ptp_message *peer_delay_fup; int peer_portid_valid; struct PortIdentity peer_portid; struct { UInteger16 announce; UInteger16 delayreq; UInteger16 sync; } seqnum; tmv_t peer_delay; struct tsproc *tsproc; int log_sync_interval; struct nrate_estimator nrate; unsigned int pdr_missing; unsigned int multiple_seq_pdr_count; unsigned int multiple_pdr_detected; /* portDS */ struct PortIdentity portIdentity; enum port_state state; /*portState*/ Integer64 asymmetry; int asCapable; Integer8 logMinDelayReqInterval; TimeInterval peerMeanPathDelay; Integer8 logAnnounceInterval; UInteger8 announceReceiptTimeout; UInteger8 syncReceiptTimeout; UInteger8 transportSpecific; Integer8 logSyncInterval; Enumeration8 delayMechanism; Integer8 logMinPdelayReqInterval; UInteger32 neighborPropDelayThresh; int follow_up_info; int freq_est_interval; int hybrid_e2e; int min_neighbor_prop_delay; int path_trace_enabled; int rx_timestamp_offset; int tx_timestamp_offset; struct fault_interval flt_interval_pertype[FT_CNT]; enum fault_type last_fault_type; unsigned int versionNumber; /*UInteger4*/ /* foreignMasterDS */ LIST_HEAD(fm, foreign_clock) foreign_masters; }; #define portnum(p) (p->portIdentity.portNumber) #define NSEC2SEC 1000000000LL static int port_capable(struct port *p); static int port_is_ieee8021as(struct port *p); static void port_nrate_initialize(struct port *p); static int announce_compare(struct ptp_message *m1, struct ptp_message *m2) { struct announce_msg *a = &m1->announce, *b = &m2->announce; int len = sizeof(a->grandmasterPriority1) + sizeof(a->grandmasterClockQuality) + sizeof(a->grandmasterPriority2) + sizeof(a->grandmasterIdentity) + sizeof(a->stepsRemoved); return memcmp(&a->grandmasterPriority1, &b->grandmasterPriority1, len); } static void announce_to_dataset(struct ptp_message *m, struct clock *c, struct dataset *out) { struct announce_msg *a = &m->announce; out->priority1 = a->grandmasterPriority1; out->identity = a->grandmasterIdentity; out->quality = a->grandmasterClockQuality; out->priority2 = a->grandmasterPriority2; out->stepsRemoved = a->stepsRemoved; out->sender = m->header.sourcePortIdentity; out->receiver = clock_parent_identity(c); } static int msg_current(struct ptp_message *m, struct timespec now) { int64_t t1, t2, tmo; t1 = m->ts.host.tv_sec * NSEC2SEC + m->ts.host.tv_nsec; t2 = now.tv_sec * NSEC2SEC + now.tv_nsec; if (m->header.logMessageInterval < -63) { tmo = 0; } else if (m->header.logMessageInterval > 31) { tmo = INT64_MAX; } else if (m->header.logMessageInterval < 0) { tmo = 4LL * NSEC2SEC / (1 << -m->header.logMessageInterval); } else { tmo = 4LL * (1 << m->header.logMessageInterval) * NSEC2SEC; } return t2 - t1 < tmo; } static int msg_source_equal(struct ptp_message *m1, struct foreign_clock *fc) { struct PortIdentity *id1, *id2; id1 = &m1->header.sourcePortIdentity; id2 = &fc->dataset.sender; return 0 == memcmp(id1, id2, sizeof(*id1)); } static int pid_eq(struct PortIdentity *a, struct PortIdentity *b) { return 0 == memcmp(a, b, sizeof(*a)); } static int source_pid_eq(struct ptp_message *m1, struct ptp_message *m2) { return pid_eq(&m1->header.sourcePortIdentity, &m2->header.sourcePortIdentity); } enum fault_type last_fault_type(struct port *port) { return port->last_fault_type; } int fault_interval(struct port *port, enum fault_type ft, struct fault_interval *i) { if (!port || !i) return -EINVAL; if (ft < 0 || ft >= FT_CNT) return -EINVAL; i->type = port->flt_interval_pertype[ft].type; i->val = port->flt_interval_pertype[ft].val; return 0; } int port_fault_fd(struct port *port) { return port->fault_fd; } struct fdarray *port_fda(struct port *port) { return &port->fda; } int set_tmo_log(int fd, unsigned int scale, int log_seconds) { struct itimerspec tmo = { {0, 0}, {0, 0} }; uint64_t ns; int i; if (log_seconds < 0) { log_seconds *= -1; for (i = 1, ns = scale * 500000000ULL; i < log_seconds; i++) { ns >>= 1; } tmo.it_value.tv_nsec = ns; while (tmo.it_value.tv_nsec >= NS_PER_SEC) { tmo.it_value.tv_nsec -= NS_PER_SEC; tmo.it_value.tv_sec++; } } else tmo.it_value.tv_sec = scale * (1 << log_seconds); return timerfd_settime(fd, 0, &tmo, NULL); } int set_tmo_lin(int fd, int seconds) { struct itimerspec tmo = { {0, 0}, {0, 0} }; tmo.it_value.tv_sec = seconds; return timerfd_settime(fd, 0, &tmo, NULL); } int set_tmo_random(int fd, int min, int span, int log_seconds) { uint64_t value_ns, min_ns, span_ns; struct itimerspec tmo = { {0, 0}, {0, 0} }; if (log_seconds >= 0) { min_ns = min * NS_PER_SEC << log_seconds; span_ns = span * NS_PER_SEC << log_seconds; } else { min_ns = min * NS_PER_SEC >> -log_seconds; span_ns = span * NS_PER_SEC >> -log_seconds; } value_ns = min_ns + (span_ns * (random() % (1 << 15) + 1) >> 15); tmo.it_value.tv_sec = value_ns / NS_PER_SEC; tmo.it_value.tv_nsec = value_ns % NS_PER_SEC; return timerfd_settime(fd, 0, &tmo, NULL); } int port_set_fault_timer_log(struct port *port, unsigned int scale, int log_seconds) { return set_tmo_log(port->fault_fd, scale, log_seconds); } int port_set_fault_timer_lin(struct port *port, int seconds) { return set_tmo_lin(port->fault_fd, seconds); } static void fc_clear(struct foreign_clock *fc) { struct ptp_message *m; while (fc->n_messages) { m = TAILQ_LAST(&fc->messages, messages); TAILQ_REMOVE(&fc->messages, m, list); fc->n_messages--; msg_put(m); } } static void fc_prune(struct foreign_clock *fc) { struct timespec now; struct ptp_message *m; clock_gettime(CLOCK_MONOTONIC, &now); while (fc->n_messages > FOREIGN_MASTER_THRESHOLD) { m = TAILQ_LAST(&fc->messages, messages); TAILQ_REMOVE(&fc->messages, m, list); fc->n_messages--; msg_put(m); } while (!TAILQ_EMPTY(&fc->messages)) { m = TAILQ_LAST(&fc->messages, messages); if (msg_current(m, now)) break; TAILQ_REMOVE(&fc->messages, m, list); fc->n_messages--; msg_put(m); } } static void ts_add(struct timespec *ts, int ns) { if (!ns) { return; } ts->tv_nsec += ns; while (ts->tv_nsec < 0) { ts->tv_nsec += (long) NS_PER_SEC; ts->tv_sec--; } while (ts->tv_nsec >= (long) NS_PER_SEC) { ts->tv_nsec -= (long) NS_PER_SEC; ts->tv_sec++; } } static void ts_to_timestamp(struct timespec *src, struct Timestamp *dst) { dst->seconds_lsb = src->tv_sec; dst->seconds_msb = 0; dst->nanoseconds = src->tv_nsec; } /* * Returns non-zero if the announce message is different than last. */ static int add_foreign_master(struct port *p, struct ptp_message *m) { struct foreign_clock *fc; struct ptp_message *tmp; int broke_threshold = 0, diff = 0; LIST_FOREACH(fc, &p->foreign_masters, list) { if (msg_source_equal(m, fc)) break; } if (!fc) { pr_notice("port %hu: new foreign master %s", portnum(p), pid2str(&m->header.sourcePortIdentity)); fc = malloc(sizeof(*fc)); if (!fc) { pr_err("low memory, failed to add foreign master"); return 0; } memset(fc, 0, sizeof(*fc)); LIST_INSERT_HEAD(&p->foreign_masters, fc, list); fc->port = p; fc->dataset.sender = m->header.sourcePortIdentity; /* We do not count this first message, see 9.5.3(b) */ return 0; } /* * If this message breaks the threshold, that is an important change. */ fc_prune(fc); if (FOREIGN_MASTER_THRESHOLD - 1 == fc->n_messages) broke_threshold = 1; /* * Okay, go ahead and add this announcement. */ msg_get(m); fc->n_messages++; TAILQ_INSERT_HEAD(&fc->messages, m, list); /* * Test if this announcement contains changed information. */ if (fc->n_messages > 1) { tmp = TAILQ_NEXT(m, list); diff = announce_compare(m, tmp); } return broke_threshold || diff; } static int follow_up_info_append(struct port *p, struct ptp_message *m) { struct follow_up_info_tlv *fui; fui = (struct follow_up_info_tlv *) m->follow_up.suffix; fui->type = TLV_ORGANIZATION_EXTENSION; fui->length = sizeof(*fui) - sizeof(fui->type) - sizeof(fui->length); memcpy(fui->id, ieee8021_id, sizeof(ieee8021_id)); fui->subtype[2] = 1; m->tlv_count = 1; return sizeof(*fui); } static struct follow_up_info_tlv *follow_up_info_extract(struct ptp_message *m) { struct follow_up_info_tlv *f; f = (struct follow_up_info_tlv *) m->follow_up.suffix; if (m->tlv_count != 1 || f->type != TLV_ORGANIZATION_EXTENSION || f->length != sizeof(*f) - sizeof(f->type) - sizeof(f->length) || // memcmp(f->id, ieee8021_id, sizeof(ieee8021_id)) || f->subtype[0] || f->subtype[1] || f->subtype[2] != 1) { return NULL; } return f; } static void free_foreign_masters(struct port *p) { struct foreign_clock *fc; while ((fc = LIST_FIRST(&p->foreign_masters)) != NULL) { LIST_REMOVE(fc, list); fc_clear(fc); free(fc); } } static int fup_sync_ok(struct ptp_message *fup, struct ptp_message *sync) { int64_t tfup, tsync; tfup = tmv_to_nanoseconds(timespec_to_tmv(fup->hwts.sw)); tsync = tmv_to_nanoseconds(timespec_to_tmv(sync->hwts.sw)); /* * NB - If the sk_check_fupsync option is not enabled, then * both of these time stamps will be zero. */ if (tfup < tsync) { return 0; } return 1; } static int incapable_ignore(struct port *p, struct ptp_message *m) { if (port_capable(p)) { return 0; } if (msg_type(m) == ANNOUNCE || msg_type(m) == SYNC) { return 1; } return 0; } static int path_trace_append(struct port *p, struct ptp_message *m, struct parent_ds *dad) { struct path_trace_tlv *ptt; int length = 1 + dad->path_length; if (length > PATH_TRACE_MAX) { return 0; } ptt = (struct path_trace_tlv *) m->announce.suffix; ptt->type = TLV_PATH_TRACE; ptt->length = length * sizeof(struct ClockIdentity); memcpy(ptt->cid, dad->ptl, ptt->length); ptt->cid[length - 1] = clock_identity(p->clock); m->tlv_count = 1; return ptt->length + sizeof(ptt->type) + sizeof(ptt->length); } static int path_trace_ignore(struct port *p, struct ptp_message *m) { struct ClockIdentity cid; struct path_trace_tlv *ptt; int i, cnt; if (!p->path_trace_enabled) { return 0; } if (msg_type(m) != ANNOUNCE) { return 0; } if (m->tlv_count != 1) { return 1; } ptt = (struct path_trace_tlv *) m->announce.suffix; if (ptt->type != TLV_PATH_TRACE) { return 1; } cnt = path_length(ptt); cid = clock_identity(p->clock); for (i = 0; i < cnt; i++) { if (0 == memcmp(&ptt->cid[i], &cid, sizeof(cid))) return 1; } return 0; } static int peer_prepare_and_send(struct port *p, struct ptp_message *msg, int event) { int cnt; if (msg_pre_send(msg)) { return -1; } cnt = transport_peer(p->trp, &p->fda, event, msg); if (cnt <= 0) { return -1; } if (msg_sots_valid(msg)) { ts_add(&msg->hwts.ts, p->tx_timestamp_offset); } return 0; } static int port_capable(struct port *p) { if (!port_is_ieee8021as(p)) { /* Normal 1588 ports are always capable. */ goto capable; } if (tmv_to_nanoseconds(p->peer_delay) > p->neighborPropDelayThresh) { if (p->asCapable) pr_debug("port %hu: peer_delay (%" PRId64 ") > neighborPropDelayThresh " "(%" PRId32 "), resetting asCapable", portnum(p), tmv_to_nanoseconds(p->peer_delay), p->neighborPropDelayThresh); goto not_capable; } if (tmv_to_nanoseconds(p->peer_delay) < p->min_neighbor_prop_delay) { if (p->asCapable) pr_debug("port %hu: peer_delay (%" PRId64 ") < min_neighbor_prop_delay " "(%" PRId32 "), resetting asCapable", portnum(p), tmv_to_nanoseconds(p->peer_delay), p->min_neighbor_prop_delay); goto not_capable; } if (p->pdr_missing > ALLOWED_LOST_RESPONSES) { if (p->asCapable) pr_debug("port %hu: missed %d peer delay resp, " "resetting asCapable", portnum(p), p->pdr_missing); goto not_capable; } if (p->multiple_seq_pdr_count) { if (p->asCapable) pr_debug("port %hu: multiple sequential peer delay resp, " "resetting asCapable", portnum(p)); goto not_capable; } if (!p->peer_portid_valid) { if (p->asCapable) pr_debug("port %hu: invalid peer port id, " "resetting asCapable", portnum(p)); goto not_capable; } if (!p->nrate.ratio_valid) { if (p->asCapable) pr_debug("port %hu: invalid nrate, " "resetting asCapable", portnum(p)); goto not_capable; } capable: if (!p->asCapable) pr_debug("port %hu: setting asCapable", portnum(p)); p->asCapable = 1; return 1; not_capable: if (p->asCapable) port_nrate_initialize(p); p->asCapable = 0; return 0; } static int port_clr_tmo(int fd) { struct itimerspec tmo = { {0, 0}, {0, 0} }; return timerfd_settime(fd, 0, &tmo, NULL); } static int port_ignore(struct port *p, struct ptp_message *m) { struct ClockIdentity c1, c2; if (incapable_ignore(p, m)) { return 1; } if (path_trace_ignore(p, m)) { return 1; } if (msg_transport_specific(m) != p->transportSpecific) { return 1; } if (pid_eq(&m->header.sourcePortIdentity, &p->portIdentity)) { return 1; } if (m->header.domainNumber != clock_domain_number(p->clock)) { return 1; } c1 = clock_identity(p->clock); c2 = m->header.sourcePortIdentity.clockIdentity; if (0 == memcmp(&c1, &c2, sizeof(c1))) { return 1; } return 0; } /* * Test whether a 802.1AS port may transmit a sync message. */ static int port_sync_incapable(struct port *p) { struct ClockIdentity cid; struct PortIdentity pid; if (!port_is_ieee8021as(p)) { return 0; } if (clock_gm_capable(p->clock)) { return 0; } cid = clock_identity(p->clock); pid = clock_parent_identity(p->clock); if (!memcmp(&cid, &pid.clockIdentity, sizeof(cid))) { /* * We are the GM, but without gmCapable set. */ return 1; } return 0; } static int port_is_ieee8021as(struct port *p) { return p->follow_up_info ? 1 : 0; } static void port_management_send_error(struct port *p, struct port *ingress, struct ptp_message *msg, int error_id) { if (port_management_error(p->portIdentity, ingress, msg, error_id)) pr_err("port %hu: management error failed", portnum(p)); } static const Octet profile_id_drr[] = {0x00, 0x1B, 0x19, 0x00, 0x01, 0x00}; static const Octet profile_id_p2p[] = {0x00, 0x1B, 0x19, 0x00, 0x02, 0x00}; static int port_management_fill_response(struct port *target, struct ptp_message *rsp, int id) { int datalen = 0, respond = 0; struct management_tlv *tlv; struct management_tlv_datum *mtd; struct portDS *pds; struct port_ds_np *pdsnp; struct port_properties_np *ppn; struct clock_description *desc; struct mgmt_clock_description *cd; uint8_t *buf; uint16_t u16; tlv = (struct management_tlv *) rsp->management.suffix; tlv->type = TLV_MANAGEMENT; tlv->id = id; switch (id) { case TLV_NULL_MANAGEMENT: datalen = 0; respond = 1; break; case TLV_CLOCK_DESCRIPTION: cd = &rsp->last_tlv.cd; buf = tlv->data; cd->clockType = (UInteger16 *) buf; buf += sizeof(*cd->clockType); if (clock_num_ports(target->clock) > 1) { *cd->clockType = CLOCK_TYPE_BOUNDARY; } else { *cd->clockType = CLOCK_TYPE_ORDINARY; } cd->physicalLayerProtocol = (struct PTPText *) buf; switch(transport_type(target->trp)) { case TRANS_UDP_IPV4: case TRANS_UDP_IPV6: case TRANS_IEEE_802_3: ptp_text_set(cd->physicalLayerProtocol, "IEEE 802.3"); break; default: ptp_text_set(cd->physicalLayerProtocol, NULL); break; } buf += sizeof(struct PTPText) + cd->physicalLayerProtocol->length; cd->physicalAddress = (struct PhysicalAddress *) buf; u16 = transport_physical_addr(target->trp, cd->physicalAddress->address); memcpy(&cd->physicalAddress->length, &u16, 2); buf += sizeof(struct PhysicalAddress) + u16; cd->protocolAddress = (struct PortAddress *) buf; u16 = transport_type(target->trp); memcpy(&cd->protocolAddress->networkProtocol, &u16, 2); u16 = transport_protocol_addr(target->trp, cd->protocolAddress->address); memcpy(&cd->protocolAddress->addressLength, &u16, 2); buf += sizeof(struct PortAddress) + u16; desc = clock_description(target->clock); cd->manufacturerIdentity = buf; memcpy(cd->manufacturerIdentity, desc->manufacturerIdentity, OUI_LEN); buf += OUI_LEN; *(buf++) = 0; /* reserved */ cd->productDescription = (struct PTPText *) buf; ptp_text_copy(cd->productDescription, &desc->productDescription); buf += sizeof(struct PTPText) + cd->productDescription->length; cd->revisionData = (struct PTPText *) buf; ptp_text_copy(cd->revisionData, &desc->revisionData); buf += sizeof(struct PTPText) + cd->revisionData->length; cd->userDescription = (struct PTPText *) buf; ptp_text_copy(cd->userDescription, &desc->userDescription); buf += sizeof(struct PTPText) + cd->userDescription->length; if (target->delayMechanism == DM_P2P) { memcpy(buf, profile_id_p2p, PROFILE_ID_LEN); } else { memcpy(buf, profile_id_drr, PROFILE_ID_LEN); } buf += PROFILE_ID_LEN; datalen = buf - tlv->data; respond = 1; break; case TLV_PORT_DATA_SET: pds = (struct portDS *) tlv->data; pds->portIdentity = target->portIdentity; if (target->state == PS_GRAND_MASTER) { pds->portState = PS_MASTER; } else { pds->portState = target->state; } pds->logMinDelayReqInterval = target->logMinDelayReqInterval; pds->peerMeanPathDelay = target->peerMeanPathDelay; pds->logAnnounceInterval = target->logAnnounceInterval; pds->announceReceiptTimeout = target->announceReceiptTimeout; pds->logSyncInterval = target->logSyncInterval; if (target->delayMechanism) { pds->delayMechanism = target->delayMechanism; } else { pds->delayMechanism = DM_E2E; } pds->logMinPdelayReqInterval = target->logMinPdelayReqInterval; pds->versionNumber = target->versionNumber; datalen = sizeof(*pds); respond = 1; break; case TLV_LOG_ANNOUNCE_INTERVAL: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = target->logAnnounceInterval; datalen = sizeof(*mtd); respond = 1; break; case TLV_ANNOUNCE_RECEIPT_TIMEOUT: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = target->announceReceiptTimeout; datalen = sizeof(*mtd); respond = 1; break; case TLV_LOG_SYNC_INTERVAL: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = target->logSyncInterval; datalen = sizeof(*mtd); respond = 1; break; case TLV_VERSION_NUMBER: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = target->versionNumber; datalen = sizeof(*mtd); respond = 1; break; case TLV_DELAY_MECHANISM: mtd = (struct management_tlv_datum *) tlv->data; if (target->delayMechanism) mtd->val = target->delayMechanism; else mtd->val = DM_E2E; datalen = sizeof(*mtd); respond = 1; break; case TLV_LOG_MIN_PDELAY_REQ_INTERVAL: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = target->logMinPdelayReqInterval; datalen = sizeof(*mtd); respond = 1; break; case TLV_PORT_DATA_SET_NP: pdsnp = (struct port_ds_np *) tlv->data; pdsnp->neighborPropDelayThresh = target->neighborPropDelayThresh; pdsnp->asCapable = target->asCapable; datalen = sizeof(*pdsnp); respond = 1; break; case TLV_PORT_PROPERTIES_NP: ppn = (struct port_properties_np *)tlv->data; ppn->portIdentity = target->portIdentity; if (target->state == PS_GRAND_MASTER) ppn->port_state = PS_MASTER; else ppn->port_state = target->state; ppn->timestamping = target->timestamping; ptp_text_set(&ppn->interface, target->name); datalen = sizeof(*ppn) + ppn->interface.length; respond = 1; break; } if (respond) { if (datalen % 2) { tlv->data[datalen] = 0; datalen++; } tlv->length = sizeof(tlv->id) + datalen; rsp->header.messageLength += sizeof(*tlv) + datalen; rsp->tlv_count = 1; } return respond; } static int port_management_get_response(struct port *target, struct port *ingress, int id, struct ptp_message *req) { struct PortIdentity pid = port_identity(target); struct ptp_message *rsp; int respond; rsp = port_management_reply(pid, ingress, req); if (!rsp) { return 0; } respond = port_management_fill_response(target, rsp, id); if (respond) port_prepare_and_send(ingress, rsp, 0); msg_put(rsp); return respond; } static int port_management_set(struct port *target, struct port *ingress, int id, struct ptp_message *req) { int respond = 0; struct management_tlv *tlv; struct port_ds_np *pdsnp; tlv = (struct management_tlv *) req->management.suffix; switch (id) { case TLV_PORT_DATA_SET_NP: pdsnp = (struct port_ds_np *) tlv->data; target->neighborPropDelayThresh = pdsnp->neighborPropDelayThresh; respond = 1; break; } if (respond && !port_management_get_response(target, ingress, id, req)) pr_err("port %hu: failed to send management set response", portnum(target)); return respond ? 1 : 0; } static void port_nrate_calculate(struct port *p, tmv_t origin, tmv_t ingress) { struct nrate_estimator *n = &p->nrate; /* * We experienced a successful exchanges of peer delay request * and response, reset pdr_missing for this port. */ p->pdr_missing = 0; if (!n->ingress1) { n->ingress1 = ingress; n->origin1 = origin; return; } n->count++; if (n->count < n->max_count) { return; } if (tmv_eq(ingress, n->ingress1)) { pr_warning("bad timestamps in nrate calculation"); return; } n->ratio = tmv_dbl(tmv_sub(origin, n->origin1)) / tmv_dbl(tmv_sub(ingress, n->ingress1)); n->ingress1 = ingress; n->origin1 = origin; n->count = 0; n->ratio_valid = 1; } static void port_nrate_initialize(struct port *p) { int shift = p->freq_est_interval - p->logMinPdelayReqInterval; if (shift < 0) shift = 0; else if (shift >= sizeof(int) * 8) { shift = sizeof(int) * 8 - 1; pr_warning("freq_est_interval is too long"); } /* We start in the 'incapable' state. */ p->pdr_missing = ALLOWED_LOST_RESPONSES + 1; p->asCapable = 0; p->peer_portid_valid = 0; p->nrate.origin1 = tmv_zero(); p->nrate.ingress1 = tmv_zero(); p->nrate.max_count = (1 << shift); p->nrate.count = 0; p->nrate.ratio = 1.0; p->nrate.ratio_valid = 0; } static int port_set_announce_tmo(struct port *p) { return set_tmo_random(p->fda.fd[FD_ANNOUNCE_TIMER], p->announceReceiptTimeout, ANNOUNCE_SPAN, p->logAnnounceInterval); } static int port_set_delay_tmo(struct port *p) { if (p->delayMechanism == DM_P2P) { return set_tmo_log(p->fda.fd[FD_DELAY_TIMER], 1, p->logMinPdelayReqInterval); } else { return set_tmo_random(p->fda.fd[FD_DELAY_TIMER], 0, 2, p->logMinDelayReqInterval); } } static int port_set_manno_tmo(struct port *p) { return set_tmo_log(p->fda.fd[FD_MANNO_TIMER], 1, p->logAnnounceInterval); } static int port_set_qualification_tmo(struct port *p) { return set_tmo_log(p->fda.fd[FD_QUALIFICATION_TIMER], 1+clock_steps_removed(p->clock), p->logAnnounceInterval); } static int port_set_sync_rx_tmo(struct port *p) { return set_tmo_log(p->fda.fd[FD_SYNC_RX_TIMER], p->syncReceiptTimeout, p->logSyncInterval); } static int port_set_sync_tx_tmo(struct port *p) { return set_tmo_log(p->fda.fd[FD_SYNC_TX_TIMER], 1, p->logSyncInterval); } static void port_show_transition(struct port *p, enum port_state next, enum fsm_event event) { if (event == EV_FAULT_DETECTED) { pr_notice("port %hu: %s to %s on %s (%s)", portnum(p), ps_str[p->state], ps_str[next], ev_str[event], ft_str(last_fault_type(p))); } else { pr_notice("port %hu: %s to %s on %s", portnum(p), ps_str[p->state], ps_str[next], ev_str[event]); } } static void port_slave_priority_warning(struct port *p) { UInteger16 n = portnum(p); pr_warning("port %hu: master state recommended in slave only mode", n); pr_warning("port %hu: defaultDS.priority1 probably misconfigured", n); } static void port_synchronize(struct port *p, struct timespec ingress_ts, struct timestamp origin_ts, Integer64 correction1, Integer64 correction2) { enum servo_state state; tmv_t t1, t1c, t2, c1, c2; port_set_sync_rx_tmo(p); t1 = timestamp_to_tmv(origin_ts); t2 = timespec_to_tmv(ingress_ts); c1 = correction_to_tmv(correction1); c2 = correction_to_tmv(correction2); t1c = tmv_add(t1, tmv_add(c1, c2)); state = clock_synchronize(p->clock, t2, t1c); switch (state) { case SERVO_UNLOCKED: port_dispatch(p, EV_SYNCHRONIZATION_FAULT, 0); break; case SERVO_JUMP: port_dispatch(p, EV_SYNCHRONIZATION_FAULT, 0); if (p->delay_req) { msg_put(p->delay_req); p->delay_req = NULL; } if (p->peer_delay_req) { msg_put(p->peer_delay_req); p->peer_delay_req = NULL; } break; case SERVO_LOCKED: port_dispatch(p, EV_MASTER_CLOCK_SELECTED, 0); break; } } /* * Handle out of order packets. The network stack might * provide the follow up _before_ the sync message. After all, * they can arrive on two different ports. In addition, time * stamping in PHY devices might delay the event packets. */ static void port_syfufsm(struct port *p, enum syfu_event event, struct ptp_message *m) { struct ptp_message *syn, *fup; switch (p->syfu) { case SF_EMPTY: switch (event) { case SYNC_MISMATCH: msg_get(m); p->last_syncfup = m; p->syfu = SF_HAVE_SYNC; break; case FUP_MISMATCH: msg_get(m); p->last_syncfup = m; p->syfu = SF_HAVE_FUP; break; case SYNC_MATCH: break; case FUP_MATCH: break; } break; case SF_HAVE_SYNC: switch (event) { case SYNC_MISMATCH: msg_put(p->last_syncfup); msg_get(m); p->last_syncfup = m; break; case SYNC_MATCH: break; case FUP_MISMATCH: msg_put(p->last_syncfup); msg_get(m); p->last_syncfup = m; p->syfu = SF_HAVE_FUP; break; case FUP_MATCH: syn = p->last_syncfup; port_synchronize(p, syn->hwts.ts, m->ts.pdu, syn->header.correction, m->header.correction); msg_put(p->last_syncfup); p->syfu = SF_EMPTY; break; } break; case SF_HAVE_FUP: switch (event) { case SYNC_MISMATCH: msg_put(p->last_syncfup); msg_get(m); p->last_syncfup = m; p->syfu = SF_HAVE_SYNC; break; case SYNC_MATCH: fup = p->last_syncfup; port_synchronize(p, m->hwts.ts, fup->ts.pdu, m->header.correction, fup->header.correction); msg_put(p->last_syncfup); p->syfu = SF_EMPTY; break; case FUP_MISMATCH: msg_put(p->last_syncfup); msg_get(m); p->last_syncfup = m; break; case FUP_MATCH: break; } break; } } static int port_pdelay_request(struct port *p) { struct ptp_message *msg; int err; /* If multiple pdelay resp were not detected the counter can be reset */ if (!p->multiple_pdr_detected) p->multiple_seq_pdr_count = 0; p->multiple_pdr_detected = 0; msg = msg_allocate(); if (!msg) return -1; msg->hwts.type = p->timestamping; msg->header.tsmt = PDELAY_REQ | p->transportSpecific; msg->header.ver = PTP_VERSION; msg->header.messageLength = sizeof(struct pdelay_req_msg); msg->header.domainNumber = clock_domain_number(p->clock); msg->header.correction = -p->asymmetry; msg->header.sourcePortIdentity = p->portIdentity; msg->header.sequenceId = p->seqnum.delayreq++; msg->header.control = CTL_OTHER; msg->header.logMessageInterval = port_is_ieee8021as(p) ? p->logMinPdelayReqInterval : 0x7f; err = peer_prepare_and_send(p, msg, 1); if (err) { pr_err("port %hu: send peer delay request failed", portnum(p)); goto out; } if (msg_sots_missing(msg)) { pr_err("missing timestamp on transmitted peer delay request"); goto out; } if (p->peer_delay_req) { if (port_capable(p)) { p->pdr_missing++; } msg_put(p->peer_delay_req); } p->peer_delay_req = msg; return 0; out: msg_put(msg); return -1; } static int port_delay_request(struct port *p) { struct ptp_message *msg; /* Time to send a new request, forget current pdelay resp and fup */ if (p->peer_delay_resp) { msg_put(p->peer_delay_resp); p->peer_delay_resp = NULL; } if (p->peer_delay_fup) { msg_put(p->peer_delay_fup); p->peer_delay_fup = NULL; } if (p->delayMechanism == DM_P2P) return port_pdelay_request(p); msg = msg_allocate(); if (!msg) return -1; msg->hwts.type = p->timestamping; msg->header.tsmt = DELAY_REQ | p->transportSpecific; msg->header.ver = PTP_VERSION; msg->header.messageLength = sizeof(struct delay_req_msg); msg->header.domainNumber = clock_domain_number(p->clock); msg->header.correction = -p->asymmetry; msg->header.sourcePortIdentity = p->portIdentity; msg->header.sequenceId = p->seqnum.delayreq++; msg->header.control = CTL_DELAY_REQ; msg->header.logMessageInterval = 0x7f; if (p->hybrid_e2e) { struct ptp_message *dst = TAILQ_FIRST(&p->best->messages); msg->address = dst->address; msg->header.flagField[0] |= UNICAST; } if (port_prepare_and_send(p, msg, 1)) { pr_err("port %hu: send delay request failed", portnum(p)); goto out; } if (msg_sots_missing(msg)) { pr_err("missing timestamp on transmitted delay request"); goto out; } if (p->delay_req) msg_put(p->delay_req); p->delay_req = msg; return 0; out: msg_put(msg); return -1; } static int port_tx_announce(struct port *p) { struct parent_ds *dad = clock_parent_ds(p->clock); struct timePropertiesDS *tp = clock_time_properties(p->clock); struct ptp_message *msg; int err, pdulen; if (!port_capable(p)) { return 0; } msg = msg_allocate(); if (!msg) return -1; pdulen = sizeof(struct announce_msg); msg->hwts.type = p->timestamping; if (p->path_trace_enabled) pdulen += path_trace_append(p, msg, dad); msg->header.tsmt = ANNOUNCE | p->transportSpecific; msg->header.ver = PTP_VERSION; msg->header.messageLength = pdulen; msg->header.domainNumber = clock_domain_number(p->clock); msg->header.sourcePortIdentity = p->portIdentity; msg->header.sequenceId = p->seqnum.announce++; msg->header.control = CTL_OTHER; msg->header.logMessageInterval = p->logAnnounceInterval; msg->header.flagField[1] = tp->flags; msg->announce.currentUtcOffset = tp->currentUtcOffset; msg->announce.grandmasterPriority1 = dad->pds.grandmasterPriority1; msg->announce.grandmasterClockQuality = dad->pds.grandmasterClockQuality; msg->announce.grandmasterPriority2 = dad->pds.grandmasterPriority2; msg->announce.grandmasterIdentity = dad->pds.grandmasterIdentity; msg->announce.stepsRemoved = clock_steps_removed(p->clock); msg->announce.timeSource = tp->timeSource; err = port_prepare_and_send(p, msg, 0); if (err) pr_err("port %hu: send announce failed", portnum(p)); msg_put(msg); return err; } static int port_tx_sync(struct port *p) { struct ptp_message *msg, *fup; int err, pdulen; int event = p->timestamping == TS_ONESTEP ? TRANS_ONESTEP : TRANS_EVENT; if (!port_capable(p)) { return 0; } if (port_sync_incapable(p)) { return 0; } msg = msg_allocate(); if (!msg) return -1; fup = msg_allocate(); if (!fup) { msg_put(msg); return -1; } pdulen = sizeof(struct sync_msg); msg->hwts.type = p->timestamping; msg->header.tsmt = SYNC | p->transportSpecific; msg->header.ver = PTP_VERSION; msg->header.messageLength = pdulen; msg->header.domainNumber = clock_domain_number(p->clock); msg->header.sourcePortIdentity = p->portIdentity; msg->header.sequenceId = p->seqnum.sync++; msg->header.control = CTL_SYNC; msg->header.logMessageInterval = p->logSyncInterval; if (p->timestamping != TS_ONESTEP) msg->header.flagField[0] |= TWO_STEP; err = port_prepare_and_send(p, msg, event); if (err) { pr_err("port %hu: send sync failed", portnum(p)); goto out; } if (p->timestamping == TS_ONESTEP) { goto out; } else if (msg_sots_missing(msg)) { pr_err("missing timestamp on transmitted sync"); err = -1; goto out; } /* * Send the follow up message right away. */ pdulen = sizeof(struct follow_up_msg); fup->hwts.type = p->timestamping; if (p->follow_up_info) pdulen += follow_up_info_append(p, fup); fup->header.tsmt = FOLLOW_UP | p->transportSpecific; fup->header.ver = PTP_VERSION; fup->header.messageLength = pdulen; fup->header.domainNumber = clock_domain_number(p->clock); fup->header.sourcePortIdentity = p->portIdentity; fup->header.sequenceId = p->seqnum.sync - 1; fup->header.control = CTL_FOLLOW_UP; fup->header.logMessageInterval = p->logSyncInterval; ts_to_timestamp(&msg->hwts.ts, &fup->follow_up.preciseOriginTimestamp); err = port_prepare_and_send(p, fup, 0); if (err) pr_err("port %hu: send follow up failed", portnum(p)); out: msg_put(msg); msg_put(fup); return err; } /* * port initialize and disable */ static int port_is_enabled(struct port *p) { switch (p->state) { case PS_INITIALIZING: case PS_FAULTY: case PS_DISABLED: return 0; case PS_LISTENING: case PS_PRE_MASTER: case PS_MASTER: case PS_GRAND_MASTER: case PS_PASSIVE: case PS_UNCALIBRATED: case PS_SLAVE: break; } return 1; } static void flush_last_sync(struct port *p) { if (p->syfu != SF_EMPTY) { msg_put(p->last_syncfup); p->syfu = SF_EMPTY; } } static void flush_delay_req(struct port *p) { if (p->delay_req) { msg_put(p->delay_req); p->delay_req = NULL; } } static void flush_peer_delay(struct port *p) { if (p->peer_delay_req) { msg_put(p->peer_delay_req); p->peer_delay_req = NULL; } if (p->peer_delay_resp) { msg_put(p->peer_delay_resp); p->peer_delay_resp = NULL; } if (p->peer_delay_fup) { msg_put(p->peer_delay_fup); p->peer_delay_fup = NULL; } } static void port_clear_fda(struct port *p, int count) { int i; for (i = 0; i < count; i++) p->fda.fd[i] = -1; } static void port_disable(struct port *p) { int i; flush_last_sync(p); flush_delay_req(p); flush_peer_delay(p); p->best = NULL; free_foreign_masters(p); transport_close(p->trp, &p->fda); for (i = 0; i < N_TIMER_FDS; i++) { close(p->fda.fd[FD_ANNOUNCE_TIMER + i]); } port_clear_fda(p, N_POLLFD); clock_fda_changed(p->clock); } static int port_initialize(struct port *p) { struct config *cfg = clock_config(p->clock); int fd[N_TIMER_FDS], i; p->multiple_seq_pdr_count = 0; p->multiple_pdr_detected = 0; p->last_fault_type = FT_UNSPECIFIED; p->logMinDelayReqInterval = config_get_int(cfg, p->name, "logMinDelayReqInterval"); p->peerMeanPathDelay = 0; p->logAnnounceInterval = config_get_int(cfg, p->name, "logAnnounceInterval"); p->announceReceiptTimeout = config_get_int(cfg, p->name, "announceReceiptTimeout"); p->syncReceiptTimeout = config_get_int(cfg, p->name, "syncReceiptTimeout"); p->transportSpecific = config_get_int(cfg, p->name, "transportSpecific"); p->transportSpecific <<= 4; p->logSyncInterval = config_get_int(cfg, p->name, "logSyncInterval"); p->logMinPdelayReqInterval = config_get_int(cfg, p->name, "logMinPdelayReqInterval"); p->neighborPropDelayThresh = config_get_int(cfg, p->name, "neighborPropDelayThresh"); p->min_neighbor_prop_delay = config_get_int(cfg, p->name, "min_neighbor_prop_delay"); for (i = 0; i < N_TIMER_FDS; i++) { fd[i] = -1; } for (i = 0; i < N_TIMER_FDS; i++) { fd[i] = timerfd_create(CLOCK_MONOTONIC, 0); if (fd[i] < 0) { pr_err("timerfd_create: %s", strerror(errno)); goto no_timers; } } if (transport_open(p->trp, p->name, &p->fda, p->timestamping)) goto no_tropen; for (i = 0; i < N_TIMER_FDS; i++) { p->fda.fd[FD_ANNOUNCE_TIMER + i] = fd[i]; } if (port_set_announce_tmo(p)) goto no_tmo; port_nrate_initialize(p); clock_fda_changed(p->clock); return 0; no_tmo: transport_close(p->trp, &p->fda); no_tropen: no_timers: for (i = 0; i < N_TIMER_FDS; i++) { if (fd[i] >= 0) close(fd[i]); } return -1; } static int port_renew_transport(struct port *p) { int res; if (!port_is_enabled(p)) { return 0; } transport_close(p->trp, &p->fda); port_clear_fda(p, FD_ANNOUNCE_TIMER); res = transport_open(p->trp, p->name, &p->fda, p->timestamping); /* Need to call clock_fda_changed even if transport_open failed in * order to update clock to the now closed descriptors. */ clock_fda_changed(p->clock); return res; } /* * Returns non-zero if the announce message is different than last. */ static int update_current_master(struct port *p, struct ptp_message *m) { struct foreign_clock *fc = p->best; struct ptp_message *tmp; struct parent_ds *dad; struct path_trace_tlv *ptt; struct timePropertiesDS tds; if (!msg_source_equal(m, fc)) return add_foreign_master(p, m); if (p->state != PS_PASSIVE) { tds.currentUtcOffset = m->announce.currentUtcOffset; tds.flags = m->header.flagField[1]; tds.timeSource = m->announce.timeSource; clock_update_time_properties(p->clock, tds); } if (p->path_trace_enabled) { ptt = (struct path_trace_tlv *) m->announce.suffix; dad = clock_parent_ds(p->clock); memcpy(dad->ptl, ptt->cid, ptt->length); dad->path_length = path_length(ptt); } port_set_announce_tmo(p); fc_prune(fc); msg_get(m); fc->n_messages++; TAILQ_INSERT_HEAD(&fc->messages, m, list); if (fc->n_messages > 1) { tmp = TAILQ_NEXT(m, list); return announce_compare(m, tmp); } return 0; } struct dataset *port_best_foreign(struct port *port) { return port->best ? &port->best->dataset : NULL; } /* message processing routines */ /* * Returns non-zero if the announce message is both qualified and different. */ static int process_announce(struct port *p, struct ptp_message *m) { int result = 0; /* Do not qualify announce messages with stepsRemoved >= 255, see * IEEE1588-2008 section 9.3.2.5 (d) */ if (m->announce.stepsRemoved >= 255) return result; switch (p->state) { case PS_INITIALIZING: case PS_FAULTY: case PS_DISABLED: break; case PS_LISTENING: case PS_PRE_MASTER: case PS_MASTER: case PS_GRAND_MASTER: result = add_foreign_master(p, m); break; case PS_PASSIVE: case PS_UNCALIBRATED: case PS_SLAVE: result = update_current_master(p, m); break; } return result; } static int process_delay_req(struct port *p, struct ptp_message *m) { struct ptp_message *msg; int err; if (p->state != PS_MASTER && p->state != PS_GRAND_MASTER) return 0; if (p->delayMechanism == DM_P2P) { pr_warning("port %hu: delay request on P2P port", portnum(p)); return 0; } msg = msg_allocate(); if (!msg) return -1; msg->hwts.type = p->timestamping; msg->header.tsmt = DELAY_RESP | p->transportSpecific; msg->header.ver = PTP_VERSION; msg->header.messageLength = sizeof(struct delay_resp_msg); msg->header.domainNumber = m->header.domainNumber; msg->header.correction = m->header.correction; msg->header.sourcePortIdentity = p->portIdentity; msg->header.sequenceId = m->header.sequenceId; msg->header.control = CTL_DELAY_RESP; msg->header.logMessageInterval = p->logMinDelayReqInterval; ts_to_timestamp(&m->hwts.ts, &msg->delay_resp.receiveTimestamp); msg->delay_resp.requestingPortIdentity = m->header.sourcePortIdentity; if (p->hybrid_e2e && m->header.flagField[0] & UNICAST) { msg->address = m->address; msg->header.flagField[0] |= UNICAST; msg->header.logMessageInterval = 0x7f; } err = port_prepare_and_send(p, msg, 0); if (err) pr_err("port %hu: send delay response failed", portnum(p)); msg_put(msg); return err; } static void process_delay_resp(struct port *p, struct ptp_message *m) { struct delay_req_msg *req; struct delay_resp_msg *rsp = &m->delay_resp; struct PortIdentity master; tmv_t c3, t3, t4, t4c; if (!p->delay_req) return; master = clock_parent_identity(p->clock); req = &p->delay_req->delay_req; if (p->state != PS_UNCALIBRATED && p->state != PS_SLAVE) return; if (!pid_eq(&rsp->requestingPortIdentity, &req->hdr.sourcePortIdentity)) return; if (rsp->hdr.sequenceId != ntohs(req->hdr.sequenceId)) return; if (!pid_eq(&master, &m->header.sourcePortIdentity)) return; c3 = correction_to_tmv(m->header.correction); t3 = timespec_to_tmv(p->delay_req->hwts.ts); t4 = timestamp_to_tmv(m->ts.pdu); t4c = tmv_sub(t4, c3); clock_path_delay(p->clock, t3, t4c); if (p->logMinDelayReqInterval == rsp->hdr.logMessageInterval) { return; } if (m->header.flagField[0] & UNICAST) { /* Unicast responses have logMinDelayReqInterval set to 0x7F. */ return; } if (rsp->hdr.logMessageInterval < -10 || rsp->hdr.logMessageInterval > 22) { pl_info(300, "port %hu: ignore bogus delay request interval 2^%d", portnum(p), rsp->hdr.logMessageInterval); return; } p->logMinDelayReqInterval = rsp->hdr.logMessageInterval; pr_notice("port %hu: minimum delay request interval 2^%d", portnum(p), p->logMinDelayReqInterval); } static void process_follow_up(struct port *p, struct ptp_message *m) { enum syfu_event event; struct PortIdentity master; switch (p->state) { case PS_INITIALIZING: case PS_FAULTY: case PS_DISABLED: case PS_LISTENING: case PS_PRE_MASTER: case PS_MASTER: case PS_GRAND_MASTER: case PS_PASSIVE: return; case PS_UNCALIBRATED: case PS_SLAVE: break; } master = clock_parent_identity(p->clock); if (memcmp(&master, &m->header.sourcePortIdentity, sizeof(master))) return; if (p->follow_up_info) { struct follow_up_info_tlv *fui = follow_up_info_extract(m); if (!fui) return; clock_follow_up_info(p->clock, fui); } if (p->syfu == SF_HAVE_SYNC && p->last_syncfup->header.sequenceId == m->header.sequenceId) { event = FUP_MATCH; } else { event = FUP_MISMATCH; } port_syfufsm(p, event, m); } static int process_pdelay_req(struct port *p, struct ptp_message *m) { struct ptp_message *rsp, *fup; int err; if (p->delayMechanism == DM_E2E) { pr_warning("port %hu: pdelay_req on E2E port", portnum(p)); return 0; } if (p->delayMechanism == DM_AUTO) { pr_info("port %hu: peer detected, switch to P2P", portnum(p)); p->delayMechanism = DM_P2P; port_set_delay_tmo(p); } if (p->peer_portid_valid) { if (!pid_eq(&p->peer_portid, &m->header.sourcePortIdentity)) { pr_err("port %hu: received pdelay_req msg with " "unexpected peer port id %s", portnum(p), pid2str(&m->header.sourcePortIdentity)); p->peer_portid_valid = 0; port_capable(p); } } else { p->peer_portid_valid = 1; p->peer_portid = m->header.sourcePortIdentity; pr_debug("port %hu: peer port id set to %s", portnum(p), pid2str(&p->peer_portid)); } rsp = msg_allocate(); if (!rsp) return -1; fup = msg_allocate(); if (!fup) { msg_put(rsp); return -1; } rsp->hwts.type = p->timestamping; rsp->header.tsmt = PDELAY_RESP | p->transportSpecific; rsp->header.ver = PTP_VERSION; rsp->header.messageLength = sizeof(struct pdelay_resp_msg); rsp->header.domainNumber = m->header.domainNumber; rsp->header.sourcePortIdentity = p->portIdentity; rsp->header.sequenceId = m->header.sequenceId; rsp->header.control = CTL_OTHER; rsp->header.logMessageInterval = 0x7f; /* * NB - There is no kernel support for one step P2P messaging, * so we always send a follow up message. */ rsp->header.flagField[0] |= TWO_STEP; /* * NB - We do not have any fraction nanoseconds for the correction * fields, neither in the response or the follow up. */ ts_to_timestamp(&m->hwts.ts, &rsp->pdelay_resp.requestReceiptTimestamp); rsp->pdelay_resp.requestingPortIdentity = m->header.sourcePortIdentity; fup->hwts.type = p->timestamping; fup->header.tsmt = PDELAY_RESP_FOLLOW_UP | p->transportSpecific; fup->header.ver = PTP_VERSION; fup->header.messageLength = sizeof(struct pdelay_resp_fup_msg); fup->header.domainNumber = m->header.domainNumber; fup->header.correction = m->header.correction; fup->header.sourcePortIdentity = p->portIdentity; fup->header.sequenceId = m->header.sequenceId; fup->header.control = CTL_OTHER; fup->header.logMessageInterval = 0x7f; fup->pdelay_resp_fup.requestingPortIdentity = m->header.sourcePortIdentity; err = peer_prepare_and_send(p, rsp, 1); if (err) { pr_err("port %hu: send peer delay response failed", portnum(p)); goto out; } if (msg_sots_missing(rsp)) { pr_err("missing timestamp on transmitted peer delay response"); goto out; } ts_to_timestamp(&rsp->hwts.ts, &fup->pdelay_resp_fup.responseOriginTimestamp); err = peer_prepare_and_send(p, fup, 0); if (err) pr_err("port %hu: send pdelay_resp_fup failed", portnum(p)); out: msg_put(rsp); msg_put(fup); return err; } static void port_peer_delay(struct port *p) { tmv_t c1, c2, t1, t2, t3, t3c, t4; struct ptp_message *req = p->peer_delay_req; struct ptp_message *rsp = p->peer_delay_resp; struct ptp_message *fup = p->peer_delay_fup; /* Check for response, validate port and sequence number. */ if (!rsp) return; if (!pid_eq(&rsp->pdelay_resp.requestingPortIdentity, &p->portIdentity)) return; if (rsp->header.sequenceId != ntohs(req->header.sequenceId)) return; t1 = timespec_to_tmv(req->hwts.ts); t4 = timespec_to_tmv(rsp->hwts.ts); c1 = correction_to_tmv(rsp->header.correction + p->asymmetry); /* Process one-step response immediately. */ if (one_step(rsp)) { t2 = tmv_zero(); t3 = tmv_zero(); c2 = tmv_zero(); goto calc; } /* Check for follow up, validate port and sequence number. */ if (!fup) return; if (!pid_eq(&fup->pdelay_resp_fup.requestingPortIdentity, &p->portIdentity)) return; if (fup->header.sequenceId != rsp->header.sequenceId) return; if (!source_pid_eq(fup, rsp)) return; /* Process follow up response. */ t2 = timestamp_to_tmv(rsp->ts.pdu); t3 = timestamp_to_tmv(fup->ts.pdu); c2 = correction_to_tmv(fup->header.correction); calc: t3c = tmv_add(t3, tmv_add(c1, c2)); tsproc_set_clock_rate_ratio(p->tsproc, p->nrate.ratio * clock_rate_ratio(p->clock)); tsproc_up_ts(p->tsproc, t1, t2); tsproc_down_ts(p->tsproc, t3c, t4); if (tsproc_update_delay(p->tsproc, &p->peer_delay)) return; p->peerMeanPathDelay = tmv_to_TimeInterval(p->peer_delay); if (p->follow_up_info) port_nrate_calculate(p, t3c, t4); if (p->state == PS_UNCALIBRATED || p->state == PS_SLAVE) { clock_peer_delay(p->clock, p->peer_delay, t1, t2, p->nrate.ratio); } msg_put(p->peer_delay_req); p->peer_delay_req = NULL; } static int process_pdelay_resp(struct port *p, struct ptp_message *m) { if (p->peer_delay_resp) { if (!source_pid_eq(p->peer_delay_resp, m)) { pr_err("port %hu: multiple peer responses", portnum(p)); if (!p->multiple_pdr_detected) { p->multiple_pdr_detected = 1; p->multiple_seq_pdr_count++; } if (p->multiple_seq_pdr_count >= 3) { p->last_fault_type = FT_BAD_PEER_NETWORK; return -1; } } } if (!p->peer_delay_req) { pr_err("port %hu: rogue peer delay response", portnum(p)); return -1; } if (p->peer_portid_valid) { if (!pid_eq(&p->peer_portid, &m->header.sourcePortIdentity)) { pr_err("port %hu: received pdelay_resp msg with " "unexpected peer port id %s", portnum(p), pid2str(&m->header.sourcePortIdentity)); p->peer_portid_valid = 0; port_capable(p); } } else { p->peer_portid_valid = 1; p->peer_portid = m->header.sourcePortIdentity; pr_debug("port %hu: peer port id set to %s", portnum(p), pid2str(&p->peer_portid)); } if (p->peer_delay_resp) { msg_put(p->peer_delay_resp); } msg_get(m); p->peer_delay_resp = m; port_peer_delay(p); return 0; } static void process_pdelay_resp_fup(struct port *p, struct ptp_message *m) { if (!p->peer_delay_req) return; if (p->peer_delay_fup) msg_put(p->peer_delay_fup); msg_get(m); p->peer_delay_fup = m; port_peer_delay(p); } static void process_sync(struct port *p, struct ptp_message *m) { enum syfu_event event; struct PortIdentity master; switch (p->state) { case PS_INITIALIZING: case PS_FAULTY: case PS_DISABLED: case PS_LISTENING: case PS_PRE_MASTER: case PS_MASTER: case PS_GRAND_MASTER: case PS_PASSIVE: return; case PS_UNCALIBRATED: case PS_SLAVE: break; } master = clock_parent_identity(p->clock); if (memcmp(&master, &m->header.sourcePortIdentity, sizeof(master))) { return; } if (m->header.logMessageInterval != p->log_sync_interval) { p->log_sync_interval = m->header.logMessageInterval; clock_sync_interval(p->clock, p->log_sync_interval); } m->header.correction += p->asymmetry; if (one_step(m)) { port_synchronize(p, m->hwts.ts, m->ts.pdu, m->header.correction, 0); flush_last_sync(p); return; } if (p->syfu == SF_HAVE_FUP && fup_sync_ok(p->last_syncfup, m) && p->last_syncfup->header.sequenceId == m->header.sequenceId) { event = SYNC_MATCH; } else { event = SYNC_MISMATCH; } port_syfufsm(p, event, m); } /* public methods */ void port_close(struct port *p) { if (port_is_enabled(p)) { port_disable(p); } transport_destroy(p->trp); tsproc_destroy(p->tsproc); if (p->fault_fd >= 0) close(p->fault_fd); free(p); } struct foreign_clock *port_compute_best(struct port *p) { struct foreign_clock *fc; struct ptp_message *tmp; p->best = NULL; LIST_FOREACH(fc, &p->foreign_masters, list) { tmp = TAILQ_FIRST(&fc->messages); if (!tmp) continue; announce_to_dataset(tmp, p->clock, &fc->dataset); fc_prune(fc); if (fc->n_messages < FOREIGN_MASTER_THRESHOLD) continue; if (!p->best) p->best = fc; else if (dscmp(&fc->dataset, &p->best->dataset) > 0) p->best = fc; else fc_clear(fc); } return p->best; } static void port_e2e_transition(struct port *p, enum port_state next) { port_clr_tmo(p->fda.fd[FD_ANNOUNCE_TIMER]); port_clr_tmo(p->fda.fd[FD_SYNC_RX_TIMER]); port_clr_tmo(p->fda.fd[FD_DELAY_TIMER]); port_clr_tmo(p->fda.fd[FD_QUALIFICATION_TIMER]); port_clr_tmo(p->fda.fd[FD_MANNO_TIMER]); port_clr_tmo(p->fda.fd[FD_SYNC_TX_TIMER]); switch (next) { case PS_INITIALIZING: break; case PS_FAULTY: case PS_DISABLED: port_disable(p); break; case PS_LISTENING: port_set_announce_tmo(p); break; case PS_PRE_MASTER: port_set_qualification_tmo(p); break; case PS_MASTER: case PS_GRAND_MASTER: set_tmo_log(p->fda.fd[FD_MANNO_TIMER], 1, -10); /*~1ms*/ port_set_sync_tx_tmo(p); break; case PS_PASSIVE: port_set_announce_tmo(p); break; case PS_UNCALIBRATED: flush_last_sync(p); flush_delay_req(p); /* fall through */ case PS_SLAVE: port_set_announce_tmo(p); port_set_delay_tmo(p); break; }; } static void port_p2p_transition(struct port *p, enum port_state next) { port_clr_tmo(p->fda.fd[FD_ANNOUNCE_TIMER]); port_clr_tmo(p->fda.fd[FD_SYNC_RX_TIMER]); /* Leave FD_DELAY_TIMER running. */ port_clr_tmo(p->fda.fd[FD_QUALIFICATION_TIMER]); port_clr_tmo(p->fda.fd[FD_MANNO_TIMER]); port_clr_tmo(p->fda.fd[FD_SYNC_TX_TIMER]); switch (next) { case PS_INITIALIZING: break; case PS_FAULTY: case PS_DISABLED: port_disable(p); break; case PS_LISTENING: port_set_announce_tmo(p); break; case PS_PRE_MASTER: port_set_qualification_tmo(p); break; case PS_MASTER: case PS_GRAND_MASTER: set_tmo_log(p->fda.fd[FD_MANNO_TIMER], 1, -10); /*~1ms*/ port_set_sync_tx_tmo(p); break; case PS_PASSIVE: port_set_announce_tmo(p); break; case PS_UNCALIBRATED: flush_last_sync(p); flush_peer_delay(p); /* fall through */ case PS_SLAVE: port_set_announce_tmo(p); break; }; } int port_dispatch(struct port *p, enum fsm_event event, int mdiff) { enum port_state next; struct fault_interval i; int fri_asap = 0; if (clock_slave_only(p->clock)) { if (event == EV_RS_MASTER || event == EV_RS_GRAND_MASTER) { port_slave_priority_warning(p); } next = ptp_slave_fsm(p->state, event, mdiff); } else { next = ptp_fsm(p->state, event, mdiff); } if (!fault_interval(p, last_fault_type(p), &i) && ((i.val == FRI_ASAP && i.type == FTMO_LOG2_SECONDS) || (i.val == 0 && i.type == FTMO_LINEAR_SECONDS))) fri_asap = 1; if (PS_INITIALIZING == next || (PS_FAULTY == next && fri_asap)) { /* * This is a special case. Since we initialize the * port immediately, we can skip right to listening * state if all goes well. */ if (port_is_enabled(p)) { port_disable(p); } next = port_initialize(p) ? PS_FAULTY : PS_LISTENING; port_show_transition(p, next, event); p->state = next; if (next == PS_LISTENING && p->delayMechanism == DM_P2P) { port_set_delay_tmo(p); } port_notify_event(p, NOTIFY_PORT_STATE); return 1; } if (next == p->state) return 0; port_show_transition(p, next, event); if (p->delayMechanism == DM_P2P) { port_p2p_transition(p, next); } else { port_e2e_transition(p, next); } p->state = next; port_notify_event(p, NOTIFY_PORT_STATE); if (p->jbod && next == PS_UNCALIBRATED) { if (clock_switch_phc(p->clock, p->phc_index)) { p->last_fault_type = FT_SWITCH_PHC; return port_dispatch(p, EV_FAULT_DETECTED, 0); } clock_sync_interval(p->clock, p->log_sync_interval); } return 0; } enum fsm_event port_event(struct port *p, int fd_index) { enum fsm_event event = EV_NONE; struct ptp_message *msg; int cnt, fd = p->fda.fd[fd_index], err; switch (fd_index) { case FD_ANNOUNCE_TIMER: case FD_SYNC_RX_TIMER: pr_debug("port %hu: %s timeout", portnum(p), fd_index == FD_SYNC_RX_TIMER ? "rx sync" : "announce"); if (p->best) fc_clear(p->best); port_set_announce_tmo(p); if (clock_slave_only(p->clock) && p->delayMechanism != DM_P2P && port_renew_transport(p)) { return EV_FAULT_DETECTED; } return EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES; case FD_DELAY_TIMER: pr_debug("port %hu: delay timeout", portnum(p)); port_set_delay_tmo(p); return port_delay_request(p) ? EV_FAULT_DETECTED : EV_NONE; case FD_QUALIFICATION_TIMER: pr_debug("port %hu: qualification timeout", portnum(p)); return EV_QUALIFICATION_TIMEOUT_EXPIRES; case FD_MANNO_TIMER: pr_debug("port %hu: master tx announce timeout", portnum(p)); port_set_manno_tmo(p); return port_tx_announce(p) ? EV_FAULT_DETECTED : EV_NONE; case FD_SYNC_TX_TIMER: pr_debug("port %hu: master sync timeout", portnum(p)); port_set_sync_tx_tmo(p); return port_tx_sync(p) ? EV_FAULT_DETECTED : EV_NONE; } msg = msg_allocate(); if (!msg) return EV_FAULT_DETECTED; msg->hwts.type = p->timestamping; cnt = transport_recv(p->trp, fd, msg); if (cnt <= 0) { pr_err("port %hu: recv message failed", portnum(p)); msg_put(msg); return EV_FAULT_DETECTED; } err = msg_post_recv(msg, cnt); if (err) { switch (err) { case -EBADMSG: pr_err("port %hu: bad message", portnum(p)); break; case -ETIME: pr_err("port %hu: received %s without timestamp", portnum(p), msg_type_string(msg_type(msg))); break; case -EPROTO: pr_debug("port %hu: ignoring message", portnum(p)); break; } msg_put(msg); return EV_NONE; } if (msg_sots_valid(msg)) { ts_add(&msg->hwts.ts, -p->rx_timestamp_offset); clock_check_ts(p->clock, msg->hwts.ts); } if (port_ignore(p, msg)) { msg_put(msg); return EV_NONE; } switch (msg_type(msg)) { case SYNC: process_sync(p, msg); break; case DELAY_REQ: if (process_delay_req(p, msg)) event = EV_FAULT_DETECTED; break; case PDELAY_REQ: if (process_pdelay_req(p, msg)) event = EV_FAULT_DETECTED; break; case PDELAY_RESP: if (process_pdelay_resp(p, msg)) event = EV_FAULT_DETECTED; break; case FOLLOW_UP: process_follow_up(p, msg); break; case DELAY_RESP: process_delay_resp(p, msg); break; case PDELAY_RESP_FOLLOW_UP: process_pdelay_resp_fup(p, msg); break; case ANNOUNCE: if (process_announce(p, msg)) event = EV_STATE_DECISION_EVENT; break; case SIGNALING: break; case MANAGEMENT: if (clock_manage(p->clock, p, msg)) event = EV_STATE_DECISION_EVENT; break; } msg_put(msg); return event; } int port_forward(struct port *p, struct ptp_message *msg) { int cnt; cnt = transport_send(p->trp, &p->fda, 0, msg); return cnt <= 0 ? -1 : 0; } int port_forward_to(struct port *p, struct ptp_message *msg) { int cnt; cnt = transport_sendto(p->trp, &p->fda, 0, msg); return cnt <= 0 ? -1 : 0; } int port_prepare_and_send(struct port *p, struct ptp_message *msg, int event) { int cnt; if (msg_pre_send(msg)) return -1; if (msg->header.flagField[0] & UNICAST) { cnt = transport_sendto(p->trp, &p->fda, event, msg); } else { cnt = transport_send(p->trp, &p->fda, event, msg); } if (cnt <= 0) { return -1; } if (msg_sots_valid(msg)) { ts_add(&msg->hwts.ts, p->tx_timestamp_offset); } return 0; } struct PortIdentity port_identity(struct port *p) { return p->portIdentity; } int port_number(struct port *p) { return portnum(p); } int port_manage(struct port *p, struct port *ingress, struct ptp_message *msg) { struct management_tlv *mgt; UInteger16 target = msg->management.targetPortIdentity.portNumber; if (target != portnum(p) && target != 0xffff) { return 0; } mgt = (struct management_tlv *) msg->management.suffix; switch (management_action(msg)) { case GET: if (port_management_get_response(p, ingress, mgt->id, msg)) return 1; break; case SET: if (port_management_set(p, ingress, mgt->id, msg)) return 1; break; case COMMAND: break; default: return -1; } switch (mgt->id) { case TLV_NULL_MANAGEMENT: case TLV_CLOCK_DESCRIPTION: case TLV_PORT_DATA_SET: case TLV_LOG_ANNOUNCE_INTERVAL: case TLV_ANNOUNCE_RECEIPT_TIMEOUT: case TLV_LOG_SYNC_INTERVAL: case TLV_VERSION_NUMBER: case TLV_ENABLE_PORT: case TLV_DISABLE_PORT: case TLV_UNICAST_NEGOTIATION_ENABLE: case TLV_UNICAST_MASTER_TABLE: case TLV_UNICAST_MASTER_MAX_TABLE_SIZE: case TLV_ACCEPTABLE_MASTER_TABLE_ENABLED: case TLV_ALTERNATE_MASTER: case TLV_TRANSPARENT_CLOCK_PORT_DATA_SET: case TLV_DELAY_MECHANISM: case TLV_LOG_MIN_PDELAY_REQ_INTERVAL: port_management_send_error(p, ingress, msg, TLV_NOT_SUPPORTED); break; default: port_management_send_error(p, ingress, msg, TLV_NO_SUCH_ID); return -1; } return 1; } int port_management_error(struct PortIdentity pid, struct port *ingress, struct ptp_message *req, Enumeration16 error_id) { struct ptp_message *msg; struct management_tlv *mgt; struct management_error_status *mes; int err = 0, pdulen; msg = port_management_reply(pid, ingress, req); if (!msg) { return -1; } mgt = (struct management_tlv *) req->management.suffix; mes = (struct management_error_status *) msg->management.suffix; mes->type = TLV_MANAGEMENT_ERROR_STATUS; mes->length = 8; mes->error = error_id; mes->id = mgt->id; pdulen = msg->header.messageLength + sizeof(*mes); msg->header.messageLength = pdulen; msg->tlv_count = 1; err = port_prepare_and_send(ingress, msg, 0); msg_put(msg); return err; } static struct ptp_message * port_management_construct(struct PortIdentity pid, struct port *ingress, UInteger16 sequenceId, struct PortIdentity *targetPortIdentity, UInteger8 boundaryHops, uint8_t action) { struct ptp_message *msg; int pdulen; msg = msg_allocate(); if (!msg) return NULL; pdulen = sizeof(struct management_msg); msg->hwts.type = ingress->timestamping; msg->header.tsmt = MANAGEMENT | ingress->transportSpecific; msg->header.ver = PTP_VERSION; msg->header.messageLength = pdulen; msg->header.domainNumber = clock_domain_number(ingress->clock); msg->header.sourcePortIdentity = pid; msg->header.sequenceId = sequenceId; msg->header.control = CTL_MANAGEMENT; msg->header.logMessageInterval = 0x7f; if (targetPortIdentity) msg->management.targetPortIdentity = *targetPortIdentity; msg->management.startingBoundaryHops = boundaryHops; msg->management.boundaryHops = boundaryHops; switch (action) { case GET: case SET: msg->management.flags = RESPONSE; break; case COMMAND: msg->management.flags = ACKNOWLEDGE; break; } return msg; } struct ptp_message *port_management_reply(struct PortIdentity pid, struct port *ingress, struct ptp_message *req) { UInteger8 boundaryHops; boundaryHops = req->management.startingBoundaryHops - req->management.boundaryHops; return port_management_construct(pid, ingress, req->header.sequenceId, &req->header.sourcePortIdentity, boundaryHops, management_action(req)); } struct ptp_message *port_management_notify(struct PortIdentity pid, struct port *port) { return port_management_construct(pid, port, 0, NULL, 1, GET); } void port_notify_event(struct port *p, enum notification event) { struct PortIdentity pid = port_identity(p); struct ptp_message *msg; UInteger16 msg_len; int id; switch (event) { case NOTIFY_PORT_STATE: id = TLV_PORT_DATA_SET; break; default: return; } /* targetPortIdentity and sequenceId will be filled by * clock_send_notification */ msg = port_management_notify(pid, p); if (!msg) return; if (!port_management_fill_response(p, msg, id)) goto err; msg_len = msg->header.messageLength; if (msg_pre_send(msg)) goto err; clock_send_notification(p->clock, msg, msg_len, event); err: msg_put(msg); } struct port *port_open(int phc_index, enum timestamp_type timestamping, int number, struct interface *interface, struct clock *clock) { struct config *cfg = clock_config(clock); struct port *p = malloc(sizeof(*p)); enum transport_type transport; int i; if (!p) return NULL; memset(p, 0, sizeof(*p)); p->phc_index = phc_index; p->jbod = config_get_int(cfg, interface->name, "boundary_clock_jbod"); transport = config_get_int(cfg, interface->name, "network_transport"); if (transport == TRANS_UDS) ; /* UDS cannot have a PHC. */ else if (!interface->ts_info.valid) pr_warning("port %d: get_ts_info not supported", number); else if (phc_index >= 0 && phc_index != interface->ts_info.phc_index) { if (p->jbod) { pr_warning("port %d: just a bunch of devices", number); p->phc_index = interface->ts_info.phc_index; } else { pr_err("port %d: PHC device mismatch", number); pr_err("port %d: /dev/ptp%d requested, ptp%d attached", number, phc_index, interface->ts_info.phc_index); goto err_port; } } p->name = interface->name; p->asymmetry = config_get_int(cfg, p->name, "delayAsymmetry"); p->asymmetry <<= 16; p->follow_up_info = config_get_int(cfg, p->name, "follow_up_info"); p->freq_est_interval = config_get_int(cfg, p->name, "freq_est_interval"); p->hybrid_e2e = config_get_int(cfg, p->name, "hybrid_e2e"); p->path_trace_enabled = config_get_int(cfg, p->name, "path_trace_enabled"); p->rx_timestamp_offset = config_get_int(cfg, p->name, "ingressLatency"); p->tx_timestamp_offset = config_get_int(cfg, p->name, "egressLatency"); p->clock = clock; p->trp = transport_create(cfg, transport); if (!p->trp) goto err_port; p->timestamping = timestamping; p->portIdentity.clockIdentity = clock_identity(clock); p->portIdentity.portNumber = number; p->state = PS_INITIALIZING; p->delayMechanism = config_get_int(cfg, p->name, "delay_mechanism"); p->versionNumber = PTP_VERSION; if (p->hybrid_e2e && p->delayMechanism != DM_E2E) { pr_warning("port %d: hybrid_e2e only works with E2E", number); } /* Set fault timeouts to a default value */ for (i = 0; i < FT_CNT; i++) { p->flt_interval_pertype[i].type = FTMO_LOG2_SECONDS; p->flt_interval_pertype[i].val = 4; } p->flt_interval_pertype[FT_BAD_PEER_NETWORK].type = FTMO_LINEAR_SECONDS; p->flt_interval_pertype[FT_BAD_PEER_NETWORK].val = config_get_int(cfg, p->name, "fault_badpeernet_interval"); p->flt_interval_pertype[FT_UNSPECIFIED].val = config_get_int(cfg, p->name, "fault_reset_interval"); p->tsproc = tsproc_create(config_get_int(cfg, p->name, "tsproc_mode"), config_get_int(cfg, p->name, "delay_filter"), config_get_int(cfg, p->name, "delay_filter_length")); if (!p->tsproc) { pr_err("Failed to create time stamp processor"); goto err_transport; } p->nrate.ratio = 1.0; port_clear_fda(p, N_POLLFD); p->fault_fd = -1; if (number) { p->fault_fd = timerfd_create(CLOCK_MONOTONIC, 0); if (p->fault_fd < 0) { pr_err("timerfd_create failed: %m"); goto err_tsproc; } } return p; err_tsproc: tsproc_destroy(p->tsproc); err_transport: transport_destroy(p->trp); err_port: free(p); return NULL; } enum port_state port_state(struct port *port) { return port->state; } linuxptp-1.6/port.h000066400000000000000000000251271257727010700144170ustar00rootroot00000000000000/** * @file port.h * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_PORT_H #define HAVE_PORT_H #include "dm.h" #include "fd.h" #include "foreign.h" #include "fsm.h" #include "notification.h" #include "transport.h" /* forward declarations */ struct interface; struct clock; /** Opaque type. */ struct port; /** * Returns the dataset from a port's best foreign clock record, if any * has yet been discovered. This function does not bring the returned * dataset up to date, so the caller should invoke port_compute_best() * beforehand. * * @param port A pointer previously obtained via port_open(). * @return A pointer to a dataset, or NULL. */ struct dataset *port_best_foreign(struct port *port); /** * Close a port and free its associated resources. After this call * returns, @a port is no longer a valid port instance. * * @param port A pointer previously obtained via port_open(). */ void port_close(struct port *port); /** * Computes the 'best' foreign master discovered on a port. This has * the side effect of updating the 'dataset' field of the returned * foreign master. * * @param port A pointer previously obtained via port_open(). * @return A pointer to the port's best foreign master, or NULL. */ struct foreign_clock *port_compute_best(struct port *port); /** * Dispatch a port event. This may cause a state transition on the * port, with the associated side effect. * * @param port A pointer previously obtained via port_open(). * @param event One of the @a fsm_event codes. * @param mdiff Whether a new master has been selected. * @return Zero if the port's file descriptor array is still valid, * and non-zero if it has become invalid. */ int port_dispatch(struct port *p, enum fsm_event event, int mdiff); /** * Generates state machine events based on activity on a port's file * descriptors. * * @param port A pointer previously obtained via port_open(). * @param fd_index The index of the active file descriptor. * @return One of the @a fsm_event codes. */ enum fsm_event port_event(struct port *port, int fd_index); /** * Forward a message on a given port. * @param port A pointer previously obtained via port_open(). * @param msg The message to send. Must be in network byte order. * @return Zero on success, non-zero otherwise. */ int port_forward(struct port *p, struct ptp_message *msg); /** * Forward a message on a given port to the address stored in the message. * @param port A pointer previously obtained via port_open(). * @param msg The message to send. Must be in network byte order. * @return Zero on success, non-zero otherwise. */ int port_forward_to(struct port *p, struct ptp_message *msg); /** * Prepare message for transmission and send it to a given port. Note that * a single message cannot be sent several times using this function, that * would lead to corrupted data being sent. Use msg_pre_send and * port_forward if you need to send single message to several ports. * @param p A pointer previously obtained via port_open(). * @param msg The message to send. * @param event 0 if the message is a general message, 1 if it is an * event message. */ int port_prepare_and_send(struct port *p, struct ptp_message *msg, int event); /** * Obtain a port's identity. * @param p A pointer previously obtained via port_open(). * @return The port identity of 'p'. */ struct PortIdentity port_identity(struct port *p); /** * Obtain a port number. * @param p A port instance. * @return The port number of 'p'. */ int port_number(struct port *p); /** * Manage a port according to a given message. * @param p A pointer previously obtained via port_open(). * @param ingress The port on which 'msg' was received. * @param msg A management message. * @return 1 if the message was responded to, 0 if it did not apply * to the port, -1 if it was invalid. */ int port_manage(struct port *p, struct port *ingress, struct ptp_message *msg); /** * Send a management error status message. * @param pid The id of the responding port. * @param ingress Port on which the 'req' was received. * @param req The management message which triggered the error. * @param error_id One of the management error ID values. * @return Zero on success, non-zero otherwise. */ int port_management_error(struct PortIdentity pid, struct port *ingress, struct ptp_message *req, Enumeration16 error_id); /** * Allocate a reply to a management message. * * Messages are reference counted, and newly allocated messages have a * reference count of one. Allocated messages are freed using the * function @ref msg_put(). * * @param pid The id of the responding port. * @param ingress The port on which 'req' was received. * @param req A management message. * @return Pointer to a message on success, NULL otherwise. */ struct ptp_message *port_management_reply(struct PortIdentity pid, struct port *ingress, struct ptp_message *req); /** * Allocate a standalone reply management message. * * See note in @ref port_management_reply description about freeing the * message. Also note that the constructed message does not have * targetPortIdentity and sequenceId filled. * * @param pid The id of the responding port. * @param port The port to which the message will be sent. * @return Pointer to a message on success, NULL otherwise. */ struct ptp_message *port_management_notify(struct PortIdentity pid, struct port *port); /** * Construct and send notification to subscribers about an event that * occured on the port. * @param p The port. * @param event The identification of the event. */ void port_notify_event(struct port *p, enum notification event); /** * Open a network port. * @param phc_index The PHC device index for the network device. * @param timestamping The timestamping mode for this port. * @param number An arbitrary number assigned to this port. * @param interface The interface data * @param clock A pointer to the system PTP clock. * @return A pointer to an open port on success, or NULL otherwise. */ struct port *port_open(int phc_index, enum timestamp_type timestamping, int number, struct interface *interface, struct clock *clock); /** * Returns a port's current state. * @param port A port instance. * @return One of the @ref port_state values. */ enum port_state port_state(struct port *port); /** * Return array of file descriptors for this port. The fault fd is not * included. * @param port A port instance * @return Array of file descriptors. Unused descriptors are guranteed * to be set to -1. */ struct fdarray *port_fda(struct port *port); /** * Return file descriptor of the port. * @param port A port instance. * @return File descriptor or -1 if not applicable. */ int port_fault_fd(struct port *port); /** * Utility function for setting or resetting a file descriptor timer. * * This function sets the timer 'fd' to the value M(2^N), where M is * the value of the 'scale' parameter and N in the value of the * 'log_seconds' parameter. * * Passing both 'scale' and 'log_seconds' as zero disables the timer. * * @param fd A file descriptor previously opened with timerfd_create(2). * @param scale The multiplicative factor for the timer. * @param log_seconds The exponential factor for the timer. * @return Zero on success, non-zero otherwise. */ int set_tmo_log(int fd, unsigned int scale, int log_seconds); /** * Utility function for setting a file descriptor timer. * * This function sets the timer 'fd' to a random value between M * 2^N and * (M + S) * 2^N, where M is the value of the 'min' parameter, S is the value * of the 'span' parameter, and N in the value of the 'log_seconds' parameter. * * @param fd A file descriptor previously opened with timerfd_create(2). * @param min The minimum value for the timer. * @param span The span value for the timer. Must be a positive value. * @param log_seconds The exponential factor for the timer. * @return Zero on success, non-zero otherwise. */ int set_tmo_random(int fd, int min, int span, int log_seconds); /** * Utility function for setting or resetting a file descriptor timer. * * This function sets the timer 'fd' to the value of the 'seconds' parameter. * * Passing 'seconds' as zero disables the timer. * * @param fd A file descriptor previously opened with timerfd_create(2). * @param seconds The timeout value for the timer. * @return Zero on success, non-zero otherwise. */ int set_tmo_lin(int fd, int seconds); /** * Sets port's fault file descriptor timer. * Passing both 'scale' and 'log_seconds' as zero disables the timer. * * @param fd A port instance. * @param scale The multiplicative factor for the timer. * @param log_seconds The exponential factor for the timer. * @return Zero on success, non-zero otherwise. */ int port_set_fault_timer_log(struct port *port, unsigned int scale, int log_seconds); /** * Sets port's fault file descriptor timer. * Passing 'seconds' as zero disables the timer. * * @param fd A port instance. * @param seconds The timeout value for the timer. * @return Zero on success, non-zero otherwise. */ int port_set_fault_timer_lin(struct port *port, int seconds); /** * Returns a port's last fault type. * * @param port A port instance. * @return One of the @ref fault_type values. */ enum fault_type last_fault_type(struct port *port); /** * Fills passed in struct fault_interval with the value associated to a * port and fault type. * * @param port A port instance. * @param ft Fault type. * @param i Pointer to the struct which will be filled in. * @return Zero on success, non-zero otherwise. */ int fault_interval(struct port *port, enum fault_type ft, struct fault_interval *i); #endif linuxptp-1.6/print.c000066400000000000000000000035061257727010700145570ustar00rootroot00000000000000/** * @file print.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include "print.h" static int verbose = 0; static int print_level = LOG_INFO; static int use_syslog = 1; static const char *progname; void print_set_progname(const char *name) { progname = name; } void print_set_syslog(int value) { use_syslog = value ? 1 : 0; } void print_set_level(int level) { print_level = level; } void print_set_verbose(int value) { verbose = value ? 1 : 0; } void print(int level, char const *format, ...) { struct timespec ts; va_list ap; char buf[1024]; FILE *f; if (level > print_level) return; clock_gettime(CLOCK_MONOTONIC, &ts); va_start(ap, format); vsnprintf(buf, sizeof(buf), format, ap); va_end(ap); if (verbose) { f = level >= LOG_NOTICE ? stdout : stderr; fprintf(f, "%s[%ld.%03ld]: %s\n", progname ? progname : "", ts.tv_sec, ts.tv_nsec / 1000000, buf); fflush(f); } if (use_syslog) { syslog(level, "[%ld.%03ld] %s", ts.tv_sec, ts.tv_nsec / 1000000, buf); } } linuxptp-1.6/print.h000066400000000000000000000042341257727010700145630ustar00rootroot00000000000000/** * @file print.h * @brief Logging support functions * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_PRINT_H #define HAVE_PRINT_H #include #define PRINT_LEVEL_MIN LOG_EMERG #define PRINT_LEVEL_MAX LOG_DEBUG #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))) #endif void print(int level, char const *format, ...); void print_set_progname(const char *name); void print_set_syslog(int value); void print_set_level(int level); void print_set_verbose(int value); #define pr_emerg(x...) print(LOG_EMERG, x) #define pr_alert(x...) print(LOG_ALERT, x) #define pr_crit(x...) print(LOG_CRIT, x) #define pr_err(x...) print(LOG_ERR, x) #define pr_warning(x...) print(LOG_WARNING, x) #define pr_notice(x...) print(LOG_NOTICE, x) #define pr_info(x...) print(LOG_INFO, x) #define pr_debug(x...) print(LOG_DEBUG, x) #define PRINT_RL(l, i, x...) \ do { \ static time_t last = -i; \ if (!rate_limited(i, &last)) \ print(l, x); \ } while (0); /* Rate limited versions */ #define pl_emerg(i, x...) PRINT_RL(LOG_EMERG, i, x) #define pl_alert(i, x...) PRINT_RL(LOG_ALERT, i, x) #define pl_crit(i, x...) PRINT_RL(LOG_CRIT, i, x) #define pl_err(i, x...) PRINT_RL(LOG_ERR, i, x) #define pl_warning(i, x...) PRINT_RL(LOG_WARNING, i, x) #define pl_notice(i, x...) PRINT_RL(LOG_NOTICE, i, x) #define pl_info(i, x...) PRINT_RL(LOG_INFO, i, x) #define pl_debug(i, x...) PRINT_RL(LOG_DEBUG, i, x) #endif linuxptp-1.6/ptp4l.8000066400000000000000000000441021257727010700144100ustar00rootroot00000000000000.TH PTP4l 8 "December 2014" "linuxptp" .SH NAME ptp4l - PTP Boundary/Ordinary Clock .SH SYNOPSIS .B ptp4l [ .B \-AEP246HSLmqsv ] [ .BI \-f " config" ] [ .BI \-p " phc-device" ] [ .BI \-l " print-level" ] [ .BI \-i " interface" ] .I .\|.\|. .SH DESCRIPTION .B ptp4l is an implementation of the Precision Time Protocol (PTP) according to IEEE standard 1588 for Linux. It implements Boundary Clock (BC) and Ordinary Clock (OC). .SH OPTIONS .TP .B \-A Select the delay mechanism automatically. Start with E2E and switch to P2P when a peer delay request is received. .TP .B \-E Select the delay request-response (E2E) mechanism. This is the default mechanism. All clocks on single PTP communication path must use the same mechanism. A warning will be printed when a peer delay request is received on port using the E2E mechanism. .TP .B \-P Select the peer delay (P2P) mechanism. A warning will be printed when a delay request is received on port using the P2P mechanism. .TP .B \-2 Select the IEEE 802.3 network transport. .TP .B \-4 Select the UDP IPv4 network transport. This is the default transport. .TP .B \-6 Select the UDP IPv6 network transport. .TP .B \-H Select the hardware time stamping. All ports specified by the .B \-i option and in the configuration file must be attached to the same PTP hardware clock (PHC). This is the default time stamping. .TP .B \-S Select the software time stamping. .TP .B \-L Select the legacy hardware time stamping. .TP .BI \-f " config" Read configuration from the specified file. No configuration file is read by default. .TP .BI \-i " interface" Specify a PTP port, it may be used multiple times. At least one port must be specified by this option or in the configuration file. .TP .BI \-p " phc-device" With hardware time stamping, force which PHC device (e.g. /dev/ptp0) should be used. .TP .B \-s Enable the slaveOnly mode. .TP .BI \-l " print-level" Set the maximum syslog level of messages which should be printed or sent to the system logger. The default is 6 (LOG_INFO). .TP .B \-m Print messages to the standard output. .TP .B \-q Don't send messages to the system logger. .TP .B \-v Prints the software version and exits. .TP .BI \-h Display a help message. .SH CONFIGURATION FILE The configuration file is divided into sections. Each section starts with a line containing its name enclosed in brackets and it follows with settings. Each setting is placed on a separate line, it contains the name of the option and the value separated by whitespace characters. Empty lines and lines starting with # are ignored. The global section (indicated as .BR [global] ) sets the program options, clock options and default port options. Other sections are port specific sections and they override the default port options. The name of the section is the name of the configured port (e.g. .BR [eth0] ). Ports specified in the configuration file don't need to be specified by the .B \-i option. An empty port section can be used to replace the command line option. .SH PORT OPTIONS .TP .B delayAsymmetry The time difference in nanoseconds of the transmit and receive paths. This value should be positive when the master-to-slave propagation time is longer and negative when the slave-to-master time is longer. The default is 0 nanoseconds. .TP .B logAnnounceInterval The mean time interval between Announce messages. A shorter interval makes ptp4l react faster to the changes in the master-slave hierarchy. The interval should be the same in the whole domain. It's specified as a power of two in seconds. The default is 1 (2 seconds). .TP .B logSyncInterval The mean time interval between Sync messages. A shorter interval may improve accuracy of the local clock. It's specified as a power of two in seconds. The default is 0 (1 second). .TP .B logMinDelayReqInterval The minimum permitted mean time interval between Delay_Req messages. A shorter interval makes ptp4l react faster to the changes in the path delay. It's specified as a power of two in seconds. The default is 0 (1 second). .TP .B logMinPdelayReqInterval The minimum permitted mean time interval between Pdelay_Req messages. It's specified as a power of two in seconds. The default is 0 (1 second). .TP .B announceReceiptTimeout The number of missed Announce messages before the last Announce messages expires. The default is 3. .TP .B syncReceiptTimeout The number of sync/follow up messages that may go missing before triggering a Best Master Clock election. This option is used for running in gPTP mode according to the 802.1AS-2011 standard. Setting this option to zero will disable the sync message timeout. The default is 0 or disabled. .TP .B transportSpecific The transport specific field. Must be in the range 0 to 255. The default is 0. .TP .B path_trace_enabled Enable the mechanism used to trace the route of the Announce messages. The default is 0 (disabled). .TP .B follow_up_info Include the 802.1AS data in the Follow_Up messages if enabled. The default is 0 (disabled). .TP .B fault_reset_interval The time in seconds between the detection of a port's fault and the fault being reset. This value is expressed as a power of two. Setting this value to \-128 or to the special key word "ASAP" will let the fault be reset immediately. The default is 4 (16 seconds). .TP .B fault_badpeernet_interval The time in seconds between the detection of a peer network misconfiguration and the fault being reset. The port is disabled for the duration of the interval. The value is in seconds and the special key word ASAP will let the fault be reset immediately. The default is 16 seconds. .TP .B delay_mechanism Select the delay mechanism. Possible values are E2E, P2P and Auto. The default is E2E. .TP .B hybrid_e2e Enables the "hybrid" delay mechanism from the draft Enterprise Profile. When enabled, ports in the slave state send their delay request messages to the unicast address taken from the master's announce message. Ports in the master state will reply to unicast delay requests using unicast delay responses. This option has no effect if the delay_mechanism is set to P2P. The default is 0 (disabled). .TP .B ptp_dst_mac The MAC address to which PTP messages should be sent. Relevant only with L2 transport. The default is 01:1B:19:00:00:00. .TP .B p2p_dst_mac The MAC address to which peer delay messages should be sent. Relevant only with L2 transport. The default is 01:80:C2:00:00:0E. .TP .B network_transport Select the network transport. Possible values are UDPv4, UDPv6 and L2. The default is UDPv4. .TP .B neighborPropDelayThresh Upper limit for peer delay in nanoseconds. If the estimated peer delay is greater than this value the port is marked as not 802.1AS capable. .TP .B min_neighbor_prop_delay Lower limit for peer delay in nanoseconds. If the estimated peer delay is smaller than this value the port is marked as not 802.1AS capable. .TP .B tsproc_mode Select the time stamp processing mode used to calculate offset and delay. Possible values are filter, raw, filter_weight, raw_weight. Raw modes perform well when the rate of sync messages (logSyncInterval) is similar to the rate of delay messages (logMinDelayReqInterval or logMinPdelayReqInterval). Weighting is useful with larger network jitters (e.g. software time stamping). The default is filter. .TP .B delay_filter Select the algorithm used to filter the measured delay and peer delay. Possible values are moving_average and moving_median. The default is moving_median. .TP .B delay_filter_length The length of the delay filter in samples. The default is 10. .TP .B egressLatency Specifies the difference in nanoseconds between the actual transmission time at the reference plane and the reported transmit time stamp. This value will be added to egress time stamps obtained from the hardware. The default is 0. .TP .B ingressLatency Specifies the difference in nanoseconds between the reported receive time stamp and the actual reception time at reference plane. This value will be subtracted from ingress time stamps obtained from the hardware. The default is 0. .TP .B boundary_clock_jbod When running as a boundary clock (that is, when more than one network interface is configured), ptp4l performs a sanity check to make sure that all of the ports share the same hardware clock device. This option allows ptp4l to work as a boundary clock using "just a bunch of devices" that are not synchronized to each other. For this mode, the collection of clocks must be synchronized by an external program, for example phc2sys(8) in "automatic" mode. The default is 0 (disabled). .TP .B udp_ttl Specifies the Time to live (TTL) value for IPv4 multicast messages and the hop limit for IPv6 multicast messages. This option is only relevant with the IPv4 and IPv6 UDP transports. The default is 1 to restrict the messages sent by .B ptp4l to the same subnet. .SH PROGRAM AND CLOCK OPTIONS .TP .B twoStepFlag Enable two-step mode for sync messages. One-step mode can be used only with hardware time stamping. The default is 1 (enabled). .TP .B slaveOnly The local clock is a slave-only clock if enabled. This option is only for use with 1588 clocks and should not be enabled for 802.1AS clocks. The default is 0 (disabled). .TP .B gmCapable If this option is enabled, then the local clock is able to become grand master. This is only for use with 802.1AS clocks and has no effect on 1588 clocks. The default is 1 (enabled). .TP .B priority1 The priority1 attribute of the local clock. It is used in the best master selection algorithm, lower values take precedence. Must be in the range 0 to 255. The default is 128. .TP .B priority2 The priority2 attribute of the local clock. It is used in the best master selection algorithm, lower values take precedence. Must be in the range 0 to 255. The default is 128. .TP .B clockClass The clockClass attribute of the local clock. It denotes the traceability of the time distributed by the grandmaster clock. The default is 248. .TP .B clockAccuracy The clockAccuracy attribute of the local clock. It is used in the best master selection algorithm. The default is 0xFE. .TP .B offsetScaledLogVariance The offsetScaledLogVariance attribute of the local clock. It characterizes the stability of the clock. The default is 0xFFFF. .TP .B domainNumber The domain attribute of the local clock. The default is 0. .TP .B free_running Don't adjust the local clock if enabled. The default is 0 (disabled). .TP .B freq_est_interval The time interval over which is estimated the ratio of the local and peer clock frequencies. It is specified as a power of two in seconds. The default is 1 (2 seconds). .TP .B assume_two_step Treat one-step responses as two-step if enabled. It is used to work around buggy 802.1AS switches. The default is 0 (disabled). .TP .B tx_timestamp_timeout The number of milliseconds to poll waiting for the tx time stamp from the kernel when a message has recently been sent. The default is 1. .TP .B check_fup_sync Because of packet reordering that can occur in the network, in the hardware, or in the networking stack, a follow up message can appear to arrive in the application before the matching sync message. As this is a normal occurrence, and the sequenceID message field ensures proper matching, the ptp4l program accepts out of order packets. This option adds an additional check using the software time stamps from the networking stack to verify that the sync message did arrive first. This option is only useful if you do not trust the sequence IDs generated by the master. The default is 0 (disabled). .TP .B clock_servo The servo which is used to synchronize the local clock. Valid values are "pi" for a PI controller, "linreg" for an adaptive controller using linear regression, "ntpshm" for the NTP SHM reference clock to allow another process to synchronize the local clock (the SHM segment number is set to the domain number), and "nullf" for a servo that always dials frequency offset zero (for use in SyncE nodes). The default is "pi." .TP .B pi_proportional_const The proportional constant of the PI controller. When set to 0.0, the proportional constant will be set by the following formula from the current sync interval. The default is 0.0. kp = min(kp_scale * sync^kp_exponent, kp_norm_max / sync)) .TP .B pi_integral_const The integral constant of the PI controller. When set to 0.0, the integral constant will be set by the following formula from the current sync interval. The default is 0.0. ki = min(ki_scale * sync^ki_exponent, ki_norm_max / sync) .TP .B pi_proportional_scale The kp_scale constant in the formula used to set the proportional constant of the PI controller from the sync interval. When set to 0.0, the value will be selected from 0.7 and 0.1 for the hardware and software time stamping respectively. The default is 0.0. .TP .B pi_proportional_exponent The kp_exponent constant in the formula used to set the proportional constant of the PI controller from the sync interval. The default is \-0.3. .TP .B pi_proportional_norm_max The kp_norm_max constant in the formula used to set the proportional constant of the PI controller from the sync interval. The default is 0.7 .TP .B pi_integral_scale The ki_scale constant in the formula used to set the integral constant of the PI controller from the sync interval. When set to 0.0, the value will be selected from 0.3 and 0.001 for the hardware and software time stamping respectively. The default is 0.0. .TP .B pi_integral_exponent The ki_exponent constant in the formula used to set the integral constant of the PI controller from the sync interval. The default is 0.4. .TP .B pi_integral_norm_max The ki_norm_max constant in the formula used to set the integral constant of the PI controller from the sync interval. The default is 0.3. .TP .B step_threshold The maximum offset the servo will correct by changing the clock frequency instead of stepping the clock. When set to 0.0, the servo will never step the clock except on start. It's specified in seconds. The default is 0.0. This option used to be called .BR pi_offset_const . .TP .B first_step_threshold The maximum offset the servo will correct by changing the clock frequency instead of stepping the clock. This is only applied on the first update. It's specified in seconds. When set to 0.0, the servo won't step the clock on start. The default is 0.00002 (20 microseconds). This option used to be called .BR pi_f_offset_const . .TP .B max_frequency The maximum allowed frequency adjustment of the clock in parts per billion (ppb). This is an additional limit to the maximum allowed by the hardware. When set to 0, the hardware limit will be used. The default is 900000000 (90%). This option used to be called .BR pi_max_frequency . .TP .B sanity_freq_limit The maximum allowed frequency offset between uncorrected clock and the system monotonic clock in parts per billion (ppb). This is used as a sanity check of the synchronized clock. When a larger offset is measured, a warning message will be printed and the servo will be reset. When set to 0, the sanity check is disabled. The default is 200000000 (20%). .TP .B ntpshm_segment The number of the SHM segment used by ntpshm servo. The default is 0. .TP .B udp6_scope Specifies the desired scope for the IPv6 multicast messages. This will be used as the second byte of the primary address. This option is only relevant with IPv6 transport. See RFC 4291. The default is 0x0E for the global scope. .TP .B uds_address Specifies the address of the UNIX domain socket for receiving local management messages. The default is /var/run/ptp4l. .TP .B logging_level The maximum logging level of messages which should be printed. The default is 6 (LOG_INFO). .TP .B verbose Print messages to the standard output if enabled. The default is 0 (disabled). .TP .B use_syslog Print messages to the system log if enabled. The default is 1 (enabled). .TP .B summary_interval The time interval in which are printed summary statistics of the clock. It is specified as a power of two in seconds. The statistics include offset root mean square (RMS), maximum absolute offset, frequency offset mean and standard deviation, and path delay mean and standard deviation. The units are nanoseconds and parts per billion (ppb). If there is only one clock update in the interval, the sample will be printed instead of the statistics. The messages are printed at the LOG_INFO level. The default is 0 (1 second). .TP .B time_stamping The time stamping method. The allowed values are hardware, software and legacy. The default is hardware. .TP .B productDescription The product description string. Allowed values must be of the form manufacturerName;modelNumber;instanceIdentifier and contain at most 64 utf8 symbols. The default is ";;". .TP .B revisionData The revision description string which contains the revisions for node hardware (HW), firmware (FW), and software (SW). Allowed values are of the form HW;FW;SW and contain at most 32 utf8 symbols. The default is an ";;". .TP .B userDescription The user description string. Allowed values are of the form name;location and contain at most 128 utf8 symbols. The default is an empty string. .TP .B manufacturerIdentity The manufacturer id which should be an OUI owned by the manufacturer. The default is 00:00:00. .TP .B kernel_leap When a leap second is announced, let the kernel apply it by stepping the clock instead of correcting the one-second offset with servo, which would correct the one-second offset slowly by changing the clock frequency (unless the .B step_threshold option is set to correct such offset by stepping). Relevant only with software time stamping. The default is 1 (enabled). .TP .B timeSource The time source is a single byte code that gives an idea of the kind of local clock in use. The value is purely informational, having no effect on the outcome of the Best Master Clock algorithm, and is advertised when the clock becomes grand master. .SH TIME SCALE USAGE .B ptp4l as domain master either uses PTP or UTC time scale depending on time stamping mode. In software and legacy time stamping modes it announces Arbitrary time scale mode, which is effectively UTC here, in hardware time stamping mode it announces use of PTP time scale. When .B ptp4l is the domain master using hardware time stamping, it is up to .B phc2sys to maintain the correct offset between UTC and PTP times. See .BR phc2sys (8) manual page for more details. .SH SEE ALSO .BR pmc (8), .BR phc2sys (8) linuxptp-1.6/ptp4l.c000066400000000000000000000234341257727010700144700ustar00rootroot00000000000000/** * @file ptp4l.c * @brief PTP Boundary Clock main program * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "clock.h" #include "config.h" #include "ntpshm.h" #include "pi.h" #include "print.h" #include "raw.h" #include "sk.h" #include "transport.h" #include "udp6.h" #include "uds.h" #include "util.h" #include "version.h" int assume_two_step = 0; static struct default_ds ptp4l_dds; static void usage(char *progname) { fprintf(stderr, "\nusage: %s [options]\n\n" " Delay Mechanism\n\n" " -A Auto, starting with E2E\n" " -E E2E, delay request-response (default)\n" " -P P2P, peer delay mechanism\n\n" " Network Transport\n\n" " -2 IEEE 802.3\n" " -4 UDP IPV4 (default)\n" " -6 UDP IPV6\n\n" " Time Stamping\n\n" " -H HARDWARE (default)\n" " -S SOFTWARE\n" " -L LEGACY HW\n\n" " Other Options\n\n" " -f [file] read configuration from 'file'\n" " -i [dev] interface device to use, for example 'eth0'\n" " (may be specified multiple times)\n" " -p [dev] PTP hardware clock device to use, default auto\n" " (ignored for SOFTWARE/LEGACY HW time stamping)\n" " -s slave only mode (overrides configuration file)\n" " -l [num] set the logging level to 'num'\n" " -m print messages to stdout\n" " -q do not print messages to the syslog\n" " -v prints the software version and exits\n" " -h prints this message and exits\n" "\n", progname); } int main(int argc, char *argv[]) { char *config = NULL, *req_phc = NULL, *progname, *tmp; int c, err = -1; struct interface *iface; struct clock *clock = NULL; struct config *cfg; struct default_ds *dds = &ptp4l_dds; struct defaultDS *ds = &ptp4l_dds.dds; int phc_index = -1, print_level, required_modes = 0; unsigned char oui[OUI_LEN]; if (handle_term_signals()) return -1; cfg = config_create(); if (!cfg) { return -1; } /* Process the command line arguments. */ progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; while (EOF != (c = getopt(argc, argv, "AEP246HSLf:i:p:sl:mqvh"))) { switch (c) { case 'A': if (config_set_int(cfg, "delay_mechanism", DM_AUTO)) goto out; break; case 'E': if (config_set_int(cfg, "delay_mechanism", DM_E2E)) goto out; break; case 'P': if (config_set_int(cfg, "delay_mechanism", DM_P2P)) goto out; break; case '2': if (config_set_int(cfg, "network_transport", TRANS_IEEE_802_3)) goto out; break; case '4': if (config_set_int(cfg, "network_transport", TRANS_UDP_IPV4)) goto out; break; case '6': if (config_set_int(cfg, "network_transport", TRANS_UDP_IPV6)) goto out; break; case 'H': if (config_set_int(cfg, "time_stamping", TS_HARDWARE)) goto out; break; case 'S': if (config_set_int(cfg, "time_stamping", TS_SOFTWARE)) goto out; break; case 'L': if (config_set_int(cfg, "time_stamping", TS_LEGACY_HW)) goto out; break; case 'f': config = optarg; break; case 'i': if (!config_create_interface(optarg, cfg)) goto out; break; case 'p': req_phc = optarg; break; case 's': if (config_set_int(cfg, "slaveOnly", 1)) { goto out; } break; case 'l': if (get_arg_val_i(c, optarg, &print_level, PRINT_LEVEL_MIN, PRINT_LEVEL_MAX)) goto out; config_set_int(cfg, "logging_level", print_level); break; case 'm': config_set_int(cfg, "verbose", 1); break; case 'q': config_set_int(cfg, "use_syslog", 0); break; case 'v': version_show(stdout); return 0; case 'h': usage(progname); return 0; case '?': usage(progname); goto out; default: usage(progname); goto out; } } if (config && (c = config_read(config, cfg))) { return c; } print_set_progname(progname); print_set_verbose(config_get_int(cfg, NULL, "verbose")); print_set_syslog(config_get_int(cfg, NULL, "use_syslog")); print_set_level(config_get_int(cfg, NULL, "logging_level")); assume_two_step = config_get_int(cfg, NULL, "assume_two_step"); sk_check_fupsync = config_get_int(cfg, NULL, "check_fup_sync"); sk_tx_timeout = config_get_int(cfg, NULL, "tx_timestamp_timeout"); ds->clockQuality.clockClass = config_get_int(cfg, NULL, "clockClass"); ds->clockQuality.clockAccuracy = config_get_int(cfg, NULL, "clockAccuracy"); ds->clockQuality.offsetScaledLogVariance = config_get_int(cfg, NULL, "offsetScaledLogVariance"); dds->clock_desc.productDescription.max_symbols = 64; dds->clock_desc.revisionData.max_symbols = 32; dds->clock_desc.userDescription.max_symbols = 128; tmp = config_get_string(cfg, NULL, "productDescription"); if (count_char(tmp, ';') != 2 || static_ptp_text_set(&dds->clock_desc.productDescription, tmp)) { fprintf(stderr, "invalid productDescription '%s'.\n", tmp); goto out; } tmp = config_get_string(cfg, NULL, "revisionData"); if (count_char(tmp, ';') != 2 || static_ptp_text_set(&dds->clock_desc.revisionData, tmp)) { fprintf(stderr, "invalid revisionData '%s'.\n", tmp); goto out; } tmp = config_get_string(cfg, NULL, "userDescription"); if (static_ptp_text_set(&dds->clock_desc.userDescription, tmp)) { fprintf(stderr, "invalid userDescription '%s'.\n", tmp); goto out; } tmp = config_get_string(cfg, NULL, "manufacturerIdentity"); if (OUI_LEN != sscanf(tmp, "%hhx:%hhx:%hhx", &oui[0], &oui[1], &oui[2])) { fprintf(stderr, "invalid manufacturerIdentity '%s'.\n", tmp); goto out; } memcpy(dds->clock_desc.manufacturerIdentity, oui, OUI_LEN); ds->domainNumber = config_get_int(cfg, NULL, "domainNumber"); if (config_get_int(cfg, NULL, "slaveOnly")) { ds->flags |= DDS_SLAVE_ONLY; ds->clockQuality.clockClass = 248; } if (config_get_int(cfg, NULL, "twoStepFlag")) { ds->flags |= DDS_TWO_STEP_FLAG; } ds->priority1 = config_get_int(cfg, NULL, "priority1"); ds->priority2 = config_get_int(cfg, NULL, "priority2"); if (!config_get_int(cfg, NULL, "gmCapable") && ds->flags & DDS_SLAVE_ONLY) { fprintf(stderr, "Cannot mix 1588 slaveOnly with 802.1AS !gmCapable.\n"); goto out; } if (!config_get_int(cfg, NULL, "gmCapable") || ds->flags & DDS_SLAVE_ONLY) { ds->clockQuality.clockClass = 255; } if (config_get_int(cfg, NULL, "clock_servo") == CLOCK_SERVO_NTPSHM) { config_set_int(cfg, "kernel_leap", 0); config_set_int(cfg, "sanity_freq_limit", 0); } if (STAILQ_EMPTY(&cfg->interfaces)) { fprintf(stderr, "no interface specified\n"); usage(progname); goto out; } if (!(ds->flags & DDS_TWO_STEP_FLAG)) { switch (config_get_int(cfg, NULL, "time_stamping")) { case TS_SOFTWARE: case TS_LEGACY_HW: fprintf(stderr, "one step is only possible " "with hardware time stamping\n"); goto out; case TS_HARDWARE: if (config_set_int(cfg, "time_stamping", TS_ONESTEP)) goto out; break; case TS_ONESTEP: break; } } switch (config_get_int(cfg, NULL, "time_stamping")) { case TS_SOFTWARE: required_modes |= SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE; break; case TS_LEGACY_HW: required_modes |= SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_SYS_HARDWARE; break; case TS_HARDWARE: case TS_ONESTEP: required_modes |= SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE; break; } /* Init interface configs and check whether timestamping mode is * supported. */ STAILQ_FOREACH(iface, &cfg->interfaces, list) { config_init_interface(iface, cfg); if (iface->ts_info.valid && ((iface->ts_info.so_timestamping & required_modes) != required_modes)) { fprintf(stderr, "interface '%s' does not support " "requested timestamping mode.\n", iface->name); goto out; } } /* determine PHC Clock index */ iface = STAILQ_FIRST(&cfg->interfaces); if (config_get_int(cfg, NULL, "free_running")) { phc_index = -1; } else if (config_get_int(cfg, NULL, "time_stamping") == TS_SOFTWARE || config_get_int(cfg, NULL, "time_stamping") == TS_LEGACY_HW) { phc_index = -1; } else if (req_phc) { if (1 != sscanf(req_phc, "/dev/ptp%d", &phc_index)) { fprintf(stderr, "bad ptp device string\n"); goto out; } } else if (iface->ts_info.valid) { phc_index = iface->ts_info.phc_index; } else { fprintf(stderr, "ptp device not specified and\n" "automatic determination is not\n" "supported. please specify ptp device\n"); goto out; } if (phc_index >= 0) { pr_info("selected /dev/ptp%d as PTP clock", phc_index); } if (generate_clock_identity(&ds->clockIdentity, iface->name)) { fprintf(stderr, "failed to generate a clock identity\n"); goto out; } clock = clock_create(cfg, phc_index, &cfg->interfaces, &ptp4l_dds); if (!clock) { fprintf(stderr, "failed to create a clock\n"); goto out; } err = 0; while (is_running()) { if (clock_poll(clock)) break; } out: if (clock) clock_destroy(clock); config_destroy(cfg); return err; } linuxptp-1.6/raw.c000066400000000000000000000217021257727010700142120ustar00rootroot00000000000000/** * @file raw.c * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "address.h" #include "config.h" #include "contain.h" #include "ether.h" #include "print.h" #include "raw.h" #include "sk.h" #include "transport_private.h" #include "util.h" struct raw { struct transport t; struct address src_addr; struct address ptp_addr; struct address p2p_addr; int vlan; }; #define OP_AND (BPF_ALU | BPF_AND | BPF_K) #define OP_JEQ (BPF_JMP | BPF_JEQ | BPF_K) #define OP_JUN (BPF_JMP | BPF_JA) #define OP_LDB (BPF_LD | BPF_B | BPF_ABS) #define OP_LDH (BPF_LD | BPF_H | BPF_ABS) #define OP_RETK (BPF_RET | BPF_K) #define PTP_GEN_BIT 0x08 /* indicates general message, if set in message type */ #define N_RAW_FILTER 12 #define RAW_FILTER_TEST 9 static struct sock_filter raw_filter[N_RAW_FILTER] = { {OP_LDH, 0, 0, OFF_ETYPE }, {OP_JEQ, 0, 4, ETH_P_8021Q }, /*f goto non-vlan block*/ {OP_LDH, 0, 0, OFF_ETYPE + 4 }, {OP_JEQ, 0, 7, ETH_P_1588 }, /*f goto reject*/ {OP_LDB, 0, 0, ETH_HLEN + VLAN_HLEN }, {OP_JUN, 0, 0, 2 }, /*goto test general bit*/ {OP_JEQ, 0, 4, ETH_P_1588 }, /*f goto reject*/ {OP_LDB, 0, 0, ETH_HLEN }, {OP_AND, 0, 0, PTP_GEN_BIT }, /*test general bit*/ {OP_JEQ, 0, 1, 0 }, /*0,1=accept event; 1,0=accept general*/ {OP_RETK, 0, 0, 1500 }, /*accept*/ {OP_RETK, 0, 0, 0 }, /*reject*/ }; static int raw_configure(int fd, int event, int index, unsigned char *addr1, unsigned char *addr2, int enable) { int err1, err2, filter_test, option; struct packet_mreq mreq; struct sock_fprog prg = { N_RAW_FILTER, raw_filter }; filter_test = RAW_FILTER_TEST; if (event) { raw_filter[filter_test].jt = 0; raw_filter[filter_test].jf = 1; } else { raw_filter[filter_test].jt = 1; raw_filter[filter_test].jf = 0; } if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &prg, sizeof(prg))) { pr_err("setsockopt SO_ATTACH_FILTER failed: %m"); return -1; } option = enable ? PACKET_ADD_MEMBERSHIP : PACKET_DROP_MEMBERSHIP; mreq.mr_ifindex = index; mreq.mr_type = PACKET_MR_MULTICAST; mreq.mr_alen = MAC_LEN; memcpy(mreq.mr_address, addr1, MAC_LEN); err1 = setsockopt(fd, SOL_PACKET, option, &mreq, sizeof(mreq)); if (err1) pr_warning("setsockopt PACKET_MR_MULTICAST failed: %m"); memcpy(mreq.mr_address, addr2, MAC_LEN); err2 = setsockopt(fd, SOL_PACKET, option, &mreq, sizeof(mreq)); if (err2) pr_warning("setsockopt PACKET_MR_MULTICAST failed: %m"); if (!err1 && !err2) return 0; mreq.mr_ifindex = index; mreq.mr_type = PACKET_MR_ALLMULTI; mreq.mr_alen = 0; if (!setsockopt(fd, SOL_PACKET, option, &mreq, sizeof(mreq))) { return 0; } pr_warning("setsockopt PACKET_MR_ALLMULTI failed: %m"); mreq.mr_ifindex = index; mreq.mr_type = PACKET_MR_PROMISC; mreq.mr_alen = 0; if (!setsockopt(fd, SOL_PACKET, option, &mreq, sizeof(mreq))) { return 0; } pr_warning("setsockopt PACKET_MR_PROMISC failed: %m"); pr_err("all socket options failed"); return -1; } static int raw_close(struct transport *t, struct fdarray *fda) { close(fda->fd[0]); close(fda->fd[1]); return 0; } static int open_socket(const char *name, int event, unsigned char *ptp_dst_mac, unsigned char *p2p_dst_mac) { struct sockaddr_ll addr; int fd, index; fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (fd < 0) { pr_err("socket failed: %m"); goto no_socket; } index = sk_interface_index(fd, name); if (index < 0) goto no_option; memset(&addr, 0, sizeof(addr)); addr.sll_ifindex = index; addr.sll_family = AF_PACKET; addr.sll_protocol = htons(ETH_P_ALL); if (bind(fd, (struct sockaddr *) &addr, sizeof(addr))) { pr_err("bind failed: %m"); goto no_option; } if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name))) { pr_err("setsockopt SO_BINDTODEVICE failed: %m"); goto no_option; } if (raw_configure(fd, event, index, ptp_dst_mac, p2p_dst_mac, 1)) goto no_option; return fd; no_option: close(fd); no_socket: return -1; } static void mac_to_addr(struct address *addr, void *mac) { addr->sll.sll_family = AF_PACKET; addr->sll.sll_halen = MAC_LEN; memcpy(addr->sll.sll_addr, mac, MAC_LEN); addr->len = sizeof(addr->sll); } static void addr_to_mac(void *mac, struct address *addr) { memcpy(mac, &addr->sll.sll_addr, MAC_LEN); } static int raw_open(struct transport *t, const char *name, struct fdarray *fda, enum timestamp_type ts_type) { struct raw *raw = container_of(t, struct raw, t); unsigned char ptp_dst_mac[MAC_LEN]; unsigned char p2p_dst_mac[MAC_LEN]; int efd, gfd; char *str; str = config_get_string(t->cfg, name, "ptp_dst_mac"); if (str2mac(str, ptp_dst_mac)) { pr_err("invalid ptp_dst_mac %s", str); return -1; } str = config_get_string(t->cfg, name, "p2p_dst_mac"); if (str2mac(str, p2p_dst_mac)) { pr_err("invalid p2p_dst_mac %s", str); return -1; } mac_to_addr(&raw->ptp_addr, ptp_dst_mac); mac_to_addr(&raw->p2p_addr, p2p_dst_mac); if (sk_interface_macaddr(name, &raw->src_addr)) goto no_mac; efd = open_socket(name, 1, ptp_dst_mac, p2p_dst_mac); if (efd < 0) goto no_event; gfd = open_socket(name, 0, ptp_dst_mac, p2p_dst_mac); if (gfd < 0) goto no_general; if (sk_timestamping_init(efd, name, ts_type, TRANS_IEEE_802_3)) goto no_timestamping; if (sk_general_init(gfd)) goto no_timestamping; fda->fd[FD_EVENT] = efd; fda->fd[FD_GENERAL] = gfd; return 0; no_timestamping: close(gfd); no_general: close(efd); no_event: no_mac: return -1; } static int raw_recv(struct transport *t, int fd, void *buf, int buflen, struct address *addr, struct hw_timestamp *hwts) { int cnt, hlen; unsigned char *ptr = buf; struct eth_hdr *hdr; struct raw *raw = container_of(t, struct raw, t); if (raw->vlan) { hlen = sizeof(struct vlan_hdr); } else { hlen = sizeof(struct eth_hdr); } ptr -= hlen; buflen += hlen; hdr = (struct eth_hdr *) ptr; cnt = sk_receive(fd, ptr, buflen, addr, hwts, 0); if (cnt >= 0) cnt -= hlen; if (cnt < 0) return cnt; if (raw->vlan) { if (ETH_P_1588 == ntohs(hdr->type)) { pr_notice("raw: disabling VLAN mode"); raw->vlan = 0; } } else { if (ETH_P_8021Q == ntohs(hdr->type)) { pr_notice("raw: switching to VLAN mode"); raw->vlan = 1; } } return cnt; } static int raw_send(struct transport *t, struct fdarray *fda, int event, int peer, void *buf, int len, struct address *addr, struct hw_timestamp *hwts) { struct raw *raw = container_of(t, struct raw, t); ssize_t cnt; int fd = event ? fda->fd[FD_EVENT] : fda->fd[FD_GENERAL]; unsigned char pkt[1600], *ptr = buf; struct eth_hdr *hdr; ptr -= sizeof(*hdr); len += sizeof(*hdr); if (!addr) addr = peer ? &raw->p2p_addr : &raw->ptp_addr; hdr = (struct eth_hdr *) ptr; addr_to_mac(&hdr->dst, addr); addr_to_mac(&hdr->src, &raw->src_addr); hdr->type = htons(ETH_P_1588); cnt = send(fd, ptr, len, 0); if (cnt < 1) { pr_err("send failed: %d %m", errno); return cnt; } /* * Get the time stamp right away. */ return event == TRANS_EVENT ? sk_receive(fd, pkt, len, NULL, hwts, MSG_ERRQUEUE) : cnt; } static void raw_release(struct transport *t) { struct raw *raw = container_of(t, struct raw, t); free(raw); } static int raw_physical_addr(struct transport *t, uint8_t *addr) { struct raw *raw = container_of(t, struct raw, t); addr_to_mac(addr, &raw->src_addr); return MAC_LEN; } static int raw_protocol_addr(struct transport *t, uint8_t *addr) { struct raw *raw = container_of(t, struct raw, t); addr_to_mac(addr, &raw->src_addr); return MAC_LEN; } struct transport *raw_transport_create(void) { struct raw *raw; raw = calloc(1, sizeof(*raw)); if (!raw) return NULL; raw->t.close = raw_close; raw->t.open = raw_open; raw->t.recv = raw_recv; raw->t.send = raw_send; raw->t.release = raw_release; raw->t.physical_addr = raw_physical_addr; raw->t.protocol_addr = raw_protocol_addr; return &raw->t; } linuxptp-1.6/raw.h000066400000000000000000000021741257727010700142210ustar00rootroot00000000000000/** * @file raw.h * @brief Implements transport over IEEE 802.3 aka raw Ethernet. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_RAW_H #define HAVE_RAW_H #include "fd.h" #include "transport.h" /** * Allocate an instance of a raw Ethernet transport. * @return Pointer to a new transport instance on success, NULL otherwise. */ struct transport *raw_transport_create(void); #endif linuxptp-1.6/servo.c000066400000000000000000000057411257727010700145640ustar00rootroot00000000000000/** * @file servo.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include "config.h" #include "linreg.h" #include "ntpshm.h" #include "nullf.h" #include "pi.h" #include "servo_private.h" #define NSEC_PER_SEC 1000000000 struct servo *servo_create(struct config *cfg, enum servo_type type, int fadj, int max_ppb, int sw_ts) { double servo_first_step_threshold; double servo_step_threshold; int servo_max_frequency; struct servo *servo; switch (type) { case CLOCK_SERVO_PI: servo = pi_servo_create(cfg, fadj, sw_ts); break; case CLOCK_SERVO_LINREG: servo = linreg_servo_create(fadj); break; case CLOCK_SERVO_NTPSHM: servo = ntpshm_servo_create(cfg); break; case CLOCK_SERVO_NULLF: servo = nullf_servo_create(); break; default: return NULL; } servo_step_threshold = config_get_double(cfg, NULL, "step_threshold"); if (servo_step_threshold > 0.0) { servo->step_threshold = servo_step_threshold * NSEC_PER_SEC; } else { servo->step_threshold = 0.0; } servo_first_step_threshold = config_get_double(cfg, NULL, "first_step_threshold"); if (servo_first_step_threshold > 0.0) { servo->first_step_threshold = servo_first_step_threshold * NSEC_PER_SEC; } else { servo->first_step_threshold = 0.0; } servo_max_frequency = config_get_int(cfg, NULL, "max_frequency"); servo->max_frequency = max_ppb; if (servo_max_frequency && servo->max_frequency > servo_max_frequency) { servo->max_frequency = servo_max_frequency; } servo->first_update = 1; return servo; } void servo_destroy(struct servo *servo) { servo->destroy(servo); } double servo_sample(struct servo *servo, int64_t offset, uint64_t local_ts, double weight, enum servo_state *state) { double r; r = servo->sample(servo, offset, local_ts, weight, state); if (*state != SERVO_UNLOCKED) servo->first_update = 0; return r; } void servo_sync_interval(struct servo *servo, double interval) { servo->sync_interval(servo, interval); } void servo_reset(struct servo *servo) { servo->reset(servo); } double servo_rate_ratio(struct servo *servo) { if (servo->rate_ratio) return servo->rate_ratio(servo); return 1.0; } void servo_leap(struct servo *servo, int leap) { if (servo->leap) servo->leap(servo, leap); } linuxptp-1.6/servo.h000066400000000000000000000075161257727010700145730ustar00rootroot00000000000000/** * @file servo.h * @brief Implements a generic clock servo interface. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_SERVO_H #define HAVE_SERVO_H #include struct config; /** Opaque type */ struct servo; /** * Defines the available servo cores */ enum servo_type { CLOCK_SERVO_PI, CLOCK_SERVO_LINREG, CLOCK_SERVO_NTPSHM, CLOCK_SERVO_NULLF, }; /** * Defines the caller visible states of a clock servo. */ enum servo_state { /** * The servo is not yet ready to track the master clock. */ SERVO_UNLOCKED, /** * The is ready to track and requests a clock jump to * immediately correct the estimated offset. */ SERVO_JUMP, /** * The servo is tracking the master clock. */ SERVO_LOCKED, }; /** * Create a new instance of a clock servo. * @param type The type of the servo to create. * @param fadj The clock's current adjustment in parts per billion. * @param max_ppb The absolute maxinum adjustment allowed by the clock * in parts per billion. The clock servo will clamp its * output according to this limit. * @param sw_ts Indicates that software time stamping will be used, * and the servo should use more aggressive filtering. * @return A pointer to a new servo on success, NULL otherwise. */ struct servo *servo_create(struct config *cfg, enum servo_type type, int fadj, int max_ppb, int sw_ts); /** * Destroy an instance of a clock servo. * @param servo Pointer to a servo obtained via @ref servo_create(). */ void servo_destroy(struct servo *servo); /** * Feed a sample into a clock servo. * @param servo Pointer to a servo obtained via @ref servo_create(). * @param offset The estimated clock offset in nanoseconds. * @param local_ts The local time stamp of the sample in nanoseconds. * @param weight The weight of the sample, larger if more reliable, * 1.0 is the maximum value. * @param state Returns the servo's state. * @return The clock adjustment in parts per billion. */ double servo_sample(struct servo *servo, int64_t offset, uint64_t local_ts, double weight, enum servo_state *state); /** * Inform a clock servo about the master's sync interval. * @param servo Pointer to a servo obtained via @ref servo_create(). * @param interval The sync interval in seconds. */ void servo_sync_interval(struct servo *servo, double interval); /** * Reset a clock servo. * @param servo Pointer to a servo obtained via @ref servo_create(). */ void servo_reset(struct servo *servo); /** * Obtain ratio between master's frequency and current servo frequency. * @param servo Pointer to a servo obtained via @ref servo_create(). * @return The rate ratio, 1.0 is returned when not known. */ double servo_rate_ratio(struct servo *servo); /** * Inform a clock servo about upcoming leap second. * @param servo Pointer to a servo obtained via @ref servo_create(). * @param leap +1 when leap second will be inserted, -1 when leap second * will be deleted, 0 when it passed. */ void servo_leap(struct servo *servo, int leap); #endif linuxptp-1.6/servo_private.h000066400000000000000000000025501257727010700163160ustar00rootroot00000000000000/** * @file servo_private.h * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_SERVO_PRIVATE_H #define HAVE_SERVO_PRIVATE_H #include "contain.h" struct servo { double max_frequency; double step_threshold; double first_step_threshold; int first_update; void (*destroy)(struct servo *servo); double (*sample)(struct servo *servo, int64_t offset, uint64_t local_ts, double weight, enum servo_state *state); void (*sync_interval)(struct servo *servo, double interval); void (*reset)(struct servo *servo); double (*rate_ratio)(struct servo *servo); void (*leap)(struct servo *servo, int leap); }; #endif linuxptp-1.6/sk.c000066400000000000000000000214771257727010700140470ustar00rootroot00000000000000/** * @file sk.c * @brief Implements protocol independent socket methods. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "address.h" #include "ether.h" #include "missing.h" #include "print.h" #include "sk.h" /* globals */ int sk_tx_timeout = 1; int sk_check_fupsync; /* private methods */ static int hwts_init(int fd, const char *device, int rx_filter, int one_step) { struct ifreq ifreq; struct hwtstamp_config cfg, req; int err; memset(&ifreq, 0, sizeof(ifreq)); memset(&cfg, 0, sizeof(cfg)); strncpy(ifreq.ifr_name, device, sizeof(ifreq.ifr_name) - 1); ifreq.ifr_data = (void *) &cfg; cfg.tx_type = one_step ? HWTSTAMP_TX_ONESTEP_SYNC : HWTSTAMP_TX_ON; cfg.rx_filter = rx_filter; req = cfg; err = ioctl(fd, SIOCSHWTSTAMP, &ifreq); if (err < 0) return err; if (memcmp(&cfg, &req, sizeof(cfg))) { pr_warning("driver changed our HWTSTAMP options"); pr_warning("tx_type %d not %d", cfg.tx_type, req.tx_type); pr_warning("rx_filter %d not %d", cfg.rx_filter, req.rx_filter); if (cfg.tx_type != req.tx_type || (cfg.rx_filter != HWTSTAMP_FILTER_ALL && cfg.rx_filter != HWTSTAMP_FILTER_PTP_V2_EVENT)) { return -1; } } return 0; } /* public methods */ int sk_interface_index(int fd, const char *name) { struct ifreq ifreq; int err; memset(&ifreq, 0, sizeof(ifreq)); strncpy(ifreq.ifr_name, name, sizeof(ifreq.ifr_name) - 1); err = ioctl(fd, SIOCGIFINDEX, &ifreq); if (err < 0) { pr_err("ioctl SIOCGIFINDEX failed: %m"); return err; } return ifreq.ifr_ifindex; } int sk_general_init(int fd) { int on = sk_check_fupsync ? 1 : 0; if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPNS, &on, sizeof(on)) < 0) { pr_err("ioctl SO_TIMESTAMPNS failed: %m"); return -1; } return 0; } int sk_get_ts_info(const char *name, struct sk_ts_info *sk_info) { #ifdef ETHTOOL_GET_TS_INFO struct ethtool_ts_info info; struct ifreq ifr; int fd, err; memset(&ifr, 0, sizeof(ifr)); memset(&info, 0, sizeof(info)); info.cmd = ETHTOOL_GET_TS_INFO; strncpy(ifr.ifr_name, name, IFNAMSIZ - 1); ifr.ifr_data = (char *) &info; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { pr_err("socket failed: %m"); goto failed; } err = ioctl(fd, SIOCETHTOOL, &ifr); if (err < 0) { pr_err("ioctl SIOCETHTOOL failed: %m"); close(fd); goto failed; } close(fd); /* copy the necessary data to sk_info */ memset(sk_info, 0, sizeof(struct sk_ts_info)); sk_info->valid = 1; 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; failed: #endif /* clear data and ensure it is not marked valid */ memset(sk_info, 0, sizeof(struct sk_ts_info)); return -1; } int sk_interface_macaddr(const char *name, struct address *mac) { struct ifreq ifreq; int err, fd; memset(&ifreq, 0, sizeof(ifreq)); strncpy(ifreq.ifr_name, name, sizeof(ifreq.ifr_name) - 1); fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) { pr_err("socket failed: %m"); return -1; } err = ioctl(fd, SIOCGIFHWADDR, &ifreq); if (err < 0) { pr_err("ioctl SIOCGIFHWADDR failed: %m"); close(fd); return -1; } mac->sll.sll_family = AF_PACKET; mac->sll.sll_halen = MAC_LEN; memcpy(mac->sll.sll_addr, &ifreq.ifr_hwaddr.sa_data, MAC_LEN); mac->len = sizeof(mac->sll); close(fd); return 0; } int sk_interface_addr(const char *name, int family, struct address *addr) { struct ifaddrs *ifaddr, *i; int result = -1; if (getifaddrs(&ifaddr) == -1) { pr_err("getifaddrs failed: %m"); return -1; } for (i = ifaddr; i; i = i->ifa_next) { if (i->ifa_addr && family == i->ifa_addr->sa_family && strcmp(name, i->ifa_name) == 0) { switch (family) { case AF_INET: addr->len = sizeof(addr->sin); memcpy(&addr->sin, i->ifa_addr, addr->len); break; case AF_INET6: addr->len = sizeof(addr->sin6); memcpy(&addr->sin6, i->ifa_addr, addr->len); break; default: continue; } result = 0; break; } } freeifaddrs(ifaddr); return result; } static short sk_events = POLLPRI; static short sk_revents = POLLPRI; int sk_receive(int fd, void *buf, int buflen, struct address *addr, struct hw_timestamp *hwts, int flags) { char control[256]; int cnt = 0, res = 0, level, type; struct cmsghdr *cm; struct iovec iov = { buf, buflen }; struct msghdr msg; struct timespec *sw, *ts = NULL; memset(control, 0, sizeof(control)); memset(&msg, 0, sizeof(msg)); if (addr) { msg.msg_name = &addr->ss; msg.msg_namelen = sizeof(addr->ss); } 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, sk_events, 0 }; res = poll(&pfd, 1, sk_tx_timeout); if (res < 1) { pr_err(res ? "poll for tx timestamp failed: %m" : "timed out while polling for tx timestamp"); pr_err("increasing tx_timestamp_timeout may correct " "this issue, but it is likely caused by a driver bug"); return res; } else if (!(pfd.revents & sk_revents)) { pr_err("poll for tx timestamp woke up on non ERR event"); return -1; } } cnt = recvmsg(fd, &msg, flags); if (cnt < 1) pr_err("recvmsg%sfailed: %m", flags == MSG_ERRQUEUE ? " tx timestamp " : " "); for (cm = CMSG_FIRSTHDR(&msg); cm != NULL; cm = CMSG_NXTHDR(&msg, cm)) { level = cm->cmsg_level; type = cm->cmsg_type; if (SOL_SOCKET == level && SO_TIMESTAMPING == type) { if (cm->cmsg_len < sizeof(*ts) * 3) { pr_warning("short SO_TIMESTAMPING message"); return -1; } ts = (struct timespec *) CMSG_DATA(cm); } if (SOL_SOCKET == level && SO_TIMESTAMPNS == type) { if (cm->cmsg_len < sizeof(*sw)) { pr_warning("short SO_TIMESTAMPNS message"); return -1; } sw = (struct timespec *) CMSG_DATA(cm); hwts->sw = *sw; } } if (addr) addr->len = msg.msg_namelen; if (!ts) { memset(&hwts->ts, 0, sizeof(hwts->ts)); return cnt; } switch (hwts->type) { case TS_SOFTWARE: hwts->ts = ts[0]; break; case TS_HARDWARE: case TS_ONESTEP: hwts->ts = ts[2]; break; case TS_LEGACY_HW: hwts->ts = ts[1]; break; } return cnt; } int sk_timestamping_init(int fd, const char *device, enum timestamp_type type, enum transport_type transport) { int err, filter1, filter2 = 0, flags, one_step; switch (type) { case TS_SOFTWARE: flags = SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE; break; case TS_HARDWARE: case TS_ONESTEP: flags = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE; break; case TS_LEGACY_HW: flags = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_SYS_HARDWARE; break; default: return -1; } if (type != TS_SOFTWARE) { filter1 = HWTSTAMP_FILTER_PTP_V2_EVENT; one_step = type == TS_ONESTEP ? 1 : 0; switch (transport) { case TRANS_UDP_IPV4: case TRANS_UDP_IPV6: filter2 = HWTSTAMP_FILTER_PTP_V2_L4_EVENT; break; case TRANS_IEEE_802_3: filter2 = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; break; case TRANS_DEVICENET: case TRANS_CONTROLNET: case TRANS_PROFINET: case TRANS_UDS: return -1; } err = hwts_init(fd, device, filter1, one_step); if (err) { pr_info("driver rejected most general HWTSTAMP filter"); err = hwts_init(fd, device, filter2, one_step); if (err) { pr_err("ioctl SIOCSHWTSTAMP failed: %m"); return err; } } } if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)) < 0) { pr_err("ioctl SO_TIMESTAMPING failed: %m"); return -1; } flags = 1; if (setsockopt(fd, SOL_SOCKET, SO_SELECT_ERR_QUEUE, &flags, sizeof(flags)) < 0) { pr_warning("%s: SO_SELECT_ERR_QUEUE: %m", device); sk_events = 0; sk_revents = POLLERR; } /* Enable the sk_check_fupsync option, perhaps. */ if (sk_general_init(fd)) { return -1; } return 0; } linuxptp-1.6/sk.h000066400000000000000000000103021257727010700140350ustar00rootroot00000000000000/** * @file sk.h * @brief Implements protocol independent socket methods. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_SK_H #define HAVE_SK_H #include "address.h" #include "transport.h" /** * 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 { int valid; int phc_index; unsigned int so_timestamping; unsigned int tx_types; unsigned int rx_filters; }; /** * Obtain the numerical index from a network interface by name. * @param fd An open socket. * @param device The name of the network interface of interest. * @return The result from the SIOCGIFINDEX ioctl. */ int sk_interface_index(int fd, const char *device); /** * Prepare a given socket for PTP "general" messages. * @param fd An open socket. * @return Zero on success, non-zero otherwise. */ int sk_general_init(int fd); /** * Obtain supported timestamping information * @param name The name of the interface * @param info Struct containing obtained timestamping information. * @return zero on success, negative on failure. */ int sk_get_ts_info(const char *name, struct sk_ts_info *sk_info); /** * Obtain the MAC address of a network interface. * @param name The name of the interface * @param mac Buffer to hold the result * @return Zero on success, non-zero otherwise. */ int sk_interface_macaddr(const char *name, struct address *mac); /** * Obtains the first IP address assigned to a network interface. * @param name The name of the interface * @param family The family of the address to get: AF_INET or AF_INET6 * @param addr Buffer to hold the result * @return The number of bytes written to addr on success, -1 otherwise. */ int sk_interface_addr(const char *name, int family, struct address *addr); /** * Read a message from a socket. * @param fd An open socket. * @param buf Buffer to receive the message. * @param buflen Size of 'buf' in bytes. * @param addr Pointer to a buffer to receive the message's source * address. May be NULL. * @param hwts Pointer to a buffer to receive the message's time stamp. * @param flags Flags to pass to RECV(2). * @return */ int sk_receive(int fd, void *buf, int buflen, struct address *addr, struct hw_timestamp *hwts, int flags); /** * Enable time stamping on a given network interface. * @param fd An open socket. * @param device The name of the network interface to configure. * @param type The requested flavor of time stamping. * @param transport The type of transport used. * @return Zero on success, non-zero otherwise. */ int sk_timestamping_init(int fd, const char *device, enum timestamp_type type, enum transport_type transport); /** * Limits the time that RECVMSG(2) will poll while waiting for the tx timestamp * if MSG_ERRQUEUE is set. Specified in milliseconds. */ extern int sk_tx_timeout; /** * Enables the SO_TIMESTAMPNS socket option on the both the event and * general sockets in order to test the order of paired sync and * follow up messages using their network stack receipt time stamps. */ extern int sk_check_fupsync; #endif linuxptp-1.6/stats.c000066400000000000000000000041001257727010700145500ustar00rootroot00000000000000/** * @file stats.c * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "stats.h" struct stats { unsigned int num; double min; double max; double mean; double sum_sqr; double sum_diff_sqr; }; struct stats *stats_create(void) { struct stats *stats; stats = calloc(1, sizeof *stats); return stats; } void stats_destroy(struct stats *stats) { free(stats); } void stats_add_value(struct stats *stats, double value) { double old_mean = stats->mean; if (!stats->num || stats->max < value) stats->max = value; if (!stats->num || stats->min > value) stats->min = value; stats->num++; stats->mean = old_mean + (value - old_mean) / stats->num; stats->sum_sqr += value * value; stats->sum_diff_sqr += (value - old_mean) * (value - stats->mean); } unsigned int stats_get_num_values(struct stats *stats) { return stats->num; } int stats_get_result(struct stats *stats, struct stats_result *result) { if (!stats->num) return -1; result->min = stats->min; result->max = stats->max; result->max_abs = stats->max > -stats->min ? stats->max : -stats->min; result->mean = stats->mean; result->rms = sqrt(stats->sum_sqr / stats->num); result->stddev = sqrt(stats->sum_diff_sqr / stats->num); return 0; } void stats_reset(struct stats *stats) { memset(stats, 0, sizeof *stats); } linuxptp-1.6/stats.h000066400000000000000000000043331257727010700145650ustar00rootroot00000000000000/** * @file stats.h * @brief Implements various statistics. * @note Copyright (C) 2013 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_STATS_H #define HAVE_STATS_H /** Opaque type */ struct stats; /** * Create a new instance of statistics. * @return A pointer to a new stats on success, NULL otherwise. */ struct stats *stats_create(void); /** * Destroy an instance of stats. * @param servo Pointer to stats obtained via @ref stats_create(). */ void stats_destroy(struct stats *stats); /** * Add a new value to the stats. * @param stats Pointer to stats obtained via @ref stats_create(). * @param value The measured value. */ void stats_add_value(struct stats *stats, double value); /** * Get the number of values collected in the stats so far. * @param stats Pointer to stats obtained via @ref stats_create(). * @return The number of values. */ unsigned int stats_get_num_values(struct stats *stats); struct stats_result { double min; double max; double max_abs; double mean; double rms; double stddev; }; /** * Obtain the results of the calculated statistics. * @param stats Pointer to stats obtained via @ref stats_create(). * @param stats_result Pointer to stats_result to store the results. * @return Zero on success, non-zero if no values were added. */ int stats_get_result(struct stats *stats, struct stats_result *result); /** * Reset all statistics. * @param stats Pointer to stats obtained via @ref stats_create(). */ void stats_reset(struct stats *stats); #endif linuxptp-1.6/sysoff.c000066400000000000000000000057101257727010700147330ustar00rootroot00000000000000/** * @file sysoff.c * @brief Implements the system offset estimation method. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "sysoff.h" #define NS_PER_SEC 1000000000LL #ifdef PTP_SYS_OFFSET static int64_t pctns(struct ptp_clock_time *t) { return t->sec * NS_PER_SEC + t->nsec; } static struct { int64_t interval; int64_t offset; uint64_t timestamp; } samples[PTP_MAX_SAMPLES]; static void insertion_sort(int length, int64_t interval, int64_t offset, uint64_t ts) { int i = length - 1; while (i >= 0) { if (samples[i].interval < interval) break; samples[i+1] = samples[i]; i--; } samples[i+1].interval = interval; samples[i+1].offset = offset; samples[i+1].timestamp = ts; } static int64_t sysoff_estimate(struct ptp_clock_time *pct, int n_samples, uint64_t *ts, int64_t *delay) { int64_t t1, t2, tp; int64_t interval, offset; int i; for (i = 0; i < n_samples; i++) { t1 = pctns(&pct[2*i]); tp = pctns(&pct[2*i+1]); t2 = pctns(&pct[2*i+2]); interval = t2 - t1; offset = (t2 + t1) / 2 - tp; insertion_sort(i, interval, offset, (t2 + t1) / 2); } *ts = samples[0].timestamp; *delay = samples[0].interval; return samples[0].offset; } int sysoff_measure(int fd, int n_samples, int64_t *result, uint64_t *ts, int64_t *delay) { struct ptp_sys_offset pso; pso.n_samples = n_samples; if (ioctl(fd, PTP_SYS_OFFSET, &pso)) { perror("ioctl PTP_SYS_OFFSET"); return SYSOFF_RUN_TIME_MISSING; } *result = sysoff_estimate(pso.ts, n_samples, ts, delay); return SYSOFF_SUPPORTED; } int sysoff_probe(int fd, int n_samples) { int64_t junk, delay; uint64_t ts; 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; } return sysoff_measure(fd, n_samples, &junk, &ts, &delay); } #else /* !PTP_SYS_OFFSET */ int sysoff_measure(int fd, int n_samples, int64_t *result, uint64_t *ts, int64_t *delay) { return SYSOFF_COMPILE_TIME_MISSING; } int sysoff_probe(int fd, int n_samples) { return SYSOFF_COMPILE_TIME_MISSING; } #endif /* PTP_SYS_OFFSET */ linuxptp-1.6/sysoff.h000066400000000000000000000033041257727010700147350ustar00rootroot00000000000000/** * @file sysoff.h * @brief Implements the system offset estimation method. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include enum { SYSOFF_SUPPORTED, SYSOFF_COMPILE_TIME_MISSING, SYSOFF_RUN_TIME_MISSING, }; /** * Check to see if the PTP_SYS_OFFSET ioctl is supported. * @param fd An open file descriptor to a PHC device. * @return One of the SYSOFF_ enumeration values. */ int sysoff_probe(int fd, int n_samples); /** * Measure the offset between a PHC and the system time. * @param fd An open file descriptor to a PHC device. * @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. */ int sysoff_measure(int fd, int n_samples, int64_t *result, uint64_t *ts, int64_t *delay); linuxptp-1.6/timemaster.8000066400000000000000000000234761257727010700155320ustar00rootroot00000000000000.TH TIMEMASTER 8 "October 2014" "linuxptp" .SH NAME timemaster - run NTP with PTP as reference clocks .SH SYNOPSIS .B timemaster [ .B \-nmqv ] [ .BI \-l " print-level" ] .BI \-f " file" .SH DESCRIPTION \fBtimemaster\fR is a program that uses \fBptp4l\fR and \fBphc2sys\fR in combination with \fBchronyd\fR or \fBntpd\fR to synchronize the system clock to NTP and PTP time sources. The PTP time is provided by \fBphc2sys\fR and \fBptp4l\fR via SHM reference clocks to \fBchronyd\fR/\fBntpd\fR, which can compare all time sources and use the best sources to synchronize the system clock. On start, \fBtimemaster\fR reads a configuration file that specifies the NTP and PTP time sources, checks which network interfaces have and share a PTP hardware clock (PHC), generates configuration files for \fBptp4l\fR and \fBchronyd\fR/\fBntpd\fR, and start the \fBptp4l\fR, \fBphc2sys\fR, \fBchronyd\fR/\fBntpd\fR processes as needed. Then, it waits for a signal to kill the processes, remove the generated configuration files and exit. .SH OPTIONS .TP .BI \-f " file" Specify the path to the \fBtimemaster\fR configuration file. .TP .BI \-n Don't start the programs, only print their configuration files and the commands that would be executed if this option wasn't specified. .TP .BI \-l " level" Set the maximum syslog level of messages which should be printed or sent to the system logger. The default value is 6 (LOG_INFO). .TP .B \-m Print messages to the standard output. .TP .B \-q Don't send messages to the system logger. .TP .B \-v Print the software version and exit. .TP .BI \-h Display a help message and exit. .SH CONFIGURATION FILE The configuration file is divided into sections. Each section starts with a line containing its name enclosed in brackets and it follows with settings. Each setting is placed on a separate line, it contains the name of the option and the value separated by whitespace characters. Empty lines and lines starting with # are ignored. Sections that can used in the configuration file and options that can be set in them are described below. .SS [timemaster] .TP .B ntp_program Select which NTP implementation should be used. Possible values are \fBchronyd\fR and \fBntpd\fR. The default value is \fBchronyd\fR. Limitations of the implementations relevant to the timemaster configuration are listed in \fBNOTES\fR. .TP .B rundir Specify the directory where should be generated \fBchronyd\fR, \fBntpd\fR and \fBptp4l\fR configuration files and sockets. The directory will be created if it doesn't exist. The default value is \fB/var/run/timemaster\fR. .SS [ntp_server address] The \fBntp_server\fR section specifies an NTP server that should be used as a time source. The address of the server is included in the name of the section. .TP .B minpoll .TQ .B maxpoll Specify the minimum and maximum NTP polling interval as powers of two in seconds. The default values are 6 (64 seconds) and 10 (1024 seconds) respectively. Shorter polling intervals usually improve the accuracy significantly, but they should be used only when allowed by the operators of the NTP service (public NTP servers generally don't allow too frequent queries). If the NTP server is located on the same LAN, polling intervals around 4 (16 seconds) might give best accuracy. .TP .B iburst Enable or disable sending a burst of NTP packets on start to speed up the initial synchronization. Possible values are 1 and 0. The default value is 0 (disabled). .SS [ptp_domain number] The \fBptp_domain\fR section specifies a PTP domain that should be used as a time source. The PTP domain number is included in the name of the section. The \fBptp4l\fR instances are configured to run in the \fBslaveOnly\fR mode. In this section at least the \fBinterfaces\fR option needs to be set, other options are optional. .TP .B interfaces Specify which network interfaces should be used for this PTP domain. A separate \fBptp4l\fR instance will be started for each group of interfaces sharing the same PHC and for each interface that supports only SW time stamping. HW time stamping is enabled automatically. If an interface with HW time stamping is specified also in other PTP domains, only the \fBptp4l\fR instance from the first PTP domain will be using HW time stamping. .TP .B ntp_poll Specify the polling interval of the NTP SHM reference clock reading samples from \fBptp4l\fR or \fBphc2sys\fR. It's specified as a power of two in seconds. The default value is 2 (4 seconds). .TP .B phc2sys_poll Specify the polling interval used by \fBphc2sys\fR to read a PTP clock synchronized by \fBptp4l\fR and update the SHM sample for \fBchronyd\fR/\fBntpd\fR. It's specified as a power of two in seconds. The default value is 0 (1 second). .TP .B delay Specify the maximum assumed roundtrip delay to the primary source of the time in this PTP domain. This value is included in the distance used by \fBchronyd\fR in the source selection algorithm to detect falsetickers and assign weights for source combining. The default value is 1e\-4 (100 microseconds). With \fBntpd\fR, the \fBtos mindist\fR command can be used to set a limit with similar purpose globally for all time sources. .TP .B ptp4l_option Specify an extra \fBptp4l\fR option specific to this PTP domain that should be added to the configuration files generated for \fBptp4l\fR. This option may be used multiple times in one \fBptp_domain\fR section. .SS [chronyd] .TP .B path Specify the path to the \fBchronyd\fR binary. The default value is \fBchronyd\fR to search for the binary in \fBPATH\fR. .TP .B options Specify extra options that should be added to the \fBchronyd\fR command line. No extra options are added by default. .SS [chrony.conf] Settings specified in this section are copied directly to the configuration file generated for \fBchronyd\fR. If this section is not present in the \fBtimemaster\fR configuration file, the following setting will be added: .EX makestep 1 3 .EE This configures \fBchronyd\fR to step the system clock in the first three updates if the offset is larger than 1 second. .SS [ntpd] .TP .B path Specify the path to the \fBntpd\fR binary. The default value is \fBntpd\fR to search for the binary in \fBPATH\fR. .TP .B options Specify extra options that should be added to the \fBntpd\fR command line. No extra options are added by default. .SS [ntp.conf] Settings specified in this section are copied directly to the configuration file generated for \fBntpd\fR. If this section is not present in the \fBtimemaster\fR configuration file, the following settings will be added: .EX restrict default nomodify notrap nopeer noquery restrict 127.0.0.1 restrict ::1 .EE This configures \fBntpd\fR to use safe default restrictions. .SS [phc2sys] .TP .B path Specify the path to the \fBphc2sys\fR binary. The default value is \fBphc2sys\fR to search for the binary in \fBPATH\fR. .TP .B options Specify extra options that should be added to all \fBphc2sys\fR command lines. By default, \fB\-l 5\fR is added to the command lines. .SS [ptp4l] .TP .B path Specify the path to the \fBptp4l\fR binary. The default value is \fBptp4l\fR to search for the binary in \fBPATH\fR. .TP .B options Specify extra options that should be added to all \fBptp4l\fR command lines. By default, \fB\-l 5\fR is added to the command lines. .SS [ptp4l.conf] Settings specified in this section are copied directly to the configuration files generated for all \fBptp4l\fR instances. There is no default content of this section. .SH NOTES For best accuracy, \fBchronyd\fR is usually preferred over \fBntpd\fR, it also synchronizes the system clock faster. Both NTP implementations, however, have some limitations that need to be considered before choosing the one to be used in a given \fBtimemaster\fR configuration. The \fBchronyd\fR limitations are: .RS In version 1.31 and older, the maximum number of reference clocks used at the same time is 8. This limits the number of PHCs and interfaces using SW time stamping that can be used for PTP. Using polling intervals (\fBminpoll\fR, \fBmaxpoll\fR, \fBntp_poll\fR options) shorter than 2 (4 seconds) is not recommended with versions before 1.30. With 1.30 and later values of 0 or 1 can be used for NTP sources and negative values for PTP sources (\fBntp_poll\fR) to specify a subsecond interval. .RE The \fBntpd\fR limitations are: .RS In versions before 4.2.8p1, only the first two shared-memory segments created by the \fBntpd\fR SHM refclock driver have owner-only access. Other segments are created with world access, which allows any user on the system to write to the segments and disrupt or take control over the synchronization of the clock. In 4.2.8p1 the access was made configurable with the mode option, which is set by \fBtimemaster\fR for owner-ownly access. The shortest polling interval for all sources is 3 (8 seconds). Nanosecond resolution in the SHM refclock driver is supported in version 4.2.7p303 and later, older versions have only microsecond resolution. .RE .SH EXAMPLES A minimal configuration file using one NTP source and two PTP sources would be: .EX [ntp_server 10.1.1.1] [ptp_domain 0] interfaces eth0 [ptp_domain 1] interfaces eth1 .EE A more complex example using all \fBtimemaster\fR options would be: .EX [ntp_server 10.1.1.1] minpoll 3 maxpoll 4 iburst 1 [ptp_domain 0] interfaces eth0 eth1 ntp_poll 0 phc2sys_poll \-2 delay 10e\-6 ptp4l_option clock_servo linreg ptp4l_option delay_mechanism P2P [timemaster] ntp_program chronyd rundir /var/run/timemaster [chronyd] path /usr/sbin/chronyd options [chrony.conf] makestep 1 3 logchange 0.5 rtcsync driftfile /var/lib/chrony/drift [ntpd] path /usr/sbin/ntpd options \-u ntp:ntp [ntp.conf] restrict default nomodify notrap nopeer noquery restrict 127.0.0.1 restrict ::1 driftfile /var/lib/ntp/drift [phc2sys] path /usr/sbin/phc2sys options \-l 5 [ptp4l] path /usr/sbin/ptp4l options [ptp4l.conf] logging_level 5 .EE .SH SEE ALSO .BR chronyd (8), .BR ntpd (8), .BR phc2sys (8), .BR ptp4l (8) linuxptp-1.6/timemaster.c000066400000000000000000000664051257727010700156040ustar00rootroot00000000000000/** * @file timemaster.c * @brief Program to run NTP with PTP as reference clocks. * @note Copyright (C) 2014 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "print.h" #include "sk.h" #include "util.h" #include "version.h" #define DEFAULT_RUNDIR "/var/run/timemaster" #define DEFAULT_NTP_PROGRAM CHRONYD #define DEFAULT_NTP_MINPOLL 6 #define DEFAULT_NTP_MAXPOLL 10 #define DEFAULT_PTP_DELAY 1e-4 #define DEFAULT_PTP_NTP_POLL 2 #define DEFAULT_PTP_PHC2SYS_POLL 0 #define DEFAULT_CHRONYD_SETTINGS \ "makestep 1 3" #define DEFAULT_NTPD_SETTINGS \ "restrict default nomodify notrap nopeer noquery", \ "restrict 127.0.0.1", \ "restrict ::1" #define DEFAULT_PTP4L_OPTIONS "-l", "5" #define DEFAULT_PHC2SYS_OPTIONS "-l", "5" enum source_type { NTP_SERVER, PTP_DOMAIN, }; enum ntp_program { CHRONYD, NTPD, }; struct ntp_server { char *address; int minpoll; int maxpoll; int iburst; }; struct ptp_domain { int domain; int ntp_poll; int phc2sys_poll; double delay; char **interfaces; char **ptp4l_settings; }; struct source { enum source_type type; union { struct ntp_server ntp; struct ptp_domain ptp; }; }; struct program_config { char *path; char **options; char **settings; }; struct timemaster_config { struct source **sources; enum ntp_program ntp_program; char *rundir; struct program_config chronyd; struct program_config ntpd; struct program_config phc2sys; struct program_config ptp4l; }; struct config_file { char *path; char *content; }; struct script { struct config_file **configs; char ***commands; }; static void free_parray(void **a) { void **p; for (p = a; *p; p++) free(*p); free(a); } static void extend_string_array(char ***a, char **strings) { char **s; for (s = strings; *s; s++) parray_append((void ***)a, xstrdup(*s)); } static void extend_config_string(char **s, char **lines) { for (; *lines; lines++) string_appendf(s, "%s\n", *lines); } static int parse_bool(char *s, int *b) { if (get_ranged_int(s, b, 0, 1) != PARSED_OK) return 1; return 0; } static int parse_int(char *s, int *i) { if (get_ranged_int(s, i, INT_MIN, INT_MAX) != PARSED_OK) return 1; return 0; } static int parse_double(char *s, double *d) { if (get_ranged_double(s, d, INT_MIN, INT_MAX) != PARSED_OK) return 1; return 0; } static char *parse_word(char *s) { while (*s && !isspace(*s)) s++; while (*s && isspace(*s)) *(s++) = '\0'; return s; } static void parse_words(char *s, char ***a) { char *w; if (**a) { free_parray((void **)(*a)); *a = (char **)parray_new(); } while (*s) { w = s; s = parse_word(s); parray_append((void ***)a, xstrdup(w)); } } static void replace_string(char *s, char **str) { if (*str) free(*str); *str = xstrdup(s); } static char *parse_section_name(char *s) { char *s1, *s2; s1 = s + 1; for (s2 = s1; *s2 && *s2 != ']'; s2++) ; *s2 = '\0'; return xstrdup(s1); } static void parse_setting(char *s, char **name, char **value) { *name = s; for (*value = s; **value && !isspace(**value); (*value)++) ; for (; **value && !isspace(**value); (*value)++) ; for (; **value && isspace(**value); (*value)++) **value = '\0'; } static void source_destroy(struct source *source) { switch (source->type) { case NTP_SERVER: free(source->ntp.address); break; case PTP_DOMAIN: free_parray((void **)source->ptp.interfaces); free_parray((void **)source->ptp.ptp4l_settings); break; } free(source); } static struct source *source_ntp_parse(char *parameter, char **settings) { char *name, *value; struct ntp_server ntp_server; struct source *source; int r = 0; if (!*parameter) { pr_err("missing address for ntp_server"); return NULL; } ntp_server.address = parameter; ntp_server.minpoll = DEFAULT_NTP_MINPOLL; ntp_server.maxpoll = DEFAULT_NTP_MAXPOLL; ntp_server.iburst = 0; for (; *settings; settings++) { parse_setting(*settings, &name, &value); if (!strcasecmp(name, "minpoll")) { r = parse_int(value, &ntp_server.minpoll); } else if (!strcasecmp(name, "maxpoll")) { r = parse_int(value, &ntp_server.maxpoll); } else if (!strcasecmp(name, "iburst")) { r = parse_bool(value, &ntp_server.iburst); } else { pr_err("unknown ntp_server setting %s", name); return NULL; } if (r) { pr_err("invalid value %s for %s", value, name); return NULL; } } source = xmalloc(sizeof(*source)); source->type = NTP_SERVER; source->ntp = ntp_server; source->ntp.address = xstrdup(source->ntp.address); return source; } static struct source *source_ptp_parse(char *parameter, char **settings) { char *name, *value; struct source *source; int r = 0; source = xmalloc(sizeof(*source)); source->type = PTP_DOMAIN; source->ptp.delay = DEFAULT_PTP_DELAY; source->ptp.ntp_poll = DEFAULT_PTP_NTP_POLL; source->ptp.phc2sys_poll = DEFAULT_PTP_PHC2SYS_POLL; source->ptp.interfaces = (char **)parray_new(); source->ptp.ptp4l_settings = (char **)parray_new(); if (parse_int(parameter, &source->ptp.domain)) { pr_err("invalid ptp_domain number %s", parameter); goto failed; } for (; *settings; settings++) { parse_setting(*settings, &name, &value); if (!strcasecmp(name, "delay")) { r = parse_double(value, &source->ptp.delay); } else if (!strcasecmp(name, "ntp_poll")) { r = parse_int(value, &source->ptp.ntp_poll); } else if (!strcasecmp(name, "phc2sys_poll")) { r = parse_int(value, &source->ptp.phc2sys_poll); } else if (!strcasecmp(name, "ptp4l_option")) { parray_append((void ***)&source->ptp.ptp4l_settings, xstrdup(value)); } else if (!strcasecmp(name, "interfaces")) { parse_words(value, &source->ptp.interfaces); } else { pr_err("unknown ptp_domain setting %s", name); goto failed; } if (r) { pr_err("invalid value %s for %s", value, name); goto failed; } } if (!*source->ptp.interfaces) { pr_err("no interfaces specified for ptp_domain %d", source->ptp.domain); goto failed; } return source; failed: source_destroy(source); return NULL; } static int parse_program_settings(char **settings, struct program_config *config) { char *name, *value; for (; *settings; settings++) { parse_setting(*settings, &name, &value); if (!strcasecmp(name, "path")) { replace_string(value, &config->path); } else if (!strcasecmp(name, "options")) { parse_words(value, &config->options); } else { pr_err("unknown program setting %s", name); return 1; } } return 0; } static int parse_timemaster_settings(char **settings, struct timemaster_config *config) { char *name, *value; for (; *settings; settings++) { parse_setting(*settings, &name, &value); if (!strcasecmp(name, "ntp_program")) { if (!strcasecmp(value, "chronyd")) { config->ntp_program = CHRONYD; } else if (!strcasecmp(value, "ntpd")) { config->ntp_program = NTPD; } else { pr_err("unknown ntp program %s", value); return 1; } } else if (!strcasecmp(name, "rundir")) { replace_string(value, &config->rundir); } else { pr_err("unknown timemaster setting %s", name); return 1; } } return 0; } static int parse_section(char **settings, char *name, struct timemaster_config *config) { struct source *source = NULL; char ***settings_dst = NULL; char *parameter = parse_word(name); if (!strcasecmp(name, "ntp_server")) { source = source_ntp_parse(parameter, settings); if (!source) return 1; } else if (!strcasecmp(name, "ptp_domain")) { source = source_ptp_parse(parameter, settings); if (!source) return 1; } else if (!strcasecmp(name, "chrony.conf")) { settings_dst = &config->chronyd.settings; } else if (!strcasecmp(name, "ntp.conf")) { settings_dst = &config->ntpd.settings; } else if (!strcasecmp(name, "ptp4l.conf")) { settings_dst = &config->ptp4l.settings; } else if (!strcasecmp(name, "chronyd")) { if (parse_program_settings(settings, &config->chronyd)) return 1; } else if (!strcasecmp(name, "ntpd")) { if (parse_program_settings(settings, &config->ntpd)) return 1; } else if (!strcasecmp(name, "phc2sys")) { if (parse_program_settings(settings, &config->phc2sys)) return 1; } else if (!strcasecmp(name, "ptp4l")) { if (parse_program_settings(settings, &config->ptp4l)) return 1; } else if (!strcasecmp(name, "timemaster")) { if (parse_timemaster_settings(settings, config)) return 1; } else { pr_err("unknown section %s", name); return 1; } if (source) parray_append((void ***)&config->sources, source); if (settings_dst) { free_parray((void **)*settings_dst); *settings_dst = (char **)parray_new(); extend_string_array(settings_dst, settings); } return 0; } static void init_program_config(struct program_config *config, const char *name, ...) { const char *s; va_list ap; config->path = xstrdup(name); config->settings = (char **)parray_new(); config->options = (char **)parray_new(); va_start(ap, name); /* add default options and settings */ while ((s = va_arg(ap, const char *))) parray_append((void ***)&config->options, xstrdup(s)); while ((s = va_arg(ap, const char *))) parray_append((void ***)&config->settings, xstrdup(s)); va_end(ap); } static void free_program_config(struct program_config *config) { free(config->path); free_parray((void **)config->settings); free_parray((void **)config->options); } static void config_destroy(struct timemaster_config *config) { struct source **sources; for (sources = config->sources; *sources; sources++) source_destroy(*sources); free(config->sources); free_program_config(&config->chronyd); free_program_config(&config->ntpd); free_program_config(&config->phc2sys); free_program_config(&config->ptp4l); free(config->rundir); free(config); } static struct timemaster_config *config_parse(char *path) { struct timemaster_config *config = xcalloc(1, sizeof(*config)); FILE *f; char buf[4096], *line, *section_name = NULL; char **section_lines = NULL; int ret = 0; config->sources = (struct source **)parray_new(); config->ntp_program = DEFAULT_NTP_PROGRAM; config->rundir = xstrdup(DEFAULT_RUNDIR); init_program_config(&config->chronyd, "chronyd", NULL, DEFAULT_CHRONYD_SETTINGS, NULL); init_program_config(&config->ntpd, "ntpd", NULL, DEFAULT_NTPD_SETTINGS, NULL); init_program_config(&config->phc2sys, "phc2sys", DEFAULT_PHC2SYS_OPTIONS, NULL, NULL); init_program_config(&config->ptp4l, "ptp4l", DEFAULT_PTP4L_OPTIONS, NULL, NULL); f = fopen(path, "r"); if (!f) { pr_err("failed to open %s: %m", path); free(config); return NULL; } while (fgets(buf, sizeof(buf), f)) { /* remove trailing and leading whitespace */ for (line = buf + strlen(buf) - 1; line >= buf && isspace(*line); line--) *line = '\0'; for (line = buf; *line && isspace(*line); line++) ; /* skip comments and empty lines */ if (!*line || *line == '#') continue; if (*line == '[') { /* parse previous section before starting another */ if (section_name) { if (parse_section(section_lines, section_name, config)) { ret = 1; break; } free_parray((void **)section_lines); free(section_name); } section_name = parse_section_name(line); section_lines = (char **)parray_new(); continue; } if (!section_lines) { pr_err("settings outside section"); ret = 1; break; } parray_append((void ***)§ion_lines, xstrdup(line)); } if (!ret && section_name && parse_section(section_lines, section_name, config)) { ret = 1; } fclose(f); if (section_name) free(section_name); if (section_lines) free_parray((void **)section_lines); if (ret) { config_destroy(config); return NULL; } return config; } static char **get_ptp4l_command(struct program_config *config, struct config_file *file, char **interfaces, int hw_ts) { char **command = (char **)parray_new(); parray_append((void ***)&command, xstrdup(config->path)); extend_string_array(&command, config->options); parray_extend((void ***)&command, xstrdup("-f"), xstrdup(file->path), xstrdup(hw_ts ? "-H" : "-S"), NULL); for (; *interfaces; interfaces++) parray_extend((void ***)&command, xstrdup("-i"), xstrdup(*interfaces), NULL); return command; } static char **get_phc2sys_command(struct program_config *config, int domain, int poll, int shm_segment, char *uds_path) { char **command = (char **)parray_new(); parray_append((void ***)&command, xstrdup(config->path)); extend_string_array(&command, config->options); parray_extend((void ***)&command, xstrdup("-a"), xstrdup("-r"), xstrdup("-R"), string_newf("%.2f", poll > 0 ? 1.0 / (1 << poll) : 1 << -poll), xstrdup("-z"), xstrdup(uds_path), xstrdup("-n"), string_newf("%d", domain), xstrdup("-E"), xstrdup("ntpshm"), xstrdup("-M"), string_newf("%d", shm_segment), NULL); return command; } static char *get_refid(char *prefix, unsigned int number) { if (number < 10) return string_newf("%.3s%u", prefix, number); else if (number < 100) return string_newf("%.2s%u", prefix, number); else if (number < 1000) return string_newf("%.1s%u", prefix, number); return NULL; }; static void add_shm_source(int shm_segment, int poll, int dpoll, double delay, char *prefix, struct timemaster_config *config, char **ntp_config) { char *refid = get_refid(prefix, shm_segment); switch (config->ntp_program) { case CHRONYD: string_appendf(ntp_config, "refclock SHM %d poll %d dpoll %d " "refid %s precision 1.0e-9 delay %.1e\n", shm_segment, poll, dpoll, refid, delay); break; case NTPD: string_appendf(ntp_config, "server 127.127.28.%d minpoll %d maxpoll %d " "mode 1\n" "fudge 127.127.28.%d refid %s\n", shm_segment, poll, poll, shm_segment, refid); break; } free(refid); } static int add_ntp_source(struct ntp_server *source, char **ntp_config) { pr_debug("adding NTP server %s", source->address); string_appendf(ntp_config, "server %s minpoll %d maxpoll %d%s\n", source->address, source->minpoll, source->maxpoll, source->iburst ? " iburst" : ""); return 0; } static int add_ptp_source(struct ptp_domain *source, struct timemaster_config *config, int *shm_segment, int ***allocated_phcs, char **ntp_config, struct script *script) { struct config_file *config_file; char **command, *uds_path, **interfaces; int i, j, num_interfaces, *phc, *phcs, hw_ts; struct sk_ts_info ts_info; pr_debug("adding PTP domain %d", source->domain); hw_ts = SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE; for (num_interfaces = 0; source->interfaces[num_interfaces]; num_interfaces++) ; if (!num_interfaces) return 0; /* get PHCs used by specified interfaces */ phcs = xmalloc(num_interfaces * sizeof(int)); for (i = 0; i < num_interfaces; i++) { phcs[i] = -1; /* check if the interface has a usable PHC */ if (sk_get_ts_info(source->interfaces[i], &ts_info)) { pr_err("failed to get time stamping info for %s", source->interfaces[i]); free(phcs); return 1; } if (!ts_info.valid || ((ts_info.so_timestamping & hw_ts) != hw_ts)) { pr_debug("interface %s: no PHC", source->interfaces[i]); continue; } pr_debug("interface %s: PHC %d", source->interfaces[i], ts_info.phc_index); /* and the PHC isn't already used in another source */ for (j = 0; (*allocated_phcs)[j]; j++) { if (*(*allocated_phcs)[j] == ts_info.phc_index) { pr_debug("PHC %d already allocated", ts_info.phc_index); break; } } if (!(*allocated_phcs)[j]) phcs[i] = ts_info.phc_index; } for (i = 0; i < num_interfaces; i++) { /* skip if already used by ptp4l in this domain */ if (phcs[i] == -2) continue; interfaces = (char **)parray_new(); parray_append((void ***)&interfaces, source->interfaces[i]); /* merge all interfaces sharing PHC to one ptp4l command */ if (phcs[i] >= 0) { for (j = i + 1; j < num_interfaces; j++) { if (phcs[i] == phcs[j]) { parray_append((void ***)&interfaces, source->interfaces[j]); /* mark the interface as used */ phcs[j] = -2; } } /* don't use this PHC in other sources */ phc = xmalloc(sizeof(int)); *phc = phcs[i]; parray_append((void ***)allocated_phcs, phc); } uds_path = string_newf("%s/ptp4l.%d.socket", config->rundir, *shm_segment); config_file = xmalloc(sizeof(*config_file)); config_file->path = string_newf("%s/ptp4l.%d.conf", config->rundir, *shm_segment); config_file->content = xstrdup("[global]\n"); extend_config_string(&config_file->content, config->ptp4l.settings); extend_config_string(&config_file->content, source->ptp4l_settings); string_appendf(&config_file->content, "slaveOnly 1\n" "domainNumber %d\n" "uds_address %s\n", source->domain, uds_path); if (phcs[i] >= 0) { /* HW time stamping */ command = get_ptp4l_command(&config->ptp4l, config_file, interfaces, 1); parray_append((void ***)&script->commands, command); command = get_phc2sys_command(&config->phc2sys, source->domain, source->phc2sys_poll, *shm_segment, uds_path); parray_append((void ***)&script->commands, command); } else { /* SW time stamping */ command = get_ptp4l_command(&config->ptp4l, config_file, interfaces, 0); parray_append((void ***)&script->commands, command); string_appendf(&config_file->content, "clock_servo ntpshm\n" "ntpshm_segment %d\n", *shm_segment); } parray_append((void ***)&script->configs, config_file); add_shm_source(*shm_segment, source->ntp_poll, source->phc2sys_poll, source->delay, "PTP", config, ntp_config); (*shm_segment)++; free(uds_path); free(interfaces); } free(phcs); return 0; } static char **get_chronyd_command(struct program_config *config, struct config_file *file) { char **command = (char **)parray_new(); parray_append((void ***)&command, xstrdup(config->path)); extend_string_array(&command, config->options); parray_extend((void ***)&command, xstrdup("-n"), xstrdup("-f"), xstrdup(file->path), NULL); return command; } static char **get_ntpd_command(struct program_config *config, struct config_file *file) { char **command = (char **)parray_new(); parray_append((void ***)&command, xstrdup(config->path)); extend_string_array(&command, config->options); parray_extend((void ***)&command, xstrdup("-n"), xstrdup("-c"), xstrdup(file->path), NULL); return command; } static struct config_file *add_ntp_program(struct timemaster_config *config, struct script *script) { struct config_file *ntp_config = xmalloc(sizeof(*ntp_config)); char **command = NULL; ntp_config->content = xstrdup(""); switch (config->ntp_program) { case CHRONYD: extend_config_string(&ntp_config->content, config->chronyd.settings); ntp_config->path = string_newf("%s/chrony.conf", config->rundir); command = get_chronyd_command(&config->chronyd, ntp_config); break; case NTPD: extend_config_string(&ntp_config->content, config->ntpd.settings); ntp_config->path = string_newf("%s/ntp.conf", config->rundir); command = get_ntpd_command(&config->ntpd, ntp_config); break; } parray_append((void ***)&script->configs, ntp_config); parray_append((void ***)&script->commands, command); return ntp_config; } static void script_destroy(struct script *script) { char ***commands, **command; struct config_file *config, **configs; for (configs = script->configs; *configs; configs++) { config = *configs; free(config->path); free(config->content); free(config); } free(script->configs); for (commands = script->commands; *commands; commands++) { for (command = *commands; *command; command++) free(*command); free(*commands); } free(script->commands); free(script); } static struct script *script_create(struct timemaster_config *config) { struct script *script = xmalloc(sizeof(*script)); struct source *source, **sources; struct config_file *ntp_config = NULL; int **allocated_phcs = (int **)parray_new(); int ret = 0, shm_segment = 0; script->configs = (struct config_file **)parray_new(); script->commands = (char ***)parray_new(); ntp_config = add_ntp_program(config, script); for (sources = config->sources; (source = *sources); sources++) { switch (source->type) { case NTP_SERVER: if (add_ntp_source(&source->ntp, &ntp_config->content)) ret = 1; break; case PTP_DOMAIN: if (add_ptp_source(&source->ptp, config, &shm_segment, &allocated_phcs, &ntp_config->content, script)) ret = 1; break; } } free_parray((void **)allocated_phcs); if (ret) { script_destroy(script); return NULL; } return script; } static pid_t start_program(char **command, sigset_t *mask) { char **arg, *s; pid_t pid; #ifdef HAVE_POSIX_SPAWN posix_spawnattr_t attr; if (posix_spawnattr_init(&attr)) { pr_err("failed to init spawn attributes: %m"); return 0; } if (posix_spawnattr_setsigmask(&attr, mask) || posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK) || posix_spawnp(&pid, command[0], NULL, &attr, command, environ)) { pr_err("failed to spawn %s: %m", command[0]); posix_spawnattr_destroy(&attr); return 0; } posix_spawnattr_destroy(&attr); #else pid = fork(); if (pid < 0) { pr_err("fork() failed: %m"); return 0; } if (!pid) { /* restore the signal mask */ if (sigprocmask(SIG_SETMASK, mask, NULL) < 0) { pr_err("sigprocmask() failed: %m"); exit(100); } execvp(command[0], (char **)command); pr_err("failed to execute %s: %m", command[0]); exit(101); } #endif for (s = xstrdup(""), arg = command; *arg; arg++) string_appendf(&s, "%s ", *arg); pr_info("process %d started: %s", pid, s); free(s); return pid; } static int create_config_files(struct config_file **configs) { struct config_file *config; FILE *file; char *tmp, *dir; struct stat st; for (; (config = *configs); configs++) { tmp = xstrdup(config->path); dir = dirname(tmp); if (stat(dir, &st) < 0 && errno == ENOENT && mkdir(dir, 0755) < 0) { pr_err("failed to create %s: %m", dir); free(tmp); return 1; } free(tmp); pr_debug("creating %s", config->path); file = fopen(config->path, "w"); if (!file) { pr_err("failed to open %s: %m", config->path); return 1; } if (fwrite(config->content, strlen(config->content), 1, file) != 1) { pr_err("failed to write to %s", config->path); fclose(file); return 1; } fclose(file); } return 0; } static int remove_config_files(struct config_file **configs) { struct config_file *config; for (; (config = *configs); configs++) { pr_debug("removing %s", config->path); if (unlink(config->path)) pr_err("failed to remove %s: %m", config->path); } return 0; } static int script_run(struct script *script) { sigset_t mask, old_mask; siginfo_t info; pid_t pid, *pids; int i, num_commands, status, ret = 0; if (create_config_files(script->configs)) return 1; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGQUIT); sigaddset(&mask, SIGINT); /* block the signals */ if (sigprocmask(SIG_BLOCK, &mask, &old_mask) < 0) { pr_err("sigprocmask() failed: %m"); return 1; } for (num_commands = 0; script->commands[num_commands]; num_commands++) ; pids = xcalloc(num_commands, sizeof(*pids)); for (i = 0; i < num_commands; i++) { pids[i] = start_program(script->commands[i], &old_mask); if (!pids[i]) { kill(getpid(), SIGTERM); break; } } /* wait for one of the blocked signals */ while (1) { if (sigwaitinfo(&mask, &info) > 0) break; if (errno != EINTR) { pr_err("sigwaitinfo() failed: %m"); break; } } pr_info("received signal %d", info.si_signo); /* kill all started processes */ for (i = 0; i < num_commands; i++) { if (pids[i] > 0) { pr_debug("killing process %d", pids[i]); kill(pids[i], SIGTERM); } } while ((pid = wait(&status)) >= 0) { if (!WIFEXITED(status)) { pr_info("process %d terminated abnormally", pid); ret = 1; } else { if (WEXITSTATUS(status)) ret = 1; pr_info("process %d terminated with status %d", pid, WEXITSTATUS(status)); } } free(pids); if (remove_config_files(script->configs)) return 1; return ret; } static void script_print(struct script *script) { char ***commands, **command; struct config_file *config, **configs; for (configs = script->configs; *configs; configs++) { config = *configs; fprintf(stderr, "%s:\n\n%s\n", config->path, config->content); } fprintf(stderr, "commands:\n\n"); for (commands = script->commands; *commands; commands++) { for (command = *commands; *command; command++) fprintf(stderr, "%s ", *command); fprintf(stderr, "\n"); } } static void usage(char *progname) { fprintf(stderr, "\nusage: %s [options] -f file\n\n" " -f file specify path to configuration file\n" " -n only print generated files and commands\n" " -l level set logging level (6)\n" " -m print messages to stdout\n" " -q do not print messages to syslog\n" " -v print version and exit\n" " -h print this message and exit\n", progname); } int main(int argc, char **argv) { struct timemaster_config *config; struct script *script; char *progname, *config_path = NULL; int c, ret = 0, log_stdout = 0, log_syslog = 1, dry_run = 0; progname = strrchr(argv[0], '/'); progname = progname ? progname + 1 : argv[0]; print_set_progname(progname); print_set_verbose(1); print_set_syslog(0); while (EOF != (c = getopt(argc, argv, "f:nl:mqvh"))) { switch (c) { case 'f': config_path = optarg; break; case 'n': dry_run = 1; break; case 'l': print_set_level(atoi(optarg)); break; case 'm': log_stdout = 1; break; case 'q': log_syslog = 0; break; case 'v': version_show(stdout); return 0; case 'h': usage(progname); return 0; default: usage(progname); return 1; } } if (!config_path) { pr_err("no configuration file specified"); return 1; } config = config_parse(config_path); if (!config) return 1; script = script_create(config); config_destroy(config); if (!script) return 1; print_set_verbose(log_stdout); print_set_syslog(log_syslog); if (dry_run) script_print(script); else ret = script_run(script); script_destroy(script); if (!dry_run) pr_info("exiting"); return ret; } linuxptp-1.6/tlv.c000066400000000000000000000355521257727010700142360ustar00rootroot00000000000000/** * @file tlv.c * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "port.h" #include "tlv.h" #include "msg.h" #define TLV_LENGTH_INVALID(tlv, type) \ (tlv->length < sizeof(struct type) - sizeof(struct TLV)) uint8_t ieee8021_id[3] = { IEEE_802_1_COMMITTEE }; static void scaled_ns_n2h(ScaledNs *sns) { sns->nanoseconds_msb = ntohs(sns->nanoseconds_msb); sns->nanoseconds_lsb = net2host64(sns->nanoseconds_msb); sns->fractional_nanoseconds = ntohs(sns->fractional_nanoseconds); } static void scaled_ns_h2n(ScaledNs *sns) { sns->nanoseconds_msb = htons(sns->nanoseconds_msb); sns->nanoseconds_lsb = host2net64(sns->nanoseconds_msb); sns->fractional_nanoseconds = htons(sns->fractional_nanoseconds); } static uint16_t flip16(uint16_t *p) { uint16_t v; memcpy(&v, p, sizeof(v)); v = htons(v); memcpy(p, &v, sizeof(v)); return v; } static int mgt_post_recv(struct management_tlv *m, uint16_t data_len, struct tlv_extra *extra) { struct defaultDS *dds; struct currentDS *cds; struct parentDS *pds; struct timePropertiesDS *tp; struct portDS *p; struct port_ds_np *pdsnp; struct time_status_np *tsn; struct grandmaster_settings_np *gsn; struct subscribe_events_np *sen; struct port_properties_np *ppn; struct mgmt_clock_description *cd; int extra_len = 0, len; uint8_t *buf; uint16_t u16; switch (m->id) { case TLV_CLOCK_DESCRIPTION: cd = &extra->cd; buf = m->data; len = data_len; cd->clockType = (UInteger16 *) buf; buf += sizeof(*cd->clockType); len -= sizeof(*cd->clockType); if (len < 0) goto bad_length; flip16(cd->clockType); cd->physicalLayerProtocol = (struct PTPText *) buf; buf += sizeof(struct PTPText); len -= sizeof(struct PTPText); if (len < 0) goto bad_length; buf += cd->physicalLayerProtocol->length; len -= cd->physicalLayerProtocol->length; if (len < 0) goto bad_length; cd->physicalAddress = (struct PhysicalAddress *) buf; buf += sizeof(struct PhysicalAddress); len -= sizeof(struct PhysicalAddress); if (len < 0) goto bad_length; u16 = flip16(&cd->physicalAddress->length); if (u16 > TRANSPORT_ADDR_LEN) goto bad_length; buf += u16; len -= u16; if (len < 0) goto bad_length; cd->protocolAddress = (struct PortAddress *) buf; buf += sizeof(struct PortAddress); len -= sizeof(struct PortAddress); if (len < 0) goto bad_length; flip16(&cd->protocolAddress->networkProtocol); u16 = flip16(&cd->protocolAddress->addressLength); if (u16 > TRANSPORT_ADDR_LEN) goto bad_length; buf += u16; len -= u16; if (len < 0) goto bad_length; cd->manufacturerIdentity = buf; buf += OUI_LEN + 1; len -= OUI_LEN + 1; if (len < 0) goto bad_length; cd->productDescription = (struct PTPText *) buf; buf += sizeof(struct PTPText); len -= sizeof(struct PTPText); if (len < 0) goto bad_length; buf += cd->productDescription->length; len -= cd->productDescription->length; if (len < 0) goto bad_length; cd->revisionData = (struct PTPText *) buf; buf += sizeof(struct PTPText); len -= sizeof(struct PTPText); if (len < 0) goto bad_length; buf += cd->revisionData->length; len -= cd->revisionData->length; if (len < 0) goto bad_length; cd->userDescription = (struct PTPText *) buf; buf += sizeof(struct PTPText); len -= sizeof(struct PTPText); if (len < 0) goto bad_length; buf += cd->userDescription->length; len -= cd->userDescription->length; if (len < 0) goto bad_length; cd->profileIdentity = buf; buf += PROFILE_ID_LEN; len -= PROFILE_ID_LEN; if (len < 0) goto bad_length; extra_len = buf - m->data; break; case TLV_USER_DESCRIPTION: if (data_len < sizeof(struct PTPText)) goto bad_length; extra->cd.userDescription = (struct PTPText *) m->data; extra_len = sizeof(struct PTPText); extra_len += extra->cd.userDescription->length; break; case TLV_DEFAULT_DATA_SET: if (data_len != sizeof(struct defaultDS)) goto bad_length; dds = (struct defaultDS *) m->data; dds->numberPorts = ntohs(dds->numberPorts); dds->clockQuality.offsetScaledLogVariance = ntohs(dds->clockQuality.offsetScaledLogVariance); break; case TLV_CURRENT_DATA_SET: if (data_len != sizeof(struct currentDS)) goto bad_length; cds = (struct currentDS *) m->data; cds->stepsRemoved = ntohs(cds->stepsRemoved); cds->offsetFromMaster = net2host64(cds->offsetFromMaster); cds->meanPathDelay = net2host64(cds->meanPathDelay); break; case TLV_PARENT_DATA_SET: if (data_len != sizeof(struct parentDS)) goto bad_length; pds = (struct parentDS *) m->data; pds->parentPortIdentity.portNumber = ntohs(pds->parentPortIdentity.portNumber); pds->observedParentOffsetScaledLogVariance = ntohs(pds->observedParentOffsetScaledLogVariance); pds->observedParentClockPhaseChangeRate = ntohl(pds->observedParentClockPhaseChangeRate); pds->grandmasterClockQuality.offsetScaledLogVariance = ntohs(pds->grandmasterClockQuality.offsetScaledLogVariance); break; case TLV_TIME_PROPERTIES_DATA_SET: if (data_len != sizeof(struct timePropertiesDS)) goto bad_length; tp = (struct timePropertiesDS *) m->data; tp->currentUtcOffset = ntohs(tp->currentUtcOffset); break; case TLV_PORT_DATA_SET: if (data_len != sizeof(struct portDS)) goto bad_length; p = (struct portDS *) m->data; p->portIdentity.portNumber = ntohs(p->portIdentity.portNumber); p->peerMeanPathDelay = net2host64(p->peerMeanPathDelay); break; case TLV_TIME_STATUS_NP: if (data_len != sizeof(struct time_status_np)) goto bad_length; tsn = (struct time_status_np *) m->data; tsn->master_offset = net2host64(tsn->master_offset); tsn->ingress_time = net2host64(tsn->ingress_time); tsn->cumulativeScaledRateOffset = ntohl(tsn->cumulativeScaledRateOffset); tsn->scaledLastGmPhaseChange = ntohl(tsn->scaledLastGmPhaseChange); tsn->gmTimeBaseIndicator = ntohs(tsn->gmTimeBaseIndicator); scaled_ns_n2h(&tsn->lastGmPhaseChange); tsn->gmPresent = ntohl(tsn->gmPresent); break; case TLV_GRANDMASTER_SETTINGS_NP: if (data_len != sizeof(struct grandmaster_settings_np)) goto bad_length; gsn = (struct grandmaster_settings_np *) m->data; gsn->clockQuality.offsetScaledLogVariance = ntohs(gsn->clockQuality.offsetScaledLogVariance); gsn->utc_offset = ntohs(gsn->utc_offset); break; case TLV_PORT_DATA_SET_NP: if (data_len != sizeof(struct port_ds_np)) goto bad_length; pdsnp = (struct port_ds_np *) m->data; pdsnp->neighborPropDelayThresh = ntohl(pdsnp->neighborPropDelayThresh); pdsnp->asCapable = ntohl(pdsnp->asCapable); break; case TLV_SUBSCRIBE_EVENTS_NP: if (data_len != sizeof(struct subscribe_events_np)) goto bad_length; sen = (struct subscribe_events_np *)m->data; sen->duration = ntohs(sen->duration); break; case TLV_PORT_PROPERTIES_NP: if (data_len < sizeof(struct port_properties_np)) goto bad_length; ppn = (struct port_properties_np *)m->data; ppn->portIdentity.portNumber = ntohs(ppn->portIdentity.portNumber); extra_len = sizeof(struct port_properties_np); extra_len += ppn->interface.length; break; case TLV_SAVE_IN_NON_VOLATILE_STORAGE: case TLV_RESET_NON_VOLATILE_STORAGE: case TLV_INITIALIZE: case TLV_FAULT_LOG_RESET: case TLV_ENABLE_PORT: case TLV_DISABLE_PORT: if (data_len != 0) goto bad_length; break; } if (extra_len) { if (extra_len % 2) extra_len++; if (extra_len + sizeof(m->id) != m->length) goto bad_length; } return 0; bad_length: return -EBADMSG; } static void mgt_pre_send(struct management_tlv *m, struct tlv_extra *extra) { struct defaultDS *dds; struct currentDS *cds; struct parentDS *pds; struct timePropertiesDS *tp; struct portDS *p; struct port_ds_np *pdsnp; struct time_status_np *tsn; struct grandmaster_settings_np *gsn; struct subscribe_events_np *sen; struct port_properties_np *ppn; struct mgmt_clock_description *cd; switch (m->id) { case TLV_CLOCK_DESCRIPTION: if (extra) { cd = &extra->cd; flip16(cd->clockType); flip16(&cd->physicalAddress->length); flip16(&cd->protocolAddress->networkProtocol); flip16(&cd->protocolAddress->addressLength); } break; case TLV_DEFAULT_DATA_SET: dds = (struct defaultDS *) m->data; dds->numberPorts = htons(dds->numberPorts); dds->clockQuality.offsetScaledLogVariance = htons(dds->clockQuality.offsetScaledLogVariance); break; case TLV_CURRENT_DATA_SET: cds = (struct currentDS *) m->data; cds->stepsRemoved = htons(cds->stepsRemoved); cds->offsetFromMaster = host2net64(cds->offsetFromMaster); cds->meanPathDelay = host2net64(cds->meanPathDelay); break; case TLV_PARENT_DATA_SET: pds = (struct parentDS *) m->data; pds->parentPortIdentity.portNumber = htons(pds->parentPortIdentity.portNumber); pds->observedParentOffsetScaledLogVariance = htons(pds->observedParentOffsetScaledLogVariance); pds->observedParentClockPhaseChangeRate = htonl(pds->observedParentClockPhaseChangeRate); pds->grandmasterClockQuality.offsetScaledLogVariance = htons(pds->grandmasterClockQuality.offsetScaledLogVariance); break; case TLV_TIME_PROPERTIES_DATA_SET: tp = (struct timePropertiesDS *) m->data; tp->currentUtcOffset = htons(tp->currentUtcOffset); break; case TLV_PORT_DATA_SET: p = (struct portDS *) m->data; p->portIdentity.portNumber = htons(p->portIdentity.portNumber); p->peerMeanPathDelay = host2net64(p->peerMeanPathDelay); break; case TLV_TIME_STATUS_NP: tsn = (struct time_status_np *) m->data; tsn->master_offset = host2net64(tsn->master_offset); tsn->ingress_time = host2net64(tsn->ingress_time); tsn->cumulativeScaledRateOffset = htonl(tsn->cumulativeScaledRateOffset); tsn->scaledLastGmPhaseChange = htonl(tsn->scaledLastGmPhaseChange); tsn->gmTimeBaseIndicator = htons(tsn->gmTimeBaseIndicator); scaled_ns_h2n(&tsn->lastGmPhaseChange); tsn->gmPresent = htonl(tsn->gmPresent); break; case TLV_GRANDMASTER_SETTINGS_NP: gsn = (struct grandmaster_settings_np *) m->data; gsn->clockQuality.offsetScaledLogVariance = htons(gsn->clockQuality.offsetScaledLogVariance); gsn->utc_offset = htons(gsn->utc_offset); break; case TLV_PORT_DATA_SET_NP: pdsnp = (struct port_ds_np *) m->data; pdsnp->neighborPropDelayThresh = htonl(pdsnp->neighborPropDelayThresh); pdsnp->asCapable = htonl(pdsnp->asCapable); break; case TLV_SUBSCRIBE_EVENTS_NP: sen = (struct subscribe_events_np *)m->data; sen->duration = htons(sen->duration); break; case TLV_PORT_PROPERTIES_NP: ppn = (struct port_properties_np *)m->data; ppn->portIdentity.portNumber = htons(ppn->portIdentity.portNumber); break; } } static int org_post_recv(struct organization_tlv *org) { struct follow_up_info_tlv *f; if (0 == memcmp(org->id, ieee8021_id, sizeof(ieee8021_id))) { if (org->subtype[0] || org->subtype[1]) { return 0; } switch (org->subtype[2]) { case 1: if (org->length + sizeof(struct TLV) != sizeof(struct follow_up_info_tlv)) goto bad_length; f = (struct follow_up_info_tlv *) org; f->cumulativeScaledRateOffset = ntohl(f->cumulativeScaledRateOffset); f->gmTimeBaseIndicator = ntohs(f->gmTimeBaseIndicator); scaled_ns_n2h(&f->lastGmPhaseChange); f->scaledLastGmPhaseChange = ntohl(f->scaledLastGmPhaseChange); break; } } return 0; bad_length: return -EBADMSG; } static void org_pre_send(struct organization_tlv *org) { struct follow_up_info_tlv *f; if (0 == memcmp(org->id, ieee8021_id, sizeof(ieee8021_id))) { if (org->subtype[0] || org->subtype[1]) { return; } switch (org->subtype[2]) { case 1: f = (struct follow_up_info_tlv *) org; f->cumulativeScaledRateOffset = htonl(f->cumulativeScaledRateOffset); f->gmTimeBaseIndicator = htons(f->gmTimeBaseIndicator); scaled_ns_h2n(&f->lastGmPhaseChange); f->scaledLastGmPhaseChange = htonl(f->scaledLastGmPhaseChange); break; } } } int tlv_post_recv(struct TLV *tlv, struct tlv_extra *extra) { int result = 0; struct management_tlv *mgt; struct management_error_status *mes; struct path_trace_tlv *ptt; struct tlv_extra dummy_extra; if (!extra) extra = &dummy_extra; switch (tlv->type) { case TLV_MANAGEMENT: if (TLV_LENGTH_INVALID(tlv, management_tlv)) goto bad_length; mgt = (struct management_tlv *) tlv; mgt->id = ntohs(mgt->id); if (tlv->length > sizeof(mgt->id)) result = mgt_post_recv(mgt, tlv->length - sizeof(mgt->id), extra); break; case TLV_MANAGEMENT_ERROR_STATUS: if (TLV_LENGTH_INVALID(tlv, management_error_status)) goto bad_length; mes = (struct management_error_status *) tlv; mes->error = ntohs(mes->error); mes->id = ntohs(mes->id); break; case TLV_ORGANIZATION_EXTENSION: if (TLV_LENGTH_INVALID(tlv, organization_tlv)) goto bad_length; result = org_post_recv((struct organization_tlv *) tlv); break; case TLV_REQUEST_UNICAST_TRANSMISSION: case TLV_GRANT_UNICAST_TRANSMISSION: case TLV_CANCEL_UNICAST_TRANSMISSION: case TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION: break; case TLV_PATH_TRACE: ptt = (struct path_trace_tlv *) tlv; if (path_length(ptt) > PATH_TRACE_MAX) { ptt->length = PATH_TRACE_MAX * sizeof(struct ClockIdentity); } break; case TLV_ALTERNATE_TIME_OFFSET_INDICATOR: case TLV_AUTHENTICATION: case TLV_AUTHENTICATION_CHALLENGE: case TLV_SECURITY_ASSOCIATION_UPDATE: case TLV_CUM_FREQ_SCALE_FACTOR_OFFSET: default: break; } return result; bad_length: return -EBADMSG; } void tlv_pre_send(struct TLV *tlv, struct tlv_extra *extra) { struct management_tlv *mgt; struct management_error_status *mes; switch (tlv->type) { case TLV_MANAGEMENT: mgt = (struct management_tlv *) tlv; if (tlv->length > sizeof(mgt->id)) mgt_pre_send(mgt, extra); mgt->id = htons(mgt->id); break; case TLV_MANAGEMENT_ERROR_STATUS: mes = (struct management_error_status *) tlv; mes->error = htons(mes->error); mes->id = htons(mes->id); break; case TLV_ORGANIZATION_EXTENSION: org_pre_send((struct organization_tlv *) tlv); break; case TLV_REQUEST_UNICAST_TRANSMISSION: case TLV_GRANT_UNICAST_TRANSMISSION: case TLV_CANCEL_UNICAST_TRANSMISSION: case TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION: case TLV_PATH_TRACE: case TLV_ALTERNATE_TIME_OFFSET_INDICATOR: case TLV_AUTHENTICATION: case TLV_AUTHENTICATION_CHALLENGE: case TLV_SECURITY_ASSOCIATION_UPDATE: case TLV_CUM_FREQ_SCALE_FACTOR_OFFSET: default: break; } } linuxptp-1.6/tlv.h000066400000000000000000000171511257727010700142360ustar00rootroot00000000000000/** * @file tlv.h * @brief Implements helper routines for processing Type Length Value fields. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_TLV_H #define HAVE_TLV_H #include "ddt.h" #include "ds.h" /* TLV types */ #define TLV_MANAGEMENT 0x0001 #define TLV_MANAGEMENT_ERROR_STATUS 0x0002 #define TLV_ORGANIZATION_EXTENSION 0x0003 #define TLV_REQUEST_UNICAST_TRANSMISSION 0x0004 #define TLV_GRANT_UNICAST_TRANSMISSION 0x0005 #define TLV_CANCEL_UNICAST_TRANSMISSION 0x0006 #define TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION 0x0007 #define TLV_PATH_TRACE 0x0008 #define TLV_ALTERNATE_TIME_OFFSET_INDICATOR 0x0009 #define TLV_AUTHENTICATION 0x2000 #define TLV_AUTHENTICATION_CHALLENGE 0x2001 #define TLV_SECURITY_ASSOCIATION_UPDATE 0x2002 #define TLV_CUM_FREQ_SCALE_FACTOR_OFFSET 0x2003 enum management_action { GET, SET, RESPONSE, COMMAND, ACKNOWLEDGE, }; /* Clock management ID values */ #define TLV_USER_DESCRIPTION 0x0002 #define TLV_SAVE_IN_NON_VOLATILE_STORAGE 0x0003 #define TLV_RESET_NON_VOLATILE_STORAGE 0x0004 #define TLV_INITIALIZE 0x0005 #define TLV_FAULT_LOG 0x0006 #define TLV_FAULT_LOG_RESET 0x0007 #define TLV_DEFAULT_DATA_SET 0x2000 #define TLV_CURRENT_DATA_SET 0x2001 #define TLV_PARENT_DATA_SET 0x2002 #define TLV_TIME_PROPERTIES_DATA_SET 0x2003 #define TLV_PRIORITY1 0x2005 #define TLV_PRIORITY2 0x2006 #define TLV_DOMAIN 0x2007 #define TLV_SLAVE_ONLY 0x2008 #define TLV_TIME 0x200F #define TLV_CLOCK_ACCURACY 0x2010 #define TLV_UTC_PROPERTIES 0x2011 #define TLV_TRACEABILITY_PROPERTIES 0x2012 #define TLV_TIMESCALE_PROPERTIES 0x2013 #define TLV_PATH_TRACE_LIST 0x2015 #define TLV_PATH_TRACE_ENABLE 0x2016 #define TLV_GRANDMASTER_CLUSTER_TABLE 0x2017 #define TLV_ACCEPTABLE_MASTER_TABLE 0x201A #define TLV_ACCEPTABLE_MASTER_MAX_TABLE_SIZE 0x201C #define TLV_ALTERNATE_TIME_OFFSET_ENABLE 0x201E #define TLV_ALTERNATE_TIME_OFFSET_NAME 0x201F #define TLV_ALTERNATE_TIME_OFFSET_MAX_KEY 0x2020 #define TLV_ALTERNATE_TIME_OFFSET_PROPERTIES 0x2021 #define TLV_TRANSPARENT_CLOCK_DEFAULT_DATA_SET 0x4000 #define TLV_PRIMARY_DOMAIN 0x4002 #define TLV_TIME_STATUS_NP 0xC000 #define TLV_GRANDMASTER_SETTINGS_NP 0xC001 #define TLV_SUBSCRIBE_EVENTS_NP 0xC003 /* Port management ID values */ #define TLV_NULL_MANAGEMENT 0x0000 #define TLV_CLOCK_DESCRIPTION 0x0001 #define TLV_PORT_DATA_SET 0x2004 #define TLV_LOG_ANNOUNCE_INTERVAL 0x2009 #define TLV_ANNOUNCE_RECEIPT_TIMEOUT 0x200A #define TLV_LOG_SYNC_INTERVAL 0x200B #define TLV_VERSION_NUMBER 0x200C #define TLV_ENABLE_PORT 0x200D #define TLV_DISABLE_PORT 0x200E #define TLV_UNICAST_NEGOTIATION_ENABLE 0x2014 #define TLV_UNICAST_MASTER_TABLE 0x2018 #define TLV_UNICAST_MASTER_MAX_TABLE_SIZE 0x2019 #define TLV_ACCEPTABLE_MASTER_TABLE_ENABLED 0x201B #define TLV_ALTERNATE_MASTER 0x201D #define TLV_TRANSPARENT_CLOCK_PORT_DATA_SET 0x4001 #define TLV_DELAY_MECHANISM 0x6000 #define TLV_LOG_MIN_PDELAY_REQ_INTERVAL 0x6001 #define TLV_PORT_DATA_SET_NP 0xC002 #define TLV_PORT_PROPERTIES_NP 0xC004 /* Management error ID values */ #define TLV_RESPONSE_TOO_BIG 0x0001 #define TLV_NO_SUCH_ID 0x0002 #define TLV_WRONG_LENGTH 0x0003 #define TLV_WRONG_VALUE 0x0004 #define TLV_NOT_SETABLE 0x0005 #define TLV_NOT_SUPPORTED 0x0006 #define TLV_GENERAL_ERROR 0xFFFE struct management_tlv { Enumeration16 type; UInteger16 length; Enumeration16 id; Octet data[0]; } PACKED; struct management_tlv_datum { uint8_t val; uint8_t reserved; } PACKED; struct management_error_status { Enumeration16 type; UInteger16 length; Enumeration16 error; Enumeration16 id; Octet reserved[4]; Octet data[0]; } PACKED; /* Organizationally Unique Identifiers */ #define IEEE_802_1_COMMITTEE 0x00, 0x80, 0xC2 extern uint8_t ieee8021_id[3]; struct organization_tlv { Enumeration16 type; UInteger16 length; Octet id[3]; Octet subtype[3]; } PACKED; #define PATH_TRACE_MAX \ ((sizeof(struct message_data) - sizeof(struct announce_msg) - sizeof(struct TLV)) / \ sizeof(struct ClockIdentity)) struct path_trace_tlv { Enumeration16 type; UInteger16 length; struct ClockIdentity cid[0]; } PACKED; static inline unsigned int path_length(struct path_trace_tlv *p) { return p->length / sizeof(struct ClockIdentity); } typedef struct Integer96 { uint16_t nanoseconds_msb; uint64_t nanoseconds_lsb; uint16_t fractional_nanoseconds; } PACKED ScaledNs; struct follow_up_info_tlv { Enumeration16 type; UInteger16 length; Octet id[3]; Octet subtype[3]; Integer32 cumulativeScaledRateOffset; UInteger16 gmTimeBaseIndicator; ScaledNs lastGmPhaseChange; Integer32 scaledLastGmPhaseChange; } PACKED; struct time_status_np { int64_t master_offset; /*nanoseconds*/ int64_t ingress_time; /*nanoseconds*/ Integer32 cumulativeScaledRateOffset; Integer32 scaledLastGmPhaseChange; UInteger16 gmTimeBaseIndicator; ScaledNs lastGmPhaseChange; Integer32 gmPresent; struct ClockIdentity gmIdentity; } PACKED; struct grandmaster_settings_np { struct ClockQuality clockQuality; Integer16 utc_offset; UInteger8 time_flags; Enumeration8 time_source; } PACKED; struct port_ds_np { UInteger32 neighborPropDelayThresh; /*nanoseconds*/ Integer32 asCapable; } PACKED; #define EVENT_BITMASK_CNT 64 struct subscribe_events_np { uint16_t duration; /* seconds */ uint8_t bitmask[EVENT_BITMASK_CNT]; } PACKED; struct port_properties_np { struct PortIdentity portIdentity; uint8_t port_state; uint8_t timestamping; struct PTPText interface; } PACKED; enum clock_type { CLOCK_TYPE_ORDINARY = 0x8000, CLOCK_TYPE_BOUNDARY = 0x4000, CLOCK_TYPE_P2P = 0x2000, CLOCK_TYPE_E2E = 0x1000, CLOCK_TYPE_MANAGEMENT = 0x0800, }; #define PROFILE_ID_LEN 6 struct mgmt_clock_description { UInteger16 *clockType; struct PTPText *physicalLayerProtocol; struct PhysicalAddress *physicalAddress; struct PortAddress *protocolAddress; Octet *manufacturerIdentity; struct PTPText *productDescription; struct PTPText *revisionData; struct PTPText *userDescription; Octet *profileIdentity; }; struct tlv_extra { union { struct mgmt_clock_description cd; }; }; /** * Converts recognized value sub-fields into host byte order. * @param tlv Pointer to a Type Length Value field. * @param extra Additional struct where data from tlv will be saved, * can be NULL. * @return Zero if successful, otherwise non-zero */ int tlv_post_recv(struct TLV *tlv, struct tlv_extra *extra); /** * Converts recognized value sub-fields into network byte order. * @param tlv Pointer to a Type Length Value field. * @param extra Additional struct containing tlv data to send, can be * NULL. */ void tlv_pre_send(struct TLV *tlv, struct tlv_extra *extra); #endif linuxptp-1.6/tmv.h000066400000000000000000000050061257727010700142330ustar00rootroot00000000000000/** * @file tmv.h * @brief Implements an abstract time value type. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_TMV_H #define HAVE_TMV_H #include #include "ddt.h" #include "pdt.h" #define NS_PER_SEC 1000000000LL /** * We implement the time value as a 64 bit signed integer containing * nanoseconds. Using this representation, we could really spare the * arithmetic functions such as @ref tmv_add() and the like, and just * use plain old math operators in the code. * * However, we are going to be a bit pedantic here and enforce the * use of the these functions, so that we can easily upgrade the code * to a finer representation later on. In that way, we can make use of * the fractional nanosecond parts of the correction fields, if and * when people start asking for them. */ typedef int64_t tmv_t; static inline tmv_t tmv_add(tmv_t a, tmv_t b) { return a + b; } static inline tmv_t tmv_div(tmv_t a, int divisor) { return a / divisor; } static inline int tmv_eq(tmv_t a, tmv_t b) { return a == b ? 1 : 0; } static inline int tmv_is_zero(tmv_t x) { return x == ((tmv_t) 0) ? 1 : 0; } static inline tmv_t tmv_sub(tmv_t a, tmv_t b) { return a - b; } static inline tmv_t tmv_zero(void) { return (tmv_t) 0; } static inline tmv_t correction_to_tmv(Integer64 c) { return c >> 16; } static inline double tmv_dbl(tmv_t x) { return (double) x; } static inline tmv_t dbl_tmv(double x) { return (tmv_t) x; } static inline int64_t tmv_to_nanoseconds(tmv_t x) { return x; } static inline TimeInterval tmv_to_TimeInterval(tmv_t x) { return x << 16; } static inline tmv_t timespec_to_tmv(struct timespec ts) { return ts.tv_sec * NS_PER_SEC + ts.tv_nsec; } static inline tmv_t timestamp_to_tmv(struct timestamp ts) { return ts.sec * NS_PER_SEC + ts.nsec; } #endif linuxptp-1.6/transport.c000066400000000000000000000056361257727010700154650ustar00rootroot00000000000000/** * @file transport.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include "transport.h" #include "transport_private.h" #include "raw.h" #include "udp.h" #include "udp6.h" #include "uds.h" int transport_close(struct transport *t, struct fdarray *fda) { return t->close(t, fda); } int transport_open(struct transport *t, const char *name, struct fdarray *fda, enum timestamp_type tt) { return t->open(t, name, fda, tt); } int transport_recv(struct transport *t, int fd, struct ptp_message *msg) { return t->recv(t, fd, msg, sizeof(msg->data), &msg->address, &msg->hwts); } int transport_send(struct transport *t, struct fdarray *fda, int event, struct ptp_message *msg) { int len = ntohs(msg->header.messageLength); return t->send(t, fda, event, 0, msg, len, NULL, &msg->hwts); } int transport_peer(struct transport *t, struct fdarray *fda, int event, struct ptp_message *msg) { int len = ntohs(msg->header.messageLength); return t->send(t, fda, event, 1, msg, len, NULL, &msg->hwts); } int transport_sendto(struct transport *t, struct fdarray *fda, int event, struct ptp_message *msg) { int len = ntohs(msg->header.messageLength); return t->send(t, fda, event, 0, msg, len, &msg->address, &msg->hwts); } int transport_physical_addr(struct transport *t, uint8_t *addr) { if (t->physical_addr) { return t->physical_addr(t, addr); } return 0; } int transport_protocol_addr(struct transport *t, uint8_t *addr) { if (t->protocol_addr) { return t->protocol_addr(t, addr); } return 0; } enum transport_type transport_type(struct transport *t) { return t->type; } struct transport *transport_create(struct config *cfg, enum transport_type type) { struct transport *t = NULL; switch (type) { case TRANS_UDS: t = uds_transport_create(); break; case TRANS_UDP_IPV4: t = udp_transport_create(); break; case TRANS_UDP_IPV6: t = udp6_transport_create(); break; case TRANS_IEEE_802_3: t = raw_transport_create(); break; case TRANS_DEVICENET: case TRANS_CONTROLNET: case TRANS_PROFINET: break; } if (t) { t->type = type; t->cfg = cfg; } return t; } void transport_destroy(struct transport *t) { t->release(t); } linuxptp-1.6/transport.h000066400000000000000000000110051257727010700154550ustar00rootroot00000000000000/** * @file transport.h * @brief Defines an abstract transport layer. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_TRANSPORT_H #define HAVE_TRANSPORT_H #include #include #include "fd.h" #include "msg.h" struct config; /* Values from networkProtocol enumeration 7.4.1 Table 3 */ enum transport_type { /* 0 is Reserved in spec. Use it for UDS */ TRANS_UDS = 0, TRANS_UDP_IPV4 = 1, TRANS_UDP_IPV6, TRANS_IEEE_802_3, TRANS_DEVICENET, TRANS_CONTROLNET, TRANS_PROFINET, }; /** * Values for the 'event' parameter in transport_send() and * transport_peer(). */ enum transport_event { TRANS_GENERAL, TRANS_EVENT, TRANS_ONESTEP, }; struct transport; int transport_close(struct transport *t, struct fdarray *fda); int transport_open(struct transport *t, const char *name, struct fdarray *fda, enum timestamp_type tt); int transport_recv(struct transport *t, int fd, struct ptp_message *msg); /** * Sends the PTP message using the given transport. The message is sent to * the default (usually multicast) address, any address field in the * ptp_message itself is ignored. * @param t The transport. * @param fda The array of descriptors filled in by transport_open. * @param event 1 for event message, 0 for general message. * @param msg The message to send. * @return Number of bytes send, or negative value in case of an error. */ int transport_send(struct transport *t, struct fdarray *fda, int event, struct ptp_message *msg); /** * Sends the PTP message using the given transport. The message is sent to * the address used for p2p delay measurements (usually a multicast * address), any address field in the ptp_message itself is ignored. * @param t The transport. * @param fda The array of descriptors filled in by transport_open. * @param event 1 for event message, 0 for general message. * @param msg The message to send. * @return Number of bytes send, or negative value in case of an error. */ int transport_peer(struct transport *t, struct fdarray *fda, int event, struct ptp_message *msg); /** * Sends the PTP message using the given transport. The address has to be * provided in the address field of the message. * @param t The transport. * @param fda The array of descriptors filled in by transport_open. * @param event 1 for event message, 0 for general message. * @param msg The message to send. The address of the destination has to * be set in the address field. * @return Number of bytes send, or negative value in case of an error. */ int transport_sendto(struct transport *t, struct fdarray *fda, int event, struct ptp_message *msg); /** * Returns the transport's type. */ enum transport_type transport_type(struct transport *t); #define TRANSPORT_ADDR_LEN 16 /** * Gets the transport's physical address. * @param t The transport. * @param addr The address will be written to this buffer. * @return The number of bytes written to the buffer. Will be 0-16 * bytes */ int transport_physical_addr(struct transport *t, uint8_t *addr); /** * Gets the transport's protocol address. * @param t The transport. * @param addr The address will be written to this buffer. * @return The number of bytes written to the buffer. Will be 0-16 * bytes */ int transport_protocol_addr(struct transport *t, uint8_t *addr); /** * Allocate an instance of the specified transport. * @param config Pointer to the configuration database. * @param type Which transport to obtain. * @return Pointer to a transport instance on success, NULL otherwise. */ struct transport *transport_create(struct config *cfg, enum transport_type type); /** * Free an instance of a transport. * @param t Pointer obtained by calling transport_create(). */ void transport_destroy(struct transport *t); #endif linuxptp-1.6/transport_private.h000066400000000000000000000033041257727010700172120ustar00rootroot00000000000000/** * @file transport_private.h * @brief Defines a private interface for the abstract transport layer. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_TRANSPORT_PRIVATE_H #define HAVE_TRANSPORT_PRIVATE_H #include #include "address.h" #include "fd.h" #include "transport.h" struct transport { enum transport_type type; struct config *cfg; int (*close)(struct transport *t, struct fdarray *fda); int (*open)(struct transport *t, const char *name, struct fdarray *fda, enum timestamp_type tt); int (*recv)(struct transport *t, int fd, void *buf, int buflen, struct address *addr, struct hw_timestamp *hwts); int (*send)(struct transport *t, struct fdarray *fda, int event, int peer, void *buf, int buflen, struct address *addr, struct hw_timestamp *hwts); void (*release)(struct transport *t); int (*physical_addr)(struct transport *t, uint8_t *addr); int (*protocol_addr)(struct transport *t, uint8_t *addr); }; #endif linuxptp-1.6/tsproc.c000066400000000000000000000106721257727010700147370ustar00rootroot00000000000000/** * @file tsproc.c * @note Copyright (C) 2015 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "tsproc.h" #include "filter.h" #include "print.h" struct tsproc { /* Processing options */ int raw_mode; int weighting; /* Current ratio between remote and local clock frequency */ double clock_rate_ratio; /* Latest down measurement */ tmv_t t1; tmv_t t2; /* Latest up measurement */ tmv_t t3; tmv_t t4; /* Current filtered delay */ tmv_t filtered_delay; /* Delay filter */ struct filter *delay_filter; }; struct tsproc *tsproc_create(enum tsproc_mode mode, enum filter_type delay_filter, int filter_length) { struct tsproc *tsp; tsp = calloc(1, sizeof(*tsp)); if (!tsp) return NULL; switch (mode) { case TSPROC_FILTER: tsp->raw_mode = 0; tsp->weighting = 0; break; case TSPROC_RAW: tsp->raw_mode = 1; tsp->weighting = 0; break; case TSPROC_FILTER_WEIGHT: tsp->raw_mode = 0; tsp->weighting = 1; break; case TSPROC_RAW_WEIGHT: tsp->raw_mode = 1; tsp->weighting = 1; break; default: free(tsp); return NULL; } tsp->delay_filter = filter_create(delay_filter, filter_length); if (!tsp->delay_filter) { free(tsp); return NULL; } tsp->clock_rate_ratio = 1.0; return tsp; } void tsproc_destroy(struct tsproc *tsp) { filter_destroy(tsp->delay_filter); free(tsp); } void tsproc_down_ts(struct tsproc *tsp, tmv_t remote_ts, tmv_t local_ts) { tsp->t1 = remote_ts; tsp->t2 = local_ts; } void tsproc_up_ts(struct tsproc *tsp, tmv_t local_ts, tmv_t remote_ts) { tsp->t3 = local_ts; tsp->t4 = remote_ts; } void tsproc_set_clock_rate_ratio(struct tsproc *tsp, double clock_rate_ratio) { tsp->clock_rate_ratio = clock_rate_ratio; } void tsproc_set_delay(struct tsproc *tsp, tmv_t delay) { tsp->filtered_delay = delay; } tmv_t get_raw_delay(struct tsproc *tsp) { tmv_t t23, t41, delay; /* delay = ((t2 - t3) * rr + (t4 - t1)) / 2 */ t23 = tmv_sub(tsp->t2, tsp->t3); if (tsp->clock_rate_ratio != 1.0) t23 = dbl_tmv(tmv_dbl(t23) * tsp->clock_rate_ratio); t41 = tmv_sub(tsp->t4, tsp->t1); delay = tmv_div(tmv_add(t23, t41), 2); if (delay < 0) { pr_debug("negative delay %10" PRId64, delay); pr_debug("delay = (t2 - t3) * rr + (t4 - t1)"); pr_debug("t2 - t3 = %+10" PRId64, t23); pr_debug("t4 - t1 = %+10" PRId64, t41); pr_debug("rr = %.9f", tsp->clock_rate_ratio); } return delay; } int tsproc_update_delay(struct tsproc *tsp, tmv_t *delay) { tmv_t raw_delay; if (tmv_is_zero(tsp->t1) || tmv_is_zero(tsp->t2) || tmv_is_zero(tsp->t3) || tmv_is_zero(tsp->t4)) return -1; raw_delay = get_raw_delay(tsp); tsp->filtered_delay = filter_sample(tsp->delay_filter, raw_delay); pr_debug("delay filtered %10" PRId64 " raw %10" PRId64, tsp->filtered_delay, raw_delay); if (delay) *delay = tsp->raw_mode ? raw_delay : tsp->filtered_delay; return 0; } int tsproc_update_offset(struct tsproc *tsp, tmv_t *offset, double *weight) { tmv_t delay, raw_delay = 0; if (tmv_is_zero(tsp->t1) || tmv_is_zero(tsp->t2) || tmv_is_zero(tsp->t3) || tmv_is_zero(tsp->t4)) return -1; if (tsp->raw_mode || tsp->weighting) raw_delay = get_raw_delay(tsp); delay = tsp->raw_mode ? raw_delay : tsp->filtered_delay; /* offset = t2 - t1 - delay */ *offset = tmv_sub(tmv_sub(tsp->t2, tsp->t1), delay); if (!weight) return 0; if (tsp->weighting && tsp->filtered_delay > 0 && raw_delay > 0) { *weight = (double)tsp->filtered_delay / raw_delay; if (*weight > 1.0) *weight = 1.0; } else { *weight = 1.0; } return 0; } void tsproc_reset(struct tsproc *tsp, int full) { tsp->t1 = tmv_zero(); tsp->t2 = tmv_zero(); tsp->t3 = tmv_zero(); tsp->t4 = tmv_zero(); if (full) { tsp->clock_rate_ratio = 1.0; filter_reset(tsp->delay_filter); } } linuxptp-1.6/tsproc.h000066400000000000000000000073621257727010700147460ustar00rootroot00000000000000/** * @file tsproc.h * @brief Time stamp processor. * @note Copyright (C) 2015 Miroslav Lichvar * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_TSPROC_H #define HAVE_TSPROC_H #include "filter.h" /** Opaque type */ struct tsproc; /** * Defines the available modes. */ enum tsproc_mode { TSPROC_FILTER, TSPROC_RAW, TSPROC_FILTER_WEIGHT, TSPROC_RAW_WEIGHT, }; /** * Create a new instance of the time stamp processor. * @param mode Time stamp processing mode. * @param delay_filter Type of the filter that will be applied to delay. * @param filter_length Length of the filter. * @return A pointer to a new tsproc on success, NULL otherwise. */ struct tsproc *tsproc_create(enum tsproc_mode mode, enum filter_type delay_filter, int filter_length); /** * Destroy a time stamp processor. * @param tsp Pointer obtained via @ref tsproc_create(). */ void tsproc_destroy(struct tsproc *tsp); /** * Feed a downstream measurement into a time stamp processor. * @param tsp Pointer obtained via @ref tsproc_create(). * @param remote_ts The remote transmission time. * @param local_ts The local reception time. */ void tsproc_down_ts(struct tsproc *tsp, tmv_t remote_ts, tmv_t local_ts); /** * Feed an upstream measurement into a time stamp processor. * @param tsp Pointer obtained via @ref tsproc_create(). * @param local_ts The local transmission time. * @param remote_ts The remote reception time. */ void tsproc_up_ts(struct tsproc *tsp, tmv_t local_ts, tmv_t remote_ts); /** * Set ratio between remote and local clock frequencies. * @param tsp Pointer obtained via @ref tsproc_create(). * @param clock_rate_ratio The ratio between frequencies. */ void tsproc_set_clock_rate_ratio(struct tsproc *tsp, double clock_rate_ratio); /** * Set delay in a time stamp processor. This can be used to override the last * calculated value. * @param tsp Pointer obtained via @ref tsproc_create(). * @param delay The new delay. */ void tsproc_set_delay(struct tsproc *tsp, tmv_t delay); /** * Update delay in a time stamp processor using new measurements. * @param tsp Pointer obtained via @ref tsproc_create(). * @param delay A pointer to store the new delay, may be NULL. * @return 0 on success, -1 when missing a measurement. */ int tsproc_update_delay(struct tsproc *tsp, tmv_t *delay); /** * Update offset in a time stamp processor using new measurements. * @param tsp Pointer obtained via @ref tsproc_create(). * @param offset A pointer to store the new offset. * @param weight A pointer to store the weight of the sample, may be NULL. * @return 0 on success, -1 when missing a measurement. */ int tsproc_update_offset(struct tsproc *tsp, tmv_t *offset, double *weight); /** * Reset a time stamp processor. * @param tsp Pointer obtained via @ref tsproc_create(). * @param full 0 to reset stored measurements (e.g. after clock was stepped), * 1 to reset everything (e.g. when remote clock changed). */ void tsproc_reset(struct tsproc *tsp, int full); #endif linuxptp-1.6/udp.c000066400000000000000000000160741257727010700142170ustar00rootroot00000000000000/** * @file udp.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "address.h" #include "config.h" #include "contain.h" #include "print.h" #include "sk.h" #include "ether.h" #include "transport_private.h" #include "udp.h" #define EVENT_PORT 319 #define GENERAL_PORT 320 #define PTP_PRIMARY_MCAST_IPADDR "224.0.1.129" #define PTP_PDELAY_MCAST_IPADDR "224.0.0.107" struct udp { struct transport t; struct address ip; struct address mac; }; static int mcast_bind(int fd, int index) { int err; struct ip_mreqn req; memset(&req, 0, sizeof(req)); req.imr_ifindex = index; err = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &req, sizeof(req)); if (err) { pr_err("setsockopt IP_MULTICAST_IF failed: %m"); return -1; } return 0; } static int mcast_join(int fd, int index, const struct sockaddr *grp, socklen_t grplen) { int err, off = 0; struct ip_mreqn req; struct sockaddr_in *sa = (struct sockaddr_in *) grp; memset(&req, 0, sizeof(req)); memcpy(&req.imr_multiaddr, &sa->sin_addr, sizeof(struct in_addr)); req.imr_ifindex = index; err = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &req, sizeof(req)); if (err) { pr_err("setsockopt IP_ADD_MEMBERSHIP failed: %m"); return -1; } err = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &off, sizeof(off)); if (err) { pr_err("setsockopt IP_MULTICAST_LOOP failed: %m"); return -1; } return 0; } static int udp_close(struct transport *t, struct fdarray *fda) { close(fda->fd[0]); close(fda->fd[1]); return 0; } static int open_socket(const char *name, struct in_addr mc_addr[2], short port, int ttl) { struct sockaddr_in addr; int fd, index, on = 1; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) { pr_err("socket failed: %m"); goto no_socket; } index = sk_interface_index(fd, name); if (index < 0) goto no_option; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) { pr_err("setsockopt SO_REUSEADDR failed: %m"); goto no_option; } if (bind(fd, (struct sockaddr *) &addr, sizeof(addr))) { pr_err("bind failed: %m"); goto no_option; } if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name))) { pr_err("setsockopt SO_BINDTODEVICE failed: %m"); goto no_option; } if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl))) { pr_err("setsockopt IP_MULTICAST_TTL failed: %m"); goto no_option; } addr.sin_addr = mc_addr[0]; if (mcast_join(fd, index, (struct sockaddr *) &addr, sizeof(addr))) { pr_err("mcast_join failed"); goto no_option; } addr.sin_addr = mc_addr[1]; if (mcast_join(fd, index, (struct sockaddr *) &addr, sizeof(addr))) { pr_err("mcast_join failed"); goto no_option; } if (mcast_bind(fd, index)) { goto no_option; } return fd; no_option: close(fd); no_socket: return -1; } enum { MC_PRIMARY, MC_PDELAY }; static struct in_addr mcast_addr[2]; static int udp_open(struct transport *t, const char *name, struct fdarray *fda, enum timestamp_type ts_type) { struct udp *udp = container_of(t, struct udp, t); int efd, gfd, ttl; ttl = config_get_int(t->cfg, name, "udp_ttl"); udp->mac.len = 0; sk_interface_macaddr(name, &udp->mac); udp->ip.len = 0; sk_interface_addr(name, AF_INET, &udp->ip); if (!inet_aton(PTP_PRIMARY_MCAST_IPADDR, &mcast_addr[MC_PRIMARY])) return -1; if (!inet_aton(PTP_PDELAY_MCAST_IPADDR, &mcast_addr[MC_PDELAY])) return -1; efd = open_socket(name, mcast_addr, EVENT_PORT, ttl); if (efd < 0) goto no_event; gfd = open_socket(name, mcast_addr, GENERAL_PORT, ttl); if (gfd < 0) goto no_general; if (sk_timestamping_init(efd, name, ts_type, TRANS_UDP_IPV4)) goto no_timestamping; if (sk_general_init(gfd)) goto no_timestamping; fda->fd[FD_EVENT] = efd; fda->fd[FD_GENERAL] = gfd; return 0; no_timestamping: close(gfd); no_general: close(efd); no_event: return -1; } static int udp_recv(struct transport *t, int fd, void *buf, int buflen, struct address *addr, struct hw_timestamp *hwts) { return sk_receive(fd, buf, buflen, addr, hwts, 0); } static int udp_send(struct transport *t, struct fdarray *fda, int event, int peer, void *buf, int len, struct address *addr, struct hw_timestamp *hwts) { ssize_t cnt; int fd = event ? fda->fd[FD_EVENT] : fda->fd[FD_GENERAL]; struct address addr_buf; unsigned char junk[1600]; if (!addr) { memset(&addr_buf, 0, sizeof(addr_buf)); addr_buf.sin.sin_family = AF_INET; addr_buf.sin.sin_addr = peer ? mcast_addr[MC_PDELAY] : mcast_addr[MC_PRIMARY]; addr_buf.len = sizeof(addr_buf.sin); addr = &addr_buf; } addr->sin.sin_port = htons(event ? EVENT_PORT : GENERAL_PORT); /* * Extend the payload by two, for UDP checksum correction. * This is not really part of the standard, but it is the way * that the phyter works. */ if (event == TRANS_ONESTEP) len += 2; cnt = sendto(fd, buf, len, 0, &addr->sa, sizeof(addr->sin)); if (cnt < 1) { pr_err("sendto failed: %m"); return cnt; } /* * Get the time stamp right away. */ return event == TRANS_EVENT ? sk_receive(fd, junk, len, NULL, hwts, MSG_ERRQUEUE) : cnt; } static void udp_release(struct transport *t) { struct udp *udp = container_of(t, struct udp, t); free(udp); } static int udp_physical_addr(struct transport *t, uint8_t *addr) { struct udp *udp = container_of(t, struct udp, t); int len = 0; if (udp->mac.len) { len = MAC_LEN; memcpy(addr, udp->mac.sll.sll_addr, len); } return len; } static int udp_protocol_addr(struct transport *t, uint8_t *addr) { struct udp *udp = container_of(t, struct udp, t); int len = 0; if (udp->ip.len) { len = sizeof(udp->ip.sin.sin_addr.s_addr); memcpy(addr, &udp->ip.sin.sin_addr.s_addr, len); } return len; } struct transport *udp_transport_create(void) { struct udp *udp = calloc(1, sizeof(*udp)); if (!udp) return NULL; udp->t.close = udp_close; udp->t.open = udp_open; udp->t.recv = udp_recv; udp->t.send = udp_send; udp->t.release = udp_release; udp->t.physical_addr = udp_physical_addr; udp->t.protocol_addr = udp_protocol_addr; return &udp->t; } linuxptp-1.6/udp.h000066400000000000000000000021451257727010700142160ustar00rootroot00000000000000/** * @file udp.h * @brief Implements transport over IPv4 UDP. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_UPD_H #define HAVE_UPD_H #include "fd.h" #include "transport.h" /** * Allocate an instance of a UDP/IPv4 transport. * @return Pointer to a new transport instance on success, NULL otherwise. */ struct transport *udp_transport_create(void); #endif linuxptp-1.6/udp6.c000066400000000000000000000170421257727010700143010ustar00rootroot00000000000000/** * @file udp6.c * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "address.h" #include "config.h" #include "contain.h" #include "print.h" #include "sk.h" #include "ether.h" #include "transport_private.h" #include "udp6.h" #define EVENT_PORT 319 #define GENERAL_PORT 320 #define PTP_PRIMARY_MCAST_IP6ADDR "FF0E:0:0:0:0:0:0:181" #define PTP_PDELAY_MCAST_IP6ADDR "FF02:0:0:0:0:0:0:6B" struct udp6 { struct transport t; int index; struct address ip; struct address mac; }; static int is_link_local(struct in6_addr *addr) { return addr->s6_addr[1] == 0x02 ? 1 : 0; } static int mc_bind(int fd, int index) { int err; struct ipv6_mreq req; memset(&req, 0, sizeof(req)); req.ipv6mr_interface = index; err = setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &req, sizeof(req)); if (err) { pr_err("setsockopt IPV6_MULTICAST_IF failed: %m"); return -1; } return 0; } static int mc_join(int fd, int index, const struct sockaddr *grp, socklen_t grplen) { int err, off = 0; struct ipv6_mreq req; struct sockaddr_in6 *sa = (struct sockaddr_in6 *) grp; memset(&req, 0, sizeof(req)); memcpy(&req.ipv6mr_multiaddr, &sa->sin6_addr, sizeof(struct in6_addr)); req.ipv6mr_interface = index; err = setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &req, sizeof(req)); if (err) { pr_err("setsockopt IPV6_ADD_MEMBERSHIP failed: %m"); return -1; } err = setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, sizeof(off)); if (err) { pr_err("setsockopt IPV6_MULTICAST_LOOP failed: %m"); return -1; } return 0; } static int udp6_close(struct transport *t, struct fdarray *fda) { close(fda->fd[0]); close(fda->fd[1]); return 0; } static int open_socket_ipv6(const char *name, struct in6_addr mc_addr[2], short port, int *interface_index, int hop_limit) { struct sockaddr_in6 addr; int fd, index, on = 1; memset(&addr, 0, sizeof(addr)); addr.sin6_family = AF_INET6; addr.sin6_addr = in6addr_any; addr.sin6_port = htons(port); fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) { pr_err("socket failed: %m"); goto no_socket; } index = sk_interface_index(fd, name); if (index < 0) goto no_option; *interface_index = index; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) { pr_err("setsockopt SO_REUSEADDR failed: %m"); goto no_option; } if (bind(fd, (struct sockaddr *) &addr, sizeof(addr))) { pr_err("bind failed: %m"); goto no_option; } if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name))) { pr_err("setsockopt SO_BINDTODEVICE failed: %m"); goto no_option; } if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hop_limit, sizeof(hop_limit))) { pr_err("setsockopt IPV6_MULTICAST_HOPS failed: %m"); goto no_option; } addr.sin6_addr = mc_addr[0]; if (mc_join(fd, index, (struct sockaddr *) &addr, sizeof(addr))) { pr_err("mcast_join failed"); goto no_option; } addr.sin6_addr = mc_addr[1]; if (mc_join(fd, index, (struct sockaddr *) &addr, sizeof(addr))) { pr_err("mcast_join failed"); goto no_option; } if (mc_bind(fd, index)) { goto no_option; } return fd; no_option: close(fd); no_socket: return -1; } enum { MC_PRIMARY, MC_PDELAY }; static struct in6_addr mc6_addr[2]; static int udp6_open(struct transport *t, const char *name, struct fdarray *fda, enum timestamp_type ts_type) { struct udp6 *udp6 = container_of(t, struct udp6, t); int efd, gfd, hop_limit; hop_limit = config_get_int(t->cfg, name, "udp_ttl"); udp6->mac.len = 0; sk_interface_macaddr(name, &udp6->mac); udp6->ip.len = 0; sk_interface_addr(name, AF_INET6, &udp6->ip); if (1 != inet_pton(AF_INET6, PTP_PRIMARY_MCAST_IP6ADDR, &mc6_addr[MC_PRIMARY])) return -1; mc6_addr[MC_PRIMARY].s6_addr[1] = config_get_int(t->cfg, name, "udp6_scope"); if (1 != inet_pton(AF_INET6, PTP_PDELAY_MCAST_IP6ADDR, &mc6_addr[MC_PDELAY])) return -1; efd = open_socket_ipv6(name, mc6_addr, EVENT_PORT, &udp6->index, hop_limit); if (efd < 0) goto no_event; gfd = open_socket_ipv6(name, mc6_addr, GENERAL_PORT, &udp6->index, hop_limit); if (gfd < 0) goto no_general; if (sk_timestamping_init(efd, name, ts_type, TRANS_UDP_IPV6)) goto no_timestamping; if (sk_general_init(gfd)) goto no_timestamping; fda->fd[FD_EVENT] = efd; fda->fd[FD_GENERAL] = gfd; return 0; no_timestamping: close(gfd); no_general: close(efd); no_event: return -1; } static int udp6_recv(struct transport *t, int fd, void *buf, int buflen, struct address *addr, struct hw_timestamp *hwts) { return sk_receive(fd, buf, buflen, addr, hwts, 0); } static int udp6_send(struct transport *t, struct fdarray *fda, int event, int peer, void *buf, int len, struct address *addr, struct hw_timestamp *hwts) { struct udp6 *udp6 = container_of(t, struct udp6, t); ssize_t cnt; int fd = event ? fda->fd[FD_EVENT] : fda->fd[FD_GENERAL]; struct address addr_buf; unsigned char junk[1600]; if (!addr) { memset(&addr_buf, 0, sizeof(addr_buf)); addr_buf.sin6.sin6_family = AF_INET6; addr_buf.sin6.sin6_addr = peer ? mc6_addr[MC_PDELAY] : mc6_addr[MC_PRIMARY]; if (is_link_local(&addr_buf.sin6.sin6_addr)) addr_buf.sin6.sin6_scope_id = udp6->index; addr_buf.len = sizeof(addr_buf.sin6); addr = &addr_buf; } addr->sin6.sin6_port = htons(event ? EVENT_PORT : GENERAL_PORT); len += 2; /* Extend the payload by two, for UDP checksum corrections. */ cnt = sendto(fd, buf, len, 0, &addr->sa, sizeof(addr->sin6)); if (cnt < 1) { pr_err("sendto failed: %m"); return cnt; } /* * Get the time stamp right away. */ return event == TRANS_EVENT ? sk_receive(fd, junk, len, NULL, hwts, MSG_ERRQUEUE) : cnt; } static void udp6_release(struct transport *t) { struct udp6 *udp6 = container_of(t, struct udp6, t); free(udp6); } static int udp6_physical_addr(struct transport *t, uint8_t *addr) { struct udp6 *udp6 = container_of(t, struct udp6, t); int len = 0; if (udp6->mac.len) { len = MAC_LEN; memcpy(addr, udp6->mac.sll.sll_addr, len); } return len; } static int udp6_protocol_addr(struct transport *t, uint8_t *addr) { struct udp6 *udp6 = container_of(t, struct udp6, t); int len = 0; if (udp6->ip.len) { len = sizeof(udp6->ip.sin6.sin6_addr.s6_addr); memcpy(addr, &udp6->ip.sin6.sin6_addr.s6_addr, len); } return len; } struct transport *udp6_transport_create(void) { struct udp6 *udp6; udp6 = calloc(1, sizeof(*udp6)); if (!udp6) return NULL; udp6->t.close = udp6_close; udp6->t.open = udp6_open; udp6->t.recv = udp6_recv; udp6->t.send = udp6_send; udp6->t.release = udp6_release; udp6->t.physical_addr = udp6_physical_addr; udp6->t.protocol_addr = udp6_protocol_addr; return &udp6->t; } linuxptp-1.6/udp6.h000066400000000000000000000021511257727010700143010ustar00rootroot00000000000000/** * @file udp6.h * @brief Implements transport over IPv6 UDP. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_UPD6_H #define HAVE_UPD6_H #include "fd.h" #include "transport.h" /** * Allocate an instance of a UDP/IPv6 transport. * @return Pointer to a new transport instance on success, NULL otherwise. */ struct transport *udp6_transport_create(void); #endif linuxptp-1.6/uds.c000066400000000000000000000072021257727010700142130ustar00rootroot00000000000000/** * @file uds.c * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include "address.h" #include "contain.h" #include "print.h" #include "transport_private.h" #include "uds.h" #define UDS_FILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) /*0660*/ struct uds { struct transport t; struct address address; }; static int uds_close(struct transport *t, struct fdarray *fda) { struct sockaddr_un sa; socklen_t len = sizeof(sa); if (!getsockname(fda->fd[FD_GENERAL], (struct sockaddr *) &sa, &len) && sa.sun_family == AF_LOCAL) { unlink(sa.sun_path); } close(fda->fd[FD_GENERAL]); return 0; } static int uds_open(struct transport *t, const char *name, struct fdarray *fda, enum timestamp_type tt) { int fd, err; struct sockaddr_un sa; struct uds *uds = container_of(t, struct uds, t); char *uds_path = config_get_string(t->cfg, NULL, "uds_address"); fd = socket(AF_LOCAL, SOCK_DGRAM, 0); if (fd < 0) { pr_err("uds: failed to create socket: %m"); return -1; } memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_LOCAL; strncpy(sa.sun_path, name, sizeof(sa.sun_path) - 1); unlink(name); err = bind(fd, (struct sockaddr *) &sa, sizeof(sa)); if (err < 0) { pr_err("uds: bind failed: %m"); close(fd); return -1; } /* For client use, pre load the server path. */ memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_LOCAL; strncpy(sa.sun_path, uds_path, sizeof(sa.sun_path) - 1); uds->address.sun = sa; uds->address.len = sizeof(sa); chmod(name, UDS_FILEMODE); fda->fd[FD_EVENT] = -1; fda->fd[FD_GENERAL] = fd; return 0; } static int uds_recv(struct transport *t, int fd, void *buf, int buflen, struct address *addr, struct hw_timestamp *hwts) { int cnt; struct uds *uds = container_of(t, struct uds, t); addr->len = sizeof(addr->sun); cnt = recvfrom(fd, buf, buflen, 0, &addr->sa, &addr->len); if (cnt <= 0) { pr_err("uds: recvfrom failed: %m"); return cnt; } uds->address = *addr; return cnt; } static int uds_send(struct transport *t, struct fdarray *fda, int event, int peer, void *buf, int buflen, struct address *addr, struct hw_timestamp *hwts) { int cnt, fd = fda->fd[FD_GENERAL]; struct uds *uds = container_of(t, struct uds, t); if (!addr) addr = &uds->address; cnt = sendto(fd, buf, buflen, 0, &addr->sa, addr->len); if (cnt <= 0 && errno != ECONNREFUSED) { pr_err("uds: sendto failed: %m"); } return cnt; } static void uds_release(struct transport *t) { struct uds *uds = container_of(t, struct uds, t); free(uds); } struct transport *uds_transport_create(void) { struct uds *uds; uds = calloc(1, sizeof(*uds)); if (!uds) return NULL; uds->t.close = uds_close; uds->t.open = uds_open; uds->t.recv = uds_recv; uds->t.send = uds_send; uds->t.release = uds_release; return &uds->t; } linuxptp-1.6/uds.h000066400000000000000000000022131257727010700142150ustar00rootroot00000000000000/** * @file uds.h * @brief Implements a management interface via UNIX domain sockets. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_UDS_H #define HAVE_UDS_H #include "config.h" #include "fd.h" #include "transport.h" /** * Allocate an instance of a UDS transport. * @return Pointer to a new transport instance on success, NULL otherwise. */ struct transport *uds_transport_create(void); #endif linuxptp-1.6/util.c000066400000000000000000000252241257727010700144010ustar00rootroot00000000000000/** * @file util.c * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "address.h" #include "print.h" #include "sk.h" #include "util.h" #define NS_PER_SEC 1000000000LL #define NS_PER_HOUR (3600 * NS_PER_SEC) #define NS_PER_DAY (24 * NS_PER_HOUR) static int running = 1; const char *ps_str[] = { "NONE", "INITIALIZING", "FAULTY", "DISABLED", "LISTENING", "PRE_MASTER", "MASTER", "PASSIVE", "UNCALIBRATED", "SLAVE", "GRAND_MASTER", }; const char *ev_str[] = { "NONE", "POWERUP", "INITIALIZE", "DESIGNATED_ENABLED", "DESIGNATED_DISABLED", "FAULT_CLEARED", "FAULT_DETECTED", "STATE_DECISION_EVENT", "QUALIFICATION_TIMEOUT_EXPIRES", "ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES", "SYNCHRONIZATION_FAULT", "MASTER_CLOCK_SELECTED", "RS_MASTER", "RS_GRAND_MASTER", "RS_SLAVE", "RS_PASSIVE", }; char *cid2str(struct ClockIdentity *id) { static char buf[64]; unsigned char *ptr = id->id; snprintf(buf, sizeof(buf), "%02x%02x%02x.%02x%02x.%02x%02x%02x", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7]); return buf; } int count_char(const char *str, char c) { int num = 0; char s; while ((s = *(str++))) { if (s == c) num++; } return num; } char *pid2str(struct PortIdentity *id) { static char buf[64]; unsigned char *ptr = id->clockIdentity.id; snprintf(buf, sizeof(buf), "%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], id->portNumber); return buf; } int str2mac(const char *s, unsigned char mac[MAC_LEN]) { unsigned char buf[MAC_LEN]; int c; c = sscanf(s, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &buf[0], &buf[1], &buf[2], &buf[3], &buf[4], &buf[5]); if (c != MAC_LEN) { return -1; } memcpy(mac, buf, MAC_LEN); return 0; } int str2pid(const char *s, struct PortIdentity *result) { struct PortIdentity pid; unsigned char *ptr = pid.clockIdentity.id; int c; c = sscanf(s, " %02hhx%02hhx%02hhx.%02hhx%02hhx.%02hhx%02hhx%02hhx-%hu", &ptr[0], &ptr[1], &ptr[2], &ptr[3], &ptr[4], &ptr[5], &ptr[6], &ptr[7], &pid.portNumber); if (c == 9) { *result = pid; return 0; } return -1; } int generate_clock_identity(struct ClockIdentity *ci, const char *name) { struct address addr; if (sk_interface_macaddr(name, &addr)) return -1; ci->id[0] = addr.sll.sll_addr[0]; ci->id[1] = addr.sll.sll_addr[1]; ci->id[2] = addr.sll.sll_addr[2]; ci->id[3] = 0xFF; ci->id[4] = 0xFE; ci->id[5] = addr.sll.sll_addr[3]; ci->id[6] = addr.sll.sll_addr[4]; ci->id[7] = addr.sll.sll_addr[5]; return 0; } /* Naive count of utf8 symbols. Doesn't detect invalid UTF-8 and * probably doesn't count combining characters correctly. */ static size_t strlen_utf8(const Octet *s) { size_t len = 0; char c; while ((c = *(s++))) { if ((c & 0xC0) != 0x80) len++; } return len; } int static_ptp_text_copy(struct static_ptp_text *dst, const struct PTPText *src) { int len = src->length; if (dst->max_symbols > 0 && strlen_utf8(src->text) > dst->max_symbols) return -1; dst->length = len; memcpy(dst->text, src->text, len); dst->text[len] = '\0'; return 0; } void ptp_text_copy(struct PTPText *dst, const struct static_ptp_text *src) { dst->length = src->length; memcpy(dst->text, src->text, src->length); } int ptp_text_set(struct PTPText *dst, const char *src) { size_t len; if (src) { len = strlen(src); if (len > MAX_PTP_OCTETS) return -1; dst->length = len; memcpy(dst->text, src, len); } else { dst->length = 0; } return 0; } int static_ptp_text_set(struct static_ptp_text *dst, const char *src) { int len = strlen(src); if (len > MAX_PTP_OCTETS) return -1; if (dst->max_symbols > 0 && strlen_utf8((Octet *) src) > dst->max_symbols) return -1; dst->length = len; memcpy(dst->text, src, len); dst->text[len] = '\0'; return 0; } int is_utc_ambiguous(uint64_t ts) { /* The Linux kernel inserts leap second by stepping the clock backwards at 0:00 UTC, the last second before midnight is played twice. */ if (NS_PER_DAY - ts % NS_PER_DAY <= NS_PER_SEC) return 1; return 0; } int leap_second_status(uint64_t ts, int leap_set, int *leap, int *utc_offset) { int leap_status = leap_set; /* The leap bits obtained by PTP should be set at most 12 hours before midnight and unset at most 2 announce intervals after midnight. Split updates which are too early and which are too late at 6 hours after midnight. */ if (ts % NS_PER_DAY > 6 * NS_PER_HOUR) { if (!leap_status) leap_status = *leap; } else { if (leap_status) leap_status = 0; } /* Fix early or late update of leap and utc_offset. */ if (!*leap && leap_status) { *utc_offset -= leap_status; *leap = leap_status; } else if (*leap && !leap_status) { *utc_offset += *leap; *leap = leap_status; } return leap_status; } enum parser_result get_ranged_int(const char *str_val, int *result, int min, int max) { long parsed_val; char *endptr = NULL; errno = 0; parsed_val = strtol(str_val, &endptr, 0); if (*endptr != '\0' || endptr == str_val) return MALFORMED; if (errno == ERANGE || parsed_val < min || parsed_val > max) return OUT_OF_RANGE; *result = parsed_val; return PARSED_OK; } enum parser_result get_ranged_uint(const char *str_val, unsigned int *result, unsigned int min, unsigned int max) { unsigned long parsed_val; char *endptr = NULL; errno = 0; parsed_val = strtoul(str_val, &endptr, 0); if (*endptr != '\0' || endptr == str_val) return MALFORMED; if (errno == ERANGE || parsed_val < min || parsed_val > max) return OUT_OF_RANGE; *result = parsed_val; return PARSED_OK; } enum parser_result get_ranged_double(const char *str_val, double *result, double min, double max) { double parsed_val; char *endptr = NULL; errno = 0; parsed_val = strtod(str_val, &endptr); if (*endptr != '\0' || endptr == str_val) return MALFORMED; if (errno == ERANGE || parsed_val < min || parsed_val > max) return OUT_OF_RANGE; *result = parsed_val; return PARSED_OK; } int get_arg_val_i(int op, const char *optarg, int *val, int min, int max) { enum parser_result r; r = get_ranged_int(optarg, val, min, max); if (r == MALFORMED) { fprintf(stderr, "-%c: %s is a malformed value\n", op, optarg); return -1; } if (r == OUT_OF_RANGE) { fprintf(stderr, "-%c: %s is out of range. Must be in the range %d to %d\n", op, optarg, min, max); return -1; } return 0; } int get_arg_val_ui(int op, const char *optarg, unsigned int *val, unsigned int min, unsigned int max) { enum parser_result r; r = get_ranged_uint(optarg, val, min, max); if (r == MALFORMED) { fprintf(stderr, "-%c: %s is a malformed value\n", op, optarg); return -1; } if (r == OUT_OF_RANGE) { fprintf(stderr, "-%c: %s is out of range. Must be in the range %u to %u\n", op, optarg, min, max); return -1; } return 0; } int get_arg_val_d(int op, const char *optarg, double *val, double min, double max) { enum parser_result r; r = get_ranged_double(optarg, val, min, max); if (r == MALFORMED) { fprintf(stderr, "-%c: %s is a malformed value\n", op, optarg); return -1; } if (r == OUT_OF_RANGE) { fprintf(stderr, "-%c: %s is out of range. Must be in the range %e to %e\n", op, optarg, min, max); return -1; } return 0; } static void handle_int_quit_term(int s) { running = 0; } int handle_term_signals(void) { if (SIG_ERR == signal(SIGINT, handle_int_quit_term)) { fprintf(stderr, "cannot handle SIGINT\n"); return -1; } if (SIG_ERR == signal(SIGQUIT, handle_int_quit_term)) { fprintf(stderr, "cannot handle SIGQUIT\n"); return -1; } if (SIG_ERR == signal(SIGTERM, handle_int_quit_term)) { fprintf(stderr, "cannot handle SIGTERM\n"); return -1; } return 0; } int is_running(void) { return running; } void *xmalloc(size_t size) { void *r; r = malloc(size); if (!r) { pr_err("failed to allocate memory"); exit(1); } return r; } void *xcalloc(size_t nmemb, size_t size) { void *r; r = calloc(nmemb, size); if (!r) { pr_err("failed to allocate memory"); exit(1); } return r; } void *xrealloc(void *ptr, size_t size) { void *r; r = realloc(ptr, size); if (!r) { pr_err("failed to allocate memory"); exit(1); } return r; } char *xstrdup(const char *s) { void *r; r = strdup(s); if (!r) { pr_err("failed to allocate memory"); exit(1); } return r; } char *string_newf(const char *format, ...) { va_list ap; char *s; va_start(ap, format); if (vasprintf(&s, format, ap) < 0) { pr_err("failed to allocate memory"); exit(1); } va_end(ap); return s; } void string_append(char **s, const char *str) { size_t len1, len2; len1 = strlen(*s); len2 = strlen(str); *s = xrealloc(*s, len1 + len2 + 1); memcpy((*s) + len1, str, len2 + 1); } void string_appendf(char **s, const char *format, ...) { va_list ap; size_t len1, len2; char *s2; len1 = strlen(*s); va_start(ap, format); len2 = vasprintf(&s2, format, ap); va_end(ap); if (len2 < 0) { *s = NULL; return; } *s = xrealloc(*s, len1 + len2 + 1); memcpy((*s) + len1, s2, len2 + 1); free(s2); } void **parray_new(void) { void **a; a = xmalloc(sizeof(*a)); *a = NULL; return a; } void parray_append(void ***a, void *p) { parray_extend(a, p, NULL); } void parray_extend(void ***a, ...) { va_list ap; int ilen, len, alloced; void *p; for (len = 0; (*a)[len]; len++) ; len++; va_start(ap, a); for (ilen = 0; va_arg(ap, void *); ilen++) ; va_end(ap); /* Reallocate in exponentially increasing sizes. */ for (alloced = 1; alloced < len; alloced <<= 1) ; if (alloced < len + ilen) { while (alloced < len + ilen) alloced *= 2; *a = xrealloc(*a, alloced * sizeof **a); } va_start(ap, a); while ((p = va_arg(ap, void *))) (*a)[len++ - 1] = p; va_end(ap); (*a)[len - 1] = NULL; } int rate_limited(int interval, time_t *last) { struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts)) return 1; if (*last + interval > ts.tv_sec) return 1; *last = ts.tv_sec; return 0; } linuxptp-1.6/util.h000066400000000000000000000300211257727010700143750ustar00rootroot00000000000000/** * @file util.h * @brief Various little utility functions that do not fit in elsewhere. * @note Copyright (C) 2011 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_UTIL_H #define HAVE_UTIL_H #include "ddt.h" #include "ether.h" /** * Table of human readable strings, one for each port state. */ extern const char *ps_str[]; /** * Table of human readable strings, one for each port event. */ extern const char *ev_str[]; /** * Convert a clock identity into a human readable string. * * Note that this function uses a static global variable to store the * result and therefore is not reentrant. * * @param id Clock idendtity to show. * @return Pointer to a static global buffer holding the result. */ char *cid2str(struct ClockIdentity *id); /** * Counts the number of occurrences of a given character. * @param str String to evaluate. * @param c The character of interest. * @return The number of time 'c' appears in 'str'. */ int count_char(const char *str, char c); /** * Convert a port identity into a human readable string. * * Note that this function uses a static global variable to store the * result and therefore is not reentrant. * * @param id Port idendtity to show. * @return Pointer to a static global buffer holding the result. */ char *pid2str(struct PortIdentity *id); /** * Scan a string containing a MAC address and convert it into binary form. * * @param s String in human readable form. * @param mac Pointer to a buffer to hold the result. * @return Zero on success, or -1 if the string is incorrectly formatted. */ int str2mac(const char *s, unsigned char mac[MAC_LEN]); /** * Scan a string containing a port identity and convert it into binary form. * * @param s String in human readable form. * @param result Pointer to a buffer to hold the result. * @return Zero on success, or -1 if the string is incorrectly formatted. */ int str2pid(const char *s, struct PortIdentity *result); int generate_clock_identity(struct ClockIdentity *ci, const char *name); /** * Copies a PTPText to a static_ptp_text. This copies the text into * the static_ptp_text. * @param dst The static_ptp_text to copy to * @param src The PTPText to copy from * @return Zero on success, -1 if text in src is too long or not valid * UTF8 */ int static_ptp_text_copy(struct static_ptp_text *dst, const struct PTPText *src); /** * Copies a static_ptp_text to a PTPText. Caller must ensure it's * valid to write to the memory after the PTPText struct. The trailing * \0 is not copied. * @param dst The PTPText to copy to * @param src The static_ptp_text to copy from */ void ptp_text_copy(struct PTPText *dst, const struct static_ptp_text *src); /** * Sets a PTPText from a null-terminated char*. Caller must ensure it's * valid to write to the memory after the PTPText struct. The trailing * \0 is not copied. * @param dst The PTPText to copy to * @param src The text to copy from * @return Zero on success, -1 if src is too long */ int ptp_text_set(struct PTPText *dst, const char *src); /** * Sets a static_ptp_text from a null-terminated char*. * @param dst The static_ptp_text to copy to * @param src The text to copy from * @return Zero on success, -1 if text in src is too long or not valid * UTF8 */ int static_ptp_text_set(struct static_ptp_text *dst, const char *src); /** * Check if UTC time stamp can be both before and after a leap second. * * @param ts UTC time stamp in nanoseconds. * @return 0 if not, 1 if yes. */ int is_utc_ambiguous(uint64_t ts); /** * Get leap second status in given time. * * @param ts UTC time stamp in nanoseconds. * @param leap_set Previous leap second status (+1/0/-1). * @param leap Announced leap second (+1/0/-1), will be corrected if * early/late. * @param utc_offset Announced UTC offset, will be corrected if early/late. * @return 0 if the leap second passed, +1 if leap second will be * inserted, -1 if leap second will be deleted. */ int leap_second_status(uint64_t ts, int leap_set, int *leap, int *utc_offset); /** * Values returned by get_ranged_*(). */ enum parser_result { PARSED_OK, NOT_PARSED, BAD_VALUE, MALFORMED, OUT_OF_RANGE, }; /** * Get an integer value from string with error checking and range * specification. * * @param str_val String which contains an integer value. * @param result Parsed value is stored in here. * @param min Lower limit. Return OUT_OF_RANGE if parsed value * is less than min. * @param max Upper Limit. Return OUT_OF_RANGE if parsed value * is bigger than max. * @return PARSED_OK on success, MALFORMED if str_val is malformed, * OUT_OF_RANGE if str_val is out of range. */ enum parser_result get_ranged_int(const char *str_val, int *result, int min, int max); /** * Get an unsigned integer value from string with error checking and range * specification. * * @param str_val String which contains an unsigned integer value. * @param result Parsed value is stored in here. * @param min Lower limit. Return OUT_OF_RANGE if parsed value * is less than min. * @param max Upper Limit. Return OUT_OF_RANGE if parsed value * is bigger than max. * @return PARSED_OK on success, MALFORMED if str_val is malformed, * OUT_OF_RANGE if str_val is out of range. */ enum parser_result get_ranged_uint(const char *str_val, unsigned int *result, unsigned int min, unsigned int max); /** * Get a double value from string with error checking and range * specification. * * @param str_val String which contains a double value. * @param result Parsed value is stored in here. * @param min Lower limit. Return OUT_OF_RANGE if parsed value * is less than min. * @param max Upper Limit. Return OUT_OF_RANGE if parsed value * is bigger than max. * @return PARSED_OK on success, MALFORMED if str_val is malformed, * OUT_OF_RANGE if str_val is out of range. */ enum parser_result get_ranged_double(const char *str_val, double *result, double min, double max); /** * Common procedure to get an int value from argument for ptp4l and phc2sys. * * @param op Character code of an option. * @param optarg Option argument string. * @param val Parsed value is stored in here. * @param min Lower limit. Return -1 if parsed value is less than min. * @param max Upper limit. Return -1 if parsed value is bigger than max. * @return 0 on success, -1 if some error occurs. */ int get_arg_val_i(int op, const char *optarg, int *val, int min, int max); /** * Common procedure to get an unsigned int value from argument for ptp4l * and phc2sys. * * @param op Character code of an option. * @param optarg Option argument string. * @param val Parsed value is stored in here. * @param min Lower limit. Return -1 if parsed value is less than min. * @param max Upper limit. Return -1 if parsed value is bigger than max. * @return 0 on success, -1 if some error occurs. */ int get_arg_val_ui(int op, const char *optarg, unsigned int *val, unsigned int min, unsigned int max); /** * Common procedure to get a double value from argument for ptp4l and phc2sys. * * @param op Character code of an option. * @param optarg Option argument string. * @param val Parsed value is stored in here. * @param min Lower limit. Return -1 if parsed value is less than min. * @param max Upper limit. Return -1 if parsed value is bigger than max. * @return 0 on success, -1 if some error occurs. */ int get_arg_val_d(int op, const char *optarg, double *val, double min, double max); /** * Setup a handler for terminating signals (SIGINT, SIGQUIT, SIGTERM). * * @return 0 on success, -1 on error. */ int handle_term_signals(void); /** * Check if a terminating signal was received. * * @return 1 if no terminating signal was received, 0 otherwise. */ int is_running(void); /** * Allocate memory. This is a malloc() wrapper that terminates the process when * the allocation fails. * * @param size Size of the block. Must be larger than 0. * @return Pointer to the allocated memory. */ void *xmalloc(size_t size); /** * Allocate and clear an array. This is a calloc() wrapper that terminates the * process when the allocation fails. * * @param nmemb Number of elements. Must be larger than 0. * @param size Size of the element. Must be larger than 0. * @return Pointer to the allocated memory. */ void *xcalloc(size_t nmemb, size_t size); /** * Reallocate memory. This is a realloc() wrapper that terminates the process * when the allocation fails. * * @param size Size of the block. Must be larger than 0. * @return Pointer to the allocated memory. */ void *xrealloc(void *ptr, size_t size); /** * Duplicate a string. This is a strdup() wrapper that terminates the process * when the allocation fails. * * @param s String that should be duplicated. * @return Pointer to the duplicated string. */ char *xstrdup(const char *s); /** * Get an allocated and formatted string. This is a wrapper around asprintf() * that terminates the process on errors. * * @param format printf() format string. * @param ... printf() arguments. * @return Pointer to the allocated string. */ #ifdef __GNUC__ __attribute__ ((format (printf, 1, 2))) #endif char *string_newf(const char *format, ...); /** * Reallocate a string and append another string to it. The process is * terminated when the allocation fails. * * @param s String that should be extended. * @param str String appended to s. */ void string_append(char **s, const char *str); #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))) #endif /** * Reallocate a string and append a formatted string to it. The process is * terminated when the allocation fails. * * @param s String that should be extended. * @param format printf() format string. * @param ... printf() arguments. */ void string_appendf(char **s, const char *format, ...); /** * Get an empty array of pointers terminated by NULL. The process is terminated * when the allocation fails. * * @return Pointer to the allocated array. */ void **parray_new(void); /** * Append pointer to a NULL-terminated pointer array. The array is reallocated * in exponentially increasing sizes. The process is terminated when the * allocation fails. * * @param a Pointer to pointer array. * @param p Pointer appended to the array. */ void parray_append(void ***a, void *p); /** * Append pointers to a NULL-terminated pointer array. The array is reallocated * in exponentially increasing sizes. The process is terminated when the * allocation fails. * * @param a Pointer to pointer array. * @param ... NULL-terminated list of pointers. */ void parray_extend(void ***a, ...); /** * Check if enough time has passed to implement a simple rate limiting. * * @param interval Minimum interval between two calls returning 0 (in seconds). * @param last Time of the last call that returned 0, input/output. * @return 1 when rate limited, 0 otherwise. */ int rate_limited(int interval, time_t *last); #endif linuxptp-1.6/version.c000066400000000000000000000021271257727010700151060ustar00rootroot00000000000000/** * @file version.c * @brief Provides a software version string. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "version.h" #define STRINGIFY_(x) #x #define STRINGIFY(x) STRINGIFY_(x) static const char *version = STRINGIFY(VER); void version_show(FILE *fp) { fprintf(fp, "%s\n", version); } const char *version_string(void) { return version; } linuxptp-1.6/version.h000066400000000000000000000023401257727010700151100ustar00rootroot00000000000000/** * @file version.h * @brief Provides a software version string. * @note Copyright (C) 2012 Richard Cochran * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef HAVE_VERSION_H #define HAVE_VERSION_H #include /** * Print the software version string into the given file. * @param fp File pointer open for writing. */ void version_show(FILE *fp); /** * Provide the software version as a human readable string. * @return Pointer to a static global buffer holding the result. */ const char *version_string(void); #endif linuxptp-1.6/version.sh000077500000000000000000000025501257727010700153010ustar00rootroot00000000000000#!/bin/sh # # This scripts takes the major and minor release numbers and adds # local version information from the git version control system. # Adapted from scripts/setlocalversion in the Linux kernel sources. # major=1 minor=6 extra= 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() { cd "$srctree" # Check for git and a git repo. if test -d .git && head=`git rev-parse --verify --short HEAD 2>/dev/null`; then # If we are at a tagged commit (like "v2.6.30-rc6"), we ignore # it, because this version is defined in the top level Makefile. if [ -z "`git describe --exact-match 2>/dev/null`" ]; then # If we are past a tagged commit (like # "v2.6.30-rc5-302-g72357d5"), we pretty print it. if atag="`git describe 2>/dev/null`"; then echo "$atag" | awk -F- '{printf("-%05d-%s", $(NF-1),$(NF))}' # If we don't have a tag at all we print -g{commitish}. else printf '%s%s' -g $head fi fi # Update index only on r/w media [ -w . ] && git update-index --refresh --unmerged > /dev/null # Check for uncommitted changes if git diff-index --name-only HEAD | grep -qv "^scripts/package"; then printf '%s' -dirty fi # All done with git return fi } res="${major}.${minor}${extra}$(scm_version)" echo "$res"