ptpd/ptpd-1.0.0-git599b03b.patch
OBS User autobuild 92ea52eb1a Accepting request 2261 from home:dimstar:branches:GNOME:Factory
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
2009-10-20 10:56:08 +00:00

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(&currentTime, ptpClock);
+ subTime(&offset, time, &currentTime);
+ 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, &timestamp, &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