pax_global_header00006660000000000000000000000064140704626770014527gustar00rootroot0000000000000052 comment=6feb76186ac39eab2fc011053f1cf46bfc94c8e4 linuxptp-3.1.1/000077500000000000000000000000001407046267700134145ustar00rootroot00000000000000linuxptp-3.1.1/.gitignore000066400000000000000000000001431407046267700154020ustar00rootroot00000000000000/*.d /*.o /.version /hwstamp_ctl /nsm /phc2sys /pmc /ptp4l /phc_ctl /snmp4lptp /timemaster /ts2phc linuxptp-3.1.1/CODING_STYLE.org000066400000000000000000000061471407046267700160600ustar00rootroot00000000000000 * 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-3.1.1/COPYING000066400000000000000000000432541407046267700144570ustar00rootroot00000000000000 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-3.1.1/README.org000066400000000000000000000153341407046267700150700ustar00rootroot00000000000000 * 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 clock_adjtimex system call. - Implements Boundary Clock (BC), Ordinary Clock (OC) and Transparent Clock (TC). - 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. - Implements unicast operation. - Supports a number of profiles, including: - The automotive profile - The default 1588 profile. - The enterprise profile. - The telecom profiles G.8265.1, G.8275.1, and G.8275.2. - Supports the NetSync Monitor protocol. - Implements Peer to peer one-step. - Supports bonded, IPoIB, and vlan interfaces. * 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. Check whether your network interface supports PTP with the following command. #+BEGIN_EXAMPLE ethtool -T eth0 #+END_EXAMPLE This command shows whether a MAC supports hardware or software time stamping. The following example output indicates support for hardware time stamping. #+BEGIN_EXAMPLE Time stamping parameters for eth6: Capabilities: hardware-transmit (SOF_TIMESTAMPING_TX_HARDWARE) software-transmit (SOF_TIMESTAMPING_TX_SOFTWARE) hardware-receive (SOF_TIMESTAMPING_RX_HARDWARE) software-receive (SOF_TIMESTAMPING_RX_SOFTWARE) software-system-clock (SOF_TIMESTAMPING_SOFTWARE) hardware-raw-clock (SOF_TIMESTAMPING_RAW_HARDWARE) PTP Hardware Clock: 1 Hardware Transmit Timestamp Modes: off (HWTSTAMP_TX_OFF) on (HWTSTAMP_TX_ON) Hardware Receive Filter Modes: none (HWTSTAMP_FILTER_NONE) all (HWTSTAMP_FILTER_ALL) #+END_EXAMPLE The next example shows the case where the MAC only supports software time stamping. The ~ptp4l~ program requires either the ~-S~ command line argument or the ~time_stamping software~ configuration option when using such interfaces. #+BEGIN_EXAMPLE Time stamping parameters for enp6s0: Capabilities: software-transmit (SOF_TIMESTAMPING_TX_SOFTWARE) software-receive (SOF_TIMESTAMPING_RX_SOFTWARE) software-system-clock (SOF_TIMESTAMPING_SOFTWARE) PTP Hardware Clock: none Hardware Transmit Timestamp Modes: none Hardware Receive Filter Modes: none #+END_EXAMPLE Note the ~software-transmit (SOF_TIMESTAMPING_TX_SOFTWARE)~ capability. If this is lacking, then the MAC cannot be used at all. However, adding this capability entails adding a single line of code to the device driver. * Installation 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 Exablaze for donating an ExaNIC X10 - http://exablaze.com/exanic-x10 Thanks to Intel Corporation for donating four NICs, the 82574, 82580, 82599, and the i210. - http://www.intel.com - http://e1000.sourceforge.net Thanks to Meinberg Funkuhren for donating a LANTIME M1000. - https://www.meinbergglobal.com Thanks to Moser Baer for sponsoring the Telecom Profiles and unicast support. - http://www.mobatime.com For testing I use an OTMC 100 grandmaster clock donated by OMICRON Lab. - http://www.omicron-lab.com/ptp linuxptp-3.1.1/address.h000066400000000000000000000023541407046267700152160ustar00rootroot00000000000000/** * @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 #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-3.1.1/as_capable.h000066400000000000000000000026111407046267700156370ustar00rootroot00000000000000/** * @file as_capable.h * @brief Enumerates the states for asCapable. * @note Copyright (C) 2018 Intel Corporation * * 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_AS_CAPABLE_H #define HAVE_AS_CAPABLE_H /* Enum used by the asCapable config option. */ enum as_capable_option { AS_CAPABLE_TRUE, AS_CAPABLE_AUTO, }; /* * Defines whether the device can interoperate with the device on other end via * IEEE 802.1AS protocol. * * More information about this in Section 10.2.4.1 of IEEE 802.1AS standard. */ enum as_capable { NOT_CAPABLE, AS_CAPABLE, /* * Non-standard extension to support Automotive Profile. asCapable * always set to true without checking the system at other end. */ ALWAYS_CAPABLE, }; #endif linuxptp-3.1.1/bmc.c000066400000000000000000000100451407046267700143210ustar00rootroot00000000000000/** * @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" 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, int (*compare)(struct dataset *a, struct dataset *b)) { 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); /* * This scenario is particularly important in the designated_slave_fsm * when it is in PS_SLAVE state. In this scenario, there is no other * foreign master and it will elect itself as master ultimately * resulting in printing out some unnecessary warnings (see * port_slave_priority_warning()). */ if (!port_best && port_bmca(r) == BMCA_NOOP) { return ps; } if (!port_best && PS_LISTENING == ps) return ps; if (clock_class(c) <= 127) { if (compare(clock_ds, port_best) > 0) { return PS_GRAND_MASTER; /*M1*/ } else { return PS_PASSIVE; /*P1*/ } } if (compare(clock_ds, clock_best) > 0) { return PS_GRAND_MASTER; /*M2*/ } if (clock_best_port(c) == r) { return PS_SLAVE; /*S1*/ } if (compare(clock_best, port_best) == A_BETTER_TOPO) { return PS_PASSIVE; /*P2*/ } else { return PS_MASTER; /*M3*/ } } linuxptp-3.1.1/bmc.h000066400000000000000000000045531407046267700143350ustar00rootroot00000000000000/** * @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" #define A_BETTER_TOPO 2 #define A_BETTER 1 #define B_BETTER -1 #define B_BETTER_TOPO -2 enum { DS_CMP_IEEE1588, DS_CMP_G8275, }; /** * BMC state decision algorithm. * @param c The local clock. * @param r The port in question. * @param compare The data set comparison algorithm. * @return A @ref port_state value as the recommended state. */ enum port_state bmc_state_decision(struct clock *c, struct port *r, int (*comapre)(struct dataset *a, struct dataset *b)); /** * Compare two data sets using the algorithm defined in IEEE 1588. * @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); /** * Second part of the data set comparison algorithm, not for general * public use. */ int dscmp2(struct dataset *a, struct dataset *b); /** * Compare two data sets using the algorithm defined in the Telecom * Profiles according to ITU-T G.8275.1 and G.8275.2. * * @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 telecom_dscmp(struct dataset *a, struct dataset *b); #endif linuxptp-3.1.1/clock.c000066400000000000000000001426071407046267700146650ustar00rootroot00000000000000/** * @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 #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 "rtnl.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 interface { STAILQ_ENTRY(interface) list; }; 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 { enum clock_type type; struct config *config; clockid_t clkid; struct servo *servo; enum servo_type servo_type; int (*dscmp)(struct dataset *a, struct dataset *b); 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 sde; int free_running; int freq_est_interval; int local_sync_uncertain; int write_phase_mode; int grand_master_capable; /* for 802.1AS only */ int utc_timescale; int utc_offset_set; int leap_set; int kernel_leap; int utc_offset; int time_flags; /* grand master role */ int time_source; /* grand master role */ UInteger8 max_steps_removed; enum servo_state servo_state; enum timestamp_type timestamping; tmv_t master_offset; tmv_t path_delay; tmv_t ingress_ts; tmv_t initial_delay; struct tsproc *tsproc; struct freq_estimator fest; struct time_status_np status; double master_local_rr; /* maintained when free_running */ double nrr; struct clock_description desc; struct clock_stats stats; int stats_interval; struct clockcheck *sanity_check; struct interface *udsif; LIST_HEAD(clock_subscribers_head, clock_subscriber) subscribers; struct monitor *slave_event_monitor; }; 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 void clock_stats_display(struct clock_stats *s); 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, *tmp; struct timespec now; int i, remove = 1; for (i = 0; i < EVENT_BITMASK_CNT; i++) { if (bitmask[i]) { remove = 0; break; } } LIST_FOREACH_SAFE(s, &c->subscribers, list, tmp) { if (pid_eq(&s->targetPortIdentity, &req->header.sourcePortIdentity)) { if (!remove) { /* Update transport address and event mask. */ 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 (pid_eq(&s->targetPortIdentity, &req->header.sourcePortIdentity)) { 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, 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; interface_destroy(c->udsif); clock_flush_subscriptions(c); LIST_FOREACH_SAFE(p, &c->ports, list, tmp) { clock_remove_port(c, p); } monitor_destroy(c->slave_event_monitor); 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(); tc_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) { struct grandmaster_settings_np *gsn; struct management_tlv_datum *mtd; struct subscribe_events_np *sen; struct management_tlv *tlv; struct time_status_np *tsn; struct tlv_extra *extra; struct PTPText *text; int datalen = 0; extra = tlv_extra_alloc(); if (!extra) { pr_err("failed to allocate TLV descriptor"); return 0; } extra->tlv = (struct TLV *) rsp->management.suffix; 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; break; case TLV_DEFAULT_DATA_SET: memcpy(tlv->data, &c->dds, sizeof(c->dds)); datalen = sizeof(c->dds); break; case TLV_CURRENT_DATA_SET: memcpy(tlv->data, &c->cur, sizeof(c->cur)); datalen = sizeof(c->cur); break; case TLV_PARENT_DATA_SET: memcpy(tlv->data, &c->dad.pds, sizeof(c->dad.pds)); datalen = sizeof(c->dad.pds); break; case TLV_TIME_PROPERTIES_DATA_SET: memcpy(tlv->data, &c->tds, sizeof(c->tds)); datalen = sizeof(c->tds); break; case TLV_PRIORITY1: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->dds.priority1; datalen = sizeof(*mtd); break; case TLV_PRIORITY2: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->dds.priority2; datalen = sizeof(*mtd); break; case TLV_DOMAIN: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->dds.domainNumber; datalen = sizeof(*mtd); break; case TLV_SLAVE_ONLY: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->dds.flags & DDS_SLAVE_ONLY; datalen = sizeof(*mtd); break; case TLV_CLOCK_ACCURACY: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->dds.clockQuality.clockAccuracy; datalen = sizeof(*mtd); break; case TLV_TRACEABILITY_PROPERTIES: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->tds.flags & (TIME_TRACEABLE|FREQ_TRACEABLE); datalen = sizeof(*mtd); break; case TLV_TIMESCALE_PROPERTIES: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->tds.flags & PTP_TIMESCALE; datalen = sizeof(*mtd); break; case TLV_TIME_STATUS_NP: tsn = (struct time_status_np *) tlv->data; tsn->master_offset = tmv_to_nanoseconds(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); 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); 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); datalen = sizeof(*sen); break; case TLV_SYNCHRONIZATION_UNCERTAIN_NP: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = c->local_sync_uncertain; datalen = sizeof(*mtd); break; default: /* The caller should *not* respond to this message. */ tlv_extra_recycle(extra); return 0; } if (datalen % 2) { tlv->data[datalen] = 0; datalen++; } tlv->length = sizeof(tlv->id) + datalen; rsp->header.messageLength += sizeof(*tlv) + datalen; msg_tlv_attach(rsp, extra); /* The caller can respond to this message. */ return 1; } 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, TRANS_GENERAL); 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; case TLV_SYNCHRONIZATION_UNCERTAIN_NP: mtd = (struct management_tlv_datum *) tlv->data; switch (mtd->val) { case SYNC_UNCERTAIN_DONTCARE: case SYNC_UNCERTAIN_FALSE: case SYNC_UNCERTAIN_TRUE: /* Display stats on change of local_sync_uncertain */ if (c->local_sync_uncertain != mtd->val && stats_get_num_values(c->stats.offset)) clock_stats_display(&c->stats); c->local_sync_uncertain = mtd->val; respond = 1; break; } 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, double offset, double freq) { stats_add_value(s->offset, offset); stats_add_value(s->freq, freq); if (stats_get_num_values(s->offset) < s->max_count) return; clock_stats_display(s); } static void clock_stats_display(struct clock_stats *s) { struct stats_result offset_stats, freq_stats, delay_stats; 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) { struct freq_estimator *f = &c->fest; double freq, fui, ratio; enum servo_state state; if (c->local_sync_uncertain == SYNC_UNCERTAIN_FALSE) { state = SERVO_LOCKED; } else { 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 (tmv_is_zero(f->ingress1)) { f->ingress1 = ingress; f->origin1 = origin; return state; } f->count++; if (f->count < f->max_count) { return state; } if (tmv_cmp(ingress, f->ingress1) == 0) { 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_dbl(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; c->master_local_rr = ratio; 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; if (!c->best) return; 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 < c->utc_offset) { pr_warning("running in a temporal vortex"); } if ((c->tds.flags & UTC_OFF_VALID && c->tds.flags & TIME_TRACEABLE) || (c->tds.currentUtcOffset > c->utc_offset)) { pr_info("updating UTC offset to %d", c->tds.currentUtcOffset); c->utc_offset = c->tds.currentUtcOffset; } } 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; utc_offset = c->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; } int (*clock_dscmp(struct clock *c))(struct dataset *a, struct dataset *b) { return c->dscmp; } struct currentDS *clock_current_dataset(struct clock *c) { return &c->cur; } static int clock_add_port(struct clock *c, const char *phc_device, 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_device, 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); } int clock_required_modes(struct clock *c) { int required_modes = 0; switch (c->timestamping) { 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: case TS_P2P1STEP: required_modes |= SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE; break; default: break; } return required_modes; } struct clock *clock_create(enum clock_type type, struct config *config, const char *phc_device) { enum servo_type servo = config_get_int(config, NULL, "clock_servo"); char ts_label[IF_NAMESIZE], phc[32], *tmp; enum timestamp_type timestamping; int fadj = 0, max_adj = 0, sw_ts; int phc_index, required_modes = 0; struct clock *c = &the_clock; const char *uds_ifname; struct port *p; unsigned char oui[OUI_LEN]; 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); } switch (type) { case CLOCK_TYPE_ORDINARY: case CLOCK_TYPE_BOUNDARY: case CLOCK_TYPE_P2P: case CLOCK_TYPE_E2E: c->type = type; break; case CLOCK_TYPE_MANAGEMENT: return NULL; } /* Initialize the defaultDS. */ c->dds.clockQuality.clockClass = config_get_int(config, NULL, "clockClass"); c->dds.clockQuality.clockAccuracy = config_get_int(config, NULL, "clockAccuracy"); c->dds.clockQuality.offsetScaledLogVariance = config_get_int(config, NULL, "offsetScaledLogVariance"); c->desc.productDescription.max_symbols = 64; c->desc.revisionData.max_symbols = 32; c->desc.userDescription.max_symbols = 128; tmp = config_get_string(config, NULL, "productDescription"); if (count_char(tmp, ';') != 2 || static_ptp_text_set(&c->desc.productDescription, tmp)) { pr_err("invalid productDescription '%s'", tmp); return NULL; } tmp = config_get_string(config, NULL, "revisionData"); if (count_char(tmp, ';') != 2 || static_ptp_text_set(&c->desc.revisionData, tmp)) { pr_err("invalid revisionData '%s'", tmp); return NULL; } tmp = config_get_string(config, NULL, "userDescription"); if (static_ptp_text_set(&c->desc.userDescription, tmp)) { pr_err("invalid userDescription '%s'", tmp); return NULL; } tmp = config_get_string(config, NULL, "manufacturerIdentity"); if (OUI_LEN != sscanf(tmp, "%hhx:%hhx:%hhx", &oui[0], &oui[1], &oui[2])) { pr_err("invalid manufacturerIdentity '%s'", tmp); return NULL; } memcpy(c->desc.manufacturerIdentity, oui, OUI_LEN); c->dds.domainNumber = config_get_int(config, NULL, "domainNumber"); if (config_get_int(config, NULL, "slaveOnly")) { c->dds.flags |= DDS_SLAVE_ONLY; } if (!config_get_int(config, NULL, "gmCapable") && c->dds.flags & DDS_SLAVE_ONLY) { pr_err("Cannot mix 1588 slaveOnly with 802.1AS !gmCapable"); return NULL; } if (!config_get_int(config, NULL, "gmCapable") || c->dds.flags & DDS_SLAVE_ONLY) { c->dds.clockQuality.clockClass = 255; } c->default_dataset.localPriority = config_get_int(config, NULL, "G.8275.defaultDS.localPriority"); c->max_steps_removed = config_get_int(config, NULL,"maxStepsRemoved"); /* Harmonize the twoStepFlag with the time_stamping option. */ if (config_harmonize_onestep(config)) { return NULL; } if (config_get_int(config, NULL, "twoStepFlag")) { c->dds.flags |= DDS_TWO_STEP_FLAG; } timestamping = config_get_int(config, NULL, "time_stamping"); if (timestamping == TS_SOFTWARE) { sw_ts = 1; } else { sw_ts = 0; } c->dds.priority1 = config_get_int(config, NULL, "priority1"); c->dds.priority2 = config_get_int(config, NULL, "priority2"); /* Check the time stamping mode on each interface. */ c->timestamping = timestamping; required_modes = clock_required_modes(c); STAILQ_FOREACH(iface, &config->interfaces, list) { memset(ts_label, 0, sizeof(ts_label)); rtnl_get_ts_device(interface_name(iface), ts_label); interface_set_label(iface, ts_label); interface_ensure_tslabel(iface); interface_get_tsinfo(iface); if (interface_tsinfo_valid(iface) && !interface_tsmodes_supported(iface, required_modes)) { pr_err("interface '%s' does not support requested timestamping mode", interface_name(iface)); return NULL; } } iface = STAILQ_FIRST(&config->interfaces); /* determine PHC Clock index */ if (config_get_int(config, NULL, "free_running")) { phc_index = -1; } else if (timestamping == TS_SOFTWARE || timestamping == TS_LEGACY_HW) { phc_index = -1; } else if (phc_device) { if (1 != sscanf(phc_device, "/dev/ptp%d", &phc_index)) { phc_index = -1; } } else if (interface_tsinfo_valid(iface)) { phc_index = interface_phc_index(iface); } else { pr_err("PTP device not specified and automatic determination" " is not supported. Please specify PTP device."); return NULL; } if (phc_index >= 0) { pr_info("selected /dev/ptp%d as PTP clock", phc_index); } if (strcmp(config_get_string(config, NULL, "clockIdentity"), "000000.0000.000000") == 0) { if (generate_clock_identity(&c->dds.clockIdentity, interface_name(iface))) { pr_err("failed to generate a clock identity"); return NULL; } } else { if (str2cid(config_get_string(config, NULL, "clockIdentity"), &c->dds.clockIdentity)) { pr_err("failed to set clock identity"); return NULL; } } /* Configure the UDS. */ uds_ifname = config_get_string(config, NULL, "uds_address"); c->udsif = interface_create(uds_ifname); if (config_set_section_int(config, interface_name(c->udsif), "announceReceiptTimeout", 0)) { return NULL; } if (config_set_section_int(config, interface_name(c->udsif), "delay_mechanism", DM_AUTO)) { return NULL; } if (config_set_section_int(config, interface_name(c->udsif), "network_transport", TRANS_UDS)) { return NULL; } if (config_set_section_int(config, interface_name(c->udsif), "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->local_sync_uncertain = SYNC_UNCERTAIN_DONTCARE; c->write_phase_mode = config_get_int(config, NULL, "write_phase_mode"); c->grand_master_capable = config_get_int(config, NULL, "gmCapable"); c->kernel_leap = config_get_int(config, NULL, "kernel_leap"); c->utc_offset = config_get_int(config, NULL, "utc_offset"); c->time_source = config_get_int(config, NULL, "timeSource"); 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, sizeof(phc), "/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 if (phc_device) { c->clkid = phc_open(phc_device); if (c->clkid == CLOCK_INVALID) { pr_err("Failed to open %s: %m", phc_device); return NULL; } max_adj = clockadj_max_freq(c->clkid); 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); /* Disable write phase mode if not implemented by driver */ if (c->write_phase_mode && !phc_has_writephase(c->clkid)) { pr_err("clock does not support write phase mode"); return NULL; } } 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; if (config_get_int(config, NULL, "dataset_comparison") == DS_CMP_G8275) { c->dscmp = telecom_dscmp; } else { c->dscmp = dscmp; } 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->initial_delay = dbl_tmv(config_get_int(config, NULL, "initial_delay")); c->master_local_rr = 1.0; 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; } } /* 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; if (clock_resize_pollfd(c, 0)) { pr_err("failed to allocate pollfd"); return NULL; } /* Create the UDS interface. */ c->uds_port = port_open(phc_device, phc_index, timestamping, 0, c->udsif, c); if (!c->uds_port) { pr_err("failed to open the UDS port"); return NULL; } clock_fda_changed(c); c->slave_event_monitor = monitor_create(config, c->uds_port); if (!c->slave_event_monitor) { pr_err("failed to create slave event monitor"); return NULL; } /* Create the ports. */ STAILQ_FOREACH(iface, &config->interfaces, list) { if (clock_add_port(c, phc_device, phc_index, timestamping, iface)) { pr_err("failed to open port %s", interface_name(iface)); 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; } struct port *clock_first_port(struct clock *c) { return LIST_FIRST(&c->ports); } 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_free_running(struct clock *c) { return c->free_running ? 1 : 0; } 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 whole 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; /* Don't forward any requests to the UDS port. */ if (out == c->uds_port) { switch (management_action(msg)) { case GET: case SET: case COMMAND: 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++; } } } tmv_t clock_ingress_time(struct clock *c) { return c->ingress_ts; } 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(msg) != 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: case TLV_SYNCHRONIZATION_UNCERTAIN_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; 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; if (msg_pre_send(msg)) goto err; clock_send_notification(c, msg, 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; } void clock_set_sde(struct clock *c, int sde) { c->sde = sde; } int clock_poll(struct clock *c) { int cnt, i; 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 = 0; i < N_POLLFD; i++) { if (cur[i].revents & (POLLIN|POLLPRI|POLLERR)) { if (cur[i].revents & POLLERR) { pr_err("port %d: unexpected socket error", port_number(p)); event = EV_FAULT_DETECTED; } else { event = port_event(p, i); } if (EV_STATE_DECISION_EVENT == event) { c->sde = 1; } if (EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES == event) { c->sde = 1; } port_dispatch(p, event, 0); /* Clear any fault after a little while. */ if (PS_FAULTY == port_state(p)) { clock_fault_timeout(p, 1); break; } } } /* * When the fault timer expires we clear the fault, * but only if the link is up. */ if (cur[N_POLLFD].revents & (POLLIN|POLLPRI)) { clock_fault_timeout(p, 0); if (port_link_status_get(p)) { 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) { c->sde = 1; } } } if (c->sde) { handle_state_decision_event(c); c->sde = 0; } 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_dbl(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_dbl(ppd)); } struct monitor *clock_slave_monitor(struct clock *c) { return c->slave_event_monitor; } int clock_slave_only(struct clock *c) { return c->dds.flags & DDS_SLAVE_ONLY; } UInteger8 clock_max_steps_removed(struct clock *c) { return c->max_steps_removed; } 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, sizeof(phc), "/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; } static void clock_synchronize_locked(struct clock *c, double adj) { 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); } } enum servo_state clock_synchronize(struct clock *c, tmv_t ingress, tmv_t origin) { enum servo_state state = SERVO_UNLOCKED; double adj, weight; int64_t offset; c->ingress_ts = ingress; tsproc_down_ts(c->tsproc, origin, ingress); if (tsproc_update_offset(c->tsproc, &c->master_offset, &weight)) { if (c->free_running) { return clock_no_adjust(c, ingress, origin); } else { 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); } offset = tmv_to_nanoseconds(c->master_offset); adj = servo_sample(c->servo, offset, tmv_to_nanoseconds(ingress), weight, &state); c->servo_state = state; 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: clock_synchronize_locked(c, adj); break; case SERVO_LOCKED_STABLE: if (c->write_phase_mode) { clockadj_set_phase(c->clkid, -offset); adj = 0; } else { clock_synchronize_locked(c, adj); } break; } if (c->stats.max_count > 1) { clock_stats_update(&c->stats, tmv_dbl(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)); } 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) { struct timePropertiesDS tds = c->tds; switch (c->local_sync_uncertain) { case SYNC_UNCERTAIN_DONTCARE: tds.flags &= ~SYNC_UNCERTAIN; break; case SYNC_UNCERTAIN_FALSE: /* Pass the upstream value, if any. */ break; case SYNC_UNCERTAIN_TRUE: tds.flags |= SYNC_UNCERTAIN; break; } return 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 || c->dscmp(&fc->dataset, &best->dataset) > 0) best = fc; } if (best) { best_id = best->dataset.identity; } else { best_id = c->dds.clockIdentity; } if (cid_eq(&best_id, &c->dds.clockIdentity)) { pr_notice("selected local clock %s as best master", cid2str(&best_id)); } else { 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); if (!tmv_is_zero(c->initial_delay)) tsproc_set_delay(c->tsproc, c->initial_delay); c->ingress_ts = tmv_zero(); c->path_delay = c->initial_delay; c->master_local_rr = 1.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, c->dscmp); switch (ps) { case PS_LISTENING: event = EV_NONE; break; case PS_GRAND_MASTER: pr_notice("port %d: assuming the grand master role", port_number(piter)); 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; } enum clock_type clock_type(struct clock *c) { return c->type; } void clock_check_ts(struct clock *c, uint64_t ts) { if (c->sanity_check && clockcheck_sample(c->sanity_check, ts)) { servo_reset(c->servo); } } double clock_rate_ratio(struct clock *c) { if (c->free_running) { return c->master_local_rr; } return servo_rate_ratio(c->servo); } struct servo *clock_servo(struct clock *c) { return c->servo; } enum servo_state clock_servo_state(struct clock *c) { return c->servo_state; } linuxptp-3.1.1/clock.h000066400000000000000000000270571407046267700146730ustar00rootroot00000000000000/** * @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 "monitor.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; enum clock_type { CLOCK_TYPE_ORDINARY = 0x8000, CLOCK_TYPE_BOUNDARY = 0x4000, CLOCK_TYPE_P2P = 0x2000, CLOCK_TYPE_E2E = 0x1000, CLOCK_TYPE_MANAGEMENT = 0x0800, }; /** * 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); /** * Obtains a reference to the current dataset. * @param c The clock instance. * @return A pointer to the current dataset, without fail. */ struct currentDS *clock_current_dataset(struct clock *c); /** * Obtains the clock's data set comparison function. * @param c The clock instance. * @return A pointer to the data set comparison function, without fail. */ int (*clock_dscmp(struct clock *c))(struct dataset *a, struct dataset *b); /** * Obtains the required time stamping mode. * @param c The clock instance. * @return The value of required time stamping mode, which is a bit mask * of SOF_TIMESTAMPING_ flags. */ int clock_required_modes(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 type Specifies which type of clock to create. * @param config Pointer to the configuration database. * @param phc_device PTP hardware clock device to use. Pass NULL for automatic * selection based on the network interface. * @return A pointer to the single global clock instance. */ struct clock *clock_create(enum clock_type type, struct config *config, const char *phc_device); /** * 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); /** * Obtains a reference to the first port in the clock's list. * @param c The clock instance. * @return A pointer to a port, or NULL if no ports are present. */ struct port *clock_first_port(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); /** * Determine if a clock is free running or not. * @param c The clock instance. * @return One if the clock is free running or zero otherwise. */ int clock_free_running(struct clock *c); /** * 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); /** * Obtains the time of the latest synchronization. * @param c The clock instance. * @return The local time stamp of the last received Sync message. */ tmv_t clock_ingress_time(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 event The event that occured. */ void clock_send_notification(struct clock *c, struct ptp_message *msg, 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); /** * Set clock sde * @param c A pointer to a clock instance obtained with clock_create(). * @param sde Pass one (1) if need a decision event and zero if not. */ void clock_set_sde(struct clock *c, int sde); /** * 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 servo struct. * @param c The clock instance. * @return A pointer to the clock's servo. */ struct servo *clock_servo(struct clock *c); /** * Obtain the current state of clock's servo. * @param c The clock instance. * @return The current state of the clock's servo. */ enum servo_state clock_servo_state(struct clock *c); /** * Obtain the slave monitor instance from a clock. * @param c The clock instance. * @return The slave monitor associated with the clock. */ struct monitor *clock_slave_monitor(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 max steps removed field from a clock's default data set. * @param c The clock instance. * @return The value of the clock's max steps removed field. */ UInteger8 clock_max_steps_removed(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 copy of the clock's time properties data set. */ 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 type of a clock. * @param c The clock instance. * @return One of the @ref clock_type enumeration values. */ enum clock_type clock_type(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, uint64_t 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-3.1.1/clockadj.c000066400000000000000000000120021407046267700153250ustar00rootroot00000000000000/** * @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 && tx.tick) f += 1e3 * realtime_hz * (tx.tick - realtime_nominal_tick); } return f; } void clockadj_set_phase(clockid_t clkid, long offset) { struct timex tx; memset(&tx, 0, sizeof(tx)); tx.modes = ADJ_OFFSET | ADJ_NANO; tx.offset = offset; if (clock_adjtime(clkid, &tx) < 0) { pr_err("failed to set the clock offset: %m"); } } 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"); } int clockadj_max_freq(clockid_t clkid) { 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 (clkid == CLOCK_REALTIME && (realtime_nominal_tick && 2 * f >= 1000 * realtime_hz)) f = realtime_nominal_tick / 10 * 1000 * realtime_hz; return f; } 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) { return clockadj_max_freq(CLOCK_REALTIME); } 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-3.1.1/clockadj.h000066400000000000000000000055241407046267700153450ustar00rootroot00000000000000/** * @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); /** * Set clock's phase offset. * @param clkid A clock ID obtained using phc_open() or CLOCK_REALTIME. * @param offset The phase offset in nanoseconds. */ void clockadj_set_phase(clockid_t clkid, long offset); /** * 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); /** * Read maximum frequency adjustment of the target clock. * @return The maximum frequency adjustment in parts per billion (ppb). */ int clockadj_max_freq(clockid_t clkid); /** * 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-3.1.1/clockcheck.c000066400000000000000000000064551407046267700156630ustar00rootroot00000000000000/** * @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-3.1.1/clockcheck.h000066400000000000000000000044241407046267700156620ustar00rootroot00000000000000/** * @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-3.1.1/config.c000066400000000000000000000734451407046267700150420ustar00rootroot00000000000000/** * @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 #include "as_capable.h" #include "bmc.h" #include "clock.h" #include "config.h" #include "ether.h" #include "hash.h" #include "print.h" #include "util.h" struct interface { STAILQ_ENTRY(interface) list; }; enum config_section { GLOBAL_SECTION, UC_MTAB_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 clock_type_enu[] = { { "OC", CLOCK_TYPE_ORDINARY }, { "BC", CLOCK_TYPE_BOUNDARY }, { "P2P_TC", CLOCK_TYPE_P2P }, { "E2E_TC", CLOCK_TYPE_E2E }, { NULL, 0 }, }; static struct config_enum dataset_comp_enu[] = { { "ieee1588", DS_CMP_IEEE1588 }, { "G.8275.x", DS_CMP_G8275 }, { 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 extts_polarity_enu[] = { { "rising", PTP_RISING_EDGE }, { "falling", PTP_FALLING_EDGE }, { "both", PTP_RISING_EDGE | PTP_FALLING_EDGE }, { NULL, 0 }, }; static struct config_enum hwts_filter_enu[] = { { "normal", HWTS_FILTER_NORMAL }, { "check", HWTS_FILTER_CHECK }, { "full", HWTS_FILTER_FULL }, { 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 }, { "onestep", TS_ONESTEP }, { "p2p1step", TS_P2P1STEP }, { 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 }, }; static struct config_enum as_capable_enu[] = { { "true", AS_CAPABLE_TRUE }, { "auto", AS_CAPABLE_AUTO }, { NULL, 0 }, }; static struct config_enum bmca_enu[] = { { "ptp", BMCA_PTP }, { "noop", BMCA_NOOP }, { NULL, 0 }, }; struct config_item config_tab[] = { PORT_ITEM_INT("announceReceiptTimeout", 3, 2, UINT8_MAX), PORT_ITEM_ENU("asCapable", AS_CAPABLE_AUTO, as_capable_enu), GLOB_ITEM_INT("assume_two_step", 0, 0, 1), PORT_ITEM_INT("boundary_clock_jbod", 0, 0, 1), PORT_ITEM_ENU("BMCA", BMCA_PTP, bmca_enu), 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_STR("clockIdentity", "000000.0000.000000"), GLOB_ITEM_ENU("clock_servo", CLOCK_SERVO_PI, clock_servo_enu), GLOB_ITEM_ENU("clock_type", CLOCK_TYPE_ORDINARY, clock_type_enu), GLOB_ITEM_ENU("dataset_comparison", DS_CMP_IEEE1588, dataset_comp_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("dscp_event", 0, 0, 63), GLOB_ITEM_INT("dscp_general", 0, 0, 63), 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("G.8275.defaultDS.localPriority", 128, 1, UINT8_MAX), PORT_ITEM_INT("G.8275.portDS.localPriority", 128, 1, UINT8_MAX), GLOB_ITEM_INT("gmCapable", 1, 0, 1), GLOB_ITEM_ENU("hwts_filter", HWTS_FILTER_NORMAL, hwts_filter_enu), PORT_ITEM_INT("hybrid_e2e", 0, 0, 1), PORT_ITEM_INT("ignore_source_id", 0, 0, 1), PORT_ITEM_INT("ignore_transport_specific", 0, 0, 1), PORT_ITEM_INT("ingressLatency", 0, INT_MIN, INT_MAX), PORT_ITEM_INT("inhibit_announce", 0, 0, 1), PORT_ITEM_INT("inhibit_delay_req", 0, 0, 1), PORT_ITEM_INT("inhibit_multicast_service", 0, 0, 1), GLOB_ITEM_INT("initial_delay", 0, 0, INT_MAX), GLOB_ITEM_INT("kernel_leap", 1, 0, 1), GLOB_ITEM_STR("leapfile", NULL), 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), PORT_ITEM_INT("masterOnly", 0, 0, 1), GLOB_ITEM_INT("maxStepsRemoved", 255, 2, UINT8_MAX), GLOB_ITEM_STR("message_tag", NULL), 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("msg_interval_request", 0, 0, 1), PORT_ITEM_INT("neighborPropDelayThresh", 20000000, 0, INT_MAX), PORT_ITEM_INT("net_sync_monitor", 0, 0, 1), 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("operLogPdelayReqInterval", 0, INT8_MIN, INT8_MAX), PORT_ITEM_INT("operLogSyncInterval", 0, INT8_MIN, INT8_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("servo_num_offset_values", 10, 0, INT_MAX), GLOB_ITEM_INT("servo_offset_threshold", 0, 0, INT_MAX), GLOB_ITEM_STR("slave_event_monitor", ""), GLOB_ITEM_INT("slaveOnly", 0, 0, 1), GLOB_ITEM_INT("socket_priority", 0, 0, 15), 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("tc_spanning_tree", 0, 0, 1), 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_INT("ts2phc.channel", 0, 0, INT_MAX), PORT_ITEM_INT("ts2phc.extts_correction", 0, INT_MIN, INT_MAX), PORT_ITEM_ENU("ts2phc.extts_polarity", PTP_RISING_EDGE, extts_polarity_enu), PORT_ITEM_INT("ts2phc.master", 0, 0, 1), GLOB_ITEM_STR("ts2phc.nmea_remote_host", ""), GLOB_ITEM_STR("ts2phc.nmea_remote_port", ""), GLOB_ITEM_STR("ts2phc.nmea_serialport", "/dev/ttyS0"), PORT_ITEM_INT("ts2phc.pin_index", 0, 0, INT_MAX), GLOB_ITEM_INT("ts2phc.pulsewidth", 500000000, 1000000, 999000000), 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"), PORT_ITEM_INT("unicast_listen", 0, 0, 1), PORT_ITEM_INT("unicast_master_table", 0, 0, INT_MAX), PORT_ITEM_INT("unicast_req_duration", 3600, 10, INT_MAX), GLOB_ITEM_INT("use_syslog", 1, 0, 1), GLOB_ITEM_STR("userDescription", ""), GLOB_ITEM_INT("utc_offset", CURRENT_UTC_OFFSET, 0, INT_MAX), GLOB_ITEM_INT("verbose", 0, 0, 1), GLOB_ITEM_INT("write_phase_mode", 0, 0, 1), }; static struct unicast_master_table *current_uc_mtab; 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 int config_switch_unicast_mtab(struct config *cfg, int idx, int line_num) { struct unicast_master_table *table; if (idx < 1) { fprintf(stderr, "line %d: table_id %d is out of range. " "Must be in the range %d to %d\n", line_num, idx, 1, INT_MAX); return -1; } STAILQ_FOREACH(table, &cfg->unicast_master_tables, list) { if (table->table_index == idx) { fprintf(stderr, "line %d: table_id %d already taken\n", line_num, idx); return -1; } } table = calloc(1, sizeof(*table)); if (!table) { fprintf(stderr, "low memory\n"); return -1; } STAILQ_INIT(&table->addrs); table->table_index = idx; memset(&table->peer_addr.portIdentity, 0xff, sizeof(table->peer_addr.portIdentity)); STAILQ_INSERT_TAIL(&cfg->unicast_master_tables, table, list); current_uc_mtab = table; return 0; } static int config_unicast_mtab_address(enum transport_type type, char *address, int line_num) { struct unicast_master_address *item; if (!current_uc_mtab) { fprintf(stderr, "line %d: missing table_id\n", line_num); return -1; } item = calloc(1, sizeof(*item)); if (!item) { fprintf(stderr, "low memory\n"); return -1; } if (str2addr(type, address, &item->address)) { fprintf(stderr, "line %d: bad address\n", line_num); free(item); return -1; } memset(&item->portIdentity, 0xff, sizeof(item->portIdentity)); item->type = type; STAILQ_INSERT_TAIL(¤t_uc_mtab->addrs, item, list); current_uc_mtab->count++; return 0; } static int config_unicast_mtab_peer(char *address, int line_num) { if (!current_uc_mtab) { fprintf(stderr, "line %d: missing table_id\n", line_num); return -1; } if (current_uc_mtab->peer_name) { free(current_uc_mtab->peer_name); } current_uc_mtab->peer_name = strdup(address); if (!current_uc_mtab->peer_name) { fprintf(stderr, "low memory\n"); return -1; } return 0; } static int config_unicast_mtab_query_interval(int lqi, int line_num) { if (!current_uc_mtab) { fprintf(stderr, "line %d: missing table_id\n", line_num); return -1; } if (lqi < INT8_MIN || lqi > INT8_MAX) { fprintf(stderr, "line %d: logQueryInterval %d out of range\n", line_num, lqi); return -1; } current_uc_mtab->logQueryInterval = lqi; return 0; } static enum parser_result parse_section_line(char *s, enum config_section *section) { if (!strcasecmp(s, "[global]")) { *section = GLOBAL_SECTION; } else if (!strcasecmp(s, "[unicast_master_table]")) { *section = UC_MTAB_SECTION; current_uc_mtab = NULL; } 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, int commandline, 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 (!commandline && 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; } if (commandline) { dst->flags |= CFG_ITEM_LOCKED; } 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 int parse_unicast_mtab_line(struct config *cfg, char *line, int line_num) { char address[64 + 1] = {0}, transport[16 + 1] = {0}; enum transport_type type = TRANS_UDS; struct config_enum *cte; int cnt, lqi, table_id; cnt = sscanf(line, " table_id %d", &table_id); if (cnt == 1) { return config_switch_unicast_mtab(cfg, table_id, line_num); } cnt = sscanf(line, " logQueryInterval %d", &lqi); if (cnt == 1) { return config_unicast_mtab_query_interval(lqi, line_num); } cnt = sscanf(line, " peer_address %64s", address); if (cnt == 1) { return config_unicast_mtab_peer(address, line_num); } cnt = sscanf(line, " %16s %64s", transport, address); if (cnt != 2) { fprintf(stderr, "bad master table at line %d\n", line_num); return -1; } for (cte = nw_trans_enu; cte->label; cte++) { if (!strcasecmp(cte->label, transport)) { type = cte->value; break; } } return config_unicast_mtab_address(type, address, line_num); } 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; } } static struct option *config_alloc_longopts(void) { struct config_item *ci; struct option *opts; int i; opts = calloc(1, (1 + N_CONFIG_ITEMS) * sizeof(*opts)); if (!opts) { return NULL; } for (i = 0; i < N_CONFIG_ITEMS; i++) { ci = &config_tab[i]; opts[i].name = ci->label; opts[i].has_arg = required_argument; } return opts; } int config_read(const 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; } continue; } if (current_section == UC_MTAB_SECTION) { if (parse_unicast_mtab_line(cfg, line, line_num)) { goto parse_error; } 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" : interface_name(current_port)); goto parse_error; } check_deprecated_options(&option); parser_res = parse_item(cfg, 0, current_section == GLOBAL_SECTION ? NULL : interface_name(current_port), 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" : interface_name(current_port)); 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(const char *name, struct config *cfg) { struct interface *iface; const char *ifname; /* only create each interface once (by name) */ STAILQ_FOREACH(iface, &cfg->interfaces, list) { ifname = interface_name(iface); if (0 == strncmp(name, ifname, MAX_IFNAME_SIZE)) return iface; } iface = interface_create(name); if (!iface) { fprintf(stderr, "cannot allocate memory for a port\n"); return NULL; } STAILQ_INSERT_TAIL(&cfg->interfaces, iface, list); cfg->n_interfaces++; return iface; } 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); STAILQ_INIT(&cfg->unicast_master_tables); cfg->opts = config_alloc_longopts(); if (!cfg->opts) { free(cfg); return NULL; } cfg->htab = hash_create(); if (!cfg->htab) { free(cfg->opts); 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->opts); free(cfg); return NULL; } void config_destroy(struct config *cfg) { struct unicast_master_address *address; struct unicast_master_table *table; struct interface *iface; while ((iface = STAILQ_FIRST(&cfg->interfaces))) { STAILQ_REMOVE_HEAD(&cfg->interfaces, list); interface_destroy(iface); } while ((table = STAILQ_FIRST(&cfg->unicast_master_tables))) { while ((address = STAILQ_FIRST(&table->addrs))) { STAILQ_REMOVE_HEAD(&table->addrs, list); free(address); } if (table->peer_name) { free(table->peer_name); } STAILQ_REMOVE_HEAD(&cfg->unicast_master_tables, list); free(table); } hash_destroy(cfg->htab, config_item_free); free(cfg->opts); 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_harmonize_onestep(struct config *cfg) { enum timestamp_type tstype = config_get_int(cfg, NULL, "time_stamping"); int two_step_flag = config_get_int(cfg, NULL, "twoStepFlag"); switch (tstype) { case TS_SOFTWARE: case TS_LEGACY_HW: if (!two_step_flag) { pr_err("one step is only possible " "with hardware time stamping"); return -1; } break; case TS_HARDWARE: if (!two_step_flag) { pr_debug("upgrading to one step time stamping " "in order to match the twoStepFlag"); if (config_set_int(cfg, "time_stamping", TS_ONESTEP)) { return -1; } } break; case TS_ONESTEP: case TS_P2P1STEP: if (two_step_flag) { pr_debug("one step mode implies twoStepFlag=0, " "clearing twoStepFlag to match"); if (config_set_int(cfg, "twoStepFlag", 0)) { return -1; } } break; } return 0; } int config_parse_option(struct config *cfg, const char *opt, const char *val) { enum parser_result result; result = parse_item(cfg, 1, NULL, opt, val); switch (result) { case PARSED_OK: return 0; case NOT_PARSED: fprintf(stderr, "unknown option %s\n", opt); break; case BAD_VALUE: fprintf(stderr, "%s is a bad value for option %s\n", val, opt); break; case MALFORMED: fprintf(stderr, "%s is a malformed value for option %s\n", val, opt); break; case OUT_OF_RANGE: fprintf(stderr, "%s is an out of range value for option %s\n", val, opt); break; } return -1; } 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-3.1.1/config.h000066400000000000000000000050551407046267700150370ustar00rootroot00000000000000/** * @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 #include "ds.h" #include "dm.h" #include "filter.h" #include "interface.h" #include "mtab.h" #include "transport.h" #include "servo.h" #include "sk.h" struct config { /* configured interfaces */ STAILQ_HEAD(interfaces_head, interface) interfaces; int n_interfaces; /* for parsing command line options */ struct option *opts; /* hash of all non-legacy items */ struct hash *htab; /* unicast master tables */ STAILQ_HEAD(ucmtab_head, unicast_master_table) unicast_master_tables; }; int config_read(const char *name, struct config *cfg); struct interface *config_create_interface(const char *name, 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_harmonize_onestep(struct config *cfg); static inline struct option *config_long_options(struct config *cfg) { return cfg->opts; } int config_parse_option(struct config *cfg, const char *opt, const char *val); 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-3.1.1/configs/000077500000000000000000000000001407046267700150445ustar00rootroot00000000000000linuxptp-3.1.1/configs/E2E-TC.cfg000066400000000000000000000005131407046267700164030ustar00rootroot00000000000000# # End to End Transparent Clock example configuration containing # those attributes which differ from the defaults. See the file, # default.cfg, for the complete list of available options. # [global] priority1 254 free_running 1 freq_est_interval 3 tc_spanning_tree 1 summary_interval 1 clock_type E2E_TC network_transport L2 linuxptp-3.1.1/configs/G.8265.1.cfg000066400000000000000000000007641407046267700164640ustar00rootroot00000000000000# # Telecom G.8265.1 example configuration containing those attributes # which either differ from the defaults or are relevant to the # profile. See the file, default.cfg, for the complete list of # available options. # [global] masterOnly 0 hybrid_e2e 1 inhibit_multicast_service 1 unicast_listen 1 unicast_req_duration 60 # # Customize the following for slave operation: # #[unicast_master_table] #table_id 1 #logQueryInterval 2 #UDPv4 192.168.1.11 # #[eth0] #unicast_master_table 1 linuxptp-3.1.1/configs/G.8275.1.cfg000066400000000000000000000007721407046267700164640ustar00rootroot00000000000000# # Telecom G.8275.1 example configuration containing those attributes # which either differ from the defaults or are relevant to the # profile. See the file, default.cfg, for the complete list of # available options. # [global] dataset_comparison G.8275.x G.8275.defaultDS.localPriority 128 maxStepsRemoved 255 logAnnounceInterval -3 logSyncInterval -4 logMinDelayReqInterval -4 masterOnly 0 G.8275.portDS.localPriority 128 ptp_dst_mac 01:80:C2:00:00:0E network_transport L2 linuxptp-3.1.1/configs/G.8275.2.cfg000066400000000000000000000012011407046267700164510ustar00rootroot00000000000000# # Telecom G.8275.2 example configuration containing those attributes # which either differ from the defaults or are relevant to the # profile. See the file, default.cfg, for the complete list of # available options. # [global] dataset_comparison G.8275.x G.8275.defaultDS.localPriority 128 maxStepsRemoved 255 logAnnounceInterval 0 masterOnly 0 G.8275.portDS.localPriority 128 hybrid_e2e 1 inhibit_multicast_service 1 unicast_listen 1 unicast_req_duration 60 # # Customize the following for slave operation: # #[unicast_master_table] #table_id 1 #logQueryInterval 2 #UDPv4 192.168.1.11 # #[eth0] #unicast_master_table 1 linuxptp-3.1.1/configs/P2P-TC.cfg000066400000000000000000000005421407046267700164330ustar00rootroot00000000000000# # Peer to Peer Transparent Clock example configuration containing # those attributes which differ from the defaults. See the file, # default.cfg, for the complete list of available options. # [global] priority1 254 free_running 1 freq_est_interval 3 tc_spanning_tree 1 summary_interval 1 clock_type P2P_TC network_transport L2 delay_mechanism P2P linuxptp-3.1.1/configs/UNICAST-MASTER.cfg000066400000000000000000000003721407046267700176260ustar00rootroot00000000000000# # Unicast master example configuration containing those attributes # which differ from the defaults. See the file, default.cfg, for the # complete list of available options. # [global] hybrid_e2e 1 inhibit_multicast_service 1 unicast_listen 1 linuxptp-3.1.1/configs/UNICAST-SLAVE.cfg000066400000000000000000000015311407046267700175030ustar00rootroot00000000000000# # UNICAST slave example configuration with contrived master tables. # This example will not work out of the box! # [global] # # Request service for sixty seconds. # unicast_req_duration 60 # # This table has four possible UDPv4 master clocks. # [unicast_master_table] table_id 1 logQueryInterval 2 UDPv4 192.168.1.11 UDPv4 192.168.2.22 UDPv4 192.168.3.33 # # This table has just one Layer-2 master clock. # [unicast_master_table] table_id 2 logQueryInterval 2 L2 00:11:22:33:44:55 # # This table would be for use with the P2P delay mechanism. # [unicast_master_table] table_id 3 logQueryInterval 2 peer_address 192.168.4.44 UDPv4 192.168.4.44 # # eth0 uses the master table with ID 1 over UDPv4. # [eth0] unicast_master_table 1 # # eth1 uses the master table with ID 2 over Layer-2. # [eth1] network_transport L2 unicast_master_table 2 linuxptp-3.1.1/configs/automotive-master.cfg000066400000000000000000000012461407046267700212150ustar00rootroot00000000000000# # Automotive Profile example configuration for master containing those # attributes which differ from the defaults. See the file, default.cfg, for # the complete list of available options. # [global] # Options carried over from gPTP. gmCapable 1 priority1 248 priority2 248 logSyncInterval -3 syncReceiptTimeout 3 neighborPropDelayThresh 800 min_neighbor_prop_delay -20000000 assume_two_step 1 path_trace_enabled 1 follow_up_info 1 transportSpecific 0x1 ptp_dst_mac 01:80:C2:00:00:0E network_transport L2 delay_mechanism P2P # # Automotive Profile specific options # BMCA noop masterOnly 1 inhibit_announce 1 asCapable true inhibit_delay_req 1 linuxptp-3.1.1/configs/automotive-slave.cfg000066400000000000000000000015671407046267700210420ustar00rootroot00000000000000# # Automotive Profile example configuration for slaves containing those # attributes which differ from the defaults. See the file, default.cfg, for # the complete list of available options. # [global] # # Options carried over from gPTP. # gmCapable 1 priority1 248 priority2 248 logSyncInterval -3 syncReceiptTimeout 3 neighborPropDelayThresh 800 min_neighbor_prop_delay -20000000 assume_two_step 1 path_trace_enabled 1 follow_up_info 1 transportSpecific 0x1 ptp_dst_mac 01:80:C2:00:00:0E network_transport L2 delay_mechanism P2P # # Automotive Profile specific options # BMCA noop slaveOnly 1 inhibit_announce 1 asCapable true ignore_source_id 1 # Required to quickly correct Time Jumps in master step_threshold 1 operLogSyncInterval 0 operLogPdelayReqInterval 2 msg_interval_request 1 servo_offset_threshold 30 servo_num_offset_values 10 linuxptp-3.1.1/configs/default.cfg000066400000000000000000000041601407046267700171520ustar00rootroot00000000000000[global] # # Default Data Set # twoStepFlag 1 slaveOnly 0 socket_priority 0 priority1 128 priority2 128 domainNumber 0 #utc_offset 37 clockClass 248 clockAccuracy 0xFE offsetScaledLogVariance 0xFFFF free_running 0 freq_est_interval 1 dscp_event 0 dscp_general 0 dataset_comparison ieee1588 G.8275.defaultDS.localPriority 128 maxStepsRemoved 255 # # Port Data Set # logAnnounceInterval 1 logSyncInterval 0 operLogSyncInterval 0 logMinDelayReqInterval 0 logMinPdelayReqInterval 0 operLogPdelayReqInterval 0 announceReceiptTimeout 3 syncReceiptTimeout 0 delayAsymmetry 0 fault_reset_interval 4 neighborPropDelayThresh 20000000 masterOnly 0 G.8275.portDS.localPriority 128 asCapable auto BMCA ptp inhibit_announce 0 inhibit_delay_req 0 ignore_source_id 0 # # Run time options # assume_two_step 0 logging_level 6 path_trace_enabled 0 follow_up_info 0 hybrid_e2e 0 inhibit_multicast_service 0 net_sync_monitor 0 tc_spanning_tree 0 tx_timestamp_timeout 1 unicast_listen 0 unicast_master_table 0 unicast_req_duration 3600 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 msg_interval_request 0 servo_num_offset_values 10 servo_offset_threshold 0 write_phase_mode 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 # clock_type OC 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-3.1.1/configs/gPTP.cfg000066400000000000000000000007661407046267700163500ustar00rootroot00000000000000# # 802.1AS example configuration containing those attributes which # differ from the defaults. See the file, default.cfg, for the # complete list of available options. # [global] gmCapable 1 priority1 248 priority2 248 logAnnounceInterval 0 logSyncInterval -3 syncReceiptTimeout 3 neighborPropDelayThresh 800 min_neighbor_prop_delay -20000000 assume_two_step 1 path_trace_enabled 1 follow_up_info 1 transportSpecific 0x1 ptp_dst_mac 01:80:C2:00:00:0E network_transport L2 delay_mechanism P2P linuxptp-3.1.1/configs/snmpd.conf000066400000000000000000000023051407046267700170340ustar00rootroot00000000000000# Map 'linuxptp' community to the 'LinuxPtpUser' # Map 'public' community to the 'AllUser' # sec.name source community com2sec LinuxPtpUser default linuxptp com2sec AllUser default public # Map 'LinuxPtpUser' to 'LinuxPtpGroup' for SNMP Version 2c # Map 'AllUser' to 'AllGroup' for SNMP Version 2c # sec.model sec.name group LinuxPtpGroup v2c LinuxPtpUser group AllGroup v2c AllUser # Define 'SystemView', which includes everything under .1.3.6.1.2.1.241 # Define 'AllView', which includes everything under .1 # incl/excl subtree view SystemView included .1.3.6.1.2.1.241 view AllView included .1 # Give 'ConfigGroup' read access to objects in the view 'SystemView' # Give 'AllGroup' read access to objects in the view 'AllView' # context model level prefix read write notify access LinuxPtpGroup "" any noauth exact SystemView none none access AllGroup "" any noauth exact AllView none none # turn on the AgentX master agent support master agentx linuxptp-3.1.1/configs/ts2phc-TC.cfg000066400000000000000000000012601407046267700172330ustar00rootroot00000000000000# # This example shows ts2phc keeping a group of three Intel i210 cards # synchronized to each other in order to form a Transparent Clock. # The cards are configured to use their SDP0 pins connected in # hardware. Here eth3 and eth4 will be slaved to eth6. # # Important! The polarity is set to "both" because the i210 always # time stamps both the rising and the falling edges of the input # signal. # [global] use_syslog 0 verbose 1 logging_level 6 ts2phc.pulsewidth 500000000 [eth6] ts2phc.channel 0 ts2phc.master 1 ts2phc.pin_index 0 [eth3] ts2phc.channel 0 ts2phc.extts_polarity both ts2phc.pin_index 0 [eth4] ts2phc.channel 0 ts2phc.extts_polarity both ts2phc.pin_index 0 linuxptp-3.1.1/configs/ts2phc-generic.cfg000066400000000000000000000007261407046267700203470ustar00rootroot00000000000000# # This example uses a PPS signal from a GPS receiver as an input to # the SDP0 pin of an Intel i210 card. The pulse from the receiver has # a width of 100 milliseconds. # # Important! The polarity is set to "both" because the i210 always # time stamps both the rising and the falling edges of the input # signal. # [global] use_syslog 0 verbose 1 logging_level 6 ts2phc.pulsewidth 100000000 [eth6] ts2phc.channel 0 ts2phc.extts_polarity both ts2phc.pin_index 0 linuxptp-3.1.1/contain.h000066400000000000000000000022131407046267700152160ustar00rootroot00000000000000/** * @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-3.1.1/ddt.h000066400000000000000000000054611407046267700143460ustar00rootroot00000000000000/** * @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; }; /* Four bits are dedicated to messageType field */ #define MAX_MESSAGE_TYPES 16 struct PortStats { uint64_t rxMsgType[MAX_MESSAGE_TYPES]; uint64_t txMsgType[MAX_MESSAGE_TYPES]; }; #endif linuxptp-3.1.1/designated_fsm.c000066400000000000000000000043061407046267700165370ustar00rootroot00000000000000/** * @file designated_fsm.c * @brief Implements designated Finite State Machines. * @note Copyright (C) 2018 Intel Corporation * * 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-1335 USA. */ #include "fsm.h" #include "designated_fsm.h" enum port_state designated_master_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: switch (event) { case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_INIT_COMPLETE: next = PS_MASTER; break; default: break; } break; case PS_FAULTY: if (event == EV_FAULT_CLEARED) { next = PS_INITIALIZING; } break; case PS_MASTER: if (event == EV_FAULT_DETECTED) { next = PS_FAULTY; } break; default: break; } return next; } enum port_state designated_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: switch (event) { case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_INIT_COMPLETE: next = PS_SLAVE; break; default: break; } break; case PS_FAULTY: if (event == EV_FAULT_CLEARED) { next = PS_INITIALIZING; } break; case PS_SLAVE: switch (event) { case EV_FAULT_DETECTED: next = PS_FAULTY; break; default: break; } break; default: break; } return next; } linuxptp-3.1.1/designated_fsm.h000066400000000000000000000032571407046267700165500ustar00rootroot00000000000000/** * @file designated_fsm.c * @brief Implements designated Finite State Machines. * @note Copyright (C) 2018 Intel Corporation * * 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-1335 USA. */ #ifndef HAVE_DESIGNATED_FSM_H #define HAVE_DESIGNATED_FSM_H #include "fsm.h" /** * Run the state machine for a clock which is designated as master port. * @param state The current state of the port. * @param event The event to be processed. * @param mdiff This param is not used by this function. * @return The new state for the port. */ enum port_state designated_master_fsm(enum port_state state, enum fsm_event event, int mdiff); /** * Run the state machine for a clock designated as slave port. * @param state The current state of the port. * @param event The event to be processed. * @param mdiff This param is not used by this function. * @return The new state for the port. */ enum port_state designated_slave_fsm(enum port_state state, enum fsm_event event, int mdiff); #endif linuxptp-3.1.1/dm.h000066400000000000000000000021671407046267700141730ustar00rootroot00000000000000/** * @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-3.1.1/ds.h000066400000000000000000000062011407046267700141720ustar00rootroot00000000000000/** * @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 dataset { UInteger8 priority1; struct ClockIdentity identity; struct ClockQuality quality; UInteger8 priority2; UInteger8 localPriority; /* Telecom Profile only */ 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 37 /* 1 Jan 2017 */ #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-3.1.1/e2e_tc.c000066400000000000000000000117241407046267700147260ustar00rootroot00000000000000/** * @file e2e_tc.c * @note Copyright (C) 2018 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-1335 USA. */ #include #include "port.h" #include "port_private.h" #include "print.h" #include "rtnl.h" #include "tc.h" void e2e_dispatch(struct port *p, enum fsm_event event, int mdiff) { if (!port_state_update(p, event, mdiff)) { return; } if (!portnum(p)) { /* UDS needs no timers. */ return; } 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]); /* * Handle the side effects of the state transition. */ switch (p->state) { case PS_INITIALIZING: break; case PS_FAULTY: case PS_DISABLED: port_disable(p); break; case PS_LISTENING: port_set_announce_tmo(p); port_set_delay_tmo(p); break; case PS_PRE_MASTER: port_set_qualification_tmo(p); break; case PS_MASTER: case PS_GRAND_MASTER: 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); break; }; } enum fsm_event e2e_event(struct port *p, int fd_index) { int cnt, fd = p->fda.fd[fd_index]; enum fsm_event event = EV_NONE; struct ptp_message *msg, *dup; 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); return EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES; case FD_DELAY_TIMER: pr_debug("port %hu: delay timeout", portnum(p)); port_set_delay_tmo(p); delay_req_prune(p); tc_prune(p); if (!clock_free_running(p->clock)) { switch (p->state) { case PS_UNCALIBRATED: case PS_SLAVE: if (port_delay_request(p)) { event = EV_FAULT_DETECTED; } break; default: break; }; } return event; case FD_QUALIFICATION_TIMER: pr_debug("port %hu: qualification timeout", portnum(p)); return EV_QUALIFICATION_TIMEOUT_EXPIRES; case FD_MANNO_TIMER: case FD_SYNC_TX_TIMER: case FD_UNICAST_REQ_TIMER: case FD_UNICAST_SRV_TIMER: pr_err("unexpected timer expiration"); return EV_NONE; case FD_RTNL: pr_debug("port %hu: received link status notification", portnum(p)); rtnl_link_status(fd, p->name, port_link_status, p); if (p->link_status == (LINK_UP|LINK_STATE_CHANGED)) { return EV_FAULT_CLEARED; } else if ((p->link_status == (LINK_DOWN|LINK_STATE_CHANGED)) || (p->link_status & TS_LABEL_CHANGED)) { return EV_FAULT_DETECTED; } else { return 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; } if (msg_sots_valid(msg)) { ts_add(&msg->hwts.ts, -p->rx_timestamp_offset); } if (msg_unicast(msg)) { pl_warning(600, "cannot handle unicast messages!"); msg_put(msg); return EV_NONE; } dup = msg_duplicate(msg, cnt); if (!dup) { msg_put(msg); return EV_NONE; } if (tc_ignore(p, dup)) { msg_put(dup); dup = NULL; } switch (msg_type(msg)) { case SYNC: if (tc_fwd_sync(p, msg)) { event = EV_FAULT_DETECTED; break; } if (dup) { process_sync(p, dup); } break; case DELAY_REQ: if (tc_fwd_request(p, msg)) { event = EV_FAULT_DETECTED; } break; case PDELAY_REQ: break; case PDELAY_RESP: break; case FOLLOW_UP: if (tc_fwd_folup(p, msg)) { event = EV_FAULT_DETECTED; break; } if (dup) { process_follow_up(p, dup); } break; case DELAY_RESP: if (tc_fwd_response(p, msg)) { event = EV_FAULT_DETECTED; } if (dup) { process_delay_resp(p, dup); } break; case PDELAY_RESP_FOLLOW_UP: break; case ANNOUNCE: if (tc_forward(p, msg)) { event = EV_FAULT_DETECTED; break; } if (dup && process_announce(p, dup)) { event = EV_STATE_DECISION_EVENT; } break; case SIGNALING: case MANAGEMENT: if (tc_forward(p, msg)) { event = EV_FAULT_DETECTED; } break; } msg_put(msg); if (dup) { msg_put(dup); } return event; } linuxptp-3.1.1/ether.h000066400000000000000000000025421407046267700146770ustar00rootroot00000000000000/** * @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 EUI48 6 #define EUI64 8 #define MAC_LEN EUI48 #define GUID_LEN EUI64 #define GUID_OFFSET 36 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-3.1.1/fault.c000066400000000000000000000020741407046267700146760ustar00rootroot00000000000000/** * @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-3.1.1/fault.h000066400000000000000000000022231407046267700146770ustar00rootroot00000000000000/** * @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. */ #ifndef HAVE_FAULT_H #define HAVE_FAULT_H #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); #endif linuxptp-3.1.1/fd.h000066400000000000000000000027071407046267700141640ustar00rootroot00000000000000/** * @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 8 /* * The order matters here. The DELAY timer must appear before the * ANNOUNCE and SYNC_RX timers in order to correctly handle the case * when the DELAY timer and one of the other two expire during the * same call to poll(). */ enum { FD_EVENT, FD_GENERAL, FD_DELAY_TIMER, FD_ANNOUNCE_TIMER, FD_SYNC_RX_TIMER, FD_QUALIFICATION_TIMER, FD_MANNO_TIMER, FD_SYNC_TX_TIMER, FD_UNICAST_REQ_TIMER, FD_UNICAST_SRV_TIMER, FD_RTNL, N_POLLFD, }; #define FD_FIRST_TIMER FD_DELAY_TIMER struct fdarray { int fd[N_POLLFD]; }; #endif linuxptp-3.1.1/filter.c000066400000000000000000000025211407046267700150450ustar00rootroot00000000000000/** * @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-3.1.1/filter.h000066400000000000000000000035451407046267700150610ustar00rootroot00000000000000/** * @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-3.1.1/filter_private.h000066400000000000000000000020751407046267700166100ustar00rootroot00000000000000/** * @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-3.1.1/foreign.h000066400000000000000000000032001407046267700152110ustar00rootroot00000000000000/** * @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-3.1.1/fsm.c000066400000000000000000000143621407046267700143530ustar00rootroot00000000000000/** * @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: switch (event) { case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_INIT_COMPLETE: next = PS_LISTENING; break; default: break; } 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: switch (event) { case EV_FAULT_DETECTED: next = PS_FAULTY; break; case EV_INIT_COMPLETE: next = PS_LISTENING; break; default: break; } 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-3.1.1/fsm.h000066400000000000000000000044421407046267700143560ustar00rootroot00000000000000/** * @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_INIT_COMPLETE, EV_RS_MASTER, EV_RS_GRAND_MASTER, EV_RS_SLAVE, EV_RS_PASSIVE, }; enum bmca_select { BMCA_PTP, BMCA_NOOP, }; /** * 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-3.1.1/hash.c000066400000000000000000000044131407046267700145050ustar00rootroot00000000000000/** * @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-3.1.1/hash.h000066400000000000000000000037631407046267700145210ustar00rootroot00000000000000/** * @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-3.1.1/hwstamp_ctl.8000066400000000000000000000027471407046267700160440ustar00rootroot00000000000000.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-3.1.1/hwstamp_ctl.c000066400000000000000000000122731407046267700161120ustar00rootroot00000000000000/** * @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-3.1.1/incdefs.sh000077500000000000000000000047331407046267700153750ustar00rootroot00000000000000#!/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 -o -name timex.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 if grep -q HWTSTAMP_TX_ONESTEP_P2P ${prefix}${tstamp}; then printf " -DHAVE_ONESTEP_P2P" fi } flags="$(user_flags)$(kernel_flags)" echo "$flags" linuxptp-3.1.1/interface.c000066400000000000000000000030661407046267700155250ustar00rootroot00000000000000/** * @file interface.c * @brief Implements network interface data structures. * @note Copyright (C) 2020 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #include #include "interface.h" struct interface { STAILQ_ENTRY(interface) list; char name[MAX_IFNAME_SIZE + 1]; char ts_label[MAX_IFNAME_SIZE + 1]; struct sk_ts_info ts_info; }; struct interface *interface_create(const char *name) { struct interface *iface; iface = calloc(1, sizeof(struct interface)); if (!iface) { return NULL; } strncpy(iface->name, name, MAX_IFNAME_SIZE); return iface; } void interface_destroy(struct interface *iface) { free(iface); } void interface_ensure_tslabel(struct interface *iface) { if (!iface->ts_label[0]) { memcpy(iface->ts_label, iface->name, MAX_IFNAME_SIZE); } } int interface_get_tsinfo(struct interface *iface) { return sk_get_ts_info(iface->ts_label, &iface->ts_info); } const char *interface_label(struct interface *iface) { return iface->ts_label; } const char *interface_name(struct interface *iface) { return iface->name; } int interface_phc_index(struct interface *iface) { return iface->ts_info.phc_index; } void interface_set_label(struct interface *iface, const char *label) { strncpy(iface->ts_label, label, MAX_IFNAME_SIZE); } bool interface_tsinfo_valid(struct interface *iface) { return iface->ts_info.valid ? true : false; } bool interface_tsmodes_supported(struct interface *iface, int modes) { if ((iface->ts_info.so_timestamping & modes) == modes) { return true; } return false; } linuxptp-3.1.1/interface.h000066400000000000000000000055001407046267700155250ustar00rootroot00000000000000/** * @file interface.h * @brief Implements network interface data structures. * @note Copyright (C) 2020 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_INTERFACE_H #define HAVE_INTERFACE_H #include #include #include "sk.h" #define MAX_IFNAME_SIZE 108 /* = UNIX_PATH_MAX */ #if (IF_NAMESIZE > MAX_IFNAME_SIZE) #error if_namesize larger than expected. #endif /** Opaque type */ struct interface; /** * Creates an instance of an interface. * @param name The device which indentifies this interface. * @return A pointer to an interface instance on success, NULL otherwise. */ struct interface *interface_create(const char *name); /** * Destroys an instance of an interface. * @param iface A pointer obtained via interface_create(). */ void interface_destroy(struct interface *iface); /** * Ensures that an interface has a proper time stamping label. * @param iface The interface of interest. */ void interface_ensure_tslabel(struct interface *iface); /** * Populate the time stamping information of a given interface. * @param iface The interface of interest. * @return zero on success, negative on failure. */ int interface_get_tsinfo(struct interface *iface); /** * Obtain the time stamping label of a network interface. This can be * different from the name of the interface when bonding is in effect. * * @param iface The interface of interest. * @return The time stamping device name of the network interface. */ const char *interface_label(struct interface *iface); /** * Obtains the name of a network interface. * @param iface The interface of interest. * @return The device name of the network interface. */ const char *interface_name(struct interface *iface); /** * Obtains the index of a PTP Hardware Clock device from a network interface. * @param iface The interface of interest. * @return The PHC index of the interface. */ int interface_phc_index(struct interface *iface); /** * Set the time stamping label of a given interface. * @param iface The interface of interest. * @param name The desired label for the interface. */ void interface_set_label(struct interface *iface, const char *label); /** * Tests whether an interface's time stamping information is valid or not. * @param iface The interface of interest. * @return True if the time stamping information is valid, false otherwise. */ bool interface_tsinfo_valid(struct interface *iface); /** * Tests whether an interface supports a set of given time stamping modes. * @param iface The interface of interest. * @param modes Bit mask of SOF_TIMESTAMPING_ flags. * @return True if the time stamping modes are supported, false otherwise. */ bool interface_tsmodes_supported(struct interface *iface, int modes); #endif linuxptp-3.1.1/linreg.c000066400000000000000000000223601407046267700150430ustar00rootroot00000000000000/** * @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-3.1.1/linreg.h000066400000000000000000000016431407046267700150510ustar00rootroot00000000000000/** * @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-3.1.1/lstab.c000066400000000000000000000120061407046267700146640ustar00rootroot00000000000000/** * @file lstab.c * @note Copyright (C) 2012 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include "lstab.h" /* * Keep a history of the TAI - UTC offset in a lookup table. * * Each entry gives the NTP time when a new TAI offset came into * effect. This is always the second immediately after a leap second. * * The size of the table is the number of entries from the NIST table, * plus room for two hundred more entries to be added at run time. * Since there can be at most two leap seconds per year, this allows * for at least one hundred years. * * The table data are available from * * https://www.ietf.org/timezones/data/leap-seconds.list * * ftp://ftp.nist.gov/pub/time/leap-seconds.list * * When updating this table, do not forget to set N_HISTORICAL_LEAPS * and the expiration date. */ #define BASE_TAI_OFFSET 10 #define N_HISTORICAL_LEAPS 28 #define N_LEAPS (N_HISTORICAL_LEAPS + 200) #define NTP_UTC_OFFSET 2208988800ULL struct epoch_marker { int offset; /* TAI - UTC offset of epoch */ uint64_t ntp; /* NTP time of epoch */ uint64_t tai; /* TAI time of epoch */ uint64_t utc; /* UTC time of epoch */ }; struct lstab { struct epoch_marker lstab[N_LEAPS]; uint64_t expiration_utc; int length; }; static const uint64_t expiration_date_ntp = 3818102400ULL; /* 28 December 2020 */ static const uint64_t offset_table[N_LEAPS * 2] = { 2272060800ULL, 10, /* 1 Jan 1972 */ 2287785600ULL, 11, /* 1 Jul 1972 */ 2303683200ULL, 12, /* 1 Jan 1973 */ 2335219200ULL, 13, /* 1 Jan 1974 */ 2366755200ULL, 14, /* 1 Jan 1975 */ 2398291200ULL, 15, /* 1 Jan 1976 */ 2429913600ULL, 16, /* 1 Jan 1977 */ 2461449600ULL, 17, /* 1 Jan 1978 */ 2492985600ULL, 18, /* 1 Jan 1979 */ 2524521600ULL, 19, /* 1 Jan 1980 */ 2571782400ULL, 20, /* 1 Jul 1981 */ 2603318400ULL, 21, /* 1 Jul 1982 */ 2634854400ULL, 22, /* 1 Jul 1983 */ 2698012800ULL, 23, /* 1 Jul 1985 */ 2776982400ULL, 24, /* 1 Jan 1988 */ 2840140800ULL, 25, /* 1 Jan 1990 */ 2871676800ULL, 26, /* 1 Jan 1991 */ 2918937600ULL, 27, /* 1 Jul 1992 */ 2950473600ULL, 28, /* 1 Jul 1993 */ 2982009600ULL, 29, /* 1 Jul 1994 */ 3029443200ULL, 30, /* 1 Jan 1996 */ 3076704000ULL, 31, /* 1 Jul 1997 */ 3124137600ULL, 32, /* 1 Jan 1999 */ 3345062400ULL, 33, /* 1 Jan 2006 */ 3439756800ULL, 34, /* 1 Jan 2009 */ 3550089600ULL, 35, /* 1 Jul 2012 */ 3644697600ULL, 36, /* 1 Jul 2015 */ 3692217600ULL, 37, /* 1 Jan 2017 */ }; static void epoch_marker_init(struct epoch_marker *ls, uint64_t val, int offset) { ls->ntp = val; ls->utc = val - NTP_UTC_OFFSET; ls->tai = val - NTP_UTC_OFFSET + offset; ls->offset = offset; } static void lstab_init(struct lstab *lstab) { struct epoch_marker *ls; uint64_t offset, val; int i; for (i = 0; i < N_HISTORICAL_LEAPS; i++) { ls = lstab->lstab + i; val = offset_table[2 * i]; offset = offset_table[2 * i + 1]; epoch_marker_init(ls, val, offset); } lstab->expiration_utc = expiration_date_ntp - NTP_UTC_OFFSET; lstab->length = i; } void lstab_print(struct lstab *lstab, FILE *fp) { int i, len = lstab->length; fprintf(fp, "%3s%12s%12s%12s%4s\n", "idx", "NTP", "TAI", "UTC", "OFF"); for (i = 0; i < len; i++) { fprintf(fp, "%3d" "%12" PRIu64 "%12" PRIu64 "%12" PRIu64 "%4d\n", i, lstab->lstab[i].ntp, lstab->lstab[i].tai, lstab->lstab[i].utc, lstab->lstab[i].offset); } } static int lstab_read(struct lstab *lstab, const char *name) { uint64_t expiration, val; struct epoch_marker *ls; int index = 0, offset; char buf[1024]; FILE *fp; fp = fopen(name, "r"); if (!fp) { fprintf(stderr, "failed to open '%s' for reading: %m\n", name); return -1; } while (1) { if (!fgets(buf, sizeof(buf), fp)) { break; } if (1 == sscanf(buf, "#@ %" PRIu64, &expiration)) { lstab->expiration_utc = expiration - NTP_UTC_OFFSET; continue; } if (2 == sscanf(buf, "%" PRIu64 " %d", &val, &offset)) { ls = lstab->lstab + index; epoch_marker_init(ls, val, offset); index++; } } if (!lstab->expiration_utc) { fprintf(stderr, "missing expiration date in '%s'\n", name); return -1; } lstab->length = index; return 0; } struct lstab *lstab_create(const char *filename) { struct lstab *lstab = calloc(1, sizeof(*lstab)); if (!lstab) { return NULL; } if (filename && filename[0]) { if (lstab_read(lstab, filename)) { free(lstab); return NULL; } } else { lstab_init(lstab); } return lstab; } void lstab_destroy(struct lstab *lstab) { free(lstab); } enum lstab_result lstab_utc2tai(struct lstab *lstab, uint64_t utctime, int *tai_offset) { int epoch = -1, index, next; if (utctime > lstab->expiration_utc) { return LSTAB_UNKNOWN; } for (index = lstab->length - 1; index > -1; index--) { if (utctime >= lstab->lstab[index].utc) { epoch = index; break; } } if (epoch == -1) { return LSTAB_UNKNOWN; } *tai_offset = lstab->lstab[epoch].offset; next = epoch + 1; if (next < lstab->length && utctime == lstab->lstab[next].utc - 1) { return LSTAB_AMBIGUOUS; } return LSTAB_OK; } linuxptp-3.1.1/lstab.h000066400000000000000000000032301407046267700146700ustar00rootroot00000000000000/** * @file lstab.h * @note Copyright (C) 2012 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_LEAP_SECONDS_H #define HAVE_LEAP_SECONDS_H #include /** Opaque type */ struct lstab; /** * Creates an instance of a leap second table. * @param filename File from which to initialize the table. If NULL or empty, * the hard coded default table will be used. * @return A pointer to a leap second table on success, NULL otherwise. */ struct lstab *lstab_create(const char *filename); /** * Destroys a leap second table instance. * @param lstab A pointer obtained via lstab_create(). */ void lstab_destroy(struct lstab *lstab); /** * Enumerates the possible result code for the lstab_utc2tai() method. */ enum lstab_result { /** * The given UTC value was found in the table, and the * corresponding TAI time is utctime + tai_offset. */ LSTAB_OK, /** * The given UTC value is out of the range of the table, and * the tai_offset return value is not set. */ LSTAB_UNKNOWN, /** * The given UTC value is ambiguous. The corresponding TAI time is either * * utctime + tai_offset * or * utctime + tai_offset + 1. */ LSTAB_AMBIGUOUS, }; /** * Returns the TAI - UTC offset for a given UTC time value. * @param lstab A pointer obtained via lstab_create(). * @param utctime The UTC time value of interest, in seconds. * @param tai_offset Pointer to a buffer to hold the result. * @return One of the lstab_result enumeration values. */ enum lstab_result lstab_utc2tai(struct lstab *lstab, uint64_t utctime, int *tai_offset); #endif linuxptp-3.1.1/makefile000066400000000000000000000071061407046267700151200ustar00rootroot00000000000000# # 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 -pthread $(EXTRA_LDFLAGS) PRG = ptp4l hwstamp_ctl nsm phc2sys phc_ctl pmc timemaster ts2phc FILTERS = filter.o mave.o mmedian.o SERVOS = linreg.o ntpshm.o nullf.o pi.o servo.o TRANSP = raw.o transport.o udp.o udp6.o uds.o TS2PHC = ts2phc.o lstab.o nmea.o serial.o sock.o ts2phc_generic_master.o \ ts2phc_master.o ts2phc_phc_master.o ts2phc_nmea_master.o ts2phc_slave.o OBJ = bmc.o clock.o clockadj.o clockcheck.o config.o designated_fsm.o \ e2e_tc.o fault.o $(FILTERS) fsm.o hash.o interface.o monitor.o msg.o phc.o \ port.o port_signaling.o pqueue.o print.o ptp4l.o p2p_tc.o rtnl.o $(SERVOS) \ sk.o stats.o tc.o $(TRANSP) telecom.o tlv.o tsproc.o unicast_client.o \ unicast_fsm.o unicast_service.o util.o version.o OBJECTS = $(OBJ) hwstamp_ctl.o nsm.o phc2sys.o phc_ctl.o pmc.o pmc_common.o \ sysoff.o timemaster.o $(TS2PHC) 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) nsm: config.o $(FILTERS) hash.o interface.o msg.o nsm.o phc.o print.o \ rtnl.o sk.o $(TRANSP) tlv.o tsproc.o util.o version.o pmc: config.o hash.o interface.o msg.o phc.o pmc.o pmc_common.o print.o sk.o \ tlv.o $(TRANSP) util.o version.o phc2sys: clockadj.o clockcheck.o config.o hash.o interface.o msg.o \ phc.o phc2sys.o pmc_common.o print.o $(SERVOS) sk.o stats.o \ sysoff.o tlv.o $(TRANSP) 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: phc.o print.o rtnl.o sk.o timemaster.o util.o version.o ts2phc: config.o clockadj.o hash.o interface.o phc.o print.o $(SERVOS) sk.o \ $(TS2PHC) 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) for x in $(PRG:%=%.8); do \ [ -f $$x ] && install -p -m 644 -t $(DESTDIR)$(man8dir) $$x ; \ done clean: rm -f $(OBJECTS) $(DEPEND) $(PRG) distclean: clean 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-3.1.1/mave.c000066400000000000000000000037701407046267700145170ustar00rootroot00000000000000/** * @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 = tmv_zero(); 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-3.1.1/mave.h000066400000000000000000000017041407046267700145170ustar00rootroot00000000000000/** * @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-3.1.1/missing.h000066400000000000000000000160371407046267700152450ustar00rootroot00000000000000/** * @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 #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) ((((unsigned int) ~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 HAVE_ONESTEP_P2P enum { HWTSTAMP_TX_ONESTEP_P2P = 3, }; #endif #ifdef PTP_EXTTS_REQUEST2 #define PTP_EXTTS_REQUEST_FAILED "PTP_EXTTS_REQUEST2 failed: %m" #else #define PTP_EXTTS_REQUEST_FAILED "PTP_EXTTS_REQUEST failed: %m" #define PTP_EXTTS_REQUEST2 PTP_EXTTS_REQUEST #endif #ifdef PTP_PEROUT_REQUEST2 #define PTP_PEROUT_REQUEST_FAILED "PTP_PEROUT_REQUEST2 failed: %m" #else #define PTP_PEROUT_REQUEST_FAILED "PTP_PEROUT_REQUEST failed: %m" #define PTP_PEROUT_REQUEST2 PTP_PEROUT_REQUEST #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(5,8,0) /* from upcoming Linux kernel version 5.8 */ struct compat_ptp_clock_caps { int max_adj; /* Maximum frequency adjustment in parts per billon. */ int n_alarm; /* Number of programmable alarms. */ int n_ext_ts; /* Number of external time stamp channels. */ int n_per_out; /* Number of programmable periodic signals. */ int pps; /* Whether the clock supports a PPS callback. */ int n_pins; /* Number of input/output pins. */ /* Whether the clock supports precise system-device cross timestamps */ int cross_timestamping; /* Whether the clock supports adjust phase */ int adjust_phase; int rsv[12]; /* Reserved for future use. */ }; #define ptp_clock_caps compat_ptp_clock_caps #endif /*LINUX_VERSION_CODE < 5.8*/ #ifndef PTP_MAX_SAMPLES #define PTP_MAX_SAMPLES 25 /* Maximum allowed offset measurement samples. */ #endif /* PTP_MAX_SAMPLES */ #ifndef PTP_SYS_OFFSET #define PTP_SYS_OFFSET _IOW(PTP_CLK_MAGIC, 5, struct ptp_sys_offset) struct ptp_sys_offset { unsigned int n_samples; /* Desired number of measurements. */ unsigned int rsv[3]; /* Reserved for future use. */ /* * Array of interleaved system/phc time stamps. The kernel * will provide 2*n_samples + 1 time stamps, with the last * one as a system time stamp. */ struct ptp_clock_time ts[2 * PTP_MAX_SAMPLES + 1]; }; #endif /* PTP_SYS_OFFSET */ #ifndef PTP_SYS_OFFSET_PRECISE #define PTP_SYS_OFFSET_PRECISE \ _IOWR(PTP_CLK_MAGIC, 8, struct ptp_sys_offset_precise) struct ptp_sys_offset_precise { struct ptp_clock_time device; struct ptp_clock_time sys_realtime; struct ptp_clock_time sys_monoraw; unsigned int rsv[4]; /* Reserved for future use. */ }; #endif /* PTP_SYS_OFFSET_PRECISE */ #ifndef PTP_SYS_OFFSET_EXTENDED #define PTP_SYS_OFFSET_EXTENDED \ _IOWR(PTP_CLK_MAGIC, 9, struct ptp_sys_offset_extended) struct ptp_sys_offset_extended { unsigned int n_samples; /* Desired number of measurements. */ unsigned int rsv[3]; /* Reserved for future use. */ /* * Array of [system, phc, system] time stamps. The kernel will provide * 3*n_samples time stamps. */ struct ptp_clock_time ts[PTP_MAX_SAMPLES][3]; }; #endif /* PTP_SYS_OFFSET_EXTENDED */ #ifndef PTP_PIN_SETFUNC enum ptp_pin_function { PTP_PF_NONE, PTP_PF_EXTTS, PTP_PF_PEROUT, PTP_PF_PHYSYNC, }; struct ptp_pin_desc { char name[64]; unsigned int index; unsigned int func; unsigned int chan; unsigned int rsv[5]; }; #define PTP_PIN_SETFUNC _IOW(PTP_CLK_MAGIC, 7, struct ptp_pin_desc) #endif /*!PTP_PIN_SETFUNC*/ #ifdef PTP_PIN_SETFUNC2 #define PTP_PIN_SETFUNC_FAILED "PTP_PIN_SETFUNC2 failed: %m" #else #define PTP_PIN_SETFUNC_FAILED "PTP_PIN_SETFUNC failed: %m" #define PTP_PIN_SETFUNC2 PTP_PIN_SETFUNC #endif #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 #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 IFLA_BOND_MAX enum { IFLA_BOND_UNSPEC, IFLA_BOND_MODE, IFLA_BOND_ACTIVE_SLAVE, IFLA_BOND_MIIMON, IFLA_BOND_UPDELAY, IFLA_BOND_DOWNDELAY, IFLA_BOND_USE_CARRIER, IFLA_BOND_ARP_INTERVAL, IFLA_BOND_ARP_IP_TARGET, IFLA_BOND_ARP_VALIDATE, IFLA_BOND_ARP_ALL_TARGETS, IFLA_BOND_PRIMARY, IFLA_BOND_PRIMARY_RESELECT, IFLA_BOND_FAIL_OVER_MAC, IFLA_BOND_XMIT_HASH_POLICY, IFLA_BOND_RESEND_IGMP, IFLA_BOND_NUM_PEER_NOTIF, IFLA_BOND_ALL_SLAVES_ACTIVE, IFLA_BOND_MIN_LINKS, IFLA_BOND_LP_INTERVAL, IFLA_BOND_PACKETS_PER_SLAVE, IFLA_BOND_AD_LACP_RATE, IFLA_BOND_AD_SELECT, IFLA_BOND_AD_INFO, IFLA_BOND_AD_ACTOR_SYS_PRIO, IFLA_BOND_AD_USER_PORT_KEY, IFLA_BOND_AD_ACTOR_SYSTEM, IFLA_BOND_TLB_DYNAMIC_LB, __IFLA_BOND_MAX, }; #define IFLA_BOND_MAX (__IFLA_BOND_MAX - 1) #endif /*IFLA_BOND_MAX*/ #ifndef NLA_TYPE_MAX enum { NLA_UNSPEC, NLA_U8, NLA_U16, NLA_U32, NLA_U64, NLA_STRING, NLA_FLAG, NLA_MSECS, NLA_NESTED, __NLA_TYPE_MAX, }; #define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1) #endif /*NLA_TYPE_MAX*/ #ifdef __UCLIBC__ #if (_XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L) && \ defined __UCLIBC_HAS_THREADS_NATIVE__ #include #else #define TFD_TIMER_ABSTIME (1 << 0) 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 #else /*__UCLIBC__*/ #include #endif #endif linuxptp-3.1.1/mmedian.c000066400000000000000000000053461407046267700152020ustar00rootroot00000000000000/** * @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 (tmv_cmp(m->samples[m->order[i - 1]], m->samples[m->index]) <= 0) 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-3.1.1/mmedian.h000066400000000000000000000017131407046267700152010ustar00rootroot00000000000000/** * @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-3.1.1/monitor.c000066400000000000000000000133231407046267700152510ustar00rootroot00000000000000/** * @file monitor.c * @note Copyright (C) 2020 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #include #include #include "address.h" #include "monitor.h" #include "print.h" #define RECORDS_PER_MESSAGE 1 struct monitor_message { struct ptp_message *msg; int records_per_msg; int count; }; struct monitor { struct port *dst_port; struct slave_rx_sync_timing_data_tlv *sync_tlv; struct slave_delay_timing_data_tlv *delay_tlv; struct monitor_message delay; struct monitor_message sync; }; static bool monitor_active(struct monitor *monitor) { return monitor->dst_port ? true : false; } static int monitor_forward(struct port *port, struct ptp_message *msg) { int err, pdulen = msg->header.messageLength; if (msg_pre_send(msg)) { return -1; } err = port_forward_to(port, msg); if (err) { pr_debug("failed to send signaling message to slave event monitor: %s", strerror(-err)); } if (msg_post_recv(msg, pdulen)) { return -1; } msg->header.sequenceId++; return 0; } static struct tlv_extra *monitor_init_message(struct monitor_message *mm, struct port *destination, uint16_t tlv_type, size_t tlv_size, struct address address) { struct ptp_message *msg; struct tlv_extra *extra; msg = port_signaling_construct(destination, &wildcard_pid); if (!msg) { return NULL; } extra = msg_tlv_append(msg, tlv_size); if (!extra) { msg_put(msg); return NULL; } extra->tlv->type = tlv_type; extra->tlv->length = tlv_size - sizeof(extra->tlv->type) - sizeof(extra->tlv->length); mm->msg = msg; mm->msg->address = address; mm->records_per_msg = RECORDS_PER_MESSAGE; mm->count = 0; return extra; } static int monitor_init_delay(struct monitor *monitor, struct address address) { const size_t tlv_size = sizeof(struct slave_delay_timing_data_tlv) + sizeof(struct slave_delay_timing_record) * RECORDS_PER_MESSAGE; struct tlv_extra *extra; extra = monitor_init_message(&monitor->delay, monitor->dst_port, TLV_SLAVE_DELAY_TIMING_DATA_NP, tlv_size, address); if (!extra) { return -1; } monitor->delay_tlv = (struct slave_delay_timing_data_tlv *) extra->tlv; return 0; } static int monitor_init_sync(struct monitor *monitor, struct address address) { const size_t tlv_size = sizeof(struct slave_rx_sync_timing_data_tlv) + sizeof(struct slave_rx_sync_timing_record) * RECORDS_PER_MESSAGE; struct tlv_extra *extra; extra = monitor_init_message(&monitor->sync, monitor->dst_port, TLV_SLAVE_RX_SYNC_TIMING_DATA, tlv_size, address); if (!extra) { return -1; } monitor->sync_tlv = (struct slave_rx_sync_timing_data_tlv *) extra->tlv; return 0; } struct monitor *monitor_create(struct config *config, struct port *dst) { struct monitor *monitor; struct address address; struct sockaddr_un sa; const char *path; monitor = calloc(1, sizeof(*monitor)); if (!monitor) { return NULL; } path = config_get_string(config, NULL, "slave_event_monitor"); if (!path || !path[0]) { /* Return an inactive monitor. */ return monitor; } memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_LOCAL; snprintf(sa.sun_path, sizeof(sa.sun_path) - 1, "%s", path); address.sun = sa; address.len = sizeof(sa); monitor->dst_port = dst; if (monitor_init_delay(monitor, address)) { free(monitor); return NULL; } if (monitor_init_sync(monitor, address)) { msg_put(monitor->delay.msg); free(monitor); return NULL; } return monitor; } int monitor_delay(struct monitor *monitor, struct PortIdentity source_pid, uint16_t seqid, tmv_t t3, tmv_t corr, tmv_t t4) { struct slave_delay_timing_record *record; struct ptp_message *msg; if (!monitor_active(monitor)) { return 0; } msg = monitor->delay.msg; if (!pid_eq(&monitor->delay_tlv->sourcePortIdentity, &source_pid)) { /* There was a change in remote master. Drop stale records. */ memcpy(&monitor->delay_tlv->sourcePortIdentity, &source_pid, sizeof(monitor->delay_tlv->sourcePortIdentity)); monitor->delay.count = 0; } record = monitor->delay_tlv->record + monitor->delay.count; record->sequenceId = seqid; record->delayOriginTimestamp = tmv_to_Timestamp(t3); record->totalCorrectionField = tmv_to_TimeInterval(corr); record->delayResponseTimestamp = tmv_to_Timestamp(t4); monitor->delay.count++; if (monitor->delay.count == monitor->delay.records_per_msg) { monitor->delay.count = 0; return monitor_forward(monitor->dst_port, msg); } return 0; } void monitor_destroy(struct monitor *monitor) { if (monitor->delay.msg) { msg_put(monitor->delay.msg); } if (monitor->sync.msg) { msg_put(monitor->sync.msg); } free(monitor); } int monitor_sync(struct monitor *monitor, struct PortIdentity source_pid, uint16_t seqid, tmv_t t1, tmv_t corr, tmv_t t2) { struct slave_rx_sync_timing_record *record; struct ptp_message *msg; if (!monitor_active(monitor)) { return 0; } msg = monitor->sync.msg; if (!pid_eq(&monitor->sync_tlv->sourcePortIdentity, &source_pid)) { /* There was a change in remote master. Drop stale records. */ memcpy(&monitor->sync_tlv->sourcePortIdentity, &source_pid, sizeof(monitor->sync_tlv->sourcePortIdentity)); monitor->sync.count = 0; } record = monitor->sync_tlv->record + monitor->sync.count; record->sequenceId = seqid; record->syncOriginTimestamp = tmv_to_Timestamp(t1); record->totalCorrectionField = tmv_to_TimeInterval(corr); record->scaledCumulativeRateOffset = 0; record->syncEventIngressTimestamp = tmv_to_Timestamp(t2); monitor->sync.count++; if (monitor->sync.count == monitor->sync.records_per_msg) { monitor->sync.count = 0; return monitor_forward(monitor->dst_port, msg); } return 0; } linuxptp-3.1.1/monitor.h000066400000000000000000000012061407046267700152530ustar00rootroot00000000000000/** * @file monitor.h * @note Copyright (C) 2020 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_MONITOR_H #define HAVE_MONITOR_H #include "config.h" #include "port.h" #include "tmv.h" struct monitor; struct monitor *monitor_create(struct config *config, struct port *dst); int monitor_delay(struct monitor *monitor, struct PortIdentity source_pid, uint16_t seqid, tmv_t t3, tmv_t corr, tmv_t t4); void monitor_destroy(struct monitor *monitor); int monitor_sync(struct monitor *monitor, struct PortIdentity source_pid, uint16_t seqid, tmv_t t1, tmv_t corr, tmv_t t2); #endif linuxptp-3.1.1/msg.c000066400000000000000000000325411407046267700143530ustar00rootroot00000000000000/** * @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 "contain.h" #include "msg.h" #include "print.h" #include "tlv.h" #define VERSION_MASK 0x0f #define VERSION 0x02 int assume_two_step = 0; /* * 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 = TAILQ_HEAD_INITIALIZER(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); } 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 uint8_t *msg_suffix(struct ptp_message *m) { switch (msg_type(m)) { case SYNC: return NULL; case DELAY_REQ: return m->delay_req.suffix; case PDELAY_REQ: return NULL; case PDELAY_RESP: return NULL; case FOLLOW_UP: return m->follow_up.suffix; case DELAY_RESP: return m->delay_resp.suffix; case PDELAY_RESP_FOLLOW_UP: return m->pdelay_resp_fup.suffix; case ANNOUNCE: return m->announce.suffix; case SIGNALING: return m->signaling.suffix; case MANAGEMENT: return m->management.suffix; } return NULL; } static struct tlv_extra *msg_tlv_prepare(struct ptp_message *msg, int length) { struct tlv_extra *extra, *tmp; uint8_t *ptr; /* Make sure this message type admits appended TLVs. */ ptr = msg_suffix(msg); if (!ptr) { pr_err("TLV on %s not allowed", msg_type_string(msg_type(msg))); return NULL; } tmp = TAILQ_LAST(&msg->tlv_list, tlv_list); if (tmp) { ptr = (uint8_t *) tmp->tlv; ptr += sizeof(tmp->tlv->type); ptr += sizeof(tmp->tlv->length); ptr += tmp->tlv->length; } /* Check that the message buffer has enough room for the new TLV. */ if ((unsigned long)(ptr + length) > (unsigned long)(&msg->tail_room)) { pr_debug("cannot fit TLV of length %d into message", length); return NULL; } /* Allocate a TLV descriptor and setup the pointer. */ extra = tlv_extra_alloc(); if (!extra) { pr_err("failed to allocate TLV descriptor"); return NULL; } extra->tlv = (struct TLV *) ptr; return extra; } static void msg_tlv_recycle(struct ptp_message *msg) { struct tlv_extra *extra; while ((extra = TAILQ_FIRST(&msg->tlv_list)) != NULL) { TAILQ_REMOVE(&msg->tlv_list, extra, list); tlv_extra_recycle(extra); } } 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(struct ptp_message *msg, int len) { uint8_t *ptr = msg_suffix(msg); struct tlv_extra *extra; int err, suffix_len = 0; if (!ptr) return 0; while (len >= sizeof(struct TLV)) { extra = tlv_extra_alloc(); if (!extra) { pr_err("failed to allocate TLV descriptor"); return -ENOMEM; } extra->tlv = (struct TLV *) ptr; extra->tlv->type = ntohs(extra->tlv->type); extra->tlv->length = ntohs(extra->tlv->length); if (extra->tlv->length % 2) { tlv_extra_recycle(extra); return -EBADMSG; } suffix_len += sizeof(struct TLV); len -= sizeof(struct TLV); ptr += sizeof(struct TLV); if (extra->tlv->length > len) { tlv_extra_recycle(extra); return -EBADMSG; } suffix_len += extra->tlv->length; len -= extra->tlv->length; ptr += extra->tlv->length; err = tlv_post_recv(extra); if (err) { tlv_extra_recycle(extra); return err; } msg_tlv_attach(msg, extra); } return suffix_len; } static void suffix_pre_send(struct ptp_message *msg) { struct tlv_extra *extra; struct TLV *tlv; TAILQ_FOREACH(extra, &msg->tlv_list, list) { tlv = extra->tlv; tlv_pre_send(tlv, extra); tlv->type = htons(tlv->type); tlv->length = htons(tlv->length); } msg_tlv_recycle(msg); } 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; TAILQ_INIT(&m->tlv_list); } return m; } void msg_cleanup(void) { struct message_storage *s; struct ptp_message *m; tlv_extra_cleanup(); while ((m = TAILQ_FIRST(&msg_pool)) != NULL) { TAILQ_REMOVE(&msg_pool, m, list); s = container_of(m, struct message_storage, msg); free(s); } } struct ptp_message *msg_duplicate(struct ptp_message *msg, int cnt) { struct ptp_message *dup; int err; dup = msg_allocate(); if (!dup) { return NULL; } memcpy(dup, msg, sizeof(*dup)); dup->refcnt = 1; TAILQ_INIT(&dup->tlv_list); err = msg_post_recv(dup, cnt); if (err) { switch (err) { case -EBADMSG: pr_err("msg_duplicate: bad message"); break; case -EPROTO: pr_debug("msg_duplicate: ignoring message"); break; } msg_put(dup); return NULL; } if (msg_sots_missing(msg)) { pr_err("msg_duplicate: received %s without timestamp", msg_type_string(msg_type(msg))); msg_put(dup); return NULL; } return dup; } void msg_get(struct ptp_message *m) { m->refcnt++; } int msg_post_recv(struct ptp_message *m, int cnt) { int err, pdulen, suffix_len, type; 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); break; case DELAY_RESP: timestamp_post_recv(m, &m->delay_resp.receiveTimestamp); port_id_post_recv(&m->delay_resp.requestingPortIdentity); break; case PDELAY_RESP_FOLLOW_UP: timestamp_post_recv(m, &m->pdelay_resp_fup.responseOriginTimestamp); port_id_post_recv(&m->pdelay_resp_fup.requestingPortIdentity); break; case ANNOUNCE: clock_gettime(CLOCK_MONOTONIC, &m->ts.host); timestamp_post_recv(m, &m->announce.originTimestamp); announce_post_recv(&m->announce); break; case SIGNALING: port_id_post_recv(&m->signaling.targetPortIdentity); break; case MANAGEMENT: port_id_post_recv(&m->management.targetPortIdentity); break; } suffix_len = suffix_post_recv(m, cnt - pdulen); if (suffix_len < 0) { return suffix_len; } if (pdulen + suffix_len != m->header.messageLength) { return -EBADMSG; } return 0; } int msg_pre_send(struct ptp_message *m) { int type; if (hdr_pre_send(&m->header)) return -1; type = msg_type(m); switch (type) { case SYNC: break; case DELAY_REQ: clock_gettime(CLOCK_MONOTONIC, &m->ts.host); 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); break; case DELAY_RESP: timestamp_pre_send(&m->delay_resp.receiveTimestamp); m->delay_resp.requestingPortIdentity.portNumber = htons(m->delay_resp.requestingPortIdentity.portNumber); break; case PDELAY_RESP_FOLLOW_UP: timestamp_pre_send(&m->pdelay_resp_fup.responseOriginTimestamp); port_id_pre_send(&m->pdelay_resp_fup.requestingPortIdentity); break; case ANNOUNCE: announce_pre_send(&m->announce); break; case SIGNALING: port_id_pre_send(&m->signaling.targetPortIdentity); break; case MANAGEMENT: port_id_pre_send(&m->management.targetPortIdentity); break; default: return -1; } suffix_pre_send(m); return 0; } struct tlv_extra *msg_tlv_append(struct ptp_message *msg, int length) { struct tlv_extra *extra; extra = msg_tlv_prepare(msg, length); if (extra) { msg->header.messageLength += length; msg_tlv_attach(msg, extra); } return extra; } void msg_tlv_attach(struct ptp_message *msg, struct tlv_extra *extra) { TAILQ_INSERT_TAIL(&msg->tlv_list, extra, list); } int msg_tlv_count(struct ptp_message *msg) { int count = 0; struct tlv_extra *extra; for (extra = TAILQ_FIRST(&msg->tlv_list); extra != NULL; extra = TAILQ_NEXT(extra, list)) count++; return count; } 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) { return; } pool_stats.count++; pool_debug("recycle", m); msg_tlv_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-3.1.1/msg.h000066400000000000000000000303151407046267700143550ustar00rootroot00000000000000/** * @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 #include "address.h" #include "ddt.h" #include "tlv.h" #include "tmv.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) #define SYNC_UNCERTAIN (1<<6) /* * Signaling interval special values. For more info look at 802.1AS table 10-11 */ #define SIGNAL_NO_CHANGE -128 #define SIGNAL_SET_INITIAL 126 enum timestamp_type { TS_SOFTWARE, TS_HARDWARE, TS_LEGACY_HW, TS_ONESTEP, TS_P2P1STEP, }; struct hw_timestamp { enum timestamp_type type; tmv_t ts; tmv_t 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; uint8_t suffix[0]; } 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; /** * List of TLV descriptors. Each item in the list contains * pointers to the appended TLVs. */ TAILQ_HEAD(tlv_list, tlv_extra) tlv_list; }; /** * 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; } /** * Append a new TLV onto a message for transmission. * * This is a high level API designed for the transmit path. The * function allocates a new descriptor, initializes its .tlv field, * and ensures that the TLV will fit into the message buffer. This * function increments the message length field by 'length' before * returning. * * @param msg A message obtained using msg_allocate(). At a mininum, * the message type and length fields must set by the caller. * @param length The length of the TLV to append. * @return A pointer to a TLV descriptor on success or NULL otherwise. */ struct tlv_extra *msg_tlv_append(struct ptp_message *msg, int length); /** * Place a TLV descriptor into a message's list of TLVs. * * @param msg A message obtained using msg_allocate(). * @param extra The TLV to be added to the list. */ void msg_tlv_attach(struct ptp_message *msg, struct tlv_extra *extra); /* * Return the number of TLVs attached to a message. * @param msg A message obtained using @ref msg_allocate(). * @return The number of attached TLVs. */ int msg_tlv_count(struct ptp_message *msg); /** * 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(const 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); /** * Duplicate a message instance. * * This function accepts a message in network byte order and returns a * duplicate in host byte. * * 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 msg A message obtained using @ref msg_allocate(). * The passed message must be in network byte order, not * having been passed to @ref msg_post_recv(). * * @return Pointer to a message on success, NULL otherwise. * The returned message will be in host byte order, having * been passed to @ref msg_post_recv(). */ struct ptp_message *msg_duplicate(struct ptp_message *msg, int cnt); /** * 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 !tmv_is_zero(m->hwts.ts); } /** * Test whether a message is a unicast message. * @param m Message to test. * @return One if the message is unicast, zero otherwise. */ static inline Boolean msg_unicast(struct ptp_message *m) { return field_is_set(m, 0, UNICAST); } /** * 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. */ static inline int64_t host2net64(int64_t val) { return __cpu_to_be64(val); } /** * Convert a 64 bit word into host byte order. */ static inline int64_t net2host64(int64_t val) { return __be64_to_cpu(val); } #endif linuxptp-3.1.1/mtab.h000066400000000000000000000030711407046267700145110ustar00rootroot00000000000000/** * @file mtab.h * @brief master table implementation * @note Copyright (C) 2018 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-1335 USA. */ #ifndef HAVE_MTAB_H #define HAVE_MTAB_H #include #include #include "address.h" #include "pdt.h" #include "transport.h" #include "unicast_fsm.h" struct unicast_master_address { STAILQ_ENTRY(unicast_master_address) list; struct PortIdentity portIdentity; enum transport_type type; enum unicast_state state; struct address address; unsigned int granted; unsigned int sydymsk; time_t renewal_tmo; }; struct unicast_master_table { STAILQ_HEAD(addrs_head, unicast_master_address) addrs; STAILQ_ENTRY(unicast_master_table) list; Integer8 logQueryInterval; int table_index; int count; int port; /* for use with P2P delay mechanism: */ struct unicast_master_address peer_addr; char *peer_name; }; #endif linuxptp-3.1.1/nmea.c000066400000000000000000000076071407046267700145120ustar00rootroot00000000000000/** * @file nmea.c * @note Copyright (C) 2020 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include "nmea.h" #include "print.h" #define NMEA_CHAR_MIN ' ' #define NMEA_CHAR_MAX '~' #define NMEA_MAX_LENGTH 256 enum nmea_state { NMEA_IDLE, NMEA_HAVE_DOLLAR, NMEA_HAVE_STARTG, NMEA_HAVE_STARTX, NMEA_HAVE_BODY, NMEA_HAVE_CSUMA, NMEA_HAVE_CSUM_MSB, NMEA_HAVE_CSUM_LSB, NMEA_HAVE_PENULTIMATE, }; struct nmea_parser { char sentence[NMEA_MAX_LENGTH + 1]; char payload_checksum[3]; enum nmea_state state; uint8_t checksum; int offset; }; static void nmea_reset(struct nmea_parser *np); static void nmea_accumulate(struct nmea_parser *np, char c) { if (c < NMEA_CHAR_MIN || c > NMEA_CHAR_MAX) { nmea_reset(np); return; } if (np->offset == NMEA_MAX_LENGTH) { nmea_reset(np); } np->sentence[np->offset++] = c; np->checksum ^= c; } static int nmea_parse_symbol(struct nmea_parser *np, char c) { switch (np->state) { case NMEA_IDLE: if (c == '$') { np->state = NMEA_HAVE_DOLLAR; } break; case NMEA_HAVE_DOLLAR: if (c == 'G') { np->state = NMEA_HAVE_STARTG; nmea_accumulate(np, c); } else { nmea_reset(np); } break; case NMEA_HAVE_STARTG: np->state = NMEA_HAVE_STARTX; nmea_accumulate(np, c); break; case NMEA_HAVE_STARTX: np->state = NMEA_HAVE_BODY; nmea_accumulate(np, c); break; case NMEA_HAVE_BODY: if (c == '*') { np->state = NMEA_HAVE_CSUMA; } else { nmea_accumulate(np, c); } break; case NMEA_HAVE_CSUMA: np->state = NMEA_HAVE_CSUM_MSB; np->payload_checksum[0] = c; break; case NMEA_HAVE_CSUM_MSB: np->state = NMEA_HAVE_CSUM_LSB; np->payload_checksum[1] = c; break; case NMEA_HAVE_CSUM_LSB: if (c == '\n') { /*skip the CR*/ return 0; } if (c == '\r') { np->state = NMEA_HAVE_PENULTIMATE; } else { nmea_reset(np); } break; case NMEA_HAVE_PENULTIMATE: if (c == '\n') { return 0; } nmea_reset(np); break; } return -1; } static void nmea_reset(struct nmea_parser *np) { memset(np, 0, sizeof(*np)); } static int nmea_scan_rmc(struct nmea_parser *np, struct nmea_rmc *result) { int cnt, i, msec = 0; char *ptr, status; uint8_t checksum; struct tm tm; pr_debug("nmea sentence: %s", np->sentence); cnt = sscanf(np->payload_checksum, "%02hhx", &checksum); if (cnt != 1) { return -1; } if (checksum != np->checksum) { pr_err("checksum mismatch 0x%02hhx != 0x%02hhx on %s", checksum, np->checksum, np->sentence); return -1; } cnt = sscanf(np->sentence, "G%*cRMC,%2d%2d%2d.%d,%c", &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &msec, &status); if (cnt != 5) { cnt = sscanf(np->sentence, "G%*cRMC,%2d%2d%2d,%c", &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &status); if (cnt != 4) { return -1; } } ptr = np->sentence; for (i = 0; i < 9; i++) { ptr = strchr(ptr, ','); if (!ptr) { return -1; } ptr++; } cnt = sscanf(ptr, "%2d%2d%2d", &tm.tm_mday, &tm.tm_mon, &tm.tm_year); if (cnt != 3) { return -1; } tm.tm_year += 100; tm.tm_mon--; result->ts.tv_sec = mktime(&tm); result->ts.tv_nsec = msec * 1000000UL; result->fix_valid = status == 'A' ? true : false; return 0; } int nmea_parse(struct nmea_parser *np, const char *ptr, int buflen, struct nmea_rmc *result, int *parsed) { int count = 0; while (buflen) { if (!nmea_parse_symbol(np, *ptr)) { if (!nmea_scan_rmc(np, result)) { *parsed = count + 1; return 0; } nmea_reset(np); } buflen--; count++; ptr++; } *parsed = count; return -1; } struct nmea_parser *nmea_parser_create(void) { struct nmea_parser *np; np = malloc(sizeof(*np)); if (!np) { return NULL; } nmea_reset(np); /* Ensure that mktime(3) returns a value in the UTC time scale. */ setenv("TZ", "UTC", 1); return np; } void nmea_parser_destroy(struct nmea_parser *np) { free(np); } linuxptp-3.1.1/nmea.h000066400000000000000000000021571407046267700145120ustar00rootroot00000000000000/** * @file nmea.h * @note Copyright (C) 2020 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_NMEA_H #define HAVE_NMEA_H #include #include /** Opaque type. */ struct nmea_parser; struct nmea_rmc { struct timespec ts; bool fix_valid; }; /** * Parses NMEA RMC sentences out of a given buffer. * @param np Pointer obtained via nmea_parser_create(). * @param buf Pointer to the data to be parsed. * @param buflen Length of 'buf' in bytes. * @param rmc Pointer to hold the result. * @param parsed Returns the number of bytes parsed, possibly less than buflen. * @return Zero on success, non-zero otherwise. */ int nmea_parse(struct nmea_parser *np, const char *buf, int buflen, struct nmea_rmc *rmc, int *parsed); /** * Creates an instance of an NMEA parser. * @return Pointer to a new instance on success, NULL otherwise. */ struct nmea_parser *nmea_parser_create(void); /** * Destroys an NMEA parser instance. * @param np Pointer obtained via nmea_parser_create(). */ void nmea_parser_destroy(struct nmea_parser *np); #endif linuxptp-3.1.1/notification.h000066400000000000000000000017321407046267700162560ustar00rootroot00000000000000/** * @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-3.1.1/nsm.8000066400000000000000000000067431407046267700143140ustar00rootroot00000000000000.TH NSM 8 "June 2019" "linuxptp" .SH NAME nsm \- NetSync Monitor client .SH SYNOPSIS .B nsm [ .BI \-f " config" ] [ .BI \-i " interface" ] [ .I long-options ] [ command ] ... .SH DESCRIPTION .B nsm is a program which implements a NetSync Monitor (NSM) client. NSM is an extension to the Precision Time Protocol (PTP), which enables a client to measure the offset of its clock against any PTP clock in the network which supports NSM. It uses unicast messages, but unlike PTP in the unicast mode it does not require the server to keep any state specific to the client. It is particularly useful for monitoring. The program reads commands from the standard input or from the command line. .SH COMMANDS .TP .BI NSM " address" Send a NetSync Monitor request to the specified network address (IPv4 or MAC) and print the measured offset with the response. .TP .B help Display a help message. .SH OPTIONS .TP .BI \-f " config" Read configuration from the specified file. No configuration file is read by default. .TP .BI \-i " interface" Specify the network interface. .TP .B \-h Display a help message. .TP .B \-v Print the software version and exit. .SH LONG OPTIONS Each and every configuration file option (see below in sections .BR PROGRAM\ OPTIONS and .BR PORT\ OPTIONS ) may also appear as a "long" style command line argument. For example, the transportSpecific option may be set using either of these two forms: .RS \f(CW\-\-transportSpecific 1 \-\-transportSpecific=1\fP .RE Option values given on the command line override values in the global section of the configuration file (which, in turn, overrides default values). .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 global program options as well as the default port specific 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] ). .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 network_transport Select the network transport. Possible values are UDPv4 and L2. The default is UDPv4. .TP .B transportSpecific The transport specific field. Must be in the range 0 to 255. The default is 0. .SH PROGRAM OPTIONS .TP .B domainNumber The domain attribute of the local clock. The default is 0. .B time_stamping The time stamping method. The allowed values are hardware, software and legacy. The default is hardware. .SH WARNING Be cautious when the same configuration file is used for both ptp4l and nsm. Keep in mind that values specified in the configuration file take precedence over their default values. If a certain option which is common to ptp4l and nsm is specified to a non-default value in the configuration file (e.g. for ptp4l), then this non-default value applies also for nsm. This might be not what is expected. To avoid securely these unexpected behaviour, different configuration files for ptp4l and nsm are recommended. .SH SEE ALSO .BR ptp4l (8) linuxptp-3.1.1/nsm.c000066400000000000000000000357221407046267700143660ustar00rootroot00000000000000/** * @file nsm.c * @brief NSM client program * @note Copyright (C) 2018 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 "config.h" #include "print.h" #include "rtnl.h" #include "util.h" #include "version.h" #define IFMT "\n\t\t" #define NSM_NFD 3 struct interface { STAILQ_ENTRY(interface) list; }; struct nsm { struct config *cfg; struct fdarray fda; struct transport *trp; struct tsproc *tsproc; struct ptp_message *nsm_delay_req; struct ptp_message *nsm_delay_resp; struct ptp_message *nsm_sync; struct ptp_message *nsm_fup; struct PortIdentity port_identity; UInteger16 sequence_id; const char *name; } the_nsm; static void nsm_help(FILE *fp); static int nsm_request(struct nsm *nsm, char *target); static void nsm_reset(struct nsm *nsm); static int nsm_command(struct nsm *nsm, const char *cmd) { char action_str[10+1] = {0}, id_str[64+1] = {0}; if (0 == strncasecmp(cmd, "HELP", strlen(cmd))) { nsm_help(stdout); return 0; } if (2 != sscanf(cmd, " %10s %64s", action_str, id_str)) { pr_err("bad command: %s", cmd); return -1; } if (0 == strncasecmp(action_str, "NSM", strlen(action_str))) { return nsm_request(nsm, id_str); } pr_err("bad command: %s", cmd); return -1; } static int nsm_complete(struct nsm *nsm) { if (!nsm->nsm_sync) { return 0; } if (one_step(nsm->nsm_sync)) { return nsm->nsm_delay_resp ? 1 : 0; } return (nsm->nsm_delay_resp && nsm->nsm_fup) ? 1 : 0; } static int64_t nsm_compute_offset(struct tsproc *tsp, struct ptp_message *syn, struct ptp_message *fup, struct ptp_message *req, struct ptp_message *resp) { tmv_t c1, c2, c3, t1, t1c, t2, t3, t4, t4c, offset; c1 = correction_to_tmv(syn->header.correction); c2 = correction_to_tmv(fup->header.correction); c3 = correction_to_tmv(resp->header.correction); t1 = timestamp_to_tmv(fup->ts.pdu); t2 = syn->hwts.ts; t3 = req->hwts.ts; t4 = timestamp_to_tmv(resp->ts.pdu); t1c = tmv_add(t1, tmv_add(c1, c2)); t4c = tmv_sub(t4, c3); tsproc_reset(tsp, 1); tsproc_down_ts(tsp, t1c, t2); tsproc_up_ts(tsp, t3, t4c); tsproc_update_offset(tsp, &offset, NULL); return tmv_to_nanoseconds(offset); } static void nsm_close(struct nsm *nsm) { nsm_reset(nsm); transport_close(nsm->trp, &nsm->fda); transport_destroy(nsm->trp); tsproc_destroy(nsm->tsproc); } static void nsm_handle_msg(struct nsm *nsm, struct ptp_message *msg, FILE *fp) { struct nsm_resp_tlv_head *head; struct nsm_resp_tlv_foot *foot; struct timePropertiesDS *tp; struct PortAddress *paddr; struct currentDS cds; struct parentDS *pds; struct Timestamp ts; unsigned char *ptr; int64_t offset; if (!nsm->nsm_delay_req) { return; } if (msg->header.sequenceId != ntohs(nsm->nsm_delay_req->header.sequenceId)) { return; } if (!msg_unicast(msg)) { return; } switch (msg_type(msg)) { case SYNC: if (!nsm->nsm_sync) { nsm->nsm_sync = msg; msg_get(msg); } break; case FOLLOW_UP: if (!nsm->nsm_fup) { nsm->nsm_fup = msg; msg_get(msg); } break; case DELAY_RESP: if (!nsm->nsm_delay_resp) { nsm->nsm_delay_resp = msg; msg_get(msg); } break; case DELAY_REQ: case PDELAY_REQ: case PDELAY_RESP: case PDELAY_RESP_FOLLOW_UP: case ANNOUNCE: case SIGNALING: case MANAGEMENT: return; } if (!nsm_complete(nsm)) { return; } head = (struct nsm_resp_tlv_head *) nsm->nsm_delay_resp->delay_resp.suffix; paddr = &head->parent_addr; ptr = (unsigned char *) head; ptr += sizeof(*head) + paddr->addressLength; foot = (struct nsm_resp_tlv_foot *) ptr; pds = &foot->parent; memcpy(&cds, &foot->current, sizeof(cds)); tp = &foot->timeprop; memcpy(&ts, &foot->lastsync, sizeof(ts)); offset = nsm_compute_offset(nsm->tsproc, nsm->nsm_sync, nsm->nsm_fup, nsm->nsm_delay_req, nsm->nsm_delay_resp); fprintf(fp, "NSM MEASUREMENT COMPLETE" IFMT "offset %" PRId64 IFMT "portState %s" IFMT "parentPortAddress %hu %s\n", offset, ps_str[head->port_state], head->parent_addr.networkProtocol, portaddr2str(&head->parent_addr)); fprintf(fp, "\tparentDataset" 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\n", 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)); fprintf(fp, "\tcurrentDataset" IFMT "stepsRemoved %hd" IFMT "offsetFromMaster %.1f" IFMT "meanPathDelay %.1f\n", cds.stepsRemoved, cds.offsetFromMaster / 65536.0, cds.meanPathDelay / 65536.0); fprintf(fp, "\ttimePropertiesDataset" 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\n", 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); fprintf(fp, "\tlastSyncTimestamp %" PRId64 ".%09u\n", ((uint64_t)ts.seconds_lsb) | (((uint64_t)ts.seconds_msb) << 32), ts.nanoseconds); fflush(fp); nsm_reset(nsm); } static void nsm_help(FILE *fp) { fprintf(fp, "\tSend a NetSync Monitor request to a specific port address:\n"); fprintf(fp, "\n"); fprintf(fp, "\tNSM 111.222.333.444\n"); fprintf(fp, "\tNSM aa:bb:cc:dd:ee:ff\n"); fprintf(fp, "\n"); } static int nsm_open(struct nsm *nsm, struct config *cfg) { enum transport_type transport; char ts_label[IF_NAMESIZE]; const char *ifname, *name; struct interface *iface; int count = 0; STAILQ_FOREACH(iface, &cfg->interfaces, list) { ifname = interface_name(iface); memset(ts_label, 0, sizeof(ts_label)); rtnl_get_ts_device(ifname, ts_label); interface_set_label(iface, ts_label); interface_ensure_tslabel(iface); count++; } if (count != 1) { pr_err("need exactly one interface"); return -1; } iface = STAILQ_FIRST(&cfg->interfaces); nsm->name = name = interface_name(iface); nsm->cfg = cfg; transport = config_get_int(cfg, name, "network_transport"); if (generate_clock_identity(&nsm->port_identity.clockIdentity, name)) { pr_err("failed to generate a clock identity"); return -1; } nsm->port_identity.portNumber = 1; nsm->tsproc = tsproc_create(TSPROC_RAW, FILTER_MOVING_AVERAGE, 10); if (!nsm->tsproc) { pr_err("failed to create time stamp processor"); goto no_tsproc; } nsm->trp = transport_create(cfg, transport); if (!nsm->trp) { pr_err("failed to create transport"); goto no_trans; } if (transport_open(nsm->trp, iface, &nsm->fda, config_get_int(cfg, NULL, "time_stamping"))) { pr_err("failed to open transport"); goto open_failed; } return 0; open_failed: transport_destroy(nsm->trp); no_trans: tsproc_destroy(nsm->tsproc); no_tsproc: return -1; } static struct ptp_message *nsm_recv(struct nsm *nsm, int fd) { struct ptp_message *msg; int cnt, err; msg = msg_allocate(); if (!msg) { pr_err("low memory"); return NULL; } msg->hwts.type = config_get_int(nsm->cfg, NULL, "time_stamping"); cnt = transport_recv(nsm->trp, fd, 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 -EPROTO: pr_debug("ignoring message"); break; } goto failed; } if (msg_sots_missing(msg)) { pr_err("received %s without timestamp", msg_type_string(msg_type(msg))); goto failed; } return msg; failed: msg_put(msg); return NULL; } static int nsm_request(struct nsm *nsm, char *target) { enum transport_type type = transport_type(nsm->trp); UInteger8 transportSpecific; struct ptp_message *msg; struct tlv_extra *extra; Integer64 asymmetry; struct address dst; int cnt, err; if (str2addr(type, target, &dst)) { return -1; } msg = msg_allocate(); if (!msg) { return -1; } transportSpecific = config_get_int(nsm->cfg, nsm->name, "transportSpecific"); transportSpecific <<= 4; asymmetry = config_get_int(nsm->cfg, nsm->name, "delayAsymmetry"); asymmetry <<= 16; msg->hwts.type = config_get_int(nsm->cfg, NULL, "time_stamping"); msg->header.tsmt = DELAY_REQ | transportSpecific; msg->header.ver = PTP_VERSION; msg->header.messageLength = sizeof(struct delay_req_msg); msg->header.domainNumber = config_get_int(nsm->cfg, NULL, "domainNumber"); msg->header.correction = -asymmetry; msg->header.sourcePortIdentity = nsm->port_identity; msg->header.sequenceId = nsm->sequence_id++; msg->header.control = CTL_DELAY_REQ; msg->header.logMessageInterval = 0x7f; msg->address = dst; msg->header.flagField[0] |= UNICAST; extra = msg_tlv_append(msg, sizeof(struct TLV)); if (!extra) { msg_put(msg); return -ENOMEM; } extra->tlv->type = TLV_PTPMON_REQ; extra->tlv->length = 0; err = msg_pre_send(msg); if (err) { pr_err("msg_pre_send failed"); goto out; } cnt = transport_sendto(nsm->trp, &nsm->fda, TRANS_EVENT, msg); if (cnt <= 0) { pr_err("transport_sendto failed"); err = -1; goto out; } if (msg_sots_missing(msg)) { pr_err("missing timestamp on transmitted delay request"); err = -1; goto out; } nsm_reset(nsm); nsm->nsm_delay_req = msg; return 0; out: msg_put(msg); return err; } static void nsm_reset(struct nsm *nsm) { if (nsm->nsm_delay_req) { msg_put(nsm->nsm_delay_req); } if (nsm->nsm_delay_resp) { msg_put(nsm->nsm_delay_resp); } if (nsm->nsm_sync) { msg_put(nsm->nsm_sync); } if (nsm->nsm_fup) { msg_put(nsm->nsm_fup); } nsm->nsm_delay_req = NULL; nsm->nsm_delay_resp = NULL; nsm->nsm_sync = NULL; nsm->nsm_fup = NULL; } static void usage(char *progname) { fprintf(stderr, "\nusage: %s [options]\n\n" " -f [file] read configuration from 'file'\n" " -h prints this message and exits\n" " -i [dev] interface device to use\n" " -v prints the software version and exits\n" "\n", progname); } int main(int argc, char *argv[]) { int batch_mode = 0, c, cnt, err = 0, index, length, tmo = -1; char *cmd = NULL, *config = NULL, line[1024], *progname; struct pollfd pollfd[NSM_NFD]; struct nsm *nsm = &the_nsm; struct ptp_message *msg; struct option *opts; struct config *cfg; if (handle_term_signals()) { return -1; } cfg = config_create(); if (!cfg) { return -1; } opts = config_long_options(cfg); print_set_verbose(1); print_set_syslog(0); /* Process the command line arguments. */ progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; while (EOF != (c = getopt_long(argc, argv, "f:hi:v", opts, &index))) { switch (c) { case 0: if (config_parse_option(cfg, opts[index].name, optarg)) { config_destroy(cfg); return -1; } break; case 'f': config = optarg; break; case 'i': if (!config_create_interface(optarg, cfg)) { config_destroy(cfg); return -1; } break; case 'v': version_show(stdout); config_destroy(cfg); return 0; case 'h': usage(progname); config_destroy(cfg); return 0; case '?': default: usage(progname); config_destroy(cfg); return -1; } } print_set_syslog(0); print_set_verbose(1); if (config && (err = config_read(config, cfg))) { goto out; } print_set_progname(progname); print_set_tag(config_get_string(cfg, NULL, "message_tag")); print_set_level(config_get_int(cfg, NULL, "logging_level")); err = nsm_open(nsm, cfg); if (err) { goto out; } if (optind < argc) { batch_mode = 1; } pollfd[0].fd = nsm->fda.fd[0]; pollfd[1].fd = nsm->fda.fd[1]; pollfd[2].fd = batch_mode ? -1 : STDIN_FILENO; pollfd[0].events = POLLIN | POLLPRI; pollfd[1].events = POLLIN | POLLPRI; pollfd[2].events = batch_mode ? 0 : POLLIN | POLLPRI; while (is_running()) { if (batch_mode) { if (optind < argc && !nsm->nsm_delay_req) { cmd = argv[optind++]; if (nsm_command(nsm, cmd)) { pr_err("command failed"); continue; } } /* Wait a bit for any outstanding replies. */ tmo = 100; } cnt = poll(pollfd, NSM_NFD, tmo); if (cnt < 0) { if (EINTR == errno) { continue; } else { pr_emerg("poll failed"); err = -1; break; } } else if (!cnt && optind < argc) { /* For batch mode. No response received from target node, * continue with next command. */ nsm_reset(nsm); continue; } else if (!cnt) { break; } if (pollfd[2].revents & POLLHUP) { if (tmo == -1) { /* Wait a bit longer for outstanding replies. */ tmo = 100; pollfd[2].fd = -1; pollfd[2].events = 0; } else { break; } } if (pollfd[2].revents & (POLLIN|POLLPRI)) { if (!fgets(line, sizeof(line), stdin)) { break; } length = strlen(line); if (length < 2) { continue; } line[length - 1] = 0; cmd = line; if (nsm_command(nsm, cmd)) { pr_err("command failed"); } } if (pollfd[0].revents & (POLLIN|POLLPRI)) { msg = nsm_recv(nsm, pollfd[0].fd); if (msg) { nsm_handle_msg(nsm, msg, stdout); msg_put(msg); } } if (pollfd[1].revents & (POLLIN|POLLPRI)) { msg = nsm_recv(nsm, pollfd[1].fd); if (msg) { nsm_handle_msg(nsm, msg, stdout); msg_put(msg); } } } nsm_close(nsm); out: msg_cleanup(); config_destroy(cfg); return err; } linuxptp-3.1.1/ntpshm.c000066400000000000000000000101031407046267700150640ustar00rootroot00000000000000/** * @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-3.1.1/ntpshm.h000066400000000000000000000016551407046267700151050ustar00rootroot00000000000000/** * @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-3.1.1/nullf.c000066400000000000000000000040121407046267700146750ustar00rootroot00000000000000/** * @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 < llabs(offset)) || (servo->step_threshold && servo->step_threshold < llabs(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-3.1.1/nullf.h000066400000000000000000000016371407046267700147140ustar00rootroot00000000000000/** * @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-3.1.1/p2p_tc.c000066400000000000000000000120541407046267700147510ustar00rootroot00000000000000/** * @file p2p_tc.c * @note Copyright (C) 2018 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-1335 USA. */ #include #include "port.h" #include "port_private.h" #include "print.h" #include "rtnl.h" #include "tc.h" static int p2p_delay_request(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_PASSIVE: case PS_UNCALIBRATED: case PS_SLAVE: case PS_GRAND_MASTER: break; } return port_delay_request(p); } void p2p_dispatch(struct port *p, enum fsm_event event, int mdiff) { if (!port_state_update(p, event, mdiff)) { return; } if (!portnum(p)) { /* UDS needs no timers. */ return; } 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]); /* * Handle the side effects of the state transition. */ switch (p->state) { case PS_INITIALIZING: break; case PS_FAULTY: case PS_DISABLED: port_disable(p); break; case PS_LISTENING: port_set_announce_tmo(p); port_set_delay_tmo(p); break; case PS_PRE_MASTER: port_set_qualification_tmo(p); break; case PS_MASTER: case PS_GRAND_MASTER: break; case PS_PASSIVE: port_set_announce_tmo(p); break; case PS_UNCALIBRATED: case PS_SLAVE: port_set_announce_tmo(p); break; }; } enum fsm_event p2p_event(struct port *p, int fd_index) { int cnt, fd = p->fda.fd[fd_index]; enum fsm_event event = EV_NONE; struct ptp_message *msg, *dup; 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); return EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES; case FD_DELAY_TIMER: pr_debug("port %hu: delay timeout", portnum(p)); port_set_delay_tmo(p); tc_prune(p); return p2p_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: case FD_SYNC_TX_TIMER: case FD_UNICAST_REQ_TIMER: case FD_UNICAST_SRV_TIMER: pr_err("unexpected timer expiration"); return EV_NONE; case FD_RTNL: pr_debug("port %hu: received link status notification", portnum(p)); rtnl_link_status(fd, p->name, port_link_status, p); if (p->link_status == (LINK_UP|LINK_STATE_CHANGED)) { return EV_FAULT_CLEARED; } else if ((p->link_status == (LINK_DOWN|LINK_STATE_CHANGED)) || (p->link_status & TS_LABEL_CHANGED)) { return EV_FAULT_DETECTED; } else { return 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; } if (msg_sots_valid(msg)) { ts_add(&msg->hwts.ts, -p->rx_timestamp_offset); } if (msg_unicast(msg)) { pl_warning(600, "cannot switch unicast messages!"); msg_put(msg); return EV_NONE; } dup = msg_duplicate(msg, cnt); if (!dup) { msg_put(msg); return EV_NONE; } if (tc_ignore(p, dup)) { msg_put(dup); dup = NULL; } switch (msg_type(msg)) { case SYNC: if (tc_fwd_sync(p, msg)) { event = EV_FAULT_DETECTED; break; } if (dup) { process_sync(p, dup); } break; case DELAY_REQ: break; case PDELAY_REQ: if (dup && process_pdelay_req(p, dup)) { event = EV_FAULT_DETECTED; } break; case PDELAY_RESP: if (dup && process_pdelay_resp(p, dup)) { event = EV_FAULT_DETECTED; } break; case FOLLOW_UP: if (tc_fwd_folup(p, msg)) { event = EV_FAULT_DETECTED; break; } if (dup) { process_follow_up(p, dup); } break; case DELAY_RESP: break; case PDELAY_RESP_FOLLOW_UP: if (dup) { process_pdelay_resp_fup(p, dup); } break; case ANNOUNCE: if (tc_forward(p, msg)) { event = EV_FAULT_DETECTED; break; } if (dup && process_announce(p, dup)) { event = EV_STATE_DECISION_EVENT; } break; case SIGNALING: case MANAGEMENT: if (tc_forward(p, msg)) { event = EV_FAULT_DETECTED; } break; } msg_put(msg); if (dup) { msg_put(dup); } return event; } linuxptp-3.1.1/pdt.h000066400000000000000000000024331407046267700143560ustar00rootroot00000000000000/** * @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 #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif 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-3.1.1/phc.c000066400000000000000000000057501407046267700143410ustar00rootroot00000000000000/** * @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 #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(const char *phc) { clockid_t clkid; struct timespec ts; struct timex tx; int fd; memset(&tx, 0, sizeof(tx)); fd = open(phc, O_RDWR); if (fd < 0) return CLOCK_INVALID; clkid = FD_TO_CLOCKID(fd); /* check if clkid is valid */ if (clock_gettime(clkid, &ts)) { close(fd); return CLOCK_INVALID; } if (clock_adjtime(clkid, &tx)) { 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_number_pins(clockid_t clkid) { struct ptp_clock_caps caps; if (phc_get_caps(clkid, &caps)) { return 0; } return caps.n_pins; } int phc_pin_setfunc(clockid_t clkid, struct ptp_pin_desc *desc) { int err = ioctl(CLOCKID_TO_FD(clkid), PTP_PIN_SETFUNC2, desc); if (err) { fprintf(stderr, PTP_PIN_SETFUNC_FAILED "\n"); } return err; } int phc_has_pps(clockid_t clkid) { struct ptp_clock_caps caps; if (phc_get_caps(clkid, &caps)) return 0; return caps.pps; } int phc_has_writephase(clockid_t clkid) { struct ptp_clock_caps caps; if (phc_get_caps(clkid, &caps)) { return 0; } return caps.adjust_phase; } linuxptp-3.1.1/phc.h000066400000000000000000000050341407046267700143410ustar00rootroot00000000000000/** * @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(const 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); /** * Queries the number of programmable pins of a PTP hardware clock device. * * @param clkid A clock ID obtained using phc_open(). * * @return The number of pins supported by the clock. */ int phc_number_pins(clockid_t clkid); /** * Configures a pin of a PTP hardware clock device. * * @param clkid A clock ID obtained using phc_open(). * * @param desc Pointer to a pin descriptor with the 'index', 'func', * and 'chan' fields set. * * @return Zero on success, non-zero otherwise. */ int phc_pin_setfunc(clockid_t clkid, struct ptp_pin_desc *desc); /** * 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); /** * Checks whether the given PTP hardware clock device supports write phase mode. * * @param clkid A clock ID obtained using phc_open(). * * @return Zero if write phase mode is not supported by the clock, non-zero * otherwise. */ int phc_has_writephase(clockid_t clkid); #endif linuxptp-3.1.1/phc2sys.8000066400000000000000000000346551407046267700151150ustar00rootroot00000000000000.TH PHC2SYS 8 "April 2018" "linuxptp" .SH NAME phc2sys \- synchronize two or more clocks .SH SYNOPSIS .B phc2sys \-a [ .B \-r ] [ .B \-r ] [ .BI \-f " config-file" ] [ options ] [ .I long-options ] .br .B phc2sys [ .BI \-f " config-file" ] [ .BI \-d " pps-device" ] [ .BI \-s " device" ] [ .BI \-c " device" ] [ .BI \-O " offset" ] [ .BI \-w ] [ options ] [ .I long-options ] .I .\|.\|. .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 \-f " config" Read configuration from the specified file. No configuration file is read by default. .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. This option does not support bonded interface (e.g. bond0, team0). If .B ptp4l has a port on an active-backup bond or team interface, the .B \-a option can be used to track the active interface. .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 changing the clock frequency. 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 .BI \-t " message-tag" Specify the tag which is added to all messages printed to the standard output or system log. The default is an empty string. .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 LONG OPTIONS Each and every configuration file option (see below in section .BR FILE\ OPTIONS) may also appear as a "long" style command line argument. For example, the transportSpecific option may be set using either of these two forms: .RS \f(CW\-\-transportSpecific 1 \-\-transportSpecific=1\fP .RE Option values given on the command line override values in the global section of the configuration file (which, in turn overrides default values). .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. This is the only used option. .SH FILE OPTIONS .TP .B domainNumber Specify the domain number used by phc2sys. The default is 0. Same as option .B \-n (see above). .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 step_threshold option is set to correct such offset by stepping). Relevant only with software time stamping. The default is 1 (enabled). Same as option .B \-x (see above). The maximum logging level of messages which should be printed. The default is 6 (LOG_INFO). Same as option .B \-l (see above). .TP .B logging_level The maximum logging level of messages which should be printed. The default is 6 (LOG_INFO). Same as option .B \-l (see above). .TP .B message_tag The tag which is added to all messages printed to the standard output or system log. The default is an empty string (which cannot be set in the configuration file as the option requires an argument). Same as option .B \-t (see above). .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%). Same as option .B \-L (see above). .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." Same as option .B \-E (see above). .TP .B transportSpecific The transport specific field. Must be in the range 0 to 255. The default is 0. .TP .B use_syslog Print messages to the system log if enabled. The default is 1 (enabled). Related to option .B \-q (see above). .TP .B verbose Print messages to the standard output if enabled. The default is 0 (disabled). Related to option .B \-m (see above). .TP .B pi_proportional_const Specifies the proportional constant of the PI controller. Same as option .B \-P (see above). .TP .B pi_integral_const Specifies the integral constant of the PI controller. Same as option .B \-I (see above). .TP .B step_threshold Specifies 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 -F option is used). It's specified in seconds. The value of 0.0 disables stepping after the start. The default is 0.0. Same as option .B \-S (see above). .TP .B first_step_threshold 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). Same as option .B \-F (see above). .TP .B ntpshm_segment The number of the SHM segment used by ntpshm servo. The default is 0. Same as option .B \-M (see above). .TP .B uds_address Specifies the address of the server's UNIX domain socket. The default is /var/run/ptp4 Same as option .B \-z (see above). .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 Same as above, in an IEEE 802.1AS domain. .RS \f(CWphc2sys \-a \-rr --transportSpecific=1\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 WARNING Be cautious when the same configuration file is used for both ptp4l and phc2sys. Keep in mind, that values specified in the configuration file take precedence over their default values. If a certain option, which is common to ptp4l and phc2sys, is specified to a non-default value in the configuration file (p.e., for ptp4l), then this non-default value applies also for phc2sys. This might be not what is expected. It is recommended to use seperate configuration files for ptp4l and phc2sys in order to avoid any unexpected behavior. .SH SEE ALSO .BR ptp4l (8) linuxptp-3.1.1/phc2sys.c000066400000000000000000001171701407046267700151620ustar00rootroot00000000000000/** * @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; LIST_ENTRY(clock) dst_list; clockid_t clkid; int phc_index; int sysoff_method; 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 phc2sys_private { 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; LIST_HEAD(dst_clock_head, clock) dst_clocks; struct clock *master; }; static struct config *phc2sys_config; static int update_pmc(struct phc2sys_private *priv, int subscribe); static int clock_handle_leap(struct phc2sys_private *priv, struct clock *clock, int64_t offset, uint64_t ts); static int run_pmc_get_utc_offset(struct phc2sys_private *priv, int timeout); static void run_pmc_events(struct phc2sys_private *priv); static int normalize_state(int state); static int run_pmc_port_properties(struct phc2sys_private *priv, int timeout, unsigned int port, int *state, int *tstamping, char *iface); static struct servo *servo_add(struct phc2sys_private *priv, struct clock *clock) { double ppb; int max_ppb; struct servo *servo; clockadj_init(clock->clkid); ppb = clockadj_get_freq(clock->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(clock->clkid, ppb); if (clock->clkid == CLOCK_REALTIME) { sysclk_set_leap(0); max_ppb = sysclk_max_freq(); } else { max_ppb = phc_max_adj(clock->clkid); if (!max_ppb) { pr_err("clock is not adjustable"); return NULL; } } servo = servo_create(phc2sys_config, priv->servo_type, -ppb, max_ppb, 0); if (!servo) { pr_err("Failed to create servo"); return NULL; } servo_sync_interval(servo, priv->phc_interval); return servo; } static struct clock *clock_add(struct phc2sys_private *priv, char *device) { struct clock *c; clockid_t clkid = CLOCK_INVALID; int phc_index = -1; if (device) { clkid = posix_clock_open(device, &phc_index); 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->phc_index = phc_index; c->servo_state = SERVO_UNLOCKED; c->device = device ? strdup(device) : NULL; if (c->clkid == CLOCK_REALTIME) { c->source_label = "sys"; c->is_utc = 1; } else { c->source_label = "phc"; } if (priv->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 (priv->sanity_freq_limit) { c->sanity_check = clockcheck_create(priv->sanity_freq_limit); if (!c->sanity_check) { pr_err("failed to create clock check"); return NULL; } } if (clkid != CLOCK_INVALID) c->servo = servo_add(priv, c); if (clkid != CLOCK_INVALID && clkid != CLOCK_REALTIME) c->sysoff_method = sysoff_probe(CLOCKID_TO_FD(clkid), priv->phc_readings); LIST_INSERT_HEAD(&priv->clocks, c, list); return c; } static void clock_cleanup(struct phc2sys_private *priv) { struct clock *c, *tmp; LIST_FOREACH_SAFE(c, &priv->clocks, list, tmp) { if (c->servo) { servo_destroy(c->servo); } if (c->sanity_check) { clockcheck_destroy(c->sanity_check); } if (c->delay_stats) { stats_destroy(c->delay_stats); } if (c->freq_stats) { stats_destroy(c->freq_stats); } if (c->offset_stats) { stats_destroy(c->offset_stats); } if (c->device) { free(c->device); } free(c); } } static void port_cleanup(struct phc2sys_private *priv) { struct port *p, *tmp; LIST_FOREACH_SAFE(p, &priv->ports, list, tmp) { free(p); } } static struct port *port_get(struct phc2sys_private *priv, unsigned int number) { struct port *p; LIST_FOREACH(p, &priv->ports, list) { if (p->number == number) return p; } return NULL; } static struct port *port_add(struct phc2sys_private *priv, unsigned int number, char *device) { struct port *p; struct clock *c = NULL, *tmp; p = port_get(priv, number); if (p) return p; /* port is a new one, look whether we have the device already on * a different port */ LIST_FOREACH(tmp, &priv->clocks, list) { if (!strcmp(tmp->device, device)) { c = tmp; break; } } if (!c) { c = clock_add(priv, 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(&priv->ports, p, list); return p; } static void clock_reinit(struct phc2sys_private *priv, struct clock *clock, int new_state) { int phc_index = -1, phc_switched = 0; int state, timestamping, ret = -1; struct port *p; struct servo *servo; struct sk_ts_info ts_info; char iface[IFNAMSIZ]; clockid_t clkid = CLOCK_INVALID; LIST_FOREACH(p, &priv->ports, list) { if (p->clock == clock) { ret = run_pmc_port_properties(priv, 1000, p->number, &state, ×tamping, iface); if (ret > 0) p->state = normalize_state(state); } } if (ret > 0 && timestamping != TS_SOFTWARE) { /* Check if device changed */ if (strcmp(clock->device, iface)) { free(clock->device); clock->device = strdup(iface); } /* Check if phc index changed */ if (!sk_get_ts_info(clock->device, &ts_info) && clock->phc_index != ts_info.phc_index) { clkid = posix_clock_open(clock->device, &phc_index); if (clkid == CLOCK_INVALID) return; posix_clock_close(clock->clkid); clock->clkid = clkid; clock->phc_index = phc_index; servo = servo_add(priv, clock); if (servo) { servo_destroy(clock->servo); clock->servo = servo; } phc_switched = 1; } } if (new_state == PS_MASTER || phc_switched) { 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 struct clock *find_dst_clock(struct phc2sys_private *priv, int phc_index) { struct clock *c = NULL; LIST_FOREACH(c, &priv->dst_clocks, dst_list) { if (c->phc_index == phc_index) { break; } } return c; } static void reconfigure(struct phc2sys_private *priv) { struct clock *c, *rt = NULL, *src = NULL, *last = NULL, *dup = NULL; int src_cnt = 0, dst_cnt = 0; pr_info("reconfiguring after port state change"); priv->state_changed = 0; while (priv->dst_clocks.lh_first != NULL) { LIST_REMOVE(priv->dst_clocks.lh_first, dst_list); } LIST_FOREACH(c, &priv->clocks, list) { if (c->clkid == CLOCK_REALTIME) { rt = c; continue; } if (c->new_state) { clock_reinit(priv, c, c->new_state); 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: dup = find_dst_clock(priv, c->phc_index); if (!dup) { pr_info("selecting %s for synchronization", c->device); dst_cnt++; LIST_INSERT_HEAD(&priv->dst_clocks, c, dst_list); } else { pr_info("skipping %s: %s has the same clock " "and is already selected", c->device, dup->device); } 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) { priv->master = last; /* Reset to original state in next reconfiguration. */ priv->master->new_state = priv->master->state; priv->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..."); priv->master = NULL; return; } if (src_cnt > 0 && !src) { pr_info("master clock not ready, waiting..."); priv->master = NULL; return; } if (!src_cnt && !dst_cnt) { pr_info("no PHC ready, waiting..."); priv->master = NULL; return; } if ((!src_cnt && (!rt || rt->dest_only)) || (!dst_cnt && !rt)) { pr_info("nothing to synchronize"); priv->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(priv, rt, rt->state); } LIST_INSERT_HEAD(&priv->dst_clocks, rt, dst_list); pr_info("selecting %s for synchronization", rt->device); } priv->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 phc2sys_private *priv, struct clock *dst) { int direction = priv->forced_sync_offset; if (!direction) direction = dst->is_utc - priv->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("%s " "rms %4.0f max %4.0f " "freq %+6.0f +/- %3.0f " "delay %5.0f +/- %3.0f", clock->device, offset_stats.rms, offset_stats.max_abs, freq_stats.mean, freq_stats.stddev, delay_stats.mean, delay_stats.stddev); } else { pr_info("%s " "rms %4.0f max %4.0f " "freq %+6.0f +/- %3.0f", clock->device, 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 phc2sys_private *priv, struct clock *clock, int64_t offset, uint64_t ts, int64_t delay) { enum servo_state state; double ppb; if (clock_handle_leap(priv, clock, offset, ts)) return; offset += get_sync_offset(priv, 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: case SERVO_LOCKED_STABLE: 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, priv->stats_max_count, offset, ppb, delay); } else { if (delay >= 0) { pr_info("%s %s offset %9" PRId64 " s%d freq %+7.0f " "delay %6" PRId64, clock->device, priv->master->source_label, offset, state, ppb, delay); } else { pr_info("%s %s offset %9" PRId64 " s%d freq %+7.0f", clock->device, priv->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 phc2sys_private *priv, struct clock *clock, int fd) { int64_t pps_offset, phc_offset, phc_delay; uint64_t pps_ts, phc_ts; clockid_t src = priv->master->clkid; priv->master->source_label = "pps"; if (src == CLOCK_INVALID) { /* The sync offset can't be applied with PPS alone. */ priv->sync_offset = 0; } else { enable_pps_output(priv->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, priv->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(priv, 0) < 0) continue; update_clock(priv, 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 phc2sys_private *priv, int subscriptions) { struct timespec interval; struct clock *clock; uint64_t ts; int64_t offset, delay; interval.tv_sec = priv->phc_interval; interval.tv_nsec = (priv->phc_interval - interval.tv_sec) * 1e9; while (is_running()) { clock_nanosleep(CLOCK_MONOTONIC, 0, &interval, NULL); if (update_pmc(priv, subscriptions) < 0) continue; if (subscriptions) { run_pmc_events(priv); if (priv->state_changed) { /* force getting offset, as it may have * changed after the port state change */ if (run_pmc_get_utc_offset(priv, 1000) <= 0) { pr_err("failed to get UTC offset"); continue; } reconfigure(priv); } } if (!priv->master) continue; LIST_FOREACH(clock, &priv->dst_clocks, dst_list) { if (!update_needed(clock)) continue; /* don't try to synchronize the clock to itself */ if (clock->clkid == priv->master->clkid || (clock->phc_index >= 0 && clock->phc_index == priv->master->phc_index) || !strcmp(clock->device, priv->master->device)) continue; if (!clock->servo) { pr_err("cannot update clock without servo"); return -1; } if (clock->clkid == CLOCK_REALTIME && priv->master->sysoff_method >= 0) { /* use sysoff */ if (sysoff_measure(CLOCKID_TO_FD(priv->master->clkid), priv->master->sysoff_method, priv->phc_readings, &offset, &ts, &delay) < 0) return -1; } else if (priv->master->clkid == CLOCK_REALTIME && clock->sysoff_method >= 0) { /* use reversed sysoff */ if (sysoff_measure(CLOCKID_TO_FD(clock->clkid), clock->sysoff_method, priv->phc_readings, &offset, &ts, &delay) < 0) return -1; offset = -offset; ts += offset; } else { /* use phc */ if (!read_phc(priv->master->clkid, clock->clkid, priv->phc_readings, &offset, &ts, &delay)) continue; } update_clock(priv, clock, offset, ts, delay); } } return 0; } static int check_clock_identity(struct phc2sys_private *priv, struct ptp_message *msg) { if (!priv->clock_identity_set) return 1; return cid_eq(&priv->clock_identity, &msg->header.sourcePortIdentity.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(msg) != 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 phc2sys_private *priv, struct clock *clock) { struct port *p; int state = PS_DISABLED; LIST_FOREACH(p, &priv->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 phc2sys_private *priv, 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(priv, 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(priv, clock); if (clock->state != state || clock->new_state) { clock->new_state = state; priv->state_changed = 1; } } return 1; } return 0; } static void send_subscription(struct phc2sys_private *priv) { 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(priv->pmc, TLV_SUBSCRIBE_EVENTS_NP, &sen, sizeof(sen)); } static int init_pmc(struct config *cfg, struct phc2sys_private *priv) { char uds_local[MAX_IFNAME_SIZE + 1]; snprintf(uds_local, sizeof(uds_local), "/var/run/phc2sys.%d", getpid()); priv->pmc = pmc_create(cfg, TRANS_UDS, uds_local, 0, config_get_int(cfg, NULL, "domainNumber"), config_get_int(cfg, NULL, "transportSpecific") << 4, 1); if (!priv->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 phc2sys_private *priv, 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(priv->pmc); pollfd[0].events = POLLIN|POLLPRI; if (!priv->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. */ priv->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(priv); break; default: pmc_send_get_action(priv->pmc, ds_id); break; } priv->pmc_ds_requested = 1; } if (!(pollfd[0].revents & (POLLIN|POLLPRI))) continue; *msg = pmc_recv(priv->pmc); if (!*msg) continue; if (!check_clock_identity(priv, *msg)) { msg_put(*msg); *msg = NULL; continue; } res = is_msg_mgt(*msg); if (res < 0 && get_mgt_err_id(*msg) == ds_id) { priv->pmc_ds_requested = 0; return -1; } if (res <= 0 || recv_subscribed(priv, *msg, ds_id) || get_mgt_id(*msg) != ds_id) { msg_put(*msg); *msg = NULL; continue; } priv->pmc_ds_requested = 0; return 1; } } static int run_pmc_wait_sync(struct phc2sys_private *priv, int timeout) { struct ptp_message *msg; int res; void *data; Enumeration8 portState; while (1) { res = run_pmc(priv, 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) */ priv->pmc_ds_requested = 1; } } static int run_pmc_get_utc_offset(struct phc2sys_private *priv, int timeout) { struct ptp_message *msg; int res; struct timePropertiesDS *tds; res = run_pmc(priv, timeout, TLV_TIME_PROPERTIES_DATA_SET, &msg); if (res <= 0) return res; tds = (struct timePropertiesDS *)get_mgt_data(msg); if (tds->flags & PTP_TIMESCALE) { priv->sync_offset = tds->currentUtcOffset; if (tds->flags & LEAP_61) priv->leap = 1; else if (tds->flags & LEAP_59) priv->leap = -1; else priv->leap = 0; priv->utc_offset_traceable = tds->flags & UTC_OFF_VALID && tds->flags & TIME_TRACEABLE; } else { priv->sync_offset = 0; priv->leap = 0; priv->utc_offset_traceable = 0; } msg_put(msg); return 1; } static int run_pmc_get_number_ports(struct phc2sys_private *priv, int timeout) { struct ptp_message *msg; int res; struct defaultDS *dds; res = run_pmc(priv, 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 phc2sys_private *priv, int timeout) { struct ptp_message *msg; int res; res = run_pmc(priv, timeout, TLV_SUBSCRIBE_EVENTS_NP, &msg); if (res <= 0) return res; msg_put(msg); return 1; } static void run_pmc_events(struct phc2sys_private *priv) { struct ptp_message *msg; run_pmc(priv, 0, -1, &msg); } static int run_pmc_port_properties(struct phc2sys_private *priv, 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(priv->pmc, port); while (1) { res = run_pmc(priv, 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(priv->pmc); return res; } static int run_pmc_clock_identity(struct phc2sys_private *priv, int timeout) { struct ptp_message *msg; struct defaultDS *dds; int res; res = run_pmc(priv, timeout, TLV_DEFAULT_DATA_SET, &msg); if (res <= 0) return res; dds = (struct defaultDS *)get_mgt_data(msg); memcpy(&priv->clock_identity, &dds->clockIdentity, sizeof(struct ClockIdentity)); priv->clock_identity_set = 1; msg_put(msg); return 1; } static void close_pmc(struct phc2sys_private *priv) { pmc_destroy(priv->pmc); priv->pmc = NULL; } static int auto_init_ports(struct phc2sys_private *priv, int add_rt) { struct port *port; struct clock *clock; int number_ports, res; unsigned int i; int state, timestamping; char iface[IFNAMSIZ]; while (1) { if (!is_running()) return -1; res = run_pmc_clock_identity(priv, 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(priv, 1000); if (number_ports <= 0) { pr_err("failed to get number of ports"); return -1; } res = run_pmc_subscribe(priv, 1000); if (res <= 0) { pr_err("failed to subscribe"); return -1; } for (i = 1; i <= number_ports; i++) { res = run_pmc_port_properties(priv, 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(priv, i, iface); if (!port) return -1; port->state = normalize_state(state); } if (LIST_EMPTY(&priv->clocks)) { pr_err("no suitable ports available"); return -1; } LIST_FOREACH(clock, &priv->clocks, list) { clock->new_state = clock_compute_state(priv, clock); } priv->state_changed = 1; if (add_rt) { clock = clock_add(priv, "CLOCK_REALTIME"); if (!clock) return -1; if (add_rt == 1) clock->dest_only = 1; } /* get initial offset */ if (run_pmc_get_utc_offset(priv, 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 phc2sys_private *priv, 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 (priv->pmc && !(ts > priv->pmc_last_update && ts - priv->pmc_last_update < PMC_UPDATE_INTERVAL)) { if (subscribe) run_pmc_subscribe(priv, 0); if (run_pmc_get_utc_offset(priv, 0) > 0) priv->pmc_last_update = ts; } return 0; } /* Returns: non-zero to skip clock update */ static int clock_handle_leap(struct phc2sys_private *priv, struct clock *clock, int64_t offset, uint64_t ts) { int clock_leap, node_leap = priv->leap; clock->sync_offset = priv->sync_offset; if ((node_leap || clock->leap_set) && clock->is_utc != priv->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 (priv->master->is_utc) { struct timespec tp; if (clock_gettime(priv->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(priv, 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 && priv->kernel_leap) sysclk_set_leap(clock_leap); else servo_leap(clock->servo, clock_leap); clock->leap_set = clock_leap; } } if (priv->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" " -f [file] configuration file\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" " -t [tag] add tag to log messages\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, *dst_name = NULL, *progname, *src_name = NULL; struct clock *src, *dst; struct config *cfg; struct option *opts; int autocfg = 0, c, domain_number = 0, index, ntpshm_segment; int pps_fd = -1, print_level = LOG_INFO, r = -1, rt = 0, wait_sync = 0; double phc_rate, tmp; struct phc2sys_private priv = { .phc_readings = 5, .phc_interval = 1.0, }; handle_term_signals(); cfg = phc2sys_config = config_create(); if (!cfg) { return -1; } opts = config_long_options(cfg); 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_long(argc, argv, "arc:d:f:s:E:P:I:S:F:R:N:O:L:M:i:u:wn:xz:l:t:mqvh", opts, &index))) { switch (c) { case 0: if (config_parse_option(cfg, opts[index].name, optarg)) { goto bad_usage; } break; 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 'f': config = optarg; break; case 'i': fprintf(stderr, "'-i' has been deprecated. please use '-s' instead.\n"); /* fallthrough */ case 's': src_name = strdup(optarg); break; case 'E': if (!strcasecmp(optarg, "pi")) { config_set_int(cfg, "clock_servo", CLOCK_SERVO_PI); } else if (!strcasecmp(optarg, "linreg")) { config_set_int(cfg, "clock_servo", CLOCK_SERVO_LINREG); } else if (!strcasecmp(optarg, "ntpshm")) { config_set_int(cfg, "clock_servo", 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; priv.phc_interval = 1.0 / phc_rate; break; case 'N': if (get_arg_val_i(c, optarg, &priv.phc_readings, 1, INT_MAX)) goto end; break; case 'O': if (get_arg_val_i(c, optarg, &priv.sync_offset, INT_MIN, INT_MAX)) goto end; priv.forced_sync_offset = -1; break; case 'L': if (get_arg_val_i(c, optarg, &priv.sanity_freq_limit, 0, INT_MAX) || config_set_int(cfg, "sanity_freq_limit", priv.sanity_freq_limit)) { 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, &priv.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) || config_set_int(cfg, "domainNumber", domain_number)) { goto end; } break; case 'x': if (config_set_int(cfg, "kernel_leap", 0)) { goto end; } 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) || config_set_int(cfg, "logging_level", print_level)) { goto end; } break; case 't': if (config_set_string(cfg, "message_tag", optarg)) { goto end; } break; case 'm': if (config_set_int(cfg, "verbose", 1)) { goto end; } break; case 'q': if (config_set_int(cfg, "use_syslog", 0)) { goto end; } 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 (config && (c = config_read(config, cfg))) { return c; } if (autocfg && (src_name || dst_name || pps_fd >= 0 || wait_sync || priv.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 && !priv.forced_sync_offset) { fprintf(stderr, "time offset must be specified using -w or -O\n"); goto bad_usage; } if (priv.servo_type == CLOCK_SERVO_NTPSHM) { priv.kernel_leap = 0; priv.sanity_freq_limit = 0; } print_set_progname(progname); print_set_tag(config_get_string(cfg, NULL, "message_tag")); 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")); priv.servo_type = config_get_int(cfg, NULL, "clock_servo"); if (priv.servo_type == CLOCK_SERVO_NTPSHM) { config_set_int(cfg, "kernel_leap", 0); config_set_int(cfg, "sanity_freq_limit", 0); } priv.kernel_leap = config_get_int(cfg, NULL, "kernel_leap"); priv.sanity_freq_limit = config_get_int(cfg, NULL, "sanity_freq_limit"); if (autocfg) { if (init_pmc(cfg, &priv)) goto end; if (auto_init_ports(&priv, rt) < 0) goto end; r = do_loop(&priv, 1); goto end; } src = clock_add(&priv, src_name); free(src_name); if (!src) { fprintf(stderr, "valid source clock must be selected.\n"); goto bad_usage; } src->state = PS_SLAVE; priv.master = src; dst = clock_add(&priv, 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; LIST_INSERT_HEAD(&priv.dst_clocks, dst, dst_list); 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, &priv)) goto end; while (is_running()) { r = run_pmc_wait_sync(&priv, 1000); if (r < 0) goto end; if (r > 0) break; else pr_notice("Waiting for ptp4l..."); } if (!priv.forced_sync_offset) { r = run_pmc_get_utc_offset(&priv, 1000); if (r <= 0) { pr_err("failed to get UTC offset"); goto end; } } if (priv.forced_sync_offset || (src->clkid != CLOCK_REALTIME && dst->clkid != CLOCK_REALTIME) || src->clkid == CLOCK_INVALID) close_pmc(&priv); } 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(&priv, dst, pps_fd); } else { r = do_loop(&priv, 0); } end: if (priv.pmc) close_pmc(&priv); clock_cleanup(&priv); port_cleanup(&priv); config_destroy(cfg); msg_cleanup(); return r; bad_usage: usage(progname); config_destroy(cfg); return -1; } linuxptp-3.1.1/phc_ctl.8000066400000000000000000000060141407046267700151220ustar00rootroot00000000000000.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 capabilities. 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-3.1.1/phc_ctl.c000066400000000000000000000315621407046267700152030ustar00rootroot00000000000000/* * @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 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 %lld.%09ld or %s", (long long)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 %lld.%09lu or %s", (long long)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_MAX, 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" " %d configurable input/output pins\n" " %s pulse per second support\n" " %s cross timestamping support\n", caps.max_adj, caps.n_alarm, caps.n_ext_ts, caps.n_per_out, caps.n_pins, caps.pps ? "has" : "doesn't have", caps.cross_timestamping ? "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; int method; method = sysoff_probe(CLOCKID_TO_FD(clkid), 9); if (method >= 0 && sysoff_measure(CLOCKID_TO_FD(clkid), method, 9, &sys_offset, &sys_ts, &delay) >= 0) { 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[]) { int c, cmdc, junk, print_level = LOG_INFO, result; char **cmdv, *default_cmdv[] = { "caps" }; int use_syslog = 1, verbose = 1; const char *progname; 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 = posix_clock_open(argv[optind], &junk); if (clkid == CLOCK_INVALID) return -1; /* pass the remaining arguments to the run_cmds loop */ result = run_cmds(clkid, cmdc, cmdv); posix_clock_close(clkid); if (result < -1) { /* show usage when command fails */ usage(progname); return result; } return 0; } linuxptp-3.1.1/pi.c000066400000000000000000000150351407046267700141740ustar00rootroot00000000000000/** * @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 < llabs(offset)) || (servo->step_threshold && servo->step_threshold < llabs(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 < llabs(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-3.1.1/pi.h000066400000000000000000000016661407046267700142060ustar00rootroot00000000000000/** * @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-3.1.1/pmc.8000066400000000000000000000122361407046267700142700ustar00rootroot00000000000000.TH PMC 8 "October 2013" "linuxptp" .SH NAME pmc \- PTP management client .SH SYNOPSIS .B pmc [ .BI \-f " config-file" ] [ .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" ] [ .I long-options ] [ .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 .BI \-f " config-file" Read configuration from the specified file. No configuration file is read by default. .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 LONG OPTIONS Each and every configuration file option (see below in sections .BR PROGRAM\ OPTIONS and .BR PORT\ OPTIONS ) may also appear as a "long" style command line argument. For example, the transportSpecific option may be set using either of these two forms: .RS \f(CW\-\-transportSpecific 1 \-\-transportSpecific=1\fP .RE Option values given on the command line override values in the global section of the configuration file (which, in turn, overrides default values). .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 global program options as well as the default port specific 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] ). .SH PROGRAM OPTIONS .TP .B domainNumber The domain attribute of the local clock. The default is 0. .SH PORT OPTIONS .TP .B transportSpecific The transport specific field. Must be in the range 0 to 255. The default is 0. .TP .B network_transport Select the network transport. Possible values are UDPv4, UDPv6 and L2. The default is UDPv4. .TP .B ptp_dst_mac The MAC address to which PTP management messages should be sent. Relevant only with L2 transport. The default is 01:1B:19:00:00:00. .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 PORT_PROPERTIES_NP .TP .B PORT_STATS_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 WARNING Be cautious when the same configuration file is used for both ptp4l and pmc. Keep in mind that values specified in the configuration file take precedence over their default values. If a certain option which is common to ptp4l and pmc is specified to a non-default value in the configuration file (e.g. for ptp4l), then this non-default value applies also for pmc. This might be not what is expected. To avoid securely these unexpected behaviour, different configuration files for ptp4l and pmc are recommended. .SH SEE ALSO .BR ptp4l (8) linuxptp-3.1.1/pmc.c000066400000000000000000000547411407046267700143520ustar00rootroot00000000000000/** * @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 "notification.h" #include "pmc_common.h" #include "print.h" #include "tlv.h" #include "uds.h" #include "util.h" #include "version.h" static struct pmc *pmc; #define IFMT "\n\t\t" #define P41 ((double)(1ULL << 41)) 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); } static char *bin2str(Octet *data, int len) { static char buf[BIN_BUF_SIZE]; return bin2str_impl(data, len, buf, sizeof(buf)); } #define SHOW_TIMESTAMP(ts) \ ((uint64_t)ts.seconds_lsb) | (((uint64_t)ts.seconds_msb) << 32), ts.nanoseconds static void pmc_show_delay_timing(struct slave_delay_timing_record *record, FILE *fp) { fprintf(fp, IFMT "sequenceId %hu" IFMT "delayOriginTimestamp %" PRId64 ".%09u" IFMT "totalCorrectionField %" PRId64 IFMT "delayResponseTimestamp %" PRId64 ".%09u", record->sequenceId, SHOW_TIMESTAMP(record->delayOriginTimestamp), record->totalCorrectionField << 16, SHOW_TIMESTAMP(record->delayResponseTimestamp)); } static void pmc_show_rx_sync_timing(struct slave_rx_sync_timing_record *record, FILE *fp) { fprintf(fp, IFMT "sequenceId %hu" IFMT "syncOriginTimestamp %" PRId64 ".%09u" IFMT "totalCorrectionField %" PRId64 IFMT "scaledCumulativeRateOffset %u" IFMT "syncEventIngressTimestamp %" PRId64 ".%09u", record->sequenceId, SHOW_TIMESTAMP(record->syncOriginTimestamp), record->totalCorrectionField << 16, record->scaledCumulativeRateOffset, SHOW_TIMESTAMP(record->syncEventIngressTimestamp)); } static void pmc_show_signaling(struct ptp_message *msg, FILE *fp) { struct slave_rx_sync_timing_record *sync_record; struct slave_delay_timing_record *delay_record; struct slave_rx_sync_timing_data_tlv *srstd; struct slave_delay_timing_data_tlv *sdtdt; struct tlv_extra *extra; int i, cnt; fprintf(fp, "\t%s seq %hu %s ", pid2str(&msg->header.sourcePortIdentity), msg->header.sequenceId, "SIGNALING"); TAILQ_FOREACH(extra, &msg->tlv_list, list) { switch (extra->tlv->type) { case TLV_SLAVE_RX_SYNC_TIMING_DATA: srstd = (struct slave_rx_sync_timing_data_tlv *) extra->tlv; cnt = (srstd->length - sizeof(srstd->sourcePortIdentity)) / sizeof(*sync_record); fprintf(fp, "SLAVE_RX_SYNC_TIMING_DATA N %d " IFMT "sourcePortIdentity %s", cnt, pid2str(&srstd->sourcePortIdentity)); sync_record = srstd->record; for (i = 0; i < cnt; i++) { pmc_show_rx_sync_timing(sync_record, fp); sync_record++; } break; case TLV_SLAVE_DELAY_TIMING_DATA_NP: sdtdt = (struct slave_delay_timing_data_tlv *) extra->tlv; cnt = (sdtdt->length - sizeof(sdtdt->sourcePortIdentity)) / sizeof(*delay_record); fprintf(fp, "SLAVE_DELAY_TIMING_DATA_NP N %d " IFMT "sourcePortIdentity %s", cnt, pid2str(&sdtdt->sourcePortIdentity)); delay_record = sdtdt->record; for (i = 0; i < cnt; i++) { pmc_show_delay_timing(delay_record, fp); delay_record++; } break; default: break; } } fprintf(fp, "\n"); fflush(fp); } static void pmc_show(struct ptp_message *msg, FILE *fp) { struct grandmaster_settings_np *gsn; struct mgmt_clock_description *cd; struct subscribe_events_np *sen; struct management_tlv_datum *mtd; struct port_properties_np *ppn; struct timePropertiesDS *tp; struct management_tlv *mgt; struct time_status_np *tsn; struct port_stats_np *pcp; struct tlv_extra *extra; struct port_ds_np *pnp; struct defaultDS *dds; struct currentDS *cds; struct parentDS *pds; struct portDS *p; struct TLV *tlv; int action; if (msg_type(msg) == SIGNALING) { pmc_show_signaling(msg, fp); return; } 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, pmc_action_string(action)); if (msg_tlv_count(msg) != 1) { goto out; } extra = TAILQ_FIRST(&msg->tlv_list); 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 "); goto out; } 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 = &extra->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(extra->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_SUBSCRIBE_EVENTS_NP: sen = (struct subscribe_events_np *) mgt->data; fprintf(fp, "SUBSCRIBE_EVENTS_NP " IFMT "duration %hu" IFMT "NOTIFY_PORT_STATE %s", sen->duration, (sen->bitmask[0] & 1 << NOTIFY_PORT_STATE) ? "on" : "off"); break; case TLV_SYNCHRONIZATION_UNCERTAIN_NP: mtd = (struct management_tlv_datum *) mgt->data; fprintf(fp, "SYNCHRONIZATION_UNCERTAIN_NP " IFMT "uncertain %hhu", mtd->val); 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_PORT_PROPERTIES_NP: ppn = (struct port_properties_np *) mgt->data; if (ppn->port_state > PS_SLAVE) { ppn->port_state = 0; } fprintf(fp, "PORT_PROPERTIES_NP " IFMT "portIdentity %s" IFMT "portState %s" IFMT "timestamping %s" IFMT "interface %s", pid2str(&ppn->portIdentity), ps_str[ppn->port_state], ts_str(ppn->timestamping), text2str(&ppn->interface)); break; case TLV_PORT_STATS_NP: pcp = (struct port_stats_np *) mgt->data; fprintf(fp, "PORT_STATS_NP " IFMT "portIdentity %s" IFMT "rx_Sync %" PRIu64 IFMT "rx_Delay_Req %" PRIu64 IFMT "rx_Pdelay_Req %" PRIu64 IFMT "rx_Pdelay_Resp %" PRIu64 IFMT "rx_Follow_Up %" PRIu64 IFMT "rx_Delay_Resp %" PRIu64 IFMT "rx_Pdelay_Resp_Follow_Up %" PRIu64 IFMT "rx_Announce %" PRIu64 IFMT "rx_Signaling %" PRIu64 IFMT "rx_Management %" PRIu64 IFMT "tx_Sync %" PRIu64 IFMT "tx_Delay_Req %" PRIu64 IFMT "tx_Pdelay_Req %" PRIu64 IFMT "tx_Pdelay_Resp %" PRIu64 IFMT "tx_Follow_Up %" PRIu64 IFMT "tx_Delay_Resp %" PRIu64 IFMT "tx_Pdelay_Resp_Follow_Up %" PRIu64 IFMT "tx_Announce %" PRIu64 IFMT "tx_Signaling %" PRIu64 IFMT "tx_Management %" PRIu64, pid2str(&pcp->portIdentity), pcp->stats.rxMsgType[SYNC], pcp->stats.rxMsgType[DELAY_REQ], pcp->stats.rxMsgType[PDELAY_REQ], pcp->stats.rxMsgType[PDELAY_RESP], pcp->stats.rxMsgType[FOLLOW_UP], pcp->stats.rxMsgType[DELAY_RESP], pcp->stats.rxMsgType[PDELAY_RESP_FOLLOW_UP], pcp->stats.rxMsgType[ANNOUNCE], pcp->stats.rxMsgType[SIGNALING], pcp->stats.rxMsgType[MANAGEMENT], pcp->stats.txMsgType[SYNC], pcp->stats.txMsgType[DELAY_REQ], pcp->stats.txMsgType[PDELAY_REQ], pcp->stats.txMsgType[PDELAY_RESP], pcp->stats.txMsgType[FOLLOW_UP], pcp->stats.txMsgType[DELAY_RESP], pcp->stats.txMsgType[PDELAY_RESP_FOLLOW_UP], pcp->stats.txMsgType[ANNOUNCE], pcp->stats.txMsgType[SIGNALING], pcp->stats.txMsgType[MANAGEMENT]); 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, "LOG_SYNC_INTERVAL " 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 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" " -f [file] read configuration from 'file'\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 *config = NULL, *progname; int c, cnt, index, 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 option *opts; struct config *cfg; #define N_FD 2 struct pollfd pollfd[N_FD]; handle_term_signals(); cfg = config_create(); if (!cfg) { return -1; } opts = config_long_options(cfg); /* Process the command line arguments. */ progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; while (EOF != (c = getopt_long(argc, argv, "246u""b:d:f:hi:s:t:vz", opts, &index))) { switch (c) { case 0: if (config_parse_option(cfg, opts[index].name, optarg)) { ret = -1; goto out; } break; case '2': if (config_set_int(cfg, "network_transport", TRANS_IEEE_802_3)) { ret = -1; goto out; } break; case '4': if (config_set_int(cfg, "network_transport", TRANS_UDP_IPV4)) { ret = -1; goto out; } break; case '6': if (config_set_int(cfg, "network_transport", TRANS_UDP_IPV6)) { ret = -1; goto out; } break; case 'u': if (config_set_int(cfg, "network_transport", TRANS_UDS)) { ret = -1; goto out; } break; case 'b': boundary_hops = atoi(optarg); break; case 'd': if (config_set_int(cfg, "domainNumber", atoi(optarg))) { ret = -1; goto out; } break; case 'f': config = 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)) { if (config_set_int(cfg, "transportSpecific", c)) { ret = -1; goto out; } } 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 (config && (c = config_read(config, cfg))) { config_destroy(cfg); return -1; } transport_type = config_get_int(cfg, NULL, "network_transport"); transport_specific = config_get_int(cfg, NULL, "transportSpecific") << 4; domain_number = config_get_int(cfg, NULL, "domainNumber"); 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 (pmc_do_command(pmc, 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(); out: config_destroy(cfg); return ret; } linuxptp-3.1.1/pmc_common.c000066400000000000000000000470331407046267700157160ustar00rootroot00000000000000/** * @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 #include #include "notification.h" #include "print.h" #include "tlv.h" #include "transport.h" #include "util.h" #include "pmc_common.h" #define BAD_ACTION -1 #define BAD_ID -1 #define AMBIGUOUS_ID -2 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) /* 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 static void do_get_action(struct pmc *pmc, int action, int index, char *str); static void do_set_action(struct pmc *pmc, int action, int index, char *str); static void not_supported(struct pmc *pmc, int action, int index, char *str); static void null_management(struct pmc *pmc, int action, int index, char *str); static const char *action_string[] = { "GET", "SET", "RESPONSE", "COMMAND", "ACKNOWLEDGE", }; struct management_id { char name[64]; int code; void (*func)(struct pmc *pmc, 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 }, { "SUBSCRIBE_EVENTS_NP", TLV_SUBSCRIBE_EVENTS_NP, do_set_action }, { "SYNCHRONIZATION_UNCERTAIN_NP", TLV_SYNCHRONIZATION_UNCERTAIN_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 }, { "PORT_STATS_NP", TLV_PORT_STATS_NP, do_get_action }, { "PORT_PROPERTIES_NP", TLV_PORT_PROPERTIES_NP, do_get_action }, }; static void do_get_action(struct pmc *pmc, 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(struct pmc *pmc, int action, int index, char *str) { int cnt, code = idtab[index].code, freq_traceable, leap_59, leap_61, ptp_timescale, time_traceable, utc_off_valid; struct grandmaster_settings_np gsn; struct management_tlv_datum mtd; struct subscribe_events_np sen; struct port_ds_np pnp; char onoff[4] = {0}; 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: 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_SUBSCRIBE_EVENTS_NP: memset(&sen, 0, sizeof(sen)); cnt = sscanf(str, " %*s %*s " "duration %hu " "NOTIFY_PORT_STATE %3s ", &sen.duration, onoff); if (cnt != 2) { fprintf(stderr, "%s SET needs 2 values\n", idtab[index].name); break; } if (!strcasecmp(onoff, "on")) { sen.bitmask[0] = 1 << NOTIFY_PORT_STATE; } pmc_send_set_action(pmc, code, &sen, sizeof(sen)); break; case TLV_SYNCHRONIZATION_UNCERTAIN_NP: cnt = sscanf(str, " %*s %*s %hhu", &mtd.val); if (cnt != 1) { fprintf(stderr, "%s SET needs 1 value\n", idtab[index].name); break; } switch (mtd.val) { case SYNC_UNCERTAIN_DONTCARE: case SYNC_UNCERTAIN_FALSE: case SYNC_UNCERTAIN_TRUE: pmc_send_set_action(pmc, code, &mtd, sizeof(mtd)); break; default: fprintf(stderr, "\nusage: set SYNCHRONIZATION_UNCERTAIN_NP " "%hhu (false), %hhu (true), or %hhu (don't care)\n\n", SYNC_UNCERTAIN_FALSE, SYNC_UNCERTAIN_TRUE, SYNC_UNCERTAIN_DONTCARE); } 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(struct pmc *pmc, int action, int index, char *str) { fprintf(stdout, "sorry, %s not supported yet\n", idtab[index].name); } static void null_management(struct pmc *pmc, 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(struct pmc *pmc, 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"); } 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 interface *iface; 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) { pmc->port_identity.portNumber = getpid(); } else { if (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; } pmc->iface = interface_create(iface_name); if (!pmc->iface) { pr_err("failed to create interface"); goto failed; } interface_ensure_tslabel(pmc->iface); if (transport_open(pmc->transport, pmc->iface, &pmc->fdarray, TS_SOFTWARE)) { pr_err("failed to open transport"); goto no_trans_open; } pmc->zero_length_gets = zero_datalen ? 1 : 0; return pmc; no_trans_open: interface_destroy(pmc->iface); failed: if (pmc->transport) transport_destroy(pmc->transport); free(pmc); return NULL; } void pmc_destroy(struct pmc *pmc) { transport_close(pmc->transport, &pmc->fdarray); interface_destroy(pmc->iface); 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 err; err = msg_pre_send(msg); if (err) { pr_err("msg_pre_send failed"); return -1; } return transport_send(pmc->transport, &pmc->fdarray, TRANS_GENERAL, 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; struct tlv_extra *extra; 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; extra = tlv_extra_alloc(); if (!extra) { pr_err("failed to allocate TLV descriptor"); msg_put(msg); return -ENOMEM; } extra->tlv = (struct TLV *) msg->management.suffix; msg_tlv_attach(msg, extra); 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 = &extra->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); msg_put(msg); return 0; } int pmc_send_set_action(struct pmc *pmc, int id, void *data, int datasize) { struct management_tlv *mgt; struct ptp_message *msg; struct tlv_extra *extra; msg = pmc_message(pmc, SET); if (!msg) { return -1; } extra = msg_tlv_append(msg, sizeof(*mgt) + datasize); if (!extra) { msg_put(msg); return -ENOMEM; } mgt = (struct management_tlv *) extra->tlv; mgt->type = TLV_MANAGEMENT; mgt->length = 2 + datasize; mgt->id = id; memcpy(mgt->data, data, datasize); pmc_send(pmc, msg); 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 -EPROTO: pr_debug("ignoring message"); break; } goto failed; } if (msg_sots_missing(msg)) { pr_err("received %s without timestamp", msg_type_string(msg_type(msg))); 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)); } const char *pmc_action_string(int action) { return action_string[action]; } int pmc_do_command(struct pmc *pmc, 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(pmc, 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(pmc, action, id, str); return 0; } linuxptp-3.1.1/pmc_common.h000066400000000000000000000033201407046267700157120ustar00rootroot00000000000000/** * @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); const char *pmc_action_string(int action); int pmc_do_command(struct pmc *pmc, char *str); #endif linuxptp-3.1.1/port.c000066400000000000000000002401701407046267700145500ustar00rootroot00000000000000/** * @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 #include "bmc.h" #include "clock.h" #include "designated_fsm.h" #include "filter.h" #include "missing.h" #include "msg.h" #include "phc.h" #include "port.h" #include "port_private.h" #include "print.h" #include "rtnl.h" #include "sk.h" #include "tc.h" #include "tlv.h" #include "tmv.h" #include "tsproc.h" #include "unicast_client.h" #include "unicast_service.h" #include "util.h" #define ALLOWED_LOST_RESPONSES 3 #define ANNOUNCE_SPAN 1 enum syfu_event { SYNC_MISMATCH, SYNC_MATCH, FUP_MISMATCH, FUP_MATCH, }; 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 port *p, 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->localPriority = p->localPriority; out->stepsRemoved = a->stepsRemoved; out->sender = m->header.sourcePortIdentity; out->receiver = p->portIdentity; } int clear_fault_asap(struct fault_interval *faint) { switch (faint->type) { case FTMO_LINEAR_SECONDS: return faint->val == 0 ? 1 : 0; case FTMO_LOG2_SECONDS: return faint->val == FRI_ASAP ? 1 : 0; case FTMO_CNT: return 0; } return 0; } static int check_source_identity(struct port *p, struct ptp_message *m) { struct PortIdentity master; if (p->ignore_source_id) { return 0; } master = clock_parent_identity(p->clock); return pid_eq(&master, &m->header.sourcePortIdentity) ? 0 : -1; } static void extract_address(struct ptp_message *m, struct PortAddress *paddr) { int len = 0; switch (paddr->networkProtocol) { case TRANS_UDP_IPV4: len = sizeof(m->address.sin.sin_addr.s_addr); memcpy(paddr->address, &m->address.sin.sin_addr.s_addr, len); break; case TRANS_UDP_IPV6: len = sizeof(m->address.sin6.sin6_addr.s6_addr); memcpy(paddr->address, &m->address.sin6.sin6_addr.s6_addr, len); break; case TRANS_IEEE_802_3: len = MAC_LEN; memcpy(paddr->address, &m->address.sll.sll_addr, len); break; default: return; } paddr->addressLength = len; } 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 <= -31) { 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; if (!fc) { return 0; } id1 = &m1->header.sourcePortIdentity; id2 = &fc->dataset.sender; return 0 == memcmp(id1, id2, sizeof(*id1)); } 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; } void fault_interval(struct port *port, enum fault_type ft, struct fault_interval *i) { i->type = port->flt_interval_pertype[ft].type; i->val = port->flt_interval_pertype[ft].val; } 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); } 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 int delay_req_current(struct ptp_message *m, struct timespec now) { int64_t t1, t2, tmo = 5 * NSEC2SEC; t1 = m->ts.host.tv_sec * NSEC2SEC + m->ts.host.tv_nsec; t2 = now.tv_sec * NSEC2SEC + now.tv_nsec; return t2 - t1 < tmo; } void delay_req_prune(struct port *p) { struct timespec now; struct ptp_message *m; clock_gettime(CLOCK_MONOTONIC, &now); while (!TAILQ_EMPTY(&p->delay_req)) { m = TAILQ_LAST(&p->delay_req, delay_req); if (delay_req_current(m, now)) { break; } TAILQ_REMOVE(&p->delay_req, m, list); msg_put(m); } } void ts_add(tmv_t *ts, Integer64 correction) { if (!correction) { return; } *ts = tmv_add(*ts, correction_to_tmv(correction)); } /* * 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)); TAILQ_INIT(&fc->messages); 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 ptp_message *m) { struct follow_up_info_tlv *fui; struct tlv_extra *extra; extra = msg_tlv_append(m, sizeof(*fui)); if (!extra) { return -1; } fui = (struct follow_up_info_tlv *) extra->tlv; 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; return 0; } static int net_sync_resp_append(struct port *p, struct ptp_message *m) { struct timePropertiesDS tp = clock_time_properties(p->clock); struct ClockIdentity cid = clock_identity(p->clock), pid; struct currentDS *cds = clock_current_dataset(p->clock); struct parent_ds *dad = clock_parent_ds(p->clock); struct port *best = clock_best_port(p->clock); struct nsm_resp_tlv_head *head; struct Timestamp last_sync; struct PortAddress *paddr; struct ptp_message *tmp; struct tlv_extra *extra; unsigned char *ptr; int tlv_len; uint8_t buf[sizeof(*paddr) + sizeof(struct sockaddr_storage)]; last_sync = tmv_to_Timestamp(clock_ingress_time(p->clock)); pid = dad->pds.parentPortIdentity.clockIdentity; paddr = (struct PortAddress *)buf; if (best && !cid_eq(&cid, &pid)) { /* Extract the parent's protocol address. */ paddr->networkProtocol = transport_type(best->trp); paddr->addressLength = transport_protocol_addr(best->trp, paddr->address); if (best->best) { tmp = TAILQ_FIRST(&best->best->messages); extract_address(tmp, paddr); } } else { /* We are our own parent. */ paddr->networkProtocol = transport_type(p->trp); paddr->addressLength = transport_protocol_addr(p->trp, paddr->address); } tlv_len = sizeof(*head) + sizeof(*extra->foot) + paddr->addressLength; extra = msg_tlv_append(m, tlv_len); if (!extra) { return -1; } head = (struct nsm_resp_tlv_head *) extra->tlv; head->type = TLV_PTPMON_RESP; head->length = tlv_len - sizeof(head->type) - sizeof(head->length); head->port_state = p->state == PS_GRAND_MASTER ? PS_MASTER : p->state; head->parent_addr.networkProtocol = paddr->networkProtocol; head->parent_addr.addressLength = paddr->addressLength; memcpy(head->parent_addr.address, paddr->address, paddr->addressLength); ptr = (unsigned char *) head; ptr += sizeof(*head) + paddr->addressLength; extra->foot = (struct nsm_resp_tlv_foot *) ptr; memcpy(&extra->foot->parent, &dad->pds, sizeof(extra->foot->parent)); memcpy(&extra->foot->current, cds, sizeof(extra->foot->current)); memcpy(&extra->foot->timeprop, &tp, sizeof(extra->foot->timeprop)); memcpy(&extra->foot->lastsync, &last_sync, sizeof(extra->foot->lastsync)); return 0; } static struct follow_up_info_tlv *follow_up_info_extract(struct ptp_message *m) { struct follow_up_info_tlv *f; struct tlv_extra *extra; TAILQ_FOREACH(extra, &m->tlv_list, list) { f = (struct follow_up_info_tlv *) extra->tlv; if (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 f; } } return NULL; } 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) { /* * NB - If the sk_check_fupsync option is not enabled, then * both of these time stamps will be zero. */ if (tmv_cmp(fup->hwts.sw, sync->hwts.sw) < 0) { 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) { int length = 1 + dad->path_length, ptt_len, tlv_len; struct path_trace_tlv *ptt; struct tlv_extra *extra; if (length > PATH_TRACE_MAX) { return -1; } ptt_len = length * sizeof(struct ClockIdentity); tlv_len = ptt_len + sizeof(ptt->type) + sizeof(ptt->length); extra = msg_tlv_append(m, tlv_len); if (!extra) { return -1; } ptt = (struct path_trace_tlv *) extra->tlv; ptt->type = TLV_PATH_TRACE; ptt->length = ptt_len; memcpy(ptt->cid, dad->ptl, ptt->length); ptt->cid[length - 1] = clock_identity(p->clock); return 0; } static int path_trace_ignore(struct port *p, struct ptp_message *m) { struct path_trace_tlv *ptt; struct ClockIdentity cid; struct tlv_extra *extra; int i, cnt; if (!p->path_trace_enabled) { return 0; } if (msg_type(m) != ANNOUNCE) { return 0; } TAILQ_FOREACH(extra, &m->tlv_list, list) { ptt = (struct path_trace_tlv *) extra->tlv; if (ptt->type != TLV_PATH_TRACE) { continue; } cnt = path_length(ptt); cid = clock_identity(p->clock); for (i = 0; i < cnt; i++) { if (cid_eq(&ptt->cid[i], &cid)) { return 1; } } } return 0; } static void port_stats_inc_rx(struct port *p, const struct ptp_message *msg) { p->stats.rxMsgType[msg_type(msg)]++; } static void port_stats_inc_tx(struct port *p, const struct ptp_message *msg) { p->stats.txMsgType[msg_type(msg)]++; } static int peer_prepare_and_send(struct port *p, struct ptp_message *msg, enum transport_event event) { int cnt; if (msg_pre_send(msg)) { return -1; } if (msg_unicast(msg)) { cnt = transport_sendto(p->trp, &p->fda, event, msg); } else { cnt = transport_peer(p->trp, &p->fda, event, msg); } if (cnt <= 0) { return -1; } port_stats_inc_tx(p, msg); if (msg_sots_valid(msg)) { ts_add(&msg->hwts.ts, p->tx_timestamp_offset); } return 0; } 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 == NOT_CAPABLE) { pr_debug("port %hu: setting asCapable", portnum(p)); p->asCapable = AS_CAPABLE; } return 1; not_capable: if (p->asCapable) port_nrate_initialize(p); p->asCapable = NOT_CAPABLE; return 0; } 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 (p->match_transport_specific && 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 (cid_eq(&c1, &c2)) { return 1; } return 0; } static int port_nsm_reply(struct port *p, struct ptp_message *m) { struct tlv_extra *extra; if (!p->net_sync_monitor) { return 0; } if (!p->hybrid_e2e) { return 0; } if (!msg_unicast(m)) { return 0; } TAILQ_FOREACH(extra, &m->tlv_list, list) { if (extra->tlv->type == TLV_PTPMON_REQ) { 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 (cid_eq(&cid, &pid.clockIdentity)) { /* * We are the GM, but without gmCapable set. */ return 1; } return 0; } static int port_is_ieee8021as(struct port *p) { if (p->asCapable == ALWAYS_CAPABLE) { return 0; } 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) { struct mgmt_clock_description *cd; struct management_tlv_datum *mtd; struct clock_description *desc; struct port_properties_np *ppn; struct port_stats_np *psn; struct management_tlv *tlv; struct port_ds_np *pdsnp; struct tlv_extra *extra; const char *ts_label; struct portDS *pds; uint16_t u16; uint8_t *buf; int datalen; extra = tlv_extra_alloc(); if (!extra) { pr_err("failed to allocate TLV descriptor"); return 0; } extra->tlv = (struct TLV *) rsp->management.suffix; tlv = (struct management_tlv *) rsp->management.suffix; tlv->type = TLV_MANAGEMENT; tlv->id = id; switch (id) { case TLV_NULL_MANAGEMENT: datalen = 0; break; case TLV_CLOCK_DESCRIPTION: cd = &extra->cd; buf = tlv->data; cd->clockType = (UInteger16 *) buf; buf += sizeof(*cd->clockType); *cd->clockType = clock_type(target->clock); 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; 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); break; case TLV_LOG_ANNOUNCE_INTERVAL: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = target->logAnnounceInterval; datalen = sizeof(*mtd); break; case TLV_ANNOUNCE_RECEIPT_TIMEOUT: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = target->announceReceiptTimeout; datalen = sizeof(*mtd); break; case TLV_LOG_SYNC_INTERVAL: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = target->logSyncInterval; datalen = sizeof(*mtd); break; case TLV_VERSION_NUMBER: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = target->versionNumber; datalen = sizeof(*mtd); 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); break; case TLV_LOG_MIN_PDELAY_REQ_INTERVAL: mtd = (struct management_tlv_datum *) tlv->data; mtd->val = target->logMinPdelayReqInterval; datalen = sizeof(*mtd); 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); 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; ts_label = interface_label(target->iface); ptp_text_set(&ppn->interface, ts_label); datalen = sizeof(*ppn) + ppn->interface.length; break; case TLV_PORT_STATS_NP: psn = (struct port_stats_np *)tlv->data; psn->portIdentity = target->portIdentity; psn->stats = target->stats; datalen = sizeof(*psn); break; default: /* The caller should *not* respond to this message. */ tlv_extra_recycle(extra); return 0; } if (datalen % 2) { tlv->data[datalen] = 0; datalen++; } tlv->length = sizeof(tlv->id) + datalen; rsp->header.messageLength += sizeof(*tlv) + datalen; msg_tlv_attach(rsp, extra); /* The caller can respond to this message. */ return 1; } 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, TRANS_GENERAL); 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 (tmv_is_zero(n->ingress1)) { n->ingress1 = ingress; n->origin1 = origin; return; } n->count++; if (n->count < n->max_count) { return; } if (tmv_cmp(ingress, n->ingress1) == 0) { 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->logPdelayReqInterval; 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->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; } int port_set_announce_tmo(struct port *p) { return set_tmo_random(p->fda.fd[FD_ANNOUNCE_TIMER], p->announceReceiptTimeout, p->announce_span, p->logAnnounceInterval); } int port_set_delay_tmo(struct port *p) { if (p->inhibit_delay_req) { return 0; } if (p->delayMechanism == DM_P2P) { return set_tmo_log(p->fda.fd[FD_DELAY_TIMER], 1, p->logPdelayReqInterval); } 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); } 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); } 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 message_interval_request(struct port *p, enum servo_state last_state, Integer8 sync_interval) { if (!p->msg_interval_request) return; if (last_state == SERVO_LOCKED) { p->logPdelayReqInterval = p->operLogPdelayReqInterval; p->logSyncInterval = p->operLogSyncInterval; port_tx_interval_request(p, SIGNAL_NO_CHANGE, p->logSyncInterval, SIGNAL_NO_CHANGE); port_dispatch(p, EV_MASTER_CLOCK_SELECTED, 0); } else if (sync_interval != p->operLogSyncInterval) { /* * The most likely reason for this to happen is the * master daemon re-initialized due to some fault. */ servo_reset(clock_servo(p->clock)); port_dispatch(p, EV_SYNCHRONIZATION_FAULT, 0); } } static void port_synchronize(struct port *p, uint16_t seqid, tmv_t ingress_ts, struct timestamp origin_ts, Integer64 correction1, Integer64 correction2, Integer8 sync_interval) { enum servo_state state, last_state; tmv_t t1, t1c, t2, c1, c2; port_set_sync_rx_tmo(p); t1 = timestamp_to_tmv(origin_ts); t2 = ingress_ts; c1 = correction_to_tmv(correction1); c2 = correction_to_tmv(correction2); t1c = tmv_add(t1, tmv_add(c1, c2)); switch (p->state) { case PS_UNCALIBRATED: case PS_SLAVE: monitor_sync(p->slave_event_monitor, clock_parent_identity(p->clock), seqid, t1, tmv_add(c1, c2), t2); break; default: break; } last_state = clock_servo_state(p->clock); state = clock_synchronize(p->clock, t2, t1c); switch (state) { case SERVO_UNLOCKED: port_dispatch(p, EV_SYNCHRONIZATION_FAULT, 0); if (servo_offset_threshold(clock_servo(p->clock)) != 0 && sync_interval != p->initialLogSyncInterval) { p->logPdelayReqInterval = p->logMinPdelayReqInterval; p->logSyncInterval = p->initialLogSyncInterval; port_tx_interval_request(p, SIGNAL_NO_CHANGE, SIGNAL_SET_INITIAL, SIGNAL_NO_CHANGE); } break; case SERVO_JUMP: port_dispatch(p, EV_SYNCHRONIZATION_FAULT, 0); flush_delay_req(p); 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; case SERVO_LOCKED_STABLE: message_interval_request(p, last_state, sync_interval); break; } } static void port_syfufsm_print_mismatch(struct port *p, enum syfu_event event, struct ptp_message *m) { int expected_msgtype; if (event == SYNC_MISMATCH) expected_msgtype = FOLLOW_UP; else expected_msgtype = SYNC; pr_debug("port %hu: have %s %hu, expecting %s but got %s %hu, dropping", portnum(p), msg_type_string(msg_type(p->last_syncfup)), p->last_syncfup->header.sequenceId, msg_type_string(expected_msgtype), msg_type_string(msg_type(m)), m->header.sequenceId); } /* * 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: port_syfufsm_print_mismatch(p, event, m); msg_put(p->last_syncfup); msg_get(m); p->last_syncfup = m; break; case SYNC_MATCH: break; case FUP_MISMATCH: port_syfufsm_print_mismatch(p, event, m); 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->header.sequenceId, syn->hwts.ts, m->ts.pdu, syn->header.correction, m->header.correction, m->header.logMessageInterval); msg_put(p->last_syncfup); p->syfu = SF_EMPTY; break; } break; case SF_HAVE_FUP: switch (event) { case SYNC_MISMATCH: port_syfufsm_print_mismatch(p, event, m); 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, fup->header.sequenceId, m->hwts.ts, fup->ts.pdu, m->header.correction, fup->header.correction, m->header.logMessageInterval); msg_put(p->last_syncfup); p->syfu = SF_EMPTY; break; case FUP_MISMATCH: port_syfufsm_print_mismatch(p, event, m); 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->logPdelayReqInterval : 0x7f; if (unicast_client_enabled(p) && p->unicast_master_table->peer_name) { msg->address = p->unicast_master_table->peer_addr.address; msg->header.flagField[0] |= UNICAST; } err = peer_prepare_and_send(p, msg, TRANS_EVENT); 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; } 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, TRANS_EVENT)) { 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; } TAILQ_INSERT_HEAD(&p->delay_req, msg, list); return 0; out: msg_put(msg); return -1; } int port_tx_announce(struct port *p, struct address *dst) { struct timePropertiesDS tp = clock_time_properties(p->clock); struct parent_ds *dad = clock_parent_ds(p->clock); struct ptp_message *msg; int err; if (p->inhibit_multicast_service && !dst) { return 0; } if (!port_capable(p)) { return 0; } msg = msg_allocate(); if (!msg) { return -1; } msg->hwts.type = p->timestamping; msg->header.tsmt = ANNOUNCE | p->transportSpecific; msg->header.ver = PTP_VERSION; msg->header.messageLength = sizeof(struct announce_msg); 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; if (dst) { msg->address = *dst; msg->header.flagField[0] |= UNICAST; } 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; if (p->path_trace_enabled && path_trace_append(p, msg, dad)) { pr_err("port %hu: append path trace failed", portnum(p)); } err = port_prepare_and_send(p, msg, TRANS_GENERAL); if (err) { pr_err("port %hu: send announce failed", portnum(p)); } msg_put(msg); return err; } int port_tx_sync(struct port *p, struct address *dst) { struct ptp_message *msg, *fup; int err, event; switch (p->timestamping) { case TS_SOFTWARE: case TS_LEGACY_HW: case TS_HARDWARE: event = TRANS_EVENT; break; case TS_ONESTEP: event = TRANS_ONESTEP; break; case TS_P2P1STEP: event = TRANS_P2P1STEP; break; default: return -1; } if (p->inhibit_multicast_service && !dst) { return 0; } 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; } msg->hwts.type = p->timestamping; msg->header.tsmt = SYNC | p->transportSpecific; msg->header.ver = PTP_VERSION; msg->header.messageLength = sizeof(struct sync_msg); 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 && p->timestamping != TS_P2P1STEP) { msg->header.flagField[0] |= TWO_STEP; } if (dst) { msg->address = *dst; msg->header.flagField[0] |= UNICAST; msg->header.logMessageInterval = 0x7f; } 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 || p->timestamping == TS_P2P1STEP) { 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. */ fup->hwts.type = p->timestamping; fup->header.tsmt = FOLLOW_UP | p->transportSpecific; fup->header.ver = PTP_VERSION; fup->header.messageLength = sizeof(struct follow_up_msg); 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; fup->follow_up.preciseOriginTimestamp = tmv_to_Timestamp(msg->hwts.ts); if (dst) { fup->address = *dst; fup->header.flagField[0] |= UNICAST; } if (p->follow_up_info && follow_up_info_append(fup)) { pr_err("port %hu: append fup info failed", portnum(p)); err = -1; goto out; } err = port_prepare_and_send(p, fup, TRANS_GENERAL); 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 */ 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; } void flush_last_sync(struct port *p) { if (p->syfu != SF_EMPTY) { msg_put(p->last_syncfup); p->syfu = SF_EMPTY; } } void flush_delay_req(struct port *p) { struct ptp_message *m; while ((m = TAILQ_FIRST(&p->delay_req)) != NULL) { TAILQ_REMOVE(&p->delay_req, m, list); msg_put(m); } } 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; } void port_disable(struct port *p) { int i; tc_flush(p); 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_FIRST_TIMER + i]); } /* Keep rtnl socket to get link status info. */ port_clear_fda(p, FD_RTNL); clock_fda_changed(p->clock); } 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->initialLogAnnounceInterval = config_get_int(cfg, p->name, "logAnnounceInterval"); p->logAnnounceInterval = p->initialLogAnnounceInterval; p->inhibit_announce = config_get_int(cfg, p->name, "inhibit_announce"); p->ignore_source_id = config_get_int(cfg, p->name, "ignore_source_id"); 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->match_transport_specific = !config_get_int(cfg, p->name, "ignore_transport_specific"); p->localPriority = config_get_int(cfg, p->name, "G.8275.portDS.localPriority"); p->initialLogSyncInterval = config_get_int(cfg, p->name, "logSyncInterval"); p->logSyncInterval = p->initialLogSyncInterval; p->operLogSyncInterval = config_get_int(cfg, p->name, "operLogSyncInterval"); p->logMinPdelayReqInterval = config_get_int(cfg, p->name, "logMinPdelayReqInterval"); p->logPdelayReqInterval = p->logMinPdelayReqInterval; p->operLogPdelayReqInterval = config_get_int(cfg, p->name, "operLogPdelayReqInterval"); p->neighborPropDelayThresh = config_get_int(cfg, p->name, "neighborPropDelayThresh"); p->min_neighbor_prop_delay = config_get_int(cfg, p->name, "min_neighbor_prop_delay"); if (config_get_int(cfg, p->name, "asCapable") == AS_CAPABLE_TRUE) { p->asCapable = ALWAYS_CAPABLE; } else { p->asCapable = NOT_CAPABLE; } p->inhibit_delay_req = config_get_int(cfg, p->name, "inhibit_delay_req"); if (p->inhibit_delay_req && p->asCapable != ALWAYS_CAPABLE) { pr_err("inhibit_delay_req can only be set when asCapable == 'true'."); return -1; } 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->iface, &p->fda, p->timestamping)) goto no_tropen; for (i = 0; i < N_TIMER_FDS; i++) { p->fda.fd[FD_FIRST_TIMER + i] = fd[i]; } if (port_set_announce_tmo(p)) { goto no_tmo; } if (unicast_client_enabled(p) && unicast_client_set_tmo(p)) { goto no_tmo; } /* No need to open rtnl socket on UDS port. */ if (transport_type(p->trp) != TRANS_UDS) { /* * The delay timer is usually started when the device * transitions to PS_LISTENING. But, we are skipping the state * when BMCA == 'noop'. So, start the timer here. */ if (p->bmca == BMCA_NOOP) { port_set_delay_tmo(p); } if (p->fda.fd[FD_RTNL] == -1) { p->fda.fd[FD_RTNL] = rtnl_open(); } if (p->fda.fd[FD_RTNL] >= 0) { const char *ifname = interface_name(p->iface); rtnl_link_query(p->fda.fd[FD_RTNL], ifname); } } 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_FIRST_TIMER); res = transport_open(p->trp, p->iface, &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. */ int process_announce(struct port *p, struct ptp_message *m) { int result = 0; if (m->announce.stepsRemoved >= clock_max_steps_removed(p->clock)) { 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) { int err, nsm, saved_seqnum_sync; struct ptp_message *msg; nsm = port_nsm_reply(p, m); if (!nsm && 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; msg->delay_resp.receiveTimestamp = tmv_to_Timestamp(m->hwts.ts); msg->delay_resp.requestingPortIdentity = m->header.sourcePortIdentity; if (p->hybrid_e2e && msg_unicast(m)) { msg->address = m->address; msg->header.flagField[0] |= UNICAST; msg->header.logMessageInterval = 0x7f; } if (nsm && net_sync_resp_append(p, msg)) { pr_err("port %hu: append NSM failed", portnum(p)); err = -1; goto out; } err = port_prepare_and_send(p, msg, TRANS_GENERAL); if (err) { pr_err("port %hu: send delay response failed", portnum(p)); goto out; } if (nsm) { saved_seqnum_sync = p->seqnum.sync; p->seqnum.sync = m->header.sequenceId; err = port_tx_sync(p, &m->address); p->seqnum.sync = saved_seqnum_sync; } out: msg_put(msg); return err; } void process_delay_resp(struct port *p, struct ptp_message *m) { struct delay_resp_msg *rsp = &m->delay_resp; struct ptp_message *req; tmv_t c3, t3, t4, t4c; if (p->state != PS_UNCALIBRATED && p->state != PS_SLAVE) { return; } if (!pid_eq(&rsp->requestingPortIdentity, &p->portIdentity)) { return; } if (check_source_identity(p, m)) { return; } TAILQ_FOREACH(req, &p->delay_req, list) { if (rsp->hdr.sequenceId == ntohs(req->delay_req.hdr.sequenceId)) { break; } } if (!req) { return; } c3 = correction_to_tmv(m->header.correction); t3 = req->hwts.ts; t4 = timestamp_to_tmv(m->ts.pdu); t4c = tmv_sub(t4, c3); monitor_delay(p->slave_event_monitor, clock_parent_identity(p->clock), m->header.sequenceId, t3, c3, t4); clock_path_delay(p->clock, t3, t4c); TAILQ_REMOVE(&p->delay_req, req, list); msg_put(req); if (p->logMinDelayReqInterval == rsp->hdr.logMessageInterval) { return; } if (msg_unicast(m)) { /* 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); port_set_delay_tmo(p); } void process_follow_up(struct port *p, struct ptp_message *m) { enum syfu_event event; 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; } if (check_source_identity(p, m)) { 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); } int process_pdelay_req(struct port *p, struct ptp_message *m) { struct ptp_message *rsp, *fup; enum transport_event event; int err; switch (p->timestamping) { case TS_SOFTWARE: case TS_LEGACY_HW: case TS_HARDWARE: case TS_ONESTEP: event = TRANS_EVENT; break; case TS_P2P1STEP: event = TRANS_P2P1STEP; break; default: return -1; } 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 - We do not have any fraction nanoseconds for the correction * fields, neither in the response or the follow up. */ if (p->timestamping == TS_P2P1STEP) { rsp->header.correction = m->header.correction; rsp->header.correction += p->tx_timestamp_offset; rsp->header.correction += p->rx_timestamp_offset; } else { rsp->header.flagField[0] |= TWO_STEP; rsp->pdelay_resp.requestReceiptTimestamp = tmv_to_Timestamp(m->hwts.ts); } rsp->pdelay_resp.requestingPortIdentity = m->header.sourcePortIdentity; if (msg_unicast(m)) { rsp->address = m->address; rsp->header.flagField[0] |= UNICAST; } err = peer_prepare_and_send(p, rsp, event); if (err) { pr_err("port %hu: send peer delay response failed", portnum(p)); goto out; } if (p->timestamping == TS_P2P1STEP) { goto out; } else if (msg_sots_missing(rsp)) { pr_err("missing timestamp on transmitted peer delay response"); err = -1; goto out; } /* * Send the follow up message right away. */ 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; fup->pdelay_resp_fup.responseOriginTimestamp = tmv_to_Timestamp(rsp->hwts.ts); if (msg_unicast(m)) { fup->address = m->address; fup->header.flagField[0] |= UNICAST; } err = peer_prepare_and_send(p, fup, TRANS_GENERAL); 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 = req->hwts.ts; t4 = 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)); if (p->follow_up_info) port_nrate_calculate(p, t3c, t4); 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->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; } 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; } 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); } void process_sync(struct port *p, struct ptp_message *m) { enum syfu_event event; 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; } if (check_source_identity(p, m)) { return; } if (!msg_unicast(m) && 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->header.sequenceId, m->hwts.ts, m->ts.pdu, m->header.correction, 0, m->header.logMessageInterval); 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); } if (p->fda.fd[FD_RTNL] >= 0) { rtnl_close(p->fda.fd[FD_RTNL]); } unicast_client_cleanup(p); unicast_service_cleanup(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) { int (*dscmp)(struct dataset *a, struct dataset *b); struct foreign_clock *fc; struct ptp_message *tmp; dscmp = clock_dscmp(p->clock); p->best = NULL; if (p->master_only) return p->best; LIST_FOREACH(fc, &p->foreign_masters, list) { tmp = TAILQ_FIRST(&fc->messages); if (!tmp) continue; announce_to_dataset(tmp, p, &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]); /* Leave FD_UNICAST_REQ_TIMER running. */ 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: if (!p->inhibit_announce) { 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]); /* Leave FD_UNICAST_REQ_TIMER running. */ switch (next) { case PS_INITIALIZING: break; case PS_FAULTY: case PS_DISABLED: port_disable(p); break; case PS_LISTENING: port_set_announce_tmo(p); port_set_delay_tmo(p); break; case PS_PRE_MASTER: port_set_qualification_tmo(p); break; case PS_MASTER: case PS_GRAND_MASTER: if (!p->inhibit_announce) { 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; }; } void port_dispatch(struct port *p, enum fsm_event event, int mdiff) { p->dispatch(p, event, mdiff); } static void bc_dispatch(struct port *p, enum fsm_event event, int mdiff) { if (clock_slave_only(p->clock)) { if (event == EV_RS_MASTER || event == EV_RS_GRAND_MASTER) { port_slave_priority_warning(p); } } if (!port_state_update(p, event, mdiff)) { return; } if (p->delayMechanism == DM_P2P) { port_p2p_transition(p, p->state); } else { port_e2e_transition(p, p->state); } if (p->jbod && p->state == PS_UNCALIBRATED) { if (clock_switch_phc(p->clock, p->phc_index)) { p->last_fault_type = FT_SWITCH_PHC; port_dispatch(p, EV_FAULT_DETECTED, 0); return; } clock_sync_interval(p->clock, p->log_sync_interval); } } void port_link_status(void *ctx, int linkup, int ts_index) { char ts_label[MAX_IFNAME_SIZE + 1] = {0}; int link_state, required_modes; const char *old_ts_label; struct port *p = ctx; link_state = linkup ? LINK_UP : LINK_DOWN; if (p->link_status & link_state) { p->link_status = link_state; } else { p->link_status = link_state | LINK_STATE_CHANGED; pr_notice("port %hu: link %s", portnum(p), linkup ? "up" : "down"); } /* ts_label changed */ old_ts_label = interface_label(p->iface); if (if_indextoname(ts_index, ts_label) && strcmp(old_ts_label, ts_label)) { interface_set_label(p->iface, ts_label); p->link_status |= TS_LABEL_CHANGED; pr_notice("port %hu: ts label changed to %s", portnum(p), ts_label); } /* Both link down/up and change ts_label may change phc index. */ if (p->link_status & LINK_UP && (p->link_status & LINK_STATE_CHANGED || p->link_status & TS_LABEL_CHANGED)) { interface_get_tsinfo(p->iface); /* Only switch phc with HW time stamping mode */ if (interface_tsinfo_valid(p->iface) && interface_phc_index(p->iface) >= 0) { required_modes = clock_required_modes(p->clock); if (!interface_tsmodes_supported(p->iface, required_modes)) { pr_err("interface '%s' does not support requested " "timestamping mode, set link status down by force.", interface_label(p->iface)); p->link_status = LINK_DOWN | LINK_STATE_CHANGED; } else if (p->phc_index != interface_phc_index(p->iface)) { p->phc_index = interface_phc_index(p->iface); if (clock_switch_phc(p->clock, p->phc_index)) { p->last_fault_type = FT_SWITCH_PHC; port_dispatch(p, EV_FAULT_DETECTED, 0); return; } clock_sync_interval(p->clock, p->log_sync_interval); } } } /* * A port going down can affect the BMCA result. * Force a state decision event. */ if (p->link_status & LINK_DOWN) clock_set_sde(p->clock, 1); } enum fsm_event port_event(struct port *p, int fd_index) { return p->event(p, fd_index); } static enum fsm_event bc_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); } /* * Clear out the event returned by poll(). It is only cleared * in port_*_transition(). But, when BMCA == 'noop', there is no * state transition. So, it won't be cleared anywhere else. */ if (p->bmca == BMCA_NOOP) { port_clr_tmo(p->fda.fd[FD_SYNC_RX_TIMER]); } if (p->inhibit_announce) { port_clr_tmo(p->fda.fd[FD_ANNOUNCE_TIMER]); } else { port_set_announce_tmo(p); } delay_req_prune(p); if (clock_slave_only(p->clock) && p->delayMechanism != DM_P2P && port_renew_transport(p)) { return EV_FAULT_DETECTED; } if (p->inhibit_announce) { return EV_NONE; } return EV_ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES; case FD_DELAY_TIMER: pr_debug("port %hu: delay timeout", portnum(p)); port_set_delay_tmo(p); delay_req_prune(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, NULL) ? 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, NULL) ? EV_FAULT_DETECTED : EV_NONE; case FD_UNICAST_SRV_TIMER: pr_debug("port %hu: unicast service timeout", portnum(p)); return unicast_service_timer(p) ? EV_FAULT_DETECTED : EV_NONE; case FD_UNICAST_REQ_TIMER: pr_debug("port %hu: unicast request timeout", portnum(p)); return unicast_client_timer(p) ? EV_FAULT_DETECTED : EV_NONE; case FD_RTNL: pr_debug("port %hu: received link status notification", portnum(p)); rtnl_link_status(fd, p->name, port_link_status, p); if (p->link_status == (LINK_UP | LINK_STATE_CHANGED)) return EV_FAULT_CLEARED; else if ((p->link_status == (LINK_DOWN | LINK_STATE_CHANGED)) || (p->link_status & TS_LABEL_CHANGED)) return EV_FAULT_DETECTED; else return 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 -EPROTO: pr_debug("port %hu: ignoring message", portnum(p)); break; } msg_put(msg); return EV_NONE; } port_stats_inc_rx(p, msg); if (port_ignore(p, msg)) { msg_put(msg); return EV_NONE; } if (msg_sots_missing(msg) && !(p->timestamping == TS_P2P1STEP && msg_type(msg) == PDELAY_REQ)) { pr_err("port %hu: received %s without timestamp", portnum(p), msg_type_string(msg_type(msg))); 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, tmv_to_nanoseconds(msg->hwts.ts)); } 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: if (process_signaling(p, msg)) { event = EV_FAULT_DETECTED; } 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, TRANS_GENERAL, msg); if (cnt <= 0) { return -1; } port_stats_inc_tx(p, msg); return 0; } int port_forward_to(struct port *p, struct ptp_message *msg) { int cnt; cnt = transport_sendto(p->trp, &p->fda, TRANS_GENERAL, msg); if (cnt < 0) { return cnt; } else if (!cnt) { return -EIO; } port_stats_inc_tx(p, msg); return 0; } int port_prepare_and_send(struct port *p, struct ptp_message *msg, enum transport_event event) { int cnt; if (msg_pre_send(msg)) { return -1; } if (msg_unicast(msg)) { cnt = transport_sendto(p->trp, &p->fda, event, msg); } else { cnt = transport_send(p->trp, &p->fda, event, msg); } if (cnt <= 0) { return -1; } port_stats_inc_tx(p, msg); 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_link_status_get(struct port *p) { return !!(p->link_status & LINK_UP); } 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 management_error_status *mes; struct management_tlv *mgt; struct ptp_message *msg; struct tlv_extra *extra; int err = 0; mgt = (struct management_tlv *) req->management.suffix; msg = port_management_reply(pid, ingress, req); if (!msg) { return -1; } extra = msg_tlv_append(msg, sizeof(*mes)); if (!extra) { msg_put(msg); return -ENOMEM; } mes = (struct management_error_status *) extra->tlv; mes->type = TLV_MANAGEMENT_ERROR_STATUS; mes->length = 8; mes->error = error_id; mes->id = mgt->id; err = port_prepare_and_send(ingress, msg, TRANS_GENERAL); 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; msg = msg_allocate(); if (!msg) return NULL; msg->hwts.type = ingress->timestamping; msg->header.tsmt = MANAGEMENT | ingress->transportSpecific; msg->header.ver = PTP_VERSION; msg->header.messageLength = sizeof(struct management_msg); 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; 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; if (msg_pre_send(msg)) goto err; clock_send_notification(p->clock, msg, event); err: msg_put(msg); } struct port *port_open(const char *phc_device, int phc_index, enum timestamp_type timestamping, int number, struct interface *interface, struct clock *clock) { enum clock_type type = clock_type(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)); TAILQ_INIT(&p->tc_transmitted); switch (type) { case CLOCK_TYPE_ORDINARY: case CLOCK_TYPE_BOUNDARY: p->dispatch = bc_dispatch; p->event = bc_event; break; case CLOCK_TYPE_P2P: p->dispatch = p2p_dispatch; p->event = p2p_event; break; case CLOCK_TYPE_E2E: p->dispatch = e2e_dispatch; p->event = e2e_event; break; case CLOCK_TYPE_MANAGEMENT: goto err_port; } p->phc_index = phc_index; p->jbod = config_get_int(cfg, interface_name(interface), "boundary_clock_jbod"); transport = config_get_int(cfg, interface_name(interface), "network_transport"); p->master_only = config_get_int(cfg, interface_name(interface), "masterOnly"); p->bmca = config_get_int(cfg, interface_name(interface), "BMCA"); if (p->bmca == BMCA_NOOP && transport != TRANS_UDS) { if (p->master_only) { p->state_machine = designated_master_fsm; } else if (clock_slave_only(clock)) { p->state_machine = designated_slave_fsm; } else { pr_err("Please enable at least one of masterOnly or slaveOnly when BMCA == noop.\n"); goto err_port; } } else { p->state_machine = clock_slave_only(clock) ? ptp_slave_fsm : ptp_fsm; } if (transport == TRANS_UDS) { ; /* UDS cannot have a PHC. */ } else if (!interface_tsinfo_valid(interface)) { pr_warning("port %d: get_ts_info not supported", number); } else if (phc_index >= 0 && phc_index != interface_phc_index(interface)) { if (p->jbod) { pr_warning("port %d: just a bunch of devices", number); p->phc_index = interface_phc_index(interface); } else if (phc_device) { pr_warning("port %d: taking %s from the command line, " "not the attached ptp%d", number, phc_device, interface_phc_index(interface)); p->phc_index = 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_phc_index(interface)); goto err_port; } } p->name = interface_name(interface); p->iface = interface; p->asymmetry = config_get_int(cfg, p->name, "delayAsymmetry"); p->asymmetry <<= 16; p->announce_span = transport == TRANS_UDS ? 0 : ANNOUNCE_SPAN; 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->msg_interval_request = config_get_int(cfg, p->name, "msg_interval_request"); p->net_sync_monitor = config_get_int(cfg, p->name, "net_sync_monitor"); p->path_trace_enabled = config_get_int(cfg, p->name, "path_trace_enabled"); p->tc_spanning_tree = config_get_int(cfg, p->name, "tc_spanning_tree"); p->rx_timestamp_offset = config_get_int(cfg, p->name, "ingressLatency"); p->rx_timestamp_offset <<= 16; p->tx_timestamp_offset = config_get_int(cfg, p->name, "egressLatency"); p->tx_timestamp_offset <<= 16; p->link_status = LINK_UP; 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; p->slave_event_monitor = clock_slave_monitor(clock); if (number && unicast_client_initialize(p)) { goto err_transport; } if (unicast_client_enabled(p) && config_set_section_int(cfg, p->name, "hybrid_e2e", 1)) { goto err_uc_client; } if (number && unicast_service_initialize(p)) { goto err_uc_client; } p->hybrid_e2e = config_get_int(cfg, p->name, "hybrid_e2e"); if (number && type == CLOCK_TYPE_P2P && p->delayMechanism != DM_P2P) { pr_err("port %d: P2P TC needs P2P ports", number); goto err_uc_service; } if (number && type == CLOCK_TYPE_E2E && p->delayMechanism != DM_E2E) { pr_err("port %d: E2E TC needs E2E ports", number); goto err_uc_service; } if (p->hybrid_e2e && p->delayMechanism != DM_E2E) { pr_warning("port %d: hybrid_e2e only works with E2E", number); } if (p->net_sync_monitor && !p->hybrid_e2e) { pr_warning("port %d: net_sync_monitor needs hybrid_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_uc_service; } 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_uc_service: unicast_service_cleanup(p); err_uc_client: unicast_client_cleanup(p); err_transport: transport_destroy(p->trp); err_port: free(p); return NULL; } enum port_state port_state(struct port *port) { return port->state; } int port_state_update(struct port *p, enum fsm_event event, int mdiff) { enum port_state next = p->state_machine(p->state, event, mdiff); if (PS_FAULTY == next) { struct fault_interval i; fault_interval(p, last_fault_type(p), &i); if (clear_fault_asap(&i)) { pr_notice("port %hu: clearing fault immediately", portnum(p)); next = p->state_machine(next, EV_FAULT_CLEARED, 0); } } if (PS_INITIALIZING == next) { /* * 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); } if (port_initialize(p)) { event = EV_FAULT_DETECTED; } else { event = EV_INIT_COMPLETE; } next = p->state_machine(next, event, 0); } if (next != p->state) { port_show_transition(p, next, event); p->state = next; port_notify_event(p, NOTIFY_PORT_STATE); unicast_client_state_changed(p); return 1; } return 0; } enum bmca_select port_bmca(struct port *p) { return p->bmca; } linuxptp-3.1.1/port.h000066400000000000000000000270631407046267700145610ustar00rootroot00000000000000/** * @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; /** The port identity that matches any port. */ extern const struct PortIdentity wildcard_pid; /** * 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. */ void 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, negative errno value 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 One of the @ref transport_event enumeration values. */ int port_prepare_and_send(struct port *p, struct ptp_message *msg, enum transport_event 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); /** * Obtain the link status of a port. * @param p A port instance. * @return One (1) if the link is up, zero otherwise. */ int port_link_status_get(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_device The name of PHC device as found on the command line. * @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(const char *phc_device, int phc_index, enum timestamp_type timestamping, int number, struct interface *interface, struct clock *clock); struct ptp_message *port_signaling_construct(struct port *p, const struct PortIdentity *tpid); /** * 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); /** * Update a port's current state based on a given event. * @param p 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 One (1) if the port state has changed, zero otherwise. */ int port_state_update(struct port *p, enum fsm_event event, int mdiff); /** * 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. */ void fault_interval(struct port *port, enum fault_type ft, struct fault_interval *i); /** * Obtain the BMCA type of the port. * * @param port A port instance. * @return bmca type. */ enum bmca_select port_bmca(struct port *p); /** * Release all of the memory in the TC transmit descriptor cache. */ void tc_cleanup(void); #endif linuxptp-3.1.1/port_private.h000066400000000000000000000147451407046267700163160ustar00rootroot00000000000000/** * @file port_private.h * @note Copyright (C) 2018 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_PRIVATE_H #define HAVE_PORT_PRIVATE_H #include #include "as_capable.h" #include "clock.h" #include "fsm.h" #include "monitor.h" #include "msg.h" #include "tmv.h" #define NSEC2SEC 1000000000LL enum syfu_state { SF_EMPTY, SF_HAVE_SYNC, SF_HAVE_FUP, }; enum link_state { LINK_DOWN = (1<<0), LINK_UP = (1<<1), LINK_STATE_CHANGED = (1<<3), TS_LABEL_CHANGED = (1<<4), }; struct nrate_estimator { double ratio; tmv_t origin1; tmv_t ingress1; unsigned int max_count; unsigned int count; int ratio_valid; }; struct tc_txd { TAILQ_ENTRY(tc_txd) list; struct ptp_message *msg; tmv_t residence; int ingress_port; }; struct port { LIST_ENTRY(port) list; const char *name; struct interface *iface; struct clock *clock; struct transport *trp; enum timestamp_type timestamping; struct fdarray fda; int fault_fd; int phc_index; void (*dispatch)(struct port *p, enum fsm_event event, int mdiff); enum fsm_event (*event)(struct port *p, int fd_index); int jbod; struct foreign_clock *best; enum syfu_state syfu; struct ptp_message *last_syncfup; TAILQ_HEAD(delay_req, 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 signaling; 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; enum port_state (*state_machine)(enum port_state state, enum fsm_event event, int mdiff); int bmca; int inhibit_announce; int ignore_source_id; int inhibit_delay_req; /* portDS */ struct PortIdentity portIdentity; enum port_state state; /*portState*/ Integer64 asymmetry; enum as_capable asCapable; Integer8 logMinDelayReqInterval; TimeInterval peerMeanPathDelay; Integer8 initialLogAnnounceInterval; Integer8 logAnnounceInterval; UInteger8 announceReceiptTimeout; int announce_span; UInteger8 syncReceiptTimeout; UInteger8 transportSpecific; UInteger8 localPriority; Integer8 initialLogSyncInterval; Integer8 operLogSyncInterval; Integer8 logSyncInterval; Enumeration8 delayMechanism; Integer8 logMinPdelayReqInterval; Integer8 operLogPdelayReqInterval; Integer8 logPdelayReqInterval; UInteger32 neighborPropDelayThresh; int follow_up_info; int freq_est_interval; int hybrid_e2e; int master_only; int match_transport_specific; int msg_interval_request; int min_neighbor_prop_delay; int net_sync_monitor; int path_trace_enabled; int tc_spanning_tree; Integer64 rx_timestamp_offset; Integer64 tx_timestamp_offset; int unicast_req_duration; enum link_state link_status; struct fault_interval flt_interval_pertype[FT_CNT]; enum fault_type last_fault_type; unsigned int versionNumber; /*UInteger4*/ struct PortStats stats; /* foreignMasterDS */ LIST_HEAD(fm, foreign_clock) foreign_masters; /* TC book keeping */ TAILQ_HEAD(tct, tc_txd) tc_transmitted; /* unicast client mode */ struct unicast_master_table *unicast_master_table; /* unicast service mode */ struct unicast_service *unicast_service; int inhibit_multicast_service; /* slave event monitoring */ struct monitor *slave_event_monitor; }; #define portnum(p) (p->portIdentity.portNumber) void e2e_dispatch(struct port *p, enum fsm_event event, int mdiff); enum fsm_event e2e_event(struct port *p, int fd_index); void p2p_dispatch(struct port *p, enum fsm_event event, int mdiff); enum fsm_event p2p_event(struct port *p, int fd_index); int clear_fault_asap(struct fault_interval *faint); void delay_req_prune(struct port *p); void fc_clear(struct foreign_clock *fc); void flush_delay_req(struct port *p); void flush_last_sync(struct port *p); int port_capable(struct port *p); int port_clr_tmo(int fd); int port_delay_request(struct port *p); void port_disable(struct port *p); int port_initialize(struct port *p); int port_is_enabled(struct port *p); void port_link_status(void *ctx, int index, int linkup); int port_set_announce_tmo(struct port *p); int port_set_delay_tmo(struct port *p); int port_set_qualification_tmo(struct port *p); void port_show_transition(struct port *p, enum port_state next, enum fsm_event event); struct ptp_message *port_signaling_uc_construct(struct port *p, struct address *address, struct PortIdentity *tpid); int port_tx_announce(struct port *p, struct address *dst); int port_tx_interval_request(struct port *p, Integer8 announceInterval, Integer8 timeSyncInterval, Integer8 linkDelayInterval); int port_tx_sync(struct port *p, struct address *dst); int process_announce(struct port *p, struct ptp_message *m); void process_delay_resp(struct port *p, struct ptp_message *m); void process_follow_up(struct port *p, struct ptp_message *m); int process_pdelay_req(struct port *p, struct ptp_message *m); int process_pdelay_resp(struct port *p, struct ptp_message *m); void process_pdelay_resp_fup(struct port *p, struct ptp_message *m); int process_signaling(struct port *p, struct ptp_message *m); void process_sync(struct port *p, struct ptp_message *m); int source_pid_eq(struct ptp_message *m1, struct ptp_message *m2); void ts_add(tmv_t *ts, Integer64 correction); #endif linuxptp-3.1.1/port_signaling.c000066400000000000000000000127571407046267700166130ustar00rootroot00000000000000/** * @file port_signaling.c * @brief Implements signaling messages * @note Copyright (C) 2018 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-1335 USA. */ #include "port.h" #include "port_private.h" #include "print.h" #include "unicast_client.h" #include "unicast_service.h" const struct PortIdentity wildcard_pid = { .clockIdentity = { {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} }, .portNumber = 0xffff, }; struct ptp_message *port_signaling_construct(struct port *p, const struct PortIdentity *tpid) { struct ptp_message *msg; msg = msg_allocate(); if (!msg) { return NULL; } msg->hwts.type = p->timestamping; msg->header.tsmt = SIGNALING | p->transportSpecific; msg->header.ver = PTP_VERSION; msg->header.messageLength = sizeof(struct signaling_msg); msg->header.domainNumber = clock_domain_number(p->clock); msg->header.sourcePortIdentity = p->portIdentity; msg->header.sequenceId = p->seqnum.signaling++; msg->header.control = CTL_OTHER; msg->header.logMessageInterval = 0x7F; msg->signaling.targetPortIdentity = *tpid; return msg; } struct ptp_message *port_signaling_uc_construct(struct port *p, struct address *address, struct PortIdentity *tpid) { struct ptp_message *msg; msg = port_signaling_construct(p, tpid); if (!msg) { return NULL; } msg->header.flagField[0] |= UNICAST; msg->address = *address; return msg; } static int8_t set_interval(int8_t current_interval, int8_t new_interval, int8_t initial_interval) { switch (new_interval) { case SIGNAL_NO_CHANGE: return current_interval; case SIGNAL_SET_INITIAL: return initial_interval; default: return new_interval; } } static int process_interval_request(struct port *p, struct msg_interval_req_tlv *r) { p->logAnnounceInterval = set_interval(p->logAnnounceInterval, r->announceInterval, p->initialLogAnnounceInterval); p->logSyncInterval = set_interval(p->logSyncInterval, r->timeSyncInterval, p->initialLogSyncInterval); p->logPdelayReqInterval = set_interval(p->logPdelayReqInterval, r->linkDelayInterval, p->logMinPdelayReqInterval); return 0; } int process_signaling(struct port *p, struct ptp_message *m) { struct tlv_extra *extra; struct msg_interval_req_tlv *r; int err = 0, result; 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; } /* Ignore signaling messages not addressed to this port. */ if (!pid_eq(&m->signaling.targetPortIdentity, &p->portIdentity) && !pid_eq(&m->signaling.targetPortIdentity, &wildcard_pid)) { return 0; } TAILQ_FOREACH(extra, &m->tlv_list, list) { switch (extra->tlv->type) { case TLV_REQUEST_UNICAST_TRANSMISSION: result = unicast_service_add(p, m, extra); switch (result) { case SERVICE_GRANTED: err = unicast_service_grant(p, m, extra); break; case SERVICE_DENIED: err = unicast_service_deny(p, m, extra); break; case SERVICE_DISABLED: default: break; } break; case TLV_GRANT_UNICAST_TRANSMISSION: unicast_client_grant(p, m, extra); break; case TLV_CANCEL_UNICAST_TRANSMISSION: err = unicast_client_cancel(p, m, extra); unicast_service_remove(p, m, extra); break; case TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION: break; case TLV_ORGANIZATION_EXTENSION: r = (struct msg_interval_req_tlv *) extra->tlv; if (0 == memcmp(r->id, ieee8021_id, sizeof(ieee8021_id)) && r->subtype[0] == 0 && r->subtype[1] == 0 && r->subtype[2] == 2) err = process_interval_request(p, r); break; } } return err; } int port_tx_interval_request(struct port *p, Integer8 announceInterval, Integer8 timeSyncInterval, Integer8 linkDelayInterval) { struct msg_interval_req_tlv *mir; struct PortIdentity tpid; struct ptp_message *msg; struct tlv_extra *extra; int err; if (!port_capable(p)) { return 0; } memset(&tpid, 0xff, sizeof(tpid)); msg = port_signaling_construct(p, &tpid); if (!msg) { return -1; } extra = msg_tlv_append(msg, sizeof(*mir)); if (!extra) { err = -1; goto out; } mir = (struct msg_interval_req_tlv *) extra->tlv; mir->type = TLV_ORGANIZATION_EXTENSION; mir->length = sizeof(*mir) - sizeof(mir->type) - sizeof(mir->length); memcpy(mir->id, ieee8021_id, sizeof(ieee8021_id)); mir->subtype[2] = 2; mir->timeSyncInterval = timeSyncInterval; mir->announceInterval = announceInterval; mir->linkDelayInterval = linkDelayInterval; mir->flags = 0; err = port_prepare_and_send(p, msg, TRANS_GENERAL); if (err) { pr_err("port %hu: send signaling failed", portnum(p)); } out: msg_put(msg); return err; } linuxptp-3.1.1/pqueue.c000066400000000000000000000054231407046267700150700ustar00rootroot00000000000000/** * @file pqueue.c * @brief Implements a priority queue. * @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-1335 USA. */ #include #include #include #include "pqueue.h" #define parent(x) (((1 + (x)) >> 1) - 1) #define left(x) (((1 + (x)) << 1) - 1) #define right(x) (((1 + (x)) << 1)) struct pqueue { int len; int max; int (*cmp)(void *a, void *b); void **data; }; static int pq_greater(struct pqueue *q, int a, int b) { return q->cmp(q->data[a], q->data[b]) > 0 ? 1 : 0; } static void heapify(struct pqueue *q, int index) { int i_max = index; int left = left(index); int right = right(index); if (left < q->len) { if (pq_greater(q, left, i_max)) i_max = left; } if (right < q->len && pq_greater(q, right, i_max)) { i_max = right; } if (i_max != index) { void *tmp = q->data[index]; q->data[index] = q->data[i_max]; q->data[i_max] = tmp; heapify(q, i_max); } } /* public methods */ struct pqueue *pqueue_create(int max_length, int (*compare)(void *a, void *b)) { struct pqueue *q = calloc(1, sizeof(*q)); if (!q) { return NULL; } q->len = 0; q->max = max_length; q->cmp = compare; q->data = calloc(max_length, sizeof(void *)); if (!q->data) { free(q); return NULL; } return q; } void pqueue_destroy(struct pqueue *q) { free(q->data); free(q); } void *pqueue_extract(struct pqueue *q) { void *data; if (!q->len) { return NULL; } data = q->data[0]; q->data[0] = q->data[q->len - 1]; q->len--; heapify(q, 0); return data; } int pqueue_insert(struct pqueue *q, void *d) { int index; if (q->len >= q->max) { void **buf = realloc(q->data, 2 * q->max * sizeof(void *)); if (buf) { q->data = buf; q->max *= 2; } else { return -ENOMEM; } } index = q->len; q->len++; while (index && (q->cmp(q->data[parent(index)], d) < 0)) { q->data[index] = q->data[parent(index)]; index = parent(index); } q->data[index] = d; return 0; } int pqueue_length(struct pqueue *q) { return q->len; } void *pqueue_peek(struct pqueue *q) { return q->len ? q->data[0] : (void *) 0; } linuxptp-3.1.1/pqueue.h000066400000000000000000000023031407046267700150670ustar00rootroot00000000000000/** * @file pqueue.h * @brief Implements a priority queue. * @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-1335 USA. */ #ifndef HAVE_PQUEUE_H #define HAVE_PQUEUE_H struct pqueue; struct pqueue *pqueue_create(int max_length, int (*compare)(void *a, void *b)); void pqueue_destroy(struct pqueue *q); void *pqueue_extract(struct pqueue *q); int pqueue_insert(struct pqueue *q, void *d); int pqueue_length(struct pqueue *q); void *pqueue_peek(struct pqueue *q); #endif linuxptp-3.1.1/print.c000066400000000000000000000041131407046267700147130ustar00rootroot00000000000000/** * @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; static const char *message_tag; void print_set_progname(const char *name) { progname = name; } void print_set_tag(const char *tag) { message_tag = tag; } 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[%lld.%03ld]: %s%s%s\n", progname ? progname : "", (long long)ts.tv_sec, ts.tv_nsec / 1000000, message_tag ? message_tag : "", message_tag ? " " : "", buf); fflush(f); } if (use_syslog) { syslog(level, "[%lld.%03ld] %s%s%s", (long long)ts.tv_sec, ts.tv_nsec / 1000000, message_tag ? message_tag : "", message_tag ? " " : "", buf); } } linuxptp-3.1.1/print.h000066400000000000000000000043241407046267700147240ustar00rootroot00000000000000/** * @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 #include "util.h" #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_tag(const char *tag); 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-3.1.1/ptp4l.8000066400000000000000000000755671407046267700145740ustar00rootroot00000000000000.TH PTP4l 8 "April 2018" "linuxptp" .SH NAME ptp4l - PTP Boundary/Ordinary/Transparent Clock .SH SYNOPSIS .B ptp4l [ .B \-AEP246HSLmqsv ] [ .BI \-f " config" ] [ .BI \-p " phc-device" ] [ .BI \-l " print-level" ] [ .BI \-i " interface" ] [ .I long-options ] .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), Ordinary Clock (OC), and Transparent Clock (TC). .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" (This option is deprecated.) Before Linux kernel v3.5 there was no way to discover the PHC device associated with a network interface. This option specifies the PHC device (e.g. /dev/ptp0) to be used when running on legacy kernels. .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 LONG OPTIONS Each and every configuration file option (see below) may also appear as a "long" style command line argument. For example, the slaveOnly option may be set using either of these two forms. .RS \f(CW\-\-slaveOnly 1 \-\-slaveOnly=1\fP .RE Option values given on the command line override values in the global section of the configuration file. .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. There are three different section types. .TP .B 1. 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. .TP .B 2. Port sections give 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. .TP .B 3. Tables for configuring unicast discovery begin with .B \%[unicast_master_table]. See UNICAST DISCOVERY OPTIONS, below. .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 operLogSyncInterval The Sync message interval to be requested once the clock enters the SERVO_LOCKED_STABLE state. If the 'msg_interval_request' option is set, then the local slave port will request the remote master to switch to the given message rate via a signaling message containing a Message interval request TLV. This option is specified as a power of two in seconds, and default value 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 operLogPdelayReqInterval The Pdelay Request messages interval to be used once the clock enters the SERVO_LOCKED_STABLE state. If the 'msg_interval_request' option is set, then the local slave port will adopt this rate when the local clock enters the "locked stable" state. This option is specified as a power of two in seconds, and the default value is 0 (1 second). .TP .B inhibit_delay_req Don't send any delay requests. This will need the asCapable config option to be set to 'true'. This is useful when running as a designated master who does not need to calculate offset from slave. The default is 0 (disabled). .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 ignore_transport_specific By default, incoming messages are dropped if their transportSpecific field does not match the configured value. However, many of transports specified in the 1588 standard mandate ignoring this field. Moreover, some equipment is known to set the reserved bits. Configuring this option as 1 causes this field to be ignored completely on receive. 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 inhibit_multicast_service Some unicast mode profiles insist that no multicast message are ever transmitted. Setting this option inhibits multicast transmission. The default is 0 (mutlicast enabled). .TP .B net_sync_monitor Enables the NetSync Monitor (NSM) protocol. The NSM protocol allows a station to measure how well another node is synchronized. The monitor sends a unicast delay request to the node, which replies unconditionally with unicast delay response, sync, and follow up messages. If the monitor is synchronized to the GM, it can use the time stamps in the message to estimate the node's offset. This option requires that the 'hybrid_e2e' option be enabled as well. The default is 0 (disabled). .TP .B unicast_listen When enabled, this option allows the port to grant unicast message contracts. Incoming requests for will be granted limited only by the amount of memory available. The default is 0 (disabled). .TP .B unicast_master_table When set to a positive integer, this option specifies the table id to be used for unicast discovery. Each table lives in its own section and has a unique, positive numerical ID. Entries in the table are a pair of transport type and protocol address. The default is 0 (unicast discovery disabled). .TP .B unicast_req_duration The service time in seconds to be requested during unicast discovery. Note that the remote node is free to grant a different duration. The default is 3600 seconds or one hour. .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 masterOnly Setting this option to one (1) prevents the port from entering the SLAVE state. In addition, the local clock will ignore Announce messages received on this port. This option's intended use is to support the Telecom Profiles according to ITU-T G.8265.1, G.8275.1, and G.8275.2. The default value is zero or false. .TP .B G.8275.portDS.localPriority The Telecom Profiles (ITU-T G.8275.1 and G.8275.2) specify an alternate Best Master Clock Algorithm (BMCA) with a unique data set comparison algorithm. The value of this option is associated with Announce messages arriving on a particular port and is used as a tie breaker whenever clockClass, clockAccuracy, offsetScaledLogVariance, and priority2 are equal. This option is only used when "dataset_comparison" is set to "telecom". The default value is 128. Warning: the BMCA is guaranteed to produce a spanning tree (that is, a timing network without loops) only when using the default values of G.8275.defaultDS.localPriority and G.8275.portDS.localPriority. Careful network engineering is needed when using non-default values. .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. The default is 0 (disabled). .TP .B socket_priority Configure the SO_PRIORITY of sockets. This is to support cases where a user wants to route ptp4l traffic using Linux qdiscs for the purpose of traffic shaping. This option is only available with the IEEE 802.3 transport (the \fB-2\fP option) and is silently ignored when using the UDP IPv4/6 network transports. Must be in the range of 0 to 15, inclusive. The default is 0. .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 clockIdentity The clockIdentity attribute of the local clock. The clockIdentity is an 8-octet array and should in this configuration be written in textual form, see default. It should be unique since it is used to identify the specific clock. If default is used or if not set at all, the clockIdentity will be automtically generated. The default is "000000.0000.000000" .TP .B offsetScaledLogVariance The offsetScaledLogVariance attribute of the local clock. It characterizes the stability of the clock. The default is 0xFFFF. .TP .B G.8275.defaultDS.localPriority The Telecom Profiles (ITU-T G.8275.1 and G.8275.2) specify an alternate Best Master Clock Algorithm (BMCA) with a unique data set comparison algorithm. The value of this option is associated with the local clock and is used as a tie breaker whenever clockClass, clockAccuracy, offsetScaledLogVariance, and priority2 are equal. This option is only used when "dataset_comparison" is set to "telecom". The default value is 128. Warning: the BMCA is guaranteed to produce a spanning tree (that is, a timing network without loops) only when using the default values of G.8275.defaultDS.localPriority and G.8275.portDS.localPriority. Careful network engineering is needed when using non-default values. .TP .B maxStepsRemoved When using this option, if the value of stepsRemoved of an Announce message is greater than or equal to the value of maxStepsRemoved the Announce message is not considered in the operation of the BMCA. The default value is 255. .TP .B domainNumber The domain attribute of the local clock. The default is 0. .TP .B utc_offset The current offset between TAI and UTC. The default is 37. .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 tc_spanning_tree When running as a Transparent Clock, increment the "stepsRemoved" field of Announce messages that pass through the switch. Enabling this option ensures that PTP message loops never form, provided the switches all implement this option together with the BMCA. .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 clock_type Specifies the kind of PTP clock. Valid values are "OC" for ordinary clock, "BC" for boundary clock, "P2P_TC" for peer to peer transparent clock, and "E2E_TC" for end to end transparent clock. An multi-port ordinary clock will automatically be configured as a boundary clock. The default is "OC". .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 initial_delay The initial path delay of the clock in nanoseconds used for synchronization of the clock before the delay is measured using the E2E or P2P delay mechanism. If set to 0, the clock will not be updated until the delay is measured. The default is 0. .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 dscp_event Defines the Differentiated Services Codepoint (DSCP) to be used for PTP event messages. Must be a value between 0 and 63. There are several media streaming standards out there that require specific values for this option. For example 46 (EF PHB) in AES67 or 48 (CS6 PHB) in RAVENNA. The default is 0. .TP .B dscp_general Defines the Differentiated Services Codepoint (DSCP) to be used for PTP general messages. Must be a value between 0 and 63. There are several media streaming standards out there that recommend specific values for this option. For example 34 (AF41 PHB) in AES67 or 46 (EF PHB) in RAVENNA. The default is 0. .TP .B dataset_comparison Specifies the method to be used when comparing data sets during the Best Master Clock Algorithm. The possible values are "ieee1588" and "G.8275.x". The default is "ieee1588". .TP .B logging_level The maximum logging level of messages which should be printed. The default is 6 (LOG_INFO). .TP .B message_tag The tag which is added to all messages printed to the standard output or system log. The default is an empty string (which cannot be set in the configuration file as the option requires an argument). .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. .TP .B hwts_filter Select the hardware time stamp filter setting mode. Possible values are normal, check, full. Normal mode set the filters as needed. Check mode only check but do not set. Full mode set the receive filter to mark all packets with hardware time stamp, so all applications can get them. The default is normal. .TP .B asCapable If set to 'true', all the checks which can unset asCapable variable (as described in Section 10.2.4.1 of 802.1AS) are skipped. If set to 'auto', asCapable is initialized to 'false' and will be set to 'true' after the relevant checks have passed. The default value is 'auto'. .TP .B BMCA This option enables use of static roles for master and slave devices instead of running the best master clock algorithm (BMCA) described in 1588 profile. This is useful when you know the roles of the devices in advance. When set to \'noop', the traditional BMCA algorithm used by 1588 is skipped. masterOnly and slaveOnly will be used to determine master or slave role for the device. In a bridge, slaveOnly (which is a global option) can be set to make all ports assume the slave role. masterOnly (which is a per-port config option) can then be used to set individual ports to take master role. BMCA is used in the Automotive profile to speed up the start time for grand master and slaves. The default value is 'ptp' which runs the BMCA related state machines. .TP .B inhibit_announce This will disable the timer for announce messages (i.e. FD_MANNO_TIMER) and also the announce message timeout timer (i.e. FD_ANNOUNCE_TIMER). This is used by the Automotive profile as part of switching over to a static BMCA. if this option is enabled, ignore_source_id has to be enabled in the slave because it has no way to identify master identity in Sync and Follow_Up messages. The default is 0 (disabled). .TP .B ignore_source_id This will disable source port identity checking for Sync and Follow_Up messages. This is useful when the announce messages are disabled in the master and the slave does not have any way to know it's identity. The default is 0 (disabled). .TP .B msg_interval_request This option, when set, will trigger an adjustment to the Sync and peer delay request message intervals when the clock servo transitions into the SERVO_LOCKED_STABLE state. The Sync interval will be adjusted via the signaling mechanism while the pdelay request interval is simply adjusted locally. The values to use for the new Sync and peer delay request intervals are specified by the operLogSyncInterval and operLogPdelayReqInterval options, respectively. The default value of msg_interval_request is 0 (disabled). .TP .B servo_num_offset_values The number of offset values considered in order to transition from the SERVO_LOCKED to the SERVO_LOCKED_STABLE state. The transition occurs once the last 'servo_num_offset_values' offsets are all below the 'servo_offset_threshold' value. The default value is 10. .TP .B servo_offset_threshold The offset threshold used in order to transition from the SERVO_LOCKED to the SERVO_LOCKED_STABLE state. The transition occurs once the last 'servo_num_offset_values' offsets are all below the threshold value. The default value of offset_threshold is 0 (disabled). .TP .B slave_event_monitor Specifies the address of a UNIX domain socket for slave event monitoring. A local client bound to this address will receive SLAVE_RX_SYNC_TIMING_DATA and SLAVE_DELAY_TIMING_DATA_NP TLVs. The default is the empty string (disabled). .TP .B write_phase_mode This option enables using the "write phase" feature of a PTP Hardware Clock. If supported by the device, this mode uses the hardware's built in phase offset control instead of frequency offset control. The default value is 0 (disabled). .SH UNICAST DISCOVERY OPTIONS .TP .B table_id Each table must begin with a unique, positive table ID. The port that claims a given table does so by including the ID as the value of its 'unicast_master_table' option. .TP .B logQueryInterval This option configures the time to wait between unicast negotiation attempts. It is specified as a power of two in seconds. The default is 0 (1 second). .TP .B peer_address This option specifies the unicast address of the peer for use with the peer to peer delay mechanism. If specified, the port owning the table will negotiate unicast peer delay responses from the machine at the given remote address, otherwise the port will send multicast peer delay requests. .TP .B L2|UDPv4|UDPv6 Each table entry specifies the transport type and network address of a potential remote master. If multiple masters are specified, then unicast negotiation will be performed with each if them. .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-3.1.1/ptp4l.c000066400000000000000000000151011407046267700146210ustar00rootroot00000000000000/** * @file ptp4l.c * @brief PTP Boundary Clock or Transparent 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 "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" 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] 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; enum clock_type type = CLOCK_TYPE_ORDINARY; int c, err = -1, index, print_level; struct clock *clock = NULL; struct option *opts; struct config *cfg; if (handle_term_signals()) return -1; cfg = config_create(); if (!cfg) { return -1; } opts = config_long_options(cfg); /* Process the command line arguments. */ progname = strrchr(argv[0], '/'); progname = progname ? 1+progname : argv[0]; while (EOF != (c = getopt_long(argc, argv, "AEP246HSLf:i:p:sl:mqvh", opts, &index))) { switch (c) { case 0: if (config_parse_option(cfg, opts[index].name, optarg)) goto out; break; 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_tag(config_get_string(cfg, NULL, "message_tag")); 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"); sk_hwts_filter_mode = config_get_int(cfg, NULL, "hwts_filter"); 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; } type = config_get_int(cfg, NULL, "clock_type"); switch (type) { case CLOCK_TYPE_ORDINARY: if (cfg->n_interfaces > 1) { type = CLOCK_TYPE_BOUNDARY; } break; case CLOCK_TYPE_BOUNDARY: if (cfg->n_interfaces < 2) { fprintf(stderr, "BC needs at least two interfaces\n"); goto out; } break; case CLOCK_TYPE_P2P: if (cfg->n_interfaces < 2) { fprintf(stderr, "TC needs at least two interfaces\n"); goto out; } if (DM_P2P != config_get_int(cfg, NULL, "delay_mechanism")) { fprintf(stderr, "P2P_TC needs P2P delay mechanism\n"); goto out; } break; case CLOCK_TYPE_E2E: if (cfg->n_interfaces < 2) { fprintf(stderr, "TC needs at least two interfaces\n"); goto out; } if (DM_E2E != config_get_int(cfg, NULL, "delay_mechanism")) { fprintf(stderr, "E2E_TC needs E2E delay mechanism\n"); goto out; } break; case CLOCK_TYPE_MANAGEMENT: goto out; } clock = clock_create(type, cfg, req_phc); 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-3.1.1/raw.c000066400000000000000000000227561407046267700143650ustar00rootroot00000000000000/** * @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 "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; memset(&mreq, 0, sizeof(mreq)); 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, int socket_priority) { 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 (socket_priority > 0 && setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &socket_priority, sizeof(socket_priority))) { pr_err("setsockopt SO_PRIORITY 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, struct interface *iface, 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, socket_priority; const char *name; char *str; name = interface_label(iface); 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; socket_priority = config_get_int(t->cfg, "global", "socket_priority"); efd = open_socket(name, 1, ptp_dst_mac, p2p_dst_mac, socket_priority); if (efd < 0) goto no_event; gfd = open_socket(name, 0, ptp_dst_mac, p2p_dst_mac, socket_priority); 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, MSG_DONTWAIT); 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, enum transport_event 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; unsigned char pkt[1600], *ptr = buf; struct eth_hdr *hdr; int fd = -1; switch (event) { case TRANS_GENERAL: fd = fda->fd[FD_GENERAL]; break; case TRANS_EVENT: case TRANS_ONESTEP: case TRANS_P2P1STEP: case TRANS_DEFER_EVENT: fd = fda->fd[FD_EVENT]; break; } 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) { return -errno; } /* * 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-3.1.1/raw.h000066400000000000000000000021741407046267700143620ustar00rootroot00000000000000/** * @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-3.1.1/rtnl.c000066400000000000000000000252571407046267700145520ustar00rootroot00000000000000/** * @file rtnl.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 /* Must come before linux/netlink.h on some systems. */ #include #include #include #include #include #include #include #include #include #include "missing.h" #include "print.h" #include "rtnl.h" #define BUF_SIZE 4096 #define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN)) static int rtnl_len; static char *rtnl_buf; static int get_team_active_iface(int master_index); static int nl_close(int fd) { return close(fd); } static int nl_open(int family) { int fd; struct sockaddr_nl sa; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_groups = RTNLGRP_LINK; fd = socket(AF_NETLINK, SOCK_RAW, family); if (fd < 0) { pr_err("failed to open netlink socket: %m"); return -1; } if (bind(fd, (struct sockaddr *) &sa, sizeof(sa))) { pr_err("failed to bind netlink socket: %m"); close(fd); return -1; } return fd; } int rtnl_close(int fd) { if (rtnl_buf) { free(rtnl_buf); rtnl_buf = NULL; rtnl_len = 0; } return nl_close(fd); } int rtnl_open(void) { return nl_open(NETLINK_ROUTE); } static void rtnl_get_ts_device_callback(void *ctx, int linkup, int ts_index) { int *dst = ctx; *dst = ts_index; } int rtnl_get_ts_device(const char *device, char ts_device[IF_NAMESIZE]) { int err, fd; int ts_index = -1; fd = rtnl_open(); if (fd < 0) return fd; err = rtnl_link_query(fd, device); if (err) { goto no_info; } rtnl_link_status(fd, device, rtnl_get_ts_device_callback, &ts_index); if (ts_index > 0 && if_indextoname(ts_index, ts_device)) err = 0; else err = -1; no_info: rtnl_close(fd); return err; } int rtnl_link_query(int fd, const char *device) { struct sockaddr_nl sa; struct msghdr msg; struct iovec iov; int cnt; struct { struct nlmsghdr hdr; struct ifinfomsg ifm; } __attribute__((packed)) request; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; memset(&request, 0, sizeof(request)); request.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(request.ifm)); request.hdr.nlmsg_type = RTM_GETLINK; request.hdr.nlmsg_flags = NLM_F_REQUEST; request.hdr.nlmsg_seq = 1; request.hdr.nlmsg_pid = 0; request.ifm.ifi_family = AF_UNSPEC; request.ifm.ifi_index = if_nametoindex(device ? device : ""); request.ifm.ifi_change = 0xffffffff; iov.iov_base = &request; iov.iov_len = sizeof(request); memset(&msg, 0, sizeof(msg)); msg.msg_name = &sa; msg.msg_namelen = sizeof(sa); msg.msg_iov = &iov; msg.msg_iovlen = 1; cnt = sendmsg(fd, &msg, 0); if (cnt < 0) { pr_err("rtnl: sendmsg: %m"); return -1; } return 0; } static inline __u8 rta_getattr_u8(struct rtattr *rta) { return *(__u8 *)RTA_DATA(rta); } static inline __u16 rta_getattr_u16(struct rtattr *rta) { return *(__u16 *)RTA_DATA(rta); } static inline __u32 rta_getattr_u32(struct rtattr *rta) { return *(__u32 *)RTA_DATA(rta); } static inline char *rta_getattr_str(struct rtattr *rta) { return (char *)RTA_DATA(rta); } static int rtnl_rtattr_parse(struct rtattr *tb[], int max, struct rtattr *rta, int len) { unsigned short type; memset(tb, 0, sizeof(struct rtattr *) * max); while (RTA_OK(rta, len)) { type = rta->rta_type; if ((type < max) && (!tb[type])) tb[type] = rta; rta = RTA_NEXT(rta, len); } if (len) { pr_err("Length mismatch: len %d, rta_len=%d\n", len, rta->rta_len); return -1; } return 0; } static inline int rtnl_nested_rtattr_parse(struct rtattr *tb[], int max, struct rtattr *rta) { return rtnl_rtattr_parse(tb, max, RTA_DATA(rta), RTA_PAYLOAD(rta)); } static int rtnl_linkinfo_parse(int master_index, struct rtattr *rta) { struct rtattr *linkinfo[IFLA_INFO_MAX]; struct rtattr *bond[IFLA_BOND_MAX]; int index = -1; char *kind; if (rtnl_nested_rtattr_parse(linkinfo, IFLA_INFO_MAX, rta) < 0) return -1; if (linkinfo[IFLA_INFO_KIND]) { kind = rta_getattr_str(linkinfo[IFLA_INFO_KIND]); if (kind && !strncmp(kind, "bond", 4) && linkinfo[IFLA_INFO_DATA]) { if (rtnl_nested_rtattr_parse(bond, IFLA_BOND_MAX, linkinfo[IFLA_INFO_DATA]) < 0) return -1; if (bond[IFLA_BOND_ACTIVE_SLAVE]) { index = rta_getattr_u32(bond[IFLA_BOND_ACTIVE_SLAVE]); } } else if (kind && !strncmp(kind, "team", 4)) { index = get_team_active_iface(master_index); } } return index; } int rtnl_link_status(int fd, const char *device, rtnl_callback cb, void *ctx) { struct rtattr *tb[IFLA_MAX+1]; struct ifinfomsg *info = NULL; int index, len, link_up; struct sockaddr_nl sa; int slave_index = -1; struct nlmsghdr *nh; struct msghdr msg; struct iovec iov; index = if_nametoindex(device); if (!rtnl_buf) { rtnl_len = BUF_SIZE; rtnl_buf = malloc(rtnl_len); if (!rtnl_buf) { pr_err("rtnl: low memory"); return -1; } } iov.iov_base = rtnl_buf; iov.iov_len = rtnl_len; memset(&msg, 0, sizeof(msg)); msg.msg_name = &sa; msg.msg_namelen = sizeof(sa); msg.msg_iov = &iov; msg.msg_iovlen = 1; len = recvmsg(fd, &msg, MSG_PEEK | MSG_TRUNC); if (len < 1) { pr_err("rtnl: recvmsg: %m"); return -1; } if (len > rtnl_len) { free(rtnl_buf); rtnl_len = len; rtnl_buf = malloc(len); if (!rtnl_buf) { pr_err("rtnl: failed to resize to %d bytes", len); return -1; } iov.iov_base = rtnl_buf; iov.iov_len = rtnl_len; } len = recvmsg(fd, &msg, 0); if (len < 1) { pr_err("rtnl: recvmsg: %m"); return -1; } nh = (struct nlmsghdr *) rtnl_buf; for ( ; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) { if (nh->nlmsg_type != RTM_NEWLINK) continue; info = NLMSG_DATA(nh); if (index != info->ifi_index) continue; link_up = info->ifi_flags & IFF_RUNNING ? 1 : 0; pr_debug("interface index %d is %s", index, link_up ? "up" : "down"); rtnl_rtattr_parse(tb, IFLA_MAX, IFLA_RTA(info), IFLA_PAYLOAD(nh)); if (tb[IFLA_LINKINFO]) slave_index = rtnl_linkinfo_parse(index, tb[IFLA_LINKINFO]); if (cb) cb(ctx, link_up, slave_index); } return 0; } static int genl_send_msg(int fd, int family_id, int genl_cmd, int genl_version, int rta_type, void *rta_data, int rta_len) { struct sockaddr_nl daddr; struct genlmsghdr *gnlh; struct nlmsghdr *nlh; struct rtattr *attr; char msg[BUF_SIZE]; memset(&daddr, 0, sizeof(daddr)); daddr.nl_family = AF_NETLINK; memset(&msg, 0, sizeof(msg)); nlh = (struct nlmsghdr *) msg; nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); nlh->nlmsg_type = family_id; nlh->nlmsg_flags = NLM_F_REQUEST; gnlh = (struct genlmsghdr *) NLMSG_DATA(nlh); gnlh->cmd = genl_cmd; gnlh->version = genl_version; if (rta_data && rta_len > 0) { attr = (struct rtattr *) GENLMSG_DATA(msg); attr->rta_type = rta_type; attr->rta_len = RTA_LENGTH(rta_len); nlh->nlmsg_len += NLMSG_ALIGN(attr->rta_len); if (nlh->nlmsg_len < sizeof(msg)) memcpy(RTA_DATA(attr), rta_data, rta_len); else return -1; } return sendto(fd, &msg, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(daddr)); } static int genl_get_family_id(int fd, void *family_name) { struct rtattr *tb[CTRL_ATTR_MAX+1]; struct nlmsghdr *nlh; struct rtattr *attr; char msg[BUF_SIZE]; int len, gf_id; len = genl_send_msg(fd, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, 1, CTRL_ATTR_FAMILY_NAME, family_name, strlen(family_name) + 1); if (len < 0) return len; len = recv(fd, &msg, sizeof(msg), 0); if (len < 0) return len; nlh = (struct nlmsghdr *) msg; if (nlh->nlmsg_type == NLMSG_ERROR || !NLMSG_OK(nlh, len)) return -1; attr = (struct rtattr *) GENLMSG_DATA(msg); rtnl_rtattr_parse(tb, CTRL_ATTR_MAX, attr, NLMSG_PAYLOAD(nlh, GENL_HDRLEN)); if (tb[CTRL_ATTR_FAMILY_ID]) gf_id = rta_getattr_u16(tb[CTRL_ATTR_FAMILY_ID]); else gf_id = -1; return gf_id; } static int parase_team_list_option(struct rtattr *attr) { struct rtattr *tb[TEAM_ATTR_OPTION_MAX+1]; int len = RTA_PAYLOAD(attr); const char *optname = ""; const char *mode = ""; int active_index = -1; for (attr = RTA_DATA(attr); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) { rtnl_nested_rtattr_parse(tb, TEAM_ATTR_OPTION_MAX, attr); if (tb[TEAM_ATTR_OPTION_NAME]) optname = rta_getattr_str(tb[TEAM_ATTR_OPTION_NAME]); if (!strcmp(optname, "mode") && tb[TEAM_ATTR_OPTION_TYPE] && rta_getattr_u8(tb[TEAM_ATTR_OPTION_TYPE]) == NLA_STRING) mode = rta_getattr_str(tb[TEAM_ATTR_OPTION_DATA]); if (!strcmp(optname, "activeport") && tb[TEAM_ATTR_OPTION_TYPE] && rta_getattr_u8(tb[TEAM_ATTR_OPTION_TYPE]) == NLA_U32) active_index = rta_getattr_u32(tb[TEAM_ATTR_OPTION_DATA]); } if (strcmp(mode, "activebackup")) { pr_err("team supported only in activebackup mode"); return -1; } else { return active_index; } } static int get_team_active_iface(int master_index) { struct rtattr *tb[TEAM_ATTR_MAX+1]; struct genlmsghdr *gnlh; struct nlmsghdr *nlh; char msg[BUF_SIZE]; int fd, gf_id, len; int index = -1; fd = nl_open(NETLINK_GENERIC); if (fd < 0) return fd; gf_id = genl_get_family_id(fd, TEAM_GENL_NAME); if (gf_id < 0) { pr_err("get genl family failed"); goto no_info; } len = genl_send_msg(fd, gf_id, TEAM_CMD_OPTIONS_GET, TEAM_GENL_VERSION, TEAM_ATTR_TEAM_IFINDEX, &master_index, sizeof(master_index)); if (len < 0) { pr_err("send team info request failed: %m"); goto no_info; } len = recv(fd, msg, sizeof(msg), 0); if (len < 0) { pr_err("recv team info failed: %m"); goto no_info; } nlh = (struct nlmsghdr *) msg; for ( ; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { if (nlh->nlmsg_type != gf_id) continue; gnlh = (struct genlmsghdr *) NLMSG_DATA(nlh); if (gnlh->cmd != TEAM_CMD_OPTIONS_GET) continue; rtnl_rtattr_parse(tb, TEAM_ATTR_MAX, (struct rtattr *)GENLMSG_DATA(msg), NLMSG_PAYLOAD(nlh, GENL_HDRLEN)); if (tb[TEAM_ATTR_TEAM_IFINDEX] && master_index != rta_getattr_u32(tb[TEAM_ATTR_TEAM_IFINDEX])) continue; if (tb[TEAM_ATTR_LIST_OPTION]) { index = parase_team_list_option(tb[TEAM_ATTR_LIST_OPTION]); break; } } no_info: nl_close(fd); return index; } linuxptp-3.1.1/rtnl.h000066400000000000000000000046111407046267700145460ustar00rootroot00000000000000/** * @file rtnl.h * @brief Interface link status via RT netlink * @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_RTNL_H #define HAVE_RTNL_H #include typedef void (*rtnl_callback)(void *ctx, int linkup, int ts_index); /** * Close a RT netlink socket. * @param fd A socket obtained via rtnl_open(). * @return Zero on success, non-zero otherwise. */ int rtnl_close(int fd); /** * Get name of the slave interface which timestamps packets going through * a master interface (e.g. bond0) * @param device Name of the master interface. * @param ts_device Buffer for the name of the slave interface, which must be * at least IF_NAMESIZE bytes long. * @return Zero on success, or -1 on error. */ int rtnl_get_ts_device(const char *device, char ts_device[IF_NAMESIZE]); /** * Request the link status from the kernel. * @param fd A socket obtained via rtnl_open(). * @param device Interface name. Request all iface's status if set NULL. * @return Zero on success, non-zero otherwise. */ int rtnl_link_query(int fd, const char *device); /** * Read kernel messages looking for a link up/down events. * @param fd Readable socket obtained via rtnl_open(). * @param device The device which we need to get link info. * @param cb Callback function to be invoked on each event. * @param ctx Private context passed to the callback. * @return Zero on success, non-zero otherwise. */ int rtnl_link_status(int fd, const char *device, rtnl_callback cb, void *ctx); /** * Open a RT netlink socket for monitoring link state. * @return A valid socket, or -1 on error. */ int rtnl_open(void); #endif linuxptp-3.1.1/serial.c000066400000000000000000000035331407046267700150430ustar00rootroot00000000000000/** * @file serial.c * @note Copyright (C) 2020 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include "print.h" #include "serial.h" #define CANONICAL 1 static int open_serial_baud(const char *name, tcflag_t baud, int icrnl, int hwfc) { struct termios nterm; int fd; fd = open(name, O_RDWR | O_NOCTTY); if (fd < 0) { pr_err("cannot open %s : %m", name); return fd; } memset(&nterm, 0, sizeof(nterm)); /* Input Modes */ nterm.c_iflag = IGNPAR; /* Ignore framing errors and parity errors */ if (icrnl) { /* Translate carriage return to newline on input */ nterm.c_iflag |= ICRNL; } /* Output Modes */ nterm.c_oflag = 0; /* Control Modes */ nterm.c_cflag = baud; nterm.c_cflag |= CS8; /* Character size */ nterm.c_cflag |= CLOCAL; /* Ignore modem control lines */ nterm.c_cflag |= CREAD; /* Enable receiver */ if (hwfc) { /* Enable RTS/CTS (hardware) flow control */ nterm.c_cflag |= CRTSCTS; } /* Local Modes */ if (CANONICAL) { nterm.c_lflag = ICANON; /* Enable canonical mode */ } nterm.c_cc[VTIME] = 10; /* timeout is 10 deciseconds */ nterm.c_cc[VMIN] = 1; /* blocking read until N chars received */ tcflush(fd, TCIFLUSH); tcsetattr(fd, TCSANOW, &nterm); return fd; } int serial_open(const char *name, int bps, int icrnl, int hwfc) { tcflag_t baud; switch (bps) { case 1200: baud = B1200; break; case 1800: baud = B1800; break; case 2400: baud = B2400; break; case 4800: baud = B4800; break; case 9600: baud = B9600; break; case 19200: baud = B19200; break; case 38400: baud = B38400; break; case 57600: baud = B57600; break; case 115200: baud = B115200; break; default: return -1; } return open_serial_baud(name, baud, icrnl, hwfc); } linuxptp-3.1.1/serial.h000066400000000000000000000011121407046267700150370ustar00rootroot00000000000000/** * @file serial.h * @note Copyright (C) 2020 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_SERIAL_H #define HAVE_SERIAL_H /** * Opens a serial port device. * @param name Serial port device to open. * @param bps Baud rate in bits per second. * @param icrnl Pass 1 to map CR to NL on input, zero otherwise. * @param hwfc Pass 1 to enable hardware flow control, zero otherwise. * @return An open file descriptor on success, -1 otherwise. */ int serial_open(const char *name, int bps, int icrnl, int hwfc); #endif linuxptp-3.1.1/servo.c000066400000000000000000000101401407046267700147120ustar00rootroot00000000000000/** * @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 #include "config.h" #include "linreg.h" #include "ntpshm.h" #include "nullf.h" #include "pi.h" #include "servo_private.h" #include "print.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; } if (!servo) 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; servo->offset_threshold = config_get_int(cfg, NULL, "servo_offset_threshold"); servo->num_offset_values = config_get_int(cfg, NULL, "servo_num_offset_values"); servo->curr_offset_values = servo->num_offset_values; return servo; } void servo_destroy(struct servo *servo) { servo->destroy(servo); } static int check_offset_threshold(struct servo *s, int64_t offset) { long long int abs_offset = llabs(offset); if (s->offset_threshold) { if (abs_offset < s->offset_threshold && s->curr_offset_values) s->curr_offset_values--; return s->curr_offset_values ? 0 : 1; } return 0; } 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); switch (*state) { case SERVO_UNLOCKED: servo->curr_offset_values = servo->num_offset_values; break; case SERVO_JUMP: servo->curr_offset_values = servo->num_offset_values; servo->first_update = 0; break; case SERVO_LOCKED: if (check_offset_threshold(servo, offset)) { *state = SERVO_LOCKED_STABLE; } servo->first_update = 0; break; case SERVO_LOCKED_STABLE: /* * This case will never occur since the only place * SERVO_LOCKED_STABLE is set is in this switch/case block * (case SERVO_LOCKED). */ break; } 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); } int servo_offset_threshold(struct servo *servo) { return servo->offset_threshold; } linuxptp-3.1.1/servo.h000066400000000000000000000103751407046267700147310ustar00rootroot00000000000000/** * @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, /** * The Servo has stabilized. The last 'servo_num_offset_values' values * of the estimated threshold are less than servo_offset_threshold. */ SERVO_LOCKED_STABLE, }; /** * 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); /** * Get the offset threshold for triggering the interval change request. * @param servo Pointer to a servo obtained via @ref servo_create(). * @return The offset threshold set by the user. */ int servo_offset_threshold(struct servo *servo); #endif linuxptp-3.1.1/servo_private.h000066400000000000000000000027341407046267700164630ustar00rootroot00000000000000/** * @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 #include "contain.h" #include "servo.h" struct servo { double max_frequency; double step_threshold; double first_step_threshold; int first_update; int64_t offset_threshold; int num_offset_values; int curr_offset_values; 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-3.1.1/sk.c000066400000000000000000000277311407046267700142070ustar00rootroot00000000000000/** * @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 #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; enum hwts_filter_mode sk_hwts_filter_mode = HWTS_FILTER_NORMAL; /* private methods */ static void init_ifreq(struct ifreq *ifreq, struct hwtstamp_config *cfg, const char *device) { 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; } static int hwts_init(int fd, const char *device, int rx_filter, int rx_filter2, int tx_type) { struct ifreq ifreq; struct hwtstamp_config cfg; int orig_rx_filter; int err; init_ifreq(&ifreq, &cfg, device); switch (sk_hwts_filter_mode) { case HWTS_FILTER_CHECK: err = ioctl(fd, SIOCGHWTSTAMP, &ifreq); if (err < 0) { pr_err("ioctl SIOCGHWTSTAMP failed: %m"); return err; } break; case HWTS_FILTER_FULL: cfg.tx_type = tx_type; cfg.rx_filter = HWTSTAMP_FILTER_ALL; err = ioctl(fd, SIOCSHWTSTAMP, &ifreq); if (err < 0) { pr_err("ioctl SIOCSHWTSTAMP failed: %m"); return err; } break; case HWTS_FILTER_NORMAL: cfg.tx_type = tx_type; cfg.rx_filter = orig_rx_filter = rx_filter; err = ioctl(fd, SIOCSHWTSTAMP, &ifreq); if (err < 0) { pr_info("driver rejected most general HWTSTAMP filter"); init_ifreq(&ifreq, &cfg, device); cfg.tx_type = tx_type; cfg.rx_filter = orig_rx_filter = rx_filter2; err = ioctl(fd, SIOCSHWTSTAMP, &ifreq); if (err < 0) { pr_err("ioctl SIOCSHWTSTAMP failed: %m"); return err; } } if (cfg.rx_filter == HWTSTAMP_FILTER_SOME) cfg.rx_filter = orig_rx_filter; break; } if (cfg.tx_type != tx_type || (cfg.rx_filter != rx_filter && cfg.rx_filter != rx_filter2 && cfg.rx_filter != HWTSTAMP_FILTER_ALL)) { pr_debug("tx_type %d not %d", cfg.tx_type, tx_type); pr_debug("rx_filter %d not %d or %d", cfg.rx_filter, rx_filter, rx_filter2); pr_err("The current filter does not match the required"); return -1; } return 0; } /* public methods */ int sk_interface_fd(void) { int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (fd < 0) { pr_err("socket failed: %m"); return -1; } return fd; } 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; } static int sk_interface_guidaddr(const char *name, unsigned char *guid) { char file_name[64], buf[64], addr[8]; FILE *f; char *err; int res; snprintf(file_name, sizeof buf, "/sys/class/net/%s/address", name); f = fopen(file_name, "r"); if (!f) { pr_err("failed to open %s: %m", buf); return -1; } /* Set the file position to the beginning of the GUID */ res = fseek(f, GUID_OFFSET, SEEK_SET); if (res) { pr_err("fseek failed: %m"); goto error; } err = fgets(buf, sizeof buf, f); if (err == NULL) { pr_err("fseek failed: %m"); goto error; } res = sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5], &addr[6], &addr[7]); if (res != GUID_LEN) { pr_err("sscanf failed: %m"); goto error; } memcpy(guid, addr, GUID_LEN); fclose(f); return 0; error: fclose(f); return -1; } int sk_interface_macaddr(const char *name, struct address *mac) { struct ifreq ifreq; int err, fd, type; 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; } close(fd); /* Get interface type */ type = ifreq.ifr_hwaddr.sa_family; switch (type) { case ARPHRD_INFINIBAND: err = sk_interface_guidaddr(name, mac->sll.sll_addr); if (err) { pr_err("fail to get address using sysfs: %m"); return -1; } mac->sll.sll_halen = EUI64; break; default: memcpy(mac->sll.sll_addr, &ifreq.ifr_hwaddr.sa_data, MAC_LEN); mac->sll.sll_halen = EUI48; } mac->sll.sll_family = AF_PACKET; mac->len = sizeof(mac->sll); 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 -errno; } 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 < 0) { 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 -EMSGSIZE; } 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 -EMSGSIZE; } sw = (struct timespec *) CMSG_DATA(cm); hwts->sw = timespec_to_tmv(*sw); } } if (addr) addr->len = msg.msg_namelen; if (!ts) { memset(&hwts->ts, 0, sizeof(hwts->ts)); return cnt < 1 ? -errno : cnt; } switch (hwts->type) { case TS_SOFTWARE: hwts->ts = timespec_to_tmv(ts[0]); break; case TS_HARDWARE: case TS_ONESTEP: case TS_P2P1STEP: hwts->ts = timespec_to_tmv(ts[2]); break; case TS_LEGACY_HW: hwts->ts = timespec_to_tmv(ts[1]); break; } return cnt < 1 ? -errno : cnt; } int sk_set_priority(int fd, int family, uint8_t dscp) { int level, optname, tos; socklen_t tos_len; switch (family) { case AF_INET: level = IPPROTO_IP; optname = IP_TOS; break; case AF_INET6: level = IPPROTO_IPV6; optname = IPV6_TCLASS; break; default: return -1; } tos_len = sizeof(tos); if (getsockopt(fd, level, optname, &tos, &tos_len) < 0) { tos = 0; } /* clear old DSCP value */ tos &= ~0xFC; /* set new DSCP value */ tos |= dscp<<2; tos_len = sizeof(tos); if (setsockopt(fd, level, optname, &tos, tos_len) < 0) { return -1; } return 0; } int sk_timestamping_init(int fd, const char *device, enum timestamp_type type, enum transport_type transport) { int err, filter1, filter2 = 0, flags, tx_type = HWTSTAMP_TX_ON; switch (type) { case TS_SOFTWARE: flags = SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE; break; case TS_HARDWARE: case TS_ONESTEP: case TS_P2P1STEP: 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; switch (type) { case TS_SOFTWARE: tx_type = HWTSTAMP_TX_OFF; break; case TS_HARDWARE: case TS_LEGACY_HW: tx_type = HWTSTAMP_TX_ON; break; case TS_ONESTEP: tx_type = HWTSTAMP_TX_ONESTEP_SYNC; break; case TS_P2P1STEP: tx_type = HWTSTAMP_TX_ONESTEP_P2P; break; } 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, filter2, tx_type); if (err) 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-3.1.1/sk.h000066400000000000000000000117731407046267700142130ustar00rootroot00000000000000/** * @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" /** * Defines the available Hardware time-stamp setting modes. */ enum hwts_filter_mode { HWTS_FILTER_NORMAL, /* set hardware filters in normal way */ HWTS_FILTER_CHECK, /* check filters but do not change them */ HWTS_FILTER_FULL, /* Use time-stamp on all received packets */ }; /** * 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; }; /** * Obtains a socket suitable for use with sk_interface_index(). * @return An open socket on success, -1 otherwise. */ int sk_interface_fd(void); /** * 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); /** * Set DSCP value for socket. * @param fd An open socket. * @param family The address family in use: AF_INET or AF_INET6 * @param dscp The desired DSCP code. * @return Zero on success, negative on failure */ int sk_set_priority(int fd, int family, uint8_t dscp); /** * 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; /** * Hardware time-stamp setting mode */ extern enum hwts_filter_mode sk_hwts_filter_mode; #endif linuxptp-3.1.1/sock.c000066400000000000000000000024401407046267700145170ustar00rootroot00000000000000/** * @file sock.c * @note Copyright (C) 2020 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include "print.h" #include "sock.h" typedef void *so_t; int sock_open(const char *server, const char *port) { int i, err, family[2] = { AF_INET, AF_INET6 }, fd; struct addrinfo hints, *result = NULL; socklen_t addrlen; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME; hints.ai_socktype = SOCK_STREAM; for (i = 0; i < 2; i++) { hints.ai_family = family[i]; err = getaddrinfo(server, port, &hints, &result); if (err) { pr_debug("%s: getaddrinfo failed family %d: %s", __func__, hints.ai_family, gai_strerror(err)); result = NULL; } else { break; } } if (!result) { return -1; } addrlen = (socklen_t) result->ai_addrlen; pr_debug("%s: connecting to server %s canonical %s", __func__, server, result->ai_canonname); fd = socket(result->ai_family, SOCK_STREAM, result->ai_protocol); if (fd < 0) { pr_err("%s: socket failed: %m", __func__); goto failed; } if (connect(fd, result->ai_addr, addrlen) < 0) { pr_err("%s: connect failed: %m", __func__); close(fd); fd = -1; } failed: freeaddrinfo(result); return fd; } linuxptp-3.1.1/sock.h000066400000000000000000000007371407046267700145330ustar00rootroot00000000000000/** * @file sock.h * @note Copyright (C) 2020 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_SOCK_H #define HAVE_SOCK_H /** * Opens a socket connected to a given remote address. * @param server Host name or IP address of the server. * @param port Port on the server with which to connect. * @return An open file descriptor on success, -1 otherwise. */ int sock_open(const char *server, const char *port); #endif linuxptp-3.1.1/stats.c000066400000000000000000000041001407046267700147110ustar00rootroot00000000000000/** * @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-3.1.1/stats.h000066400000000000000000000043331407046267700147260ustar00rootroot00000000000000/** * @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-3.1.1/sysoff.c000066400000000000000000000102761407046267700150770ustar00rootroot00000000000000/** * @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 #include "print.h" #include "sysoff.h" #define NS_PER_SEC 1000000000LL static int64_t pctns(struct ptp_clock_time *t) { return t->sec * NS_PER_SEC + t->nsec; } static int sysoff_precise(int fd, int64_t *result, uint64_t *ts) { struct ptp_sys_offset_precise pso; memset(&pso, 0, sizeof(pso)); if (ioctl(fd, PTP_SYS_OFFSET_PRECISE, &pso)) { pr_debug("ioctl PTP_SYS_OFFSET_PRECISE: %m"); return SYSOFF_RUN_TIME_MISSING; } *result = pctns(&pso.sys_realtime) - pctns(&pso.device); *ts = pctns(&pso.sys_realtime); return SYSOFF_PRECISE; } static int64_t sysoff_estimate(struct ptp_clock_time *pct, int extended, int n_samples, uint64_t *ts, int64_t *delay) { int64_t t1, t2, tp; int64_t interval, timestamp, offset; int64_t shortest_interval, best_timestamp, best_offset; int i = 0; if (extended) { t1 = pctns(&pct[3*i]); tp = pctns(&pct[3*i+1]); t2 = pctns(&pct[3*i+2]); } else { t1 = pctns(&pct[2*i]); tp = pctns(&pct[2*i+1]); t2 = pctns(&pct[2*i+2]); } shortest_interval = t2 - t1; best_timestamp = (t2 + t1) / 2; best_offset = best_timestamp - tp; for (i = 1; i < n_samples; i++) { if (extended) { t1 = pctns(&pct[3*i]); tp = pctns(&pct[3*i+1]); t2 = pctns(&pct[3*i+2]); } else { t1 = pctns(&pct[2*i]); tp = pctns(&pct[2*i+1]); t2 = pctns(&pct[2*i+2]); } interval = t2 - t1; timestamp = (t2 + t1) / 2; offset = timestamp - tp; if (interval < shortest_interval) { shortest_interval = interval; best_timestamp = timestamp; best_offset = offset; } } *ts = best_timestamp; *delay = shortest_interval; return best_offset; } static int sysoff_extended(int fd, int n_samples, int64_t *result, uint64_t *ts, int64_t *delay) { struct ptp_sys_offset_extended pso; memset(&pso, 0, sizeof(pso)); pso.n_samples = n_samples; if (ioctl(fd, PTP_SYS_OFFSET_EXTENDED, &pso)) { pr_debug("ioctl PTP_SYS_OFFSET_EXTENDED: %m"); return SYSOFF_RUN_TIME_MISSING; } *result = sysoff_estimate(&pso.ts[0][0], 1, n_samples, ts, delay); return SYSOFF_EXTENDED; } static int sysoff_basic(int fd, int n_samples, int64_t *result, uint64_t *ts, int64_t *delay) { struct ptp_sys_offset pso; memset(&pso, 0, sizeof(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, 0, n_samples, ts, delay); return SYSOFF_BASIC; } int sysoff_measure(int fd, int method, int n_samples, int64_t *result, uint64_t *ts, int64_t *delay) { switch (method) { case SYSOFF_PRECISE: *delay = 0; return sysoff_precise(fd, result, ts); case SYSOFF_EXTENDED: return sysoff_extended(fd, n_samples, result, ts, delay); case SYSOFF_BASIC: return sysoff_basic(fd, n_samples, result, ts, delay); } return SYSOFF_RUN_TIME_MISSING; } int sysoff_probe(int fd, int n_samples) { int64_t junk, delay; uint64_t ts; int i; if (n_samples > PTP_MAX_SAMPLES) { fprintf(stderr, "warning: %d exceeds kernel max readings %d\n", n_samples, PTP_MAX_SAMPLES); fprintf(stderr, "falling back to clock_gettime method\n"); return SYSOFF_RUN_TIME_MISSING; } for (i = 0; i < SYSOFF_LAST; i++) { if (sysoff_measure(fd, i, n_samples, &junk, &ts, &delay) < 0) continue; return i; } return SYSOFF_RUN_TIME_MISSING; } linuxptp-3.1.1/sysoff.h000066400000000000000000000035051407046267700151010ustar00rootroot00000000000000/** * @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 #include "missing.h" enum { SYSOFF_RUN_TIME_MISSING = -1, SYSOFF_PRECISE, SYSOFF_EXTENDED, SYSOFF_BASIC, SYSOFF_LAST, }; /** * Check to see if a PTP_SYS_OFFSET ioctl is supported. * @param fd An open file descriptor to a PHC device. * @return One of the SYSOFF_ enumeration values. */ 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 method A non-negative SYSOFF_ value returned by sysoff_probe(). * @param n_samples The number of consecutive readings to make. * @param result The estimated offset in nanoseconds. * @param ts The system time corresponding to the 'result'. * @param delay The delay in reading of the clock in nanoseconds. * @return One of the SYSOFF_ enumeration values. */ int sysoff_measure(int fd, int method, int n_samples, int64_t *result, uint64_t *ts, int64_t *delay); linuxptp-3.1.1/tc.c000066400000000000000000000300771407046267700141750ustar00rootroot00000000000000/** * @file tc.c * @note Copyright (C) 2018 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-1335 USA. */ #include #include "port.h" #include "print.h" #include "tc.h" #include "tmv.h" enum tc_match { TC_MISMATCH, TC_SYNC_FUP, TC_FUP_SYNC, TC_DELAY_REQRESP, }; static TAILQ_HEAD(tc_pool, tc_txd) tc_pool = TAILQ_HEAD_INITIALIZER(tc_pool); static int tc_match_delay(int ingress_port, struct ptp_message *resp, struct tc_txd *txd); static int tc_match_syfup(int ingress_port, struct ptp_message *msg, struct tc_txd *txd); static void tc_recycle(struct tc_txd *txd); static struct tc_txd *tc_allocate(void) { struct tc_txd *txd = TAILQ_FIRST(&tc_pool); if (txd) { TAILQ_REMOVE(&tc_pool, txd, list); memset(txd, 0, sizeof(*txd)); return txd; } txd = calloc(1, sizeof(*txd)); return txd; } static int tc_blocked(struct port *q, struct port *p, struct ptp_message *m) { enum port_state s; if (q == p) { return 1; } if (portnum(p) == 0) { return 1; } if (!q->tc_spanning_tree) { return 0; } /* Forward frames in the wrong domain unconditionally. */ if (m->header.domainNumber != clock_domain_number(p->clock)) { return 0; } /* Ingress state */ s = port_state(q); switch (s) { case PS_INITIALIZING: case PS_FAULTY: case PS_DISABLED: case PS_LISTENING: case PS_PRE_MASTER: case PS_PASSIVE: return 1; case PS_MASTER: case PS_GRAND_MASTER: /* Delay_Req swims against the stream. */ if (msg_type(m) != DELAY_REQ) { return 1; } break; case PS_UNCALIBRATED: case PS_SLAVE: break; } /* Egress state */ s = port_state(p); switch (s) { case PS_INITIALIZING: case PS_FAULTY: case PS_DISABLED: case PS_LISTENING: case PS_PRE_MASTER: case PS_PASSIVE: return 1; case PS_UNCALIBRATED: case PS_SLAVE: /* Delay_Req swims against the stream. */ if (msg_type(m) != DELAY_REQ) { return 1; } break; case PS_MASTER: case PS_GRAND_MASTER: /* No use forwarding Delay_Req out the wrong port. */ if (msg_type(m) == DELAY_REQ) { return 1; } break; } return 0; } static void tc_complete_request(struct port *q, struct port *p, struct ptp_message *req, tmv_t residence) { struct tc_txd *txd = tc_allocate(); if (!txd) { port_dispatch(p, EV_FAULT_DETECTED, 0); return; } #ifdef DEBUG pr_err("stash delay request from port %hd to %hd seqid %hu residence %lu", portnum(q), portnum(p), ntohs(req->header.sequenceId), (unsigned long) tmv_to_nanoseconds(residence)); #endif msg_get(req); txd->msg = req; txd->residence = residence; txd->ingress_port = portnum(q); TAILQ_INSERT_TAIL(&p->tc_transmitted, txd, list); } static void tc_complete_response(struct port *q, struct port *p, struct ptp_message *resp, tmv_t residence) { enum tc_match type = TC_MISMATCH; struct tc_txd *txd; Integer64 c1, c2; int cnt; #ifdef DEBUG pr_err("complete delay response from port %hd to %hd seqid %hu", portnum(q), portnum(p), ntohs(resp->header.sequenceId)); #endif TAILQ_FOREACH(txd, &q->tc_transmitted, list) { type = tc_match_delay(portnum(p), resp, txd); if (type == TC_DELAY_REQRESP) { residence = txd->residence; break; } } if (type != TC_DELAY_REQRESP) { return; } c1 = net2host64(resp->header.correction); c2 = c1 + tmv_to_TimeInterval(residence); resp->header.correction = host2net64(c2); cnt = transport_send(p->trp, &p->fda, TRANS_GENERAL, resp); if (cnt <= 0) { pr_err("tc failed to forward response on port %d", portnum(p)); port_dispatch(p, EV_FAULT_DETECTED, 0); } /* Restore original correction value for next egress port. */ resp->header.correction = host2net64(c1); TAILQ_REMOVE(&q->tc_transmitted, txd, list); msg_put(txd->msg); tc_recycle(txd); } static void tc_complete_syfup(struct port *q, struct port *p, struct ptp_message *msg, tmv_t residence) { enum tc_match type = TC_MISMATCH; struct ptp_message *fup; struct tc_txd *txd; Integer64 c1, c2; int cnt; TAILQ_FOREACH(txd, &p->tc_transmitted, list) { type = tc_match_syfup(portnum(q), msg, txd); switch (type) { case TC_MISMATCH: break; case TC_SYNC_FUP: fup = msg; residence = txd->residence; break; case TC_FUP_SYNC: fup = txd->msg; break; case TC_DELAY_REQRESP: pr_err("tc: unexpected match of delay request - sync!"); return; } if (type != TC_MISMATCH) { break; } } if (type == TC_MISMATCH) { txd = tc_allocate(); if (!txd) { port_dispatch(p, EV_FAULT_DETECTED, 0); return; } msg_get(msg); txd->msg = msg; txd->residence = residence; txd->ingress_port = portnum(q); TAILQ_INSERT_TAIL(&p->tc_transmitted, txd, list); return; } c1 = net2host64(fup->header.correction); c2 = c1 + tmv_to_TimeInterval(residence); c2 += tmv_to_TimeInterval(q->peer_delay); c2 += q->asymmetry; fup->header.correction = host2net64(c2); cnt = transport_send(p->trp, &p->fda, TRANS_GENERAL, fup); if (cnt <= 0) { pr_err("tc failed to forward follow up on port %d", portnum(p)); port_dispatch(p, EV_FAULT_DETECTED, 0); } /* Restore original correction value for next egress port. */ fup->header.correction = host2net64(c1); TAILQ_REMOVE(&p->tc_transmitted, txd, list); msg_put(txd->msg); tc_recycle(txd); } static void tc_complete(struct port *q, struct port *p, struct ptp_message *msg, tmv_t residence) { switch (msg_type(msg)) { case SYNC: case FOLLOW_UP: tc_complete_syfup(q, p, msg, residence); break; case DELAY_REQ: tc_complete_request(q, p, msg, residence); break; case DELAY_RESP: tc_complete_response(q, p, msg, residence); break; } } static int tc_current(struct ptp_message *m, struct timespec now) { int64_t t1, t2, tmo; tmo = 1LL * NSEC2SEC; t1 = m->ts.host.tv_sec * NSEC2SEC + m->ts.host.tv_nsec; t2 = now.tv_sec * NSEC2SEC + now.tv_nsec; return t2 - t1 < tmo; } static int tc_fwd_event(struct port *q, struct ptp_message *msg) { tmv_t egress, ingress = msg->hwts.ts, residence; struct port *p; int cnt, err; double rr; clock_gettime(CLOCK_MONOTONIC, &msg->ts.host); /* First send the event message out. */ for (p = clock_first_port(q->clock); p; p = LIST_NEXT(p, list)) { if (tc_blocked(q, p, msg)) { continue; } cnt = transport_send(p->trp, &p->fda, TRANS_DEFER_EVENT, msg); if (cnt <= 0) { pr_err("failed to forward event from port %hd to %hd", portnum(q), portnum(p)); port_dispatch(p, EV_FAULT_DETECTED, 0); } } /* Go back and gather the transmit time stamps. */ for (p = clock_first_port(q->clock); p; p = LIST_NEXT(p, list)) { if (tc_blocked(q, p, msg)) { continue; } err = transport_txts(&p->fda, msg); if (err || !msg_sots_valid(msg)) { pr_err("failed to fetch txts on port %hd to %hd event", portnum(q), portnum(p)); port_dispatch(p, EV_FAULT_DETECTED, 0); continue; } ts_add(&msg->hwts.ts, p->tx_timestamp_offset); egress = msg->hwts.ts; residence = tmv_sub(egress, ingress); rr = clock_rate_ratio(q->clock); if (rr != 1.0) { residence = dbl_tmv(tmv_dbl(residence) * rr); } tc_complete(q, p, msg, residence); } return 0; } static int tc_match_delay(int ingress_port, struct ptp_message *resp, struct tc_txd *txd) { struct ptp_message *req = txd->msg; if (ingress_port != txd->ingress_port) { return TC_MISMATCH; } if (req->header.sequenceId != resp->header.sequenceId) { return TC_MISMATCH; } if (!pid_eq(&req->header.sourcePortIdentity, &resp->delay_resp.requestingPortIdentity)) { return TC_MISMATCH; } if (msg_type(req) == DELAY_REQ && msg_type(resp) == DELAY_RESP) { return TC_DELAY_REQRESP; } return TC_MISMATCH; } static int tc_match_syfup(int ingress_port, struct ptp_message *msg, struct tc_txd *txd) { if (ingress_port != txd->ingress_port) { return TC_MISMATCH; } if (msg->header.sequenceId != txd->msg->header.sequenceId) { return TC_MISMATCH; } if (!source_pid_eq(msg, txd->msg)) { return TC_MISMATCH; } if (msg_type(txd->msg) == SYNC && msg_type(msg) == FOLLOW_UP) { return TC_SYNC_FUP; } if (msg_type(txd->msg) == FOLLOW_UP && msg_type(msg) == SYNC) { return TC_FUP_SYNC; } return TC_MISMATCH; } static void tc_recycle(struct tc_txd *txd) { TAILQ_INSERT_HEAD(&tc_pool, txd, list); } /* public methods */ void tc_cleanup(void) { struct tc_txd *txd; while ((txd = TAILQ_FIRST(&tc_pool)) != NULL) { TAILQ_REMOVE(&tc_pool, txd, list); free(txd); } } void tc_flush(struct port *q) { struct tc_txd *txd; while ((txd = TAILQ_FIRST(&q->tc_transmitted)) != NULL) { TAILQ_REMOVE(&q->tc_transmitted, txd, list); msg_put(txd->msg); tc_recycle(txd); } } int tc_forward(struct port *q, struct ptp_message *msg) { uint16_t steps_removed; struct port *p; int cnt; if (q->tc_spanning_tree && msg_type(msg) == ANNOUNCE) { steps_removed = ntohs(msg->announce.stepsRemoved); msg->announce.stepsRemoved = htons(1 + steps_removed); } for (p = clock_first_port(q->clock); p; p = LIST_NEXT(p, list)) { if (tc_blocked(q, p, msg)) { continue; } cnt = transport_send(p->trp, &p->fda, TRANS_GENERAL, msg); if (cnt <= 0) { pr_err("tc failed to forward message on port %d", portnum(p)); port_dispatch(p, EV_FAULT_DETECTED, 0); } } return 0; } int tc_fwd_folup(struct port *q, struct ptp_message *msg) { struct port *p; clock_gettime(CLOCK_MONOTONIC, &msg->ts.host); for (p = clock_first_port(q->clock); p; p = LIST_NEXT(p, list)) { if (tc_blocked(q, p, msg)) { continue; } tc_complete(q, p, msg, tmv_zero()); } return 0; } int tc_fwd_request(struct port *q, struct ptp_message *msg) { return tc_fwd_event(q, msg); } int tc_fwd_response(struct port *q, struct ptp_message *msg) { struct port *p; clock_gettime(CLOCK_MONOTONIC, &msg->ts.host); for (p = clock_first_port(q->clock); p; p = LIST_NEXT(p, list)) { if (tc_blocked(q, p, msg)) { continue; } tc_complete(q, p, msg, tmv_zero()); } return 0; } int tc_fwd_sync(struct port *q, struct ptp_message *msg) { struct ptp_message *fup = NULL; int err; if (one_step(msg)) { fup = msg_allocate(); if (!fup) { return -1; } fup->header.tsmt = FOLLOW_UP | (msg->header.tsmt & 0xf0); fup->header.ver = msg->header.ver; fup->header.messageLength = htons(sizeof(struct follow_up_msg)); fup->header.domainNumber = msg->header.domainNumber; fup->header.sourcePortIdentity = msg->header.sourcePortIdentity; fup->header.sequenceId = msg->header.sequenceId; fup->header.control = CTL_FOLLOW_UP; fup->header.logMessageInterval = msg->header.logMessageInterval; fup->follow_up.preciseOriginTimestamp = msg->sync.originTimestamp; msg->header.flagField[0] |= TWO_STEP; } err = tc_fwd_event(q, msg); if (err) { return err; } if (fup) { err = tc_fwd_folup(q, fup); msg_put(fup); } return err; } int tc_ignore(struct port *p, struct ptp_message *m) { struct ClockIdentity c1, c2; if (p->match_transport_specific && 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 (cid_eq(&c1, &c2)) { return 1; } return 0; } void tc_prune(struct port *q) { struct timespec now; struct tc_txd *txd; clock_gettime(CLOCK_MONOTONIC, &now); while ((txd = TAILQ_FIRST(&q->tc_transmitted)) != NULL) { if (tc_current(txd->msg, now)) { break; } TAILQ_REMOVE(&q->tc_transmitted, txd, list); msg_put(txd->msg); tc_recycle(txd); } } linuxptp-3.1.1/tc.h000066400000000000000000000063161407046267700142010ustar00rootroot00000000000000/** * @file tc.h * @brief Provides Transparent Clock logic. * @note Copyright (C) 2018 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-1335 USA. */ #ifndef HAVE_TC_H #define HAVE_TC_H #include "msg.h" #include "port_private.h" /** * Flushes the list of remembered residence times. * @param q Port whose list should be flushed */ void tc_flush(struct port *q); /** * Forwards a given general message out all other ports. * @param q The ingress port * @param msg The message to be sent * @return Zero on success, non-zero otherwise. */ int tc_forward(struct port *q, struct ptp_message *msg); /** * Forwards a given Follow-Up message out all other ports. * * This function adds the unique, per egress port residence time into * the correction field for the transmitted follow up message. * * @param q The ingress port * @param msg The message to be sent * @return Zero on success, non-zero otherwise. */ int tc_fwd_folup(struct port *q, struct ptp_message *msg); /** * Forwards a given delay request message out all other ports. * * This function computes the unique residence time for each egress * port, remembering it in that egress port. * * @param q The ingress port * @param msg The message to be sent * @return Zero on success, non-zero otherwise. */ int tc_fwd_request(struct port *q, struct ptp_message *msg); /** * Forwards a given response message out all other ports. * * This function adds the unique, per egress port residence time into * the correction field for the transmitted delay response message. * * @param q The ingress port * @param msg The message to be sent * @return Zero on success, non-zero otherwise. */ int tc_fwd_response(struct port *q, struct ptp_message *msg); /** * Forwards a given sync message out all other ports. * * This function computes the unique residence time for each egress * port, remembering it in that egress port. * * @param q The ingress port * @param msg The message to be sent * @return Zero on success, non-zero otherwise. */ int tc_fwd_sync(struct port *q, struct ptp_message *msg); /** * Determines whether the local clock should ignore a given message. * * @param q The ingress port * @param msg The message to test * @return One if the message should be ignored, zero otherwise. */ int tc_ignore(struct port *q, struct ptp_message *m); /** * Prunes stale entries from the list of remembered residence times. * @param q Port whose list should be pruned. */ void tc_prune(struct port *q); #endif linuxptp-3.1.1/telecom.c000066400000000000000000000037251407046267700152170ustar00rootroot00000000000000/** * @file telecom.c * @note Copyright (C) 2017 Richard Cochran * * Derived from code in bmc.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-1335 USA. */ #include #include "bmc.h" #include "ds.h" int telecom_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; 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; if (a->localPriority < b->localPriority) return A_BETTER; if (a->localPriority > b->localPriority) return B_BETTER; if (a->quality.clockClass <= 127) return dscmp2(a, b); diff = memcmp(&a->identity, &b->identity, sizeof(a->identity)); if (!diff) return dscmp2(a, b); return diff < 0 ? A_BETTER : B_BETTER; } linuxptp-3.1.1/timemaster.8000066400000000000000000000266261407046267700156730ustar00rootroot00000000000000.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. .TP .B first_shm_segment Specify the first number in a sequence of SHM segments that will be used by \fBptp4l\fR and \fBphc2sys\fR. The default value is 0. Increasing the number can be useful to avoid conflicts with time sources that are not started by \fBtimemaster\fR, e.g. \fBgpsd\fR using segments number 0 and 1. .TP .B restart_processes Enable or disable restarting of processes started by \fBtimemaster\fR. If the option is set to a non-zero value, all processes except \fBchronyd\fR and \fBntpd\fR will be automatically restarted when terminated and \fBtimemaster\fR is running for at least one second (i.e. the process did not terminate due to a configuration error). If a process was terminated and is not started again, \fBtimemaster\fR will kill the other processes and exit with a non-zero status. The default value is 1 (enabled). .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). .TP .B ntp_options Specify extra options that should be added for this source to the \fBserver\fR directive in the configuration file of the selected NTP implementation. No extra options are added by default. .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 ntp_options Specify extra options that should be added for this source to the \fBrefclock\fR or \fBserver\fR directives in the configuration file of the selected NTP implementation. No extra options are added by default. .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 global section of the configuration files generated for all \fBptp4l\fR instances. There is no default content of this section. Other sections (e.g. \fB[unicast_master_table]\fR) may be specified here, but lines beginning with the bracket need to be prefixed with the \fB>\fR character to prevent \fBtimemaster\fR from parsing it as a beginning of another 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 ntp_options key 12 [ptp_domain 0] interfaces eth0 eth1 ntp_poll 0 phc2sys_poll \-2 delay 10e\-6 ntp_options prefer ptp4l_option clock_servo linreg ptp4l_option delay_mechanism P2P [timemaster] ntp_program chronyd rundir /var/run/timemaster first_shm_segment 1 restart_processes 0 [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-3.1.1/timemaster.c000066400000000000000000001007221407046267700157340ustar00rootroot00000000000000/** * @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 #include #include "print.h" #include "rtnl.h" #include "sk.h" #include "util.h" #include "version.h" #define DEFAULT_RUNDIR "/var/run/timemaster" #define DEFAULT_FIRST_SHM_SEGMENT 0 #define DEFAULT_RESTART_PROCESSES 1 #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; char *ntp_options; }; struct ptp_domain { int domain; int ntp_poll; int phc2sys_poll; double delay; char **interfaces; char **ptp4l_settings; char *ntp_options; }; 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; int first_shm_segment; int restart_processes; 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; int **command_groups; int restart_groups; int no_restart_group; }; 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); free(source->ntp.ntp_options); break; case PTP_DOMAIN: free_parray((void **)source->ptp.interfaces); free_parray((void **)source->ptp.ptp4l_settings); free(source->ptp.ntp_options); break; } free(source); } static struct source *source_ntp_parse(char *parameter, char **settings) { char *name, *value; struct source *source; int r = 0; if (!*parameter) { pr_err("missing address for ntp_server"); return NULL; } source = xmalloc(sizeof(*source)); source->type = NTP_SERVER; source->ntp.address = xstrdup(parameter); source->ntp.minpoll = DEFAULT_NTP_MINPOLL; source->ntp.maxpoll = DEFAULT_NTP_MAXPOLL; source->ntp.iburst = 0; source->ntp.ntp_options = xstrdup(""); for (; *settings; settings++) { parse_setting(*settings, &name, &value); if (!strcasecmp(name, "minpoll")) { r = parse_int(value, &source->ntp.minpoll); } else if (!strcasecmp(name, "maxpoll")) { r = parse_int(value, &source->ntp.maxpoll); } else if (!strcasecmp(name, "iburst")) { r = parse_bool(value, &source->ntp.iburst); } else if (!strcasecmp(name, "ntp_options")) { replace_string(value, &source->ntp.ntp_options); } else { pr_err("unknown ntp_server setting %s", name); goto failed; } if (r) { pr_err("invalid value %s for %s", value, name); goto failed; } } return source; failed: source_destroy(source); return NULL; } 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(); source->ptp.ntp_options = xstrdup(""); 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, "ntp_options")) { replace_string(value, &source->ptp.ntp_options); } 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; int r = 0; 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 if (!strcasecmp(name, "first_shm_segment")) { r = parse_int(value, &config->first_shm_segment); } else if (!strcasecmp(name, "restart_processes")) { r = parse_int(value, &config->restart_processes); } else { pr_err("unknown timemaster setting %s", name); return 1; } if (r) { pr_err("invalid value %s for %s", value, name); return 1; } } return 0; } static char **parse_raw_settings(char **settings) { char **setting, *s, **parsed_settings; parsed_settings = (char **)parray_new(); for (setting = settings; *setting; setting++) { s = *setting; /* Unescape lines beginning with '>' */ if (s[0] == '>') s++; parray_append((void ***)&parsed_settings, xstrdup(s)); } return parsed_settings; } 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 = parse_raw_settings(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); config->first_shm_segment = DEFAULT_FIRST_SHM_SEGMENT; config->restart_processes = DEFAULT_RESTART_PROCESSES; 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 *message_tag) { 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("-t"), xstrdup(message_tag), 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_command(char **command, int command_group, struct script *script) { int *group; parray_append((void ***)&script->commands, command); group = xmalloc(sizeof(int)); *group = command_group; parray_append((void ***)&script->command_groups, group); } static void add_shm_source(int shm_segment, int poll, int dpoll, double delay, char *ntp_options, 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 %s\n", shm_segment, poll, dpoll, refid, delay, ntp_options); break; case NTPD: string_appendf(ntp_config, "server 127.127.28.%d minpoll %d maxpoll %d " "mode 1 %s\n" "fudge 127.127.28.%d refid %s\n", shm_segment, poll, poll, ntp_options, 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 %s\n", source->address, source->minpoll, source->maxpoll, source->iburst ? "iburst" : "", source->ntp_options); return 0; } static int add_ptp_source(struct ptp_domain *source, struct timemaster_config *config, int *shm_segment, int *command_group, int ***allocated_phcs, char **ntp_config, struct script *script) { struct config_file *config_file; char **command, *uds_path, **interfaces, *message_tag; char ts_interface[IF_NAMESIZE]; int i, j, num_interfaces, *phc, *phcs, hw_ts, sw_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; sw_ts = SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE; 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; /* * if it is a bonded interface, use the name of the active * slave interface (which will be timestamping packets) */ if (!rtnl_get_ts_device(source->interfaces[i], ts_interface)) { pr_debug("slave interface of %s: %s", source->interfaces[i], ts_interface); } else { snprintf(ts_interface, sizeof(ts_interface), "%s", source->interfaces[i]); } /* check if the interface has a usable PHC */ if (sk_get_ts_info(ts_interface, &ts_info)) { pr_err("failed to get time stamping info for %s", ts_interface); free(phcs); return 1; } if (((ts_info.so_timestamping & hw_ts) != hw_ts)) { pr_debug("interface %s: no PHC", ts_interface); if ((ts_info.so_timestamping & sw_ts) != sw_ts) { pr_err("time stamping not supported on %s", ts_interface); free(phcs); return 1; } continue; } pr_debug("interface %s: PHC %d", ts_interface, 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); message_tag = string_newf("[%d", source->domain); for (j = 0; interfaces[j]; j++) string_appendf(&message_tag, "%s%s", j ? "+" : ":", interfaces[j]); string_appendf(&message_tag, "]"); 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"); if (*config->ptp4l.settings) { extend_config_string(&config_file->content, config->ptp4l.settings); string_appendf(&config_file->content, "\n[global]\n"); } extend_config_string(&config_file->content, source->ptp4l_settings); string_appendf(&config_file->content, "slaveOnly 1\n" "domainNumber %d\n" "uds_address %s\n" "message_tag %s\n", source->domain, uds_path, message_tag); if (phcs[i] >= 0) { /* HW time stamping */ command = get_ptp4l_command(&config->ptp4l, config_file, interfaces, 1); add_command(command, *command_group, script); command = get_phc2sys_command(&config->phc2sys, source->domain, source->phc2sys_poll, *shm_segment, uds_path, message_tag); add_command(command, (*command_group)++, script); } else { /* SW time stamping */ command = get_ptp4l_command(&config->ptp4l, config_file, interfaces, 0); add_command(command, (*command_group)++, script); 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, source->ntp_options, "PTP", config, ntp_config); (*shm_segment)++; free(message_tag); 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, int command_group) { 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); add_command(command, command_group, script); return ntp_config; } static void script_destroy(struct script *script) { char ***commands, **command; int **groups; 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); for (groups = script->command_groups; *groups; groups++) free(*groups); free(script->command_groups); 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, command_group = 0; script->configs = (struct config_file **)parray_new(); script->commands = (char ***)parray_new(); script->command_groups = (int **)parray_new(); script->no_restart_group = command_group; script->restart_groups = config->restart_processes; ntp_config = add_ntp_program(config, script, command_group++); shm_segment = config->first_shm_segment; 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, &command_group, &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) { struct timespec ts_start, ts_now; sigset_t mask, old_mask; siginfo_t info; pid_t pid, *pids; int i, group, num_commands, status, quit = 0, ret = 0; for (num_commands = 0; script->commands[num_commands]; num_commands++) ; if (!num_commands) { /* nothing to do */ return 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; } 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; } } clock_gettime(CLOCK_MONOTONIC, &ts_start); /* process the blocked signals */ while (1) { if (sigwaitinfo(&mask, &info) < 0) { if (errno == EINTR) continue; pr_err("sigwaitinfo() failed: %m"); break; } clock_gettime(CLOCK_MONOTONIC, &ts_now); if (info.si_signo != SIGCHLD) { if (quit) continue; quit = 1; pr_debug("exiting on signal %d", info.si_signo); /* terminate remaining processes */ for (i = 0; i < num_commands; i++) { if (pids[i] > 0) { pr_debug("killing process %d", pids[i]); kill(pids[i], SIGTERM); } } continue; } /* wait for all terminated processes */ while (1) { pid = waitpid(-1, &status, WNOHANG); if (pid <= 0) break; if (!WIFEXITED(status)) { pr_info("process %d terminated abnormally", pid); } else { pr_info("process %d terminated with status %d", pid, WEXITSTATUS(status)); } for (i = 0; i < num_commands; i++) { if (pids[i] == pid) pids[i] = 0; } } /* wait for all processes to terminate when exiting */ if (quit) { for (i = 0; i < num_commands; i++) { if (pids[i]) break; } if (i == num_commands) break; pr_debug("waiting for other processes to terminate"); continue; } /* * terminate (and then restart if allowed) all processes in * groups that have a terminated process */ for (group = 0; group < num_commands; group++) { int terminated = 0, running = 0; for (i = 0; i < num_commands; i++) { if (*(script->command_groups[i]) != group) continue; if (pids[i]) running++; else terminated++; } if (!terminated) continue; /* * exit with a non-zero status if the group should not * be restarted (i.e. chronyd/ntpd), timemaster is * running only for a short time (and it is likely a * configuration error), or restarting is disabled * completely */ if (group == script->no_restart_group || ts_now.tv_sec - ts_start.tv_sec <= 1 || !script->restart_groups) { kill(getpid(), SIGTERM); ret = 1; break; } for (i = 0; i < num_commands; i++) { if (*(script->command_groups[i]) != group) continue; /* terminate all processes in the group first */ if (running && pids[i]) { pr_debug("killing process %d", pids[i]); kill(pids[i], SIGTERM); } else if (!running && !pids[i]) { pids[i] = start_program(script->commands[i], &old_mask); if (!pids[i]) kill(getpid(), SIGTERM); /* limit restarting rate */ sleep(1); } } } } free(pids); if (remove_config_files(script->configs)) return 1; return ret; } static void script_print(struct script *script) { char ***commands, **command; int **groups; 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, groups = script->command_groups; *commands; commands++, groups++) { fprintf(stderr, "[%d] ", **groups); 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-3.1.1/tlv.c000066400000000000000000000642711407046267700143770ustar00rootroot00000000000000/** * @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 #include #include "port.h" #include "tlv.h" #include "msg.h" #define HTONS(x) (x) = htons(x) #define HTONL(x) (x) = htonl(x) #define NTOHS(x) (x) = ntohs(x) #define NTOHL(x) (x) = ntohl(x) #define TLV_LENGTH_INVALID(tlv, type) \ (tlv->length < sizeof(struct type) - sizeof(struct TLV)) uint8_t ieee8021_id[3] = { IEEE_802_1_COMMITTEE }; static TAILQ_HEAD(tlv_pool, tlv_extra) tlv_pool = TAILQ_HEAD_INITIALIZER(tlv_pool); 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 void timestamp_host2net(struct Timestamp *t) { HTONL(t->seconds_lsb); HTONS(t->seconds_msb); HTONL(t->nanoseconds); } static void timestamp_net2host(struct Timestamp *t) { NTOHL(t->seconds_lsb); NTOHS(t->seconds_msb); NTOHL(t->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 int64_t host2net64_unaligned(int64_t *p) { int64_t v; memcpy(&v, p, sizeof(v)); v = host2net64(v); memcpy(p, &v, sizeof(v)); return v; } static int64_t net2host64_unaligned(int64_t *p) { int64_t v; memcpy(&v, p, sizeof(v)); v = net2host64(v); memcpy(p, &v, sizeof(v)); return v; } static size_t tlv_array_count(struct TLV *tlv, size_t base_size, size_t item_size) { return (tlv->length - base_size) / item_size; } static bool tlv_array_invalid(struct TLV *tlv, size_t base_size, size_t item_size) { size_t expected_length, n_items; n_items = tlv_array_count(tlv, base_size, item_size); expected_length = base_size + n_items * item_size; return (tlv->length == expected_length) ? false : true; } 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 port_stats_np *psn; 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_PORT_STATS_NP: if (data_len < sizeof(struct port_stats_np)) goto bad_length; psn = (struct port_stats_np *)m->data; psn->portIdentity.portNumber = ntohs(psn->portIdentity.portNumber); extra_len = sizeof(struct port_stats_np); 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 port_stats_np *psn; 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; case TLV_PORT_STATS_NP: psn = (struct port_stats_np *)m->data; psn->portIdentity.portNumber = htons(psn->portIdentity.portNumber); break; } } static int nsm_resp_post_recv(struct tlv_extra *extra) { struct nsm_resp_tlv_head *head; struct TLV *tlv = extra->tlv; struct timePropertiesDS *tp; struct PortAddress *paddr; struct currentDS *cds; struct parentDS *pds; unsigned char *ptr; uint16_t expected; if (tlv->length < sizeof(*head) + sizeof(*extra->foot) - sizeof(head->type) - sizeof(head->length)) { return -EBADMSG; } head = (struct nsm_resp_tlv_head *) tlv; paddr = &head->parent_addr; NTOHS(paddr->networkProtocol); NTOHS(paddr->addressLength); switch (paddr->networkProtocol) { case TRANS_UDP_IPV4: expected = 4; break; case TRANS_UDP_IPV6: expected = 16; break; case TRANS_IEEE_802_3: expected = 6; break; default: return -EBADMSG; } if (paddr->addressLength != expected) { return -EBADMSG; } if (tlv->length != sizeof(*head) + sizeof(*extra->foot) + paddr->addressLength - sizeof(head->type) - sizeof(head->length)) { return -EBADMSG; } ptr = (unsigned char *) tlv; ptr += sizeof(*head) + paddr->addressLength; extra->foot = (struct nsm_resp_tlv_foot *) ptr; pds = &extra->foot->parent; cds = &extra->foot->current; tp = &extra->foot->timeprop; /* * At this point the alignment only 2 bytes worst case. * So we need to be careful with the 64 bit words. */ NTOHS(pds->parentPortIdentity.portNumber); NTOHS(pds->observedParentOffsetScaledLogVariance); NTOHL(pds->observedParentClockPhaseChangeRate); NTOHS(pds->grandmasterClockQuality.offsetScaledLogVariance); NTOHS(cds->stepsRemoved); net2host64_unaligned(&cds->offsetFromMaster); net2host64_unaligned(&cds->meanPathDelay); NTOHS(tp->currentUtcOffset); NTOHL(extra->foot->lastsync.seconds_lsb); NTOHS(extra->foot->lastsync.seconds_msb); NTOHL(extra->foot->lastsync.nanoseconds); return 0; } static void nsm_resp_pre_send(struct tlv_extra *extra) { struct nsm_resp_tlv_head *head; struct timePropertiesDS *tp; struct PortAddress *paddr; struct currentDS *cds; struct parentDS *pds; head = (struct nsm_resp_tlv_head *) extra->tlv; paddr = &head->parent_addr; pds = &extra->foot->parent; cds = &extra->foot->current; tp = &extra->foot->timeprop; NTOHS(paddr->networkProtocol); NTOHS(paddr->addressLength); HTONS(pds->parentPortIdentity.portNumber); HTONS(pds->observedParentOffsetScaledLogVariance); HTONL(pds->observedParentClockPhaseChangeRate); HTONS(pds->grandmasterClockQuality.offsetScaledLogVariance); HTONS(cds->stepsRemoved); host2net64_unaligned(&cds->offsetFromMaster); host2net64_unaligned(&cds->meanPathDelay); HTONS(tp->currentUtcOffset); HTONL(extra->foot->lastsync.seconds_lsb); HTONS(extra->foot->lastsync.seconds_msb); HTONL(extra->foot->lastsync.nanoseconds); } 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; case 2: if (org->length + sizeof(struct TLV) != sizeof(struct msg_interval_req_tlv)) goto bad_length; } } 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; } } } static int slave_delay_timing_data_post_revc(struct tlv_extra *extra) { struct slave_delay_timing_data_tlv *slave_delay = (struct slave_delay_timing_data_tlv *) extra->tlv; size_t base_size = sizeof(slave_delay->sourcePortIdentity), n_items; struct slave_delay_timing_record *record; if (tlv_array_invalid(extra->tlv, base_size, sizeof(*record))) { return -EBADMSG; } n_items = tlv_array_count(extra->tlv, base_size, sizeof(*record)); record = slave_delay->record; NTOHS(slave_delay->sourcePortIdentity.portNumber); while (n_items) { NTOHS(record->sequenceId); timestamp_net2host(&record->delayOriginTimestamp); net2host64_unaligned(&record->totalCorrectionField); timestamp_net2host(&record->delayResponseTimestamp); n_items--; record++; } return 0; } static void slave_delay_timing_data_pre_send(struct tlv_extra *extra) { struct slave_delay_timing_data_tlv *slave_delay = (struct slave_delay_timing_data_tlv *) extra->tlv; size_t base_size = sizeof(slave_delay->sourcePortIdentity), n_items; struct slave_delay_timing_record *record; n_items = tlv_array_count(extra->tlv, base_size, sizeof(*record)); record = slave_delay->record; HTONS(slave_delay->sourcePortIdentity.portNumber); while (n_items) { HTONS(record->sequenceId); timestamp_host2net(&record->delayOriginTimestamp); host2net64_unaligned(&record->totalCorrectionField); timestamp_host2net(&record->delayResponseTimestamp); n_items--; record++; } } static int slave_rx_sync_timing_data_post_revc(struct tlv_extra *extra) { struct slave_rx_sync_timing_data_tlv *slave_data = (struct slave_rx_sync_timing_data_tlv *) extra->tlv; size_t base_size = sizeof(slave_data->sourcePortIdentity), n_items; struct slave_rx_sync_timing_record *record; if (tlv_array_invalid(extra->tlv, base_size, sizeof(*record))) { return -EBADMSG; } n_items = tlv_array_count(extra->tlv, base_size, sizeof(*record)); record = slave_data->record; NTOHS(slave_data->sourcePortIdentity.portNumber); while (n_items) { NTOHS(record->sequenceId); timestamp_net2host(&record->syncOriginTimestamp); net2host64_unaligned(&record->totalCorrectionField); NTOHL(record->scaledCumulativeRateOffset); timestamp_net2host(&record->syncEventIngressTimestamp); n_items--; record++; } return 0; } static void slave_rx_sync_timing_data_pre_send(struct tlv_extra *extra) { struct slave_rx_sync_timing_data_tlv *slave_data = (struct slave_rx_sync_timing_data_tlv *) extra->tlv; size_t base_size = sizeof(slave_data->sourcePortIdentity), n_items; struct slave_rx_sync_timing_record *record; n_items = tlv_array_count(extra->tlv, base_size, sizeof(*record)); record = slave_data->record; HTONS(slave_data->sourcePortIdentity.portNumber); while (n_items) { HTONS(record->sequenceId); timestamp_host2net(&record->syncOriginTimestamp); host2net64_unaligned(&record->totalCorrectionField); HTONL(record->scaledCumulativeRateOffset); timestamp_host2net(&record->syncEventIngressTimestamp); n_items--; record++; } } static int unicast_message_type_valid(uint8_t message_type) { message_type >>= 4; switch (message_type) { case ANNOUNCE: case SYNC: case DELAY_RESP: case PDELAY_RESP: return 1; default: return 0; } } static int unicast_negotiation_post_recv(struct tlv_extra *extra) { struct request_unicast_xmit_tlv *request; struct ack_cancel_unicast_xmit_tlv *ack; struct cancel_unicast_xmit_tlv *cancel; struct grant_unicast_xmit_tlv *grant; struct TLV *tlv = extra->tlv; switch (tlv->type) { case TLV_REQUEST_UNICAST_TRANSMISSION: if (TLV_LENGTH_INVALID(tlv, request_unicast_xmit_tlv)) { return -EBADMSG; } request = (struct request_unicast_xmit_tlv *) tlv; if (!unicast_message_type_valid(request->message_type)) { return -EBADMSG; } NTOHL(request->durationField); break; case TLV_GRANT_UNICAST_TRANSMISSION: if (TLV_LENGTH_INVALID(tlv, grant_unicast_xmit_tlv)) { return -EBADMSG; } grant = (struct grant_unicast_xmit_tlv *) tlv; if (!unicast_message_type_valid(grant->message_type)) { return -EBADMSG; } NTOHL(grant->durationField); break; case TLV_CANCEL_UNICAST_TRANSMISSION: if (TLV_LENGTH_INVALID(tlv, cancel_unicast_xmit_tlv)) { return -EBADMSG; } cancel = (struct cancel_unicast_xmit_tlv *) tlv; if (!unicast_message_type_valid(cancel->message_type_flags)) { return -EBADMSG; } break; case TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION: if (TLV_LENGTH_INVALID(tlv, ack_cancel_unicast_xmit_tlv)) { return -EBADMSG; } ack = (struct ack_cancel_unicast_xmit_tlv *) tlv; if (!unicast_message_type_valid(ack->message_type_flags)) { return -EBADMSG; } break; } return 0; } static void unicast_negotiation_pre_send(struct TLV *tlv) { struct request_unicast_xmit_tlv *request; struct grant_unicast_xmit_tlv *grant; switch (tlv->type) { case TLV_REQUEST_UNICAST_TRANSMISSION: request = (struct request_unicast_xmit_tlv *) tlv; HTONL(request->durationField); break; case TLV_GRANT_UNICAST_TRANSMISSION: grant = (struct grant_unicast_xmit_tlv *) tlv; HTONL(grant->durationField); break; case TLV_CANCEL_UNICAST_TRANSMISSION: case TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION: break; } } struct tlv_extra *tlv_extra_alloc(void) { struct tlv_extra *extra = TAILQ_FIRST(&tlv_pool); if (extra) { TAILQ_REMOVE(&tlv_pool, extra, list); } else { extra = calloc(1, sizeof(*extra)); } return extra; } void tlv_extra_cleanup(void) { struct tlv_extra *extra; while ((extra = TAILQ_FIRST(&tlv_pool)) != NULL) { TAILQ_REMOVE(&tlv_pool, extra, list); free(extra); } } void tlv_extra_recycle(struct tlv_extra *extra) { memset(extra, 0, sizeof(*extra)); TAILQ_INSERT_HEAD(&tlv_pool, extra, list); } int tlv_post_recv(struct tlv_extra *extra) { struct management_error_status *mes; struct TLV *tlv = extra->tlv; struct management_tlv *mgt; int result = 0; 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: result = unicast_negotiation_post_recv(extra); break; case TLV_PATH_TRACE: if (tlv_array_invalid(tlv, 0, sizeof(struct ClockIdentity))) { goto bad_length; } break; case TLV_ALTERNATE_TIME_OFFSET_INDICATOR: case TLV_AUTHENTICATION_2008: case TLV_AUTHENTICATION_CHALLENGE: case TLV_SECURITY_ASSOCIATION_UPDATE: case TLV_CUM_FREQ_SCALE_FACTOR_OFFSET: case TLV_PTPMON_REQ: break; case TLV_PTPMON_RESP: result = nsm_resp_post_recv(extra); break; case TLV_ORGANIZATION_EXTENSION_PROPAGATE: case TLV_ENHANCED_ACCURACY_METRICS: case TLV_ORGANIZATION_EXTENSION_DO_NOT_PROPAGATE: case TLV_L1_SYNC: case TLV_PORT_COMMUNICATION_AVAILABILITY: case TLV_PROTOCOL_ADDRESS: break; case TLV_SLAVE_RX_SYNC_TIMING_DATA: result = slave_rx_sync_timing_data_post_revc(extra); break; case TLV_SLAVE_RX_SYNC_COMPUTED_DATA: case TLV_SLAVE_TX_EVENT_TIMESTAMPS: break; case TLV_SLAVE_DELAY_TIMING_DATA_NP: result = slave_delay_timing_data_post_revc(extra); break; case TLV_CUMULATIVE_RATE_RATIO: case TLV_PAD: case TLV_AUTHENTICATION: 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: unicast_negotiation_pre_send(tlv); break; case TLV_PATH_TRACE: case TLV_ALTERNATE_TIME_OFFSET_INDICATOR: case TLV_AUTHENTICATION_2008: case TLV_AUTHENTICATION_CHALLENGE: case TLV_SECURITY_ASSOCIATION_UPDATE: case TLV_CUM_FREQ_SCALE_FACTOR_OFFSET: case TLV_PTPMON_REQ: break; case TLV_PTPMON_RESP: nsm_resp_pre_send(extra); break; case TLV_ORGANIZATION_EXTENSION_PROPAGATE: case TLV_ENHANCED_ACCURACY_METRICS: case TLV_ORGANIZATION_EXTENSION_DO_NOT_PROPAGATE: case TLV_L1_SYNC: case TLV_PORT_COMMUNICATION_AVAILABILITY: case TLV_PROTOCOL_ADDRESS: break; case TLV_SLAVE_RX_SYNC_TIMING_DATA: slave_rx_sync_timing_data_pre_send(extra); break; case TLV_SLAVE_RX_SYNC_COMPUTED_DATA: case TLV_SLAVE_TX_EVENT_TIMESTAMPS: break; case TLV_SLAVE_DELAY_TIMING_DATA_NP: slave_delay_timing_data_pre_send(extra); break; case TLV_CUMULATIVE_RATE_RATIO: case TLV_PAD: case TLV_AUTHENTICATION: default: break; } } linuxptp-3.1.1/tlv.h000066400000000000000000000275201407046267700144000ustar00rootroot00000000000000/** * @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 #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_2008 0x2000 #define TLV_AUTHENTICATION_CHALLENGE 0x2001 #define TLV_SECURITY_ASSOCIATION_UPDATE 0x2002 #define TLV_CUM_FREQ_SCALE_FACTOR_OFFSET 0x2003 #define TLV_PTPMON_REQ 0x21FE #define TLV_PTPMON_RESP 0x21FF #define TLV_ORGANIZATION_EXTENSION_PROPAGATE 0x4000 #define TLV_ENHANCED_ACCURACY_METRICS 0x4001 #define TLV_ORGANIZATION_EXTENSION_DO_NOT_PROPAGATE 0x8000 #define TLV_L1_SYNC 0x8001 #define TLV_PORT_COMMUNICATION_AVAILABILITY 0x8002 #define TLV_PROTOCOL_ADDRESS 0x8003 #define TLV_SLAVE_RX_SYNC_TIMING_DATA 0x8004 #define TLV_SLAVE_RX_SYNC_COMPUTED_DATA 0x8005 #define TLV_SLAVE_TX_EVENT_TIMESTAMPS 0x8006 #define TLV_SLAVE_DELAY_TIMING_DATA_NP 0x7F00 #define TLV_CUMULATIVE_RATE_RATIO 0x8007 #define TLV_PAD 0x8008 #define TLV_AUTHENTICATION 0x8009 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_EXTERNAL_PORT_CONFIGURATION_ENABLED 0x3000 #define TLV_HOLDOVER_UPGRADE_ENABLE 0x3002 #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 #define TLV_SYNCHRONIZATION_UNCERTAIN_NP 0xC006 /* 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_MASTER_ONLY 0x3001 #define TLV_EXT_PORT_CONFIG_PORT_DATA_SET 0x3003 #define TLV_SLAVE_EVENT_MONITORING 0x3004 // TODO - proposed value, missing in 1588 v2.1 #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 #define TLV_PORT_STATS_NP 0xC005 /* 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 /* Values for the SYNCHRONIZATION_UNCERTAIN_NP management TLV */ #define SYNC_UNCERTAIN_DONTCARE 0xff #define SYNC_UNCERTAIN_FALSE 0 #define SYNC_UNCERTAIN_TRUE 1 #define CANCEL_UNICAST_MAINTAIN_REQUEST (1 << 0) #define CANCEL_UNICAST_MAINTAIN_GRANT (1 << 1) #define GRANT_UNICAST_RENEWAL_INVITED (1 << 0) struct ack_cancel_unicast_xmit_tlv { Enumeration16 type; UInteger16 length; uint8_t message_type_flags; uint8_t reserved; } PACKED; struct cancel_unicast_xmit_tlv { Enumeration16 type; UInteger16 length; uint8_t message_type_flags; uint8_t reserved; } PACKED; struct grant_unicast_xmit_tlv { Enumeration16 type; UInteger16 length; uint8_t message_type; Integer8 logInterMessagePeriod; UInteger32 durationField; uint8_t reserved; uint8_t flags; } PACKED; 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; struct nsm_resp_tlv_head { Enumeration16 type; UInteger16 length; uint8_t port_state; uint8_t reserved; struct PortAddress parent_addr; } PACKED; struct nsm_resp_tlv_foot { struct parentDS parent; struct currentDS current; struct timePropertiesDS timeprop; struct Timestamp lastsync; } 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); } struct request_unicast_xmit_tlv { Enumeration16 type; UInteger16 length; uint8_t message_type; Integer8 logInterMessagePeriod; UInteger32 durationField; } PACKED; struct slave_delay_timing_record { UInteger16 sequenceId; struct Timestamp delayOriginTimestamp; TimeInterval totalCorrectionField; struct Timestamp delayResponseTimestamp; } PACKED; struct slave_delay_timing_data_tlv { Enumeration16 type; UInteger16 length; struct PortIdentity sourcePortIdentity; struct slave_delay_timing_record record[0]; } PACKED; #define SLAVE_DELAY_TIMING_MAX \ ((sizeof(struct message_data) - sizeof(struct signaling_msg) - \ sizeof(struct slave_delay_timing_data_tlv)) / \ sizeof(struct slave_delay_timing_record)) struct slave_rx_sync_timing_record { UInteger16 sequenceId; struct Timestamp syncOriginTimestamp; TimeInterval totalCorrectionField; Integer32 scaledCumulativeRateOffset; struct Timestamp syncEventIngressTimestamp; } PACKED; struct slave_rx_sync_timing_data_tlv { Enumeration16 type; UInteger16 length; struct PortIdentity sourcePortIdentity; struct slave_rx_sync_timing_record record[0]; } PACKED; #define SLAVE_RX_SYNC_TIMING_MAX \ ((sizeof(struct message_data) - sizeof(struct signaling_msg) - \ sizeof(struct slave_rx_sync_timing_data_tlv)) / \ sizeof(struct slave_rx_sync_timing_record)) 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 msg_interval_req_tlv { Enumeration16 type; UInteger16 length; Octet id[3]; Octet subtype[3]; Integer8 linkDelayInterval; Integer8 timeSyncInterval; Integer8 announceInterval; Octet flags; Octet reserved[2]; } 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; struct port_stats_np { struct PortIdentity portIdentity; struct PortStats stats; } PACKED; #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 { TAILQ_ENTRY(tlv_extra) list; struct TLV *tlv; union { struct mgmt_clock_description cd; struct nsm_resp_tlv_foot *foot; }; }; /** * Allocates a new tlv_extra structure. * @return Pointer to a new structure on success or NULL otherwise. */ struct tlv_extra *tlv_extra_alloc(void); /** * Release all of the memory in the tlv_extra cache. */ void tlv_extra_cleanup(void); /** * Frees a tlv_extra structure. * @param extra Pointer to the structure to free. */ void tlv_extra_recycle(struct tlv_extra *extra); /** * Converts recognized value sub-fields into host byte order. * @param extra TLV descriptor pointing to the protocol data. * @return Zero if successful, otherwise non-zero */ int tlv_post_recv(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-3.1.1/tmv.h000066400000000000000000000074221407046267700144000ustar00rootroot00000000000000/** * @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 #include "ddt.h" #include "pdt.h" #define NS_PER_SEC 1000000000LL #define MIN_TMV_TO_TIMEINTERVAL 0xFFFF800000000000ll #define MAX_TMV_TO_TIMEINTERVAL 0x00007FFFFFFFFFFFll /** * 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 struct { int64_t ns; } tmv_t; static inline tmv_t tmv_add(tmv_t a, tmv_t b) { tmv_t t; t.ns = a.ns + b.ns; return t; } static inline tmv_t tmv_div(tmv_t a, int divisor) { tmv_t t; t.ns = a.ns / divisor; return t; } static inline int tmv_cmp(tmv_t a, tmv_t b) { return a.ns == b.ns ? 0 : a.ns > b.ns ? +1 : -1; } static inline int tmv_sign(tmv_t x) { return x.ns == 0 ? 0 : x.ns > 0 ? +1 : -1; } static inline int tmv_is_zero(tmv_t x) { return x.ns == 0 ? 1 : 0; } static inline tmv_t tmv_sub(tmv_t a, tmv_t b) { tmv_t t; t.ns = a.ns - b.ns; return t; } static inline tmv_t tmv_zero(void) { tmv_t t = { 0 }; return t; } static inline tmv_t correction_to_tmv(Integer64 c) { tmv_t t; t.ns = (c >> 16); return t; } static inline double tmv_dbl(tmv_t x) { return (double) x.ns; } static inline tmv_t dbl_tmv(double x) { tmv_t t; t.ns = x; return t; } static inline int64_t tmv_to_nanoseconds(tmv_t x) { return x.ns; } static inline tmv_t nanoseconds_to_tmv(int64_t ns) { tmv_t t; t.ns = ns; return t; } static inline TimeInterval tmv_to_TimeInterval(tmv_t x) { if (x.ns < (int64_t)MIN_TMV_TO_TIMEINTERVAL) { return MIN_TMV_TO_TIMEINTERVAL << 16; } else if (x.ns > (int64_t)MAX_TMV_TO_TIMEINTERVAL) { return MAX_TMV_TO_TIMEINTERVAL << 16; } return x.ns << 16; } static inline struct Timestamp tmv_to_Timestamp(tmv_t x) { struct Timestamp result; uint64_t sec, nsec; sec = x.ns / 1000000000ULL; nsec = x.ns % 1000000000ULL; result.seconds_lsb = sec & 0xFFFFFFFF; result.seconds_msb = (sec >> 32) & 0xFFFF; result.nanoseconds = nsec; return result; } static inline tmv_t timespec_to_tmv(struct timespec ts) { tmv_t t; t.ns = ts.tv_sec * NS_PER_SEC + ts.tv_nsec; return t; } static inline struct timespec tmv_to_timespec(tmv_t t) { struct timespec ts; ts.tv_sec = t.ns / NS_PER_SEC; ts.tv_nsec = t.ns % NS_PER_SEC; return ts; } static inline tmv_t timestamp_to_tmv(struct timestamp ts) { tmv_t t; t.ns = ts.sec * NS_PER_SEC + ts.nsec; return t; } static inline tmv_t pct_to_tmv(struct ptp_clock_time pct) { tmv_t t; t.ns = pct.sec * NS_PER_SEC + pct.nsec; return t; } #endif linuxptp-3.1.1/transport.c000066400000000000000000000063771407046267700156310ustar00rootroot00000000000000/** * @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, struct interface *iface, struct fdarray *fda, enum timestamp_type tt) { return t->open(t, iface, 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, enum transport_event 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, enum transport_event 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, enum transport_event 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_txts(struct fdarray *fda, struct ptp_message *msg) { int cnt, len = ntohs(msg->header.messageLength); struct hw_timestamp *hwts = &msg->hwts; unsigned char pkt[1600]; cnt = sk_receive(fda->fd[FD_EVENT], pkt, len, NULL, hwts, MSG_ERRQUEUE); return cnt > 0 ? 0 : cnt; } 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-3.1.1/transport.h000066400000000000000000000121121407046267700156160ustar00rootroot00000000000000/** * @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; struct interface; /* 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, TRANS_P2P1STEP, TRANS_DEFER_EVENT, }; struct transport; int transport_close(struct transport *t, struct fdarray *fda); int transport_open(struct transport *t, struct interface *iface, 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 One of the @ref transport_event enumeration values. * @param msg The message to send. * @return Number of bytes sent, or negative value in case of an error. */ int transport_send(struct transport *t, struct fdarray *fda, enum transport_event 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 One of the @ref transport_event enumeration values. * @param msg The message to send. * @return Number of bytes sent, or negative value in case of an error. */ int transport_peer(struct transport *t, struct fdarray *fda, enum transport_event 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 One of the @ref transport_event enumeration values. * @param msg The message to send. The address of the destination has to * be set in the address field. * @return Number of bytes sent, or negative value in case of an error. */ int transport_sendto(struct transport *t, struct fdarray *fda, enum transport_event event, struct ptp_message *msg); /** * Fetches the transmit time stamp for a PTP message that was sent * with the TRANS_DEFER_EVENT flag. * * @param fda The array of descriptors filled in by transport_open. * @param msg The message previously sent using transport_send(), * transport_peer(), or transport_sendto(). * @return Zero on success, or negative value in case of an error. */ int transport_txts(struct fdarray *fda, 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-3.1.1/transport_private.h000066400000000000000000000033341407046267700173560ustar00rootroot00000000000000/** * @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, struct interface *iface, 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, enum transport_event 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-3.1.1/ts2phc.8000066400000000000000000000166501407046267700147200ustar00rootroot00000000000000.TH TS2PHC 8 "December 2019" "linuxptp" .SH NAME ts2phc - Synchronizes one or more PTP Hardware Clocks using external time stamps. .SH SYNOPSIS .B ts2phc [ .B \-hmqv ] [ .BI \-c " device|name" ] [ .BI \-f " config" ] [ .BI \-l " print-level" ] [ .BI \-s " device|name" ] [ .I long-options ] .I .\|.\|. .SH DESCRIPTION .B ts2phc synchronizes PTP Hardware Clocks (PHC) to external time stamp signals. A single source may be used to distribute time to one or more PHC devices. .SH OPTIONS .TP .BI \-c " device|name" Specifies a PHC slave clock to be synchronized. The clock may be identified by its character device (like /dev/ptp0) or its associated network interface (like eth0). This option may be given multiple times. .TP .BI \-f " config" Read configuration from the specified file. No configuration file is read by default. .TP .BI \-h Displays the command line help summary. .TP .BI \-l " print-level" Sets 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 Prints log messages to the standard output. .TP .B \-q Prevents sending log messages to the system logger. .TP .BI \-s " device|name" Specifies the source of the PPS signal. Use the key word "generic" for an external 1-PPS without ToD information. When using a master PHC, the clock may be identified by its character device (like /dev/ptp0) or its associated network interface (like eth0). Use the key word "nmea" for an external 1-PPS from a GPS providing ToD information via the RMC NMEA sentence. .TP .B \-v Prints the software version and exits. .SH LONG OPTIONS Each and every configuration file option (see below) may also appear as a "long" style command line argument. For example, the use_syslog option may be set using either of these two forms. .RS \f(CW\-\-use_syslog 1 \-\-use_syslog=1\fP .RE Option values given on the command line override values in the global section of the configuration file. .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. There are two different section types. .TP .B 1. The global section (indicated as .BR [global] ) sets the program options and default slave clock options. Other sections are clock specific sections, and they override the default options. .TP .B 2. Slave clock sections give the name of the configured slave (e.g. .BR [eth0] ). Slave clocks specified in the configuration file need not be specified with the .B \-c command line option. .SH GLOBAL OPTIONS .TP .B first_step_threshold The maximum offset, specified in seconds, that the servo will correct by changing the clock frequency instead of stepping the clock. This is only applied on the first update. When set to 0.0, the servo will not step the clock on start. The default is 0.00002 (20 microseconds). .TP .B free_running When set to 1, no the slave clock will be adjusted. This option can be useful in test scenarios, for example to determine how well synchronized a group of local clocks are to each other. The default is 0 (adjust the slave clocks). .TP .B leapfile The path to the current leap seconds definition file. In a Debian system this file is provided by the tzdata package and can be found at /usr/share/zoneinfo/leap-seconds.list. The default is an empty string, which causes the program to use a hard coded table that reflects the known leap seconds on the date of the software's release. .TP .B logging_level The maximum logging level of messages which should be printed. The default is 6 (LOG_INFO). .TP .B max_frequency The maximum allowed frequency adjustment of the clock in parts per billion. 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%). .TP .B message_tag The tag which is added to all messages printed to the standard output or system log. The default is an empty string (which cannot be set in the configuration file as the option requires an argument). .TP .B step_threshold The maximum offset, specified in seconds, that 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. The default is 0.0. .TP .B ts2phc.nmea_remote_host, ts2phc.nmea_remote_port Specifies the serial port character device providing ToD information when using the "nmea" PPS signal source. Note that if these two options are both specified, then the given remote connection will be used in preference to the configured serial port. These options default to the empty string, that is, not specified. .TP .B ts2phc.nmea_serialport Specifies the serial port character device providing ToD information when using the "nmea" PPS signal source. Note that if the options, ts2phc.nmea_remote_host and ts2phc.nmea_remote_port, are both specified, then the given remote connection will be used in preference to the configured serial port. The default is "/dev/ttyS0". .TP .B ts2phc.pulsewidth The expected pulse width of the external PPS signal in nanoseconds. When 'ts2phc.extts_polarity' is "both", the given pulse width is used to detect and discard the time stamp of the unwanted edge. The supported range is 1000000 to 990000000 nanoseconds. The default is 500000000 nanoseconds. .TP .B use_syslog Print messages to the system log if enabled. The default is 1 (enabled). .TP .B verbose Print messages to the standard output if enabled. The default is 0 (disabled). .SH SLAVE CLOCK OPTIONS .TP .B ts2phc.channel The external time stamping or periodic output channel to be used. Some PHC devices feature programmable pins and one or more time stamping channels. This option allows selecting a particular channel to be used. When using a PHC device as the PPS source, this option selects the periodic output channel. The default is channel 0. .TP .B ts2phc.extts_correction The value, in nanoseconds, to be added to each PPS time stamp. The default is 0 (no correction). .TP .B ts2phc.extts_polarity The polarity of the external PPS signal, either "rising" or "falling". Some PHC devices always time stamp both edges. Setting this option to "both" will allow the ts2phc program to work with such devices by detecting and ignoring the unwanted edge. In this case be sure to set 'ts2phc.pulsewidth' to the correct value. The default is "rising". .TP .B ts2phc.master Setting this option to 1 configures the given PHC device as the source of the PPS signal. The default is 0 for the slave role. .TP .B ts2phc.pin_index The pin index to be used. Some PHC devices feature programmable pins, and this option allows configuration of a particular pin for the external time stamping or periodic output function. The default is pin index 0. .SH WARNING Be cautious when sharing the same configuration file between ptp4l, phc2sys, and ts2phc. Keep in mind that values specified in the configuration file take precedence over the default values. If an option which is common to the other programs is set in the configuration file, then the value will be applied to all the programs using the file, and this might not be what is expected. It is recommended to use separate configuration files for ptp4l, phc2sys, and ts2phc in order to avoid any unexpected behavior. .SH SEE ALSO .BR phc2sys (8) .BR ptp4l (8) linuxptp-3.1.1/ts2phc.c000066400000000000000000000124121407046267700147630ustar00rootroot00000000000000/** * @file ts2phc.c * @brief Utility program to synchronize the PHC clock to external events * @note Copyright (C) 2013 Balint Ferencz * @note Based on the phc2sys utility * @note Copyright (C) 2012 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #include #include "config.h" #include "interface.h" #include "print.h" #include "ts2phc_master.h" #include "ts2phc_slave.h" #include "version.h" struct interface { STAILQ_ENTRY(interface) list; }; static void ts2phc_cleanup(struct config *cfg, struct ts2phc_master *master) { ts2phc_slave_cleanup(); if (master) { ts2phc_master_destroy(master); } if (cfg) { config_destroy(cfg); } } static void usage(char *progname) { fprintf(stderr, "\n" "usage: %s [options]\n\n" " -c [dev|name] phc slave clock (like /dev/ptp0 or eth0)\n" " (may be specified multiple times)\n" " -f [file] read configuration from 'file'\n" " -h prints this message and exits\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" " -s [dev|name] source of the PPS signal\n" " may take any of the following forms:\n" " generic - an external 1-PPS without ToD information\n" " /dev/ptp0 - a local PTP Hardware Clock (PHC)\n" " eth0 - a local PTP Hardware Clock (PHC)\n" " nmea - a gps device connected by serial port or network\n" " -v prints the software version and exits\n" "\n", progname); } int main(int argc, char *argv[]) { int c, err = 0, have_slave = 0, index, print_level; struct ts2phc_master *master = NULL; enum ts2phc_master_type pps_type; char *config = NULL, *progname; const char *pps_source = NULL; struct config *cfg = NULL; struct interface *iface; struct option *opts; handle_term_signals(); cfg = config_create(); if (!cfg) { ts2phc_cleanup(cfg, master); return -1; } opts = config_long_options(cfg); /* Process the command line arguments. */ progname = strrchr(argv[0], '/'); progname = progname ? 1 + progname : argv[0]; while (EOF != (c = getopt_long(argc, argv, "c:f:hi:l:mqs:v", opts, &index))) { switch (c) { case 0: if (config_parse_option(cfg, opts[index].name, optarg)) { ts2phc_cleanup(cfg, master); return -1; } break; case 'c': if (!config_create_interface(optarg, cfg)) { fprintf(stderr, "failed to add slave\n"); ts2phc_cleanup(cfg, master); return -1; } have_slave = 1; break; case 'f': config = optarg; break; case 'l': if (get_arg_val_i(c, optarg, &print_level, PRINT_LEVEL_MIN, PRINT_LEVEL_MAX)) { ts2phc_cleanup(cfg, master); return -1; } config_set_int(cfg, "logging_level", print_level); print_set_level(print_level); break; case 'm': config_set_int(cfg, "verbose", 1); print_set_verbose(1); break; case 'q': config_set_int(cfg, "use_syslog", 0); print_set_syslog(0); break; case 's': if (pps_source) { fprintf(stderr, "too many PPS sources\n"); ts2phc_cleanup(cfg, master); return -1; } pps_source = optarg; break; case 'v': ts2phc_cleanup(cfg, master); version_show(stdout); return 0; case 'h': ts2phc_cleanup(cfg, master); usage(progname); return -1; case '?': default: ts2phc_cleanup(cfg, master); usage(progname); return -1; } } if (config && (c = config_read(config, cfg))) { fprintf(stderr, "failed to read config\n"); ts2phc_cleanup(cfg, master); return -1; } print_set_progname(progname); print_set_tag(config_get_string(cfg, NULL, "message_tag")); 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")); STAILQ_FOREACH(iface, &cfg->interfaces, list) { if (1 == config_get_int(cfg, interface_name(iface), "ts2phc.master")) { if (pps_source) { fprintf(stderr, "too many PPS sources\n"); ts2phc_cleanup(cfg, master); return -1; } pps_source = interface_name(iface); } else { if (ts2phc_slave_add(cfg, interface_name(iface))) { fprintf(stderr, "failed to add slave\n"); ts2phc_cleanup(cfg, master); return -1; } have_slave = 1; } } if (!have_slave) { fprintf(stderr, "no slave clocks specified\n"); ts2phc_cleanup(cfg, master); usage(progname); return -1; } if (!pps_source) { fprintf(stderr, "no PPS source specified\n"); ts2phc_cleanup(cfg, master); usage(progname); return -1; } if (ts2phc_slave_arm()) { fprintf(stderr, "failed to arm slaves\n"); ts2phc_cleanup(cfg, master); return -1; } if (!strcasecmp(pps_source, "generic")) { pps_type = TS2PHC_MASTER_GENERIC; } else if (!strcasecmp(pps_source, "nmea")) { pps_type = TS2PHC_MASTER_NMEA; } else { pps_type = TS2PHC_MASTER_PHC; } master = ts2phc_master_create(cfg, pps_source, pps_type); if (!master) { fprintf(stderr, "failed to create master\n"); ts2phc_cleanup(cfg, master); return -1; } while (is_running()) { err = ts2phc_slave_poll(master); if (err) { pr_err("poll failed"); break; } } ts2phc_cleanup(cfg, master); return err; } linuxptp-3.1.1/ts2phc_generic_master.c000066400000000000000000000027131407046267700200350ustar00rootroot00000000000000/** * @file ts2phc_generic_master.c * @note Copyright (C) 2019 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #include #include #include "missing.h" #include "print.h" #include "ts2phc_generic_master.h" #include "ts2phc_master_private.h" #include "util.h" struct ts2phc_generic_master { struct ts2phc_master master; }; static void ts2phc_generic_master_destroy(struct ts2phc_master *master) { struct ts2phc_generic_master *s = container_of(master, struct ts2phc_generic_master, master); free(s); } /* * Returns the time on the PPS source device at which the most recent * PPS event was generated. This implementation assumes that the * system time is approximately correct. */ static int ts2phc_generic_master_getppstime(struct ts2phc_master *m, struct timespec *ts) { struct timex ntx; int code; memset(&ntx, 0, sizeof(ntx)); ntx.modes = ADJ_NANO; code = adjtimex(&ntx); if (code == -1) { pr_err("adjtimex failed: %m"); return -1; } ts->tv_sec = ntx.time.tv_sec + ntx.tai; ts->tv_nsec = ntx.time.tv_usec; return 0; } struct ts2phc_master *ts2phc_generic_master_create(struct config *cfg, const char *dev) { struct ts2phc_generic_master *master; master = calloc(1, sizeof(*master)); if (!master) { return NULL; } master->master.destroy = ts2phc_generic_master_destroy; master->master.getppstime = ts2phc_generic_master_getppstime; return &master->master; } linuxptp-3.1.1/ts2phc_generic_master.h000066400000000000000000000005541407046267700200430ustar00rootroot00000000000000/** * @file ts2phc_generic_master.h * @note Copyright (C) 2019 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_TS2PHC_GENERIC_MASTER_H #define HAVE_TS2PHC_GENERIC_MASTER_H #include "ts2phc_master.h" struct ts2phc_master *ts2phc_generic_master_create(struct config *cfg, const char *dev); #endif linuxptp-3.1.1/ts2phc_master.c000066400000000000000000000016601407046267700163410ustar00rootroot00000000000000/** * @file ts2phc_master.c * @note Copyright (C) 2019 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #include "ts2phc_generic_master.h" #include "ts2phc_master_private.h" #include "ts2phc_nmea_master.h" #include "ts2phc_phc_master.h" struct ts2phc_master *ts2phc_master_create(struct config *cfg, const char *dev, enum ts2phc_master_type type) { struct ts2phc_master *master = NULL; switch (type) { case TS2PHC_MASTER_GENERIC: master = ts2phc_generic_master_create(cfg, dev); break; case TS2PHC_MASTER_NMEA: master = ts2phc_nmea_master_create(cfg, dev); break; case TS2PHC_MASTER_PHC: master = ts2phc_phc_master_create(cfg, dev); break; } return master; } void ts2phc_master_destroy(struct ts2phc_master *master) { master->destroy(master); } int ts2phc_master_getppstime(struct ts2phc_master *master, struct timespec *ts) { return master->getppstime(master, ts); } linuxptp-3.1.1/ts2phc_master.h000066400000000000000000000026321407046267700163460ustar00rootroot00000000000000/** * @file ts2phc_master.h * @note Copyright (C) 2019 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_TS2PHC_MASTER_H #define HAVE_TS2PHC_MASTER_H #include struct config; /** * Opaque type */ struct ts2phc_master; /** * Defines the available PPS master clocks. */ enum ts2phc_master_type { TS2PHC_MASTER_GENERIC, TS2PHC_MASTER_NMEA, TS2PHC_MASTER_PHC, }; /** * Create a new instance of a PPS master clock. * @param cfg Pointer to a valid configuration. * @param dev Name of the master clock or NULL. * @param type The type of the clock to create. * @return A pointer to a new PPS master clock on success, NULL otherwise. */ struct ts2phc_master *ts2phc_master_create(struct config *cfg, const char *dev, enum ts2phc_master_type type); /** * Destroy an instance of a PPS master clock. * @param master Pointer to a master obtained via @ref ts2phc_master_create(). */ void ts2phc_master_destroy(struct ts2phc_master *master); /** * Returns the time on the PPS source device at which the most recent * PPS event was generated. * @param master Pointer to a master obtained via @ref ts2phc_master_create(). * @param ts Buffer to hold the time of the last PPS event. * @return Zero if the reported time is valid, non-zero otherwise. */ int ts2phc_master_getppstime(struct ts2phc_master *master, struct timespec *ts); #endif linuxptp-3.1.1/ts2phc_master_private.h000066400000000000000000000007361407046267700201030ustar00rootroot00000000000000/** * @file ts2phc_master_private.h * @note Copyright (C) 2019 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_TS2PHC_MASTER_PRIVATE_H #define HAVE_TS2PHC_MASTER_PRIVATE_H #include #include #include "contain.h" #include "ts2phc_master.h" struct ts2phc_master { void (*destroy)(struct ts2phc_master *ts2phc_master); int (*getppstime)(struct ts2phc_master *master, struct timespec *ts); }; #endif linuxptp-3.1.1/ts2phc_nmea_master.c000066400000000000000000000155361407046267700173500ustar00rootroot00000000000000/** * @file ts2phc_nmea_master.c * @note Copyright (C) 2019 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include #include #include "config.h" #include "lstab.h" #include "missing.h" #include "nmea.h" #include "print.h" #include "serial.h" #include "sock.h" #include "tmv.h" #include "ts2phc_master_private.h" #include "ts2phc_nmea_master.h" #include "util.h" #define BAUD 9600 #define MAX_RMC_AGE 5000000000ULL #define NMEA_TMO 2000 /*milliseconds*/ struct ts2phc_nmea_master { struct ts2phc_master master; struct config *config; const char *leapfile; time_t lsfile_mtime; struct lstab *lstab; pthread_t worker; /* Protects anonymous struct fields, below, from concurrent access. */ pthread_mutex_t mutex; struct { struct timespec local_monotime; struct timespec local_utctime; struct timespec rmc_utctime; bool rmc_fix_valid; }; }; static int open_nmea_connection(const char *host, const char *port, const char *serialport) { int fd; if (host[0] && port[0]) { fd = sock_open(host, port); if (fd == -1) { pr_err("failed to open nmea source %s:%s", host, port); } return fd; } fd = serial_open(serialport, BAUD, 0, 0); if (fd == -1) { pr_err("failed to open nmea source %s", serialport); } return fd; } static void *monitor_nmea_status(void *arg) { struct nmea_parser *np = nmea_parser_create(); struct pollfd pfd = { -1, POLLIN | POLLPRI }; char *host, input[256], *port, *ptr, *uart; struct ts2phc_nmea_master *master = arg; struct timespec rxtime, tmo = { 2, 0 }; int cnt, num, parsed; struct nmea_rmc rmc; struct timex ntx; if (!np) { pr_err("failed to create NMEA parser"); return NULL; } host = config_get_string(master->config, NULL, "ts2phc.nmea_remote_host"); port = config_get_string(master->config, NULL, "ts2phc.nmea_remote_port"); uart = config_get_string(master->config, NULL, "ts2phc.nmea_serialport"); memset(&ntx, 0, sizeof(ntx)); ntx.modes = ADJ_NANO; while (is_running()) { if (pfd.fd == -1) { pfd.fd = open_nmea_connection(host, port, uart); if (pfd.fd == -1) { clock_nanosleep(CLOCK_MONOTONIC, 0, &tmo, NULL); continue; } } num = poll(&pfd, 1, NMEA_TMO); clock_gettime(CLOCK_MONOTONIC, &rxtime); adjtimex(&ntx); if (num < 0) { pr_err("poll failed"); break; } if (!num) { pr_err("nmea source timed out"); close(pfd.fd); pfd.fd = -1; continue; } if (pfd.revents & POLLERR) { pr_err("nmea source socket error"); close(pfd.fd); pfd.fd = -1; continue; } if (!(pfd.revents & (POLLIN | POLLPRI))) { continue; } cnt = read(pfd.fd, input, sizeof(input)); if (cnt < 0) { pr_err("failed to read from nmea source"); close(pfd.fd); pfd.fd = -1; continue; } ptr = input; do { if (!nmea_parse(np, ptr, cnt, &rmc, &parsed)) { pthread_mutex_lock(&master->mutex); master->local_monotime = rxtime; master->local_utctime.tv_sec = ntx.time.tv_sec; master->local_utctime.tv_nsec = ntx.time.tv_usec; master->rmc_utctime = rmc.ts; master->rmc_fix_valid = rmc.fix_valid; pthread_mutex_unlock(&master->mutex); } cnt -= parsed; ptr += parsed; } while (cnt); } nmea_parser_destroy(np); if (pfd.fd != -1) { close(pfd.fd); } return NULL; } static int update_leapsecond_table(struct ts2phc_nmea_master *master) { struct stat statbuf; int err; if (!master->leapfile) { return 0; } err = stat(master->leapfile, &statbuf); if (err) { pr_err("nmea: file status failed on %s: %m", master->leapfile); return -1; } if (master->lsfile_mtime == statbuf.st_mtim.tv_sec) { return 0; } pr_info("nmea: updating leap seconds file"); if (master->lstab) { lstab_destroy(master->lstab); } master->lstab = lstab_create(master->leapfile); if (!master->lstab) { return -1; } master->lsfile_mtime = statbuf.st_mtim.tv_sec; return 0; } static void ts2phc_nmea_master_destroy(struct ts2phc_master *master) { struct ts2phc_nmea_master *m = container_of(master, struct ts2phc_nmea_master, master); pthread_join(m->worker, NULL); pthread_mutex_destroy(&m->mutex); lstab_destroy(m->lstab); free(m); } static int ts2phc_nmea_master_getppstime(struct ts2phc_master *master, struct timespec *ts) { struct ts2phc_nmea_master *m = container_of(master, struct ts2phc_nmea_master, master); tmv_t delay_t1, delay_t2, duration_since_rmc, local_t1, local_t2, rmc; int lstab_error = 0, tai_offset = 0; enum lstab_result result; struct timespec now; int64_t utc_time; bool fix_valid; clock_gettime(CLOCK_MONOTONIC, &now); local_t2 = timespec_to_tmv(now); pthread_mutex_lock(&m->mutex); local_t1 = timespec_to_tmv(m->local_monotime); delay_t2 = timespec_to_tmv(m->local_utctime); rmc = timespec_to_tmv(m->rmc_utctime); fix_valid = m->rmc_fix_valid; pthread_mutex_unlock(&m->mutex); if (!fix_valid) { pr_debug("nmea: no valid rmc fix"); return -1; } delay_t1 = rmc; pr_debug("nmea delay: %" PRId64 " ns", tmv_to_nanoseconds(tmv_sub(delay_t2, delay_t1))); duration_since_rmc = tmv_sub(local_t2, local_t1); if (tmv_to_nanoseconds(duration_since_rmc) > MAX_RMC_AGE) { pr_err("nmea: rmc time stamp stale"); return -1; } rmc = tmv_add(rmc, duration_since_rmc); utc_time = tmv_to_nanoseconds(rmc); utc_time /= (int64_t) 1000000000; *ts = tmv_to_timespec(rmc); if (update_leapsecond_table(m)) { pr_err("nmea: failed to update leap seconds table"); return -1; } result = lstab_utc2tai(m->lstab, utc_time, &tai_offset); switch (result) { case LSTAB_OK: lstab_error = 0; break; case LSTAB_UNKNOWN: pr_err("nmea: unable to find utc time in leap second table"); lstab_error = -1; break; case LSTAB_AMBIGUOUS: pr_err("nmea: utc time stamp is ambiguous"); lstab_error = -1; break; } ts->tv_sec += tai_offset; return lstab_error; } struct ts2phc_master *ts2phc_nmea_master_create(struct config *cfg, const char *dev) { struct ts2phc_nmea_master *master; struct stat statbuf; int err; master = calloc(1, sizeof(*master)); if (!master) { return NULL; } master->leapfile = config_get_string(cfg, NULL, "leapfile"); master->lstab = lstab_create(master->leapfile); if (!master->lstab) { free(master); return NULL; } if (master->leapfile) { err = stat(master->leapfile, &statbuf); if (err) { lstab_destroy(master->lstab); free(master); return NULL; } master->lsfile_mtime = statbuf.st_mtim.tv_sec; } master->master.destroy = ts2phc_nmea_master_destroy; master->master.getppstime = ts2phc_nmea_master_getppstime; master->config = cfg; pthread_mutex_init(&master->mutex, NULL); err = pthread_create(&master->worker, NULL, monitor_nmea_status, master); if (err) { pr_err("failed to create worker thread: %s", strerror(err)); lstab_destroy(master->lstab); free(master); return NULL; } return &master->master; } linuxptp-3.1.1/ts2phc_nmea_master.h000066400000000000000000000005341407046267700173450ustar00rootroot00000000000000/** * @file ts2phc_nmea_master.h * @note Copyright (C) 2019 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_TS2PHC_NMEA_MASTER_H #define HAVE_TS2PHC_NMEA_MASTER_H #include "ts2phc_master.h" struct ts2phc_master *ts2phc_nmea_master_create(struct config *cfg, const char *dev); #endif linuxptp-3.1.1/ts2phc_phc_master.c000066400000000000000000000054551407046267700172010ustar00rootroot00000000000000/** * @file ts2phc_phc_master.c * @note Copyright (C) 2019 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include "config.h" #include "phc.h" #include "print.h" #include "missing.h" #include "ts2phc_master_private.h" #include "ts2phc_phc_master.h" #include "util.h" struct ts2phc_phc_master { struct ts2phc_master master; clockid_t clkid; int channel; int fd; }; static int ts2phc_phc_master_activate(struct config *cfg, const char *dev, struct ts2phc_phc_master *master) { struct ptp_perout_request perout_request; struct ptp_pin_desc desc; struct timespec ts; memset(&desc, 0, sizeof(desc)); master->channel = config_get_int(cfg, dev, "ts2phc.channel"); desc.index = config_get_int(cfg, dev, "ts2phc.pin_index"); desc.func = PTP_PF_PEROUT; desc.chan = master->channel; if (phc_pin_setfunc(master->clkid, &desc)) { pr_warning("Failed to set the pin. Continuing bravely on..."); } if (clock_gettime(master->clkid, &ts)) { perror("clock_gettime"); return -1; } memset(&perout_request, 0, sizeof(perout_request)); perout_request.index = master->channel; perout_request.start.sec = ts.tv_sec + 2; perout_request.start.nsec = 0; perout_request.period.sec = 1; perout_request.period.nsec = 0; if (ioctl(master->fd, PTP_PEROUT_REQUEST2, &perout_request)) { pr_err(PTP_PEROUT_REQUEST_FAILED); return -1; } return 0; } static void ts2phc_phc_master_destroy(struct ts2phc_master *master) { struct ts2phc_phc_master *m = container_of(master, struct ts2phc_phc_master, master); struct ptp_perout_request perout_request; memset(&perout_request, 0, sizeof(perout_request)); perout_request.index = m->channel; if (ioctl(m->fd, PTP_PEROUT_REQUEST2, &perout_request)) { pr_err(PTP_PEROUT_REQUEST_FAILED); } posix_clock_close(m->clkid); free(m); } static int ts2phc_phc_master_getppstime(struct ts2phc_master *m, struct timespec *ts) { struct ts2phc_phc_master *master = container_of(m, struct ts2phc_phc_master, master); return clock_gettime(master->clkid, ts); } struct ts2phc_master *ts2phc_phc_master_create(struct config *cfg, const char *dev) { struct ts2phc_phc_master *master; int junk; master = calloc(1, sizeof(*master)); if (!master) { return NULL; } master->master.destroy = ts2phc_phc_master_destroy; master->master.getppstime = ts2phc_phc_master_getppstime; master->clkid = posix_clock_open(dev, &junk); if (master->clkid == CLOCK_INVALID) { free(master); return NULL; } master->fd = CLOCKID_TO_FD(master->clkid); pr_debug("PHC master %s has ptp index %d", dev, junk); if (ts2phc_phc_master_activate(cfg, dev, master)) { ts2phc_phc_master_destroy(&master->master); return NULL; } return &master->master; } linuxptp-3.1.1/ts2phc_phc_master.h000066400000000000000000000005371407046267700172020ustar00rootroot00000000000000/** * @file ts2phc_phc_master.h * @note Copyright (C) 2019 Richard Cochran * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_TS2PHC_PHC_MASTER_H #define HAVE_TS2PHC_PHC_MASTER_H #include "ts2phc_master.h" struct ts2phc_master *ts2phc_phc_master_create(struct config *cfg, const char *dev); #endif linuxptp-3.1.1/ts2phc_slave.c000066400000000000000000000237441407046267700161670ustar00rootroot00000000000000/** * @file ts2phc_slave.c * @brief Utility program to synchronize the PHC clock to external events * @note Copyright (C) 2019 Balint Ferencz * @note SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "clockadj.h" #include "missing.h" #include "phc.h" #include "print.h" #include "servo.h" #include "ts2phc_master.h" #include "ts2phc_slave.h" #include "util.h" #define NS_PER_SEC 1000000000LL #define SAMPLE_WEIGHT 1.0 #define SERVO_SYNC_INTERVAL 1.0 struct ts2phc_slave { char *name; STAILQ_ENTRY(ts2phc_slave) list; struct ptp_pin_desc pin_desc; enum servo_state state; unsigned int polarity; int32_t correction; uint32_t ignore_lower; uint32_t ignore_upper; struct servo *servo; clockid_t clk; int no_adj; int fd; }; struct ts2phc_slave_array { struct ts2phc_slave **slave; struct pollfd *pfd; } polling_array; struct ts2phc_source_timestamp { struct timespec ts; bool valid; }; enum extts_result { EXTTS_ERROR = -1, EXTTS_OK = 0, EXTTS_IGNORE = 1, }; static enum extts_result ts2phc_slave_offset(struct ts2phc_slave *slave, struct ts2phc_source_timestamp ts, int64_t *offset, uint64_t *local_ts); static STAILQ_HEAD(slave_ifaces_head, ts2phc_slave) ts2phc_slaves = STAILQ_HEAD_INITIALIZER(ts2phc_slaves); static unsigned int ts2phc_n_slaves; static int ts2phc_slave_array_create(void) { struct ts2phc_slave *slave; unsigned int i; if (polling_array.slave) { return 0; } polling_array.slave = malloc(ts2phc_n_slaves * sizeof(*polling_array.slave)); if (!polling_array.slave) { pr_err("low memory"); return -1; } polling_array.pfd = malloc(ts2phc_n_slaves * sizeof(*polling_array.pfd)); if (!polling_array.pfd) { pr_err("low memory"); free(polling_array.slave); polling_array.slave = NULL; return -1; } i = 0; STAILQ_FOREACH(slave, &ts2phc_slaves, list) { polling_array.slave[i] = slave; i++; } for (i = 0; i < ts2phc_n_slaves; i++) { polling_array.pfd[i].events = POLLIN | POLLPRI; polling_array.pfd[i].fd = polling_array.slave[i]->fd; } return 0; } static void ts2phc_slave_array_destroy(void) { free(polling_array.slave); free(polling_array.pfd); polling_array.slave = NULL; polling_array.pfd = NULL; } static int ts2phc_slave_clear_fifo(struct ts2phc_slave *slave) { struct pollfd pfd = { .events = POLLIN | POLLPRI, .fd = slave->fd, }; struct ptp_extts_event event; int cnt, size; while (1) { cnt = poll(&pfd, 1, 0); if (cnt < 0) { if (EINTR == errno) { continue; } else { pr_emerg("poll failed"); return -1; } } else if (!cnt) { break; } size = read(pfd.fd, &event, sizeof(event)); if (size != sizeof(event)) { pr_err("read failed"); return -1; } pr_debug("%s SKIP extts index %u at %lld.%09u", slave->name, event.index, event.t.sec, event.t.nsec); } return 0; } static struct ts2phc_slave *ts2phc_slave_create(struct config *cfg, const char *device) { enum servo_type servo = config_get_int(cfg, NULL, "clock_servo"); int err, fadj, junk, max_adj, pulsewidth; struct ptp_extts_request extts; struct ts2phc_slave *slave; slave = calloc(1, sizeof(*slave)); if (!slave) { pr_err("low memory"); return NULL; } slave->name = strdup(device); if (!slave->name) { pr_err("low memory"); free(slave); return NULL; } slave->pin_desc.index = config_get_int(cfg, device, "ts2phc.pin_index"); slave->pin_desc.func = PTP_PF_EXTTS; slave->pin_desc.chan = config_get_int(cfg, device, "ts2phc.channel"); slave->polarity = config_get_int(cfg, device, "ts2phc.extts_polarity"); slave->correction = config_get_int(cfg, device, "ts2phc.extts_correction"); pulsewidth = config_get_int(cfg, device, "ts2phc.pulsewidth"); pulsewidth /= 2; slave->ignore_upper = 1000000000 - pulsewidth; slave->ignore_lower = pulsewidth; slave->clk = posix_clock_open(device, &junk); if (slave->clk == CLOCK_INVALID) { pr_err("failed to open clock"); goto no_posix_clock; } slave->no_adj = config_get_int(cfg, NULL, "free_running"); slave->fd = CLOCKID_TO_FD(slave->clk); pr_debug("PHC slave %s has ptp index %d", device, junk); fadj = (int) clockadj_get_freq(slave->clk); /* 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(slave->clk, fadj); max_adj = phc_max_adj(slave->clk); slave->servo = servo_create(cfg, servo, -fadj, max_adj, 0); if (!slave->servo) { pr_err("failed to create servo"); goto no_servo; } servo_sync_interval(slave->servo, SERVO_SYNC_INTERVAL); if (phc_number_pins(slave->clk) > 0) { err = phc_pin_setfunc(slave->clk, &slave->pin_desc); if (err < 0) { pr_err("PTP_PIN_SETFUNC request failed"); goto no_pin_func; } } /* * Disable external time stamping, and then read out any stale * time stamps. */ memset(&extts, 0, sizeof(extts)); extts.index = slave->pin_desc.chan; extts.flags = 0; if (ioctl(slave->fd, PTP_EXTTS_REQUEST2, &extts)) { pr_err(PTP_EXTTS_REQUEST_FAILED); } if (ts2phc_slave_clear_fifo(slave)) { goto no_ext_ts; } return slave; no_ext_ts: no_pin_func: servo_destroy(slave->servo); no_servo: posix_clock_close(slave->clk); no_posix_clock: free(slave->name); free(slave); return NULL; } static void ts2phc_slave_destroy(struct ts2phc_slave *slave) { struct ptp_extts_request extts; memset(&extts, 0, sizeof(extts)); extts.index = slave->pin_desc.chan; extts.flags = 0; if (ioctl(slave->fd, PTP_EXTTS_REQUEST2, &extts)) { pr_err(PTP_EXTTS_REQUEST_FAILED); } servo_destroy(slave->servo); posix_clock_close(slave->clk); free(slave->name); free(slave); } static int ts2phc_slave_event(struct ts2phc_slave *slave, struct ts2phc_source_timestamp source_ts) { enum extts_result result; uint64_t extts_ts; int64_t offset; double adj; result = ts2phc_slave_offset(slave, source_ts, &offset, &extts_ts); switch (result) { case EXTTS_ERROR: return -1; case EXTTS_OK: break; case EXTTS_IGNORE: return 0; } if (slave->no_adj) { pr_info("%s master offset %10" PRId64, slave->name, offset); return 0; } if (!source_ts.valid) { pr_debug("%s ignoring invalid master time stamp", slave->name); return 0; } adj = servo_sample(slave->servo, offset, extts_ts, SAMPLE_WEIGHT, &slave->state); pr_debug("%s master offset %10" PRId64 " s%d freq %+7.0f", slave->name, offset, slave->state, adj); switch (slave->state) { case SERVO_UNLOCKED: break; case SERVO_JUMP: clockadj_set_freq(slave->clk, -adj); clockadj_step(slave->clk, -offset); break; case SERVO_LOCKED: case SERVO_LOCKED_STABLE: clockadj_set_freq(slave->clk, -adj); break; } return 0; } static enum extts_result ts2phc_slave_offset(struct ts2phc_slave *slave, struct ts2phc_source_timestamp src, int64_t *offset, uint64_t *local_ts) { struct timespec source_ts = src.ts; struct ptp_extts_event event; uint64_t event_ns, source_ns; int cnt; cnt = read(slave->fd, &event, sizeof(event)); if (cnt != sizeof(event)) { pr_err("read extts event failed: %m"); return EXTTS_ERROR; } if (event.index != slave->pin_desc.chan) { pr_err("extts on unexpected channel"); return EXTTS_ERROR; } event_ns = event.t.sec * NS_PER_SEC; event_ns += event.t.nsec; if (slave->polarity == (PTP_RISING_EDGE | PTP_FALLING_EDGE) && source_ts.tv_nsec > slave->ignore_lower && source_ts.tv_nsec < slave->ignore_upper) { pr_debug("%s SKIP extts index %u at %lld.%09u src %" PRIi64 ".%ld", slave->name, event.index, event.t.sec, event.t.nsec, (int64_t) source_ts.tv_sec, source_ts.tv_nsec); return EXTTS_IGNORE; } if (source_ts.tv_nsec > 500000000) { source_ts.tv_sec++; } source_ns = source_ts.tv_sec * NS_PER_SEC; *offset = event_ns + slave->correction - source_ns; *local_ts = event_ns + slave->correction; pr_debug("%s extts index %u at %lld.%09u corr %d src %" PRIi64 ".%ld diff %" PRId64, slave->name, event.index, event.t.sec, event.t.nsec, slave->correction, (int64_t) source_ts.tv_sec, source_ts.tv_nsec, *offset); return EXTTS_OK; } /* public methods */ int ts2phc_slave_add(struct config *cfg, const char *name) { struct ts2phc_slave *slave; /* Create each interface only once. */ STAILQ_FOREACH(slave, &ts2phc_slaves, list) { if (0 == strcmp(name, slave->name)) { return 0; } } slave = ts2phc_slave_create(cfg, name); if (!slave) { pr_err("failed to create slave"); return -1; } STAILQ_INSERT_TAIL(&ts2phc_slaves, slave, list); ts2phc_n_slaves++; return 0; } int ts2phc_slave_arm(void) { struct ptp_extts_request extts; struct ts2phc_slave *slave; int err; memset(&extts, 0, sizeof(extts)); STAILQ_FOREACH(slave, &ts2phc_slaves, list) { extts.index = slave->pin_desc.chan; extts.flags = slave->polarity | PTP_ENABLE_FEATURE; err = ioctl(slave->fd, PTP_EXTTS_REQUEST2, &extts); if (err < 0) { pr_err(PTP_EXTTS_REQUEST_FAILED); return -1; } } return 0; } void ts2phc_slave_cleanup(void) { struct ts2phc_slave *slave; ts2phc_slave_array_destroy(); while ((slave = STAILQ_FIRST(&ts2phc_slaves))) { STAILQ_REMOVE_HEAD(&ts2phc_slaves, list); ts2phc_slave_destroy(slave); ts2phc_n_slaves--; } } int ts2phc_slave_poll(struct ts2phc_master *master) { struct ts2phc_source_timestamp source_ts; unsigned int i; int cnt, err; if (ts2phc_slave_array_create()) { return -1; } cnt = poll(polling_array.pfd, ts2phc_n_slaves, 2000); if (cnt < 0) { if (EINTR == errno) { return 0; } else { pr_emerg("poll failed"); return -1; } } else if (!cnt) { pr_debug("poll returns zero, no events"); return 0; } err = ts2phc_master_getppstime(master, &source_ts.ts); source_ts.valid = err ? false : true; for (i = 0; i < ts2phc_n_slaves; i++) { if (polling_array.pfd[i].revents & (POLLIN|POLLPRI)) { ts2phc_slave_event(polling_array.slave[i], source_ts); } } return 0; } linuxptp-3.1.1/ts2phc_slave.h000066400000000000000000000007421407046267700161650ustar00rootroot00000000000000/** * @file ts2phc_slave.h * @brief Utility program to synchronize the PHC clock to external events * @note Copyright (C) 2019 Balint Ferencz * @note SPDX-License-Identifier: GPL-2.0+ */ #ifndef HAVE_TS2PHC_SLAVE_H #define HAVE_TS2PHC_SLAVE_H #include "ts2phc_master.h" int ts2phc_slave_add(struct config *cfg, const char *name); int ts2phc_slave_arm(void); void ts2phc_slave_cleanup(void); int ts2phc_slave_poll(struct ts2phc_master *master); #endif linuxptp-3.1.1/tsproc.c000066400000000000000000000121621407046267700150740ustar00rootroot00000000000000/** * @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 */ enum tsproc_mode mode; /* 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; int filtered_delay_valid; /* Delay filter */ struct filter *delay_filter; }; static int weighting(struct tsproc *tsp) { switch (tsp->mode) { case TSPROC_FILTER: case TSPROC_RAW: return 0; case TSPROC_FILTER_WEIGHT: case TSPROC_RAW_WEIGHT: return 1; } return 0; } 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: case TSPROC_RAW: case TSPROC_FILTER_WEIGHT: case TSPROC_RAW_WEIGHT: tsp->mode = mode; 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; tsp->filtered_delay_valid = 1; } 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 (tmv_sign(delay) < 0) { pr_debug("negative delay %10" PRId64, tmv_to_nanoseconds(delay)); pr_debug("delay = (t2 - t3) * rr + (t4 - t1)"); pr_debug("t2 - t3 = %+10" PRId64, tmv_to_nanoseconds(t23)); pr_debug("t4 - t1 = %+10" PRId64, tmv_to_nanoseconds(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->t2) || tmv_is_zero(tsp->t3)) return -1; raw_delay = get_raw_delay(tsp); tsp->filtered_delay = filter_sample(tsp->delay_filter, raw_delay); tsp->filtered_delay_valid = 1; pr_debug("delay filtered %10" PRId64 " raw %10" PRId64, tmv_to_nanoseconds(tsp->filtered_delay), tmv_to_nanoseconds(raw_delay)); if (!delay) { return 0; } switch (tsp->mode) { case TSPROC_FILTER: case TSPROC_FILTER_WEIGHT: *delay = tsp->filtered_delay; break; case TSPROC_RAW: case TSPROC_RAW_WEIGHT: *delay = raw_delay; break; } return 0; } int tsproc_update_offset(struct tsproc *tsp, tmv_t *offset, double *weight) { tmv_t delay = tmv_zero(), raw_delay = tmv_zero(); if (tmv_is_zero(tsp->t1) || tmv_is_zero(tsp->t2)) return -1; switch (tsp->mode) { case TSPROC_FILTER: if (!tsp->filtered_delay_valid) { return -1; } delay = tsp->filtered_delay; break; case TSPROC_RAW: case TSPROC_RAW_WEIGHT: if (tmv_is_zero(tsp->t3)) { return -1; } raw_delay = get_raw_delay(tsp); delay = raw_delay; break; case TSPROC_FILTER_WEIGHT: if (tmv_is_zero(tsp->t3) || !tsp->filtered_delay_valid) { return -1; } raw_delay = get_raw_delay(tsp); delay = tsp->filtered_delay; break; } /* offset = t2 - t1 - delay */ *offset = tmv_sub(tmv_sub(tsp->t2, tsp->t1), delay); if (!weight) return 0; if (weighting(tsp) && tmv_sign(tsp->filtered_delay) > 0 && tmv_sign(raw_delay) > 0) { *weight = tmv_dbl(tsp->filtered_delay) / tmv_dbl(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); tsp->filtered_delay_valid = 0; } } linuxptp-3.1.1/tsproc.h000066400000000000000000000073621407046267700151070ustar00rootroot00000000000000/** * @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-3.1.1/udp.c000066400000000000000000000170501407046267700143530ustar00rootroot00000000000000/** * @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 "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_in *sa) { int err, off = 0; struct ip_mreqn req; 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, &addr)) { pr_err("mcast_join failed"); goto no_option; } addr.sin_addr = mc_addr[1]; if (mcast_join(fd, index, &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, struct interface *iface, struct fdarray *fda, enum timestamp_type ts_type) { struct udp *udp = container_of(t, struct udp, t); const char *name = interface_name(iface); uint8_t event_dscp, general_dscp; 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, interface_label(iface), ts_type, TRANS_UDP_IPV4)) goto no_timestamping; if (sk_general_init(gfd)) goto no_timestamping; event_dscp = config_get_int(t->cfg, NULL, "dscp_event"); general_dscp = config_get_int(t->cfg, NULL, "dscp_general"); if (event_dscp && sk_set_priority(efd, AF_INET, event_dscp)) { pr_warning("Failed to set event DSCP priority."); } if (general_dscp && sk_set_priority(gfd, AF_INET, general_dscp)) { pr_warning("Failed to set general DSCP priority."); } 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, MSG_DONTWAIT); } static int udp_send(struct transport *t, struct fdarray *fda, enum transport_event event, int peer, void *buf, int len, struct address *addr, struct hw_timestamp *hwts) { struct address addr_buf; unsigned char junk[1600]; ssize_t cnt; int fd = -1; switch (event) { case TRANS_GENERAL: fd = fda->fd[FD_GENERAL]; break; case TRANS_EVENT: case TRANS_ONESTEP: case TRANS_P2P1STEP: case TRANS_DEFER_EVENT: fd = fda->fd[FD_EVENT]; break; } 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 -errno; } /* * 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-3.1.1/udp.h000066400000000000000000000021451407046267700143570ustar00rootroot00000000000000/** * @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-3.1.1/udp6.c000066400000000000000000000202651407046267700144430ustar00rootroot00000000000000/** * @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 "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 /* The 0x0e in second byte is substituted with udp6_scope at runtime. */ #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" enum { MC_PRIMARY, MC_PDELAY }; struct udp6 { struct transport t; int index; struct address ip; struct address mac; struct in6_addr mc6_addr[2]; }; 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_in6 *sa) { int err, off = 0; struct ipv6_mreq req; 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, &addr)) { pr_err("mcast_join failed"); goto no_option; } addr.sin6_addr = mc_addr[1]; if (mc_join(fd, index, &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; } static int udp6_open(struct transport *t, struct interface *iface, struct fdarray *fda, enum timestamp_type ts_type) { struct udp6 *udp6 = container_of(t, struct udp6, t); const char *name = interface_name(iface); uint8_t event_dscp, general_dscp; 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, &udp6->mc6_addr[MC_PRIMARY])) return -1; udp6->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, &udp6->mc6_addr[MC_PDELAY])) return -1; efd = open_socket_ipv6(name, udp6->mc6_addr, EVENT_PORT, &udp6->index, hop_limit); if (efd < 0) goto no_event; gfd = open_socket_ipv6(name, udp6->mc6_addr, GENERAL_PORT, &udp6->index, hop_limit); if (gfd < 0) goto no_general; if (sk_timestamping_init(efd, interface_label(iface), ts_type, TRANS_UDP_IPV6)) goto no_timestamping; if (sk_general_init(gfd)) goto no_timestamping; event_dscp = config_get_int(t->cfg, NULL, "dscp_event"); general_dscp = config_get_int(t->cfg, NULL, "dscp_general"); if (event_dscp && sk_set_priority(efd, AF_INET6, event_dscp)) { pr_warning("Failed to set event DSCP priority."); } if (general_dscp && sk_set_priority(gfd, AF_INET6, general_dscp)) { pr_warning("Failed to set general DSCP priority."); } 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, MSG_DONTWAIT); } static int udp6_send(struct transport *t, struct fdarray *fda, enum transport_event event, int peer, void *buf, int len, struct address *addr, struct hw_timestamp *hwts) { struct udp6 *udp6 = container_of(t, struct udp6, t); struct address addr_buf; unsigned char junk[1600]; ssize_t cnt; int fd = -1; switch (event) { case TRANS_GENERAL: fd = fda->fd[FD_GENERAL]; break; case TRANS_EVENT: case TRANS_ONESTEP: case TRANS_P2P1STEP: case TRANS_DEFER_EVENT: fd = fda->fd[FD_EVENT]; break; } if (!addr) { memset(&addr_buf, 0, sizeof(addr_buf)); addr_buf.sin6.sin6_family = AF_INET6; addr_buf.sin6.sin6_addr = peer ? udp6->mc6_addr[MC_PDELAY] : udp6->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 -errno; } /* * 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-3.1.1/udp6.h000066400000000000000000000021511407046267700144420ustar00rootroot00000000000000/** * @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-3.1.1/uds.c000066400000000000000000000072271407046267700143630ustar00rootroot00000000000000/** * @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, struct interface *iface, struct fdarray *fda, enum timestamp_type tt) { char *uds_path = config_get_string(t->cfg, NULL, "uds_address"); struct uds *uds = container_of(t, struct uds, t); const char *name = interface_name(iface); struct sockaddr_un sa; int fd, err; 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, enum transport_event 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 < 1) { return -errno; } 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-3.1.1/uds.h000066400000000000000000000022131407046267700143560ustar00rootroot00000000000000/** * @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-3.1.1/unicast_client.c000066400000000000000000000322651407046267700165740ustar00rootroot00000000000000/** * @file unicast_client.c * @brief Unicast client implementation * @note Copyright (C) 2018 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-1335 USA. */ #include #include "port.h" #include "port_private.h" #include "print.h" #include "unicast_client.h" #define E2E_SYDY_MASK (1 << ANNOUNCE | 1 << SYNC | 1 << DELAY_RESP) #define P2P_SYDY_MASK (1 << ANNOUNCE | 1 << SYNC) static int attach_ack(struct ptp_message *msg, uint8_t message_type_flags) { struct ack_cancel_unicast_xmit_tlv *ack; struct tlv_extra *extra; extra = msg_tlv_append(msg, sizeof(*ack)); if (!extra) { return -1; } ack = (struct ack_cancel_unicast_xmit_tlv *) extra->tlv; ack->type = TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION; ack->length = sizeof(*ack) - sizeof(ack->type) - sizeof(ack->length); ack->message_type_flags = message_type_flags; return 0; } static int attach_request(struct ptp_message *msg, int log_period, uint8_t message_type, int duration) { struct request_unicast_xmit_tlv *req; struct tlv_extra *extra; extra = msg_tlv_append(msg, sizeof(*req)); if (!extra) { return -1; } req = (struct request_unicast_xmit_tlv *) extra->tlv; req->type = TLV_REQUEST_UNICAST_TRANSMISSION; req->length = sizeof(*req) - sizeof(req->type) - sizeof(req->length); req->message_type = message_type << 4; req->logInterMessagePeriod = log_period; req->durationField = duration; return 0; } static int unicast_client_announce(struct port *p, struct unicast_master_address *dst) { struct ptp_message *msg; int err; msg = port_signaling_uc_construct(p, &dst->address, &dst->portIdentity); if (!msg) { return -1; } err = attach_request(msg, p->logAnnounceInterval, ANNOUNCE, p->unicast_req_duration); if (err) { goto out; } err = port_prepare_and_send(p, msg, TRANS_GENERAL); if (err) { pr_err("port %hu: signaling message failed", portnum(p)); } out: msg_put(msg); return err; } static struct unicast_master_address *unicast_client_ok(struct port *p, struct ptp_message *m) { struct unicast_master_address *ucma; if (!unicast_client_enabled(p)) { return NULL; } STAILQ_FOREACH(ucma, &p->unicast_master_table->addrs, list) { if (addreq(transport_type(p->trp), &ucma->address, &m->address)) { break; } } if (!ucma) { pr_warning("port %d: received rogue unicast grant or cancel", portnum(p)); return NULL; } return ucma; } static int unicast_client_peer_renew(struct port *p) { struct unicast_master_address *peer; struct ptp_message *msg; struct timespec now; int err; if (!p->unicast_master_table->peer_name) { return 0; } err = clock_gettime(CLOCK_MONOTONIC, &now); if (err) { pr_err("clock_gettime failed: %m"); return err; } peer = &p->unicast_master_table->peer_addr; if (now.tv_sec < peer->renewal_tmo) { return 0; } peer->renewal_tmo = 0; pr_debug("port %d: time to renew P2P unicast subscription", portnum(p)); msg = port_signaling_uc_construct(p, &peer->address, &peer->portIdentity); if (!msg) { return -1; } err = attach_request(msg, p->logPdelayReqInterval, PDELAY_RESP, p->unicast_req_duration); if (err) { goto out; } err = port_prepare_and_send(p, msg, TRANS_GENERAL); if (err) { pr_err("port %hu: P2P signaling message failed", portnum(p)); } out: msg_put(msg); return err; } static int unicast_client_renew(struct port *p, struct unicast_master_address *dst) { struct ptp_message *msg; struct timespec now; int err; err = clock_gettime(CLOCK_MONOTONIC, &now); if (err) { pr_err("clock_gettime failed: %m"); return err; } if (now.tv_sec < dst->renewal_tmo) { return 0; } dst->renewal_tmo = 0; pr_debug("port %d: time to renew unicast subscriptions", portnum(p)); msg = port_signaling_uc_construct(p, &dst->address, &dst->portIdentity); if (!msg) { return -1; } err = attach_request(msg, p->logAnnounceInterval, ANNOUNCE, p->unicast_req_duration); if (err) { goto out; } if (dst->state == UC_HAVE_SYDY) { err = attach_request(msg, p->logSyncInterval, SYNC, p->unicast_req_duration); if (err) { goto out; } if (p->delayMechanism != DM_P2P) { err = attach_request(msg, p->logMinDelayReqInterval, DELAY_RESP, p->unicast_req_duration); if (err) { goto out; } } } err = port_prepare_and_send(p, msg, TRANS_GENERAL); if (err) { pr_err("port %hu: signaling message failed", portnum(p)); } out: msg_put(msg); return err; } static void unicast_client_set_renewal(struct port *p, struct unicast_master_address *master, long duration) { struct timespec now; time_t tmo; if (clock_gettime(CLOCK_MONOTONIC, &now)) { pr_err("clock_gettime failed: %m"); return; } duration = (3 * duration) / 4; tmo = now.tv_sec + duration; if (!master->renewal_tmo || tmo < master->renewal_tmo) { master->renewal_tmo = tmo; pr_debug("port %d: renewal timeout at %lld", portnum(p), (long long)tmo); } } static int unicast_client_sydy(struct port *p, struct unicast_master_address *dst) { struct ptp_message *msg; int err; msg = port_signaling_uc_construct(p, &dst->address, &dst->portIdentity); if (!msg) { return -1; } err = attach_request(msg, p->logSyncInterval, SYNC, p->unicast_req_duration); if (err) { goto out; } if (p->delayMechanism != DM_P2P) { err = attach_request(msg, p->logMinDelayReqInterval, DELAY_RESP, p->unicast_req_duration); if (err) { goto out; } } err = port_prepare_and_send(p, msg, TRANS_GENERAL); if (err) { pr_err("port %hu: signaling message failed", portnum(p)); } out: msg_put(msg); return err; } static void free_master_table(struct unicast_master_table *table) { struct unicast_master_address *address; while ((address = STAILQ_FIRST(&table->addrs))) { STAILQ_REMOVE_HEAD(&table->addrs, list); free(address); } free(table->peer_name); free(table); } static struct unicast_master_table * clone_master_table(struct unicast_master_table *table) { struct unicast_master_address *address, *cloned_address; struct unicast_master_table *cloned_table; cloned_table = malloc(sizeof(*cloned_table)); if (!cloned_table) return NULL; *cloned_table = *table; STAILQ_INIT(&cloned_table->addrs); memset(&cloned_table->list, 0, sizeof(cloned_table->list)); if (table->peer_name) cloned_table->peer_name = strdup(table->peer_name); STAILQ_FOREACH(address, &table->addrs, list) { cloned_address = malloc(sizeof(*cloned_address)); if (!cloned_address) { free_master_table(cloned_table); return NULL; } *cloned_address = *address; STAILQ_INSERT_TAIL(&cloned_table->addrs, cloned_address, list); } return cloned_table; } /* public methods */ int unicast_client_cancel(struct port *p, struct ptp_message *m, struct tlv_extra *extra) { struct cancel_unicast_xmit_tlv *cancel; struct unicast_master_address *ucma; struct ptp_message *msg; uint8_t mtype; int err; ucma = unicast_client_ok(p, m); if (!ucma) { return 0; } cancel = (struct cancel_unicast_xmit_tlv *) extra->tlv; mtype = cancel->message_type_flags >> 4; switch (mtype) { case ANNOUNCE: case SYNC: case DELAY_RESP: break; default: return 0; } if (cancel->message_type_flags & CANCEL_UNICAST_MAINTAIN_GRANT) { return 0; } pr_warning("port %d: server unilaterally canceled unicast %s grant", portnum(p), msg_type_string(mtype)); ucma->state = unicast_fsm(ucma->state, UC_EV_CANCEL); ucma->granted &= ~(1 << mtype); /* Respond with ACK. */ msg = port_signaling_uc_construct(p, &ucma->address, &ucma->portIdentity); if (!msg) { return -1; } err = attach_ack(msg, cancel->message_type_flags); if (err) { goto out; } err = port_prepare_and_send(p, msg, TRANS_GENERAL); if (err) { pr_err("port %hu: signaling message failed", portnum(p)); } out: msg_put(msg); return err; } int unicast_client_initialize(struct port *p) { struct unicast_master_address *master, *peer; struct config *cfg = clock_config(p->clock); struct unicast_master_table *table; int table_id; table_id = config_get_int(cfg, p->name, "unicast_master_table"); if (!table_id) { return 0; } STAILQ_FOREACH(table, &cfg->unicast_master_tables, list) { if (table->table_index == table_id) { break; } } if (!table) { pr_err("port %d: no table with id %d", portnum(p), table_id); return -1; } table = clone_master_table(table); if (!table) { pr_err("low memory"); return -1; } peer = &table->peer_addr; if (table->peer_name && str2addr(transport_type(p->trp), table->peer_name, &peer->address)) { pr_err("port %d: bad peer address: %s", portnum(p), table->peer_name); free_master_table(table); return -1; } STAILQ_FOREACH(master, &table->addrs, list) { if (master->type != transport_type(p->trp)) { pr_warning("port %d: unicast master transport mismatch", portnum(p)); } if (p->delayMechanism == DM_P2P) { master->sydymsk = P2P_SYDY_MASK; } else { master->sydymsk = E2E_SYDY_MASK; } } table->port = portnum(p); p->unicast_master_table = table; p->unicast_req_duration = config_get_int(cfg, p->name, "unicast_req_duration"); return 0; } void unicast_client_cleanup(struct port *p) { if (p->unicast_master_table) free_master_table(p->unicast_master_table); } int unicast_client_enabled(struct port *p) { return p->unicast_master_table ? 1 : 0; } void unicast_client_grant(struct port *p, struct ptp_message *m, struct tlv_extra *extra) { struct unicast_master_address *ucma; struct grant_unicast_xmit_tlv *g; int mtype; ucma = unicast_client_ok(p, m); if (!ucma) { return; } g = (struct grant_unicast_xmit_tlv *) extra->tlv; mtype = g->message_type >> 4; if (!g->durationField) { pr_warning("port %d: unicast grant of %s rejected", portnum(p), msg_type_string(mtype)); if (mtype != PDELAY_RESP) { ucma->state = UC_WAIT; } return; } pr_debug("port %d: unicast %s granted for %u sec", portnum(p), msg_type_string(mtype), g->durationField); if (p->delayMechanism == DM_P2P) { switch (mtype) { case DELAY_RESP: return; case PDELAY_RESP: p->unicast_master_table->peer_addr.portIdentity = m->header.sourcePortIdentity; unicast_client_set_renewal(p, &p->unicast_master_table->peer_addr, g->durationField); p->logPdelayReqInterval = g->logInterMessagePeriod; return; default: break; } } ucma->granted |= 1 << mtype; switch (ucma->state) { case UC_WAIT: if (mtype == ANNOUNCE) { ucma->state = unicast_fsm(ucma->state, UC_EV_GRANT_ANN); ucma->portIdentity = m->header.sourcePortIdentity; unicast_client_set_renewal(p, ucma, g->durationField); } break; case UC_HAVE_ANN: break; case UC_NEED_SYDY: switch (mtype) { case DELAY_RESP: if ((ucma->granted & ucma->sydymsk) == ucma->sydymsk) { ucma->state = unicast_fsm(ucma->state, UC_EV_GRANT_SYDY); } unicast_client_set_renewal(p, ucma, g->durationField); p->logMinDelayReqInterval = g->logInterMessagePeriod; break; case SYNC: if ((ucma->granted & ucma->sydymsk) == ucma->sydymsk) { ucma->state = unicast_fsm(ucma->state, UC_EV_GRANT_SYDY); } unicast_client_set_renewal(p, ucma, g->durationField); clock_sync_interval(p->clock, g->logInterMessagePeriod); break; } break; case UC_HAVE_SYDY: switch (mtype) { case ANNOUNCE: case DELAY_RESP: case SYNC: unicast_client_set_renewal(p, ucma, g->durationField); break; } break; } } int unicast_client_set_tmo(struct port *p) { return set_tmo_log(p->fda.fd[FD_UNICAST_REQ_TIMER], 1, p->unicast_master_table->logQueryInterval); } void unicast_client_state_changed(struct port *p) { struct unicast_master_address *ucma; struct PortIdentity pid; if (!unicast_client_enabled(p)) { return; } pid = clock_parent_identity(p->clock); STAILQ_FOREACH(ucma, &p->unicast_master_table->addrs, list) { if (pid_eq(&ucma->portIdentity, &pid)) { ucma->state = unicast_fsm(ucma->state, UC_EV_SELECTED); } else { ucma->state = unicast_fsm(ucma->state, UC_EV_UNSELECTED); } } } int unicast_client_timer(struct port *p) { struct unicast_master_address *master; int err = 0; STAILQ_FOREACH(master, &p->unicast_master_table->addrs, list) { if (master->type != transport_type(p->trp)) { continue; } switch (master->state) { case UC_WAIT: err = unicast_client_announce(p, master); break; case UC_HAVE_ANN: err = unicast_client_renew(p, master); break; case UC_NEED_SYDY: err = unicast_client_sydy(p, master); break; case UC_HAVE_SYDY: err = unicast_client_renew(p, master); break; } if (p->delayMechanism == DM_P2P) { unicast_client_peer_renew(p); } } unicast_client_set_tmo(p); return err; } linuxptp-3.1.1/unicast_client.h000066400000000000000000000056571407046267700166060ustar00rootroot00000000000000/** * @file unicast_client.h * @brief Unicast client implementation * @note Copyright (C) 2018 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-1335 USA. */ #ifndef HAVE_UNICAST_CLIENT_H #define HAVE_UNICAST_CLIENT_H /** * Handles a CANCEL_UNICAST_TRANSMISSION TLV from the grantor. * @param p The port on which the signaling message was received. * @param m The signaling message containing the cancellation. * @param extra The TLV containing the cancellation. * @return Zero on success, or non-zero * if transmission of the ACK message failed. */ int unicast_client_cancel(struct port *p, struct ptp_message *m, struct tlv_extra *extra); /** * Finds and initializes the unicast master table configured for this * port, if any. * @param port The port in question. * @return Zero on success, non-zero otherwise. */ int unicast_client_initialize(struct port *port); /** * Frees all of the resources associated with a port's unicast client. * @param p The port in question. */ void unicast_client_cleanup(struct port *p); /** * Tests whether a unicast master table is associated with a given port. * @param p The port in question. * @return One (1) if a unicast master table is configured on the port, * or zero otherwise. */ int unicast_client_enabled(struct port *p); /** * Handles a GRANT_UNICAST_TRANSMISSION TLV from the grantor. * @param p The port on which the signaling message was received. * @param m The signaling message containing the grant. * @param extra The TLV containing the grant. */ void unicast_client_grant(struct port *p, struct ptp_message *m, struct tlv_extra *extra); /** * Programs the unicast request timer. * @param p The port in question. * @return Zero on success, non-zero otherwise. */ int unicast_client_set_tmo(struct port *p); /** * Notifies the unicast client code that the port state has changed. * @param p The port in question. */ void unicast_client_state_changed(struct port *p); /** * Handles the unicast request timer, sending requests as needed. * @param p The port in question. * @return Zero on success, non-zero otherwise. */ int unicast_client_timer(struct port *p); #endif linuxptp-3.1.1/unicast_fsm.c000066400000000000000000000037271407046267700161040ustar00rootroot00000000000000/** * @file unicast_fsm.c * @brief Unicast client state machine * @note Copyright (C) 2018 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-1335 USA. */ #include "unicast_fsm.h" enum unicast_state unicast_fsm(enum unicast_state state, enum unicast_event ev) { enum unicast_state next = state; switch (state) { case UC_WAIT: switch (ev) { case UC_EV_GRANT_ANN: next = UC_HAVE_ANN; break; case UC_EV_SELECTED: case UC_EV_GRANT_SYDY: case UC_EV_UNSELECTED: case UC_EV_CANCEL: break; } break; case UC_HAVE_ANN: switch (ev) { case UC_EV_GRANT_ANN: break; case UC_EV_SELECTED: next = UC_NEED_SYDY; break; case UC_EV_GRANT_SYDY: case UC_EV_UNSELECTED: break; case UC_EV_CANCEL: next = UC_WAIT; break; } break; case UC_NEED_SYDY: switch (ev) { case UC_EV_GRANT_ANN: case UC_EV_SELECTED: break; case UC_EV_GRANT_SYDY: next = UC_HAVE_SYDY; break; case UC_EV_UNSELECTED: next = UC_HAVE_ANN; break; case UC_EV_CANCEL: next = UC_WAIT; break; } break; case UC_HAVE_SYDY: switch (ev) { case UC_EV_GRANT_ANN: case UC_EV_SELECTED: case UC_EV_GRANT_SYDY: break; case UC_EV_UNSELECTED: next = UC_HAVE_ANN; break; case UC_EV_CANCEL: next = UC_WAIT; break; } break; } return next; } linuxptp-3.1.1/unicast_fsm.h000066400000000000000000000022551407046267700161040ustar00rootroot00000000000000/** * @file unicast_fsm.h * @brief Unicast client state machine * @note Copyright (C) 2018 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-1335 USA. */ #ifndef HAVE_UNICAST_FSM_H #define HAVE_UNICAST_FSM_H enum unicast_state { UC_WAIT, UC_HAVE_ANN, UC_NEED_SYDY, UC_HAVE_SYDY, }; enum unicast_event { UC_EV_GRANT_ANN, UC_EV_SELECTED, UC_EV_GRANT_SYDY, UC_EV_UNSELECTED, UC_EV_CANCEL, }; enum unicast_state unicast_fsm(enum unicast_state state, enum unicast_event ev); #endif linuxptp-3.1.1/unicast_service.c000066400000000000000000000306361407046267700167560ustar00rootroot00000000000000/** * @file unicast_service.c * @brief Unicast service * @note Copyright (C) 2018 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-1335 USA. */ #include #include #include #include "address.h" #include "clock.h" #include "missing.h" #include "port.h" #include "port_private.h" #include "pqueue.h" #include "print.h" #include "unicast_service.h" #include "util.h" #define QUEUE_LEN 16 struct unicast_client_address { LIST_ENTRY(unicast_client_address) list; struct PortIdentity portIdentity; unsigned int message_types; struct address addr; time_t grant_tmo; }; struct unicast_service_interval { LIST_HEAD(uca, unicast_client_address) clients; LIST_ENTRY(unicast_service_interval) list; struct timespec incr; struct timespec tmo; int log_period; }; struct unicast_service { LIST_HEAD(usi, unicast_service_interval) intervals; struct pqueue *queue; }; static struct timespec log_to_timespec(int log_seconds); static int timespec_compare(struct timespec *a, struct timespec *b); static void timespec_normalize(struct timespec *ts); static int attach_grant(struct ptp_message *msg, struct request_unicast_xmit_tlv *req, int duration) { struct grant_unicast_xmit_tlv *g; struct tlv_extra *extra; extra = msg_tlv_append(msg, sizeof(*g)); if (!extra) { return -1; } g = (struct grant_unicast_xmit_tlv *) extra->tlv; g->type = TLV_GRANT_UNICAST_TRANSMISSION; g->length = sizeof(*g) - sizeof(g->type) - sizeof(g->length); g->message_type = req->message_type; g->logInterMessagePeriod = req->logInterMessagePeriod; g->durationField = duration; g->flags = GRANT_UNICAST_RENEWAL_INVITED; return 0; } static int compare_timeout(void *ain, void *bin) { struct unicast_service_interval *a, *b; a = (struct unicast_service_interval *) ain; b = (struct unicast_service_interval *) bin; return timespec_compare(&a->tmo, &b->tmo); } static void initialize_interval(struct unicast_service_interval *interval, int log_period) { LIST_INIT(&interval->clients); interval->incr = log_to_timespec(log_period); clock_gettime(CLOCK_MONOTONIC, &interval->tmo); interval->tmo.tv_nsec += 10000000; timespec_normalize(&interval->tmo); interval->log_period = log_period; } static void interval_increment(struct unicast_service_interval *i) { i->tmo.tv_sec += i->incr.tv_sec; i->tmo.tv_nsec += i->incr.tv_nsec; timespec_normalize(&i->tmo); } static struct timespec log_to_timespec(int log_seconds) { struct timespec ts = {0, 0}; uint64_t ns; int i; if (log_seconds < 0) { log_seconds *= -1; for (i = 1, ns = 500000000ULL; i < log_seconds; i++) { ns >>= 1; } ts.tv_nsec = ns; } else { ts.tv_sec = 1 << log_seconds; } return ts; } /* * Returns: * 1 if 'a' is before 'b' * -1 if 'a' is after 'b' * 0 otherwise */ static int timespec_compare(struct timespec *a, struct timespec *b) { if (a->tv_sec < b->tv_sec) { return 1; } if (b->tv_sec < a->tv_sec) { return -1; } if (a->tv_nsec < b->tv_nsec) { return 1; } if (b->tv_nsec < a->tv_nsec) { return -1; } return 0; } static void timespec_normalize(struct timespec *ts) { while (ts->tv_nsec >= NS_PER_SEC) { ts->tv_nsec -= NS_PER_SEC; ts->tv_sec++; } } static int unicast_service_clients(struct port *p, struct unicast_service_interval *interval) { struct unicast_client_address *client, *next; struct timespec now; int err = 0; err = clock_gettime(CLOCK_MONOTONIC, &now); if (err) { pr_err("clock_gettime failed: %m"); return err; } LIST_FOREACH_SAFE(client, &interval->clients, list, next) { pr_debug("%s wants 0x%x", pid2str(&client->portIdentity), client->message_types); if (now.tv_sec > client->grant_tmo) { pr_debug("%s service of 0x%x expired", pid2str(&client->portIdentity), client->message_types); LIST_REMOVE(client, list); free(client); continue; } if (client->message_types & (1 << ANNOUNCE)) { if (port_tx_announce(p, &client->addr)) { err = -1; } } if (client->message_types & (1 << SYNC)) { if (port_tx_sync(p, &client->addr)) { err = -1; } } } return err; } static void unicast_service_extend(struct unicast_client_address *client, struct request_unicast_xmit_tlv *req) { struct timespec now; time_t tmo; int err; err = clock_gettime(CLOCK_MONOTONIC, &now); if (err) { pr_err("clock_gettime failed: %m"); return; } tmo = now.tv_sec + req->durationField; if (tmo > client->grant_tmo) { client->grant_tmo = tmo; pr_debug("%s grant of 0x%x extended to %lld", pid2str(&client->portIdentity), client->message_types, (long long)tmo); } } static int unicast_service_rearm_timer(struct port *p) { struct unicast_service_interval *interval; struct itimerspec tmo; int fd; fd = p->fda.fd[FD_UNICAST_SRV_TIMER]; memset(&tmo, 0, sizeof(tmo)); interval = pqueue_peek(p->unicast_service->queue); if (interval) { tmo.it_value = interval->tmo; pr_debug("arming timer tmo={%lld,%ld}", (long long)interval->tmo.tv_sec, interval->tmo.tv_nsec); } else { pr_debug("stopping unicast service timer"); } return timerfd_settime(fd, TFD_TIMER_ABSTIME, &tmo, NULL); } static int unicast_service_reply(struct port *p, struct ptp_message *dst, struct request_unicast_xmit_tlv *req, int duration) { struct ptp_message *msg; int err; msg = port_signaling_uc_construct(p, &dst->address, &dst->header.sourcePortIdentity); if (!msg) { return -1; } err = attach_grant(msg, req, duration); if (err) { goto out; } err = port_prepare_and_send(p, msg, TRANS_GENERAL); if (err) { pr_err("port %hu: signaling message failed", portnum(p)); } out: msg_put(msg); return err; } /* public methods */ int unicast_service_add(struct port *p, struct ptp_message *m, struct tlv_extra *extra) { struct unicast_client_address *client = NULL, *ctmp, *next; struct unicast_service_interval *interval = NULL, *itmp; struct request_unicast_xmit_tlv *req; unsigned int mask; uint8_t mtype; if (!p->unicast_service) { return SERVICE_DISABLED; } req = (struct request_unicast_xmit_tlv *) extra->tlv; mtype = req->message_type >> 4; mask = 1 << mtype; switch (mtype) { case ANNOUNCE: case SYNC: break; case DELAY_RESP: case PDELAY_RESP: return SERVICE_GRANTED; default: return SERVICE_DENIED; } LIST_FOREACH(itmp, &p->unicast_service->intervals, list) { /* * Remember the interval of interest. */ if (itmp->log_period == req->logInterMessagePeriod) { interval = itmp; } /* * Find any client records, and remove any stale contract. */ LIST_FOREACH_SAFE(ctmp, &itmp->clients, list, next) { if (!addreq(transport_type(p->trp), &ctmp->addr, &m->address)) { continue; } if (interval == itmp) { if (ctmp->message_types & mask) { /* Contract is unchanged. */ unicast_service_extend(ctmp, req); return SERVICE_GRANTED; } /* This is the one to use. */ client = ctmp; continue; } /* Clear any stale contracts. */ ctmp->message_types &= ~mask; if (!ctmp->message_types) { LIST_REMOVE(ctmp, list); free(ctmp); } } } if (client) { client->message_types |= mask; unicast_service_extend(client, req); return SERVICE_GRANTED; } client = calloc(1, sizeof(*client)); if (!client) { return SERVICE_DENIED; } client->portIdentity = m->header.sourcePortIdentity; client->message_types = mask; client->addr = m->address; unicast_service_extend(client, req); if (!interval) { interval = calloc(1, sizeof(*interval)); if (!interval) { free(client); return SERVICE_DENIED; } initialize_interval(interval, req->logInterMessagePeriod); LIST_INSERT_HEAD(&p->unicast_service->intervals, interval, list); if (pqueue_insert(p->unicast_service->queue, interval)) { LIST_REMOVE(interval, list); free(interval); free(client); return SERVICE_DENIED; } unicast_service_rearm_timer(p); } LIST_INSERT_HEAD(&interval->clients, client, list); return SERVICE_GRANTED; } void unicast_service_cleanup(struct port *p) { struct unicast_service_interval *itmp, *inext; struct unicast_client_address *ctmp, *cnext; if (!p->unicast_service) { return; } LIST_FOREACH_SAFE(itmp, &p->unicast_service->intervals, list, inext) { LIST_FOREACH_SAFE(ctmp, &itmp->clients, list, cnext) { LIST_REMOVE(ctmp, list); free(ctmp); } LIST_REMOVE(itmp, list); free(itmp); } pqueue_destroy(p->unicast_service->queue); free(p->unicast_service); } int unicast_service_deny(struct port *p, struct ptp_message *m, struct tlv_extra *extra) { struct request_unicast_xmit_tlv *req = (struct request_unicast_xmit_tlv *) extra->tlv; return unicast_service_reply(p, m, req, 0); } int unicast_service_grant(struct port *p, struct ptp_message *m, struct tlv_extra *extra) { struct request_unicast_xmit_tlv *req = (struct request_unicast_xmit_tlv *) extra->tlv; return unicast_service_reply(p, m, req, req->durationField); } int unicast_service_initialize(struct port *p) { struct config *cfg = clock_config(p->clock); if (!config_get_int(cfg, p->name, "unicast_listen")) { return 0; } if (config_set_section_int(cfg, p->name, "hybrid_e2e", 1)) { return -1; } p->unicast_service = calloc(1, sizeof(*p->unicast_service)); if (!p->unicast_service) { return -1; } LIST_INIT(&p->unicast_service->intervals); p->unicast_service->queue = pqueue_create(QUEUE_LEN, compare_timeout); if (!p->unicast_service->queue) { free(p->unicast_service); return -1; } p->inhibit_multicast_service = config_get_int(cfg, p->name, "inhibit_multicast_service"); return 0; } void unicast_service_remove(struct port *p, struct ptp_message *m, struct tlv_extra *extra) { struct unicast_client_address *ctmp, *next; struct cancel_unicast_xmit_tlv *cancel; struct unicast_service_interval *itmp; unsigned int mask; uint8_t mtype; if (!p->unicast_service) { return; } cancel = (struct cancel_unicast_xmit_tlv *) extra->tlv; if (cancel->message_type_flags & CANCEL_UNICAST_MAINTAIN_REQUEST) { return; } mtype = cancel->message_type_flags >> 4; mask = 1 << mtype; switch (mtype) { case ANNOUNCE: case SYNC: break; case DELAY_RESP: case PDELAY_RESP: default: return; } LIST_FOREACH(itmp, &p->unicast_service->intervals, list) { LIST_FOREACH_SAFE(ctmp, &itmp->clients, list, next) { if (!addreq(transport_type(p->trp), &ctmp->addr, &m->address)) { continue; } if (ctmp->message_types & mask) { ctmp->message_types &= ~mask; if (!ctmp->message_types) { LIST_REMOVE(ctmp, list); free(ctmp); } return; } } } } int unicast_service_timer(struct port *p) { struct unicast_service_interval *interval; int err = 0, master = 0; struct timespec now; if (!p->unicast_service) { return 0; } clock_gettime(CLOCK_MONOTONIC, &now); switch (p->state) { case PS_INITIALIZING: case PS_FAULTY: case PS_DISABLED: case PS_LISTENING: case PS_PRE_MASTER: case PS_PASSIVE: case PS_UNCALIBRATED: case PS_SLAVE: break; case PS_MASTER: case PS_GRAND_MASTER: master = 1; break; } while ((interval = pqueue_peek(p->unicast_service->queue)) != NULL) { pr_debug("peek i={2^%d} tmo={%lld,%ld}", interval->log_period, (long long)interval->tmo.tv_sec, interval->tmo.tv_nsec); if (timespec_compare(&now, &interval->tmo) > 0) { break; } interval = pqueue_extract(p->unicast_service->queue); if (master && unicast_service_clients(p, interval)) { err = -1; } if (LIST_EMPTY(&interval->clients)) { pr_debug("retire interval 2^%d", interval->log_period); LIST_REMOVE(interval, list); free(interval); continue; } interval_increment(interval); pr_debug("next i={2^%d} tmo={%lld,%ld}", interval->log_period, (long long)interval->tmo.tv_sec, interval->tmo.tv_nsec); pqueue_insert(p->unicast_service->queue, interval); } if (unicast_service_rearm_timer(p)) { err = -1; } return err; } linuxptp-3.1.1/unicast_service.h000066400000000000000000000061531407046267700167600ustar00rootroot00000000000000/** * @file unicast_service.h * @brief Unicast service * @note Copyright (C) 2018 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-1335 USA. */ #ifndef HAVE_UNICAST_SERVICE_H #define HAVE_UNICAST_SERVICE_H struct port; struct ptp_message; struct tlv_extra; #define SERVICE_GRANTED 0 #define SERVICE_DENIED 1 #define SERVICE_DISABLED 2 /** * Handle a request for unicast service. * @param p The port on which the signaling message was received. * @param m The signaling message containing the request. * @param extra The TLV containing the request. * @return SERVICE_GRANTED, SERVICE_DENIED, or SERVICE_DISABLED. */ int unicast_service_add(struct port *p, struct ptp_message *m, struct tlv_extra *extra); /** * Frees all of the resources associated with a port's unicast service. * @param p The port in question. */ void unicast_service_cleanup(struct port *p); /** * Responds to a unicast service request with a denial. * @param p The port on which the signaling message was received. * @param m The signaling message containing the request. * @param extra The TLV containing the request. * @return Zero on success, non-zero otherwise. */ int unicast_service_deny(struct port *p, struct ptp_message *m, struct tlv_extra *extra); /** * Responds to a unicast service request with a grant. * @param p The port on which the signaling message was received. * @param m The signaling message containing the request. * @param extra The TLV containing the request. * @return Zero on success, non-zero otherwise. */ int unicast_service_grant(struct port *p, struct ptp_message *m, struct tlv_extra *extra); /** * Initializes unicast service on a given port. * @param p The port in question. * @return Zero on success, non-zero otherwise. */ int unicast_service_initialize(struct port *p); /** * Handle a unicast service cancellation. * @param p The port on which the signaling message was received. * @param m The signaling message containing the cancellation. * @param extra The TLV containing the cancellation. */ void unicast_service_remove(struct port *p, struct ptp_message *m, struct tlv_extra *extra); /** * Handles the unicast service timer, sending messages according to schedule. * @param p The port in question. * @return Zero on success, non-zero otherwise. */ int unicast_service_timer(struct port *p); #endif linuxptp-3.1.1/util.c000066400000000000000000000372251407046267700145460ustar00rootroot00000000000000/** * @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 #include "address.h" #include "phc.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", "INIT_COMPLETE", "RS_MASTER", "RS_GRAND_MASTER", "RS_SLAVE", "RS_PASSIVE", }; const char *ts_str(enum timestamp_type ts) { switch (ts) { case TS_SOFTWARE: return "SOFTWARE"; case TS_HARDWARE: return "HARDWARE"; case TS_LEGACY_HW: return "LEGACY_HW"; case TS_ONESTEP: return "ONESTEP"; case TS_P2P1STEP: return "P2P1STEP"; } return "???"; } int addreq(enum transport_type type, struct address *a, struct address *b) { void *bufa, *bufb; int len; switch (type) { case TRANS_UDP_IPV4: bufa = &a->sin.sin_addr; bufb = &b->sin.sin_addr; len = sizeof(a->sin.sin_addr); break; case TRANS_UDP_IPV6: bufa = &a->sin6.sin6_addr; bufb = &b->sin6.sin6_addr; len = sizeof(a->sin6.sin6_addr); break; case TRANS_IEEE_802_3: bufa = &a->sll.sll_addr; bufb = &b->sll.sll_addr; len = MAC_LEN; break; case TRANS_UDS: case TRANS_DEVICENET: case TRANS_CONTROLNET: case TRANS_PROFINET: default: pr_err("sorry, cannot compare addresses for this transport"); return 0; } return memcmp(bufa, bufb, len) == 0 ? 1 : 0; } 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; } 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; } 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; } void posix_clock_close(clockid_t clock) { if (clock == CLOCK_REALTIME) { return; } phc_close(clock); } clockid_t posix_clock_open(const char *device, int *phc_index) { struct sk_ts_info ts_info; char phc_device[19]; 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) { if (!strncmp(device, "/dev/ptp", strlen("/dev/ptp"))) { int r = get_ranged_int(device + strlen("/dev/ptp"), phc_index, 0, 65535); if (r) { fprintf(stderr, "failed to parse PHC index from %s\n", device); return -1; } } 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; } snprintf(phc_device, sizeof(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); } *phc_index = ts_info.phc_index; return clkid; } int str2addr(enum transport_type type, const char *s, struct address *addr) { unsigned char mac[MAC_LEN]; struct in_addr ipv4_addr; struct in6_addr ipv6_addr; memset(addr, 0, sizeof(*addr)); switch (type) { case TRANS_UDS: case TRANS_DEVICENET: case TRANS_CONTROLNET: case TRANS_PROFINET: pr_err("sorry, cannot convert addresses for this transport"); return -1; case TRANS_UDP_IPV4: if (!inet_aton(s, &ipv4_addr)) { pr_err("bad IPv4 address"); return -1; } addr->sin.sin_family = AF_INET; addr->sin.sin_addr = ipv4_addr; addr->len = sizeof(addr->sin); break; case TRANS_UDP_IPV6: if (1 != inet_pton(AF_INET6, s, &ipv6_addr)) { pr_err("bad IPv6 address"); return -1; } addr->sin6.sin6_family = AF_INET6; addr->sin6.sin6_addr = ipv6_addr; addr->len = sizeof(addr->sin6); break; case TRANS_IEEE_802_3: if (str2mac(s, mac)) { pr_err("bad Layer-2 address"); return -1; } 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); break; } return 0; } 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 str2cid(const char *s, struct ClockIdentity *result) { struct ClockIdentity cid; unsigned char *ptr = cid.id; int c; c = sscanf(s, " %02hhx%02hhx%02hhx.%02hhx%02hhx.%02hhx%02hhx%02hhx", &ptr[0], &ptr[1], &ptr[2], &ptr[3], &ptr[4], &ptr[5], &ptr[6], &ptr[7]); if (c == 8) { *result = cid; return 0; } return -1; } 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; switch (addr.sll.sll_halen) { case EUI48: 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]; break; case EUI64: 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] = addr.sll.sll_addr[3]; ci->id[4] = addr.sll.sll_addr[4]; ci->id[5] = addr.sll.sll_addr[5]; ci->id[6] = addr.sll.sll_addr[6]; ci->id[7] = addr.sll.sll_addr[7]; break; default: return -1; } 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; int 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-3.1.1/util.h000066400000000000000000000352661407046267700145560ustar00rootroot00000000000000/** * @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 #include #include "address.h" #include "ddt.h" #include "ether.h" #include "transport.h" #define MAX_PRINT_BYTES 16 #define BIN_BUF_SIZE (MAX_PRINT_BYTES * 3 + 1) /** * 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[]; /** * Gets a human-readable string for a given timestamp type. * @param ts Timestamp type. * @return Human-readable rendering if TS is valid, otherwise "???". */ const char *ts_str(enum timestamp_type ts); /** * Compares two binary addresses for equality. * @param type One of the enumerated transport types. * @param a One address to compare. * @param b The second address to compare. * @return One if the addresses are identical, zero otherwise. */ int addreq(enum transport_type type, struct address *a, struct address *b); static inline uint16_t align16(uint16_t *p) { uint16_t v; memcpy(&v, p, sizeof(v)); return v; } char *bin2str_impl(Octet *data, int len, char *buf, int buf_len); /** * 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); /** * Compare two clock identities for equality. * * @param a First clock identity. * @param b Second clock identity. * @return 1 if identities are equal, 0 otherwise. */ static inline int cid_eq(struct ClockIdentity *a, struct ClockIdentity *b) { return memcmp(a, b, sizeof(*a)) == 0; } /** * 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); char *portaddr2str(struct PortAddress *addr); /** * Closes a dynamic posix clock. * @param clock A clock ID obtained via posix_clock_close(). */ void posix_clock_close(clockid_t clock); /** * Opens a dynamic posix clock by name. * @param device The PHC character device or network interface to open. * @param phc_index Returns the PHC index, if any. * @return A valid clock ID on success or CLOCK_INVALID otherwise. */ clockid_t posix_clock_open(const char *device, int *phc_index); /** * Compare two port identities for equality. * * @param a First port identity. * @param b Second port identity. * @return 1 if identities are equal, 0 otherwise. */ static inline int pid_eq(const struct PortIdentity *a, const struct PortIdentity *b) { return memcmp(a, b, sizeof(*a)) == 0; } /** * Convert a string containing a network address into binary form. * @param type The network transport type of the address. * @param s String in human readable form. * @param addr Pointer to a buffer to hold the result. * @return Zero on success, or -1 if the string is incorrectly formatted. */ int str2addr(enum transport_type type, const char *s, struct address *addr); /** * 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 clock 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 str2cid(const char *s, struct ClockIdentity *result); /** * 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-3.1.1/version.c000066400000000000000000000021271407046267700152470ustar00rootroot00000000000000/** * @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-3.1.1/version.h000066400000000000000000000023401407046267700152510ustar00rootroot00000000000000/** * @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-3.1.1/version.sh000077500000000000000000000025521407046267700154440ustar00rootroot00000000000000#!/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=3 minor=1 extra=.1 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"