92ea52eb1a
Copy from IBS Devel:LLDC:SLE-10-SP3/ptpd based on submit request 2261 from user tsariounov OBS-URL: https://build.opensuse.org/request/show/2261 OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/ptpd?expand=0&rev=7
5795 lines
209 KiB
Diff
5795 lines
209 KiB
Diff
diff --git a/Doxyfile b/Doxyfile
|
|
new file mode 100644
|
|
index 0000000..7b41ff5
|
|
--- /dev/null
|
|
+++ b/Doxyfile
|
|
@@ -0,0 +1,1161 @@
|
|
+# Doxyfile 1.3.9.1
|
|
+
|
|
+# This file describes the settings to be used by the documentation system
|
|
+# doxygen (www.doxygen.org) for a project
|
|
+#
|
|
+# All text after a hash (#) is considered a comment and will be ignored
|
|
+# The format is:
|
|
+# TAG = value [value, ...]
|
|
+# For lists items can also be appended using:
|
|
+# TAG += value [value, ...]
|
|
+# Values that contain spaces should be placed between quotes (" ")
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# Project related configuration options
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
|
|
+# by quotes) that should identify the project.
|
|
+
|
|
+PROJECT_NAME = PTPd
|
|
+
|
|
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
|
|
+# This could be handy for archiving the generated documentation or
|
|
+# if some version control system is used.
|
|
+
|
|
+PROJECT_NUMBER =
|
|
+
|
|
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
|
|
+# base path where the generated documentation will be put.
|
|
+# If a relative path is entered, it will be relative to the location
|
|
+# where doxygen was started. If left blank the current directory will be used.
|
|
+
|
|
+OUTPUT_DIRECTORY =
|
|
+
|
|
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
|
|
+# 4096 sub-directories (in 2 levels) under the output directory of each output
|
|
+# format and will distribute the generated files over these directories.
|
|
+# Enabling this option can be useful when feeding doxygen a huge amount of source
|
|
+# files, where putting all generated files in the same directory would otherwise
|
|
+# cause performance problems for the file system.
|
|
+
|
|
+CREATE_SUBDIRS = NO
|
|
+
|
|
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
|
|
+# documentation generated by doxygen is written. Doxygen will use this
|
|
+# information to generate all constant output in the proper language.
|
|
+# The default language is English, other supported languages are:
|
|
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish,
|
|
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese,
|
|
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian,
|
|
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish,
|
|
+# Swedish, and Ukrainian.
|
|
+
|
|
+OUTPUT_LANGUAGE = English
|
|
+
|
|
+# This tag can be used to specify the encoding used in the generated output.
|
|
+# The encoding is not always determined by the language that is chosen,
|
|
+# but also whether or not the output is meant for Windows or non-Windows users.
|
|
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES
|
|
+# forces the Windows encoding (this is the default for the Windows binary),
|
|
+# whereas setting the tag to NO uses a Unix-style encoding (the default for
|
|
+# all platforms other than Windows).
|
|
+
|
|
+USE_WINDOWS_ENCODING = NO
|
|
+
|
|
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
|
|
+# include brief member descriptions after the members that are listed in
|
|
+# the file and class documentation (similar to JavaDoc).
|
|
+# Set to NO to disable this.
|
|
+
|
|
+BRIEF_MEMBER_DESC = YES
|
|
+
|
|
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
|
|
+# the brief description of a member or function before the detailed description.
|
|
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
|
|
+# brief descriptions will be completely suppressed.
|
|
+
|
|
+REPEAT_BRIEF = YES
|
|
+
|
|
+# This tag implements a quasi-intelligent brief description abbreviator
|
|
+# that is used to form the text in various listings. Each string
|
|
+# in this list, if found as the leading text of the brief description, will be
|
|
+# stripped from the text and the result after processing the whole list, is used
|
|
+# as the annotated text. Otherwise, the brief description is used as-is. If left
|
|
+# blank, the following values are used ("$name" is automatically replaced with the
|
|
+# name of the entity): "The $name class" "The $name widget" "The $name file"
|
|
+# "is" "provides" "specifies" "contains" "represents" "a" "an" "the"
|
|
+
|
|
+ABBREVIATE_BRIEF =
|
|
+
|
|
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
|
|
+# Doxygen will generate a detailed section even if there is only a brief
|
|
+# description.
|
|
+
|
|
+ALWAYS_DETAILED_SEC = NO
|
|
+
|
|
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited
|
|
+# members of a class in the documentation of that class as if those members were
|
|
+# ordinary class members. Constructors, destructors and assignment operators of
|
|
+# the base classes will not be shown.
|
|
+
|
|
+INLINE_INHERITED_MEMB = NO
|
|
+
|
|
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
|
|
+# path before files name in the file list and in the header files. If set
|
|
+# to NO the shortest path that makes the file name unique will be used.
|
|
+
|
|
+FULL_PATH_NAMES = YES
|
|
+
|
|
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
|
|
+# can be used to strip a user-defined part of the path. Stripping is
|
|
+# only done if one of the specified strings matches the left-hand part of
|
|
+# the path. The tag can be used to show relative paths in the file list.
|
|
+# If left blank the directory from which doxygen is run is used as the
|
|
+# path to strip.
|
|
+
|
|
+STRIP_FROM_PATH =
|
|
+
|
|
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
|
|
+# the path mentioned in the documentation of a class, which tells
|
|
+# the reader which header file to include in order to use a class.
|
|
+# If left blank only the name of the header file containing the class
|
|
+# definition is used. Otherwise one should specify the include paths that
|
|
+# are normally passed to the compiler using the -I flag.
|
|
+
|
|
+STRIP_FROM_INC_PATH =
|
|
+
|
|
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
|
|
+# (but less readable) file names. This can be useful is your file systems
|
|
+# doesn't support long names like on DOS, Mac, or CD-ROM.
|
|
+
|
|
+SHORT_NAMES = NO
|
|
+
|
|
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
|
|
+# will interpret the first line (until the first dot) of a JavaDoc-style
|
|
+# comment as the brief description. If set to NO, the JavaDoc
|
|
+# comments will behave just like the Qt-style comments (thus requiring an
|
|
+# explicit @brief command for a brief description.
|
|
+
|
|
+JAVADOC_AUTOBRIEF = NO
|
|
+
|
|
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
|
|
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
|
|
+# comments) as a brief description. This used to be the default behaviour.
|
|
+# The new default is to treat a multi-line C++ comment block as a detailed
|
|
+# description. Set this tag to YES if you prefer the old behaviour instead.
|
|
+
|
|
+MULTILINE_CPP_IS_BRIEF = NO
|
|
+
|
|
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen
|
|
+# will output the detailed description near the top, like JavaDoc.
|
|
+# If set to NO, the detailed description appears after the member
|
|
+# documentation.
|
|
+
|
|
+DETAILS_AT_TOP = NO
|
|
+
|
|
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
|
|
+# member inherits the documentation from any documented member that it
|
|
+# re-implements.
|
|
+
|
|
+INHERIT_DOCS = YES
|
|
+
|
|
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
|
|
+# tag is set to YES, then doxygen will reuse the documentation of the first
|
|
+# member in the group (if any) for the other members of the group. By default
|
|
+# all members of a group must be documented explicitly.
|
|
+
|
|
+DISTRIBUTE_GROUP_DOC = NO
|
|
+
|
|
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
|
|
+# Doxygen uses this value to replace tabs by spaces in code fragments.
|
|
+
|
|
+TAB_SIZE = 8
|
|
+
|
|
+# This tag can be used to specify a number of aliases that acts
|
|
+# as commands in the documentation. An alias has the form "name=value".
|
|
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
|
|
+# put the command \sideeffect (or @sideeffect) in the documentation, which
|
|
+# will result in a user-defined paragraph with heading "Side Effects:".
|
|
+# You can put \n's in the value part of an alias to insert newlines.
|
|
+
|
|
+ALIASES =
|
|
+
|
|
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
|
|
+# only. Doxygen will then generate output that is more tailored for C.
|
|
+# For instance, some of the names that are used will be different. The list
|
|
+# of all members will be omitted, etc.
|
|
+
|
|
+OPTIMIZE_OUTPUT_FOR_C = YES
|
|
+
|
|
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources
|
|
+# only. Doxygen will then generate output that is more tailored for Java.
|
|
+# For instance, namespaces will be presented as packages, qualified scopes
|
|
+# will look different, etc.
|
|
+
|
|
+OPTIMIZE_OUTPUT_JAVA = NO
|
|
+
|
|
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
|
|
+# the same type (for instance a group of public functions) to be put as a
|
|
+# subgroup of that type (e.g. under the Public Functions section). Set it to
|
|
+# NO to prevent subgrouping. Alternatively, this can be done per class using
|
|
+# the \nosubgrouping command.
|
|
+
|
|
+SUBGROUPING = YES
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# Build related configuration options
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
|
|
+# documentation are documented, even if no documentation was available.
|
|
+# Private class members and static file members will be hidden unless
|
|
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
|
|
+
|
|
+EXTRACT_ALL = YES
|
|
+
|
|
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
|
|
+# will be included in the documentation.
|
|
+
|
|
+EXTRACT_PRIVATE = YES
|
|
+
|
|
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
|
|
+# will be included in the documentation.
|
|
+
|
|
+EXTRACT_STATIC = YES
|
|
+
|
|
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
|
|
+# defined locally in source files will be included in the documentation.
|
|
+# If set to NO only classes defined in header files are included.
|
|
+
|
|
+EXTRACT_LOCAL_CLASSES = YES
|
|
+
|
|
+# This flag is only useful for Objective-C code. When set to YES local
|
|
+# methods, which are defined in the implementation section but not in
|
|
+# the interface are included in the documentation.
|
|
+# If set to NO (the default) only methods in the interface are included.
|
|
+
|
|
+EXTRACT_LOCAL_METHODS = YES
|
|
+
|
|
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
|
|
+# undocumented members of documented classes, files or namespaces.
|
|
+# If set to NO (the default) these members will be included in the
|
|
+# various overviews, but no documentation section is generated.
|
|
+# This option has no effect if EXTRACT_ALL is enabled.
|
|
+
|
|
+HIDE_UNDOC_MEMBERS = NO
|
|
+
|
|
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
|
|
+# undocumented classes that are normally visible in the class hierarchy.
|
|
+# If set to NO (the default) these classes will be included in the various
|
|
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
|
|
+
|
|
+HIDE_UNDOC_CLASSES = NO
|
|
+
|
|
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
|
|
+# friend (class|struct|union) declarations.
|
|
+# If set to NO (the default) these declarations will be included in the
|
|
+# documentation.
|
|
+
|
|
+HIDE_FRIEND_COMPOUNDS = NO
|
|
+
|
|
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
|
|
+# documentation blocks found inside the body of a function.
|
|
+# If set to NO (the default) these blocks will be appended to the
|
|
+# function's detailed documentation block.
|
|
+
|
|
+HIDE_IN_BODY_DOCS = NO
|
|
+
|
|
+# The INTERNAL_DOCS tag determines if documentation
|
|
+# that is typed after a \internal command is included. If the tag is set
|
|
+# to NO (the default) then the documentation will be excluded.
|
|
+# Set it to YES to include the internal documentation.
|
|
+
|
|
+INTERNAL_DOCS = NO
|
|
+
|
|
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
|
|
+# file names in lower-case letters. If set to YES upper-case letters are also
|
|
+# allowed. This is useful if you have classes or files whose names only differ
|
|
+# in case and if your file system supports case sensitive file names. Windows
|
|
+# and Mac users are advised to set this option to NO.
|
|
+
|
|
+CASE_SENSE_NAMES = YES
|
|
+
|
|
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
|
|
+# will show members with their full class and namespace scopes in the
|
|
+# documentation. If set to YES the scope will be hidden.
|
|
+
|
|
+HIDE_SCOPE_NAMES = NO
|
|
+
|
|
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
|
|
+# will put a list of the files that are included by a file in the documentation
|
|
+# of that file.
|
|
+
|
|
+SHOW_INCLUDE_FILES = YES
|
|
+
|
|
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
|
|
+# is inserted in the documentation for inline members.
|
|
+
|
|
+INLINE_INFO = YES
|
|
+
|
|
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
|
|
+# will sort the (detailed) documentation of file and class members
|
|
+# alphabetically by member name. If set to NO the members will appear in
|
|
+# declaration order.
|
|
+
|
|
+SORT_MEMBER_DOCS = YES
|
|
+
|
|
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
|
|
+# brief documentation of file, namespace and class members alphabetically
|
|
+# by member name. If set to NO (the default) the members will appear in
|
|
+# declaration order.
|
|
+
|
|
+SORT_BRIEF_DOCS = NO
|
|
+
|
|
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
|
|
+# sorted by fully-qualified names, including namespaces. If set to
|
|
+# NO (the default), the class list will be sorted only by class name,
|
|
+# not including the namespace part.
|
|
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
|
|
+# Note: This option applies only to the class list, not to the
|
|
+# alphabetical list.
|
|
+
|
|
+SORT_BY_SCOPE_NAME = NO
|
|
+
|
|
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
|
|
+# disable (NO) the todo list. This list is created by putting \todo
|
|
+# commands in the documentation.
|
|
+
|
|
+GENERATE_TODOLIST = YES
|
|
+
|
|
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
|
|
+# disable (NO) the test list. This list is created by putting \test
|
|
+# commands in the documentation.
|
|
+
|
|
+GENERATE_TESTLIST = YES
|
|
+
|
|
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
|
|
+# disable (NO) the bug list. This list is created by putting \bug
|
|
+# commands in the documentation.
|
|
+
|
|
+GENERATE_BUGLIST = YES
|
|
+
|
|
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
|
|
+# disable (NO) the deprecated list. This list is created by putting
|
|
+# \deprecated commands in the documentation.
|
|
+
|
|
+GENERATE_DEPRECATEDLIST= YES
|
|
+
|
|
+# The ENABLED_SECTIONS tag can be used to enable conditional
|
|
+# documentation sections, marked by \if sectionname ... \endif.
|
|
+
|
|
+ENABLED_SECTIONS =
|
|
+
|
|
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
|
|
+# the initial value of a variable or define consists of for it to appear in
|
|
+# the documentation. If the initializer consists of more lines than specified
|
|
+# here it will be hidden. Use a value of 0 to hide initializers completely.
|
|
+# The appearance of the initializer of individual variables and defines in the
|
|
+# documentation can be controlled using \showinitializer or \hideinitializer
|
|
+# command in the documentation regardless of this setting.
|
|
+
|
|
+MAX_INITIALIZER_LINES = 30
|
|
+
|
|
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
|
|
+# at the bottom of the documentation of classes and structs. If set to YES the
|
|
+# list will mention the files that were used to generate the documentation.
|
|
+
|
|
+SHOW_USED_FILES = YES
|
|
+
|
|
+# If the sources in your project are distributed over multiple directories
|
|
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
|
|
+# in the documentation.
|
|
+
|
|
+SHOW_DIRECTORIES = YES
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# configuration options related to warning and progress messages
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# The QUIET tag can be used to turn on/off the messages that are generated
|
|
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
|
|
+
|
|
+QUIET = NO
|
|
+
|
|
+# The WARNINGS tag can be used to turn on/off the warning messages that are
|
|
+# generated by doxygen. Possible values are YES and NO. If left blank
|
|
+# NO is used.
|
|
+
|
|
+WARNINGS = YES
|
|
+
|
|
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
|
|
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
|
|
+# automatically be disabled.
|
|
+
|
|
+WARN_IF_UNDOCUMENTED = YES
|
|
+
|
|
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
|
|
+# potential errors in the documentation, such as not documenting some
|
|
+# parameters in a documented function, or documenting parameters that
|
|
+# don't exist or using markup commands wrongly.
|
|
+
|
|
+WARN_IF_DOC_ERROR = YES
|
|
+
|
|
+# The WARN_FORMAT tag determines the format of the warning messages that
|
|
+# doxygen can produce. The string should contain the $file, $line, and $text
|
|
+# tags, which will be replaced by the file and line number from which the
|
|
+# warning originated and the warning text.
|
|
+
|
|
+WARN_FORMAT = "$file:$line: $text"
|
|
+
|
|
+# The WARN_LOGFILE tag can be used to specify a file to which warning
|
|
+# and error messages should be written. If left blank the output is written
|
|
+# to stderr.
|
|
+
|
|
+WARN_LOGFILE =
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# configuration options related to the input files
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# The INPUT tag can be used to specify the files and/or directories that contain
|
|
+# documented source files. You may enter file names like "myfile.cpp" or
|
|
+# directories like "/usr/src/myproject". Separate the files or directories
|
|
+# with spaces.
|
|
+
|
|
+INPUT = src
|
|
+
|
|
+# If the value of the INPUT tag contains directories, you can use the
|
|
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
|
|
+# and *.h) to filter out the source-files in the directories. If left
|
|
+# blank the following patterns are tested:
|
|
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp
|
|
+# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm
|
|
+
|
|
+FILE_PATTERNS =
|
|
+
|
|
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
|
|
+# should be searched for input files as well. Possible values are YES and NO.
|
|
+# If left blank NO is used.
|
|
+
|
|
+RECURSIVE = YES
|
|
+
|
|
+# The EXCLUDE tag can be used to specify files and/or directories that should
|
|
+# excluded from the INPUT source files. This way you can easily exclude a
|
|
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
|
|
+
|
|
+EXCLUDE =
|
|
+
|
|
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories
|
|
+# that are symbolic links (a Unix filesystem feature) are excluded from the input.
|
|
+
|
|
+EXCLUDE_SYMLINKS = NO
|
|
+
|
|
+# If the value of the INPUT tag contains directories, you can use the
|
|
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
|
|
+# certain files from those directories.
|
|
+
|
|
+EXCLUDE_PATTERNS =
|
|
+
|
|
+# The EXAMPLE_PATH tag can be used to specify one or more files or
|
|
+# directories that contain example code fragments that are included (see
|
|
+# the \include command).
|
|
+
|
|
+EXAMPLE_PATH =
|
|
+
|
|
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
|
|
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
|
|
+# and *.h) to filter out the source-files in the directories. If left
|
|
+# blank all files are included.
|
|
+
|
|
+EXAMPLE_PATTERNS =
|
|
+
|
|
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
|
|
+# searched for input files to be used with the \include or \dontinclude
|
|
+# commands irrespective of the value of the RECURSIVE tag.
|
|
+# Possible values are YES and NO. If left blank NO is used.
|
|
+
|
|
+EXAMPLE_RECURSIVE = NO
|
|
+
|
|
+# The IMAGE_PATH tag can be used to specify one or more files or
|
|
+# directories that contain image that are included in the documentation (see
|
|
+# the \image command).
|
|
+
|
|
+IMAGE_PATH =
|
|
+
|
|
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
|
|
+# invoke to filter for each input file. Doxygen will invoke the filter program
|
|
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
|
|
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
|
|
+# input file. Doxygen will then use the output that the filter program writes
|
|
+# to standard output. If FILTER_PATTERNS is specified, this tag will be
|
|
+# ignored.
|
|
+
|
|
+INPUT_FILTER =
|
|
+
|
|
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
|
|
+# basis. Doxygen will compare the file name with each pattern and apply the
|
|
+# filter if there is a match. The filters are a list of the form:
|
|
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
|
|
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
|
|
+# is applied to all files.
|
|
+
|
|
+FILTER_PATTERNS =
|
|
+
|
|
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
|
|
+# INPUT_FILTER) will be used to filter the input files when producing source
|
|
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
|
|
+
|
|
+FILTER_SOURCE_FILES = NO
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# configuration options related to source browsing
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
|
|
+# be generated. Documented entities will be cross-referenced with these sources.
|
|
+# Note: To get rid of all source code in the generated output, make sure also
|
|
+# VERBATIM_HEADERS is set to NO.
|
|
+
|
|
+SOURCE_BROWSER = YES
|
|
+
|
|
+# Setting the INLINE_SOURCES tag to YES will include the body
|
|
+# of functions and classes directly in the documentation.
|
|
+
|
|
+INLINE_SOURCES = YES
|
|
+
|
|
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
|
|
+# doxygen to hide any special comment blocks from generated source code
|
|
+# fragments. Normal C and C++ comments will always remain visible.
|
|
+
|
|
+STRIP_CODE_COMMENTS = NO
|
|
+
|
|
+# If the REFERENCED_BY_RELATION tag is set to YES (the default)
|
|
+# then for each documented function all documented
|
|
+# functions referencing it will be listed.
|
|
+
|
|
+REFERENCED_BY_RELATION = YES
|
|
+
|
|
+# If the REFERENCES_RELATION tag is set to YES (the default)
|
|
+# then for each documented function all documented entities
|
|
+# called/used by that function will be listed.
|
|
+
|
|
+REFERENCES_RELATION = YES
|
|
+
|
|
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
|
|
+# will generate a verbatim copy of the header file for each class for
|
|
+# which an include is specified. Set to NO to disable this.
|
|
+
|
|
+VERBATIM_HEADERS = YES
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# configuration options related to the alphabetical class index
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
|
|
+# of all compounds will be generated. Enable this if the project
|
|
+# contains a lot of classes, structs, unions or interfaces.
|
|
+
|
|
+ALPHABETICAL_INDEX = NO
|
|
+
|
|
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
|
|
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
|
|
+# in which this list will be split (can be a number in the range [1..20])
|
|
+
|
|
+COLS_IN_ALPHA_INDEX = 5
|
|
+
|
|
+# In case all classes in a project start with a common prefix, all
|
|
+# classes will be put under the same header in the alphabetical index.
|
|
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
|
|
+# should be ignored while generating the index headers.
|
|
+
|
|
+IGNORE_PREFIX =
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# configuration options related to the HTML output
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
|
|
+# generate HTML output.
|
|
+
|
|
+GENERATE_HTML = YES
|
|
+
|
|
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
|
|
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
|
|
+# put in front of it. If left blank `html' will be used as the default path.
|
|
+
|
|
+HTML_OUTPUT = html
|
|
+
|
|
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
|
|
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
|
|
+# doxygen will generate files with .html extension.
|
|
+
|
|
+HTML_FILE_EXTENSION = .html
|
|
+
|
|
+# The HTML_HEADER tag can be used to specify a personal HTML header for
|
|
+# each generated HTML page. If it is left blank doxygen will generate a
|
|
+# standard header.
|
|
+
|
|
+HTML_HEADER =
|
|
+
|
|
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
|
|
+# each generated HTML page. If it is left blank doxygen will generate a
|
|
+# standard footer.
|
|
+
|
|
+HTML_FOOTER =
|
|
+
|
|
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
|
|
+# style sheet that is used by each HTML page. It can be used to
|
|
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
|
|
+# will generate a default style sheet. Note that doxygen will try to copy
|
|
+# the style sheet file to the HTML output directory, so don't put your own
|
|
+# stylesheet in the HTML output directory as well, or it will be erased!
|
|
+
|
|
+HTML_STYLESHEET =
|
|
+
|
|
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
|
|
+# files or namespaces will be aligned in HTML using tables. If set to
|
|
+# NO a bullet list will be used.
|
|
+
|
|
+HTML_ALIGN_MEMBERS = YES
|
|
+
|
|
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
|
|
+# will be generated that can be used as input for tools like the
|
|
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm)
|
|
+# of the generated HTML documentation.
|
|
+
|
|
+GENERATE_HTMLHELP = NO
|
|
+
|
|
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
|
|
+# be used to specify the file name of the resulting .chm file. You
|
|
+# can add a path in front of the file if the result should not be
|
|
+# written to the html output directory.
|
|
+
|
|
+CHM_FILE =
|
|
+
|
|
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
|
|
+# be used to specify the location (absolute path including file name) of
|
|
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
|
|
+# the HTML help compiler on the generated index.hhp.
|
|
+
|
|
+HHC_LOCATION =
|
|
+
|
|
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
|
|
+# controls if a separate .chi index file is generated (YES) or that
|
|
+# it should be included in the master .chm file (NO).
|
|
+
|
|
+GENERATE_CHI = NO
|
|
+
|
|
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
|
|
+# controls whether a binary table of contents is generated (YES) or a
|
|
+# normal table of contents (NO) in the .chm file.
|
|
+
|
|
+BINARY_TOC = NO
|
|
+
|
|
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
|
|
+# to the contents of the HTML help documentation and to the tree view.
|
|
+
|
|
+TOC_EXPAND = NO
|
|
+
|
|
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
|
|
+# top of each HTML page. The value NO (the default) enables the index and
|
|
+# the value YES disables it.
|
|
+
|
|
+DISABLE_INDEX = NO
|
|
+
|
|
+# This tag can be used to set the number of enum values (range [1..20])
|
|
+# that doxygen will group on one line in the generated HTML documentation.
|
|
+
|
|
+ENUM_VALUES_PER_LINE = 4
|
|
+
|
|
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
|
|
+# generated containing a tree-like index structure (just like the one that
|
|
+# is generated for HTML Help). For this to work a browser that supports
|
|
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
|
|
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
|
|
+# probably better off using the HTML help feature.
|
|
+
|
|
+GENERATE_TREEVIEW = NO
|
|
+
|
|
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
|
|
+# used to set the initial width (in pixels) of the frame in which the tree
|
|
+# is shown.
|
|
+
|
|
+TREEVIEW_WIDTH = 250
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# configuration options related to the LaTeX output
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
|
|
+# generate Latex output.
|
|
+
|
|
+GENERATE_LATEX = YES
|
|
+
|
|
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
|
|
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
|
|
+# put in front of it. If left blank `latex' will be used as the default path.
|
|
+
|
|
+LATEX_OUTPUT = latex
|
|
+
|
|
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
|
|
+# invoked. If left blank `latex' will be used as the default command name.
|
|
+
|
|
+LATEX_CMD_NAME = latex
|
|
+
|
|
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
|
|
+# generate index for LaTeX. If left blank `makeindex' will be used as the
|
|
+# default command name.
|
|
+
|
|
+MAKEINDEX_CMD_NAME = makeindex
|
|
+
|
|
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
|
|
+# LaTeX documents. This may be useful for small projects and may help to
|
|
+# save some trees in general.
|
|
+
|
|
+COMPACT_LATEX = NO
|
|
+
|
|
+# The PAPER_TYPE tag can be used to set the paper type that is used
|
|
+# by the printer. Possible values are: a4, a4wide, letter, legal and
|
|
+# executive. If left blank a4wide will be used.
|
|
+
|
|
+PAPER_TYPE = a4wide
|
|
+
|
|
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
|
|
+# packages that should be included in the LaTeX output.
|
|
+
|
|
+EXTRA_PACKAGES =
|
|
+
|
|
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
|
|
+# the generated latex document. The header should contain everything until
|
|
+# the first chapter. If it is left blank doxygen will generate a
|
|
+# standard header. Notice: only use this tag if you know what you are doing!
|
|
+
|
|
+LATEX_HEADER =
|
|
+
|
|
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
|
|
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
|
|
+# contain links (just like the HTML output) instead of page references
|
|
+# This makes the output suitable for online browsing using a pdf viewer.
|
|
+
|
|
+PDF_HYPERLINKS = NO
|
|
+
|
|
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
|
|
+# plain latex in the generated Makefile. Set this option to YES to get a
|
|
+# higher quality PDF documentation.
|
|
+
|
|
+USE_PDFLATEX = NO
|
|
+
|
|
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
|
|
+# command to the generated LaTeX files. This will instruct LaTeX to keep
|
|
+# running if errors occur, instead of asking the user for help.
|
|
+# This option is also used when generating formulas in HTML.
|
|
+
|
|
+LATEX_BATCHMODE = NO
|
|
+
|
|
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
|
|
+# include the index chapters (such as File Index, Compound Index, etc.)
|
|
+# in the output.
|
|
+
|
|
+LATEX_HIDE_INDICES = NO
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# configuration options related to the RTF output
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
|
|
+# The RTF output is optimized for Word 97 and may not look very pretty with
|
|
+# other RTF readers or editors.
|
|
+
|
|
+GENERATE_RTF = NO
|
|
+
|
|
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
|
|
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
|
|
+# put in front of it. If left blank `rtf' will be used as the default path.
|
|
+
|
|
+RTF_OUTPUT = rtf
|
|
+
|
|
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
|
|
+# RTF documents. This may be useful for small projects and may help to
|
|
+# save some trees in general.
|
|
+
|
|
+COMPACT_RTF = NO
|
|
+
|
|
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
|
|
+# will contain hyperlink fields. The RTF file will
|
|
+# contain links (just like the HTML output) instead of page references.
|
|
+# This makes the output suitable for online browsing using WORD or other
|
|
+# programs which support those fields.
|
|
+# Note: wordpad (write) and others do not support links.
|
|
+
|
|
+RTF_HYPERLINKS = NO
|
|
+
|
|
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
|
|
+# config file, i.e. a series of assignments. You only have to provide
|
|
+# replacements, missing definitions are set to their default value.
|
|
+
|
|
+RTF_STYLESHEET_FILE =
|
|
+
|
|
+# Set optional variables used in the generation of an rtf document.
|
|
+# Syntax is similar to doxygen's config file.
|
|
+
|
|
+RTF_EXTENSIONS_FILE =
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# configuration options related to the man page output
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
|
|
+# generate man pages
|
|
+
|
|
+GENERATE_MAN = NO
|
|
+
|
|
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
|
|
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
|
|
+# put in front of it. If left blank `man' will be used as the default path.
|
|
+
|
|
+MAN_OUTPUT = man
|
|
+
|
|
+# The MAN_EXTENSION tag determines the extension that is added to
|
|
+# the generated man pages (default is the subroutine's section .3)
|
|
+
|
|
+MAN_EXTENSION = .3
|
|
+
|
|
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
|
|
+# then it will generate one additional man file for each entity
|
|
+# documented in the real man page(s). These additional files
|
|
+# only source the real man page, but without them the man command
|
|
+# would be unable to find the correct page. The default is NO.
|
|
+
|
|
+MAN_LINKS = NO
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# configuration options related to the XML output
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the GENERATE_XML tag is set to YES Doxygen will
|
|
+# generate an XML file that captures the structure of
|
|
+# the code including all documentation.
|
|
+
|
|
+GENERATE_XML = NO
|
|
+
|
|
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
|
|
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
|
|
+# put in front of it. If left blank `xml' will be used as the default path.
|
|
+
|
|
+XML_OUTPUT = xml
|
|
+
|
|
+# The XML_SCHEMA tag can be used to specify an XML schema,
|
|
+# which can be used by a validating XML parser to check the
|
|
+# syntax of the XML files.
|
|
+
|
|
+XML_SCHEMA =
|
|
+
|
|
+# The XML_DTD tag can be used to specify an XML DTD,
|
|
+# which can be used by a validating XML parser to check the
|
|
+# syntax of the XML files.
|
|
+
|
|
+XML_DTD =
|
|
+
|
|
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
|
|
+# dump the program listings (including syntax highlighting
|
|
+# and cross-referencing information) to the XML output. Note that
|
|
+# enabling this will significantly increase the size of the XML output.
|
|
+
|
|
+XML_PROGRAMLISTING = YES
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# configuration options for the AutoGen Definitions output
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
|
|
+# generate an AutoGen Definitions (see autogen.sf.net) file
|
|
+# that captures the structure of the code including all
|
|
+# documentation. Note that this feature is still experimental
|
|
+# and incomplete at the moment.
|
|
+
|
|
+GENERATE_AUTOGEN_DEF = NO
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# configuration options related to the Perl module output
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
|
|
+# generate a Perl module file that captures the structure of
|
|
+# the code including all documentation. Note that this
|
|
+# feature is still experimental and incomplete at the
|
|
+# moment.
|
|
+
|
|
+GENERATE_PERLMOD = NO
|
|
+
|
|
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
|
|
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
|
|
+# to generate PDF and DVI output from the Perl module output.
|
|
+
|
|
+PERLMOD_LATEX = NO
|
|
+
|
|
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
|
|
+# nicely formatted so it can be parsed by a human reader. This is useful
|
|
+# if you want to understand what is going on. On the other hand, if this
|
|
+# tag is set to NO the size of the Perl module output will be much smaller
|
|
+# and Perl will parse it just the same.
|
|
+
|
|
+PERLMOD_PRETTY = YES
|
|
+
|
|
+# The names of the make variables in the generated doxyrules.make file
|
|
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
|
|
+# This is useful so different doxyrules.make files included by the same
|
|
+# Makefile don't overwrite each other's variables.
|
|
+
|
|
+PERLMOD_MAKEVAR_PREFIX =
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# Configuration options related to the preprocessor
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
|
|
+# evaluate all C-preprocessor directives found in the sources and include
|
|
+# files.
|
|
+
|
|
+ENABLE_PREPROCESSING = YES
|
|
+
|
|
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
|
|
+# names in the source code. If set to NO (the default) only conditional
|
|
+# compilation will be performed. Macro expansion can be done in a controlled
|
|
+# way by setting EXPAND_ONLY_PREDEF to YES.
|
|
+
|
|
+MACRO_EXPANSION = NO
|
|
+
|
|
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
|
|
+# then the macro expansion is limited to the macros specified with the
|
|
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
|
|
+
|
|
+EXPAND_ONLY_PREDEF = NO
|
|
+
|
|
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
|
|
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
|
|
+
|
|
+SEARCH_INCLUDES = YES
|
|
+
|
|
+# The INCLUDE_PATH tag can be used to specify one or more directories that
|
|
+# contain include files that are not input files but should be processed by
|
|
+# the preprocessor.
|
|
+
|
|
+INCLUDE_PATH =
|
|
+
|
|
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
|
|
+# patterns (like *.h and *.hpp) to filter out the header-files in the
|
|
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
|
|
+# be used.
|
|
+
|
|
+INCLUDE_FILE_PATTERNS =
|
|
+
|
|
+# The PREDEFINED tag can be used to specify one or more macro names that
|
|
+# are defined before the preprocessor is started (similar to the -D option of
|
|
+# gcc). The argument of the tag is a list of macros of the form: name
|
|
+# or name=definition (no spaces). If the definition and the = are
|
|
+# omitted =1 is assumed. To prevent a macro definition from being
|
|
+# undefined via #undef or recursively expanded use the := operator
|
|
+# instead of the = operator.
|
|
+
|
|
+PREDEFINED =
|
|
+
|
|
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
|
|
+# this tag can be used to specify a list of macro names that should be expanded.
|
|
+# The macro definition that is found in the sources will be used.
|
|
+# Use the PREDEFINED tag if you want to use a different macro definition.
|
|
+
|
|
+EXPAND_AS_DEFINED =
|
|
+
|
|
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
|
|
+# doxygen's preprocessor will remove all function-like macros that are alone
|
|
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
|
|
+# function macros are typically used for boiler-plate code, and will confuse the
|
|
+# parser if not removed.
|
|
+
|
|
+SKIP_FUNCTION_MACROS = YES
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# Configuration::additions related to external references
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# The TAGFILES option can be used to specify one or more tagfiles.
|
|
+# Optionally an initial location of the external documentation
|
|
+# can be added for each tagfile. The format of a tag file without
|
|
+# this location is as follows:
|
|
+# TAGFILES = file1 file2 ...
|
|
+# Adding location for the tag files is done as follows:
|
|
+# TAGFILES = file1=loc1 "file2 = loc2" ...
|
|
+# where "loc1" and "loc2" can be relative or absolute paths or
|
|
+# URLs. If a location is present for each tag, the installdox tool
|
|
+# does not have to be run to correct the links.
|
|
+# Note that each tag file must have a unique name
|
|
+# (where the name does NOT include the path)
|
|
+# If a tag file is not located in the directory in which doxygen
|
|
+# is run, you must also specify the path to the tagfile here.
|
|
+
|
|
+TAGFILES =
|
|
+
|
|
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
|
|
+# a tag file that is based on the input files it reads.
|
|
+
|
|
+GENERATE_TAGFILE =
|
|
+
|
|
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
|
|
+# in the class index. If set to NO only the inherited external classes
|
|
+# will be listed.
|
|
+
|
|
+ALLEXTERNALS = NO
|
|
+
|
|
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
|
|
+# in the modules index. If set to NO, only the current project's groups will
|
|
+# be listed.
|
|
+
|
|
+EXTERNAL_GROUPS = YES
|
|
+
|
|
+# The PERL_PATH should be the absolute path and name of the perl script
|
|
+# interpreter (i.e. the result of `which perl').
|
|
+
|
|
+PERL_PATH = /usr/bin/perl
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# Configuration options related to the dot tool
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
|
|
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or
|
|
+# super classes. Setting the tag to NO turns the diagrams off. Note that this
|
|
+# option is superseded by the HAVE_DOT option below. This is only a fallback. It is
|
|
+# recommended to install and use dot, since it yields more powerful graphs.
|
|
+
|
|
+CLASS_DIAGRAMS = YES
|
|
+
|
|
+# If set to YES, the inheritance and collaboration graphs will hide
|
|
+# inheritance and usage relations if the target is undocumented
|
|
+# or is not a class.
|
|
+
|
|
+HIDE_UNDOC_RELATIONS = YES
|
|
+
|
|
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
|
|
+# available from the path. This tool is part of Graphviz, a graph visualization
|
|
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
|
|
+# have no effect if this option is set to NO (the default)
|
|
+
|
|
+HAVE_DOT = NO
|
|
+
|
|
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
|
|
+# will generate a graph for each documented class showing the direct and
|
|
+# indirect inheritance relations. Setting this tag to YES will force the
|
|
+# the CLASS_DIAGRAMS tag to NO.
|
|
+
|
|
+CLASS_GRAPH = YES
|
|
+
|
|
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
|
|
+# will generate a graph for each documented class showing the direct and
|
|
+# indirect implementation dependencies (inheritance, containment, and
|
|
+# class references variables) of the class with other documented classes.
|
|
+
|
|
+COLLABORATION_GRAPH = YES
|
|
+
|
|
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
|
|
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
|
|
+# Language.
|
|
+
|
|
+UML_LOOK = NO
|
|
+
|
|
+# If set to YES, the inheritance and collaboration graphs will show the
|
|
+# relations between templates and their instances.
|
|
+
|
|
+TEMPLATE_RELATIONS = NO
|
|
+
|
|
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
|
|
+# tags are set to YES then doxygen will generate a graph for each documented
|
|
+# file showing the direct and indirect include dependencies of the file with
|
|
+# other documented files.
|
|
+
|
|
+INCLUDE_GRAPH = YES
|
|
+
|
|
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
|
|
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
|
|
+# documented header file showing the documented files that directly or
|
|
+# indirectly include this file.
|
|
+
|
|
+INCLUDED_BY_GRAPH = YES
|
|
+
|
|
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will
|
|
+# generate a call dependency graph for every global function or class method.
|
|
+# Note that enabling this option will significantly increase the time of a run.
|
|
+# So in most cases it will be better to enable call graphs for selected
|
|
+# functions only using the \callgraph command.
|
|
+
|
|
+CALL_GRAPH = NO
|
|
+
|
|
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
|
|
+# will graphical hierarchy of all classes instead of a textual one.
|
|
+
|
|
+GRAPHICAL_HIERARCHY = YES
|
|
+
|
|
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
|
|
+# generated by dot. Possible values are png, jpg, or gif
|
|
+# If left blank png will be used.
|
|
+
|
|
+DOT_IMAGE_FORMAT = png
|
|
+
|
|
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
|
|
+# found. If left blank, it is assumed the dot tool can be found on the path.
|
|
+
|
|
+DOT_PATH =
|
|
+
|
|
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
|
|
+# contain dot files that are included in the documentation (see the
|
|
+# \dotfile command).
|
|
+
|
|
+DOTFILE_DIRS =
|
|
+
|
|
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width
|
|
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
|
|
+# this value, doxygen will try to truncate the graph, so that it fits within
|
|
+# the specified constraint. Beware that most browsers cannot cope with very
|
|
+# large images.
|
|
+
|
|
+MAX_DOT_GRAPH_WIDTH = 1024
|
|
+
|
|
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height
|
|
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than
|
|
+# this value, doxygen will try to truncate the graph, so that it fits within
|
|
+# the specified constraint. Beware that most browsers cannot cope with very
|
|
+# large images.
|
|
+
|
|
+MAX_DOT_GRAPH_HEIGHT = 1024
|
|
+
|
|
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
|
|
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
|
|
+# from the root by following a path via at most 3 edges will be shown. Nodes that
|
|
+# lay further from the root node will be omitted. Note that setting this option to
|
|
+# 1 or 2 may greatly reduce the computation time needed for large code bases. Also
|
|
+# note that a graph may be further truncated if the graph's image dimensions are
|
|
+# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT).
|
|
+# If 0 is used for the depth value (the default), the graph is not depth-constrained.
|
|
+
|
|
+MAX_DOT_GRAPH_DEPTH = 0
|
|
+
|
|
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
|
|
+# generate a legend page explaining the meaning of the various boxes and
|
|
+# arrows in the dot generated graphs.
|
|
+
|
|
+GENERATE_LEGEND = YES
|
|
+
|
|
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
|
|
+# remove the intermediate dot files that are used to generate
|
|
+# the various graphs.
|
|
+
|
|
+DOT_CLEANUP = YES
|
|
+
|
|
+#---------------------------------------------------------------------------
|
|
+# Configuration::additions related to the search engine
|
|
+#---------------------------------------------------------------------------
|
|
+
|
|
+# The SEARCHENGINE tag specifies whether or not a search engine should be
|
|
+# used. If set to NO the values of all tags below this one will be ignored.
|
|
+
|
|
+SEARCHENGINE = NO
|
|
diff --git a/INSTALL b/INSTALL
|
|
new file mode 100644
|
|
index 0000000..004396c
|
|
--- /dev/null
|
|
+++ b/INSTALL
|
|
@@ -0,0 +1,13 @@
|
|
+Run make inside the src directory to compile the ptpd binary.
|
|
+
|
|
+You can influence the compilation by setting variables on the
|
|
+make command line:
|
|
+ CPPFLAGS=-DPTPD_DBGV
|
|
+ detailed debugging
|
|
+ CPPFLAGS=-DPTPD_DBG
|
|
+ less detailed debugging
|
|
+ CPPFLAGS="-DHAVE_LINUX_NET_TSTAMP_H -Idep/include"
|
|
+ enable -z linux_hw/linux_sw mode, using bundled
|
|
+ Linux header file
|
|
+
|
|
+The resulting binary works as-is without having to install it.
|
|
diff --git a/extras/Makefile b/extras/Makefile
|
|
new file mode 100644
|
|
index 0000000..bc101e7
|
|
--- /dev/null
|
|
+++ b/extras/Makefile
|
|
@@ -0,0 +1,5 @@
|
|
+MPICC = mpicc
|
|
+CFLAGS = -g -O2
|
|
+
|
|
+timertest: timertest.c
|
|
+ $(MPICC) $(CFLAGS) -lelf -lm -lrt $< -o $@
|
|
\ No newline at end of file
|
|
diff --git a/extras/timertest.c b/extras/timertest.c
|
|
new file mode 100644
|
|
index 0000000..e57a409
|
|
--- /dev/null
|
|
+++ b/extras/timertest.c
|
|
@@ -0,0 +1,779 @@
|
|
+/*******************************************************************
|
|
+ *
|
|
+ * Copyright (c) 2006-2008, Intel Corporation
|
|
+ * All rights reserved.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of the Intel Corporation nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Intel Corporation ''AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Intel Corporation BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ *
|
|
+ *******************************************************************/
|
|
+
|
|
+/**
|
|
+ * This program tests the quality of system time (resolution and
|
|
+ * distribution of increments) and of synchronization between different
|
|
+ * nodes in a cluster. Synchronization is tested by having all processes
|
|
+ * sleep for a while, then let each pair of processes exchange multiple
|
|
+ * messages. Only two processes are active during each message exchange.
|
|
+ *
|
|
+ * Clock offset calculation: For each message exchange three time stamps
|
|
+ * are taken: t_send -> t_middle -> t_recv. Under the assumption that the
|
|
+ * message transmission in both direction is equally fast, it follows that
|
|
+ * (t_send + t_recv) / 2 = t_middle + offset. To remove noise the exchanges
|
|
+ * with the highest (t_recv - t_send) delta are excluded before averaging the
|
|
+ * remaining samples.
|
|
+ *
|
|
+ * Usage: Compile and start as an MPI application with one process per
|
|
+ * node. It runs until killed. For unmonitored operation of a specific
|
|
+ * duration use Intel MPI's "MPD_TIMEOUT" option.
|
|
+ *
|
|
+ * Output: Is written to the syslog and stderr. Output starts with
|
|
+ * some information about the resolution of the system time call. Then
|
|
+ * for each message exchange the process with the smaller rank logs
|
|
+ * the clock offset with its peer.
|
|
+ *
|
|
+ * Analysis: perfbase experiment and input descriptions are provided to
|
|
+ * import the output of timertest and PTPd from a syslog file.
|
|
+ *
|
|
+ * Author: Patrick Ohly
|
|
+ */
|
|
+
|
|
+#include <mpi.h>
|
|
+
|
|
+#ifndef _WIN32
|
|
+# include <unistd.h>
|
|
+# include <sys/time.h>
|
|
+# include <syslog.h>
|
|
+#else
|
|
+# include <windows.h>
|
|
+# include <sys/timeb.h>
|
|
+# define sleep( sec ) Sleep( 1000 * (sec) )
|
|
+# define popen _popen
|
|
+# define pclose _pclose
|
|
+# define pclose _pclose
|
|
+#endif
|
|
+
|
|
+#include <stdio.h>
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <assert.h>
|
|
+#include <math.h>
|
|
+#include <ctype.h>
|
|
+#include <time.h>
|
|
+
|
|
+#define MSG_CNT 1000 /**< number of message to be sent back and forth */
|
|
+#define NUM_BINS 11 /**< number of bins for offset histogram */
|
|
+#define LATENCY_TEST 11 /**< number of seconds that each message latency measurement is supposed to run */
|
|
+#define MAX_SAMPLES 10000 /**< maximum number of samples to collect for median/average clock increment */
|
|
+#define CLOCK_DURATION 5 /**< duration of clock increment test in seconds */
|
|
+
|
|
+#define HAVE_LIBELF_H 1 /**< compile in support for finding functions in virtual dynamic shared object (VDSO);
|
|
+ this is necessary because on Intel 64 that VDSO was added recently in 2.6.23d-rc1
|
|
+ and glibc will not yet use it by itself */
|
|
+
|
|
+#ifndef _WIN32
|
|
+typedef int (*clock_gettime_t)(clockid_t clock_id, struct timespec *tp);
|
|
+static clock_gettime_t my_clock_gettime = clock_gettime;
|
|
+
|
|
+typedef int (*gettimeofday_t)(struct timeval *tp, void *tzp);
|
|
+static gettimeofday_t my_gettimeofday = (void *)gettimeofday;
|
|
+#endif
|
|
+
|
|
+#ifdef HAVE_LIBELF_H
|
|
+#include <libelf.h>
|
|
+/**
|
|
+ * return absolute address of dynamic symbol in Linux kernel vdso
|
|
+ */
|
|
+static void *findVDSOSym(const char *symname);
|
|
+#endif
|
|
+
|
|
+/** type used to count seconds */
|
|
+typedef double seconds_t;
|
|
+
|
|
+/** type used to count clock ticks */
|
|
+typedef long long ticks_t;
|
|
+
|
|
+/**
|
|
+ * format number of seconds with ns/us/ms/s suffix (depending on magnitude)
|
|
+ * and configurable number of digits before the decimal point (width) and after
|
|
+ * it (precision)
|
|
+ */
|
|
+static const char *prettyprintseconds( seconds_t seconds, int width, int precision, char *buffer )
|
|
+{
|
|
+ static char localbuffer[80];
|
|
+ seconds_t absseconds = fabs( seconds );
|
|
+
|
|
+ if( !buffer ) {
|
|
+ buffer = localbuffer;
|
|
+ }
|
|
+
|
|
+ if( absseconds < 1e-6 ) {
|
|
+ sprintf( buffer, "%*.*fns", width, precision, seconds * 1e9 );
|
|
+ } else if( absseconds < 1e-3 ) {
|
|
+ sprintf( buffer, "%*.*fus", width, precision, seconds * 1e6 );
|
|
+ } else if( absseconds < 1 ) {
|
|
+ sprintf( buffer, "%*.*fms", width, precision, seconds * 1e3 );
|
|
+ } else {
|
|
+ sprintf( buffer, "%*.*fs", width, precision, seconds );
|
|
+ }
|
|
+
|
|
+ return buffer;
|
|
+}
|
|
+
|
|
+/** generates a string of 'width' many hash signs */
|
|
+static const char *printbar( int width )
|
|
+{
|
|
+ static char buffer[80];
|
|
+
|
|
+ if( width > sizeof(buffer) - 1 ) {
|
|
+ width = sizeof(buffer) - 1;
|
|
+ }
|
|
+ memset( buffer, '#', width );
|
|
+ buffer[width] = 0;
|
|
+ return buffer;
|
|
+}
|
|
+
|
|
+/** only on Linux: switch between gettimeofday() and clock_gettime() calls */
|
|
+static int usetod;
|
|
+
|
|
+/** returns system time as number of ticks since common epoch */
|
|
+static ticks_t systicks(void)
|
|
+{
|
|
+#ifdef _WIN32
|
|
+ struct _timeb timebuffer;
|
|
+ _ftime( &timebuffer );
|
|
+ return (ticks_t)timebuffer.time * 1000 + timebuffer.millitm;
|
|
+#else
|
|
+ if (usetod) {
|
|
+ struct timeval cur_time;
|
|
+ my_gettimeofday( &cur_time, NULL );
|
|
+ return (ticks_t)cur_time.tv_sec * 1000000 + cur_time.tv_usec;
|
|
+ } else {
|
|
+ struct timespec cur_time;
|
|
+ my_clock_gettime(CLOCK_REALTIME, &cur_time);
|
|
+ return (ticks_t)cur_time.tv_sec * 1000000000ul + cur_time.tv_nsec;
|
|
+ }
|
|
+#endif
|
|
+}
|
|
+
|
|
+/** duration of one clock tick in seconds */
|
|
+static seconds_t clockperiod;
|
|
+
|
|
+/** returns system time as number of seconds since common epoch */
|
|
+static seconds_t systime(void)
|
|
+{
|
|
+ ticks_t ticks = systicks();
|
|
+ return ticks * clockperiod;
|
|
+}
|
|
+
|
|
+/** the result of one ping/pong message exchange */
|
|
+struct sample {
|
|
+ seconds_t t_send, t_middle, t_recv;
|
|
+};
|
|
+
|
|
+/** offset between nodes given one sample */
|
|
+static seconds_t offset( struct sample *sample )
|
|
+{
|
|
+ return (sample->t_recv + sample->t_send) / 2 - sample->t_middle;
|
|
+}
|
|
+
|
|
+/** qsort comparison function for sorting by increasing duration of samples */
|
|
+static int compare_duration( const void *a, const void *b )
|
|
+{
|
|
+ const struct sample *sa = a, *sb = b;
|
|
+ seconds_t tmp = (sb->t_recv - sb->t_send) - (sa->t_recv - sa->t_send);
|
|
+
|
|
+ if( tmp < 0 ) {
|
|
+ return -1;
|
|
+ } else if( tmp > 0 ) {
|
|
+ return 1;
|
|
+ } else {
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * send messages back and forth between two process in MPI_COMM_WORLD
|
|
+ * without time stamping
|
|
+ */
|
|
+static void simplepingpong( int source, int target, int tag, int num )
|
|
+{
|
|
+ MPI_Status status;
|
|
+ int i, rank;
|
|
+ char buffer[1];
|
|
+
|
|
+ MPI_Comm_rank( MPI_COMM_WORLD, &rank );
|
|
+
|
|
+ /* message exchange */
|
|
+ for( i = 0; i < num; i++ ) {
|
|
+ if( rank == target ) {
|
|
+ MPI_Recv( buffer, 0, MPI_BYTE, source, tag, MPI_COMM_WORLD, &status );
|
|
+ MPI_Send( buffer, 0, MPI_BYTE, source, tag, MPI_COMM_WORLD );
|
|
+ } else if( rank == source ) {
|
|
+ MPI_Send( buffer, 0, MPI_BYTE, target, tag, MPI_COMM_WORLD );
|
|
+ MPI_Recv( buffer, 0, MPI_BYTE, target, tag, MPI_COMM_WORLD, &status );
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * send messages back and forth between two process in MPI_COMM_WORLD,
|
|
+ * then calculate the clock offset and log it
|
|
+ */
|
|
+static void pingpong( int source, int target, int tag, int num, int dryrun )
|
|
+{
|
|
+ MPI_Status status;
|
|
+ int i, rank;
|
|
+ char buffer[1];
|
|
+ seconds_t *t_middle;
|
|
+ struct sample *samples;
|
|
+ char host[MPI_MAX_PROCESSOR_NAME + 1], peer[MPI_MAX_PROCESSOR_NAME + 1];
|
|
+ int len;
|
|
+
|
|
+ t_middle = malloc(sizeof(*t_middle) * num);
|
|
+ samples = malloc(sizeof(*samples) * num);
|
|
+
|
|
+ MPI_Comm_rank( MPI_COMM_WORLD, &rank );
|
|
+ MPI_Get_processor_name(host, &len);
|
|
+ host[len] = 0;
|
|
+ memset( buffer, 0, sizeof(buffer) );
|
|
+
|
|
+ /* message exchange */
|
|
+ for( i = 0; i < num; i++ ) {
|
|
+ if( rank == target ) {
|
|
+ MPI_Recv( buffer, 0, MPI_BYTE, source, tag, MPI_COMM_WORLD, &status );
|
|
+ t_middle[i] = systime();
|
|
+ MPI_Send( buffer, 0, MPI_BYTE, source, tag, MPI_COMM_WORLD );
|
|
+ } else if( rank == source ) {
|
|
+ samples[i].t_send = systime();
|
|
+ MPI_Send( buffer, 0, MPI_BYTE, target, tag, MPI_COMM_WORLD );
|
|
+ MPI_Recv( buffer, 0, MPI_BYTE, target, tag, MPI_COMM_WORLD, &status );
|
|
+ samples[i].t_recv = systime();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* calculation of offset */
|
|
+ if( dryrun ) {
|
|
+ /* don't print */
|
|
+ } else if( rank == source ) {
|
|
+ MPI_Datatype type;
|
|
+ MPI_Status status;
|
|
+ int start, end;
|
|
+ seconds_t average_off = 0, deviation_off = 0, min_off = 1e9, max_off = -1e9;
|
|
+ seconds_t average_round = 0, deviation_round = 0, min_round = 1e9, max_round = -1e9;
|
|
+ unsigned int maxcount = 0, histogram[NUM_BINS], bin;
|
|
+ char buffers[10][80];
|
|
+
|
|
+ /* sort incoming time stamps into the right place in each sample */
|
|
+ MPI_Type_vector( num, 1, 3, MPI_DOUBLE, &type );
|
|
+ MPI_Type_commit( &type );
|
|
+ MPI_Recv( &samples[0].t_middle, 1, type, target, 0, MPI_COMM_WORLD, &status );
|
|
+
|
|
+ /* get peer name */
|
|
+ MPI_Recv(peer, sizeof(peer), MPI_CHAR, target, 0, MPI_COMM_WORLD, &status);
|
|
+
|
|
+ /* sort by increasing duration */
|
|
+ qsort( samples, num, sizeof(*samples), compare_duration );
|
|
+
|
|
+ /*
|
|
+ * calculate min, max, average and empirical (n-1) standard deviation
|
|
+ * of offset, ignoring the 10% of the samples at borders
|
|
+ */
|
|
+ memset( histogram, 0, sizeof(histogram) );
|
|
+ start = num * 5 / 100;
|
|
+ end = num * 95 / 100;
|
|
+ for( i = start;
|
|
+ i < end;
|
|
+ i++ ) {
|
|
+ seconds_t tmp;
|
|
+
|
|
+ tmp = offset(samples + i);
|
|
+ if( tmp < min_off ) {
|
|
+ min_off = tmp;
|
|
+ }
|
|
+ if( tmp > max_off ) {
|
|
+ max_off = tmp;
|
|
+ }
|
|
+
|
|
+ tmp = samples[i].t_recv - samples[i].t_send;
|
|
+ if( tmp < min_round ) {
|
|
+ min_round = tmp;
|
|
+ }
|
|
+ if (tmp > max_round) {
|
|
+ max_round = tmp;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for( i = start;
|
|
+ i < end;
|
|
+ i++ ) {
|
|
+ seconds_t off = offset(samples + i);
|
|
+ seconds_t round = samples[i].t_recv - samples[i].t_send;
|
|
+
|
|
+ bin = (off - min_off) * NUM_BINS / (max_off - min_off);
|
|
+ /* sanity check */
|
|
+ if( bin >= NUM_BINS ) {
|
|
+ bin = NUM_BINS - 1;
|
|
+ }
|
|
+ histogram[bin]++;
|
|
+ average_off += off;
|
|
+ deviation_off += off * off;
|
|
+
|
|
+ average_round += round;
|
|
+ deviation_round += round * round;
|
|
+ }
|
|
+ for( bin = 0; bin < NUM_BINS; bin++ ) {
|
|
+ if( histogram[bin] > maxcount ) {
|
|
+ maxcount = histogram[bin];
|
|
+ }
|
|
+ }
|
|
+ average_off /= end - start;
|
|
+ deviation_off = sqrt( deviation_off / ( end - start ) - average_off * average_off );
|
|
+ average_round /= end - start;
|
|
+ deviation_round = sqrt( deviation_round / ( end - start ) - average_round * average_round );
|
|
+
|
|
+ syslog(LOG_INFO, "offset %s - %s", host, peer);
|
|
+ syslog(LOG_INFO,
|
|
+ "min/average/max/deviation of offset and round-trip time: "
|
|
+ "%s %s %s %s %s %s %s %s",
|
|
+ prettyprintseconds( min_off, 0, 3, buffers[0] ),
|
|
+ prettyprintseconds( average_off, 0, 3, buffers[1] ),
|
|
+ prettyprintseconds( max_off, 0, 3, buffers[2] ),
|
|
+ prettyprintseconds( deviation_off, 0, 3, buffers[3] ),
|
|
+ prettyprintseconds( min_round, 0, 3, buffers[4] ),
|
|
+ prettyprintseconds( average_round, 0, 3, buffers[5] ),
|
|
+ prettyprintseconds( max_round, 0, 3, buffers[6] ),
|
|
+ prettyprintseconds( deviation_round, 0, 3, buffers[7] ));
|
|
+ for( bin = 0; bin < NUM_BINS; bin++ ) {
|
|
+ syslog(LOG_INFO, " >= %s: %s %u\n",
|
|
+ prettyprintseconds( min_off + bin * ( max_off - min_off ) / NUM_BINS, 8, 3, buffers[0] ),
|
|
+ printbar( 40 * histogram[bin] / maxcount ),
|
|
+ histogram[bin] );
|
|
+ }
|
|
+ syslog(LOG_INFO, " >= %s: 0\n",
|
|
+ prettyprintseconds( max_off, 8, 3, buffers[0] ));
|
|
+ } else if( rank == target ) {
|
|
+ MPI_Send(t_middle, num, MPI_DOUBLE, source, 0, MPI_COMM_WORLD);
|
|
+ MPI_Send(host, strlen(host)+1, MPI_CHAR, source, 0, MPI_COMM_WORLD);
|
|
+ }
|
|
+
|
|
+ free(samples);
|
|
+ free(t_middle);
|
|
+}
|
|
+
|
|
+/** qsort routine for increasing sort of doubles */
|
|
+static int compare_ticks( const void *a, const void *b )
|
|
+{
|
|
+ ticks_t delta = *(const ticks_t *)a - *(const ticks_t *)b;
|
|
+
|
|
+ return delta < 0 ? -1 :
|
|
+ delta > 0 ? 1 :
|
|
+ 0;
|
|
+}
|
|
+
|
|
+/** the initial clock samples encounted by genhistogram() */
|
|
+static ticks_t samples[MAX_SAMPLES];
|
|
+
|
|
+/** number of entries in samples array */
|
|
+static unsigned int count;
|
|
+
|
|
+/**
|
|
+ * call timer source repeatedly and record delta between samples
|
|
+ * in histogram and samples array
|
|
+ *
|
|
+ * @param duration maximum number of seconds for whole run
|
|
+ * @param min_increase first slot in histogram is for values < 0,
|
|
+ * second for >=0 and < min_increase
|
|
+ * @param bin_size width of all following bins
|
|
+ * @param histogram_size number of slots, including special ones
|
|
+ * @param histogram buffer for histogram, filled by this function
|
|
+ * @return number of calls to systicks()
|
|
+ */
|
|
+static unsigned int genhistogram(seconds_t duration,
|
|
+ ticks_t min_increase,
|
|
+ ticks_t bin_size,
|
|
+ unsigned int histogram_size,
|
|
+ unsigned int *histogram)
|
|
+{
|
|
+ ticks_t increase;
|
|
+ ticks_t startticks, lastticks, nextticks;
|
|
+ ticks_t endticks = duration / clockperiod;
|
|
+ unsigned int calls = 0;
|
|
+
|
|
+ startticks = systicks();
|
|
+ lastticks = 0;
|
|
+ count = 0;
|
|
+ memset(histogram, 0, sizeof(*histogram) * histogram_size);
|
|
+ do {
|
|
+ calls++;
|
|
+ nextticks = systicks() - startticks;
|
|
+ increase = nextticks - lastticks;
|
|
+ if( increase < 0 ) {
|
|
+ histogram[0]++;
|
|
+ } else if( increase > 0 ) {
|
|
+ unsigned int index;
|
|
+
|
|
+ if( count < MAX_SAMPLES ) {
|
|
+ samples[count] = increase;
|
|
+ count++;
|
|
+ }
|
|
+
|
|
+ if( increase < min_increase ) {
|
|
+ index = 1;
|
|
+ } else {
|
|
+ index = (unsigned int)( ( increase - min_increase ) / bin_size ) + 2;
|
|
+ if( index >= histogram_size ) {
|
|
+ index = histogram_size - 1;
|
|
+ }
|
|
+ }
|
|
+ histogram[index]++;
|
|
+ }
|
|
+ lastticks = nextticks;
|
|
+ } while( lastticks < endticks );
|
|
+
|
|
+ return calls;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * runs a timer performance test for the given duration
|
|
+ *
|
|
+ * @param duration duration of test in seconds
|
|
+ */
|
|
+void timerperformance(seconds_t duration)
|
|
+{
|
|
+ unsigned int i;
|
|
+ unsigned int max = 0;
|
|
+ char buffer[3][256];
|
|
+ double average = 0, median = 0;
|
|
+ unsigned int calls;
|
|
+ unsigned int simple_histogram[3];
|
|
+ ticks_t min_increase, bin_size, max_increase;
|
|
+ unsigned int histogram_size;
|
|
+ unsigned int *clockhistogram;
|
|
+
|
|
+ /* determine range of clock increases for real run */
|
|
+ calls = genhistogram(2.0, 1, 1, 3, simple_histogram);
|
|
+ qsort(samples, count, sizeof(samples[0]), compare_ticks);
|
|
+
|
|
+ /* shoot for 10 slots, but allow for some extra slots at both ends as needed */
|
|
+ min_increase = samples[0] == 1 ? 1 : samples[0] * 9 / 10;
|
|
+ max_increase = samples[count - 1];
|
|
+ bin_size = (max_increase - min_increase) / 10;
|
|
+ if (bin_size * clockperiod <= 1e-9) {
|
|
+ bin_size = 1e-9 / clockperiod;
|
|
+ }
|
|
+ histogram_size = (max_increase - min_increase) / bin_size + 3 + 5;
|
|
+ clockhistogram = malloc(histogram_size * sizeof(*clockhistogram));
|
|
+ calls = genhistogram(duration, min_increase, bin_size, histogram_size, clockhistogram);
|
|
+ qsort(samples, count, sizeof(samples[0]), compare_ticks);
|
|
+
|
|
+ /* print average and medium increase */
|
|
+ for( i = 0; i < count; i++ ) {
|
|
+ average += samples[i];
|
|
+ }
|
|
+ average /= count;
|
|
+ qsort(samples, count, sizeof(samples[0]), compare_ticks);
|
|
+ median = samples[count/2];
|
|
+ syslog(LOG_INFO, "average clock increase %s -> %.3fHz, median clock increase %s -> %3.fHz, %s/call",
|
|
+ prettyprintseconds(average * clockperiod, 0, 3, buffer[0]), 1/average/clockperiod,
|
|
+ prettyprintseconds(median * clockperiod, 0, 3, buffer[1]), 1/median/clockperiod,
|
|
+ prettyprintseconds(duration / calls, 0, 3, buffer[2]));
|
|
+
|
|
+ for( i = 0; i < histogram_size; i++ ) {
|
|
+ if( clockhistogram[i] > max ) {
|
|
+ max = clockhistogram[i];
|
|
+ }
|
|
+ }
|
|
+ syslog(LOG_INFO, " < %11.3fus: %s %u",
|
|
+ 0.0,
|
|
+ printbar(clockhistogram[0] * 20 / max),
|
|
+ clockhistogram[0]);
|
|
+ syslog(LOG_INFO, " < %11.3fus: %s %u",
|
|
+ min_increase * clockperiod * 1e6,
|
|
+ printbar( clockhistogram[1] * 20 / max ),
|
|
+ clockhistogram[1]);
|
|
+ for( i = 2; i < histogram_size; i++ ) {
|
|
+ syslog(LOG_INFO, ">= %11.3fus: %s %u",
|
|
+ ( ( i - 2 ) * bin_size + min_increase ) * clockperiod * 1e6,
|
|
+ printbar( clockhistogram[i] * 20 / max ),
|
|
+ clockhistogram[i]);
|
|
+ }
|
|
+ printf( "\n" );
|
|
+
|
|
+ free(clockhistogram);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * command line parameter handling
|
|
+ */
|
|
+static const char usage[] = "timertest <options>\n"
|
|
+#ifndef _WIN32
|
|
+ " -g use gettimeofday() instead of clock_gettime() [default: clock_gettime()\n"
|
|
+#ifdef HAVE_LIBELF_H
|
|
+ " -d do not extract pointer to system functions from virtual dynamic shared\n"
|
|
+ " instead of relying on glibc to do that (current glibc does not\n"
|
|
+ " yet do that for the new 2.6.23-rc1 VDSO) [default: on]\n"
|
|
+#endif
|
|
+#endif
|
|
+ "\n"
|
|
+ "First determines the resolution of the local clocks in each process.\n"
|
|
+ "Then it does ping-pong tests between each pair of processes to measure\n"
|
|
+ "the clock offset at each exchange. Runs until killed.\n"
|
|
+ "Run with one process to just test clock resolution.\n"
|
|
+ ;
|
|
+
|
|
+int main( int argc, char **argv )
|
|
+{
|
|
+ int rank, size;
|
|
+ int option;
|
|
+ int vdso = 1;
|
|
+ int c;
|
|
+ int source, target;
|
|
+
|
|
+ MPI_Init( &argc, &argv );
|
|
+ MPI_Comm_rank( MPI_COMM_WORLD, &rank );
|
|
+ MPI_Comm_size( MPI_COMM_WORLD, &size );
|
|
+
|
|
+ while ((c = getopt(argc, argv,
|
|
+ ""
|
|
+#ifndef _WIN32
|
|
+ "g"
|
|
+#ifdef HAVE_LIBELF_H
|
|
+ "d"
|
|
+#endif
|
|
+#endif
|
|
+ )) != -1) {
|
|
+ switch (c) {
|
|
+#ifndef _WIN32
|
|
+ case 'g':
|
|
+ usetod = 1;
|
|
+ break;
|
|
+#ifdef HAVE_LIBELF_H
|
|
+ case 'd':
|
|
+ vdso = 0;
|
|
+ break;
|
|
+#endif
|
|
+#endif
|
|
+ default:
|
|
+ fputs(usage, stderr);
|
|
+ exit(1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ option = 0;
|
|
+#ifdef LOG_PERROR
|
|
+ option |= LOG_PERROR;
|
|
+#endif
|
|
+
|
|
+ clockperiod =
|
|
+#ifdef _WIN32
|
|
+ 1e-3
|
|
+#else
|
|
+ usetod ?
|
|
+ 1e-6 :
|
|
+ 1e-9
|
|
+#endif
|
|
+ ;
|
|
+
|
|
+ openlog("timertest", option, LOG_USER);
|
|
+
|
|
+#ifdef HAVE_LIBELF_H
|
|
+ if (vdso) {
|
|
+ if (usetod) {
|
|
+ my_gettimeofday = findVDSOSym("gettimeofday");
|
|
+ if (!my_gettimeofday) {
|
|
+ my_gettimeofday = (void *)gettimeofday;
|
|
+ }
|
|
+ } else {
|
|
+ my_clock_gettime = findVDSOSym("clock_gettime");
|
|
+ if (!my_clock_gettime) {
|
|
+ my_clock_gettime = clock_gettime;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
+#ifndef _WIN32
|
|
+ syslog(LOG_NOTICE, "using %s from %s",
|
|
+ usetod ? "gettimeofday()" : "clock_gettime()",
|
|
+ (usetod ? (my_gettimeofday == (void *)gettimeofday) : (my_clock_gettime == clock_gettime)) ? "glibc" : "VDSO");
|
|
+#endif
|
|
+
|
|
+ timerperformance(CLOCK_DURATION);
|
|
+
|
|
+ if (size > 1) {
|
|
+ for( source = 0; source < size - 1; source++ ) {
|
|
+ for( target = source + 1; target < size; target++ ) {
|
|
+ ticks_t start, middle, end;
|
|
+ MPI_Barrier( MPI_COMM_WORLD );
|
|
+ start = systicks();
|
|
+ simplepingpong( source, target, 123, MSG_CNT );
|
|
+ middle = systicks();
|
|
+ pingpong( source, target, 123, MSG_CNT, 1 );
|
|
+ end = systicks();
|
|
+
|
|
+ if (rank == source) {
|
|
+ syslog(LOG_NOTICE, "overhead for %d<->%d ping-pong time stamping: %f%%",
|
|
+ source, target,
|
|
+ 100 * (double)(end - middle) / (double)(middle - start) - 100);
|
|
+ }
|
|
+
|
|
+ MPI_Barrier( MPI_COMM_WORLD );
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ while (size > 1) {
|
|
+ if(!rank) {
|
|
+ syslog(LOG_NOTICE, "%s", printbar(75));
|
|
+ }
|
|
+ for( source = 0; source < size - 1; source++ ) {
|
|
+ for( target = source + 1; target < size; target++ ) {
|
|
+ MPI_Barrier( MPI_COMM_WORLD );
|
|
+ pingpong( source, target, 123, MSG_CNT, 0 );
|
|
+ MPI_Barrier( MPI_COMM_WORLD );
|
|
+ }
|
|
+ }
|
|
+
|
|
+ sleep(LATENCY_TEST);
|
|
+ }
|
|
+
|
|
+ MPI_Finalize();
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef HAVE_LIBELF_H
|
|
+
|
|
+#if __WORDSIZE == 32
|
|
+# define ElfNative_Ehdr Elf32_Ehdr
|
|
+# define elfnative_getehdr elf32_getehdr
|
|
+# define ElfNative_Shdr Elf32_Shdr
|
|
+# define elfnative_getshdr elf32_getshdr
|
|
+# define ElfNative_Sym Elf32_Sym
|
|
+# define ELFNATIVE_ST_BIND ELF32_ST_BIND
|
|
+# define ELFNATIVE_ST_TYPE ELF32_ST_TYPE
|
|
+# define ElfNative_Phdr Elf32_Phdr
|
|
+# define elfnative_getphdr elf32_getphdr
|
|
+#else
|
|
+# define ElfNative_Ehdr Elf64_Ehdr
|
|
+# define elfnative_getehdr elf64_getehdr
|
|
+# define ElfNative_Shdr Elf64_Shdr
|
|
+# define elfnative_getshdr elf64_getshdr
|
|
+# define ElfNative_Sym Elf64_Sym
|
|
+# define ELFNATIVE_ST_BIND ELF64_ST_BIND
|
|
+# define ELFNATIVE_ST_TYPE ELF64_ST_TYPE
|
|
+# define ElfNative_Phdr Elf64_Phdr
|
|
+# define elfnative_getphdr elf64_getphdr
|
|
+#endif
|
|
+
|
|
+static void *findVDSOSym(const char *symname)
|
|
+{
|
|
+ Elf *elf;
|
|
+ void *res = NULL;
|
|
+ char *start = NULL, *end = NULL;
|
|
+ FILE *map;
|
|
+
|
|
+ /**
|
|
+ * Normally a program gets a pointer to the vdso via the ELF aux
|
|
+ * vector entry AT_SYSINFO_EHDR (see
|
|
+ * http://manugarg.googlepages.com/aboutelfauxiliaryvectors) at
|
|
+ * startup. At runtime for a library, reading the memory map is
|
|
+ * simpler.
|
|
+ */
|
|
+ map = fopen("/proc/self/maps", "r");
|
|
+ if (map) {
|
|
+ char line[320];
|
|
+
|
|
+ while (fgets(line, sizeof(line), map) != NULL) {
|
|
+ /* fputs(line, stdout); */
|
|
+ if (strstr(line, "[vdso]")) {
|
|
+ sscanf(line, "%p-%p", &start, &end);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ fclose(map);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * we know where the vdso is and that it contains an ELF object
|
|
+ * => search the symbol via libelf
|
|
+ */
|
|
+ if (start) {
|
|
+ elf = elf_memory(start, end-start);
|
|
+ if (elf) {
|
|
+ Elf_Scn *scn;
|
|
+ size_t loadaddr = 0;
|
|
+
|
|
+ for (scn = elf_nextscn(elf, NULL);
|
|
+ scn && !res;
|
|
+ scn = elf_nextscn(elf, scn)) {
|
|
+ ElfNative_Shdr *shdr = elfnative_getshdr(scn);
|
|
+ Elf_Data *data;
|
|
+
|
|
+ /*
|
|
+ * All addresses are absolute, but the Linux kernel
|
|
+ * maps it at a different one. The load address can be
|
|
+ * determined by looking at any absolute address and
|
|
+ * substracting its offset relative to the file
|
|
+ * beginning because the whole file will be mapped
|
|
+ * into memory. We pick the first section for that.
|
|
+ */
|
|
+ if (!loadaddr) {
|
|
+ loadaddr = shdr->sh_addr - shdr->sh_offset;
|
|
+ }
|
|
+
|
|
+ if( !shdr ||
|
|
+ shdr->sh_type != SHT_DYNSYM ) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ data = elf_getdata(scn, 0);
|
|
+ if (!data || !data->d_size) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ ElfNative_Sym *sym = (ElfNative_Sym *)data->d_buf;
|
|
+ ElfNative_Sym *lastsym = (ElfNative_Sym *)((char *)data->d_buf + data->d_size);
|
|
+
|
|
+ for( ; !res && sym < lastsym; sym++ ) {
|
|
+ const char *name;
|
|
+
|
|
+ if( sym->st_value == 0 || /* need valid address and size */
|
|
+ sym->st_size == 0 ||
|
|
+ ELFNATIVE_ST_TYPE( sym->st_info ) != STT_FUNC || /* only functions */
|
|
+ sym->st_shndx == SHN_UNDEF ) { /* ignore dynamic linker stubs */
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ name = elf_strptr( elf, shdr->sh_link, (size_t)sym->st_name );
|
|
+ if( name && !strcmp(symname, name) ) {
|
|
+ res = (void *)(sym->st_value - loadaddr + start);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ elf_end(elf);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return res;
|
|
+}
|
|
+#endif /* HAVE_LIBELF_H */
|
|
diff --git a/src/Makefile b/src/Makefile
|
|
index cf9afff..e26bf08 100644
|
|
--- a/src/Makefile
|
|
+++ b/src/Makefile
|
|
@@ -6,7 +6,8 @@ CFLAGS = -Wall
|
|
|
|
PROG = ptpd
|
|
OBJ = ptpd.o arith.o bmc.o probe.o protocol.o \
|
|
- dep/msg.o dep/net.o dep/servo.o dep/startup.o dep/sys.o dep/timer.o
|
|
+ dep/msg.o dep/net.o dep/servo.o dep/startup.o dep/sys.o dep/timer.o \
|
|
+ dep/time.o
|
|
HDR = ptpd.h constants.h datatypes.h \
|
|
dep/ptpd_dep.h dep/constants_dep.h dep/datatypes_dep.h
|
|
|
|
diff --git a/src/bmc.c b/src/bmc.c
|
|
index 753b6a5..ca0d426 100644
|
|
--- a/src/bmc.c
|
|
+++ b/src/bmc.c
|
|
@@ -2,12 +2,14 @@
|
|
|
|
#include "ptpd.h"
|
|
|
|
-void initData(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void initData(PtpClock *ptpClock)
|
|
{
|
|
+ TimeInternal now;
|
|
+
|
|
DBG("initData\n");
|
|
|
|
- if(rtOpts->slaveOnly)
|
|
- rtOpts->clockStratum = 255;
|
|
+ if(ptpClock->runTimeOpts.slaveOnly)
|
|
+ ptpClock->runTimeOpts.clockStratum = 255;
|
|
|
|
/* Port configuration data set */
|
|
ptpClock->last_sync_event_sequence_number = 0;
|
|
@@ -19,27 +21,30 @@ void initData(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
ptpClock->clock_communication_technology = ptpClock->port_communication_technology;
|
|
memcpy(ptpClock->clock_uuid_field, ptpClock->port_uuid_field, PTP_UUID_LENGTH);
|
|
ptpClock->clock_port_id_field = 0;
|
|
- ptpClock->clock_stratum = rtOpts->clockStratum;
|
|
- memcpy(ptpClock->clock_identifier, rtOpts->clockIdentifier, PTP_CODE_STRING_LENGTH);
|
|
- ptpClock->sync_interval = rtOpts->syncInterval;
|
|
+ ptpClock->clock_stratum = ptpClock->runTimeOpts.clockStratum;
|
|
+ memcpy(ptpClock->clock_identifier, ptpClock->runTimeOpts.clockIdentifier, PTP_CODE_STRING_LENGTH);
|
|
+ ptpClock->sync_interval = ptpClock->runTimeOpts.syncInterval;
|
|
|
|
- ptpClock->clock_variance = rtOpts->clockVariance; /* see spec 7.7 */
|
|
+ ptpClock->clock_variance = ptpClock->runTimeOpts.clockVariance; /* see spec 7.7 */
|
|
ptpClock->clock_followup_capable = CLOCK_FOLLOWUP;
|
|
- ptpClock->preferred = rtOpts->clockPreferred;
|
|
+ ptpClock->preferred = ptpClock->runTimeOpts.clockPreferred;
|
|
ptpClock->initializable = INITIALIZABLE;
|
|
ptpClock->external_timing = EXTERNAL_TIMING;
|
|
ptpClock->is_boundary_clock = BOUNDARY_CLOCK;
|
|
- memcpy(ptpClock->subdomain_name, rtOpts->subdomainName, PTP_SUBDOMAIN_NAME_LENGTH);
|
|
+ memcpy(ptpClock->subdomain_name, ptpClock->runTimeOpts.subdomainName, PTP_SUBDOMAIN_NAME_LENGTH);
|
|
ptpClock->number_ports = NUMBER_PORTS;
|
|
ptpClock->number_foreign_records = 0;
|
|
- ptpClock->max_foreign_records = rtOpts->max_foreign_records;
|
|
+ ptpClock->max_foreign_records = ptpClock->runTimeOpts.max_foreign_records;
|
|
|
|
/* Global time properties data set */
|
|
- ptpClock->current_utc_offset = rtOpts->currentUtcOffset;
|
|
- ptpClock->epoch_number = rtOpts->epochNumber;
|
|
+ ptpClock->current_utc_offset = ptpClock->runTimeOpts.currentUtcOffset;
|
|
+ ptpClock->epoch_number = ptpClock->runTimeOpts.epochNumber;
|
|
|
|
/* other stuff */
|
|
- ptpClock->random_seed = ptpClock->port_uuid_field[PTP_UUID_LENGTH-1];
|
|
+ timerNow(&now);
|
|
+ ptpClock->random_seed = ptpClock->port_uuid_field[PTP_UUID_LENGTH-1] ^
|
|
+ now.seconds ^
|
|
+ now.nanoseconds;
|
|
}
|
|
|
|
/* see spec table 18 */
|
|
@@ -279,9 +284,9 @@ B:
|
|
return -1; /* B1 */
|
|
}
|
|
|
|
-UInteger8 bmcStateDecision(MsgHeader *header, MsgSync *sync, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+UInteger8 bmcStateDecision(MsgHeader *header, MsgSync *sync, PtpClock *ptpClock)
|
|
{
|
|
- if(rtOpts->slaveOnly)
|
|
+ if(ptpClock->runTimeOpts.slaveOnly)
|
|
{
|
|
s1(header, sync, ptpClock);
|
|
return PTP_SLAVE;
|
|
@@ -312,7 +317,7 @@ UInteger8 bmcStateDecision(MsgHeader *header, MsgSync *sync, RunTimeOpts *rtOpts
|
|
}
|
|
}
|
|
|
|
-UInteger8 bmc(ForeignMasterRecord *foreign, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+UInteger8 bmc(ForeignMasterRecord *foreign, PtpClock *ptpClock)
|
|
{
|
|
Integer16 i, best;
|
|
|
|
@@ -333,6 +338,6 @@ UInteger8 bmc(ForeignMasterRecord *foreign, RunTimeOpts *rtOpts, PtpClock *ptpCl
|
|
DBGV("bmc: best record %d\n", best);
|
|
ptpClock->foreign_record_best = best;
|
|
|
|
- return bmcStateDecision(&foreign[best].header, &foreign[best].sync, rtOpts, ptpClock);
|
|
+ return bmcStateDecision(&foreign[best].header, &foreign[best].sync, ptpClock);
|
|
}
|
|
|
|
diff --git a/src/constants.h b/src/constants.h
|
|
index 0f1972a..43837b3 100644
|
|
--- a/src/constants.h
|
|
+++ b/src/constants.h
|
|
@@ -14,6 +14,7 @@
|
|
#define DEFAULT_INBOUND_LATENCY 0 /* in nsec */
|
|
#define DEFAULT_OUTBOUND_LATENCY 0 /* in nsec */
|
|
#define DEFAULT_NO_RESET_CLOCK FALSE
|
|
+#define DEFAULT_NO_ADJUST_CLOCK FALSE
|
|
#define DEFAULT_AP 10
|
|
#define DEFAULT_AI 1000
|
|
#define DEFAULT_DELAY_S 6
|
|
diff --git a/src/datatypes.h b/src/datatypes.h
|
|
index 4196f5e..20d5309 100644
|
|
--- a/src/datatypes.h
|
|
+++ b/src/datatypes.h
|
|
@@ -205,8 +205,84 @@ typedef struct
|
|
MsgSync sync;
|
|
} ForeignMasterRecord;
|
|
|
|
+typedef enum {
|
|
+ TIME_SYSTEM, /**< use and control system time */
|
|
+ TIME_NIC, /**< use and control time inside network interface (via Intel
|
|
+ igb ioctl()) */
|
|
+ /**
|
|
+ * a combination of PTP between NICs (as in TIME_NIC via Intel igb
|
|
+ * ioctl()) plus a local synchronization between NIC and system
|
|
+ * time:
|
|
+ *
|
|
+ * - NIC time is controlled via PTP packets, main time seen
|
|
+ * by PTPd is the NIC time
|
|
+ * - system_to_nic and nic_to_system delays are provided by
|
|
+ * device driver on request
|
|
+ * - time.c adapts NIC time to system time on master and system time
|
|
+ * to NIC time on slaves by feeding these offsets into another
|
|
+ * instance of the clock servo (as in TIME_SYSTEM)
|
|
+ *
|
|
+ * The command line options only apply to one clock sync and
|
|
+ * the defaults are used for the other:
|
|
+ * - NIC time: default values for clock control (adjust and reset)
|
|
+ * and servo (coefficients), configurable PTP
|
|
+ * - system time: configurable clock control and servo, PTP options do
|
|
+ * not apply
|
|
+ */
|
|
+ TIME_BOTH,
|
|
+ /**
|
|
+ * time used and controlled by PTP is the system time, but hardware
|
|
+ * assistance in the NIC is used to time stamp packages (via Intel
|
|
+ * igb ioctl())
|
|
+ */
|
|
+ TIME_SYSTEM_ASSISTED,
|
|
+ /**
|
|
+ * Time used and controlled by PTP is the system time with hardware
|
|
+ * packet time stamping via standard Linux net_tstamp.h API.
|
|
+ */
|
|
+ TIME_SYSTEM_LINUX_HW,
|
|
+ /**
|
|
+ * Time used and controlled by PTP is the system time with software
|
|
+ * packet time stamping via standard Linux net_tstamp.h API.
|
|
+ */
|
|
+ TIME_SYSTEM_LINUX_SW,
|
|
+
|
|
+ TIME_MAX
|
|
+} Time;
|
|
+
|
|
+/* program options set at run-time */
|
|
+typedef struct {
|
|
+ Integer8 syncInterval;
|
|
+ Octet subdomainName[PTP_SUBDOMAIN_NAME_LENGTH];
|
|
+ Octet clockIdentifier[PTP_CODE_STRING_LENGTH];
|
|
+ UInteger32 clockVariance;
|
|
+ UInteger8 clockStratum;
|
|
+ Boolean clockPreferred;
|
|
+ Integer16 currentUtcOffset;
|
|
+ UInteger16 epochNumber;
|
|
+ Octet ifaceName[IFACE_NAME_LENGTH];
|
|
+ Boolean noResetClock;
|
|
+ Boolean noAdjust;
|
|
+ Boolean displayStats;
|
|
+ Boolean csvStats;
|
|
+ Octet unicastAddress[NET_ADDRESS_LENGTH];
|
|
+ Integer16 ap, ai;
|
|
+ Integer16 s;
|
|
+ TimeInternal inboundLatency, outboundLatency;
|
|
+ Integer16 max_foreign_records;
|
|
+ Boolean slaveOnly;
|
|
+ Boolean probe;
|
|
+ UInteger8 probe_management_key;
|
|
+ UInteger16 probe_record_key;
|
|
+ Boolean halfEpoch;
|
|
+ Time time;
|
|
+} RunTimeOpts;
|
|
+
|
|
/* main program data structure */
|
|
typedef struct {
|
|
+ /* settings associate with this instance of PtpClock */
|
|
+ RunTimeOpts runTimeOpts;
|
|
+
|
|
/* Default data set */
|
|
UInteger8 clock_communication_technology;
|
|
Octet clock_uuid_field[PTP_UUID_LENGTH];
|
|
@@ -240,6 +316,7 @@ typedef struct {
|
|
Boolean parent_stats;
|
|
Integer16 observed_variance;
|
|
Integer32 observed_drift;
|
|
+ long adj;
|
|
Boolean utc_reasonable;
|
|
UInteger8 grandmaster_communication_technology;
|
|
Octet grandmaster_uuid_field[PTP_UUID_LENGTH];
|
|
@@ -303,7 +380,32 @@ typedef struct {
|
|
|
|
UInteger16 Q;
|
|
UInteger16 R;
|
|
-
|
|
+
|
|
+ /**
|
|
+ * TRUE when the clock is used to synchronize NIC and system time and
|
|
+ * the process is the PTP_MASTER. The offset and adjustment calculation
|
|
+ * is always "master (= NIC) to slave (= system time)" and PTP_SLAVEs
|
|
+ * update their system time, but the master needs to invert the
|
|
+ * clock adjustment and control NIC time instead. This way
|
|
+ * the master's system time is propagated to slaves.
|
|
+ */
|
|
+ Boolean nic_instead_of_system;
|
|
+
|
|
+ /**
|
|
+ * TRUE if outgoing packets are not to be time-stamped in advance.
|
|
+ * Instead the outgoing time stamp is generated as it is transmitted
|
|
+ * and must be sent in a follow-up message.
|
|
+ */
|
|
+ Boolean delayedTiming;
|
|
+
|
|
+ /**
|
|
+ * a prefix to be inserted before messages about the clock:
|
|
+ * may be empty, but not NULL
|
|
+ *
|
|
+ * used to distinguish multiple active clock servos per process
|
|
+ */
|
|
+ const char *name;
|
|
+
|
|
Boolean sentDelayReq;
|
|
UInteger16 sentDelayReqSequenceId;
|
|
Boolean waitingForFollow;
|
|
@@ -316,35 +418,7 @@ typedef struct {
|
|
IntervalTimer itimer[TIMER_ARRAY_SIZE];
|
|
|
|
NetPath netPath;
|
|
-
|
|
-} PtpClock;
|
|
|
|
-/* program options set at run-time */
|
|
-typedef struct {
|
|
- Integer8 syncInterval;
|
|
- Octet subdomainName[PTP_SUBDOMAIN_NAME_LENGTH];
|
|
- Octet clockIdentifier[PTP_CODE_STRING_LENGTH];
|
|
- UInteger32 clockVariance;
|
|
- UInteger8 clockStratum;
|
|
- Boolean clockPreferred;
|
|
- Integer16 currentUtcOffset;
|
|
- UInteger16 epochNumber;
|
|
- Octet ifaceName[IFACE_NAME_LENGTH];
|
|
- Boolean noResetClock;
|
|
- Boolean noAdjust;
|
|
- Boolean displayStats;
|
|
- Boolean csvStats;
|
|
- Octet unicastAddress[NET_ADDRESS_LENGTH];
|
|
- Integer16 ap, ai;
|
|
- Integer16 s;
|
|
- TimeInternal inboundLatency, outboundLatency;
|
|
- Integer16 max_foreign_records;
|
|
- Boolean slaveOnly;
|
|
- Boolean probe;
|
|
- UInteger8 probe_management_key;
|
|
- UInteger16 probe_record_key;
|
|
- Boolean halfEpoch;
|
|
-
|
|
-} RunTimeOpts;
|
|
+} PtpClock;
|
|
|
|
#endif
|
|
diff --git a/src/dep/constants_dep.h b/src/dep/constants_dep.h
|
|
index 78900ec..33ce022 100644
|
|
--- a/src/dep/constants_dep.h
|
|
+++ b/src/dep/constants_dep.h
|
|
@@ -5,6 +5,8 @@
|
|
|
|
/* platform dependent */
|
|
|
|
+#include <limits.h>
|
|
+
|
|
#if !defined(linux) && !defined(__NetBSD__) && !defined(__FreeBSD__)
|
|
#error Not ported to this architecture, please update.
|
|
#endif
|
|
@@ -56,8 +58,86 @@
|
|
# endif
|
|
#endif
|
|
|
|
-
|
|
-#define ADJ_FREQ_MAX 512000
|
|
+/**
|
|
+ * This value used to be used as limit for adjtimex() and the clock
|
|
+ * servo. The limits of adjtimex() are now determined at runtime in
|
|
+ * dep/time.c and are larger than before because also the us/tick
|
|
+ * value is adjusted, but the servo still uses this limit as sanity
|
|
+ * check. Now that the underlying system is no longer the limiting
|
|
+ * factor, perhaps the value should be configurable?
|
|
+ *
|
|
+ * The value was 5120000 (based on the maximum adjustment possible by
|
|
+ * adjusting just the frequency in adjtimex()), but that turned out to
|
|
+ * be too small on a system under load: as soon as the observed drift
|
|
+ * got larger than 5120000, it was clamped and the frequency
|
|
+ * adjustment remained too small to drive the offset back to
|
|
+ * zero. Here's a log of that situation:
|
|
+ *
|
|
+00:00:10 knlcst4 ptpd: state, one way delay, offset from master, drift, variance, clock adjustment (ppb)
|
|
+00:00:10 knlcst4 ptpd: init
|
|
+00:00:10 knlcst4 ptpd: lstn
|
|
+00:00:30 knlcst4 ptpd: mst
|
|
+00:00:31 knlcst4 ptpd: slv, 0.000000000, 0.000000000, 0, 0, 0
|
|
+00:00:33 knlcst4 ptpd: slv, 0.000000000, 0.000104000, -104, 0, 10504
|
|
+00:00:35 knlcst4 ptpd: slv, 0.000000000, 0.001509000, -1613, 0, 152513
|
|
+00:00:37 knlcst4 ptpd: slv, 0.000000000, 0.001842500, 229, 0, -184479
|
|
+00:00:39 knlcst4 ptpd: slv, 0.000000000, 0.001268000, 1497, 0, -128297
|
|
+...
|
|
+00:30:17 knlcst4 ptpd: slv, 0.003492994, 0.000289006, -175627, 0, 146727
|
|
+00:30:19 knlcst4 ptpd: slv, 0.003492994, 0.004753994, -180380, 0, 655779
|
|
+00:30:21 knlcst4 ptpd: slv, 0.003487771, 0.000671117, -179709, 0, 112598
|
|
+00:30:23 knlcst4 ptpd: slv, 0.003487771, 0.002132729, -177577, 0, -35695
|
|
+00:30:25 knlcst4 ptpd: slv, 0.003487771, 0.004137270, -181714, 0, 595441
|
|
+00:30:27 knlcst4 ptpd: slv, 0.003487771, 0.006283270, -187997, 0, 816324
|
|
+00:30:29 knlcst4 ptpd: slv, 0.003487771, 0.004510770, -192507, 0, 643584
|
|
+[offset continuesly grows from here on]
|
|
+...
|
|
+00:35:03 knlcst4 ptpd: slv, 0.003435910, 0.061051910, -4602318, 0, 10707509
|
|
+00:35:05 knlcst4 ptpd: slv, 0.003435910, 0.061784910, -4664102, 0, 10842593
|
|
+00:35:07 knlcst4 ptpd: slv, 0.003435910, 0.060685910, -4724787, 0, 10793378
|
|
+00:35:09 knlcst4 ptpd: slv, 0.003435910, 0.057270910, -4782057, 0, 10509148
|
|
+00:35:11 knlcst4 ptpd: slv, 0.003435910, 0.055342410, -4837399, 0, 10371640
|
|
+00:35:13 knlcst4 ptpd: slv, 0.003435910, 0.059554910, -4896953, 0, 10852444
|
|
+00:35:15 knlcst4 ptpd: slv, 0.003435910, 0.061615910, -4958568, 0, 11120159
|
|
+00:35:17 knlcst4 ptpd: slv, 0.003435910, 0.062028410, -5020596, 0, 11223437
|
|
+00:35:19 knlcst4 ptpd: slv, 0.003435910, 0.063420910, -5084016, 0, 11426107
|
|
+[offset gets clamped at -5120000]
|
|
+00:35:21 knlcst4 ptpd: slv, 0.003435910, 0.060922910, -5120000, 0, 11212291
|
|
+00:35:23 knlcst4 ptpd: slv, 0.003435910, 0.061610910, -5120000, 0, 11281091
|
|
+00:35:25 knlcst4 ptpd: slv, 0.003435910, 0.061527910, -5120000, 0, 11272791
|
|
+00:35:27 knlcst4 ptpd: slv, 0.003435910, 0.063027910, -5120000, 0, 11422791
|
|
+...
|
|
+[maximum value for adjustment reached]
|
|
+00:53:31 knlcst4 ptpd: slv, 0.003368888, 0.285260388, -5120000, 0, 33554432
|
|
+00:53:33 knlcst4 ptpd: slv, 0.003368888, 0.284009388, -5120000, 0, 33520938
|
|
+00:53:35 knlcst4 ptpd: slv, 0.003368888, 0.282019888, -5120000, 0, 33321988
|
|
+00:53:37 knlcst4 ptpd: slv, 0.003368888, 0.284614888, -5120000, 0, 33554432
|
|
+00:53:39 knlcst4 ptpd: slv, 0.003368888, 0.287445888, -5120000, 0, 33554432
|
|
+00:53:41 knlcst4 ptpd: slv, 0.003368888, 0.283629388, -5120000, 0, 33482938
|
|
+00:53:43 knlcst4 ptpd: slv, 0.003368888, 0.283933388, -5120000, 0, 33513338
|
|
+00:53:45 knlcst4 ptpd: slv, 0.003368888, 0.289208888, -5120000, 0, 33554432
|
|
+00:53:47 knlcst4 ptpd: slv, 0.003368888, 0.289672388, -5120000, 0, 33554432
|
|
+00:53:49 knlcst4 ptpd: slv, 0.003368888, 0.284487388, -5120000, 0, 33554432
|
|
+00:53:51 knlcst4 ptpd: slv, 0.003368888, 0.277956888, -5120000, 0, 32915688
|
|
+00:53:53 knlcst4 ptpd: slv, 0.003368888, 0.283933388, -5120000, 0, 33513338
|
|
+00:53:55 knlcst4 ptpd: slv, 0.003368888, 0.287284388, -5120000, 0, 33554432
|
|
+00:53:57 knlcst4 ptpd: slv, 0.003368888, 0.285872388, -5120000, 0, 33554432
|
|
+00:53:59 knlcst4 ptpd: slv, 0.003368888, 0.290243888, -5120000, 0, 33554432
|
|
+00:54:01 knlcst4 ptpd: slv, 0.003368888, 0.292336388, -5120000, 0, 33554432
|
|
+00:54:03 knlcst4 ptpd: slv, 0.003368888, 0.292805388, -5120000, 0, 33554432
|
|
+00:54:05 knlcst4 ptpd: slv, 0.003379526, 0.291303707, -5120000, 0, 33554432
|
|
+...
|
|
+01:51:44 knlcst4 ptpd: slv, 0.003304196, 0.995760196, -5120000, 0, 33554432
|
|
+01:51:46 knlcst4 ptpd: slv, 0.003304196, 0.993363696, -5120000, 0, 33554432
|
|
+01:51:48 knlcst4 ptpd: resetting system clock to 1200569796s 698871196e-9
|
|
+01:51:49 knlcst4 ptpd: slv, 0.003304196, -1.000127196, 0, 0, 0
|
|
+01:51:51 knlcst4 ptpd: slv, 0.003304196, 0.001648402, 1648, 0, -166488
|
|
+01:51:53 knlcst4 ptpd: slv, 0.003304196, 0.004037804, 5685, 0, -409465
|
|
+01:51:55 knlcst4 ptpd: slv, 0.003304196, 0.002923304, 8608, 0, -300938
|
|
+01:51:57 knlcst4 ptpd: slv, 0.003304196, 0.000091696, 8517, 0, 652
|
|
+[cycle repeats]
|
|
+ */
|
|
+#define ADJ_FREQ_MAX 512000000
|
|
|
|
/* UDP/IPv4 dependent */
|
|
|
|
diff --git a/src/dep/datatypes_dep.h b/src/dep/datatypes_dep.h
|
|
index b42f2f8..6b7e127 100644
|
|
--- a/src/dep/datatypes_dep.h
|
|
+++ b/src/dep/datatypes_dep.h
|
|
@@ -23,6 +23,11 @@ typedef struct {
|
|
|
|
typedef struct {
|
|
Integer32 eventSock, generalSock, multicastAddr, unicastAddr;
|
|
+#if defined(linux)
|
|
+ /** for further ioctl() calls on eventSock */
|
|
+ struct ifreq eventSockIFR;
|
|
+#endif
|
|
+ UInteger16 lastNetSendEventLength;
|
|
} NetPath;
|
|
|
|
#endif
|
|
diff --git a/src/dep/e1000_ioctl.h b/src/dep/e1000_ioctl.h
|
|
new file mode 100644
|
|
index 0000000..0e39254
|
|
--- /dev/null
|
|
+++ b/src/dep/e1000_ioctl.h
|
|
@@ -0,0 +1,190 @@
|
|
+/*******************************************************************************
|
|
+ *
|
|
+ * Intel(R) Gigabit Ethernet Linux driver
|
|
+ *
|
|
+ * Copyright (c) 2006-2008, Intel Corporation
|
|
+ * All rights reserved.
|
|
+ *
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
+ * * Redistributions of source code must retain the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer.
|
|
+ * * Redistributions in binary form must reproduce the above copyright
|
|
+ * notice, this list of conditions and the following disclaimer in the
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
+ * * Neither the name of the Intel Corporation nor the
|
|
+ * names of its contributors may be used to endorse or promote products
|
|
+ * derived from this software without specific prior written permission.
|
|
+ *
|
|
+ * THIS SOFTWARE IS PROVIDED BY Intel Corporation ''AS IS'' AND ANY
|
|
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
+ * DISCLAIMED. IN NO EVENT SHALL Intel Corporation BE LIABLE FOR ANY
|
|
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
+ *
|
|
+ *******************************************************************************/
|
|
+
|
|
+/** @todo add size field to structs to allow for extensions */
|
|
+
|
|
+#ifndef __E1000_IOCTL_H__
|
|
+#define __E1000_IOCTL_H__
|
|
+
|
|
+/**
|
|
+ * a time stamp
|
|
+ *
|
|
+ * The value is always positive, negative time stamps must be
|
|
+ * represented with an additional +1/-1 sign factor.
|
|
+ *
|
|
+ * @todo use a better definition for 64 bit unsigned which works
|
|
+ * in the driver and user space
|
|
+ */
|
|
+struct E1000_TS {
|
|
+ unsigned long long seconds;
|
|
+ unsigned int nanoseconds;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Initialize NIC for PTP time stamping.
|
|
+ * After this call E1000_TSYNC_SYSTIME_IOCTL will return
|
|
+ * time stamps which are somewhat related to the current system time,
|
|
+ * but will drift apart again.
|
|
+ *
|
|
+ * @return 0 for success
|
|
+ */
|
|
+#define E1000_TSYNC_INIT_IOCTL 0x89f0
|
|
+
|
|
+/**
|
|
+ * Optionally (if ARGU::negative_offset != 0) update NIC time by given
|
|
+ * offset and return current time. Current time is inaccurate because
|
|
+ * accessing the NIC incurrs a certain non-deterministic delay.
|
|
+ *
|
|
+ * @todo determine how large the delay is in reality
|
|
+ */
|
|
+#define E1000_TSYNC_SYSTIME_IOCTL 0x89f1
|
|
+
|
|
+/** parameters and results of E1000_TSYNC_SYSTIME_IOCTL */
|
|
+struct E1000_TSYNC_SYSTIME_ARGU {
|
|
+ /** input: offset to be applied to time; output: current time */
|
|
+ struct E1000_TS time;
|
|
+
|
|
+ /**
|
|
+ * <0: substract input offset
|
|
+ * >0: add input offset
|
|
+ * =0: only read current time
|
|
+ */
|
|
+ int negative_offset;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Speed up (positive value) or slow down the clock by a
|
|
+ * certain amount specified as parts per billion (1e-9).
|
|
+ * For example, a parameter of 1 means "add 1 microsecond
|
|
+ * every second".
|
|
+ */
|
|
+#define E1000_TSYNC_ADJTIME_IOCTL 0x89f2
|
|
+
|
|
+struct E1000_TSYNC_ADJTIME_ARGU {
|
|
+ /**
|
|
+ * input: adjustment to be applied to time in ppb (1e-9);
|
|
+ * output: current adjustment
|
|
+ */
|
|
+ long long adj;
|
|
+
|
|
+ /**
|
|
+ * only set adjustment if != 0
|
|
+ */
|
|
+ int set_adj;
|
|
+};
|
|
+
|
|
+/** @todo: consolidate enable/disable ioctl() calls into one? */
|
|
+
|
|
+/** enable time stamping of outgoing PTP packets, returns 0 if successful */
|
|
+#define E1000_TSYNC_ENABLETX_IOCTL 0x89f4
|
|
+/** disable time stamping of outgoing PTP packets, returns 0 if successful */
|
|
+#define E1000_TSYNC_DISABLETX_IOCTL 0x89f5
|
|
+
|
|
+/**
|
|
+ * enable time stamping of incoming PTP packets, returns 0 if successful
|
|
+ *
|
|
+ * *(int *)&ifr_data determines mode
|
|
+ */
|
|
+#define E1000_TSYNC_ENABLERX_IOCTL 0x89f8
|
|
+
|
|
+/** @todo: add RX timestamp mode = 5 */
|
|
+enum {
|
|
+ E1000_L2_V2_SYNC, /**< time stamp incoming layer 2 PTP V2 Sync packets */
|
|
+ E1000_L2_V2_DELAY, /**< time stamp incoming layer 2 PTP V2 Delay_Req packets */
|
|
+ E1000_UDP_V1_SYNC, /**< time stamp incoming UDP PTP V1 Sync packets */
|
|
+ E1000_UDP_V1_DELAY, /**< time stamp incoming UDP PTP V1 Delay_Req packets */
|
|
+ E1000_TSYNC_MAX
|
|
+};
|
|
+
|
|
+
|
|
+/** disable time stamping of incoming PTP packets, returns 0 if successful */
|
|
+#define E1000_TSYNC_DISABLERX_IOCTL 0x89f9
|
|
+
|
|
+/** get information about send/receive time stamps */
|
|
+#define E1000_TSYNC_READTS_IOCTL 0x89fc
|
|
+
|
|
+struct E1000_TSYNC_READTS_ARGU {
|
|
+ /**
|
|
+ * in: not only return NIC time stamps, but also the
|
|
+ * corresponding system time (may cause additional overhead)
|
|
+ */
|
|
+ int withSystemTime;
|
|
+
|
|
+ /** out: receive information is only valid if rx_valid != 0 */
|
|
+ int rx_valid;
|
|
+ /** out: receive NIC time stamp */
|
|
+ struct E1000_TS rx;
|
|
+ /** out (if withSystemTime was true): the corresponding receive system time */
|
|
+ struct E1000_TS rx_sys;
|
|
+ /** out: the PTP sequence ID of the time stamped packet */
|
|
+ uint16_t sourceSequenceId;
|
|
+ /** out: the PTP source ID of the time stamped packet */
|
|
+ unsigned char sourceIdentity[6];
|
|
+
|
|
+ /** out: send information is only valid if tx_valid != 0 */
|
|
+ int tx_valid;
|
|
+
|
|
+ /** out: send NIC time stamp */
|
|
+ struct E1000_TS tx;
|
|
+ /** out (if withSystemTime was true): the corresponding send system time */
|
|
+ struct E1000_TS tx_sys;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * Correlates system time and NIC time each time it is called. The
|
|
+ * - offsetFromSystem = NIC time - system time
|
|
+ * is calculated as in PTP/IEEE1555:
|
|
+ * - oneWayDelay = (NICToSystem + systemToNIC)/2
|
|
+ * - offsetFromSystem = systemToNIC - oneWayDelay
|
|
+ * = (systemToNIC - NICToSystem)/2
|
|
+ *
|
|
+ * A driver which does not measure both delays can simply set one
|
|
+ * delay to zero and return twice the offset in the other field.
|
|
+ *
|
|
+ * A positive offset means that the NIC time is higher than the system
|
|
+ * time, i.e. either the system clock must speed up to catch up with
|
|
+ * the NIC or the NIC must slow down.
|
|
+ */
|
|
+#define E1000_TSYNC_COMPARETS_IOCTL 0x89fd
|
|
+
|
|
+struct E1000_TSYNC_COMPARETS_ARGU {
|
|
+ /** out: one-way delay for sending from NIC to system */
|
|
+ struct E1000_TS NICToSystem;
|
|
+ /** +1 for positiv delay or -1 for negative one */
|
|
+ int NICToSystemSign;
|
|
+
|
|
+ /** one-way delay for sending from system to NIC */
|
|
+ struct E1000_TS systemToNIC;
|
|
+ /** +1 for positiv delay or -1 for negative one */
|
|
+ int systemToNICSign;
|
|
+};
|
|
+
|
|
+#endif /* __E1000_IOCTL_H__ */
|
|
diff --git a/src/dep/msg.c b/src/dep/msg.c
|
|
index 8b43150..22b276e 100644
|
|
--- a/src/dep/msg.c
|
|
+++ b/src/dep/msg.c
|
|
@@ -155,7 +155,7 @@ void msgUnpackManagement(void *buf, MsgManagement *manage)
|
|
}
|
|
|
|
UInteger8 msgUnloadManagement(void *buf, MsgManagement *manage,
|
|
- PtpClock *ptpClock, RunTimeOpts *rtOpts)
|
|
+ PtpClock *ptpClock)
|
|
{
|
|
TimeInternal internalTime;
|
|
TimeRepresentation externalTime;
|
|
@@ -203,29 +203,29 @@ UInteger8 msgUnloadManagement(void *buf, MsgManagement *manage,
|
|
break;
|
|
|
|
case PTP_MM_SET_SYNC_INTERVAL:
|
|
- rtOpts->syncInterval = *(Integer8*)(buf + 63);
|
|
+ ptpClock->runTimeOpts.syncInterval = *(Integer8*)(buf + 63);
|
|
break;
|
|
|
|
case PTP_MM_SET_SUBDOMAIN:
|
|
- memcpy(rtOpts->subdomainName, buf + 60, 16);
|
|
- DBG("set subdomain to %s\n", rtOpts->subdomainName);
|
|
+ memcpy(ptpClock->runTimeOpts.subdomainName, buf + 60, 16);
|
|
+ DBG("set subdomain to %s\n", ptpClock->runTimeOpts.subdomainName);
|
|
break;
|
|
|
|
case PTP_MM_SET_TIME:
|
|
externalTime.seconds = flip32(*(UInteger32*)(buf + 60));
|
|
externalTime.nanoseconds = flip32(*(Integer32*)(buf + 64));
|
|
toInternalTime(&internalTime, &externalTime, &ptpClock->halfEpoch);
|
|
- setTime(&internalTime);
|
|
+ setTime(&internalTime, ptpClock);
|
|
break;
|
|
|
|
case PTP_MM_UPDATE_DEFAULT_DATA_SET:
|
|
- if(!rtOpts->slaveOnly)
|
|
+ if(!ptpClock->runTimeOpts.slaveOnly)
|
|
ptpClock->clock_stratum = *(UInteger8*)(buf + 63);
|
|
memcpy(ptpClock->clock_identifier, buf + 64, 4);
|
|
ptpClock->clock_variance = flip16(*(Integer16*)(buf + 70));
|
|
ptpClock->preferred = *(UInteger8*)(buf + 75);
|
|
- rtOpts->syncInterval = *(UInteger8*)(buf + 79);
|
|
- memcpy(rtOpts->subdomainName, buf + 80, 16);
|
|
+ ptpClock->runTimeOpts.syncInterval = *(UInteger8*)(buf + 79);
|
|
+ memcpy(ptpClock->runTimeOpts.subdomainName, buf + 80, 16);
|
|
break;
|
|
|
|
case PTP_MM_UPDATE_GLOBAL_TIME_PROPERTIES:
|
|
@@ -372,7 +372,7 @@ void msgPackHeader(void *buf, PtpClock *ptpClock)
|
|
setFlag((buf + 34), PTP_BOUNDARY_CLOCK);
|
|
}
|
|
|
|
-void msgPackSync(void *buf, Boolean burst,
|
|
+void msgPackSync(void *buf, Boolean burst, Boolean ptpAssist,
|
|
TimeRepresentation *originTimestamp, PtpClock *ptpClock)
|
|
{
|
|
*(UInteger8*)(buf +20) = 1; /* messageType */
|
|
@@ -386,7 +386,14 @@ void msgPackSync(void *buf, Boolean burst,
|
|
setFlag((buf + 34), PARENT_STATS);
|
|
else
|
|
clearFlag((buf + 34), PARENT_STATS);
|
|
-
|
|
+ /**
|
|
+ * @todo: before adding this conditional ptpAssist the PTP_ASSIST
|
|
+ * was never set although a Follow_Up was sent - a bug in the original PTPd?
|
|
+ */
|
|
+ if(ptpAssist)
|
|
+ setFlag((buf + 34), PTP_ASSIST);
|
|
+ else
|
|
+ clearFlag((buf + 34), PTP_ASSIST);
|
|
|
|
*(Integer32*)(buf + 40) = flip32(originTimestamp->seconds);
|
|
*(Integer32*)(buf + 44) = flip32(originTimestamp->nanoseconds);
|
|
@@ -411,7 +418,7 @@ void msgPackSync(void *buf, Boolean burst,
|
|
*(Integer32*)(buf + 120) = shift8(ptpClock->utc_reasonable, 3);
|
|
}
|
|
|
|
-void msgPackDelayReq(void *buf, Boolean burst,
|
|
+void msgPackDelayReq(void *buf, Boolean burst, Boolean ptpAssist,
|
|
TimeRepresentation *originTimestamp, PtpClock *ptpClock)
|
|
{
|
|
*(UInteger8*)(buf + 20) = 1; /* messageType */
|
|
@@ -425,6 +432,10 @@ void msgPackDelayReq(void *buf, Boolean burst,
|
|
setFlag((buf + 34), PARENT_STATS);
|
|
else
|
|
clearFlag((buf + 34), PARENT_STATS);
|
|
+ if(ptpAssist)
|
|
+ setFlag((buf + 34), PTP_ASSIST);
|
|
+ else
|
|
+ clearFlag((buf + 34), PTP_ASSIST);
|
|
|
|
*(Integer32*)(buf + 40) = flip32(originTimestamp->seconds);
|
|
*(Integer32*)(buf + 44) = flip32(originTimestamp->nanoseconds);
|
|
@@ -457,6 +468,7 @@ void msgPackFollowUp(void *buf, UInteger16 associatedSequenceId,
|
|
*(UInteger8*)(buf + 32) = PTP_FOLLOWUP_MESSAGE; /* control */
|
|
clearFlag((buf + 34), PTP_SYNC_BURST);
|
|
clearFlag((buf + 34), PARENT_STATS);
|
|
+ clearFlag((buf + 34), PTP_ASSIST);
|
|
|
|
*(Integer32*)(buf + 40) = shift16(flip16(associatedSequenceId), 1);
|
|
*(Integer32*)(buf + 44) = flip32(preciseOriginTimestamp->seconds);
|
|
@@ -471,6 +483,7 @@ void msgPackDelayResp(void *buf, MsgHeader *header,
|
|
*(UInteger8*)(buf + 32) = PTP_DELAY_RESP_MESSAGE; /* control */
|
|
clearFlag((buf + 34), PTP_SYNC_BURST);
|
|
clearFlag((buf + 34), PARENT_STATS);
|
|
+ clearFlag((buf + 34), PTP_ASSIST);
|
|
|
|
*(Integer32*)(buf + 40) = flip32(delayReceiptTimestamp->seconds);
|
|
*(Integer32*)(buf + 44) = flip32(delayReceiptTimestamp->nanoseconds);
|
|
@@ -486,6 +499,7 @@ UInteger16 msgPackManagement(void *buf, MsgManagement *manage, PtpClock *ptpCloc
|
|
*(UInteger8*)(buf + 32) = PTP_MANAGEMENT_MESSAGE; /* control */
|
|
clearFlag((buf + 34), PTP_SYNC_BURST);
|
|
clearFlag((buf + 34), PARENT_STATS);
|
|
+ clearFlag((buf + 34), PTP_ASSIST);
|
|
*(Integer32*)(buf + 40) = shift8(manage->targetCommunicationTechnology, 1);
|
|
memcpy(buf + 42, manage->targetUuid, 6);
|
|
*(Integer32*)(buf + 48) = shift16(flip16(manage->targetPortId), 0) | shift16(flip16(MM_STARTING_BOUNDARY_HOPS), 1);
|
|
@@ -516,6 +530,7 @@ UInteger16 msgPackManagementResponse(void *buf, MsgHeader *header, MsgManagement
|
|
*(UInteger8*)(buf + 32) = PTP_MANAGEMENT_MESSAGE; /* control */
|
|
clearFlag((buf + 34), PTP_SYNC_BURST);
|
|
clearFlag((buf + 34), PARENT_STATS);
|
|
+ clearFlag((buf + 34), PTP_ASSIST);
|
|
*(Integer32*)(buf + 40) = shift8(header->sourceCommunicationTechnology, 1);
|
|
memcpy(buf + 42, header->sourceUuid, 6);
|
|
*(Integer32*)(buf + 48) = shift16(flip16(header->sourcePortId), 0) | shift16(flip16(MM_STARTING_BOUNDARY_HOPS), 1);
|
|
@@ -619,7 +634,7 @@ UInteger16 msgPackManagementResponse(void *buf, MsgHeader *header, MsgManagement
|
|
*(UInteger8*)(buf + 55) = PTP_MM_GLOBAL_TIME_DATA_SET;
|
|
*(Integer32*)(buf + 56) = shift16(flip16(24), 1);
|
|
|
|
- getTime(&internalTime);
|
|
+ getTime(&internalTime, ptpClock);
|
|
fromInternalTime(&internalTime, &externalTime, ptpClock->halfEpoch);
|
|
*(Integer32*)(buf + 60) = flip32(externalTime.seconds);
|
|
*(Integer32*)(buf + 64) = flip32(externalTime.nanoseconds);
|
|
diff --git a/src/dep/net.c b/src/dep/net.c
|
|
index 2408d55..d9db35e 100644
|
|
--- a/src/dep/net.c
|
|
+++ b/src/dep/net.c
|
|
@@ -61,7 +61,7 @@ UInteger8 lookupCommunicationTechnology(UInteger8 communicationTechnology)
|
|
}
|
|
|
|
UInteger32 findIface(Octet *ifaceName, UInteger8 *communicationTechnology,
|
|
- Octet *uuid, NetPath *netPath)
|
|
+ Octet *uuid, PtpClock *ptpClock)
|
|
{
|
|
#if defined(linux)
|
|
|
|
@@ -83,7 +83,7 @@ UInteger32 findIface(Octet *ifaceName, UInteger8 *communicationTechnology,
|
|
i = 0;
|
|
memcpy(device[i].ifr_name, ifaceName, IFACE_NAME_LENGTH);
|
|
|
|
- if(ioctl(netPath->eventSock, SIOCGIFHWADDR, &device[i]) < 0)
|
|
+ if(ioctl(ptpClock->netPath.eventSock, SIOCGIFHWADDR, &device[i]) < 0)
|
|
DBGV("failed to get hardware address\n");
|
|
else if((*communicationTechnology = lookupCommunicationTechnology(device[i].ifr_hwaddr.sa_family)) == PTP_DEFAULT)
|
|
DBGV("unsupported communication technology (%d)\n", *communicationTechnology);
|
|
@@ -94,7 +94,7 @@ UInteger32 findIface(Octet *ifaceName, UInteger8 *communicationTechnology,
|
|
{
|
|
/* no iface specified */
|
|
/* get list of network interfaces*/
|
|
- if(ioctl(netPath->eventSock, SIOCGIFCONF, &data) < 0)
|
|
+ if(ioctl(ptpClock->netPath.eventSock, SIOCGIFCONF, &data) < 0)
|
|
{
|
|
PERROR("failed query network interfaces");
|
|
return 0;
|
|
@@ -108,11 +108,11 @@ UInteger32 findIface(Octet *ifaceName, UInteger8 *communicationTechnology,
|
|
{
|
|
DBGV("%d %s %s\n",i,device[i].ifr_name,inet_ntoa(((struct sockaddr_in *)&device[i].ifr_addr)->sin_addr));
|
|
|
|
- if(ioctl(netPath->eventSock, SIOCGIFFLAGS, &device[i]) < 0)
|
|
+ if(ioctl(ptpClock->netPath.eventSock, SIOCGIFFLAGS, &device[i]) < 0)
|
|
DBGV("failed to get device flags\n");
|
|
else if((device[i].ifr_flags&flags) != flags)
|
|
DBGV("does not meet requirements (%08x, %08x)\n", device[i].ifr_flags, flags);
|
|
- else if(ioctl(netPath->eventSock, SIOCGIFHWADDR, &device[i]) < 0)
|
|
+ else if(ioctl(ptpClock->netPath.eventSock, SIOCGIFHWADDR, &device[i]) < 0)
|
|
DBGV("failed to get hardware address\n");
|
|
else if((*communicationTechnology = lookupCommunicationTechnology(device[i].ifr_hwaddr.sa_family)) == PTP_DEFAULT)
|
|
DBGV("unsupported communication technology (%d)\n", *communicationTechnology);
|
|
@@ -134,12 +134,13 @@ UInteger32 findIface(Octet *ifaceName, UInteger8 *communicationTechnology,
|
|
return 0;
|
|
}
|
|
|
|
- if(ioctl(netPath->eventSock, SIOCGIFADDR, &device[i]) < 0)
|
|
+ if(ioctl(ptpClock->netPath.eventSock, SIOCGIFADDR, &device[i]) < 0)
|
|
{
|
|
PERROR("failed to get ip address");
|
|
return 0;
|
|
}
|
|
-
|
|
+
|
|
+ ptpClock->netPath.eventSockIFR = device[i];
|
|
return ((struct sockaddr_in *)&device[i].ifr_addr)->sin_addr.s_addr;
|
|
|
|
#elif defined(BSD_INTERFACE_FUNCTIONS)
|
|
@@ -205,10 +206,10 @@ UInteger32 findIface(Octet *ifaceName, UInteger8 *communicationTechnology,
|
|
return FALSE;
|
|
}
|
|
|
|
- printf("==> %s %s %s\n", ifv4->ifa_name,
|
|
- inet_ntoa(((struct sockaddr_in *)ifv4->ifa_addr)->sin_addr),
|
|
- ether_ntoa((struct ether_addr *)LLADDR((struct sockaddr_dl *)ifh->ifa_addr))
|
|
- );
|
|
+ DBG("==> %s %s %s\n", ifv4->ifa_name,
|
|
+ inet_ntoa(((struct sockaddr_in *)ifv4->ifa_addr)->sin_addr),
|
|
+ ether_ntoa((struct ether_addr *)LLADDR((struct sockaddr_dl *)ifh->ifa_addr))
|
|
+ );
|
|
|
|
*communicationTechnology = PTP_ETHER;
|
|
memcpy(ifaceName, ifh->ifa_name, IFACE_NAME_LENGTH);
|
|
@@ -223,7 +224,7 @@ UInteger32 findIface(Octet *ifaceName, UInteger8 *communicationTechnology,
|
|
/* must specify 'subdomainName', optionally 'ifaceName', if not then pass ifaceName == "" */
|
|
/* returns other args */
|
|
/* on socket options, see the 'socket(7)' and 'ip' man pages */
|
|
-Boolean netInit(NetPath *netPath, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+Boolean netInit(PtpClock *ptpClock)
|
|
{
|
|
int temp, i;
|
|
struct in_addr interfaceAddr, netAddr;
|
|
@@ -231,25 +232,26 @@ Boolean netInit(NetPath *netPath, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
struct ip_mreq imr;
|
|
char addrStr[NET_ADDRESS_LENGTH];
|
|
char *s;
|
|
+ Boolean useSystemTimeStamps = ptpClock->runTimeOpts.time == TIME_SYSTEM;
|
|
|
|
DBG("netInit\n");
|
|
|
|
/* open sockets */
|
|
- if( (netPath->eventSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) ) < 0
|
|
- || (netPath->generalSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) ) < 0 )
|
|
+ if( (ptpClock->netPath.eventSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) ) < 0
|
|
+ || (ptpClock->netPath.generalSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) ) < 0 )
|
|
{
|
|
PERROR("failed to initalize sockets");
|
|
return FALSE;
|
|
}
|
|
|
|
/* find a network interface */
|
|
- if( !(interfaceAddr.s_addr = findIface(rtOpts->ifaceName, &ptpClock->port_communication_technology,
|
|
- ptpClock->port_uuid_field, netPath)) )
|
|
+ if( !(interfaceAddr.s_addr = findIface(ptpClock->runTimeOpts.ifaceName, &ptpClock->port_communication_technology,
|
|
+ ptpClock->port_uuid_field, ptpClock)) )
|
|
return FALSE;
|
|
|
|
temp = 1; /* allow address reuse */
|
|
- if( setsockopt(netPath->eventSock, SOL_SOCKET, SO_REUSEADDR, &temp, sizeof(int)) < 0
|
|
- || setsockopt(netPath->generalSock, SOL_SOCKET, SO_REUSEADDR, &temp, sizeof(int)) < 0 )
|
|
+ if( setsockopt(ptpClock->netPath.eventSock, SOL_SOCKET, SO_REUSEADDR, &temp, sizeof(int)) < 0
|
|
+ || setsockopt(ptpClock->netPath.generalSock, SOL_SOCKET, SO_REUSEADDR, &temp, sizeof(int)) < 0 )
|
|
{
|
|
DBG("failed to set socket reuse\n");
|
|
}
|
|
@@ -259,14 +261,14 @@ Boolean netInit(NetPath *netPath, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
addr.sin_port = htons(PTP_EVENT_PORT);
|
|
- if(bind(netPath->eventSock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0)
|
|
+ if(bind(ptpClock->netPath.eventSock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0)
|
|
{
|
|
PERROR("failed to bind event socket");
|
|
return FALSE;
|
|
}
|
|
|
|
addr.sin_port = htons(PTP_GENERAL_PORT);
|
|
- if(bind(netPath->generalSock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0)
|
|
+ if(bind(ptpClock->netPath.generalSock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0)
|
|
{
|
|
PERROR("failed to bind general socket");
|
|
return FALSE;
|
|
@@ -277,21 +279,21 @@ Boolean netInit(NetPath *netPath, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
*(Integer16*)ptpClock->general_port_address = PTP_GENERAL_PORT;
|
|
|
|
/* send a uni-cast address if specified (useful for testing) */
|
|
- if(rtOpts->unicastAddress[0])
|
|
+ if(ptpClock->runTimeOpts.unicastAddress[0])
|
|
{
|
|
- if(!inet_aton(rtOpts->unicastAddress, &netAddr))
|
|
+ if(!inet_aton(ptpClock->runTimeOpts.unicastAddress, &netAddr))
|
|
{
|
|
- ERROR("failed to encode uni-cast address: %s\n", rtOpts->unicastAddress);
|
|
+ ERROR("failed to encode uni-cast address: %s\n", ptpClock->runTimeOpts.unicastAddress);
|
|
return FALSE;
|
|
}
|
|
|
|
- netPath->unicastAddr = netAddr.s_addr;
|
|
+ ptpClock->netPath.unicastAddr = netAddr.s_addr;
|
|
}
|
|
else
|
|
- netPath->unicastAddr = 0;
|
|
+ ptpClock->netPath.unicastAddr = 0;
|
|
|
|
/* resolve PTP subdomain */
|
|
- if(!lookupSubdomainAddress(rtOpts->subdomainName, addrStr))
|
|
+ if(!lookupSubdomainAddress(ptpClock->runTimeOpts.subdomainName, addrStr))
|
|
return FALSE;
|
|
|
|
if(!inet_aton(addrStr, &netAddr))
|
|
@@ -300,7 +302,7 @@ Boolean netInit(NetPath *netPath, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
return FALSE;
|
|
}
|
|
|
|
- netPath->multicastAddr = netAddr.s_addr;
|
|
+ ptpClock->netPath.multicastAddr = netAddr.s_addr;
|
|
|
|
s = addrStr;
|
|
for(i = 0; i < SUBDOMAIN_ADDRESS_LENGTH; ++i)
|
|
@@ -316,16 +318,16 @@ Boolean netInit(NetPath *netPath, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
/* multicast send only on specified interface */
|
|
imr.imr_multiaddr.s_addr = netAddr.s_addr;
|
|
imr.imr_interface.s_addr = interfaceAddr.s_addr;
|
|
- if( setsockopt(netPath->eventSock, IPPROTO_IP, IP_MULTICAST_IF, &imr.imr_interface.s_addr, sizeof(struct in_addr)) < 0
|
|
- || setsockopt(netPath->generalSock, IPPROTO_IP, IP_MULTICAST_IF, &imr.imr_interface.s_addr, sizeof(struct in_addr)) < 0 )
|
|
+ if( setsockopt(ptpClock->netPath.eventSock, IPPROTO_IP, IP_MULTICAST_IF, &imr.imr_interface.s_addr, sizeof(struct in_addr)) < 0
|
|
+ || setsockopt(ptpClock->netPath.generalSock, IPPROTO_IP, IP_MULTICAST_IF, &imr.imr_interface.s_addr, sizeof(struct in_addr)) < 0 )
|
|
{
|
|
PERROR("failed to enable multi-cast on the interface");
|
|
return FALSE;
|
|
}
|
|
|
|
/* join multicast group (for receiving) on specified interface */
|
|
- if( setsockopt(netPath->eventSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(struct ip_mreq)) < 0
|
|
- || setsockopt(netPath->generalSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(struct ip_mreq)) < 0 )
|
|
+ if( setsockopt(ptpClock->netPath.eventSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(struct ip_mreq)) < 0
|
|
+ || setsockopt(ptpClock->netPath.generalSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(struct ip_mreq)) < 0 )
|
|
{
|
|
PERROR("failed to join the multi-cast group");
|
|
return FALSE;
|
|
@@ -333,61 +335,76 @@ Boolean netInit(NetPath *netPath, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
|
|
/* set socket time-to-live to 1 */
|
|
temp = 1;
|
|
- if( setsockopt(netPath->eventSock, IPPROTO_IP, IP_MULTICAST_TTL, &temp, sizeof(int)) < 0
|
|
- || setsockopt(netPath->generalSock, IPPROTO_IP, IP_MULTICAST_TTL, &temp, sizeof(int)) < 0 )
|
|
+ if( setsockopt(ptpClock->netPath.eventSock, IPPROTO_IP, IP_MULTICAST_TTL, &temp, sizeof(int)) < 0
|
|
+ || setsockopt(ptpClock->netPath.generalSock, IPPROTO_IP, IP_MULTICAST_TTL, &temp, sizeof(int)) < 0 )
|
|
{
|
|
PERROR("failed to set the multi-cast time-to-live");
|
|
return FALSE;
|
|
}
|
|
|
|
- /* enable loopback */
|
|
- temp = 1;
|
|
- if( setsockopt(netPath->eventSock, IPPROTO_IP, IP_MULTICAST_LOOP, &temp, sizeof(int)) < 0
|
|
- || setsockopt(netPath->generalSock, IPPROTO_IP, IP_MULTICAST_LOOP, &temp, sizeof(int)) < 0 )
|
|
+ /* set loopback: needed only for time stamping with the system clock */
|
|
+ temp = useSystemTimeStamps;
|
|
+ if( setsockopt(ptpClock->netPath.eventSock, IPPROTO_IP, IP_MULTICAST_LOOP, &temp, sizeof(int)) < 0
|
|
+ || setsockopt(ptpClock->netPath.generalSock, IPPROTO_IP, IP_MULTICAST_LOOP, &temp, sizeof(int)) < 0 )
|
|
{
|
|
PERROR("failed to enable multi-cast loopback");
|
|
return FALSE;
|
|
}
|
|
|
|
- /* make timestamps available through recvmsg() */
|
|
- temp = 1;
|
|
- if( setsockopt(netPath->eventSock, SOL_SOCKET, SO_TIMESTAMP, &temp, sizeof(int)) < 0
|
|
- || setsockopt(netPath->generalSock, SOL_SOCKET, SO_TIMESTAMP, &temp, sizeof(int)) < 0 )
|
|
+ /* make timestamps available through recvmsg() (only needed for time stamping with system clock) */
|
|
+ temp = useSystemTimeStamps;
|
|
+ if( setsockopt(ptpClock->netPath.eventSock, SOL_SOCKET, SO_TIMESTAMP, &temp, sizeof(int)) < 0
|
|
+ || setsockopt(ptpClock->netPath.generalSock, SOL_SOCKET, SO_TIMESTAMP, &temp, sizeof(int)) < 0 )
|
|
{
|
|
PERROR("failed to enable receive time stamps");
|
|
return FALSE;
|
|
}
|
|
|
|
-
|
|
return TRUE;
|
|
}
|
|
|
|
/* shut down the UDP stuff */
|
|
-Boolean netShutdown(NetPath *netPath)
|
|
+Boolean netShutdown(PtpClock *ptpClock)
|
|
{
|
|
struct ip_mreq imr;
|
|
|
|
- imr.imr_multiaddr.s_addr = netPath->multicastAddr;
|
|
+#ifdef HAVE_LINUX_NET_TSTAMP_H
|
|
+ if (ptpClock->runTimeOpts.time == TIME_SYSTEM_LINUX_HW &&
|
|
+ ptpClock->netPath.eventSock > 0) {
|
|
+ struct hwtstamp_config hwconfig;
|
|
+
|
|
+ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&hwconfig;
|
|
+ memset(&hwconfig, 0, sizeof(&hwconfig));
|
|
+
|
|
+ hwconfig.tx_type = HWTSTAMP_TX_OFF;
|
|
+ hwconfig.rx_filter = HWTSTAMP_FILTER_NONE;
|
|
+ if (ioctl(ptpClock->netPath.eventSock, SIOCSHWTSTAMP, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ PERROR("turning off net_tstamp SIOCSHWTSTAMP: %s", strerror(errno));
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ imr.imr_multiaddr.s_addr = ptpClock->netPath.multicastAddr;
|
|
imr.imr_interface.s_addr = htonl(INADDR_ANY);
|
|
|
|
- setsockopt(netPath->eventSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, sizeof(struct ip_mreq));
|
|
- setsockopt(netPath->generalSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, sizeof(struct ip_mreq));
|
|
+ setsockopt(ptpClock->netPath.eventSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, sizeof(struct ip_mreq));
|
|
+ setsockopt(ptpClock->netPath.generalSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, sizeof(struct ip_mreq));
|
|
|
|
- netPath->multicastAddr = 0;
|
|
- netPath->unicastAddr = 0;
|
|
+ ptpClock->netPath.multicastAddr = 0;
|
|
+ ptpClock->netPath.unicastAddr = 0;
|
|
|
|
- if(netPath->eventSock > 0)
|
|
- close(netPath->eventSock);
|
|
- netPath->eventSock = -1;
|
|
+ if(ptpClock->netPath.eventSock > 0)
|
|
+ close(ptpClock->netPath.eventSock);
|
|
+ ptpClock->netPath.eventSock = -1;
|
|
|
|
- if(netPath->generalSock > 0)
|
|
- close(netPath->generalSock);
|
|
- netPath->generalSock = -1;
|
|
+ if(ptpClock->netPath.generalSock > 0)
|
|
+ close(ptpClock->netPath.generalSock);
|
|
+ ptpClock->netPath.generalSock = -1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
-int netSelect(TimeInternal *timeout, NetPath *netPath)
|
|
+int netSelect(TimeInternal *timeout, PtpClock *ptpClock)
|
|
{
|
|
int ret, nfds;
|
|
fd_set readfds;
|
|
@@ -397,8 +414,8 @@ int netSelect(TimeInternal *timeout, NetPath *netPath)
|
|
return FALSE;
|
|
|
|
FD_ZERO(&readfds);
|
|
- FD_SET(netPath->eventSock, &readfds);
|
|
- FD_SET(netPath->generalSock, &readfds);
|
|
+ FD_SET(ptpClock->netPath.eventSock, &readfds);
|
|
+ FD_SET(ptpClock->netPath.generalSock, &readfds);
|
|
|
|
if(timeout)
|
|
{
|
|
@@ -409,10 +426,10 @@ int netSelect(TimeInternal *timeout, NetPath *netPath)
|
|
else
|
|
tv_ptr = 0;
|
|
|
|
- if(netPath->eventSock > netPath->generalSock)
|
|
- nfds = netPath->eventSock;
|
|
+ if(ptpClock->netPath.eventSock > ptpClock->netPath.generalSock)
|
|
+ nfds = ptpClock->netPath.eventSock;
|
|
else
|
|
- nfds = netPath->generalSock;
|
|
+ nfds = ptpClock->netPath.generalSock;
|
|
|
|
ret = select(nfds + 1, &readfds, 0, 0, tv_ptr) > 0;
|
|
if(ret < 0)
|
|
@@ -424,18 +441,18 @@ int netSelect(TimeInternal *timeout, NetPath *netPath)
|
|
return ret;
|
|
}
|
|
|
|
-ssize_t netRecvEvent(Octet *buf, TimeInternal *time, NetPath *netPath)
|
|
+ssize_t netRecvEvent(Octet *buf, TimeInternal *time, PtpClock *ptpClock)
|
|
{
|
|
- ssize_t ret;
|
|
+ ssize_t ret = 0;
|
|
struct msghdr msg;
|
|
struct iovec vec[1];
|
|
struct sockaddr_in from_addr;
|
|
union {
|
|
struct cmsghdr cm;
|
|
- char control[CMSG_SPACE(sizeof(struct timeval))];
|
|
+ char control[512];
|
|
} cmsg_un;
|
|
struct cmsghdr *cmsg;
|
|
- struct timeval *tv;
|
|
+ Boolean have_time;
|
|
|
|
vec[0].iov_base = buf;
|
|
vec[0].iov_len = PACKET_SIZE;
|
|
@@ -452,8 +469,36 @@ ssize_t netRecvEvent(Octet *buf, TimeInternal *time, NetPath *netPath)
|
|
msg.msg_control = cmsg_un.control;
|
|
msg.msg_controllen = sizeof(cmsg_un.control);
|
|
msg.msg_flags = 0;
|
|
-
|
|
- ret = recvmsg(netPath->eventSock, &msg, MSG_DONTWAIT);
|
|
+
|
|
+#ifdef HAVE_LINUX_NET_TSTAMP_H
|
|
+ if(ptpClock->runTimeOpts.time == TIME_SYSTEM_LINUX_HW ||
|
|
+ ptpClock->runTimeOpts.time == TIME_SYSTEM_LINUX_SW) {
|
|
+ ret = recvmsg(ptpClock->netPath.eventSock, &msg, MSG_ERRQUEUE|MSG_DONTWAIT);
|
|
+ if(ret <= 0) {
|
|
+ if (errno != EAGAIN && errno != EINTR)
|
|
+ return ret;
|
|
+ } else {
|
|
+ /*
|
|
+ * strip network transport header: assumes that this is the
|
|
+ * most recently sent message
|
|
+ */
|
|
+ if(ret > ptpClock->netPath.lastNetSendEventLength) {
|
|
+ memmove(buf,
|
|
+ buf + ret - ptpClock->netPath.lastNetSendEventLength,
|
|
+ ptpClock->netPath.lastNetSendEventLength);
|
|
+ ret = ptpClock->netPath.lastNetSendEventLength;
|
|
+ } else {
|
|
+ /* No clue what this message is. Skip it. */
|
|
+ PERROR("received unexpected bounce via error queue");
|
|
+ ret = 0;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+#endif /* HAVE_LINUX_NET_TSTAMP_H */
|
|
+
|
|
+ if(ret <= 0) {
|
|
+ ret = recvmsg(ptpClock->netPath.eventSock, &msg, MSG_DONTWAIT);
|
|
+ }
|
|
if(ret <= 0)
|
|
{
|
|
if(errno == EAGAIN || errno == EINTR)
|
|
@@ -468,11 +513,11 @@ ssize_t netRecvEvent(Octet *buf, TimeInternal *time, NetPath *netPath)
|
|
return 0;
|
|
}
|
|
|
|
- /* get time stamp of packet */
|
|
+ /* get time stamp of packet? */
|
|
if(!time)
|
|
{
|
|
- ERROR("null receive time stamp argument\n");
|
|
- return 0;
|
|
+ /* caller does not need time (probably wasn't even enabled) */
|
|
+ return ret;
|
|
}
|
|
|
|
if(msg.msg_flags&MSG_CTRUNC)
|
|
@@ -481,25 +526,54 @@ ssize_t netRecvEvent(Octet *buf, TimeInternal *time, NetPath *netPath)
|
|
return 0;
|
|
}
|
|
|
|
- if(msg.msg_controllen < sizeof(cmsg_un.control))
|
|
- {
|
|
- ERROR("received short ancillary data (%d/%d)\n",
|
|
- msg.msg_controllen, (int)sizeof(cmsg_un.control));
|
|
-
|
|
- return 0;
|
|
- }
|
|
-
|
|
- tv = 0;
|
|
- for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg))
|
|
- {
|
|
- if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMP)
|
|
- tv = (struct timeval *)CMSG_DATA(cmsg);
|
|
+ for (cmsg = CMSG_FIRSTHDR(&msg), have_time = FALSE;
|
|
+ !have_time && cmsg != NULL;
|
|
+ cmsg = CMSG_NXTHDR(&msg, cmsg))
|
|
+ {
|
|
+ if (cmsg->cmsg_level == SOL_SOCKET) {
|
|
+ switch (cmsg->cmsg_type) {
|
|
+ case SCM_TIMESTAMP: {
|
|
+ struct timeval *tv = (struct timeval *)CMSG_DATA(cmsg);
|
|
+ if(cmsg->cmsg_len < sizeof(*tv))
|
|
+ {
|
|
+ ERROR("received short SCM_TIMESTAMP (%d/%d)\n",
|
|
+ cmsg->cmsg_len, sizeof(*tv));
|
|
+ return 0;
|
|
+ }
|
|
+ time->seconds = tv->tv_sec;
|
|
+ time->nanoseconds = tv->tv_usec*1000;
|
|
+ have_time = TRUE;
|
|
+ break;
|
|
+ }
|
|
+#ifdef HAVE_LINUX_NET_TSTAMP_H
|
|
+ case SO_TIMESTAMPING: {
|
|
+ /* array of three time stamps: software, HW, raw HW */
|
|
+ struct timespec *stamp =
|
|
+ (struct timespec *)CMSG_DATA(cmsg);
|
|
+ if(cmsg->cmsg_len < sizeof(*stamp) * 3)
|
|
+ {
|
|
+ ERROR("received short SO_TIMESTAMPING (%d/%d)\n",
|
|
+ cmsg->cmsg_len, (int)sizeof(*stamp) * 3);
|
|
+ return 0;
|
|
+ }
|
|
+ if (ptpClock->runTimeOpts.time == TIME_SYSTEM_LINUX_HW) {
|
|
+ /* look at second element in array which is the HW tstamp */
|
|
+ stamp++;
|
|
+ }
|
|
+ if (stamp->tv_sec && stamp->tv_nsec) {
|
|
+ time->seconds = stamp->tv_sec;
|
|
+ time->nanoseconds = stamp->tv_nsec;
|
|
+ have_time = TRUE;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+#endif /* HAVE_LINUX_NET_TSTAMP_H */
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
- if(tv)
|
|
+ if(have_time)
|
|
{
|
|
- time->seconds = tv->tv_sec;
|
|
- time->nanoseconds = tv->tv_usec*1000;
|
|
DBGV("kernel recv time stamp %us %dns\n", time->seconds, time->nanoseconds);
|
|
}
|
|
else
|
|
@@ -507,20 +581,20 @@ ssize_t netRecvEvent(Octet *buf, TimeInternal *time, NetPath *netPath)
|
|
/* do not try to get by with recording the time here, better to fail
|
|
because the time recorded could be well after the message receive,
|
|
which would put a big spike in the offset signal sent to the clock servo */
|
|
- DBG("no recieve time stamp\n");
|
|
+ DBG("no receive time stamp\n");
|
|
return 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
-ssize_t netRecvGeneral(Octet *buf, NetPath *netPath)
|
|
+ssize_t netRecvGeneral(Octet *buf, PtpClock *ptpClock)
|
|
{
|
|
ssize_t ret;
|
|
struct sockaddr_in addr;
|
|
socklen_t addr_len = sizeof(struct sockaddr_in);
|
|
|
|
- ret = recvfrom(netPath->generalSock, buf, PACKET_SIZE, MSG_DONTWAIT, (struct sockaddr *)&addr, &addr_len);
|
|
+ ret = recvfrom(ptpClock->netPath.generalSock, buf, PACKET_SIZE, MSG_DONTWAIT, (struct sockaddr *)&addr, &addr_len);
|
|
if(ret <= 0)
|
|
{
|
|
if(errno == EAGAIN || errno == EINTR)
|
|
@@ -532,24 +606,95 @@ ssize_t netRecvGeneral(Octet *buf, NetPath *netPath)
|
|
return ret;
|
|
}
|
|
|
|
-ssize_t netSendEvent(Octet *buf, UInteger16 length, NetPath *netPath)
|
|
+ssize_t netSendEvent(Octet *buf, UInteger16 length, TimeInternal *sendTimeStamp, PtpClock *ptpClock)
|
|
{
|
|
ssize_t ret;
|
|
struct sockaddr_in addr;
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(PTP_EVENT_PORT);
|
|
- addr.sin_addr.s_addr = netPath->multicastAddr;
|
|
-
|
|
- ret = sendto(netPath->eventSock, buf, length, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
|
|
+ addr.sin_addr.s_addr = ptpClock->netPath.multicastAddr;
|
|
+ ptpClock->netPath.lastNetSendEventLength = length;
|
|
+
|
|
+ ret = sendto(ptpClock->netPath.eventSock, buf, length, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
|
|
if(ret <= 0)
|
|
DBG("error sending multi-cast event message\n");
|
|
-
|
|
- if(netPath->unicastAddr)
|
|
+ else if(sendTimeStamp)
|
|
+ {
|
|
+ /*
|
|
+ * The packet is assumed to generated a time stamp soon. For
|
|
+ * simplicity reasons wait until it got time stamped.
|
|
+ *
|
|
+ * Tests under load showed that the time stamp was not
|
|
+ * always generated (packet dropped inside the driver?).
|
|
+ * This situation is handled by trying only for a while,
|
|
+ * then giving up and returning a zero timestamp.
|
|
+ */
|
|
+#undef DEBUG_PACKET_LOSS
|
|
+#ifdef DEBUG_PACKET_LOSS
|
|
+ /* to debug the error case drop every 10th outgoing packet */
|
|
+ static int debugPacketCounter;
|
|
+ debugPacketCounter++;
|
|
+#endif
|
|
+ sendTimeStamp->seconds = 0;
|
|
+ sendTimeStamp->nanoseconds = 0;
|
|
+
|
|
+ /* fast path: get send time stamp directly */
|
|
+ if(
|
|
+#ifdef DEBUG_PACKET_LOSS
|
|
+ (debugPacketCounter % 10) &&
|
|
+#endif
|
|
+ getSendTime(sendTimeStamp, ptpClock)) {
|
|
+ DBGV("got send time stamp in first attempt\n");
|
|
+ } else {
|
|
+ /*
|
|
+ * need to wait for it: need to check system time, counting
|
|
+ * the number of nanoSleep()s is too inaccurate because it
|
|
+ * each call sleeps much longer than requested
|
|
+ */
|
|
+ TimeInternal start, now;
|
|
+ timerNow(&start);
|
|
+ while(TRUE) {
|
|
+ Boolean gotTime;
|
|
+ TimeInternal delayAfterPacketSend;
|
|
+ delayAfterPacketSend.seconds = 0;
|
|
+ delayAfterPacketSend.nanoseconds = 1000;
|
|
+ nanoSleep(&delayAfterPacketSend);
|
|
+ gotTime =
|
|
+#ifdef DEBUG_PACKET_LOSS
|
|
+ (debugPacketCounter % 10) &&
|
|
+#endif
|
|
+ getSendTime(sendTimeStamp, ptpClock);
|
|
+ timerNow(&now);
|
|
+ subTime(&now, &now, &start);
|
|
+ /* 0.5 seconds is the maximum we wait... */
|
|
+ if(gotTime || now.seconds >= 1 || now.nanoseconds >= 500000000) {
|
|
+ DBGV("%s send time stamp after %d.%09ds\n",
|
|
+ gotTime ? "got" : "failed to get",
|
|
+ now.seconds, now.nanoseconds);
|
|
+#ifdef PTPD_DBGV
|
|
+ if (!gotTime) {
|
|
+ /* unpack the message because that logs its content */
|
|
+ MsgHeader header;
|
|
+ DBGV("unpacking message without time stamp\n");
|
|
+ msgUnpackHeader(buf, &header);
|
|
+ }
|
|
+#endif
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @TODO: why is the packet sent twice when unicast is enabled?
|
|
+ * If that's correct, deal with the send time stamps.
|
|
+ */
|
|
+ if(ptpClock->netPath.unicastAddr)
|
|
{
|
|
- addr.sin_addr.s_addr = netPath->unicastAddr;
|
|
+ addr.sin_addr.s_addr = ptpClock->netPath.unicastAddr;
|
|
|
|
- ret = sendto(netPath->eventSock, buf, length, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
|
|
+ ret = sendto(ptpClock->netPath.eventSock, buf, length, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
|
|
if(ret <= 0)
|
|
DBG("error sending uni-cast event message\n");
|
|
}
|
|
@@ -557,24 +702,24 @@ ssize_t netSendEvent(Octet *buf, UInteger16 length, NetPath *netPath)
|
|
return ret;
|
|
}
|
|
|
|
-ssize_t netSendGeneral(Octet *buf, UInteger16 length, NetPath *netPath)
|
|
+ssize_t netSendGeneral(Octet *buf, UInteger16 length, PtpClock *ptpClock)
|
|
{
|
|
ssize_t ret;
|
|
struct sockaddr_in addr;
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(PTP_GENERAL_PORT);
|
|
- addr.sin_addr.s_addr = netPath->multicastAddr;
|
|
+ addr.sin_addr.s_addr = ptpClock->netPath.multicastAddr;
|
|
|
|
- ret = sendto(netPath->generalSock, buf, length, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
|
|
+ ret = sendto(ptpClock->netPath.generalSock, buf, length, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
|
|
if(ret <= 0)
|
|
DBG("error sending multi-cast general message\n");
|
|
|
|
- if(netPath->unicastAddr)
|
|
+ if(ptpClock->netPath.unicastAddr)
|
|
{
|
|
- addr.sin_addr.s_addr = netPath->unicastAddr;
|
|
+ addr.sin_addr.s_addr = ptpClock->netPath.unicastAddr;
|
|
|
|
- ret = sendto(netPath->eventSock, buf, length, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
|
|
+ ret = sendto(ptpClock->netPath.eventSock, buf, length, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
|
|
if(ret <= 0)
|
|
DBG("error sending uni-cast general message\n");
|
|
}
|
|
diff --git a/src/dep/ptpd_dep.h b/src/dep/ptpd_dep.h
|
|
index b898e3e..c018d64 100644
|
|
--- a/src/dep/ptpd_dep.h
|
|
+++ b/src/dep/ptpd_dep.h
|
|
@@ -10,6 +10,7 @@
|
|
#include<errno.h>
|
|
#include<signal.h>
|
|
#include<fcntl.h>
|
|
+#include<syslog.h>
|
|
#include<sys/stat.h>
|
|
#include<time.h>
|
|
#include<sys/time.h>
|
|
@@ -19,22 +20,58 @@
|
|
#include<sys/ioctl.h>
|
|
#include<arpa/inet.h>
|
|
|
|
+#ifdef HAVE_LINUX_NET_TSTAMP_H
|
|
+#include "asm/types.h"
|
|
+#include "linux/net_tstamp.h"
|
|
+#include "linux/errqueue.h"
|
|
+
|
|
+#ifndef SO_TIMESTAMPNS
|
|
+# define SO_TIMESTAMPNS 35
|
|
+#endif
|
|
+
|
|
+#ifndef SO_TIMESTAMPING
|
|
+# define SO_TIMESTAMPING 37
|
|
+#endif
|
|
+
|
|
+#ifndef SIOCGSTAMPNS
|
|
+# define SIOCGSTAMPNS 0x8907
|
|
+#endif
|
|
+
|
|
+#ifndef SIOCSHWTSTAMP
|
|
+# define SIOCSHWTSTAMP 0x89b0
|
|
+#endif
|
|
+
|
|
+#endif /* HAVE_LINUX_NET_TSTAMP_H */
|
|
+
|
|
+/**
|
|
+ * route output either into syslog or stderr, depending on global useSyslog settings
|
|
+ * @param priority same as for syslog()
|
|
+ * @param format printf style format string
|
|
+ */
|
|
+void message(int priority, const char *format, ...);
|
|
+
|
|
+/**
|
|
+ * if TRUE then message() will print via syslog(); no init required and
|
|
+ * can be reverted to FALSE at any time
|
|
+ */
|
|
+extern Boolean useSyslog;
|
|
|
|
/* system messages */
|
|
-#define ERROR(x, ...) fprintf(stderr, "(ptpd error) " x, ##__VA_ARGS__)
|
|
-#define PERROR(x, ...) fprintf(stderr, "(ptpd error) " x ": %m\n", ##__VA_ARGS__)
|
|
-#define NOTIFY(x, ...) fprintf(stderr, "(ptpd notice) " x, ##__VA_ARGS__)
|
|
+#define ERROR(x, ...) message(LOG_ERR, x, ##__VA_ARGS__)
|
|
+#define PERROR(x, ...) message(LOG_ERR, x ": %m\n", ##__VA_ARGS__)
|
|
+#define NOTIFY(x, ...) message(LOG_NOTICE, x, ##__VA_ARGS__)
|
|
+#define INFO(x, ...) message(LOG_INFO, x, ##__VA_ARGS__)
|
|
|
|
/* debug messages */
|
|
#ifdef PTPD_DBGV
|
|
#define PTPD_DBG
|
|
-#define DBGV(x, ...) fprintf(stderr, "(ptpd debug) " x, ##__VA_ARGS__)
|
|
+#define DBGV(x, ...) message(LOG_DEBUG, x, ##__VA_ARGS__)
|
|
#else
|
|
#define DBGV(x, ...)
|
|
#endif
|
|
|
|
#ifdef PTPD_DBG
|
|
-#define DBG(x, ...) fprintf(stderr, "(ptpd debug) " x, ##__VA_ARGS__)
|
|
+#define DBG(x, ...) message(LOG_DEBUG, x, ##__VA_ARGS__)
|
|
#else
|
|
#define DBG(x, ...)
|
|
#endif
|
|
@@ -85,11 +122,11 @@ void msgUnpackDelayReq(void*,MsgDelayReq*);
|
|
void msgUnpackFollowUp(void*,MsgFollowUp*);
|
|
void msgUnpackDelayResp(void*,MsgDelayResp*);
|
|
void msgUnpackManagement(void*,MsgManagement*);
|
|
-UInteger8 msgUnloadManagement(void*,MsgManagement*,PtpClock*,RunTimeOpts*);
|
|
+UInteger8 msgUnloadManagement(void*,MsgManagement*,PtpClock*);
|
|
void msgUnpackManagementPayload(void *buf, MsgManagement *manage);
|
|
void msgPackHeader(void*,PtpClock*);
|
|
-void msgPackSync(void*,Boolean,TimeRepresentation*,PtpClock*);
|
|
-void msgPackDelayReq(void*,Boolean,TimeRepresentation*,PtpClock*);
|
|
+void msgPackSync(void*,Boolean,Boolean,TimeRepresentation*,PtpClock*);
|
|
+void msgPackDelayReq(void*,Boolean,Boolean,TimeRepresentation*,PtpClock*);
|
|
void msgPackFollowUp(void*,UInteger16,TimeRepresentation*,PtpClock*);
|
|
void msgPackDelayResp(void*,MsgHeader*,TimeRepresentation*,PtpClock*);
|
|
UInteger16 msgPackManagement(void*,MsgManagement*,PtpClock*);
|
|
@@ -97,21 +134,21 @@ UInteger16 msgPackManagementResponse(void*,MsgHeader*,MsgManagement*,PtpClock*);
|
|
|
|
/* net.c */
|
|
/* linux API dependent */
|
|
-Boolean netInit(NetPath*,RunTimeOpts*,PtpClock*);
|
|
-Boolean netShutdown(NetPath*);
|
|
-int netSelect(TimeInternal*,NetPath*);
|
|
-ssize_t netRecvEvent(Octet*,TimeInternal*,NetPath*);
|
|
-ssize_t netRecvGeneral(Octet*,NetPath*);
|
|
-ssize_t netSendEvent(Octet*,UInteger16,NetPath*);
|
|
-ssize_t netSendGeneral(Octet*,UInteger16,NetPath*);
|
|
+Boolean netInit(PtpClock*);
|
|
+Boolean netShutdown(PtpClock*);
|
|
+int netSelect(TimeInternal*,PtpClock*);
|
|
+ssize_t netRecvEvent(Octet*,TimeInternal*,PtpClock*);
|
|
+ssize_t netRecvGeneral(Octet*,PtpClock*);
|
|
+ssize_t netSendEvent(Octet*,UInteger16,TimeInternal*,PtpClock*);
|
|
+ssize_t netSendGeneral(Octet*,UInteger16,PtpClock*);
|
|
|
|
/* servo.c */
|
|
-void initClock(RunTimeOpts*,PtpClock*);
|
|
+void initClock(PtpClock*);
|
|
void updateDelay(TimeInternal*,TimeInternal*,
|
|
- one_way_delay_filter*,RunTimeOpts*,PtpClock*);
|
|
+ one_way_delay_filter*,PtpClock*);
|
|
void updateOffset(TimeInternal*,TimeInternal*,
|
|
- offset_from_master_filter*,RunTimeOpts*,PtpClock*);
|
|
-void updateClock(RunTimeOpts*,PtpClock*);
|
|
+ offset_from_master_filter*,PtpClock*);
|
|
+void updateClock(PtpClock*);
|
|
|
|
/* startup.c */
|
|
/* unix API dependent */
|
|
@@ -120,20 +157,119 @@ void ptpdShutdown(void);
|
|
|
|
/* sys.c */
|
|
/* unix API dependent */
|
|
-void displayStats(RunTimeOpts*,PtpClock*);
|
|
-Boolean nanoSleep(TimeInternal*);
|
|
-void getTime(TimeInternal*);
|
|
-void setTime(TimeInternal*);
|
|
+void displayStats(PtpClock*);
|
|
UInteger16 getRand(UInteger32*);
|
|
-Boolean adjFreq(Integer32);
|
|
|
|
-/* timer.c */
|
|
+/**
|
|
+ * @defgroup time Time Source
|
|
+ *
|
|
+ * Interface to the clock which is used to time stamp
|
|
+ * packages and which is adjusted by PTPd.
|
|
+ *
|
|
+ * The intention is to hide different actual implementations
|
|
+ * behind one interface:
|
|
+ * - system time (gettimeofday())
|
|
+ * - NIC time (timer inside the network hardware)
|
|
+ * - ...
|
|
+ */
|
|
+/*@{*/
|
|
+/** @file time.c */
|
|
+Boolean initTime(PtpClock*);
|
|
+void getTime(TimeInternal*, PtpClock*);
|
|
+void setTime(TimeInternal*, PtpClock*);
|
|
+
|
|
+/**
|
|
+ * Adjusts the time, ideally by varying the clock rate.
|
|
+ *
|
|
+ * @param adj frequency adjustment: a time source which supports that ignores the offset
|
|
+ * @param offset offset (reference time - local time) from last measurement: a time source which
|
|
+ * cannot adjust the frequence must fall back to this cruder method (may be NULL)
|
|
+ */
|
|
+void adjTime(Integer32 adj, TimeInternal *offset, PtpClock*);
|
|
+
|
|
+/**
|
|
+ * Adjusts the time by shifting the clock.
|
|
+ *
|
|
+ * @param offset this value must be substracted from clock (might be negative)
|
|
+ */
|
|
+void adjTimeOffset(TimeInternal *offset, PtpClock*);
|
|
+
|
|
+/**
|
|
+ * Gets the time when the latest outgoing packet left the host.
|
|
+ *
|
|
+ * There is no way to identify the packet the time stamp belongs to,
|
|
+ * so this must be called after sending each packet until the time
|
|
+ * stamp for the packet is available. This can be some (hopefully
|
|
+ * small) time after the packet was passed to the IP stack.
|
|
+ *
|
|
+ * There is no mechanism either to determine packet loss and thus a
|
|
+ * time stamp which never becomes available.
|
|
+ *
|
|
+ * @todo Can such packet loss occur?
|
|
+ *
|
|
+ * Does not work with TIME_SYSTEM.
|
|
+ *
|
|
+ * @retval sendTimeStamp set to the time when the packet left the host
|
|
+ * @return TRUE if the time stamp was available
|
|
+ */
|
|
+Boolean getSendTime(TimeInternal *sendTimeStamp, PtpClock*);
|
|
+
|
|
+/**
|
|
+ * Gets the time when the packet identified by the given attributes
|
|
+ * was received by the host.
|
|
+ *
|
|
+ * Because the arrival of packets is out of the control of PTPd, the
|
|
+ * time stamping must support unique identification of which time
|
|
+ * stamp belongs to which packet.
|
|
+ *
|
|
+ * Due to packet loss in the receive queue, there can be time stamps
|
|
+ * without IP packets. getReceiveTime() automatically discards stale
|
|
+ * time stamps, including the ones that where returned by
|
|
+ * getReceiveTime(). This implies that there is not guarantee that
|
|
+ * calling getReceiveTime() more than once for the same packet
|
|
+ * will always return a result.
|
|
+ *
|
|
+ * Due to hardware limitations only one time stamp might be stored
|
|
+ * until queried by the NIC driver; this can lead to packets without
|
|
+ * time stamp. This needs to be handled by the caller of
|
|
+ * getReceiveTime(), for example by ignoring the packet.
|
|
+ *
|
|
+ * Does not work with TIME_SYSTEM.
|
|
+ *
|
|
+ * @retval recvTimeStamp set to the time when the packet entered the host, if available
|
|
+ * @return TRUE if the time stamp was available
|
|
+ */
|
|
+Boolean getReceiveTime(TimeInternal *recvTimeStamp,
|
|
+ Octet sourceUuid[PTP_UUID_LENGTH],
|
|
+ UInteger16 sequenceId, PtpClock*);
|
|
+
|
|
+/** called regularly every second while process is idle */
|
|
+void timeNoActivity(PtpClock*);
|
|
+
|
|
+/**
|
|
+ * called while still in the old state and before entering a new one:
|
|
+ * transition is relevant for hardware assisted timing
|
|
+ */
|
|
+void timeToState(UInteger8 state, PtpClock *ptpClock);
|
|
+
|
|
+/*@}*/
|
|
+
|
|
+/**
|
|
+ * @defgroup timer regular wakeup at different timer intervals
|
|
+ *
|
|
+ * This timing is always done using the system time of the host.
|
|
+ */
|
|
+/*@{*/
|
|
+/** @file timer.c */
|
|
void initTimer(void);
|
|
void timerUpdate(IntervalTimer*);
|
|
void timerStop(UInteger16,IntervalTimer*);
|
|
void timerStart(UInteger16,UInteger16,IntervalTimer*);
|
|
Boolean timerExpired(UInteger16,IntervalTimer*);
|
|
-
|
|
+Boolean nanoSleep(TimeInternal*);
|
|
+/** gets the current system time */
|
|
+void timerNow(TimeInternal*);
|
|
+/*@}*/
|
|
|
|
#endif
|
|
|
|
diff --git a/src/dep/servo.c b/src/dep/servo.c
|
|
index ae2da3b..3a3458e 100644
|
|
--- a/src/dep/servo.c
|
|
+++ b/src/dep/servo.c
|
|
@@ -1,8 +1,8 @@
|
|
#include "../ptpd.h"
|
|
|
|
-void initClock(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void initClock(PtpClock *ptpClock)
|
|
{
|
|
- DBG("initClock\n");
|
|
+ DBG("%sinitClock\n", ptpClock->name);
|
|
|
|
/* clear vars */
|
|
ptpClock->master_to_slave_delay.seconds = ptpClock->master_to_slave_delay.nanoseconds = 0;
|
|
@@ -10,20 +10,23 @@ void initClock(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
ptpClock->observed_variance = 0;
|
|
ptpClock->observed_drift = 0; /* clears clock servo accumulator (the I term) */
|
|
ptpClock->owd_filt.s_exp = 0; /* clears one-way delay filter */
|
|
- ptpClock->halfEpoch = ptpClock->halfEpoch || rtOpts->halfEpoch;
|
|
- rtOpts->halfEpoch = 0;
|
|
+ ptpClock->halfEpoch = ptpClock->halfEpoch || ptpClock->runTimeOpts.halfEpoch;
|
|
+ ptpClock->runTimeOpts.halfEpoch = 0;
|
|
|
|
/* level clock */
|
|
- if(!rtOpts->noAdjust)
|
|
- adjFreq(0);
|
|
+ if(!ptpClock->runTimeOpts.noAdjust)
|
|
+ adjTime(0, NULL, ptpClock);
|
|
}
|
|
|
|
void updateDelay(TimeInternal *send_time, TimeInternal *recv_time,
|
|
- one_way_delay_filter *owd_filt, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+ one_way_delay_filter *owd_filt, PtpClock *ptpClock)
|
|
{
|
|
Integer16 s;
|
|
|
|
- DBGV("updateDelay\n");
|
|
+ DBGV("%supdateDelay send %10ds %11dns recv %10ds %11dns\n",
|
|
+ ptpClock->name,
|
|
+ send_time->seconds, send_time->nanoseconds,
|
|
+ recv_time->seconds, recv_time->nanoseconds);
|
|
|
|
/* calc 'slave_to_master_delay' */
|
|
subTime(&ptpClock->slave_to_master_delay, recv_time, send_time);
|
|
@@ -32,6 +35,11 @@ void updateDelay(TimeInternal *send_time, TimeInternal *recv_time,
|
|
addTime(&ptpClock->one_way_delay, &ptpClock->master_to_slave_delay, &ptpClock->slave_to_master_delay);
|
|
ptpClock->one_way_delay.seconds /= 2;
|
|
ptpClock->one_way_delay.nanoseconds /= 2;
|
|
+
|
|
+ DBGV("%supdateDelay slave_to_master_delay %10ds %11dns one_way_delay %10ds %11dns\n",
|
|
+ ptpClock->name,
|
|
+ ptpClock->slave_to_master_delay.seconds, ptpClock->slave_to_master_delay.nanoseconds,
|
|
+ ptpClock->one_way_delay.seconds, ptpClock->one_way_delay.nanoseconds);
|
|
|
|
if(ptpClock->one_way_delay.seconds)
|
|
{
|
|
@@ -41,7 +49,7 @@ void updateDelay(TimeInternal *send_time, TimeInternal *recv_time,
|
|
}
|
|
|
|
/* avoid overflowing filter */
|
|
- s = rtOpts->s;
|
|
+ s = ptpClock->runTimeOpts.s;
|
|
while(abs(owd_filt->y)>>(31-s))
|
|
--s;
|
|
|
|
@@ -60,13 +68,16 @@ void updateDelay(TimeInternal *send_time, TimeInternal *recv_time,
|
|
owd_filt->nsec_prev = ptpClock->one_way_delay.nanoseconds;
|
|
ptpClock->one_way_delay.nanoseconds = owd_filt->y;
|
|
|
|
- DBG("delay filter %d, %d\n", owd_filt->y, owd_filt->s_exp);
|
|
+ DBG("%sdelay filter %d, %d\n", ptpClock->name, owd_filt->y, owd_filt->s_exp);
|
|
}
|
|
|
|
void updateOffset(TimeInternal *send_time, TimeInternal *recv_time,
|
|
- offset_from_master_filter *ofm_filt, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+ offset_from_master_filter *ofm_filt, PtpClock *ptpClock)
|
|
{
|
|
- DBGV("updateOffset\n");
|
|
+ DBGV("%supdateOffset send %10ds %11dns recv %10ds %11dns\n",
|
|
+ ptpClock->name,
|
|
+ send_time->seconds, send_time->nanoseconds,
|
|
+ recv_time->seconds, recv_time->nanoseconds);
|
|
|
|
/* calc 'master_to_slave_delay' */
|
|
subTime(&ptpClock->master_to_slave_delay, recv_time, send_time);
|
|
@@ -74,6 +85,11 @@ void updateOffset(TimeInternal *send_time, TimeInternal *recv_time,
|
|
/* update 'offset_from_master' */
|
|
subTime(&ptpClock->offset_from_master, &ptpClock->master_to_slave_delay, &ptpClock->one_way_delay);
|
|
|
|
+ DBGV("%supdateOffset master_to_slave_delay %10ds %11dns offset_from_master %10ds %11dns\n",
|
|
+ ptpClock->name,
|
|
+ ptpClock->master_to_slave_delay.seconds, ptpClock->master_to_slave_delay.nanoseconds,
|
|
+ ptpClock->offset_from_master.seconds, ptpClock->offset_from_master.nanoseconds);
|
|
+
|
|
if(ptpClock->offset_from_master.seconds)
|
|
{
|
|
/* cannot filter with secs, clear filter */
|
|
@@ -86,32 +102,29 @@ void updateOffset(TimeInternal *send_time, TimeInternal *recv_time,
|
|
ofm_filt->nsec_prev = ptpClock->offset_from_master.nanoseconds;
|
|
ptpClock->offset_from_master.nanoseconds = ofm_filt->y;
|
|
|
|
- DBGV("offset filter %d\n", ofm_filt->y);
|
|
+ DBGV("%soffset filter %d\n", ptpClock->name, ofm_filt->y);
|
|
}
|
|
|
|
-void updateClock(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void updateClock(PtpClock *ptpClock)
|
|
{
|
|
Integer32 adj;
|
|
- TimeInternal timeTmp;
|
|
|
|
- DBGV("updateClock\n");
|
|
+ DBGV("%supdateClock\n", ptpClock->name);
|
|
|
|
if(ptpClock->offset_from_master.seconds)
|
|
{
|
|
/* if secs, reset clock or set freq adjustment to max */
|
|
- if(!rtOpts->noAdjust)
|
|
+ if(!ptpClock->runTimeOpts.noAdjust || ptpClock->nic_instead_of_system)
|
|
{
|
|
- if(!rtOpts->noResetClock)
|
|
+ if(!ptpClock->runTimeOpts.noResetClock)
|
|
{
|
|
- getTime(&timeTmp);
|
|
- subTime(&timeTmp, &timeTmp, &ptpClock->offset_from_master);
|
|
- setTime(&timeTmp);
|
|
- initClock(rtOpts, ptpClock);
|
|
+ adjTimeOffset(&ptpClock->offset_from_master, ptpClock);
|
|
+ initClock(ptpClock);
|
|
}
|
|
else
|
|
{
|
|
adj = ptpClock->offset_from_master.nanoseconds > 0 ? ADJ_FREQ_MAX : -ADJ_FREQ_MAX;
|
|
- adjFreq(-adj);
|
|
+ adjTime(-adj, &ptpClock->offset_from_master, ptpClock);
|
|
}
|
|
}
|
|
}
|
|
@@ -120,13 +133,13 @@ void updateClock(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
/* the PI controller */
|
|
|
|
/* no negative or zero attenuation */
|
|
- if(rtOpts->ap < 1)
|
|
- rtOpts->ap = 1;
|
|
- if(rtOpts->ai < 1)
|
|
- rtOpts->ai = 1;
|
|
+ if(ptpClock->runTimeOpts.ap < 1)
|
|
+ ptpClock->runTimeOpts.ap = 1;
|
|
+ if(ptpClock->runTimeOpts.ai < 1)
|
|
+ ptpClock->runTimeOpts.ai = 1;
|
|
|
|
/* the accumulator for the I component */
|
|
- ptpClock->observed_drift += ptpClock->offset_from_master.nanoseconds/rtOpts->ai;
|
|
+ ptpClock->observed_drift += ptpClock->offset_from_master.nanoseconds/ptpClock->runTimeOpts.ai;
|
|
|
|
/* clamp the accumulator to ADJ_FREQ_MAX for sanity */
|
|
if(ptpClock->observed_drift > ADJ_FREQ_MAX)
|
|
@@ -134,24 +147,28 @@ void updateClock(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
else if(ptpClock->observed_drift < -ADJ_FREQ_MAX)
|
|
ptpClock->observed_drift = -ADJ_FREQ_MAX;
|
|
|
|
- adj = ptpClock->offset_from_master.nanoseconds/rtOpts->ap + ptpClock->observed_drift;
|
|
+ adj = ptpClock->offset_from_master.nanoseconds/ptpClock->runTimeOpts.ap + ptpClock->observed_drift;
|
|
|
|
/* apply controller output as a clock tick rate adjustment */
|
|
- if(!rtOpts->noAdjust)
|
|
- adjFreq(-adj);
|
|
+ if(!ptpClock->runTimeOpts.noAdjust || ptpClock->nic_instead_of_system)
|
|
+ adjTime(-adj, &ptpClock->offset_from_master, ptpClock);
|
|
}
|
|
|
|
- if(rtOpts->displayStats)
|
|
- displayStats(rtOpts, ptpClock);
|
|
+ if(ptpClock->runTimeOpts.displayStats)
|
|
+ displayStats(ptpClock);
|
|
|
|
- DBGV("master-to-slave delay: %10ds %11dns\n",
|
|
+ DBGV("%smaster-to-slave delay: %10ds %11dns\n",
|
|
+ ptpClock->name,
|
|
ptpClock->master_to_slave_delay.seconds, ptpClock->master_to_slave_delay.nanoseconds);
|
|
- DBGV("slave-to-master delay: %10ds %11dns\n",
|
|
+ DBGV("%sslave-to-master delay: %10ds %11dns\n",
|
|
+ ptpClock->name,
|
|
ptpClock->slave_to_master_delay.seconds, ptpClock->slave_to_master_delay.nanoseconds);
|
|
- DBGV("one-way delay: %10ds %11dns\n",
|
|
+ DBGV("%sone-way delay: %10ds %11dns\n",
|
|
+ ptpClock->name,
|
|
ptpClock->one_way_delay.seconds, ptpClock->one_way_delay.nanoseconds);
|
|
- DBG("offset from master: %10ds %11dns\n",
|
|
+ DBG("%soffset from master: %10ds %11dns\n",
|
|
+ ptpClock->name,
|
|
ptpClock->offset_from_master.seconds, ptpClock->offset_from_master.nanoseconds);
|
|
- DBG("observed drift: %10d\n", ptpClock->observed_drift);
|
|
+ DBG("%sobserved drift: %10d\n", ptpClock->name, ptpClock->observed_drift);
|
|
}
|
|
|
|
diff --git a/src/dep/startup.c b/src/dep/startup.c
|
|
index b9bef63..b3a76de 100644
|
|
--- a/src/dep/startup.c
|
|
+++ b/src/dep/startup.c
|
|
@@ -35,7 +35,7 @@ void catch_close(int sig)
|
|
|
|
void ptpdShutdown()
|
|
{
|
|
- netShutdown(&ptpClock->netPath);
|
|
+ netShutdown(ptpClock);
|
|
|
|
free(ptpClock->foreign);
|
|
free(ptpClock);
|
|
@@ -44,9 +44,9 @@ void ptpdShutdown()
|
|
PtpClock * ptpdStartup(int argc, char **argv, Integer16 *ret, RunTimeOpts *rtOpts)
|
|
{
|
|
int c, fd = -1, nondaemon = 0, noclose = 0;
|
|
-
|
|
+
|
|
/* parse command line arguments */
|
|
- while( (c = getopt(argc, argv, "?cf:dDxta:w:b:u:l:o:e:hy:m:gps:i:v:n:k:r")) != -1 ) {
|
|
+ while( (c = getopt(argc, argv, "?cf:dDz:xta:w:b:u:l:o:e:hy:m:gps:i:v:n:k:r")) != -1 ) {
|
|
switch(c) {
|
|
case '?':
|
|
printf(
|
|
@@ -54,10 +54,21 @@ PtpClock * ptpdStartup(int argc, char **argv, Integer16 *ret, RunTimeOpts *rtOpt
|
|
"-? show this page\n"
|
|
"\n"
|
|
"-c run in command line (non-daemon) mode\n"
|
|
-"-f FILE send output to FILE\n"
|
|
+"-f FILE send output to FILE (FILE=syslog writes into the system log)\n"
|
|
"-d display stats\n"
|
|
"-D display stats in .csv format\n"
|
|
"\n"
|
|
+"-z CLOCK selects which timer is used and controlled\n"
|
|
+" system = the host's system time (default)\n"
|
|
+" nic = the network interface\n"
|
|
+" both = NIC time is synchronized over the network via PTP\n"
|
|
+" and system time against NIC via local PTP\n"
|
|
+" assisted = system time is synchronized across network via\n"
|
|
+" NIC assisted time stamping\n"
|
|
+" linux_hw = synchronize system time with Linux kernel assistance\n"
|
|
+" via net_tstamp API, uses NIC time stamping\n"
|
|
+" linux_sw = synchronize system time with Linux kernel assistance\n"
|
|
+" via net_tstamp API, uses software time stamping\n"
|
|
"-x do not reset the clock if off by more than one second\n"
|
|
"-t do not adjust the system clock\n"
|
|
"-a NUMBER,NUMBER specify clock servo P and I attenuations\n"
|
|
@@ -93,14 +104,21 @@ PtpClock * ptpdStartup(int argc, char **argv, Integer16 *ret, RunTimeOpts *rtOpt
|
|
break;
|
|
|
|
case 'f':
|
|
- if((fd = creat(optarg, 0400)) != -1)
|
|
+ if(!strcmp(optarg, "syslog"))
|
|
{
|
|
- dup2(fd, STDOUT_FILENO);
|
|
- dup2(fd, STDERR_FILENO);
|
|
- noclose = 1;
|
|
+ useSyslog = TRUE;
|
|
}
|
|
else
|
|
- PERROR("could not open output file");
|
|
+ {
|
|
+ if((fd = creat(optarg, 0400)) != -1)
|
|
+ {
|
|
+ dup2(fd, STDOUT_FILENO);
|
|
+ dup2(fd, STDERR_FILENO);
|
|
+ noclose = 1;
|
|
+ }
|
|
+ else
|
|
+ PERROR("could not open output file");
|
|
+ }
|
|
break;
|
|
|
|
case 'd':
|
|
@@ -110,12 +128,42 @@ PtpClock * ptpdStartup(int argc, char **argv, Integer16 *ret, RunTimeOpts *rtOpt
|
|
break;
|
|
|
|
case 'D':
|
|
-#ifndef PTPD_DBG
|
|
rtOpts->displayStats = TRUE;
|
|
rtOpts->csvStats = TRUE;
|
|
-#endif
|
|
break;
|
|
|
|
+ case 'z':
|
|
+ if(!strcasecmp(optarg, "nic"))
|
|
+ {
|
|
+ rtOpts->time = TIME_NIC;
|
|
+ }
|
|
+ else if(!strcasecmp(optarg, "system"))
|
|
+ {
|
|
+ rtOpts->time = TIME_SYSTEM;
|
|
+ }
|
|
+ else if(!strcasecmp(optarg, "both"))
|
|
+ {
|
|
+ rtOpts->time = TIME_BOTH;
|
|
+ }
|
|
+ else if(!strcasecmp(optarg, "assisted"))
|
|
+ {
|
|
+ rtOpts->time = TIME_SYSTEM_ASSISTED;
|
|
+ }
|
|
+ else if(!strcasecmp(optarg, "linux_hw"))
|
|
+ {
|
|
+ rtOpts->time = TIME_SYSTEM_LINUX_HW;
|
|
+ }
|
|
+ else if(!strcasecmp(optarg, "linux_sw"))
|
|
+ {
|
|
+ rtOpts->time = TIME_SYSTEM_LINUX_SW;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ ERROR("Unsupported -z clock '%s'.\n", optarg);
|
|
+ *ret = 1;
|
|
+ }
|
|
+ break;
|
|
+
|
|
case 'x':
|
|
rtOpts->noResetClock = TRUE;
|
|
break;
|
|
@@ -222,6 +270,7 @@ PtpClock * ptpdStartup(int argc, char **argv, Integer16 *ret, RunTimeOpts *rtOpt
|
|
}
|
|
|
|
ptpClock = (PtpClock*)calloc(1, sizeof(PtpClock));
|
|
+ ptpClock->name = "";
|
|
if(!ptpClock)
|
|
{
|
|
PERROR("failed to allocate memory for protocol engine data");
|
|
@@ -230,6 +279,7 @@ PtpClock * ptpdStartup(int argc, char **argv, Integer16 *ret, RunTimeOpts *rtOpt
|
|
}
|
|
else
|
|
{
|
|
+ ptpClock->runTimeOpts = *rtOpts;
|
|
DBG("allocated %d bytes for protocol engine data\n", (int)sizeof(PtpClock));
|
|
ptpClock->foreign = (ForeignMasterRecord*)calloc(rtOpts->max_foreign_records, sizeof(ForeignMasterRecord));
|
|
if(!ptpClock->foreign)
|
|
diff --git a/src/dep/sys.c b/src/dep/sys.c
|
|
index 5206d34..1e01d82 100644
|
|
--- a/src/dep/sys.c
|
|
+++ b/src/dep/sys.c
|
|
@@ -1,18 +1,63 @@
|
|
/* sys.c */
|
|
|
|
#include "../ptpd.h"
|
|
+#include <stdarg.h>
|
|
|
|
-void displayStats(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+Boolean useSyslog;
|
|
+
|
|
+void message(int priority, const char *format, ...)
|
|
+{
|
|
+ va_list ap;
|
|
+
|
|
+ va_start(ap, format);
|
|
+ if (useSyslog)
|
|
+ {
|
|
+ static Boolean logOpened;
|
|
+ if (!logOpened)
|
|
+ {
|
|
+ openlog("ptpd", 0, LOG_USER);
|
|
+ logOpened = TRUE;
|
|
+ }
|
|
+ vsyslog(priority, format, ap);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ fprintf(stderr, "(ptpd %s) ",
|
|
+ priority == LOG_EMERG ? "emergency" :
|
|
+ priority == LOG_ALERT ? "alert" :
|
|
+ priority == LOG_CRIT ? "critical" :
|
|
+ priority == LOG_ERR ? "error" :
|
|
+ priority == LOG_WARNING ? "warning" :
|
|
+ priority == LOG_NOTICE ? "notice" :
|
|
+ priority == LOG_INFO ? "info" :
|
|
+ priority == LOG_DEBUG ? "debug" :
|
|
+ "???");
|
|
+ vfprintf(stderr, format, ap);
|
|
+ }
|
|
+ va_end(ap);
|
|
+}
|
|
+
|
|
+static size_t sprintfTime(PtpClock *ptpClock, char *buffer, TimeInternal *t, const char *prefix)
|
|
+{
|
|
+ return sprintf(buffer,
|
|
+ ", %s%s%d.%09d",
|
|
+ ptpClock->runTimeOpts.csvStats ? "" : prefix,
|
|
+ (t->seconds < 0 || t->nanoseconds < 0) ? "-" : "",
|
|
+ abs(t->seconds),
|
|
+ abs(t->nanoseconds));
|
|
+}
|
|
+
|
|
+void displayStats(PtpClock *ptpClock)
|
|
{
|
|
static int start = 1;
|
|
- static char sbuf[SCREEN_BUFSZ];
|
|
+ static char sbuf[2 * SCREEN_BUFSZ];
|
|
char *s;
|
|
int len = 0;
|
|
|
|
- if(start && rtOpts->csvStats)
|
|
+ if(start && ptpClock->runTimeOpts.csvStats)
|
|
{
|
|
start = 0;
|
|
- printf("state, one way delay, offset from master, drift, variance");
|
|
+ INFO("state, one way delay, offset from master, drift, variance, clock adjustment (ppb), slave to master delay, master to slave delay\n");
|
|
fflush(stdout);
|
|
}
|
|
|
|
@@ -32,63 +77,36 @@ void displayStats(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
default: s = "?"; break;
|
|
}
|
|
|
|
- len += sprintf(sbuf + len, "%s%s", rtOpts->csvStats ? "\n": "\rstate: ", s);
|
|
+ len += sprintf(sbuf + len, "%s%s%s", ptpClock->runTimeOpts.csvStats ? "": "state: ", ptpClock->name, s);
|
|
|
|
- if(ptpClock->port_state == PTP_SLAVE)
|
|
+ if(ptpClock->port_state == PTP_SLAVE ||
|
|
+ (ptpClock->port_state == PTP_MASTER && ptpClock->nic_instead_of_system))
|
|
{
|
|
- len += sprintf(sbuf + len,
|
|
- ", %s%d.%09d" ", %s%d.%09d",
|
|
- rtOpts->csvStats ? "" : "owd: ",
|
|
- ptpClock->one_way_delay.seconds,
|
|
- abs(ptpClock->one_way_delay.nanoseconds),
|
|
- rtOpts->csvStats ? "" : "ofm: ",
|
|
- ptpClock->offset_from_master.seconds,
|
|
- abs(ptpClock->offset_from_master.nanoseconds));
|
|
-
|
|
+ len += sprintfTime(ptpClock, sbuf + len, &ptpClock->one_way_delay, "owd: ");
|
|
+ len += sprintfTime(ptpClock, sbuf + len, &ptpClock->offset_from_master, "ofm: ");
|
|
+
|
|
len += sprintf(sbuf + len,
|
|
", %s%d" ", %s%d",
|
|
- rtOpts->csvStats ? "" : "drift: ", ptpClock->observed_drift,
|
|
- rtOpts->csvStats ? "" : "var: ", ptpClock->observed_variance);
|
|
+ ptpClock->runTimeOpts.csvStats ? "" : "drift: ", ptpClock->observed_drift,
|
|
+ ptpClock->runTimeOpts.csvStats ? "" : "var: ", ptpClock->observed_variance);
|
|
+
|
|
+ len += sprintf(sbuf + len,
|
|
+ ", %s%ld",
|
|
+ ptpClock->runTimeOpts.csvStats ? "" : "adj: ", ptpClock->adj);
|
|
+
|
|
+ len += sprintfTime(ptpClock, sbuf + len, &ptpClock->slave_to_master_delay, "stm: ");
|
|
+ len += sprintfTime(ptpClock, sbuf + len, &ptpClock->master_to_slave_delay, "mts: ");
|
|
}
|
|
-
|
|
- write(1, sbuf, rtOpts->csvStats ? len : SCREEN_MAXSZ + 1);
|
|
-}
|
|
|
|
-Boolean nanoSleep(TimeInternal *t)
|
|
-{
|
|
- struct timespec ts, tr;
|
|
-
|
|
- ts.tv_sec = t->seconds;
|
|
- ts.tv_nsec = t->nanoseconds;
|
|
-
|
|
- if(nanosleep(&ts, &tr) < 0)
|
|
+ if (ptpClock->runTimeOpts.csvStats)
|
|
{
|
|
- t->seconds = tr.tv_sec;
|
|
- t->nanoseconds = tr.tv_nsec;
|
|
- return FALSE;
|
|
+ INFO("%s\n", sbuf);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ /* overwrite the same line over and over again... */
|
|
+ INFO("%.*s\r", SCREEN_MAXSZ + 1, sbuf);
|
|
}
|
|
-
|
|
- return TRUE;
|
|
-}
|
|
-
|
|
-void getTime(TimeInternal *time)
|
|
-{
|
|
- struct timeval tv;
|
|
-
|
|
- gettimeofday(&tv, 0);
|
|
- time->seconds = tv.tv_sec;
|
|
- time->nanoseconds = tv.tv_usec*1000;
|
|
-}
|
|
-
|
|
-void setTime(TimeInternal *time)
|
|
-{
|
|
- struct timeval tv;
|
|
-
|
|
- tv.tv_sec = time->seconds;
|
|
- tv.tv_usec = time->nanoseconds/1000;
|
|
- settimeofday(&tv, 0);
|
|
-
|
|
- NOTIFY("resetting system clock to %ds %dns\n", time->seconds, time->nanoseconds);
|
|
}
|
|
|
|
UInteger16 getRand(UInteger32 *seed)
|
|
@@ -96,18 +114,3 @@ UInteger16 getRand(UInteger32 *seed)
|
|
return rand_r((unsigned int*)seed);
|
|
}
|
|
|
|
-Boolean adjFreq(Integer32 adj)
|
|
-{
|
|
- struct timex t;
|
|
-
|
|
- if(adj > ADJ_FREQ_MAX)
|
|
- adj = ADJ_FREQ_MAX;
|
|
- else if(adj < -ADJ_FREQ_MAX)
|
|
- adj = -ADJ_FREQ_MAX;
|
|
-
|
|
- t.modes = MOD_FREQUENCY;
|
|
- t.freq = adj*((1<<16)/1000);
|
|
-
|
|
- return !adjtimex(&t);
|
|
-}
|
|
-
|
|
diff --git a/src/dep/time.c b/src/dep/time.c
|
|
new file mode 100644
|
|
index 0000000..2a828b4
|
|
--- /dev/null
|
|
+++ b/src/dep/time.c
|
|
@@ -0,0 +1,771 @@
|
|
+#include "../ptpd.h"
|
|
+#include <stdarg.h>
|
|
+
|
|
+#include "e1000_ioctl.h"
|
|
+
|
|
+/** global state for controlling system time when TIME_BOTH is selected */
|
|
+static PtpClock timeBothClock;
|
|
+
|
|
+/**
|
|
+ * Most recent send time stamp from NIC, 0/0 if none available right now.
|
|
+ * Reset by getSendTime().
|
|
+ */
|
|
+static TimeInternal lastSendTime;
|
|
+
|
|
+#ifndef RECV_ARRAY_SIZE
|
|
+/**
|
|
+ * Must be large enough to buffer all time stamps received from the NIC
|
|
+ * but not yet requested by the protocol processor. Because new information
|
|
+ * can only be added when the protocol asks for old one, this should not
|
|
+ * get very full.
|
|
+ */
|
|
+# define RECV_ARRAY_SIZE 10
|
|
+#endif
|
|
+
|
|
+/**
|
|
+ * An array of the latest RECV_ARRAY_SIZE packet receive information.
|
|
+ * Once it overflows the oldest ones are overwritten in a round-robin
|
|
+ * fashion.
|
|
+ */
|
|
+static struct {
|
|
+ TimeInternal recvTimeStamp;
|
|
+ UInteger16 sequenceId;
|
|
+ Octet sourceUuid[PTP_UUID_LENGTH];
|
|
+} lastRecvTimes[RECV_ARRAY_SIZE];
|
|
+
|
|
+/**
|
|
+ * Oldest valid and next free entry in lastRecvTimes.
|
|
+ * Valid ones are [oldest, free[ if oldest <= free,
|
|
+ * otherwise [oldest, RECV_ARRAY_SIZE[ and [0, free[.
|
|
+ */
|
|
+static int oldestRecv, nextFreeRecv;
|
|
+
|
|
+/**
|
|
+ * if TIME_BOTH: measure NIC<->system time offsets and adapt system time
|
|
+ *
|
|
+ * This function is called whenever init.c gets control; to prevent to
|
|
+ * frequent changes it ignores invocations less than one second away from
|
|
+ * the previous one.
|
|
+ */
|
|
+static void syncSystemWithNIC(PtpClock *ptpClock)
|
|
+{
|
|
+ struct E1000_TSYNC_COMPARETS_ARGU ts;
|
|
+ TimeInternal delay;
|
|
+ static TimeInternal zero;
|
|
+
|
|
+ if(ptpClock->runTimeOpts.time != TIME_BOTH)
|
|
+ return;
|
|
+ else
|
|
+ {
|
|
+#if 1
|
|
+ static TimeInternal lastsync;
|
|
+ TimeInternal now, offset;
|
|
+ timerNow(&now);
|
|
+ subTime(&offset, &now, &lastsync);
|
|
+ if(offset.seconds <= 0)
|
|
+ return;
|
|
+ lastsync = now;
|
|
+#endif
|
|
+ }
|
|
+
|
|
+ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts;
|
|
+ memset(&ts, 0, sizeof(ts));
|
|
+ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_COMPARETS_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ ERROR("could not correlate E1000 hardware and system time on %s: %s\n",
|
|
+ ptpClock->netPath.eventSockIFR.ifr_name,
|
|
+ strerror(errno));
|
|
+ return;
|
|
+ }
|
|
+ delay.seconds = ts.systemToNIC.seconds * ts.systemToNICSign;
|
|
+ delay.nanoseconds = ts.systemToNIC.nanoseconds * ts.systemToNICSign;
|
|
+ DBGV("system to NIC delay %ld.%09d\n",
|
|
+ delay.seconds, delay.nanoseconds);
|
|
+ updateDelay(&delay, &zero, &timeBothClock.owd_filt, &timeBothClock);
|
|
+
|
|
+ delay.seconds = ts.NICToSystem.seconds * ts.NICToSystemSign;
|
|
+ delay.nanoseconds = ts.NICToSystem.nanoseconds * ts.NICToSystemSign;
|
|
+ DBGV("NIC to system delay %ld.%09d\n",
|
|
+ delay.seconds, delay.nanoseconds);
|
|
+ updateOffset(&delay, &zero, &timeBothClock.ofm_filt, &timeBothClock);
|
|
+
|
|
+ if(ptpClock->port_state == PTP_MASTER)
|
|
+ {
|
|
+ timeBothClock.nic_instead_of_system = TRUE;
|
|
+ timeBothClock.runTimeOpts.time = TIME_NIC;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ timeBothClock.nic_instead_of_system = FALSE;
|
|
+ timeBothClock.runTimeOpts.time = TIME_SYSTEM;
|
|
+ }
|
|
+ updateClock(&timeBothClock);
|
|
+ DBGV("system time updated\n");
|
|
+}
|
|
+
|
|
+static Boolean selectNICTimeMode(Boolean sync, PtpClock *ptpClock)
|
|
+{
|
|
+ DBGV("time stamp incoming %s packets\n", sync ? "Sync" : "Delay_Req");
|
|
+
|
|
+ switch (ptpClock->runTimeOpts.time) {
|
|
+#ifdef HAVE_LINUX_NET_TSTAMP_H
|
|
+ case TIME_SYSTEM_LINUX_HW: {
|
|
+ struct hwtstamp_config hwconfig;
|
|
+ int so_timestamping_flags;
|
|
+
|
|
+ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&hwconfig;
|
|
+ memset(&hwconfig, 0, sizeof(&hwconfig));
|
|
+
|
|
+ /*
|
|
+ * Configure for time stamping of incoming Sync or Delay_Req
|
|
+ * messages and for time stamping of all out-going event
|
|
+ * messages. Out-going messages will be bounced via the error
|
|
+ * queue of the event socket.
|
|
+ */
|
|
+ hwconfig.tx_type = HWTSTAMP_TX_ON;
|
|
+ hwconfig.rx_filter = sync ?
|
|
+ HWTSTAMP_FILTER_PTP_V1_L4_SYNC :
|
|
+ HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ;
|
|
+ so_timestamping_flags = SOF_TIMESTAMPING_TX_HARDWARE|SOF_TIMESTAMPING_RX_HARDWARE|SOF_TIMESTAMPING_SYS_HARDWARE;
|
|
+
|
|
+ if (ioctl(ptpClock->netPath.eventSock, SIOCSHWTSTAMP, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ if (errno == ERANGE) {
|
|
+ /* hardware time stamping not supported */
|
|
+ PERROR("net_tstamp SIOCSHWTSTAMP: mode of operation not supported");
|
|
+ return FALSE;
|
|
+ } else {
|
|
+ PERROR("net_tstamp SIOCSHWTSTAMP: %s", strerror(errno));
|
|
+ return FALSE;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (setsockopt(ptpClock->netPath.eventSock, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping_flags, sizeof(so_timestamping_flags)) < 0) {
|
|
+ PERROR("net_tstamp SO_TIMESTAMPING: %s", strerror(errno));
|
|
+ return FALSE;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ case TIME_SYSTEM_LINUX_SW: {
|
|
+ /* same as before, but without requiring support by the NIC */
|
|
+ int so_timestamping_flags =
|
|
+ SOF_TIMESTAMPING_TX_SOFTWARE|SOF_TIMESTAMPING_RX_SOFTWARE|SOF_TIMESTAMPING_SOFTWARE;
|
|
+ if (setsockopt(ptpClock->netPath.eventSock, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping_flags, sizeof(so_timestamping_flags)) < 0) {
|
|
+ PERROR("net_tstamp SO_TIMESTAMPING: %s", strerror(errno));
|
|
+ return FALSE;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+#else
|
|
+ case TIME_SYSTEM_LINUX_SW:
|
|
+ case TIME_SYSTEM_LINUX_HW:
|
|
+ PERROR("net_tstamp interface not supported");
|
|
+ return FALSE;
|
|
+#endif /* HAVE_LINUX_NET_TSTAMP_H */
|
|
+ default:
|
|
+ *(int *)&ptpClock->netPath.eventSockIFR.ifr_data = sync ? E1000_UDP_V1_SYNC : E1000_UDP_V1_DELAY;
|
|
+ if(ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_ENABLERX_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ ERROR("could not activate E1000 hardware receive time stamping on %s: %s\n",
|
|
+ ptpClock->netPath.eventSockIFR.ifr_name,
|
|
+ strerror(errno));
|
|
+ return FALSE;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return TRUE;
|
|
+}
|
|
+
|
|
+static Boolean initNICTime(Boolean sync, PtpClock *ptpClock)
|
|
+{
|
|
+ /** @todo also check success indicator in ifr_data */
|
|
+ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_INIT_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ ERROR("could not activate E1000 hardware time stamping on %s: %s\n",
|
|
+ ptpClock->netPath.eventSockIFR.ifr_name,
|
|
+ strerror(errno));
|
|
+ }
|
|
+ else if(ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_ENABLETX_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ ERROR("could not activate E1000 hardware send time stamping on %s: %s\n",
|
|
+ ptpClock->netPath.eventSockIFR.ifr_name,
|
|
+ strerror(errno));
|
|
+ }
|
|
+ else if(!selectNICTimeMode(sync, ptpClock)) {
|
|
+ // error already printed
|
|
+ }
|
|
+ else {
|
|
+#if 0
|
|
+ // move the NIC time for debugging purposes
|
|
+ TimeInternal timeTmp;
|
|
+
|
|
+ DBGV("shift NIC time\n");
|
|
+ getTime(&timeTmp, ptpClock);
|
|
+ timeTmp.seconds -= 2;
|
|
+ setTime(&timeTmp, ptpClock);
|
|
+ DBGV("shift NIC time done\n");
|
|
+#endif
|
|
+ return TRUE;
|
|
+ }
|
|
+
|
|
+ return FALSE;
|
|
+}
|
|
+
|
|
+Boolean initTime(PtpClock *ptpClock)
|
|
+{
|
|
+ switch(ptpClock->runTimeOpts.time) {
|
|
+ case TIME_SYSTEM:
|
|
+ return TRUE;
|
|
+ break;
|
|
+ case TIME_BOTH:
|
|
+ /* prepare clock servo for controlling system time */
|
|
+ timeBothClock = *ptpClock;
|
|
+ timeBothClock.runTimeOpts.time = TIME_SYSTEM;
|
|
+ timeBothClock.name = "sys ";
|
|
+ initClock(&timeBothClock);
|
|
+
|
|
+ /* default options for NIC synchronization */
|
|
+ ptpClock->runTimeOpts.noResetClock = DEFAULT_NO_RESET_CLOCK;
|
|
+ ptpClock->runTimeOpts.noAdjust = DEFAULT_NO_ADJUST_CLOCK;
|
|
+ ptpClock->runTimeOpts.s = DEFAULT_DELAY_S;
|
|
+ ptpClock->runTimeOpts.ap = DEFAULT_AP;
|
|
+ ptpClock->runTimeOpts.ai = DEFAULT_AI;
|
|
+
|
|
+ return initNICTime(TRUE, ptpClock);
|
|
+ break;
|
|
+ case TIME_SYSTEM_LINUX_HW:
|
|
+ case TIME_SYSTEM_LINUX_SW:
|
|
+ return selectNICTimeMode(TRUE, ptpClock);
|
|
+ break;
|
|
+ case TIME_NIC:
|
|
+ case TIME_SYSTEM_ASSISTED:
|
|
+ return initNICTime(TRUE, ptpClock);
|
|
+ break;
|
|
+ default:
|
|
+ ERROR("unsupported selection of time source\n");
|
|
+ return FALSE;
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+void getTime(TimeInternal *time, PtpClock *ptpClock)
|
|
+{
|
|
+ switch(ptpClock->runTimeOpts.time)
|
|
+ {
|
|
+ case TIME_SYSTEM_LINUX_HW:
|
|
+ case TIME_SYSTEM_LINUX_SW:
|
|
+ case TIME_SYSTEM_ASSISTED:
|
|
+ case TIME_SYSTEM: {
|
|
+ struct timeval tv;
|
|
+
|
|
+ gettimeofday(&tv, 0);
|
|
+ time->seconds = tv.tv_sec;
|
|
+ time->nanoseconds = tv.tv_usec*1000;
|
|
+ break;
|
|
+ }
|
|
+ case TIME_BOTH:
|
|
+ case TIME_NIC: {
|
|
+ struct E1000_TSYNC_SYSTIME_ARGU ts;
|
|
+
|
|
+ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts;
|
|
+ memset(&ts, 0, sizeof(ts));
|
|
+ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_SYSTIME_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ ERROR("could not read E1000 hardware time on %s: %s\n",
|
|
+ ptpClock->netPath.eventSockIFR.ifr_name,
|
|
+ strerror(errno));
|
|
+ return;
|
|
+ }
|
|
+ time->seconds = ts.time.seconds;
|
|
+ time->nanoseconds = ts.time.nanoseconds;
|
|
+
|
|
+ syncSystemWithNIC(ptpClock);
|
|
+ break;
|
|
+ }
|
|
+ default:
|
|
+ ERROR("unsupported selection of time source\n");
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+void setTime(TimeInternal *time, PtpClock *ptpClock)
|
|
+{
|
|
+ switch(ptpClock->runTimeOpts.time)
|
|
+ {
|
|
+ case TIME_SYSTEM_LINUX_HW:
|
|
+ case TIME_SYSTEM_LINUX_SW:
|
|
+ case TIME_SYSTEM_ASSISTED:
|
|
+ case TIME_SYSTEM: {
|
|
+ NOTIFY("resetting system clock to %ds %dns\n", time->seconds, time->nanoseconds);
|
|
+ struct timeval tv;
|
|
+ tv.tv_sec = time->seconds;
|
|
+ tv.tv_usec = time->nanoseconds/1000;
|
|
+ settimeofday(&tv, 0);
|
|
+ break;
|
|
+ }
|
|
+ case TIME_BOTH:
|
|
+ case TIME_NIC: {
|
|
+ struct E1000_TSYNC_SYSTIME_ARGU ts;
|
|
+ TimeInternal currentTime, offset;
|
|
+
|
|
+ NOTIFY("resetting NIC clock to %ds %dns\n", time->seconds, time->nanoseconds);
|
|
+ memset(&ts, 0, sizeof(ts));
|
|
+ getTime(¤tTime, ptpClock);
|
|
+ subTime(&offset, time, ¤tTime);
|
|
+ ts.negative_offset = (offset.seconds < 0 || offset.nanoseconds < 0) ? -1 : 1;
|
|
+ ts.time.seconds = ts.negative_offset * offset.seconds;
|
|
+ ts.time.nanoseconds = ts.negative_offset * offset.nanoseconds;
|
|
+ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts;
|
|
+ NOTIFY("adding NIC offset %s%ld.%09d (%ld/%p)\n",
|
|
+ ts.negative_offset < 0 ? "-" : "",
|
|
+ ts.time.seconds,
|
|
+ ts.time.nanoseconds,
|
|
+ sizeof(ts), &ts);
|
|
+ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_SYSTIME_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ ERROR("could not set E1000 hardware time on %s: %s\n",
|
|
+ ptpClock->netPath.eventSockIFR.ifr_name,
|
|
+ strerror(errno));
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ DBGV("new NIC time %ld.%09d\n",
|
|
+ ts.time.seconds,
|
|
+ ts.time.nanoseconds);
|
|
+ syncSystemWithNIC(ptpClock);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ default:
|
|
+ ERROR("unsupported selection of time source\n");
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+void adjTime(Integer32 adj, TimeInternal *offset, PtpClock *ptpClock)
|
|
+{
|
|
+ switch(ptpClock->runTimeOpts.time)
|
|
+ {
|
|
+ case TIME_SYSTEM_LINUX_HW:
|
|
+ case TIME_SYSTEM_LINUX_SW:
|
|
+ case TIME_SYSTEM_ASSISTED:
|
|
+ case TIME_SYSTEM: {
|
|
+ struct timex t;
|
|
+ static Boolean maxAdjValid;
|
|
+ static long maxAdj;
|
|
+ static long minTick, maxTick;
|
|
+ static long userHZ;
|
|
+ static long tickRes; /* USER_HZ * 1000 [ppb] */
|
|
+ long tickAdj;
|
|
+ long freqAdj;
|
|
+ int res;
|
|
+
|
|
+ if (!maxAdjValid) {
|
|
+ userHZ = sysconf(_SC_CLK_TCK);
|
|
+ t.modes = 0;
|
|
+ adjtimex(&t);
|
|
+ maxAdj = t.tolerance / ((1<<16)/1000);
|
|
+ tickRes = userHZ * 1000;
|
|
+ /* limits from the adjtimex command man page; could be determined via binary search */
|
|
+ minTick = (900000 - 1000000) / userHZ;
|
|
+ maxTick = (1100000 - 1000000) / userHZ;
|
|
+ maxAdjValid = TRUE;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * The Linux man page for the adjtimex() system call does not
|
|
+ * describe limits for frequency. The more recent man page for
|
|
+ * the adjtimex command on RH5 does and says that
|
|
+ * -tolerance <= frequency <= tolerance
|
|
+ * which was confirmed by trying out values just outside that interval.
|
|
+ *
|
|
+ * Note that this contradicts the comments for struct timex which say
|
|
+ * that freq and tolerance have different units (scaled ppm vs ppm).
|
|
+ *
|
|
+ * We follow the actual implementation on Linux 2.6.22 and do the
|
|
+ * range check after scaling.
|
|
+ */
|
|
+
|
|
+ t.modes = MOD_FREQUENCY|MOD_CLKB;
|
|
+ /*
|
|
+ * @todo
|
|
+ * Where is the official documentation for "scaled ppm"?
|
|
+ * Should this perhaps be adj * (1<<16) / 1000 (more accurate
|
|
+ * than multiplying by ((1<<16)/1000) == 65)?
|
|
+ */
|
|
+
|
|
+ /*
|
|
+ * 1 t.tick = 1 e-6 s * USER_HZ 1/s = 1 USER_HZ * 1000 ppb
|
|
+ *
|
|
+ * Large values of adj can be turned into t.tick adjustments:
|
|
+ * tickAdj t.tick = adj ppb / ( USER_HZ * 1000 ppb )
|
|
+ *
|
|
+ * Round this so that the error is as small is possible,
|
|
+ * because we need to fit that into t.freq.
|
|
+ */
|
|
+ freqAdj = adj;
|
|
+ tickAdj = 0;
|
|
+ if(freqAdj > maxAdj)
|
|
+ {
|
|
+ tickAdj = (adj - maxAdj + tickRes - 1) / tickRes;
|
|
+ if(tickAdj > maxTick)
|
|
+ tickAdj = maxTick;
|
|
+ freqAdj = adj - tickAdj * tickRes;
|
|
+ }
|
|
+ else if(freqAdj < -maxAdj)
|
|
+ {
|
|
+ tickAdj = -((-adj - maxAdj + tickRes - 1) / tickRes);
|
|
+ if(tickAdj < minTick)
|
|
+ tickAdj = minTick;
|
|
+ freqAdj = adj - tickAdj * tickRes;
|
|
+ }
|
|
+ if(freqAdj > maxAdj)
|
|
+ freqAdj = maxAdj;
|
|
+ else if(freqAdj < -maxAdj)
|
|
+ freqAdj = -maxAdj;
|
|
+
|
|
+ t.freq = freqAdj * ((1<<16)/1000);
|
|
+ t.tick = tickAdj + 1000000 / userHZ;
|
|
+ ptpClock->adj = tickAdj * tickRes + freqAdj;
|
|
+
|
|
+ INFO("requested adj %d ppb => adjust system frequency by %d scaled ppm (%d ppb) + %ld us/tick (%d ppb) = adj %d ppb (freq limit %ld/%ld ppm, tick limit %ld/%ld us*USER_HZ)\n",
|
|
+ adj,
|
|
+ t.freq, freqAdj,
|
|
+ t.tick - 1000000 / userHZ, tickAdj * tickRes,
|
|
+ ptpClock->adj,
|
|
+ -maxAdj, maxAdj,
|
|
+ minTick, maxTick);
|
|
+
|
|
+ res = adjtimex(&t);
|
|
+ switch (res) {
|
|
+ case -1:
|
|
+ ERROR("adjtimex(freq = %d) failed: %s\n",
|
|
+ t.freq, strerror(errno));
|
|
+ break;
|
|
+ case TIME_OK:
|
|
+ INFO(" -> TIME_OK\n");
|
|
+ break;
|
|
+ case TIME_INS:
|
|
+ ERROR("adjtimex -> insert leap second?!\n");
|
|
+ break;
|
|
+ case TIME_DEL:
|
|
+ ERROR("adjtimex -> delete leap second?!\n");
|
|
+ break;
|
|
+ case TIME_OOP:
|
|
+ ERROR("adjtimex -> leap second in progress?!\n");
|
|
+ break;
|
|
+ case TIME_WAIT:
|
|
+ ERROR("adjtimex -> leap second has occurred?!\n");
|
|
+ break;
|
|
+ case TIME_BAD:
|
|
+ ERROR("adjtimex -> time bad\n");
|
|
+ break;
|
|
+ default:
|
|
+ ERROR("adjtimex -> unknown result %d\n", res);
|
|
+ break;
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ case TIME_BOTH:
|
|
+ case TIME_NIC: {
|
|
+ if(offset)
|
|
+ {
|
|
+#if 0
|
|
+ struct E1000_TSYNC_SYSTIME_ARGU ts;
|
|
+ memset(&ts, 0, sizeof(ts));
|
|
+ // always store positive seconds/nanoseconds
|
|
+ ts.negative_offset = (offset->seconds < 0 || offset->nanoseconds < 0) ? -1 : 1;
|
|
+ ts.time.seconds = ts.negative_offset * offset->seconds;
|
|
+ ts.time.nanoseconds = ts.negative_offset * offset->nanoseconds;
|
|
+ // invert the sign: if offset is positive, we need to substract it and vice versa
|
|
+ ts.negative_offset *= -1;
|
|
+ DBGV("adjust NIC time by offset %s%lu.%09d (adj %d)\n",
|
|
+ ts.negative_offset < 0 ? "-" : "",
|
|
+ ts.time.seconds, ts.time.nanoseconds,
|
|
+ adj);
|
|
+ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts;
|
|
+ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_SYSTIME_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ ERROR("could not modify E1000 hardware time on %s: %s\n",
|
|
+ ptpClock->netPath.eventSockIFR.ifr_name,
|
|
+ strerror(errno));
|
|
+ }
|
|
+ else
|
|
+ syncSystemWithNIC(ptpClock);
|
|
+#else
|
|
+ // adjust NIC frequency
|
|
+ struct E1000_TSYNC_ADJTIME_ARGU ts;
|
|
+ memset(&ts, 0, sizeof(ts));
|
|
+ ts.adj = (long long)adj;
|
|
+ if(ptpClock->nic_instead_of_system)
|
|
+ ts.adj = -ts.adj;
|
|
+ ts.set_adj = TRUE;
|
|
+ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts;
|
|
+ DBGV("adjust NIC frequency by %d ppb\n", ts.adj);
|
|
+ ptpClock->adj = ts.adj;
|
|
+ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_ADJTIME_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ ERROR("could not modify E1000 hardware frequency on %s: %s\n",
|
|
+ ptpClock->netPath.eventSockIFR.ifr_name,
|
|
+ strerror(errno));
|
|
+ }
|
|
+ else
|
|
+ syncSystemWithNIC(ptpClock);
|
|
+#endif
|
|
+ }
|
|
+ else
|
|
+ syncSystemWithNIC(ptpClock);
|
|
+ break;
|
|
+ }
|
|
+ default:
|
|
+ ERROR("unsupported selection of time source\n");
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+
|
|
+void adjTimeOffset(TimeInternal *offset, PtpClock *ptpClock)
|
|
+{
|
|
+ switch(ptpClock->runTimeOpts.time)
|
|
+ {
|
|
+ case TIME_BOTH:
|
|
+ case TIME_NIC: {
|
|
+ struct E1000_TSYNC_SYSTIME_ARGU ts;
|
|
+ memset(&ts, 0, sizeof(ts));
|
|
+ // always store positive seconds/nanoseconds
|
|
+ ts.negative_offset = (offset->seconds < 0 || offset->nanoseconds < 0) ? -1 : 1;
|
|
+ ts.time.seconds = ts.negative_offset * offset->seconds;
|
|
+ ts.time.nanoseconds = ts.negative_offset * offset->nanoseconds;
|
|
+
|
|
+ // invert the sign: if offset is positive, we need to substract it and vice versa;
|
|
+ // when in nic_instead_of_system the logic is already inverted
|
|
+ if (!ptpClock->nic_instead_of_system)
|
|
+ ts.negative_offset *= -1;
|
|
+
|
|
+ DBGV("adjust NIC time by offset %s%lu.%09d\n",
|
|
+ ts.negative_offset < 0 ? "-" : "",
|
|
+ ts.time.seconds, ts.time.nanoseconds);
|
|
+ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts;
|
|
+ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_SYSTIME_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ ERROR("could not modify E1000 hardware time on %s: %s\n",
|
|
+ ptpClock->netPath.eventSockIFR.ifr_name,
|
|
+ strerror(errno));
|
|
+ }
|
|
+ else
|
|
+ syncSystemWithNIC(ptpClock);
|
|
+ break;
|
|
+ }
|
|
+ default: {
|
|
+ TimeInternal timeTmp;
|
|
+
|
|
+ getTime(&timeTmp, ptpClock);
|
|
+ subTime(&timeTmp, &timeTmp, offset);
|
|
+ setTime(&timeTmp, ptpClock);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static void getTimeStamps(PtpClock *ptpClock)
|
|
+{
|
|
+ struct E1000_TSYNC_READTS_ARGU ts;
|
|
+
|
|
+ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts;
|
|
+ memset(&ts, 0, sizeof(ts));
|
|
+ ts.withSystemTime = (ptpClock->runTimeOpts.time == TIME_SYSTEM_ASSISTED);
|
|
+ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_READTS_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ ERROR("could not read E1000 hardware time stamps on %s: %s\n",
|
|
+ ptpClock->netPath.eventSockIFR.ifr_name,
|
|
+ strerror(errno));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ DBGV("rx %s, tx %s\n",
|
|
+ ts.rx_valid ? "valid" : "invalid",
|
|
+ ts.tx_valid ? "valid" : "invalid");
|
|
+
|
|
+ if(ts.rx_valid)
|
|
+ {
|
|
+ int newIndex;
|
|
+
|
|
+ if(nextFreeRecv == RECV_ARRAY_SIZE)
|
|
+ {
|
|
+ newIndex = 0;
|
|
+ nextFreeRecv = 1;
|
|
+ oldestRecv = 2;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ newIndex = nextFreeRecv;
|
|
+ nextFreeRecv++;
|
|
+ if(oldestRecv && nextFreeRecv == oldestRecv)
|
|
+ ++oldestRecv;
|
|
+ }
|
|
+
|
|
+ if(oldestRecv >= RECV_ARRAY_SIZE)
|
|
+ oldestRecv = 0;
|
|
+
|
|
+ DBGV("new entry %d, oldest %d, next free %d\n", newIndex, oldestRecv, nextFreeRecv);
|
|
+
|
|
+ lastRecvTimes[newIndex].recvTimeStamp.seconds = ts.withSystemTime ? ts.rx_sys.seconds : ts.rx.seconds;
|
|
+ lastRecvTimes[newIndex].recvTimeStamp.nanoseconds = ts.withSystemTime ? ts.rx_sys.nanoseconds : ts.rx.nanoseconds;
|
|
+ lastRecvTimes[newIndex].sequenceId = ts.sourceSequenceId;
|
|
+ memcpy(lastRecvTimes[newIndex].sourceUuid, ts.sourceIdentity, sizeof(ts.sourceIdentity));
|
|
+
|
|
+ DBGV("rx %d: time %lu.%09u (%lu.%09u), sequence %u, uuid %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n",
|
|
+ newIndex,
|
|
+ lastRecvTimes[newIndex].recvTimeStamp.seconds,
|
|
+ lastRecvTimes[newIndex].recvTimeStamp.nanoseconds,
|
|
+ ts.withSystemTime ? ts.rx.seconds : 0,
|
|
+ ts.withSystemTime ? ts.rx.nanoseconds : 0,
|
|
+ lastRecvTimes[newIndex].sequenceId,
|
|
+ ts.sourceIdentity[0],
|
|
+ ts.sourceIdentity[1],
|
|
+ ts.sourceIdentity[2],
|
|
+ ts.sourceIdentity[3],
|
|
+ ts.sourceIdentity[4],
|
|
+ ts.sourceIdentity[5]);
|
|
+ }
|
|
+
|
|
+ if(ts.tx_valid)
|
|
+ {
|
|
+ lastSendTime.seconds = ts.withSystemTime ? ts.tx_sys.seconds : ts.tx.seconds;
|
|
+ lastSendTime.nanoseconds = ts.withSystemTime ? ts.tx_sys.nanoseconds : ts.tx.nanoseconds;
|
|
+
|
|
+ DBGV("tx time %lu.%09u (%lu.%09u)\n",
|
|
+ lastSendTime.seconds,
|
|
+ lastSendTime.nanoseconds,
|
|
+ ts.withSystemTime ? ts.tx.seconds : 0,
|
|
+ ts.withSystemTime ? ts.tx.nanoseconds : 0);
|
|
+ }
|
|
+}
|
|
+
|
|
+Boolean getSendTime(TimeInternal *sendTimeStamp,
|
|
+ PtpClock *ptpClock)
|
|
+{
|
|
+ /* check for new time stamps */
|
|
+ getTimeStamps(ptpClock);
|
|
+
|
|
+ if(lastSendTime.seconds || lastSendTime.nanoseconds)
|
|
+ {
|
|
+ *sendTimeStamp = lastSendTime;
|
|
+ lastSendTime.seconds = 0;
|
|
+ lastSendTime.nanoseconds = 0;
|
|
+ return TRUE;
|
|
+ }
|
|
+ else
|
|
+ return FALSE;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * helper function for getReceiveTime() which searches for time stamp
|
|
+ * in lastRecvTimes[leftIndex, rightIndex[
|
|
+ */
|
|
+static Boolean getReceiveTimeFromArray(TimeInternal *recvTimeStamp,
|
|
+ Octet sourceUuid[PTP_UUID_LENGTH],
|
|
+ UInteger16 sequenceId,
|
|
+ int leftIndex, int rightIndex)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ for(i = leftIndex; i < rightIndex; i++)
|
|
+ {
|
|
+ if(!memcmp(lastRecvTimes[i].sourceUuid, sourceUuid, sizeof(lastRecvTimes[i].sourceUuid)) &&
|
|
+ lastRecvTimes[i].sequenceId == sequenceId)
|
|
+ {
|
|
+ DBGV("found rx index %d: time %lu.%09u, sequence %u, uuid %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n",
|
|
+ i,
|
|
+ lastRecvTimes[i].recvTimeStamp.seconds,
|
|
+ lastRecvTimes[i].recvTimeStamp.nanoseconds,
|
|
+ lastRecvTimes[i].sequenceId,
|
|
+ lastRecvTimes[i].sourceUuid[0],
|
|
+ lastRecvTimes[i].sourceUuid[1],
|
|
+ lastRecvTimes[i].sourceUuid[2],
|
|
+ lastRecvTimes[i].sourceUuid[3],
|
|
+ lastRecvTimes[i].sourceUuid[4],
|
|
+ lastRecvTimes[i].sourceUuid[5]);
|
|
+ *recvTimeStamp = lastRecvTimes[i].recvTimeStamp;
|
|
+ // invalidate entry to prevent accidental reuse (happened when slaves were
|
|
+ // restarted quickly while the master still had their old sequence IDs in the array)
|
|
+ memset(&lastRecvTimes[i], 0, sizeof(lastRecvTimes[i]));
|
|
+ return TRUE;
|
|
+ }
|
|
+ }
|
|
+ return FALSE;
|
|
+}
|
|
+
|
|
+Boolean getReceiveTime(TimeInternal *recvTimeStamp,
|
|
+ Octet sourceUuid[PTP_UUID_LENGTH],
|
|
+ UInteger16 sequenceId,
|
|
+ PtpClock *ptpClock)
|
|
+{
|
|
+ /* check for new time stamps */
|
|
+ getTimeStamps(ptpClock);
|
|
+
|
|
+ if(oldestRecv <= nextFreeRecv)
|
|
+ return getReceiveTimeFromArray(recvTimeStamp, sourceUuid, sequenceId, oldestRecv, nextFreeRecv);
|
|
+ else
|
|
+ {
|
|
+ if(getReceiveTimeFromArray(recvTimeStamp, sourceUuid, sequenceId, oldestRecv, RECV_ARRAY_SIZE))
|
|
+ return TRUE;
|
|
+ else
|
|
+ return getReceiveTimeFromArray(recvTimeStamp, sourceUuid, sequenceId, 0, nextFreeRecv);
|
|
+ }
|
|
+}
|
|
+
|
|
+void timeNoActivity(PtpClock *ptpClock)
|
|
+{
|
|
+#ifdef PTPD_DBGV
|
|
+ switch(ptpClock->runTimeOpts.time) {
|
|
+ case TIME_NIC:
|
|
+ case TIME_BOTH:
|
|
+ case TIME_SYSTEM_ASSISTED:
|
|
+ {
|
|
+ TimeInternal now, ts, offset;
|
|
+ struct E1000_TSYNC_COMPARETS_ARGU argu;
|
|
+ int sign;
|
|
+
|
|
+ getTime(&ts, ptpClock);
|
|
+ timerNow(&now);
|
|
+ subTime(&offset, &now, &ts);
|
|
+ sign = (offset.seconds < 0 || offset.nanoseconds < 0) ? -1 : 1;
|
|
+ DBGV("system time %d.%09d, NIC time %d.%09d => system time - NIC time = %s%d.%09d\n",
|
|
+ now.seconds, now.nanoseconds,
|
|
+ ts.seconds, ts.nanoseconds,
|
|
+ sign < 0 ? "-" : "",
|
|
+ sign * offset.seconds, sign * offset.nanoseconds);
|
|
+
|
|
+ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&argu;
|
|
+ memset(&ts, 0, sizeof(ts));
|
|
+ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_COMPARETS_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) {
|
|
+ ERROR("could not correlate E1000 hardware and system time on %s: %s\n",
|
|
+ ptpClock->netPath.eventSockIFR.ifr_name,
|
|
+ strerror(errno));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ now.seconds = argu.systemToNICSign * argu.systemToNIC.seconds;
|
|
+ now.nanoseconds = argu.systemToNICSign * argu.systemToNIC.nanoseconds;
|
|
+ ts.seconds = argu.NICToSystemSign * argu.NICToSystem.seconds;
|
|
+ ts.nanoseconds = argu.NICToSystemSign * argu.NICToSystem.nanoseconds;
|
|
+ subTime(&offset, &now, &ts);
|
|
+ offset.seconds /= 2;
|
|
+ offset.nanoseconds /= 2;
|
|
+ DBGV("delay system to NIC %s%ld.%09d/NIC to system %s%ld.%09d => system - NIC time = %d.%09d\n",
|
|
+ argu.systemToNICSign > 0 ? "" : argu.systemToNICSign < 0 ? "-" : "?",
|
|
+ argu.systemToNIC.seconds, argu.systemToNIC.nanoseconds,
|
|
+ argu.NICToSystemSign > 0 ? "" : argu.NICToSystemSign < 0 ? "-" : "?",
|
|
+ argu.NICToSystem.seconds, argu.NICToSystem.nanoseconds,
|
|
+ offset.seconds, offset.nanoseconds);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+ syncSystemWithNIC(ptpClock);
|
|
+}
|
|
+
|
|
+void timeToState(UInteger8 state, PtpClock *ptpClock)
|
|
+{
|
|
+ if(ptpClock->runTimeOpts.time > TIME_SYSTEM &&
|
|
+ state != ptpClock->port_state)
|
|
+ {
|
|
+ if(state == PTP_MASTER)
|
|
+ /* only master listens for Delay_Req... */
|
|
+ selectNICTimeMode(FALSE, ptpClock);
|
|
+ else if(ptpClock->port_state == PTP_MASTER)
|
|
+ /** ... and only while he still is master */
|
|
+ selectNICTimeMode(TRUE, ptpClock);
|
|
+
|
|
+ timeBothClock.port_state = state;
|
|
+ }
|
|
+}
|
|
diff --git a/src/dep/timer.c b/src/dep/timer.c
|
|
index f9f4110..b40eb40 100644
|
|
--- a/src/dep/timer.c
|
|
+++ b/src/dep/timer.c
|
|
@@ -9,7 +9,13 @@ void catch_alarm(int sig)
|
|
{
|
|
elapsed += TIMER_INTERVAL;
|
|
|
|
- DBGV("catch_alarm: elapsed %d\n", elapsed);
|
|
+ /*
|
|
+ * DBGV() calls vsyslog() which doesn't seem to be reentrant:
|
|
+ * with Linux 2.6.23 and libc 2.5 (RH5) this even locked up the
|
|
+ * system.
|
|
+ *
|
|
+ * DBGV("catch_alarm: elapsed %d\n", elapsed);
|
|
+ */
|
|
}
|
|
|
|
void initTimer(void)
|
|
@@ -84,3 +90,28 @@ Boolean timerExpired(UInteger16 index, IntervalTimer *itimer)
|
|
return TRUE;
|
|
}
|
|
|
|
+Boolean nanoSleep(TimeInternal *t)
|
|
+{
|
|
+ struct timespec ts, tr;
|
|
+
|
|
+ ts.tv_sec = t->seconds;
|
|
+ ts.tv_nsec = t->nanoseconds;
|
|
+
|
|
+ if(nanosleep(&ts, &tr) < 0)
|
|
+ {
|
|
+ t->seconds = tr.tv_sec;
|
|
+ t->nanoseconds = tr.tv_nsec;
|
|
+ return FALSE;
|
|
+ }
|
|
+
|
|
+ return TRUE;
|
|
+}
|
|
+
|
|
+void timerNow(TimeInternal *time)
|
|
+{
|
|
+ struct timeval tv;
|
|
+
|
|
+ gettimeofday(&tv, 0);
|
|
+ time->seconds = tv.tv_sec;
|
|
+ time->nanoseconds = tv.tv_usec*1000;
|
|
+}
|
|
diff --git a/src/probe.c b/src/probe.c
|
|
index 870a25e..215019f 100644
|
|
--- a/src/probe.c
|
|
+++ b/src/probe.c
|
|
@@ -8,29 +8,29 @@ UInteger8 management_key_array[KEY_ARRAY_LEN] =
|
|
void displayHeader(MsgHeader*);
|
|
void displayManagement(MsgHeader*,MsgManagement*);
|
|
|
|
-void probe(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void probe(PtpClock *ptpClock)
|
|
{
|
|
UInteger16 i;
|
|
UInteger16 length;
|
|
- TimeInternal interval, now, finish, timestamp;
|
|
+ TimeInternal interval, now, finish;
|
|
|
|
/* check */
|
|
- if(rtOpts->probe_management_key == PTP_MM_UPDATE_DEFAULT_DATA_SET
|
|
- || rtOpts->probe_management_key == PTP_MM_UPDATE_GLOBAL_TIME_PROPERTIES
|
|
- || rtOpts->probe_management_key == PTP_MM_SET_SYNC_INTERVAL)
|
|
+ if(ptpClock->runTimeOpts.probe_management_key == PTP_MM_UPDATE_DEFAULT_DATA_SET
|
|
+ || ptpClock->runTimeOpts.probe_management_key == PTP_MM_UPDATE_GLOBAL_TIME_PROPERTIES
|
|
+ || ptpClock->runTimeOpts.probe_management_key == PTP_MM_SET_SYNC_INTERVAL)
|
|
{
|
|
ERROR("send not supported for that management message\n");
|
|
return;
|
|
}
|
|
|
|
/* init */
|
|
- if(!netInit(&ptpClock->netPath, rtOpts, ptpClock))
|
|
+ if(!netInit(ptpClock))
|
|
{
|
|
ERROR("failed to initialize network\n");
|
|
return;
|
|
}
|
|
|
|
- initData(rtOpts, ptpClock);
|
|
+ initData(ptpClock);
|
|
msgPackHeader(ptpClock->msgObuf, ptpClock);
|
|
|
|
memset(&ptpClock->msgTmp.manage, 0, sizeof(MsgManagement));
|
|
@@ -39,10 +39,10 @@ void probe(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
/* send */
|
|
for(i = 0; i < KEY_ARRAY_LEN; ++i)
|
|
{
|
|
- if(rtOpts->probe_management_key > 0)
|
|
+ if(ptpClock->runTimeOpts.probe_management_key > 0)
|
|
{
|
|
- ptpClock->msgTmp.manage.managementMessageKey = rtOpts->probe_management_key;
|
|
- ptpClock->msgTmp.manage.recordKey = rtOpts->probe_record_key;
|
|
+ ptpClock->msgTmp.manage.managementMessageKey = ptpClock->runTimeOpts.probe_management_key;
|
|
+ ptpClock->msgTmp.manage.recordKey = ptpClock->runTimeOpts.probe_record_key;
|
|
}
|
|
else
|
|
ptpClock->msgTmp.manage.managementMessageKey = management_key_array[i];
|
|
@@ -55,27 +55,27 @@ void probe(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
|
|
printf("\n(sending managementMessageKey %hhu)\n", ptpClock->msgTmp.manage.managementMessageKey);
|
|
|
|
- if(!netSendGeneral(ptpClock->msgObuf, length, &ptpClock->netPath))
|
|
+ if(!netSendGeneral(ptpClock->msgObuf, length, ptpClock))
|
|
{
|
|
ERROR("failed to send message\n");
|
|
return;
|
|
}
|
|
|
|
- if(rtOpts->probe_management_key > 0)
|
|
+ if(ptpClock->runTimeOpts.probe_management_key > 0)
|
|
break;
|
|
}
|
|
|
|
- getTime(&finish);
|
|
+ timerNow(&finish);
|
|
finish.seconds += PTP_SYNC_INTERVAL_TIMEOUT(ptpClock->sync_interval);
|
|
for(;;)
|
|
{
|
|
interval.seconds = PTP_SYNC_INTERVAL_TIMEOUT(ptpClock->sync_interval);
|
|
interval.nanoseconds = 0;
|
|
- netSelect(&interval, &ptpClock->netPath);
|
|
+ netSelect(&interval, ptpClock);
|
|
|
|
- netRecvEvent(ptpClock->msgIbuf, ×tamp, &ptpClock->netPath);
|
|
+ netRecvEvent(ptpClock->msgIbuf, NULL, ptpClock);
|
|
|
|
- if(netRecvGeneral(ptpClock->msgIbuf, &ptpClock->netPath))
|
|
+ if(netRecvGeneral(ptpClock->msgIbuf, ptpClock))
|
|
{
|
|
msgUnpackHeader(ptpClock->msgIbuf, &ptpClock->msgTmpHeader);
|
|
|
|
@@ -90,7 +90,7 @@ void probe(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
fflush(stdout);
|
|
}
|
|
|
|
- getTime(&now);
|
|
+ timerNow(&now);
|
|
if( now.seconds > finish.seconds || (now.seconds == finish.seconds
|
|
&& now.nanoseconds > finish.nanoseconds) )
|
|
break;
|
|
diff --git a/src/protocol.c b/src/protocol.c
|
|
index 8bde2ce..0a53b82 100644
|
|
--- a/src/protocol.c
|
|
+++ b/src/protocol.c
|
|
@@ -2,22 +2,22 @@
|
|
|
|
#include "ptpd.h"
|
|
|
|
-Boolean doInit(RunTimeOpts*,PtpClock*);
|
|
-void doState(RunTimeOpts*,PtpClock*);
|
|
-void toState(UInteger8,RunTimeOpts*,PtpClock*);
|
|
-
|
|
-void handle(RunTimeOpts*,PtpClock*);
|
|
-void handleSync(MsgHeader*,Octet*,ssize_t,TimeInternal*,Boolean,RunTimeOpts*,PtpClock*);
|
|
-void handleFollowUp(MsgHeader*,Octet*,ssize_t,Boolean,RunTimeOpts*,PtpClock*);
|
|
-void handleDelayReq(MsgHeader*,Octet*,ssize_t,TimeInternal*,Boolean,RunTimeOpts*,PtpClock*);
|
|
-void handleDelayResp(MsgHeader*,Octet*,ssize_t,Boolean,RunTimeOpts*,PtpClock*);
|
|
-void handleManagement(MsgHeader*,Octet*,ssize_t,Boolean,RunTimeOpts*,PtpClock*);
|
|
-
|
|
-void issueSync(RunTimeOpts*,PtpClock*);
|
|
-void issueFollowup(TimeInternal*,RunTimeOpts*,PtpClock*);
|
|
-void issueDelayReq(RunTimeOpts*,PtpClock*);
|
|
-void issueDelayResp(TimeInternal*,MsgHeader*,RunTimeOpts*,PtpClock*);
|
|
-void issueManagement(MsgHeader*,MsgManagement*,RunTimeOpts*,PtpClock*);
|
|
+Boolean doInit(PtpClock*);
|
|
+void doState(PtpClock*);
|
|
+void toState(UInteger8,PtpClock*);
|
|
+
|
|
+void handle(PtpClock*);
|
|
+void handleSync(MsgHeader*,Octet*,ssize_t,TimeInternal*,Boolean,Boolean,PtpClock*);
|
|
+void handleFollowUp(MsgHeader*,Octet*,ssize_t,Boolean,PtpClock*);
|
|
+void handleDelayReq(MsgHeader*,Octet*,ssize_t,TimeInternal*,Boolean,Boolean,PtpClock*);
|
|
+void handleDelayResp(MsgHeader*,Octet*,ssize_t,Boolean,PtpClock*);
|
|
+void handleManagement(MsgHeader*,Octet*,ssize_t,Boolean,PtpClock*);
|
|
+
|
|
+void issueSync(PtpClock*);
|
|
+void issueFollowup(TimeInternal*,PtpClock*);
|
|
+void issueDelayReq(PtpClock*);
|
|
+void issueDelayResp(TimeInternal*,MsgHeader*,PtpClock*);
|
|
+void issueManagement(MsgHeader*,MsgManagement*,PtpClock*);
|
|
|
|
MsgSync * addForeign(Octet*,MsgHeader*,PtpClock*);
|
|
|
|
@@ -26,43 +26,70 @@ MsgSync * addForeign(Octet*,MsgHeader*,PtpClock*);
|
|
checked for 'port_state'. the actions and events may or may not change
|
|
'port_state' by calling toState(), but once they are done we loop around
|
|
again and perform the actions required for the new 'port_state'. */
|
|
-void protocol(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void protocol(PtpClock *ptpClock)
|
|
{
|
|
DBG("event POWERUP\n");
|
|
|
|
- toState(PTP_INITIALIZING, rtOpts, ptpClock);
|
|
+ toState(PTP_INITIALIZING, ptpClock);
|
|
|
|
for(;;)
|
|
{
|
|
if(ptpClock->port_state != PTP_INITIALIZING)
|
|
- doState(rtOpts, ptpClock);
|
|
- else if(!doInit(rtOpts, ptpClock))
|
|
+ doState(ptpClock);
|
|
+ else if(!doInit(ptpClock))
|
|
return;
|
|
|
|
if(ptpClock->message_activity)
|
|
DBGV("activity\n");
|
|
else
|
|
+ {
|
|
DBGV("no activity\n");
|
|
+ timeNoActivity(ptpClock);
|
|
+ }
|
|
}
|
|
}
|
|
|
|
-Boolean doInit(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+Boolean doInit(PtpClock *ptpClock)
|
|
{
|
|
DBG("manufacturerIdentity: %s\n", MANUFACTURER_ID);
|
|
|
|
/* initialize networking */
|
|
- netShutdown(&ptpClock->netPath);
|
|
- if(!netInit(&ptpClock->netPath, rtOpts, ptpClock))
|
|
+ netShutdown(ptpClock);
|
|
+ if(!netInit(ptpClock))
|
|
{
|
|
ERROR("failed to initialize network\n");
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
return FALSE;
|
|
}
|
|
-
|
|
+
|
|
+ /* initialize timing, may fail e.g. if timer depends on hardware */
|
|
+ if(!initTime(ptpClock))
|
|
+ {
|
|
+ ERROR("failed to initialize timing\n");
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
+ return FALSE;
|
|
+ }
|
|
+
|
|
+ switch (ptpClock->runTimeOpts.time) {
|
|
+ case TIME_SYSTEM:
|
|
+ case TIME_SYSTEM_LINUX_HW:
|
|
+ case TIME_SYSTEM_LINUX_SW:
|
|
+ /*
|
|
+ * send time stamp will be returned to socket when available,
|
|
+ * either via IP_MULTICAST_LOOP or SIOCSHWTSTAMP + error queue
|
|
+ */
|
|
+ ptpClock->delayedTiming = FALSE;
|
|
+ break;
|
|
+ default:
|
|
+ /* ask for time stamp shortly after sending */
|
|
+ ptpClock->delayedTiming = TRUE;
|
|
+ break;
|
|
+ }
|
|
+
|
|
/* initialize other stuff */
|
|
- initData(rtOpts, ptpClock);
|
|
+ initData(ptpClock);
|
|
initTimer();
|
|
- initClock(rtOpts, ptpClock);
|
|
+ initClock(ptpClock);
|
|
m1(ptpClock);
|
|
msgPackHeader(ptpClock->msgObuf, ptpClock);
|
|
|
|
@@ -71,7 +98,7 @@ Boolean doInit(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
DBG("256*log2(clock variance): %d\n", ptpClock->clock_variance);
|
|
DBG("clock stratum: %d\n", ptpClock->clock_stratum);
|
|
DBG("clock preferred?: %s\n", ptpClock->preferred?"yes":"no");
|
|
- DBG("bound interface name: %s\n", rtOpts->ifaceName);
|
|
+ DBG("bound interface name: %s\n", ptpClock->runTimeOpts.ifaceName);
|
|
DBG("communication technology: %d\n", ptpClock->port_communication_technology);
|
|
DBG("uuid: %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n",
|
|
ptpClock->port_uuid_field[0], ptpClock->port_uuid_field[1], ptpClock->port_uuid_field[2],
|
|
@@ -85,12 +112,12 @@ Boolean doInit(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
DBG("general port address: %hhx %hhx\n",
|
|
ptpClock->general_port_address[0], ptpClock->general_port_address[1]);
|
|
|
|
- toState(PTP_LISTENING, rtOpts, ptpClock);
|
|
+ toState(PTP_LISTENING, ptpClock);
|
|
return TRUE;
|
|
}
|
|
|
|
/* handle actions and events for 'port_state' */
|
|
-void doState(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void doState(PtpClock *ptpClock)
|
|
{
|
|
UInteger8 state;
|
|
|
|
@@ -105,9 +132,9 @@ void doState(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
if(ptpClock->record_update)
|
|
{
|
|
ptpClock->record_update = FALSE;
|
|
- state = bmc(ptpClock->foreign, rtOpts, ptpClock);
|
|
+ state = bmc(ptpClock->foreign, ptpClock);
|
|
if(state != ptpClock->port_state)
|
|
- toState(state, rtOpts, ptpClock);
|
|
+ toState(state, ptpClock);
|
|
}
|
|
break;
|
|
|
|
@@ -121,27 +148,27 @@ void doState(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
/* imaginary troubleshooting */
|
|
|
|
DBG("event FAULT_CLEARED\n");
|
|
- toState(PTP_INITIALIZING, rtOpts, ptpClock);
|
|
+ toState(PTP_INITIALIZING, ptpClock);
|
|
return;
|
|
|
|
case PTP_LISTENING:
|
|
case PTP_PASSIVE:
|
|
case PTP_UNCALIBRATED:
|
|
case PTP_SLAVE:
|
|
- handle(rtOpts, ptpClock);
|
|
+ handle(ptpClock);
|
|
|
|
if(timerExpired(SYNC_RECEIPT_TIMER, ptpClock->itimer))
|
|
{
|
|
DBG("event SYNC_RECEIPT_TIMEOUT_EXPIRES\n");
|
|
ptpClock->number_foreign_records = 0;
|
|
ptpClock->foreign_record_i = 0;
|
|
- if(!rtOpts->slaveOnly && ptpClock->clock_stratum != 255)
|
|
+ if(!ptpClock->runTimeOpts.slaveOnly && ptpClock->clock_stratum != 255)
|
|
{
|
|
m1(ptpClock);
|
|
- toState(PTP_MASTER, rtOpts, ptpClock);
|
|
+ toState(PTP_MASTER, ptpClock);
|
|
}
|
|
else if(ptpClock->port_state != PTP_LISTENING)
|
|
- toState(PTP_LISTENING, rtOpts, ptpClock);
|
|
+ toState(PTP_LISTENING, ptpClock);
|
|
}
|
|
|
|
break;
|
|
@@ -150,18 +177,18 @@ void doState(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
if(timerExpired(SYNC_INTERVAL_TIMER, ptpClock->itimer))
|
|
{
|
|
DBGV("event SYNC_INTERVAL_TIMEOUT_EXPIRES\n");
|
|
- issueSync(rtOpts, ptpClock);
|
|
+ issueSync(ptpClock);
|
|
}
|
|
|
|
- handle(rtOpts, ptpClock);
|
|
+ handle(ptpClock);
|
|
|
|
- if(rtOpts->slaveOnly || ptpClock->clock_stratum == 255)
|
|
- toState(PTP_LISTENING, rtOpts, ptpClock);
|
|
+ if(ptpClock->runTimeOpts.slaveOnly || ptpClock->clock_stratum == 255)
|
|
+ toState(PTP_LISTENING, ptpClock);
|
|
|
|
break;
|
|
|
|
case PTP_DISABLED:
|
|
- handle(rtOpts, ptpClock);
|
|
+ handle(ptpClock);
|
|
break;
|
|
|
|
default:
|
|
@@ -171,10 +198,10 @@ void doState(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
}
|
|
|
|
/* perform actions required when leaving 'port_state' and entering 'state' */
|
|
-void toState(UInteger8 state, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void toState(UInteger8 state, PtpClock *ptpClock)
|
|
{
|
|
ptpClock->message_activity = TRUE;
|
|
-
|
|
+
|
|
/* leaving state tasks */
|
|
switch(ptpClock->port_state)
|
|
{
|
|
@@ -184,12 +211,14 @@ void toState(UInteger8 state, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
break;
|
|
|
|
case PTP_SLAVE:
|
|
- initClock(rtOpts, ptpClock);
|
|
+ initClock(ptpClock);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
+
|
|
+ timeToState(state, ptpClock);
|
|
|
|
/* entering state tasks */
|
|
switch(state)
|
|
@@ -247,7 +276,7 @@ void toState(UInteger8 state, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
case PTP_SLAVE:
|
|
DBG("state PTP_PTP_SLAVE\n");
|
|
|
|
- initClock(rtOpts, ptpClock);
|
|
+ initClock(ptpClock);
|
|
|
|
/* R is chosen to allow a few syncs before we first get a one-way delay estimate */
|
|
/* this is to allow the offset filter to fill for an accurate initial clock reset */
|
|
@@ -271,25 +300,27 @@ void toState(UInteger8 state, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
break;
|
|
}
|
|
|
|
- if(rtOpts->displayStats)
|
|
- displayStats(rtOpts, ptpClock);
|
|
+ if(ptpClock->runTimeOpts.displayStats)
|
|
+ displayStats(ptpClock);
|
|
}
|
|
|
|
/* check and handle received messages */
|
|
-void handle(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void handle(PtpClock *ptpClock)
|
|
{
|
|
int ret;
|
|
ssize_t length;
|
|
Boolean isFromSelf;
|
|
+ Boolean isEvent;
|
|
+ Boolean badTime = FALSE;
|
|
TimeInternal time = { 0, 0 };
|
|
|
|
if(!ptpClock->message_activity)
|
|
{
|
|
- ret = netSelect(0, &ptpClock->netPath);
|
|
+ ret = netSelect(0, ptpClock);
|
|
if(ret < 0)
|
|
{
|
|
PERROR("failed to poll sockets");
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
return;
|
|
}
|
|
else if(!ret)
|
|
@@ -301,21 +332,25 @@ void handle(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
}
|
|
|
|
DBGV("handle: something\n");
|
|
-
|
|
- length = netRecvEvent(ptpClock->msgIbuf, &time, &ptpClock->netPath);
|
|
+
|
|
+ isEvent = TRUE;
|
|
+ length = netRecvEvent(ptpClock->msgIbuf,
|
|
+ ptpClock->delayedTiming ? NULL : &time,
|
|
+ ptpClock);
|
|
if(length < 0)
|
|
{
|
|
PERROR("failed to receive on the event socket");
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
return;
|
|
}
|
|
else if(!length)
|
|
{
|
|
- length = netRecvGeneral(ptpClock->msgIbuf, &ptpClock->netPath);
|
|
+ isEvent = FALSE;
|
|
+ length = netRecvGeneral(ptpClock->msgIbuf, ptpClock);
|
|
if(length < 0)
|
|
{
|
|
PERROR("failed to receive on the general socket");
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
return;
|
|
}
|
|
else if(!length)
|
|
@@ -330,18 +365,36 @@ void handle(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
if(length < HEADER_LENGTH)
|
|
{
|
|
ERROR("message shorter than header length\n");
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
return;
|
|
}
|
|
|
|
msgUnpackHeader(ptpClock->msgIbuf, &ptpClock->msgTmpHeader);
|
|
+
|
|
+ if(isEvent && ptpClock->delayedTiming)
|
|
+ {
|
|
+ /* query hardware for matching receive time stamp */
|
|
+ if(!getReceiveTime(&time, ptpClock->msgTmpHeader.sourceUuid, ptpClock->msgTmpHeader.sequenceId, ptpClock))
|
|
+ {
|
|
+ /*
|
|
+ * Incoming packets without hardware time stamp cannot be ignored outright because
|
|
+ * a master might only be able to time stamp DelayReq packets; ignoring the Sync
|
|
+ * packets from another, better clock would break the clock selection protocol.
|
|
+ * Therefore set system time as fallback and decide below what to do.
|
|
+ */
|
|
+ DBGV("*** message with no time stamp ***\n");
|
|
+ getTime(&time, ptpClock);
|
|
+ badTime = TRUE;
|
|
+ }
|
|
+ }
|
|
|
|
- DBGV("event Receipt of Message\n"
|
|
+ DBGV("%s Receipt of Message\n"
|
|
" version %d\n"
|
|
" type %d\n"
|
|
" uuid %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n"
|
|
" sequence %d\n"
|
|
" time %us %dns\n",
|
|
+ isEvent ? "event" : "control",
|
|
ptpClock->msgTmpHeader.versionPTP,
|
|
ptpClock->msgTmpHeader.control,
|
|
ptpClock->msgTmpHeader.sourceUuid[0], ptpClock->msgTmpHeader.sourceUuid[1],
|
|
@@ -370,28 +423,28 @@ void handle(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
/* subtract the inbound latency adjustment if it is not a loop back and the
|
|
time stamp seems reasonable */
|
|
if(!isFromSelf && time.seconds > 0)
|
|
- subTime(&time, &time, &rtOpts->inboundLatency);
|
|
+ subTime(&time, &time, &ptpClock->runTimeOpts.inboundLatency);
|
|
|
|
switch(ptpClock->msgTmpHeader.control)
|
|
{
|
|
case PTP_SYNC_MESSAGE:
|
|
- handleSync(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, &time, isFromSelf, rtOpts, ptpClock);
|
|
+ handleSync(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, &time, badTime, isFromSelf, ptpClock);
|
|
break;
|
|
|
|
case PTP_FOLLOWUP_MESSAGE:
|
|
- handleFollowUp(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, rtOpts, ptpClock);
|
|
+ handleFollowUp(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, ptpClock);
|
|
break;
|
|
|
|
case PTP_DELAY_REQ_MESSAGE:
|
|
- handleDelayReq(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, &time, isFromSelf, rtOpts, ptpClock);
|
|
+ handleDelayReq(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, &time, badTime, isFromSelf, ptpClock);
|
|
break;
|
|
|
|
case PTP_DELAY_RESP_MESSAGE:
|
|
- handleDelayResp(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, rtOpts, ptpClock);
|
|
+ handleDelayResp(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, ptpClock);
|
|
break;
|
|
|
|
case PTP_MANAGEMENT_MESSAGE:
|
|
- handleManagement(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, rtOpts, ptpClock);
|
|
+ handleManagement(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, ptpClock);
|
|
break;
|
|
|
|
default:
|
|
@@ -400,7 +453,7 @@ void handle(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
}
|
|
}
|
|
|
|
-void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal *time, Boolean isFromSelf, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal *time, Boolean badTime, Boolean isFromSelf, PtpClock *ptpClock)
|
|
{
|
|
MsgSync *sync;
|
|
TimeInternal originTimestamp;
|
|
@@ -408,7 +461,7 @@ void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal
|
|
if(length < SYNC_PACKET_LENGTH)
|
|
{
|
|
ERROR("short sync message\n");
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
return;
|
|
}
|
|
|
|
@@ -450,6 +503,11 @@ void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal
|
|
/* spec recommends handling a sync interval discrepancy as a fault */
|
|
}
|
|
|
|
+ /*
|
|
+ * TODO: Sync packets without hardware time stamp are rare, but might happen.
|
|
+ * Need to decide what to do with the bad default time stamp, similar to handleDelayReq().
|
|
+ */
|
|
+
|
|
ptpClock->sync_receive_time.seconds = time->seconds;
|
|
ptpClock->sync_receive_time.nanoseconds = time->nanoseconds;
|
|
|
|
@@ -459,8 +517,8 @@ void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal
|
|
|
|
toInternalTime(&originTimestamp, &sync->originTimestamp, &ptpClock->halfEpoch);
|
|
updateOffset(&originTimestamp, &ptpClock->sync_receive_time,
|
|
- &ptpClock->ofm_filt, rtOpts, ptpClock);
|
|
- updateClock(rtOpts, ptpClock);
|
|
+ &ptpClock->ofm_filt, ptpClock);
|
|
+ updateClock(ptpClock);
|
|
}
|
|
else
|
|
{
|
|
@@ -471,7 +529,7 @@ void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal
|
|
|
|
if(!(--ptpClock->R))
|
|
{
|
|
- issueDelayReq(rtOpts, ptpClock);
|
|
+ issueDelayReq(ptpClock);
|
|
|
|
ptpClock->Q = 0;
|
|
ptpClock->R = getRand(&ptpClock->random_seed)%(PTP_DELAY_REQ_INTERVAL - 2) + 2;
|
|
@@ -499,15 +557,15 @@ void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal
|
|
}
|
|
else if(ptpClock->port_state == PTP_MASTER && ptpClock->clock_followup_capable)
|
|
{
|
|
- addTime(time, time, &rtOpts->outboundLatency);
|
|
- issueFollowup(time, rtOpts, ptpClock);
|
|
+ addTime(time, time, &ptpClock->runTimeOpts.outboundLatency);
|
|
+ issueFollowup(time, ptpClock);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
-void handleFollowUp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void handleFollowUp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, PtpClock *ptpClock)
|
|
{
|
|
MsgFollowUp *follow;
|
|
TimeInternal preciseOriginTimestamp;
|
|
@@ -515,7 +573,7 @@ void handleFollowUp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean i
|
|
if(length < FOLLOW_UP_PACKET_LENGTH)
|
|
{
|
|
ERROR("short folow up message\n");
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
return;
|
|
}
|
|
|
|
@@ -548,8 +606,8 @@ void handleFollowUp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean i
|
|
|
|
toInternalTime(&preciseOriginTimestamp, &follow->preciseOriginTimestamp, &ptpClock->halfEpoch);
|
|
updateOffset(&preciseOriginTimestamp, &ptpClock->sync_receive_time,
|
|
- &ptpClock->ofm_filt, rtOpts, ptpClock);
|
|
- updateClock(rtOpts, ptpClock);
|
|
+ &ptpClock->ofm_filt, ptpClock);
|
|
+ updateClock(ptpClock);
|
|
}
|
|
else
|
|
{
|
|
@@ -563,12 +621,12 @@ void handleFollowUp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean i
|
|
}
|
|
}
|
|
|
|
-void handleDelayReq(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal *time, Boolean isFromSelf, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void handleDelayReq(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal *time, Boolean badTime, Boolean isFromSelf, PtpClock *ptpClock)
|
|
{
|
|
if(length < DELAY_REQ_PACKET_LENGTH)
|
|
{
|
|
ERROR("short delay request message\n");
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
return;
|
|
}
|
|
|
|
@@ -585,7 +643,10 @@ void handleDelayReq(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInter
|
|
|| header->sourceCommunicationTechnology == PTP_DEFAULT
|
|
|| ptpClock->clock_communication_technology == PTP_DEFAULT )
|
|
{
|
|
- issueDelayResp(time, &ptpClock->msgTmpHeader, rtOpts, ptpClock);
|
|
+ if( badTime )
|
|
+ NOTIFY("avoid inaccurate DelayResp because of bad time stamp\n");
|
|
+ else
|
|
+ issueDelayResp(time, &ptpClock->msgTmpHeader, ptpClock);
|
|
}
|
|
|
|
break;
|
|
@@ -598,12 +659,12 @@ void handleDelayReq(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInter
|
|
ptpClock->delay_req_send_time.seconds = time->seconds;
|
|
ptpClock->delay_req_send_time.nanoseconds = time->nanoseconds;
|
|
|
|
- addTime(&ptpClock->delay_req_send_time, &ptpClock->delay_req_send_time, &rtOpts->outboundLatency);
|
|
+ addTime(&ptpClock->delay_req_send_time, &ptpClock->delay_req_send_time, &ptpClock->runTimeOpts.outboundLatency);
|
|
|
|
if(ptpClock->delay_req_receive_time.seconds)
|
|
{
|
|
updateDelay(&ptpClock->delay_req_send_time, &ptpClock->delay_req_receive_time,
|
|
- &ptpClock->owd_filt, rtOpts, ptpClock);
|
|
+ &ptpClock->owd_filt, ptpClock);
|
|
|
|
ptpClock->delay_req_send_time.seconds = 0;
|
|
ptpClock->delay_req_send_time.nanoseconds = 0;
|
|
@@ -619,14 +680,14 @@ void handleDelayReq(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInter
|
|
}
|
|
}
|
|
|
|
-void handleDelayResp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void handleDelayResp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, PtpClock *ptpClock)
|
|
{
|
|
MsgDelayResp *resp;
|
|
|
|
if(length < DELAY_RESP_PACKET_LENGTH)
|
|
{
|
|
ERROR("short delay request message\n");
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
return;
|
|
}
|
|
|
|
@@ -658,7 +719,7 @@ void handleDelayResp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean
|
|
if(ptpClock->delay_req_send_time.seconds)
|
|
{
|
|
updateDelay(&ptpClock->delay_req_send_time, &ptpClock->delay_req_receive_time,
|
|
- &ptpClock->owd_filt, rtOpts, ptpClock);
|
|
+ &ptpClock->owd_filt, ptpClock);
|
|
|
|
ptpClock->delay_req_send_time.seconds = 0;
|
|
ptpClock->delay_req_send_time.nanoseconds = 0;
|
|
@@ -678,7 +739,7 @@ void handleDelayResp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean
|
|
}
|
|
}
|
|
|
|
-void handleManagement(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void handleManagement(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, PtpClock *ptpClock)
|
|
{
|
|
MsgManagement *manage;
|
|
|
|
@@ -705,14 +766,14 @@ void handleManagement(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean
|
|
case PTP_MM_GET_PORT_DATA_SET:
|
|
case PTP_MM_GET_GLOBAL_TIME_DATA_SET:
|
|
case PTP_MM_GET_FOREIGN_DATA_SET:
|
|
- issueManagement(header, manage, rtOpts, ptpClock);
|
|
+ issueManagement(header, manage, ptpClock);
|
|
break;
|
|
|
|
default:
|
|
ptpClock->record_update = TRUE;
|
|
- state = msgUnloadManagement(ptpClock->msgIbuf, manage, ptpClock, rtOpts);
|
|
+ state = msgUnloadManagement(ptpClock->msgIbuf, manage, ptpClock);
|
|
if(state != ptpClock->port_state)
|
|
- toState(state, rtOpts, ptpClock);
|
|
+ toState(state, ptpClock);
|
|
break;
|
|
}
|
|
}
|
|
@@ -723,25 +784,40 @@ void handleManagement(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean
|
|
}
|
|
|
|
/* pack and send various messages */
|
|
-void issueSync(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void issueSync(PtpClock *ptpClock)
|
|
{
|
|
TimeInternal internalTime;
|
|
TimeRepresentation originTimestamp;
|
|
|
|
++ptpClock->last_sync_event_sequence_number;
|
|
ptpClock->grandmaster_sequence_number = ptpClock->last_sync_event_sequence_number;
|
|
-
|
|
- getTime(&internalTime);
|
|
+
|
|
+ /* try to predict outgoing time stamp */
|
|
+ getTime(&internalTime, ptpClock);
|
|
fromInternalTime(&internalTime, &originTimestamp, ptpClock->halfEpoch);
|
|
- msgPackSync(ptpClock->msgObuf, FALSE, &originTimestamp, ptpClock);
|
|
+ msgPackSync(ptpClock->msgObuf, FALSE, TRUE, &originTimestamp, ptpClock);
|
|
|
|
- if(!netSendEvent(ptpClock->msgObuf, SYNC_PACKET_LENGTH, &ptpClock->netPath))
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ if(!netSendEvent(ptpClock->msgObuf, SYNC_PACKET_LENGTH,
|
|
+ ptpClock->delayedTiming ? &internalTime : NULL,
|
|
+ ptpClock))
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
else
|
|
+ {
|
|
DBGV("sent sync message\n");
|
|
+ if(ptpClock->delayedTiming)
|
|
+ {
|
|
+ if (internalTime.seconds || internalTime.nanoseconds) {
|
|
+ /* compensate with configurable latency, then tell client real time stamp */
|
|
+ addTime(&internalTime, &internalTime, &ptpClock->runTimeOpts.outboundLatency);
|
|
+ issueFollowup(&internalTime, ptpClock);
|
|
+ } else {
|
|
+ NOTIFY("WARNING: sync message without hardware time stamp, skipped followup\n");
|
|
+ }
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
-void issueFollowup(TimeInternal *time, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void issueFollowup(TimeInternal *time, PtpClock *ptpClock)
|
|
{
|
|
TimeRepresentation preciseOriginTimestamp;
|
|
|
|
@@ -750,46 +826,62 @@ void issueFollowup(TimeInternal *time, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
fromInternalTime(time, &preciseOriginTimestamp, ptpClock->halfEpoch);
|
|
msgPackFollowUp(ptpClock->msgObuf, ptpClock->last_sync_event_sequence_number, &preciseOriginTimestamp, ptpClock);
|
|
|
|
- if(!netSendGeneral(ptpClock->msgObuf, FOLLOW_UP_PACKET_LENGTH, &ptpClock->netPath))
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ if(!netSendGeneral(ptpClock->msgObuf, FOLLOW_UP_PACKET_LENGTH, ptpClock))
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
else
|
|
DBGV("sent followup message\n");
|
|
}
|
|
|
|
-void issueDelayReq(RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void issueDelayReq(PtpClock *ptpClock)
|
|
{
|
|
TimeInternal internalTime;
|
|
TimeRepresentation originTimestamp;
|
|
|
|
ptpClock->sentDelayReq = TRUE;
|
|
ptpClock->sentDelayReqSequenceId = ++ptpClock->last_sync_event_sequence_number;
|
|
-
|
|
- getTime(&internalTime);
|
|
+
|
|
+ /* try to predict outgoing time stamp */
|
|
+ getTime(&internalTime, ptpClock);
|
|
fromInternalTime(&internalTime, &originTimestamp, ptpClock->halfEpoch);
|
|
- msgPackDelayReq(ptpClock->msgObuf, FALSE, &originTimestamp, ptpClock);
|
|
+ msgPackDelayReq(ptpClock->msgObuf, FALSE, FALSE, &originTimestamp, ptpClock);
|
|
|
|
- if(!netSendEvent(ptpClock->msgObuf, DELAY_REQ_PACKET_LENGTH, &ptpClock->netPath))
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ if(!netSendEvent(ptpClock->msgObuf, DELAY_REQ_PACKET_LENGTH,
|
|
+ ptpClock->delayedTiming ? &internalTime : NULL,
|
|
+ ptpClock))
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
else
|
|
+ {
|
|
DBGV("sent delay request message\n");
|
|
+ if(ptpClock->delayedTiming)
|
|
+ {
|
|
+ if (internalTime.seconds || internalTime.nanoseconds) {
|
|
+ /* compensate with configurable latency, then store for later use */
|
|
+ addTime(&internalTime, &internalTime, &ptpClock->runTimeOpts.outboundLatency);
|
|
+ ptpClock->delay_req_send_time = internalTime;
|
|
+ } else {
|
|
+ NOTIFY("WARNING: delay request message without hardware time stamp, will skip response\n");
|
|
+ ptpClock->sentDelayReq = FALSE;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
-void issueDelayResp(TimeInternal *time, MsgHeader *header, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void issueDelayResp(TimeInternal *time, MsgHeader *header, PtpClock *ptpClock)
|
|
{
|
|
TimeRepresentation delayReceiptTimestamp;
|
|
|
|
++ptpClock->last_general_event_sequence_number;
|
|
-
|
|
+
|
|
fromInternalTime(time, &delayReceiptTimestamp, ptpClock->halfEpoch);
|
|
msgPackDelayResp(ptpClock->msgObuf, header, &delayReceiptTimestamp, ptpClock);
|
|
|
|
- if(!netSendGeneral(ptpClock->msgObuf, DELAY_RESP_PACKET_LENGTH, &ptpClock->netPath))
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ if(!netSendGeneral(ptpClock->msgObuf, DELAY_RESP_PACKET_LENGTH, ptpClock))
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
else
|
|
DBGV("sent delay response message\n");
|
|
}
|
|
|
|
-void issueManagement(MsgHeader *header, MsgManagement *manage, RunTimeOpts *rtOpts, PtpClock *ptpClock)
|
|
+void issueManagement(MsgHeader *header, MsgManagement *manage, PtpClock *ptpClock)
|
|
{
|
|
UInteger16 length;
|
|
|
|
@@ -798,8 +890,8 @@ void issueManagement(MsgHeader *header, MsgManagement *manage, RunTimeOpts *rtOp
|
|
if(!(length = msgPackManagementResponse(ptpClock->msgObuf, header, manage, ptpClock)))
|
|
return;
|
|
|
|
- if(!netSendGeneral(ptpClock->msgObuf, length, &ptpClock->netPath))
|
|
- toState(PTP_FAULTY, rtOpts, ptpClock);
|
|
+ if(!netSendGeneral(ptpClock->msgObuf, length, ptpClock))
|
|
+ toState(PTP_FAULTY, ptpClock);
|
|
else
|
|
DBGV("sent management message\n");
|
|
}
|
|
diff --git a/src/ptpd.c b/src/ptpd.c
|
|
index cb98438..b19ec63 100644
|
|
--- a/src/ptpd.c
|
|
+++ b/src/ptpd.c
|
|
@@ -19,6 +19,7 @@ int main(int argc, char **argv)
|
|
rtOpts.inboundLatency.nanoseconds = DEFAULT_INBOUND_LATENCY;
|
|
rtOpts.outboundLatency.nanoseconds = DEFAULT_OUTBOUND_LATENCY;
|
|
rtOpts.noResetClock = DEFAULT_NO_RESET_CLOCK;
|
|
+ rtOpts.noAdjust = DEFAULT_NO_ADJUST_CLOCK;
|
|
rtOpts.s = DEFAULT_DELAY_S;
|
|
rtOpts.ap = DEFAULT_AP;
|
|
rtOpts.ai = DEFAULT_AI;
|
|
@@ -30,12 +31,12 @@ int main(int argc, char **argv)
|
|
|
|
if(rtOpts.probe)
|
|
{
|
|
- probe(&rtOpts, ptpClock);
|
|
+ probe(ptpClock);
|
|
}
|
|
else
|
|
{
|
|
/* do the protocol engine */
|
|
- protocol(&rtOpts, ptpClock);
|
|
+ protocol(ptpClock);
|
|
}
|
|
|
|
ptpdShutdown();
|
|
diff --git a/src/ptpd.h b/src/ptpd.h
|
|
index 55cc3d9..95915fa 100644
|
|
--- a/src/ptpd.h
|
|
+++ b/src/ptpd.h
|
|
@@ -20,16 +20,16 @@ void addTime(TimeInternal*,TimeInternal*,TimeInternal*);
|
|
void subTime(TimeInternal*,TimeInternal*,TimeInternal*);
|
|
|
|
/* bmc.c */
|
|
-UInteger8 bmc(ForeignMasterRecord*,RunTimeOpts*,PtpClock*);
|
|
+UInteger8 bmc(ForeignMasterRecord*,PtpClock*);
|
|
void m1(PtpClock*);
|
|
void s1(MsgHeader*,MsgSync*,PtpClock*);
|
|
-void initData(RunTimeOpts*,PtpClock*);
|
|
+void initData(PtpClock*);
|
|
|
|
/* probe.c */
|
|
-void probe(RunTimeOpts*,PtpClock*);
|
|
+void probe(PtpClock*);
|
|
|
|
/* protocol.c */
|
|
-void protocol(RunTimeOpts*,PtpClock*);
|
|
+void protocol(PtpClock*);
|
|
|
|
|
|
#endif
|