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