From 92ea52eb1a635a07e10ac3b2159b807e1d9fd61795c8234a3c4088f2491deae5 Mon Sep 17 00:00:00 2001 From: OBS User autobuild Date: Tue, 20 Oct 2009 10:56:08 +0000 Subject: [PATCH] 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 --- ptpd-1.0.0-git599b03b.patch | 5794 +++++++++++++++++++++++++++++++++++ ptpd.changes | 34 + ptpd.spec | 26 +- 3 files changed, 5835 insertions(+), 19 deletions(-) create mode 100644 ptpd-1.0.0-git599b03b.patch diff --git a/ptpd-1.0.0-git599b03b.patch b/ptpd-1.0.0-git599b03b.patch new file mode 100644 index 0000000..c60c941 --- /dev/null +++ b/ptpd-1.0.0-git599b03b.patch @@ -0,0 +1,5794 @@ +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 , where ++# is the value of the INPUT_FILTER tag, and 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 ++ ++#ifndef _WIN32 ++# include ++# include ++# include ++#else ++# include ++# include ++# define sleep( sec ) Sleep( 1000 * (sec) ) ++# define popen _popen ++# define pclose _pclose ++# define pclose _pclose ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++/** ++ * 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 \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 ++ + #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 + #include + #include ++#include + #include + #include + #include +@@ -19,22 +20,58 @@ + #include + #include + ++#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 + +-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 ++ ++#include "e1000_ioctl.h" ++ ++/** global state for controlling system time when TIME_BOTH is selected */ ++static PtpClock timeBothClock; ++ ++/** ++ * Most recent send time stamp from NIC, 0/0 if none available right now. ++ * Reset by getSendTime(). ++ */ ++static TimeInternal lastSendTime; ++ ++#ifndef RECV_ARRAY_SIZE ++/** ++ * Must be large enough to buffer all time stamps received from the NIC ++ * but not yet requested by the protocol processor. Because new information ++ * can only be added when the protocol asks for old one, this should not ++ * get very full. ++ */ ++# define RECV_ARRAY_SIZE 10 ++#endif ++ ++/** ++ * An array of the latest RECV_ARRAY_SIZE packet receive information. ++ * Once it overflows the oldest ones are overwritten in a round-robin ++ * fashion. ++ */ ++static struct { ++ TimeInternal recvTimeStamp; ++ UInteger16 sequenceId; ++ Octet sourceUuid[PTP_UUID_LENGTH]; ++} lastRecvTimes[RECV_ARRAY_SIZE]; ++ ++/** ++ * Oldest valid and next free entry in lastRecvTimes. ++ * Valid ones are [oldest, free[ if oldest <= free, ++ * otherwise [oldest, RECV_ARRAY_SIZE[ and [0, free[. ++ */ ++static int oldestRecv, nextFreeRecv; ++ ++/** ++ * if TIME_BOTH: measure NIC<->system time offsets and adapt system time ++ * ++ * This function is called whenever init.c gets control; to prevent to ++ * frequent changes it ignores invocations less than one second away from ++ * the previous one. ++ */ ++static void syncSystemWithNIC(PtpClock *ptpClock) ++{ ++ struct E1000_TSYNC_COMPARETS_ARGU ts; ++ TimeInternal delay; ++ static TimeInternal zero; ++ ++ if(ptpClock->runTimeOpts.time != TIME_BOTH) ++ return; ++ else ++ { ++#if 1 ++ static TimeInternal lastsync; ++ TimeInternal now, offset; ++ timerNow(&now); ++ subTime(&offset, &now, &lastsync); ++ if(offset.seconds <= 0) ++ return; ++ lastsync = now; ++#endif ++ } ++ ++ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts; ++ memset(&ts, 0, sizeof(ts)); ++ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_COMPARETS_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) { ++ ERROR("could not correlate E1000 hardware and system time on %s: %s\n", ++ ptpClock->netPath.eventSockIFR.ifr_name, ++ strerror(errno)); ++ return; ++ } ++ delay.seconds = ts.systemToNIC.seconds * ts.systemToNICSign; ++ delay.nanoseconds = ts.systemToNIC.nanoseconds * ts.systemToNICSign; ++ DBGV("system to NIC delay %ld.%09d\n", ++ delay.seconds, delay.nanoseconds); ++ updateDelay(&delay, &zero, &timeBothClock.owd_filt, &timeBothClock); ++ ++ delay.seconds = ts.NICToSystem.seconds * ts.NICToSystemSign; ++ delay.nanoseconds = ts.NICToSystem.nanoseconds * ts.NICToSystemSign; ++ DBGV("NIC to system delay %ld.%09d\n", ++ delay.seconds, delay.nanoseconds); ++ updateOffset(&delay, &zero, &timeBothClock.ofm_filt, &timeBothClock); ++ ++ if(ptpClock->port_state == PTP_MASTER) ++ { ++ timeBothClock.nic_instead_of_system = TRUE; ++ timeBothClock.runTimeOpts.time = TIME_NIC; ++ } ++ else ++ { ++ timeBothClock.nic_instead_of_system = FALSE; ++ timeBothClock.runTimeOpts.time = TIME_SYSTEM; ++ } ++ updateClock(&timeBothClock); ++ DBGV("system time updated\n"); ++} ++ ++static Boolean selectNICTimeMode(Boolean sync, PtpClock *ptpClock) ++{ ++ DBGV("time stamp incoming %s packets\n", sync ? "Sync" : "Delay_Req"); ++ ++ switch (ptpClock->runTimeOpts.time) { ++#ifdef HAVE_LINUX_NET_TSTAMP_H ++ case TIME_SYSTEM_LINUX_HW: { ++ struct hwtstamp_config hwconfig; ++ int so_timestamping_flags; ++ ++ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&hwconfig; ++ memset(&hwconfig, 0, sizeof(&hwconfig)); ++ ++ /* ++ * Configure for time stamping of incoming Sync or Delay_Req ++ * messages and for time stamping of all out-going event ++ * messages. Out-going messages will be bounced via the error ++ * queue of the event socket. ++ */ ++ hwconfig.tx_type = HWTSTAMP_TX_ON; ++ hwconfig.rx_filter = sync ? ++ HWTSTAMP_FILTER_PTP_V1_L4_SYNC : ++ HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ; ++ so_timestamping_flags = SOF_TIMESTAMPING_TX_HARDWARE|SOF_TIMESTAMPING_RX_HARDWARE|SOF_TIMESTAMPING_SYS_HARDWARE; ++ ++ if (ioctl(ptpClock->netPath.eventSock, SIOCSHWTSTAMP, &ptpClock->netPath.eventSockIFR) < 0) { ++ if (errno == ERANGE) { ++ /* hardware time stamping not supported */ ++ PERROR("net_tstamp SIOCSHWTSTAMP: mode of operation not supported"); ++ return FALSE; ++ } else { ++ PERROR("net_tstamp SIOCSHWTSTAMP: %s", strerror(errno)); ++ return FALSE; ++ } ++ } ++ ++ if (setsockopt(ptpClock->netPath.eventSock, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping_flags, sizeof(so_timestamping_flags)) < 0) { ++ PERROR("net_tstamp SO_TIMESTAMPING: %s", strerror(errno)); ++ return FALSE; ++ } ++ break; ++ } ++ case TIME_SYSTEM_LINUX_SW: { ++ /* same as before, but without requiring support by the NIC */ ++ int so_timestamping_flags = ++ SOF_TIMESTAMPING_TX_SOFTWARE|SOF_TIMESTAMPING_RX_SOFTWARE|SOF_TIMESTAMPING_SOFTWARE; ++ if (setsockopt(ptpClock->netPath.eventSock, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping_flags, sizeof(so_timestamping_flags)) < 0) { ++ PERROR("net_tstamp SO_TIMESTAMPING: %s", strerror(errno)); ++ return FALSE; ++ } ++ break; ++ } ++#else ++ case TIME_SYSTEM_LINUX_SW: ++ case TIME_SYSTEM_LINUX_HW: ++ PERROR("net_tstamp interface not supported"); ++ return FALSE; ++#endif /* HAVE_LINUX_NET_TSTAMP_H */ ++ default: ++ *(int *)&ptpClock->netPath.eventSockIFR.ifr_data = sync ? E1000_UDP_V1_SYNC : E1000_UDP_V1_DELAY; ++ if(ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_ENABLERX_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) { ++ ERROR("could not activate E1000 hardware receive time stamping on %s: %s\n", ++ ptpClock->netPath.eventSockIFR.ifr_name, ++ strerror(errno)); ++ return FALSE; ++ } ++ break; ++ } ++ ++ return TRUE; ++} ++ ++static Boolean initNICTime(Boolean sync, PtpClock *ptpClock) ++{ ++ /** @todo also check success indicator in ifr_data */ ++ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_INIT_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) { ++ ERROR("could not activate E1000 hardware time stamping on %s: %s\n", ++ ptpClock->netPath.eventSockIFR.ifr_name, ++ strerror(errno)); ++ } ++ else if(ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_ENABLETX_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) { ++ ERROR("could not activate E1000 hardware send time stamping on %s: %s\n", ++ ptpClock->netPath.eventSockIFR.ifr_name, ++ strerror(errno)); ++ } ++ else if(!selectNICTimeMode(sync, ptpClock)) { ++ // error already printed ++ } ++ else { ++#if 0 ++ // move the NIC time for debugging purposes ++ TimeInternal timeTmp; ++ ++ DBGV("shift NIC time\n"); ++ getTime(&timeTmp, ptpClock); ++ timeTmp.seconds -= 2; ++ setTime(&timeTmp, ptpClock); ++ DBGV("shift NIC time done\n"); ++#endif ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ ++Boolean initTime(PtpClock *ptpClock) ++{ ++ switch(ptpClock->runTimeOpts.time) { ++ case TIME_SYSTEM: ++ return TRUE; ++ break; ++ case TIME_BOTH: ++ /* prepare clock servo for controlling system time */ ++ timeBothClock = *ptpClock; ++ timeBothClock.runTimeOpts.time = TIME_SYSTEM; ++ timeBothClock.name = "sys "; ++ initClock(&timeBothClock); ++ ++ /* default options for NIC synchronization */ ++ ptpClock->runTimeOpts.noResetClock = DEFAULT_NO_RESET_CLOCK; ++ ptpClock->runTimeOpts.noAdjust = DEFAULT_NO_ADJUST_CLOCK; ++ ptpClock->runTimeOpts.s = DEFAULT_DELAY_S; ++ ptpClock->runTimeOpts.ap = DEFAULT_AP; ++ ptpClock->runTimeOpts.ai = DEFAULT_AI; ++ ++ return initNICTime(TRUE, ptpClock); ++ break; ++ case TIME_SYSTEM_LINUX_HW: ++ case TIME_SYSTEM_LINUX_SW: ++ return selectNICTimeMode(TRUE, ptpClock); ++ break; ++ case TIME_NIC: ++ case TIME_SYSTEM_ASSISTED: ++ return initNICTime(TRUE, ptpClock); ++ break; ++ default: ++ ERROR("unsupported selection of time source\n"); ++ return FALSE; ++ break; ++ } ++} ++ ++void getTime(TimeInternal *time, PtpClock *ptpClock) ++{ ++ switch(ptpClock->runTimeOpts.time) ++ { ++ case TIME_SYSTEM_LINUX_HW: ++ case TIME_SYSTEM_LINUX_SW: ++ case TIME_SYSTEM_ASSISTED: ++ case TIME_SYSTEM: { ++ struct timeval tv; ++ ++ gettimeofday(&tv, 0); ++ time->seconds = tv.tv_sec; ++ time->nanoseconds = tv.tv_usec*1000; ++ break; ++ } ++ case TIME_BOTH: ++ case TIME_NIC: { ++ struct E1000_TSYNC_SYSTIME_ARGU ts; ++ ++ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts; ++ memset(&ts, 0, sizeof(ts)); ++ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_SYSTIME_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) { ++ ERROR("could not read E1000 hardware time on %s: %s\n", ++ ptpClock->netPath.eventSockIFR.ifr_name, ++ strerror(errno)); ++ return; ++ } ++ time->seconds = ts.time.seconds; ++ time->nanoseconds = ts.time.nanoseconds; ++ ++ syncSystemWithNIC(ptpClock); ++ break; ++ } ++ default: ++ ERROR("unsupported selection of time source\n"); ++ break; ++ } ++} ++ ++void setTime(TimeInternal *time, PtpClock *ptpClock) ++{ ++ switch(ptpClock->runTimeOpts.time) ++ { ++ case TIME_SYSTEM_LINUX_HW: ++ case TIME_SYSTEM_LINUX_SW: ++ case TIME_SYSTEM_ASSISTED: ++ case TIME_SYSTEM: { ++ NOTIFY("resetting system clock to %ds %dns\n", time->seconds, time->nanoseconds); ++ struct timeval tv; ++ tv.tv_sec = time->seconds; ++ tv.tv_usec = time->nanoseconds/1000; ++ settimeofday(&tv, 0); ++ break; ++ } ++ case TIME_BOTH: ++ case TIME_NIC: { ++ struct E1000_TSYNC_SYSTIME_ARGU ts; ++ TimeInternal currentTime, offset; ++ ++ NOTIFY("resetting NIC clock to %ds %dns\n", time->seconds, time->nanoseconds); ++ memset(&ts, 0, sizeof(ts)); ++ getTime(¤tTime, ptpClock); ++ subTime(&offset, time, ¤tTime); ++ ts.negative_offset = (offset.seconds < 0 || offset.nanoseconds < 0) ? -1 : 1; ++ ts.time.seconds = ts.negative_offset * offset.seconds; ++ ts.time.nanoseconds = ts.negative_offset * offset.nanoseconds; ++ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts; ++ NOTIFY("adding NIC offset %s%ld.%09d (%ld/%p)\n", ++ ts.negative_offset < 0 ? "-" : "", ++ ts.time.seconds, ++ ts.time.nanoseconds, ++ sizeof(ts), &ts); ++ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_SYSTIME_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) { ++ ERROR("could not set E1000 hardware time on %s: %s\n", ++ ptpClock->netPath.eventSockIFR.ifr_name, ++ strerror(errno)); ++ } ++ else ++ { ++ DBGV("new NIC time %ld.%09d\n", ++ ts.time.seconds, ++ ts.time.nanoseconds); ++ syncSystemWithNIC(ptpClock); ++ } ++ break; ++ } ++ default: ++ ERROR("unsupported selection of time source\n"); ++ break; ++ } ++} ++ ++void adjTime(Integer32 adj, TimeInternal *offset, PtpClock *ptpClock) ++{ ++ switch(ptpClock->runTimeOpts.time) ++ { ++ case TIME_SYSTEM_LINUX_HW: ++ case TIME_SYSTEM_LINUX_SW: ++ case TIME_SYSTEM_ASSISTED: ++ case TIME_SYSTEM: { ++ struct timex t; ++ static Boolean maxAdjValid; ++ static long maxAdj; ++ static long minTick, maxTick; ++ static long userHZ; ++ static long tickRes; /* USER_HZ * 1000 [ppb] */ ++ long tickAdj; ++ long freqAdj; ++ int res; ++ ++ if (!maxAdjValid) { ++ userHZ = sysconf(_SC_CLK_TCK); ++ t.modes = 0; ++ adjtimex(&t); ++ maxAdj = t.tolerance / ((1<<16)/1000); ++ tickRes = userHZ * 1000; ++ /* limits from the adjtimex command man page; could be determined via binary search */ ++ minTick = (900000 - 1000000) / userHZ; ++ maxTick = (1100000 - 1000000) / userHZ; ++ maxAdjValid = TRUE; ++ } ++ ++ /* ++ * The Linux man page for the adjtimex() system call does not ++ * describe limits for frequency. The more recent man page for ++ * the adjtimex command on RH5 does and says that ++ * -tolerance <= frequency <= tolerance ++ * which was confirmed by trying out values just outside that interval. ++ * ++ * Note that this contradicts the comments for struct timex which say ++ * that freq and tolerance have different units (scaled ppm vs ppm). ++ * ++ * We follow the actual implementation on Linux 2.6.22 and do the ++ * range check after scaling. ++ */ ++ ++ t.modes = MOD_FREQUENCY|MOD_CLKB; ++ /* ++ * @todo ++ * Where is the official documentation for "scaled ppm"? ++ * Should this perhaps be adj * (1<<16) / 1000 (more accurate ++ * than multiplying by ((1<<16)/1000) == 65)? ++ */ ++ ++ /* ++ * 1 t.tick = 1 e-6 s * USER_HZ 1/s = 1 USER_HZ * 1000 ppb ++ * ++ * Large values of adj can be turned into t.tick adjustments: ++ * tickAdj t.tick = adj ppb / ( USER_HZ * 1000 ppb ) ++ * ++ * Round this so that the error is as small is possible, ++ * because we need to fit that into t.freq. ++ */ ++ freqAdj = adj; ++ tickAdj = 0; ++ if(freqAdj > maxAdj) ++ { ++ tickAdj = (adj - maxAdj + tickRes - 1) / tickRes; ++ if(tickAdj > maxTick) ++ tickAdj = maxTick; ++ freqAdj = adj - tickAdj * tickRes; ++ } ++ else if(freqAdj < -maxAdj) ++ { ++ tickAdj = -((-adj - maxAdj + tickRes - 1) / tickRes); ++ if(tickAdj < minTick) ++ tickAdj = minTick; ++ freqAdj = adj - tickAdj * tickRes; ++ } ++ if(freqAdj > maxAdj) ++ freqAdj = maxAdj; ++ else if(freqAdj < -maxAdj) ++ freqAdj = -maxAdj; ++ ++ t.freq = freqAdj * ((1<<16)/1000); ++ t.tick = tickAdj + 1000000 / userHZ; ++ ptpClock->adj = tickAdj * tickRes + freqAdj; ++ ++ INFO("requested adj %d ppb => adjust system frequency by %d scaled ppm (%d ppb) + %ld us/tick (%d ppb) = adj %d ppb (freq limit %ld/%ld ppm, tick limit %ld/%ld us*USER_HZ)\n", ++ adj, ++ t.freq, freqAdj, ++ t.tick - 1000000 / userHZ, tickAdj * tickRes, ++ ptpClock->adj, ++ -maxAdj, maxAdj, ++ minTick, maxTick); ++ ++ res = adjtimex(&t); ++ switch (res) { ++ case -1: ++ ERROR("adjtimex(freq = %d) failed: %s\n", ++ t.freq, strerror(errno)); ++ break; ++ case TIME_OK: ++ INFO(" -> TIME_OK\n"); ++ break; ++ case TIME_INS: ++ ERROR("adjtimex -> insert leap second?!\n"); ++ break; ++ case TIME_DEL: ++ ERROR("adjtimex -> delete leap second?!\n"); ++ break; ++ case TIME_OOP: ++ ERROR("adjtimex -> leap second in progress?!\n"); ++ break; ++ case TIME_WAIT: ++ ERROR("adjtimex -> leap second has occurred?!\n"); ++ break; ++ case TIME_BAD: ++ ERROR("adjtimex -> time bad\n"); ++ break; ++ default: ++ ERROR("adjtimex -> unknown result %d\n", res); ++ break; ++ } ++ break; ++ } ++ case TIME_BOTH: ++ case TIME_NIC: { ++ if(offset) ++ { ++#if 0 ++ struct E1000_TSYNC_SYSTIME_ARGU ts; ++ memset(&ts, 0, sizeof(ts)); ++ // always store positive seconds/nanoseconds ++ ts.negative_offset = (offset->seconds < 0 || offset->nanoseconds < 0) ? -1 : 1; ++ ts.time.seconds = ts.negative_offset * offset->seconds; ++ ts.time.nanoseconds = ts.negative_offset * offset->nanoseconds; ++ // invert the sign: if offset is positive, we need to substract it and vice versa ++ ts.negative_offset *= -1; ++ DBGV("adjust NIC time by offset %s%lu.%09d (adj %d)\n", ++ ts.negative_offset < 0 ? "-" : "", ++ ts.time.seconds, ts.time.nanoseconds, ++ adj); ++ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts; ++ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_SYSTIME_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) { ++ ERROR("could not modify E1000 hardware time on %s: %s\n", ++ ptpClock->netPath.eventSockIFR.ifr_name, ++ strerror(errno)); ++ } ++ else ++ syncSystemWithNIC(ptpClock); ++#else ++ // adjust NIC frequency ++ struct E1000_TSYNC_ADJTIME_ARGU ts; ++ memset(&ts, 0, sizeof(ts)); ++ ts.adj = (long long)adj; ++ if(ptpClock->nic_instead_of_system) ++ ts.adj = -ts.adj; ++ ts.set_adj = TRUE; ++ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts; ++ DBGV("adjust NIC frequency by %d ppb\n", ts.adj); ++ ptpClock->adj = ts.adj; ++ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_ADJTIME_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) { ++ ERROR("could not modify E1000 hardware frequency on %s: %s\n", ++ ptpClock->netPath.eventSockIFR.ifr_name, ++ strerror(errno)); ++ } ++ else ++ syncSystemWithNIC(ptpClock); ++#endif ++ } ++ else ++ syncSystemWithNIC(ptpClock); ++ break; ++ } ++ default: ++ ERROR("unsupported selection of time source\n"); ++ break; ++ } ++} ++ ++void adjTimeOffset(TimeInternal *offset, PtpClock *ptpClock) ++{ ++ switch(ptpClock->runTimeOpts.time) ++ { ++ case TIME_BOTH: ++ case TIME_NIC: { ++ struct E1000_TSYNC_SYSTIME_ARGU ts; ++ memset(&ts, 0, sizeof(ts)); ++ // always store positive seconds/nanoseconds ++ ts.negative_offset = (offset->seconds < 0 || offset->nanoseconds < 0) ? -1 : 1; ++ ts.time.seconds = ts.negative_offset * offset->seconds; ++ ts.time.nanoseconds = ts.negative_offset * offset->nanoseconds; ++ ++ // invert the sign: if offset is positive, we need to substract it and vice versa; ++ // when in nic_instead_of_system the logic is already inverted ++ if (!ptpClock->nic_instead_of_system) ++ ts.negative_offset *= -1; ++ ++ DBGV("adjust NIC time by offset %s%lu.%09d\n", ++ ts.negative_offset < 0 ? "-" : "", ++ ts.time.seconds, ts.time.nanoseconds); ++ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts; ++ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_SYSTIME_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) { ++ ERROR("could not modify E1000 hardware time on %s: %s\n", ++ ptpClock->netPath.eventSockIFR.ifr_name, ++ strerror(errno)); ++ } ++ else ++ syncSystemWithNIC(ptpClock); ++ break; ++ } ++ default: { ++ TimeInternal timeTmp; ++ ++ getTime(&timeTmp, ptpClock); ++ subTime(&timeTmp, &timeTmp, offset); ++ setTime(&timeTmp, ptpClock); ++ } ++ } ++} ++ ++static void getTimeStamps(PtpClock *ptpClock) ++{ ++ struct E1000_TSYNC_READTS_ARGU ts; ++ ++ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&ts; ++ memset(&ts, 0, sizeof(ts)); ++ ts.withSystemTime = (ptpClock->runTimeOpts.time == TIME_SYSTEM_ASSISTED); ++ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_READTS_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) { ++ ERROR("could not read E1000 hardware time stamps on %s: %s\n", ++ ptpClock->netPath.eventSockIFR.ifr_name, ++ strerror(errno)); ++ return; ++ } ++ ++ DBGV("rx %s, tx %s\n", ++ ts.rx_valid ? "valid" : "invalid", ++ ts.tx_valid ? "valid" : "invalid"); ++ ++ if(ts.rx_valid) ++ { ++ int newIndex; ++ ++ if(nextFreeRecv == RECV_ARRAY_SIZE) ++ { ++ newIndex = 0; ++ nextFreeRecv = 1; ++ oldestRecv = 2; ++ } ++ else ++ { ++ newIndex = nextFreeRecv; ++ nextFreeRecv++; ++ if(oldestRecv && nextFreeRecv == oldestRecv) ++ ++oldestRecv; ++ } ++ ++ if(oldestRecv >= RECV_ARRAY_SIZE) ++ oldestRecv = 0; ++ ++ DBGV("new entry %d, oldest %d, next free %d\n", newIndex, oldestRecv, nextFreeRecv); ++ ++ lastRecvTimes[newIndex].recvTimeStamp.seconds = ts.withSystemTime ? ts.rx_sys.seconds : ts.rx.seconds; ++ lastRecvTimes[newIndex].recvTimeStamp.nanoseconds = ts.withSystemTime ? ts.rx_sys.nanoseconds : ts.rx.nanoseconds; ++ lastRecvTimes[newIndex].sequenceId = ts.sourceSequenceId; ++ memcpy(lastRecvTimes[newIndex].sourceUuid, ts.sourceIdentity, sizeof(ts.sourceIdentity)); ++ ++ DBGV("rx %d: time %lu.%09u (%lu.%09u), sequence %u, uuid %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", ++ newIndex, ++ lastRecvTimes[newIndex].recvTimeStamp.seconds, ++ lastRecvTimes[newIndex].recvTimeStamp.nanoseconds, ++ ts.withSystemTime ? ts.rx.seconds : 0, ++ ts.withSystemTime ? ts.rx.nanoseconds : 0, ++ lastRecvTimes[newIndex].sequenceId, ++ ts.sourceIdentity[0], ++ ts.sourceIdentity[1], ++ ts.sourceIdentity[2], ++ ts.sourceIdentity[3], ++ ts.sourceIdentity[4], ++ ts.sourceIdentity[5]); ++ } ++ ++ if(ts.tx_valid) ++ { ++ lastSendTime.seconds = ts.withSystemTime ? ts.tx_sys.seconds : ts.tx.seconds; ++ lastSendTime.nanoseconds = ts.withSystemTime ? ts.tx_sys.nanoseconds : ts.tx.nanoseconds; ++ ++ DBGV("tx time %lu.%09u (%lu.%09u)\n", ++ lastSendTime.seconds, ++ lastSendTime.nanoseconds, ++ ts.withSystemTime ? ts.tx.seconds : 0, ++ ts.withSystemTime ? ts.tx.nanoseconds : 0); ++ } ++} ++ ++Boolean getSendTime(TimeInternal *sendTimeStamp, ++ PtpClock *ptpClock) ++{ ++ /* check for new time stamps */ ++ getTimeStamps(ptpClock); ++ ++ if(lastSendTime.seconds || lastSendTime.nanoseconds) ++ { ++ *sendTimeStamp = lastSendTime; ++ lastSendTime.seconds = 0; ++ lastSendTime.nanoseconds = 0; ++ return TRUE; ++ } ++ else ++ return FALSE; ++} ++ ++/** ++ * helper function for getReceiveTime() which searches for time stamp ++ * in lastRecvTimes[leftIndex, rightIndex[ ++ */ ++static Boolean getReceiveTimeFromArray(TimeInternal *recvTimeStamp, ++ Octet sourceUuid[PTP_UUID_LENGTH], ++ UInteger16 sequenceId, ++ int leftIndex, int rightIndex) ++{ ++ int i; ++ ++ for(i = leftIndex; i < rightIndex; i++) ++ { ++ if(!memcmp(lastRecvTimes[i].sourceUuid, sourceUuid, sizeof(lastRecvTimes[i].sourceUuid)) && ++ lastRecvTimes[i].sequenceId == sequenceId) ++ { ++ DBGV("found rx index %d: time %lu.%09u, sequence %u, uuid %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", ++ i, ++ lastRecvTimes[i].recvTimeStamp.seconds, ++ lastRecvTimes[i].recvTimeStamp.nanoseconds, ++ lastRecvTimes[i].sequenceId, ++ lastRecvTimes[i].sourceUuid[0], ++ lastRecvTimes[i].sourceUuid[1], ++ lastRecvTimes[i].sourceUuid[2], ++ lastRecvTimes[i].sourceUuid[3], ++ lastRecvTimes[i].sourceUuid[4], ++ lastRecvTimes[i].sourceUuid[5]); ++ *recvTimeStamp = lastRecvTimes[i].recvTimeStamp; ++ // invalidate entry to prevent accidental reuse (happened when slaves were ++ // restarted quickly while the master still had their old sequence IDs in the array) ++ memset(&lastRecvTimes[i], 0, sizeof(lastRecvTimes[i])); ++ return TRUE; ++ } ++ } ++ return FALSE; ++} ++ ++Boolean getReceiveTime(TimeInternal *recvTimeStamp, ++ Octet sourceUuid[PTP_UUID_LENGTH], ++ UInteger16 sequenceId, ++ PtpClock *ptpClock) ++{ ++ /* check for new time stamps */ ++ getTimeStamps(ptpClock); ++ ++ if(oldestRecv <= nextFreeRecv) ++ return getReceiveTimeFromArray(recvTimeStamp, sourceUuid, sequenceId, oldestRecv, nextFreeRecv); ++ else ++ { ++ if(getReceiveTimeFromArray(recvTimeStamp, sourceUuid, sequenceId, oldestRecv, RECV_ARRAY_SIZE)) ++ return TRUE; ++ else ++ return getReceiveTimeFromArray(recvTimeStamp, sourceUuid, sequenceId, 0, nextFreeRecv); ++ } ++} ++ ++void timeNoActivity(PtpClock *ptpClock) ++{ ++#ifdef PTPD_DBGV ++ switch(ptpClock->runTimeOpts.time) { ++ case TIME_NIC: ++ case TIME_BOTH: ++ case TIME_SYSTEM_ASSISTED: ++ { ++ TimeInternal now, ts, offset; ++ struct E1000_TSYNC_COMPARETS_ARGU argu; ++ int sign; ++ ++ getTime(&ts, ptpClock); ++ timerNow(&now); ++ subTime(&offset, &now, &ts); ++ sign = (offset.seconds < 0 || offset.nanoseconds < 0) ? -1 : 1; ++ DBGV("system time %d.%09d, NIC time %d.%09d => system time - NIC time = %s%d.%09d\n", ++ now.seconds, now.nanoseconds, ++ ts.seconds, ts.nanoseconds, ++ sign < 0 ? "-" : "", ++ sign * offset.seconds, sign * offset.nanoseconds); ++ ++ ptpClock->netPath.eventSockIFR.ifr_data = (void *)&argu; ++ memset(&ts, 0, sizeof(ts)); ++ if (ioctl(ptpClock->netPath.eventSock, E1000_TSYNC_COMPARETS_IOCTL, &ptpClock->netPath.eventSockIFR) < 0) { ++ ERROR("could not correlate E1000 hardware and system time on %s: %s\n", ++ ptpClock->netPath.eventSockIFR.ifr_name, ++ strerror(errno)); ++ return; ++ } ++ ++ now.seconds = argu.systemToNICSign * argu.systemToNIC.seconds; ++ now.nanoseconds = argu.systemToNICSign * argu.systemToNIC.nanoseconds; ++ ts.seconds = argu.NICToSystemSign * argu.NICToSystem.seconds; ++ ts.nanoseconds = argu.NICToSystemSign * argu.NICToSystem.nanoseconds; ++ subTime(&offset, &now, &ts); ++ offset.seconds /= 2; ++ offset.nanoseconds /= 2; ++ DBGV("delay system to NIC %s%ld.%09d/NIC to system %s%ld.%09d => system - NIC time = %d.%09d\n", ++ argu.systemToNICSign > 0 ? "" : argu.systemToNICSign < 0 ? "-" : "?", ++ argu.systemToNIC.seconds, argu.systemToNIC.nanoseconds, ++ argu.NICToSystemSign > 0 ? "" : argu.NICToSystemSign < 0 ? "-" : "?", ++ argu.NICToSystem.seconds, argu.NICToSystem.nanoseconds, ++ offset.seconds, offset.nanoseconds); ++ break; ++ } ++ } ++#endif ++ syncSystemWithNIC(ptpClock); ++} ++ ++void timeToState(UInteger8 state, PtpClock *ptpClock) ++{ ++ if(ptpClock->runTimeOpts.time > TIME_SYSTEM && ++ state != ptpClock->port_state) ++ { ++ if(state == PTP_MASTER) ++ /* only master listens for Delay_Req... */ ++ selectNICTimeMode(FALSE, ptpClock); ++ else if(ptpClock->port_state == PTP_MASTER) ++ /** ... and only while he still is master */ ++ selectNICTimeMode(TRUE, ptpClock); ++ ++ timeBothClock.port_state = state; ++ } ++} +diff --git a/src/dep/timer.c b/src/dep/timer.c +index f9f4110..b40eb40 100644 +--- a/src/dep/timer.c ++++ b/src/dep/timer.c +@@ -9,7 +9,13 @@ void catch_alarm(int sig) + { + elapsed += TIMER_INTERVAL; + +- DBGV("catch_alarm: elapsed %d\n", elapsed); ++ /* ++ * DBGV() calls vsyslog() which doesn't seem to be reentrant: ++ * with Linux 2.6.23 and libc 2.5 (RH5) this even locked up the ++ * system. ++ * ++ * DBGV("catch_alarm: elapsed %d\n", elapsed); ++ */ + } + + void initTimer(void) +@@ -84,3 +90,28 @@ Boolean timerExpired(UInteger16 index, IntervalTimer *itimer) + return TRUE; + } + ++Boolean nanoSleep(TimeInternal *t) ++{ ++ struct timespec ts, tr; ++ ++ ts.tv_sec = t->seconds; ++ ts.tv_nsec = t->nanoseconds; ++ ++ if(nanosleep(&ts, &tr) < 0) ++ { ++ t->seconds = tr.tv_sec; ++ t->nanoseconds = tr.tv_nsec; ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++void timerNow(TimeInternal *time) ++{ ++ struct timeval tv; ++ ++ gettimeofday(&tv, 0); ++ time->seconds = tv.tv_sec; ++ time->nanoseconds = tv.tv_usec*1000; ++} +diff --git a/src/probe.c b/src/probe.c +index 870a25e..215019f 100644 +--- a/src/probe.c ++++ b/src/probe.c +@@ -8,29 +8,29 @@ UInteger8 management_key_array[KEY_ARRAY_LEN] = + void displayHeader(MsgHeader*); + void displayManagement(MsgHeader*,MsgManagement*); + +-void probe(RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void probe(PtpClock *ptpClock) + { + UInteger16 i; + UInteger16 length; +- TimeInternal interval, now, finish, timestamp; ++ TimeInternal interval, now, finish; + + /* check */ +- if(rtOpts->probe_management_key == PTP_MM_UPDATE_DEFAULT_DATA_SET +- || rtOpts->probe_management_key == PTP_MM_UPDATE_GLOBAL_TIME_PROPERTIES +- || rtOpts->probe_management_key == PTP_MM_SET_SYNC_INTERVAL) ++ if(ptpClock->runTimeOpts.probe_management_key == PTP_MM_UPDATE_DEFAULT_DATA_SET ++ || ptpClock->runTimeOpts.probe_management_key == PTP_MM_UPDATE_GLOBAL_TIME_PROPERTIES ++ || ptpClock->runTimeOpts.probe_management_key == PTP_MM_SET_SYNC_INTERVAL) + { + ERROR("send not supported for that management message\n"); + return; + } + + /* init */ +- if(!netInit(&ptpClock->netPath, rtOpts, ptpClock)) ++ if(!netInit(ptpClock)) + { + ERROR("failed to initialize network\n"); + return; + } + +- initData(rtOpts, ptpClock); ++ initData(ptpClock); + msgPackHeader(ptpClock->msgObuf, ptpClock); + + memset(&ptpClock->msgTmp.manage, 0, sizeof(MsgManagement)); +@@ -39,10 +39,10 @@ void probe(RunTimeOpts *rtOpts, PtpClock *ptpClock) + /* send */ + for(i = 0; i < KEY_ARRAY_LEN; ++i) + { +- if(rtOpts->probe_management_key > 0) ++ if(ptpClock->runTimeOpts.probe_management_key > 0) + { +- ptpClock->msgTmp.manage.managementMessageKey = rtOpts->probe_management_key; +- ptpClock->msgTmp.manage.recordKey = rtOpts->probe_record_key; ++ ptpClock->msgTmp.manage.managementMessageKey = ptpClock->runTimeOpts.probe_management_key; ++ ptpClock->msgTmp.manage.recordKey = ptpClock->runTimeOpts.probe_record_key; + } + else + ptpClock->msgTmp.manage.managementMessageKey = management_key_array[i]; +@@ -55,27 +55,27 @@ void probe(RunTimeOpts *rtOpts, PtpClock *ptpClock) + + printf("\n(sending managementMessageKey %hhu)\n", ptpClock->msgTmp.manage.managementMessageKey); + +- if(!netSendGeneral(ptpClock->msgObuf, length, &ptpClock->netPath)) ++ if(!netSendGeneral(ptpClock->msgObuf, length, ptpClock)) + { + ERROR("failed to send message\n"); + return; + } + +- if(rtOpts->probe_management_key > 0) ++ if(ptpClock->runTimeOpts.probe_management_key > 0) + break; + } + +- getTime(&finish); ++ timerNow(&finish); + finish.seconds += PTP_SYNC_INTERVAL_TIMEOUT(ptpClock->sync_interval); + for(;;) + { + interval.seconds = PTP_SYNC_INTERVAL_TIMEOUT(ptpClock->sync_interval); + interval.nanoseconds = 0; +- netSelect(&interval, &ptpClock->netPath); ++ netSelect(&interval, ptpClock); + +- netRecvEvent(ptpClock->msgIbuf, ×tamp, &ptpClock->netPath); ++ netRecvEvent(ptpClock->msgIbuf, NULL, ptpClock); + +- if(netRecvGeneral(ptpClock->msgIbuf, &ptpClock->netPath)) ++ if(netRecvGeneral(ptpClock->msgIbuf, ptpClock)) + { + msgUnpackHeader(ptpClock->msgIbuf, &ptpClock->msgTmpHeader); + +@@ -90,7 +90,7 @@ void probe(RunTimeOpts *rtOpts, PtpClock *ptpClock) + fflush(stdout); + } + +- getTime(&now); ++ timerNow(&now); + if( now.seconds > finish.seconds || (now.seconds == finish.seconds + && now.nanoseconds > finish.nanoseconds) ) + break; +diff --git a/src/protocol.c b/src/protocol.c +index 8bde2ce..0a53b82 100644 +--- a/src/protocol.c ++++ b/src/protocol.c +@@ -2,22 +2,22 @@ + + #include "ptpd.h" + +-Boolean doInit(RunTimeOpts*,PtpClock*); +-void doState(RunTimeOpts*,PtpClock*); +-void toState(UInteger8,RunTimeOpts*,PtpClock*); +- +-void handle(RunTimeOpts*,PtpClock*); +-void handleSync(MsgHeader*,Octet*,ssize_t,TimeInternal*,Boolean,RunTimeOpts*,PtpClock*); +-void handleFollowUp(MsgHeader*,Octet*,ssize_t,Boolean,RunTimeOpts*,PtpClock*); +-void handleDelayReq(MsgHeader*,Octet*,ssize_t,TimeInternal*,Boolean,RunTimeOpts*,PtpClock*); +-void handleDelayResp(MsgHeader*,Octet*,ssize_t,Boolean,RunTimeOpts*,PtpClock*); +-void handleManagement(MsgHeader*,Octet*,ssize_t,Boolean,RunTimeOpts*,PtpClock*); +- +-void issueSync(RunTimeOpts*,PtpClock*); +-void issueFollowup(TimeInternal*,RunTimeOpts*,PtpClock*); +-void issueDelayReq(RunTimeOpts*,PtpClock*); +-void issueDelayResp(TimeInternal*,MsgHeader*,RunTimeOpts*,PtpClock*); +-void issueManagement(MsgHeader*,MsgManagement*,RunTimeOpts*,PtpClock*); ++Boolean doInit(PtpClock*); ++void doState(PtpClock*); ++void toState(UInteger8,PtpClock*); ++ ++void handle(PtpClock*); ++void handleSync(MsgHeader*,Octet*,ssize_t,TimeInternal*,Boolean,Boolean,PtpClock*); ++void handleFollowUp(MsgHeader*,Octet*,ssize_t,Boolean,PtpClock*); ++void handleDelayReq(MsgHeader*,Octet*,ssize_t,TimeInternal*,Boolean,Boolean,PtpClock*); ++void handleDelayResp(MsgHeader*,Octet*,ssize_t,Boolean,PtpClock*); ++void handleManagement(MsgHeader*,Octet*,ssize_t,Boolean,PtpClock*); ++ ++void issueSync(PtpClock*); ++void issueFollowup(TimeInternal*,PtpClock*); ++void issueDelayReq(PtpClock*); ++void issueDelayResp(TimeInternal*,MsgHeader*,PtpClock*); ++void issueManagement(MsgHeader*,MsgManagement*,PtpClock*); + + MsgSync * addForeign(Octet*,MsgHeader*,PtpClock*); + +@@ -26,43 +26,70 @@ MsgSync * addForeign(Octet*,MsgHeader*,PtpClock*); + checked for 'port_state'. the actions and events may or may not change + 'port_state' by calling toState(), but once they are done we loop around + again and perform the actions required for the new 'port_state'. */ +-void protocol(RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void protocol(PtpClock *ptpClock) + { + DBG("event POWERUP\n"); + +- toState(PTP_INITIALIZING, rtOpts, ptpClock); ++ toState(PTP_INITIALIZING, ptpClock); + + for(;;) + { + if(ptpClock->port_state != PTP_INITIALIZING) +- doState(rtOpts, ptpClock); +- else if(!doInit(rtOpts, ptpClock)) ++ doState(ptpClock); ++ else if(!doInit(ptpClock)) + return; + + if(ptpClock->message_activity) + DBGV("activity\n"); + else ++ { + DBGV("no activity\n"); ++ timeNoActivity(ptpClock); ++ } + } + } + +-Boolean doInit(RunTimeOpts *rtOpts, PtpClock *ptpClock) ++Boolean doInit(PtpClock *ptpClock) + { + DBG("manufacturerIdentity: %s\n", MANUFACTURER_ID); + + /* initialize networking */ +- netShutdown(&ptpClock->netPath); +- if(!netInit(&ptpClock->netPath, rtOpts, ptpClock)) ++ netShutdown(ptpClock); ++ if(!netInit(ptpClock)) + { + ERROR("failed to initialize network\n"); +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ toState(PTP_FAULTY, ptpClock); + return FALSE; + } +- ++ ++ /* initialize timing, may fail e.g. if timer depends on hardware */ ++ if(!initTime(ptpClock)) ++ { ++ ERROR("failed to initialize timing\n"); ++ toState(PTP_FAULTY, ptpClock); ++ return FALSE; ++ } ++ ++ switch (ptpClock->runTimeOpts.time) { ++ case TIME_SYSTEM: ++ case TIME_SYSTEM_LINUX_HW: ++ case TIME_SYSTEM_LINUX_SW: ++ /* ++ * send time stamp will be returned to socket when available, ++ * either via IP_MULTICAST_LOOP or SIOCSHWTSTAMP + error queue ++ */ ++ ptpClock->delayedTiming = FALSE; ++ break; ++ default: ++ /* ask for time stamp shortly after sending */ ++ ptpClock->delayedTiming = TRUE; ++ break; ++ } ++ + /* initialize other stuff */ +- initData(rtOpts, ptpClock); ++ initData(ptpClock); + initTimer(); +- initClock(rtOpts, ptpClock); ++ initClock(ptpClock); + m1(ptpClock); + msgPackHeader(ptpClock->msgObuf, ptpClock); + +@@ -71,7 +98,7 @@ Boolean doInit(RunTimeOpts *rtOpts, PtpClock *ptpClock) + DBG("256*log2(clock variance): %d\n", ptpClock->clock_variance); + DBG("clock stratum: %d\n", ptpClock->clock_stratum); + DBG("clock preferred?: %s\n", ptpClock->preferred?"yes":"no"); +- DBG("bound interface name: %s\n", rtOpts->ifaceName); ++ DBG("bound interface name: %s\n", ptpClock->runTimeOpts.ifaceName); + DBG("communication technology: %d\n", ptpClock->port_communication_technology); + DBG("uuid: %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", + ptpClock->port_uuid_field[0], ptpClock->port_uuid_field[1], ptpClock->port_uuid_field[2], +@@ -85,12 +112,12 @@ Boolean doInit(RunTimeOpts *rtOpts, PtpClock *ptpClock) + DBG("general port address: %hhx %hhx\n", + ptpClock->general_port_address[0], ptpClock->general_port_address[1]); + +- toState(PTP_LISTENING, rtOpts, ptpClock); ++ toState(PTP_LISTENING, ptpClock); + return TRUE; + } + + /* handle actions and events for 'port_state' */ +-void doState(RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void doState(PtpClock *ptpClock) + { + UInteger8 state; + +@@ -105,9 +132,9 @@ void doState(RunTimeOpts *rtOpts, PtpClock *ptpClock) + if(ptpClock->record_update) + { + ptpClock->record_update = FALSE; +- state = bmc(ptpClock->foreign, rtOpts, ptpClock); ++ state = bmc(ptpClock->foreign, ptpClock); + if(state != ptpClock->port_state) +- toState(state, rtOpts, ptpClock); ++ toState(state, ptpClock); + } + break; + +@@ -121,27 +148,27 @@ void doState(RunTimeOpts *rtOpts, PtpClock *ptpClock) + /* imaginary troubleshooting */ + + DBG("event FAULT_CLEARED\n"); +- toState(PTP_INITIALIZING, rtOpts, ptpClock); ++ toState(PTP_INITIALIZING, ptpClock); + return; + + case PTP_LISTENING: + case PTP_PASSIVE: + case PTP_UNCALIBRATED: + case PTP_SLAVE: +- handle(rtOpts, ptpClock); ++ handle(ptpClock); + + if(timerExpired(SYNC_RECEIPT_TIMER, ptpClock->itimer)) + { + DBG("event SYNC_RECEIPT_TIMEOUT_EXPIRES\n"); + ptpClock->number_foreign_records = 0; + ptpClock->foreign_record_i = 0; +- if(!rtOpts->slaveOnly && ptpClock->clock_stratum != 255) ++ if(!ptpClock->runTimeOpts.slaveOnly && ptpClock->clock_stratum != 255) + { + m1(ptpClock); +- toState(PTP_MASTER, rtOpts, ptpClock); ++ toState(PTP_MASTER, ptpClock); + } + else if(ptpClock->port_state != PTP_LISTENING) +- toState(PTP_LISTENING, rtOpts, ptpClock); ++ toState(PTP_LISTENING, ptpClock); + } + + break; +@@ -150,18 +177,18 @@ void doState(RunTimeOpts *rtOpts, PtpClock *ptpClock) + if(timerExpired(SYNC_INTERVAL_TIMER, ptpClock->itimer)) + { + DBGV("event SYNC_INTERVAL_TIMEOUT_EXPIRES\n"); +- issueSync(rtOpts, ptpClock); ++ issueSync(ptpClock); + } + +- handle(rtOpts, ptpClock); ++ handle(ptpClock); + +- if(rtOpts->slaveOnly || ptpClock->clock_stratum == 255) +- toState(PTP_LISTENING, rtOpts, ptpClock); ++ if(ptpClock->runTimeOpts.slaveOnly || ptpClock->clock_stratum == 255) ++ toState(PTP_LISTENING, ptpClock); + + break; + + case PTP_DISABLED: +- handle(rtOpts, ptpClock); ++ handle(ptpClock); + break; + + default: +@@ -171,10 +198,10 @@ void doState(RunTimeOpts *rtOpts, PtpClock *ptpClock) + } + + /* perform actions required when leaving 'port_state' and entering 'state' */ +-void toState(UInteger8 state, RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void toState(UInteger8 state, PtpClock *ptpClock) + { + ptpClock->message_activity = TRUE; +- ++ + /* leaving state tasks */ + switch(ptpClock->port_state) + { +@@ -184,12 +211,14 @@ void toState(UInteger8 state, RunTimeOpts *rtOpts, PtpClock *ptpClock) + break; + + case PTP_SLAVE: +- initClock(rtOpts, ptpClock); ++ initClock(ptpClock); + break; + + default: + break; + } ++ ++ timeToState(state, ptpClock); + + /* entering state tasks */ + switch(state) +@@ -247,7 +276,7 @@ void toState(UInteger8 state, RunTimeOpts *rtOpts, PtpClock *ptpClock) + case PTP_SLAVE: + DBG("state PTP_PTP_SLAVE\n"); + +- initClock(rtOpts, ptpClock); ++ initClock(ptpClock); + + /* R is chosen to allow a few syncs before we first get a one-way delay estimate */ + /* this is to allow the offset filter to fill for an accurate initial clock reset */ +@@ -271,25 +300,27 @@ void toState(UInteger8 state, RunTimeOpts *rtOpts, PtpClock *ptpClock) + break; + } + +- if(rtOpts->displayStats) +- displayStats(rtOpts, ptpClock); ++ if(ptpClock->runTimeOpts.displayStats) ++ displayStats(ptpClock); + } + + /* check and handle received messages */ +-void handle(RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void handle(PtpClock *ptpClock) + { + int ret; + ssize_t length; + Boolean isFromSelf; ++ Boolean isEvent; ++ Boolean badTime = FALSE; + TimeInternal time = { 0, 0 }; + + if(!ptpClock->message_activity) + { +- ret = netSelect(0, &ptpClock->netPath); ++ ret = netSelect(0, ptpClock); + if(ret < 0) + { + PERROR("failed to poll sockets"); +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ toState(PTP_FAULTY, ptpClock); + return; + } + else if(!ret) +@@ -301,21 +332,25 @@ void handle(RunTimeOpts *rtOpts, PtpClock *ptpClock) + } + + DBGV("handle: something\n"); +- +- length = netRecvEvent(ptpClock->msgIbuf, &time, &ptpClock->netPath); ++ ++ isEvent = TRUE; ++ length = netRecvEvent(ptpClock->msgIbuf, ++ ptpClock->delayedTiming ? NULL : &time, ++ ptpClock); + if(length < 0) + { + PERROR("failed to receive on the event socket"); +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ toState(PTP_FAULTY, ptpClock); + return; + } + else if(!length) + { +- length = netRecvGeneral(ptpClock->msgIbuf, &ptpClock->netPath); ++ isEvent = FALSE; ++ length = netRecvGeneral(ptpClock->msgIbuf, ptpClock); + if(length < 0) + { + PERROR("failed to receive on the general socket"); +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ toState(PTP_FAULTY, ptpClock); + return; + } + else if(!length) +@@ -330,18 +365,36 @@ void handle(RunTimeOpts *rtOpts, PtpClock *ptpClock) + if(length < HEADER_LENGTH) + { + ERROR("message shorter than header length\n"); +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ toState(PTP_FAULTY, ptpClock); + return; + } + + msgUnpackHeader(ptpClock->msgIbuf, &ptpClock->msgTmpHeader); ++ ++ if(isEvent && ptpClock->delayedTiming) ++ { ++ /* query hardware for matching receive time stamp */ ++ if(!getReceiveTime(&time, ptpClock->msgTmpHeader.sourceUuid, ptpClock->msgTmpHeader.sequenceId, ptpClock)) ++ { ++ /* ++ * Incoming packets without hardware time stamp cannot be ignored outright because ++ * a master might only be able to time stamp DelayReq packets; ignoring the Sync ++ * packets from another, better clock would break the clock selection protocol. ++ * Therefore set system time as fallback and decide below what to do. ++ */ ++ DBGV("*** message with no time stamp ***\n"); ++ getTime(&time, ptpClock); ++ badTime = TRUE; ++ } ++ } + +- DBGV("event Receipt of Message\n" ++ DBGV("%s Receipt of Message\n" + " version %d\n" + " type %d\n" + " uuid %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n" + " sequence %d\n" + " time %us %dns\n", ++ isEvent ? "event" : "control", + ptpClock->msgTmpHeader.versionPTP, + ptpClock->msgTmpHeader.control, + ptpClock->msgTmpHeader.sourceUuid[0], ptpClock->msgTmpHeader.sourceUuid[1], +@@ -370,28 +423,28 @@ void handle(RunTimeOpts *rtOpts, PtpClock *ptpClock) + /* subtract the inbound latency adjustment if it is not a loop back and the + time stamp seems reasonable */ + if(!isFromSelf && time.seconds > 0) +- subTime(&time, &time, &rtOpts->inboundLatency); ++ subTime(&time, &time, &ptpClock->runTimeOpts.inboundLatency); + + switch(ptpClock->msgTmpHeader.control) + { + case PTP_SYNC_MESSAGE: +- handleSync(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, &time, isFromSelf, rtOpts, ptpClock); ++ handleSync(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, &time, badTime, isFromSelf, ptpClock); + break; + + case PTP_FOLLOWUP_MESSAGE: +- handleFollowUp(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, rtOpts, ptpClock); ++ handleFollowUp(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, ptpClock); + break; + + case PTP_DELAY_REQ_MESSAGE: +- handleDelayReq(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, &time, isFromSelf, rtOpts, ptpClock); ++ handleDelayReq(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, &time, badTime, isFromSelf, ptpClock); + break; + + case PTP_DELAY_RESP_MESSAGE: +- handleDelayResp(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, rtOpts, ptpClock); ++ handleDelayResp(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, ptpClock); + break; + + case PTP_MANAGEMENT_MESSAGE: +- handleManagement(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, rtOpts, ptpClock); ++ handleManagement(&ptpClock->msgTmpHeader, ptpClock->msgIbuf, length, isFromSelf, ptpClock); + break; + + default: +@@ -400,7 +453,7 @@ void handle(RunTimeOpts *rtOpts, PtpClock *ptpClock) + } + } + +-void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal *time, Boolean isFromSelf, RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal *time, Boolean badTime, Boolean isFromSelf, PtpClock *ptpClock) + { + MsgSync *sync; + TimeInternal originTimestamp; +@@ -408,7 +461,7 @@ void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal + if(length < SYNC_PACKET_LENGTH) + { + ERROR("short sync message\n"); +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ toState(PTP_FAULTY, ptpClock); + return; + } + +@@ -450,6 +503,11 @@ void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal + /* spec recommends handling a sync interval discrepancy as a fault */ + } + ++ /* ++ * TODO: Sync packets without hardware time stamp are rare, but might happen. ++ * Need to decide what to do with the bad default time stamp, similar to handleDelayReq(). ++ */ ++ + ptpClock->sync_receive_time.seconds = time->seconds; + ptpClock->sync_receive_time.nanoseconds = time->nanoseconds; + +@@ -459,8 +517,8 @@ void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal + + toInternalTime(&originTimestamp, &sync->originTimestamp, &ptpClock->halfEpoch); + updateOffset(&originTimestamp, &ptpClock->sync_receive_time, +- &ptpClock->ofm_filt, rtOpts, ptpClock); +- updateClock(rtOpts, ptpClock); ++ &ptpClock->ofm_filt, ptpClock); ++ updateClock(ptpClock); + } + else + { +@@ -471,7 +529,7 @@ void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal + + if(!(--ptpClock->R)) + { +- issueDelayReq(rtOpts, ptpClock); ++ issueDelayReq(ptpClock); + + ptpClock->Q = 0; + ptpClock->R = getRand(&ptpClock->random_seed)%(PTP_DELAY_REQ_INTERVAL - 2) + 2; +@@ -499,15 +557,15 @@ void handleSync(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal + } + else if(ptpClock->port_state == PTP_MASTER && ptpClock->clock_followup_capable) + { +- addTime(time, time, &rtOpts->outboundLatency); +- issueFollowup(time, rtOpts, ptpClock); ++ addTime(time, time, &ptpClock->runTimeOpts.outboundLatency); ++ issueFollowup(time, ptpClock); + } + } + break; + } + } + +-void handleFollowUp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void handleFollowUp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, PtpClock *ptpClock) + { + MsgFollowUp *follow; + TimeInternal preciseOriginTimestamp; +@@ -515,7 +573,7 @@ void handleFollowUp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean i + if(length < FOLLOW_UP_PACKET_LENGTH) + { + ERROR("short folow up message\n"); +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ toState(PTP_FAULTY, ptpClock); + return; + } + +@@ -548,8 +606,8 @@ void handleFollowUp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean i + + toInternalTime(&preciseOriginTimestamp, &follow->preciseOriginTimestamp, &ptpClock->halfEpoch); + updateOffset(&preciseOriginTimestamp, &ptpClock->sync_receive_time, +- &ptpClock->ofm_filt, rtOpts, ptpClock); +- updateClock(rtOpts, ptpClock); ++ &ptpClock->ofm_filt, ptpClock); ++ updateClock(ptpClock); + } + else + { +@@ -563,12 +621,12 @@ void handleFollowUp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean i + } + } + +-void handleDelayReq(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal *time, Boolean isFromSelf, RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void handleDelayReq(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInternal *time, Boolean badTime, Boolean isFromSelf, PtpClock *ptpClock) + { + if(length < DELAY_REQ_PACKET_LENGTH) + { + ERROR("short delay request message\n"); +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ toState(PTP_FAULTY, ptpClock); + return; + } + +@@ -585,7 +643,10 @@ void handleDelayReq(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInter + || header->sourceCommunicationTechnology == PTP_DEFAULT + || ptpClock->clock_communication_technology == PTP_DEFAULT ) + { +- issueDelayResp(time, &ptpClock->msgTmpHeader, rtOpts, ptpClock); ++ if( badTime ) ++ NOTIFY("avoid inaccurate DelayResp because of bad time stamp\n"); ++ else ++ issueDelayResp(time, &ptpClock->msgTmpHeader, ptpClock); + } + + break; +@@ -598,12 +659,12 @@ void handleDelayReq(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInter + ptpClock->delay_req_send_time.seconds = time->seconds; + ptpClock->delay_req_send_time.nanoseconds = time->nanoseconds; + +- addTime(&ptpClock->delay_req_send_time, &ptpClock->delay_req_send_time, &rtOpts->outboundLatency); ++ addTime(&ptpClock->delay_req_send_time, &ptpClock->delay_req_send_time, &ptpClock->runTimeOpts.outboundLatency); + + if(ptpClock->delay_req_receive_time.seconds) + { + updateDelay(&ptpClock->delay_req_send_time, &ptpClock->delay_req_receive_time, +- &ptpClock->owd_filt, rtOpts, ptpClock); ++ &ptpClock->owd_filt, ptpClock); + + ptpClock->delay_req_send_time.seconds = 0; + ptpClock->delay_req_send_time.nanoseconds = 0; +@@ -619,14 +680,14 @@ void handleDelayReq(MsgHeader *header, Octet *msgIbuf, ssize_t length, TimeInter + } + } + +-void handleDelayResp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void handleDelayResp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, PtpClock *ptpClock) + { + MsgDelayResp *resp; + + if(length < DELAY_RESP_PACKET_LENGTH) + { + ERROR("short delay request message\n"); +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ toState(PTP_FAULTY, ptpClock); + return; + } + +@@ -658,7 +719,7 @@ void handleDelayResp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean + if(ptpClock->delay_req_send_time.seconds) + { + updateDelay(&ptpClock->delay_req_send_time, &ptpClock->delay_req_receive_time, +- &ptpClock->owd_filt, rtOpts, ptpClock); ++ &ptpClock->owd_filt, ptpClock); + + ptpClock->delay_req_send_time.seconds = 0; + ptpClock->delay_req_send_time.nanoseconds = 0; +@@ -678,7 +739,7 @@ void handleDelayResp(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean + } + } + +-void handleManagement(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void handleManagement(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean isFromSelf, PtpClock *ptpClock) + { + MsgManagement *manage; + +@@ -705,14 +766,14 @@ void handleManagement(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean + case PTP_MM_GET_PORT_DATA_SET: + case PTP_MM_GET_GLOBAL_TIME_DATA_SET: + case PTP_MM_GET_FOREIGN_DATA_SET: +- issueManagement(header, manage, rtOpts, ptpClock); ++ issueManagement(header, manage, ptpClock); + break; + + default: + ptpClock->record_update = TRUE; +- state = msgUnloadManagement(ptpClock->msgIbuf, manage, ptpClock, rtOpts); ++ state = msgUnloadManagement(ptpClock->msgIbuf, manage, ptpClock); + if(state != ptpClock->port_state) +- toState(state, rtOpts, ptpClock); ++ toState(state, ptpClock); + break; + } + } +@@ -723,25 +784,40 @@ void handleManagement(MsgHeader *header, Octet *msgIbuf, ssize_t length, Boolean + } + + /* pack and send various messages */ +-void issueSync(RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void issueSync(PtpClock *ptpClock) + { + TimeInternal internalTime; + TimeRepresentation originTimestamp; + + ++ptpClock->last_sync_event_sequence_number; + ptpClock->grandmaster_sequence_number = ptpClock->last_sync_event_sequence_number; +- +- getTime(&internalTime); ++ ++ /* try to predict outgoing time stamp */ ++ getTime(&internalTime, ptpClock); + fromInternalTime(&internalTime, &originTimestamp, ptpClock->halfEpoch); +- msgPackSync(ptpClock->msgObuf, FALSE, &originTimestamp, ptpClock); ++ msgPackSync(ptpClock->msgObuf, FALSE, TRUE, &originTimestamp, ptpClock); + +- if(!netSendEvent(ptpClock->msgObuf, SYNC_PACKET_LENGTH, &ptpClock->netPath)) +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ if(!netSendEvent(ptpClock->msgObuf, SYNC_PACKET_LENGTH, ++ ptpClock->delayedTiming ? &internalTime : NULL, ++ ptpClock)) ++ toState(PTP_FAULTY, ptpClock); + else ++ { + DBGV("sent sync message\n"); ++ if(ptpClock->delayedTiming) ++ { ++ if (internalTime.seconds || internalTime.nanoseconds) { ++ /* compensate with configurable latency, then tell client real time stamp */ ++ addTime(&internalTime, &internalTime, &ptpClock->runTimeOpts.outboundLatency); ++ issueFollowup(&internalTime, ptpClock); ++ } else { ++ NOTIFY("WARNING: sync message without hardware time stamp, skipped followup\n"); ++ } ++ } ++ } + } + +-void issueFollowup(TimeInternal *time, RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void issueFollowup(TimeInternal *time, PtpClock *ptpClock) + { + TimeRepresentation preciseOriginTimestamp; + +@@ -750,46 +826,62 @@ void issueFollowup(TimeInternal *time, RunTimeOpts *rtOpts, PtpClock *ptpClock) + fromInternalTime(time, &preciseOriginTimestamp, ptpClock->halfEpoch); + msgPackFollowUp(ptpClock->msgObuf, ptpClock->last_sync_event_sequence_number, &preciseOriginTimestamp, ptpClock); + +- if(!netSendGeneral(ptpClock->msgObuf, FOLLOW_UP_PACKET_LENGTH, &ptpClock->netPath)) +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ if(!netSendGeneral(ptpClock->msgObuf, FOLLOW_UP_PACKET_LENGTH, ptpClock)) ++ toState(PTP_FAULTY, ptpClock); + else + DBGV("sent followup message\n"); + } + +-void issueDelayReq(RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void issueDelayReq(PtpClock *ptpClock) + { + TimeInternal internalTime; + TimeRepresentation originTimestamp; + + ptpClock->sentDelayReq = TRUE; + ptpClock->sentDelayReqSequenceId = ++ptpClock->last_sync_event_sequence_number; +- +- getTime(&internalTime); ++ ++ /* try to predict outgoing time stamp */ ++ getTime(&internalTime, ptpClock); + fromInternalTime(&internalTime, &originTimestamp, ptpClock->halfEpoch); +- msgPackDelayReq(ptpClock->msgObuf, FALSE, &originTimestamp, ptpClock); ++ msgPackDelayReq(ptpClock->msgObuf, FALSE, FALSE, &originTimestamp, ptpClock); + +- if(!netSendEvent(ptpClock->msgObuf, DELAY_REQ_PACKET_LENGTH, &ptpClock->netPath)) +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ if(!netSendEvent(ptpClock->msgObuf, DELAY_REQ_PACKET_LENGTH, ++ ptpClock->delayedTiming ? &internalTime : NULL, ++ ptpClock)) ++ toState(PTP_FAULTY, ptpClock); + else ++ { + DBGV("sent delay request message\n"); ++ if(ptpClock->delayedTiming) ++ { ++ if (internalTime.seconds || internalTime.nanoseconds) { ++ /* compensate with configurable latency, then store for later use */ ++ addTime(&internalTime, &internalTime, &ptpClock->runTimeOpts.outboundLatency); ++ ptpClock->delay_req_send_time = internalTime; ++ } else { ++ NOTIFY("WARNING: delay request message without hardware time stamp, will skip response\n"); ++ ptpClock->sentDelayReq = FALSE; ++ } ++ } ++ } + } + +-void issueDelayResp(TimeInternal *time, MsgHeader *header, RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void issueDelayResp(TimeInternal *time, MsgHeader *header, PtpClock *ptpClock) + { + TimeRepresentation delayReceiptTimestamp; + + ++ptpClock->last_general_event_sequence_number; +- ++ + fromInternalTime(time, &delayReceiptTimestamp, ptpClock->halfEpoch); + msgPackDelayResp(ptpClock->msgObuf, header, &delayReceiptTimestamp, ptpClock); + +- if(!netSendGeneral(ptpClock->msgObuf, DELAY_RESP_PACKET_LENGTH, &ptpClock->netPath)) +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ if(!netSendGeneral(ptpClock->msgObuf, DELAY_RESP_PACKET_LENGTH, ptpClock)) ++ toState(PTP_FAULTY, ptpClock); + else + DBGV("sent delay response message\n"); + } + +-void issueManagement(MsgHeader *header, MsgManagement *manage, RunTimeOpts *rtOpts, PtpClock *ptpClock) ++void issueManagement(MsgHeader *header, MsgManagement *manage, PtpClock *ptpClock) + { + UInteger16 length; + +@@ -798,8 +890,8 @@ void issueManagement(MsgHeader *header, MsgManagement *manage, RunTimeOpts *rtOp + if(!(length = msgPackManagementResponse(ptpClock->msgObuf, header, manage, ptpClock))) + return; + +- if(!netSendGeneral(ptpClock->msgObuf, length, &ptpClock->netPath)) +- toState(PTP_FAULTY, rtOpts, ptpClock); ++ if(!netSendGeneral(ptpClock->msgObuf, length, ptpClock)) ++ toState(PTP_FAULTY, ptpClock); + else + DBGV("sent management message\n"); + } +diff --git a/src/ptpd.c b/src/ptpd.c +index cb98438..b19ec63 100644 +--- a/src/ptpd.c ++++ b/src/ptpd.c +@@ -19,6 +19,7 @@ int main(int argc, char **argv) + rtOpts.inboundLatency.nanoseconds = DEFAULT_INBOUND_LATENCY; + rtOpts.outboundLatency.nanoseconds = DEFAULT_OUTBOUND_LATENCY; + rtOpts.noResetClock = DEFAULT_NO_RESET_CLOCK; ++ rtOpts.noAdjust = DEFAULT_NO_ADJUST_CLOCK; + rtOpts.s = DEFAULT_DELAY_S; + rtOpts.ap = DEFAULT_AP; + rtOpts.ai = DEFAULT_AI; +@@ -30,12 +31,12 @@ int main(int argc, char **argv) + + if(rtOpts.probe) + { +- probe(&rtOpts, ptpClock); ++ probe(ptpClock); + } + else + { + /* do the protocol engine */ +- protocol(&rtOpts, ptpClock); ++ protocol(ptpClock); + } + + ptpdShutdown(); +diff --git a/src/ptpd.h b/src/ptpd.h +index 55cc3d9..95915fa 100644 +--- a/src/ptpd.h ++++ b/src/ptpd.h +@@ -20,16 +20,16 @@ void addTime(TimeInternal*,TimeInternal*,TimeInternal*); + void subTime(TimeInternal*,TimeInternal*,TimeInternal*); + + /* bmc.c */ +-UInteger8 bmc(ForeignMasterRecord*,RunTimeOpts*,PtpClock*); ++UInteger8 bmc(ForeignMasterRecord*,PtpClock*); + void m1(PtpClock*); + void s1(MsgHeader*,MsgSync*,PtpClock*); +-void initData(RunTimeOpts*,PtpClock*); ++void initData(PtpClock*); + + /* probe.c */ +-void probe(RunTimeOpts*,PtpClock*); ++void probe(PtpClock*); + + /* protocol.c */ +-void protocol(RunTimeOpts*,PtpClock*); ++void protocol(PtpClock*); + + + #endif diff --git a/ptpd.changes b/ptpd.changes index 6861940..148a351 100644 --- a/ptpd.changes +++ b/ptpd.changes @@ -1,3 +1,37 @@ +------------------------------------------------------------------- +Mon Oct 19 12:59:53 MDT 2009 - alext@suse.de + +- Update to ptpd git snapshot of 599b03b post 1.0.0. release, includes + patches by Patrick Ohly from the ptpd git repo as follows: + +599b03b... added basic compilation instructions +ef2a5cc... Linux HW time stamping: define fallbacks no longer in net_tstamp.h, + need them in user space to compile +f837c84... netShutdown(): check whether it is called during init and avoid + SIOCSHWTSTAMP with invalid eventSock +70181a1... Linux: use generic net_tstamp API for more accurate time stamping, + with and without HW support +e003660... Merge branch 'ptpd-1.0.0-cluster-clock' +c6eb9c8... timertest: ping-pong test which measures clock skew between nodes +9275c8d... adjtimex call: added error checking +03085e7... use ajdtimex() tick adjustment to increase range of total clock + adjustment +576ffbf... fixed and extended printing of statistics +dc61251... include time in initialization of random seed for delay requests: + otherwise different runs use the same intervals +c380fa7... allow much higher frequency adjustments +7523072... added implementation of E1000 NIC time control +96beda4... optionally set PTP_ASSIST in msgPackDelayReq() +7ad1b32... *always* set PTP_ASSIST in Sync message +3444e67... configuration file for doxygen +864a492... separated system time and time which is controlled/used by PTP + - added Doxygen-style comments + - time.c accesses PTP time (currently always uses system time) + - timer.c controls delays in the host's system time (as before) + - moved nanoSleep() to be consistent +3762491... added the possibility to log into system log: use -f syslog +b8e2d39... fix ubild on freebsd + ------------------------------------------------------------------- Fri Jun 12 14:07:27 CEST 2009 - coolo@novell.com diff --git a/ptpd.spec b/ptpd.spec index 36a2bae..077b1ce 100644 --- a/ptpd.spec +++ b/ptpd.spec @@ -21,14 +21,15 @@ Name: ptpd Summary: Implements the Precision Time protocol as defined by IEEE 1588 standard Version: 1.0.0 -Release: 2 -License: BSD 3-Clause +Release: 3 +License: BSD 3-clause (or similar) Group: System/Daemons Source0: %{name}-%{version}.tar.bz2 Source1: conf.ptpd.init Source2: conf.sysconfig.ptpd -Patch0: glibc210.patch -Patch1: append_to_cflags.patch +Patch0: ptpd-1.0.0-git599b03b.patch +Patch1: glibc210.patch +Patch2: append_to_cflags.patch Url: http://ptpd.sourceforge.net/ BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -60,7 +61,8 @@ Authors: %prep %setup -q %patch0 -p1 -%patch1 +%patch1 -p1 +%patch2 %build cd src @@ -107,17 +109,3 @@ rm -rf ${RPM_BUILD_ROOT} %{insserv_cleanup} %changelog -* Fri Jun 12 2009 coolo@novell.com -- fix build with glibc 2.10 -* Wed Nov 12 2008 dbahi@suse.de -- modified to use 1.0.0 release instead of rc -* Wed Nov 07 2007 dbahi@suse.de -- cleaned up init script and install process -* Fri Nov 02 2007 dbahi@suse.de -- patch makefile to allow env CFLAGS -- adjusted sysconfig to use %%fill_only -* Fri Nov 02 2007 dbahi@suse.de -- added system v init script and sysconfig file suitable - for use with yast /etc/sysconfig editor -* Tue Oct 23 2007 dbahi@suse.de -- initial package creation