diff --git a/kernel/Doxyfile b/kernel/Doxyfile new file mode 100644 index 0000000..21147d4 --- /dev/null +++ b/kernel/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 = atomthreads + +# 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 = doxygen-kernel + +# 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 = 4 + +# 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 = NO + +# 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 = NO + +# 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 = "../../kernel" + +# 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 = NO + +# 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 = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# 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 = YES + +# 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 = NO + +# 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/kernel/atom.h b/kernel/atom.h new file mode 100755 index 0000000..faa0bdb --- /dev/null +++ b/kernel/atom.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef __ATOM_H +#define __ATOM_H + +#include "atomtimer.h" +#include "atomuser.h" + +/* Data types */ + +/* Forward declaration */ +struct atom_tcb; + +typedef struct atom_tcb +{ + /* Thread's current stack pointer. When a thread is scheduled + * out the architecture port can save*/ + POINTER sp_save_ptr; + + /* Thread priority (0-255) */ + uint8_t priority; + + /* Thread entry point and parameter */ + void (*entry_point)(uint32_t); + uint32_t entry_param; + + /* Queue pointers */ + struct atom_tcb *prev_tcb; /* Previous TCB in doubly-linked TCB list */ + struct atom_tcb *next_tcb; /* Next TCB in doubly-linked list */ + + /* Suspension data */ + uint8_t suspended; /* TRUE if task is currently suspended */ + uint8_t suspend_wake_status; /* Status returned to woken suspend calls */ + ATOM_TIMER *suspend_timo_cb; /* Callback registered for suspension timeouts */ + +} ATOM_TCB; + + +/* Global data */ +extern ATOM_TCB *tcbReadyQ; +extern uint8_t atomOSStarted; + + +/* Constants */ +#define TRUE 1 +#define FALSE 0 + +/* Error values */ + +#define ATOM_OK 0 +#define ATOM_ERROR 1 +#define ATOM_TIMEOUT 2 +#define ATOM_WOULDBLOCK 3 +#define ATOM_ERR_CONTEXT 200 +#define ATOM_ERR_PARAM 201 +#define ATOM_ERR_DELETED 202 +#define ATOM_ERR_OVF 203 +#define ATOM_ERR_QUEUE 204 +#define ATOM_ERR_TIMER 205 +#define ATOM_ERR_NOT_FOUND 206 +#define ATOM_ERR_OWNERSHIP 207 + +/* Idle thread priority (lowest) */ +#define IDLE_THREAD_PRIORITY 255 + + +/* Function prototypes */ +extern uint8_t atomOSInit (void *idle_thread_stack_top); +extern void atomOSStart (void); + +extern void atomSched (uint8_t timer_tick); + +extern void atomIntEnter (void); +extern void atomIntExit (uint8_t timer_tick); + +extern uint8_t tcbEnqueuePriority (ATOM_TCB **tcb_queue_ptr, ATOM_TCB *tcb_ptr); +extern ATOM_TCB *tcbDequeueHead (ATOM_TCB **tcb_queue_ptr); +extern ATOM_TCB *tcbDequeueEntry (ATOM_TCB **tcb_queue_ptr, ATOM_TCB *tcb_ptr); +extern ATOM_TCB *tcbDequeuePriority (ATOM_TCB **tcb_queue_ptr, uint8_t priority); + +extern ATOM_TCB *atomCurrentContext (void); + +extern uint8_t atomThreadCreate (ATOM_TCB *tcb_ptr, uint8_t priority, void (*entry_point)(uint32_t), uint32_t entry_param, void *stack_top); + +extern void archContextSwitch (ATOM_TCB *old_tcb_ptr, ATOM_TCB *new_tcb_ptr); +extern void archThreadContextInit (ATOM_TCB *tcb_ptr, void *stack_top, void (*entry_point)(uint32_t), uint32_t entry_param); +extern void archFirstThreadRestore(ATOM_TCB *new_tcb_ptr); + +extern void atomTimerTick (void); + + +#endif /* __ATOM_H */ diff --git a/kernel/atomkernel.c b/kernel/atomkernel.c new file mode 100755 index 0000000..6e3bb5e --- /dev/null +++ b/kernel/atomkernel.c @@ -0,0 +1,796 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#include +#include "atom.h" +#include "atomuser.h" + + +/* Global data */ + +/** + * This is the head of the queue of threads that are ready to run. It is + * ordered by priority, with the higher priority threads coming first. + * Where there are multiple threads of the same priority, the TCB pointers + * are FIFO-ordered. + * + * Dequeuing the head is a fast operation because the list is ordered. + * Enqueuing may have to walk up to the end of the list. This means that + * context-switch times depend on the number of threads on the ready queue, + * but efficient use is made of available RAM on tiny systems by avoiding + * priority tables etc. This scheme can be easily swapped out for other + * scheduler schemes by replacing the TCB enqueue and dequeue functions. + * + * Once a thread is scheduled in, it is not present on the ready queue while + * it is running. When scheduled out it will be either placed back on the + * ready queue, or will be suspended on some OS primitive (e.g. on the + * suspended TCB queue for a semaphore, or in the timer list if suspended on + * a timer delay). + */ +ATOM_TCB *tcbReadyQ = NULL; + +/** Set to TRUE when OS is started and running threads */ +uint8_t atomOSStarted = FALSE; + + +/* Local data */ + +/** This is a pointer to the TCB for the currently-running thread */ +static ATOM_TCB *curr_tcb = NULL; + +/** Storage for the idle thread's TCB */ +static ATOM_TCB idle_tcb; + +/* Number of nested interrupts */ +static int atomIntCnt = 0; + + +/* Forward declarations */ +static void atomThreadSwitch(ATOM_TCB *old_tcb, ATOM_TCB *new_tcb); +static void atomIdleThread (uint32_t data); + + +/** + * \b atomSched + * + * This is an internal function not for use by application code. + * + * This is the main scheduler routine. It is called by the various OS + * library routines to check if any threads should be scheduled in now. + * If so, the context will be switched from the current thread to the + * new one. + * + * The scheduler is priority-based with round-robin performed on threads + * with the same priority. Round-robin is only performed on timer ticks + * however. During reschedules caused by an OS operation (e.g. after + * giving or taking a semaphore) we only allow the scheduling in of + * threads with higher priority than current priority. On timer ticks we + * also allow the scheduling of same-priority threads - in that case we + * schedule in the head of the ready list for that priority and put the + * current thread at the tail. + * + * @param[in] timer_tick Should be TRUE when called from the system tick + * + * @return None + */ +void atomSched (uint8_t timer_tick) +{ + CRITICAL_STORE; + ATOM_TCB *new_tcb = NULL; + int16_t lowest_pri; + + /** + * Check the OS has actually started. As long as the proper initialisation + * sequence is followed there should be no calls here until the OS is + * started, but we check to handle badly-behaved ports. + */ + if (atomOSStarted == FALSE) + { + /* Don't schedule anything in until the OS is started */ + return; + } + + /* Enter critical section */ + CRITICAL_START (); + + /** + * If the current thread is going into suspension, then + * unconditionally dequeue the next thread for execution. + */ + if (curr_tcb->suspended == TRUE) + { + /** + * Dequeue the next ready to run thread. There will always be + * at least the idle thread waiting. Note that this could + * actually be the suspending thread if it was unsuspended + * before the scheduler was called. + */ + new_tcb = tcbDequeueHead (&tcbReadyQ); + + /** + * Don't need to add the current thread to any queue because + * it was suspended by another OS mechanism and will be + * sitting on a suspend queue or similar within one of the OS + * primitive libraries (e.g. semaphore). + */ + + /* Switch to the new thread */ + atomThreadSwitch (curr_tcb, new_tcb); + } + + /** + * Otherwise the current thread is still ready, but check + * if any other threads are ready. + */ + else + { + /* Calculate which priority is allowed to be scheduled in */ + if (timer_tick == TRUE) + { + /* Same priority or higher threads can preempt */ + lowest_pri = (int16_t)curr_tcb->priority; + } + else if (curr_tcb->priority > 0) + { + /* Only higher priority threads can preempt, invalid for 0 (highest) */ + lowest_pri = (int16_t)(curr_tcb->priority - 1); + } + else + { + /** + * Current priority is already highest (0), don't allow preempt by + * threads of any priority because this is not a time-slice. + */ + lowest_pri = -1; + } + + /* Check if a reschedule is allowed */ + if (lowest_pri >= 0) + { + /* Check for a thread at the given minimum priority level or higher */ + new_tcb = tcbDequeuePriority (&tcbReadyQ, (uint8_t)lowest_pri); + + /* If a thread was found, schedule it in */ + if (new_tcb) + { + /* Add the current thread to the ready queue */ + (void)tcbEnqueuePriority (&tcbReadyQ, curr_tcb); + + /* Switch to the new thread */ + atomThreadSwitch (curr_tcb, new_tcb); + } + } + } + + /* Exit critical section */ + CRITICAL_END (); +} + + +/** + * \b atomThreadSwitch + * + * This is an internal function not for use by application code. + * + * The function is called by the scheduler to perform a context switch. + * Execution will switch to the new thread's context, therefore the + * function doesn't actually return until the old thread is scheduled + * back in. + * + * @param[in] old_tcb Pointer to TCB for thread being scheduled out + * @param[in] new_tcb Pointer to TCB for thread being scheduled in + * + * @return None + */ +static void atomThreadSwitch(ATOM_TCB *old_tcb, ATOM_TCB *new_tcb) +{ + /** + * Check if the new thread is actually the current one, in which + * case we don't need to do any context switch. This can happen + * if a thread goes into suspend but is unsuspended again before + * it is fully scheduled out. + */ + if (old_tcb != new_tcb) + { + /* Set the new currently-running thread pointer */ + curr_tcb = new_tcb; + + /* Call the architecture-specific context switch */ + archContextSwitch (old_tcb, new_tcb); + } + + /** + * The context switch shifted execution to a different thread. By the time + * we get back here, we are running in old_tcb context again. Clear its + * suspend status now that we're back. + */ + old_tcb->suspended = FALSE; + +} + + +/** + * \b atomThreadCreate + * + * Creates and starts a new thread. + * + * Callers provide the ATOM_TCB structure storage, these are not obtained + * from an internal TCB free list. + * + * The function puts the new thread on the ready queue and calls the + * scheduler. If the priority is higher than the current priority, then the + * new thread may be scheduled in before the function returns. + * + * @param[in] tcb_ptr Pointer to the thread's TCB storage + * @param[in] priority Priority of the thread (0 to 255) + * @param[in] entry_point Thread entry point + * @param[in] entry_param Parameter passed to thread entry point + * @param[in] stack_top Top of the stack area + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_PARAM Bad parameters + * @retval ATOM_ERR_QUEUE Error putting the thread on the ready queue + */ +uint8_t atomThreadCreate (ATOM_TCB *tcb_ptr, uint8_t priority, void (*entry_point)(uint32_t), uint32_t entry_param, void *stack_top) +{ + CRITICAL_STORE; + uint8_t status; + + if ((tcb_ptr == NULL) || (entry_point == NULL) || (stack_top == NULL)) + { + /* Bad parameters */ + status = ATOM_ERR_PARAM; + } + else + { + + /* Set up the TCB initial values */ + tcb_ptr->suspended = FALSE; + tcb_ptr->priority = priority; + tcb_ptr->prev_tcb = NULL; + tcb_ptr->next_tcb = NULL; + tcb_ptr->suspend_timo_cb = NULL; + + /** + * Store the thread entry point and parameter in the TCB. This may + * not be necessary for all architecture ports if they put all of + * this information in the initial thread stack. + */ + tcb_ptr->entry_point = entry_point; + tcb_ptr->entry_param = entry_param; + + /** + * Call the arch-specific routine to set up the stack. This routine + * is responsible for creating the context save area necessary for + * allowing atomThreadSwitch() to schedule it in. The initial + * archContextSwitch() call when this thread gets scheduled in the + * first time will then restore the program counter to the thread + * entry point, and any other necessary register values ready for + * it to start running. + */ + archThreadContextInit (tcb_ptr, stack_top, entry_point, entry_param); + + /* Protect access to the OS queue */ + CRITICAL_START (); + + /* Put this thread on the ready queue */ + if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* Queue-related error */ + status = ATOM_ERR_QUEUE; + } + else + { + /* Exit critical region */ + CRITICAL_END (); + + /** + * If the OS is started and we're in thread context, check if we + * should be scheduled in now. + */ + if ((atomOSStarted == TRUE) && atomCurrentContext()) + atomSched (FALSE); + + /* Success */ + status = ATOM_OK; + } + } + + return (status); +} + + +/** + * \b atomIntEnter + * + * Interrupt handler entry routine. + * + * Must be called at the start of any interrupt handlers that may + * call an OS primitive and make a thread ready. + * + * @return None + */ +void atomIntEnter (void) +{ + /* Increment the interrupt count */ + atomIntCnt++; +} + + +/** + * \b atomIntExit + * + * Interrupt handler exit routine. + * + * Must be called at the end of any interrupt handlers that may + * call an OS primitive and make a thread ready. + * + * This is responsible for calling the scheduler at the end of + * interrupt handlers to determine whether a new thread has now + * been made ready and should be scheduled in. + * + * @param timer_tick TRUE if this is a timer tick + * + * @return None + */ +void atomIntExit (uint8_t timer_tick) +{ + /* Decrement the interrupt count */ + atomIntCnt--; + + /* Call the scheduler */ + atomSched (timer_tick); +} + + +/** + * \b atomCurrentContext + * + * Get the current thread context. + * + * Returns a pointer to the current thread's TCB, or NULL if not in + * thread-context (in interrupt context). + * + * @retval Pointer to current thread's TCB, NULL if in interrupt context + */ +ATOM_TCB *atomCurrentContext (void) +{ + /* Return the current thread's TCB or NULL if in interrupt context */ + if (atomIntCnt == 0) + return (curr_tcb); + else + return (NULL); +} + + +/** + * \b atomOSInit + * + * Initialise the atomthreads OS. + * + * Must be called before any application code uses the atomthreads APIs. No + * threads are actually started until the application calls atomOSStart(). + * + * Callers must provide a pointer to some storage for the idle thread stack. + * The caller is responsible for calculating the appropriate space required + * for their particular architecture. + * + * Applications should use the following initialisation sequence: + * + * -> Call atomOSInit() before calling any atomthreads APIs + * -> Arrange for a timer to call atomTimerTick() periodically + * -> Create one or more application threads using atomThreadCreate() + * -> Start the OS using atomOSStart(). At this point the highest + * priority application thread created will be started. + * + * Interrupts should be disabled until the first thread restore is complete, + * to avoid any complications due to interrupts occurring while crucial + * operating system facilities are being initialised. They are normally + * enabled by the archFirstThreadRestore() routine in the architecture port. + * + * @param[in] idle_thread_stack_top Ptr to top of stack area for idle thread + * + * @retval ATOM_OK Success + * @retval ATOM_ERROR Initialisation error + */ +uint8_t atomOSInit (void *idle_thread_stack_top) +{ + uint8_t status; + + /* Initialise data */ + curr_tcb = NULL; + tcbReadyQ = NULL; + atomOSStarted = FALSE; + + /* Create the idle thread */ + status = atomThreadCreate(&idle_tcb, + IDLE_THREAD_PRIORITY, + atomIdleThread, + 0, + idle_thread_stack_top); + + /* Return status */ + return (status); + +} +/** + * \b atomOSStart + * + * Start the highest priority thread running. + * + * This function must be called after all OS initialisation is complete, and + * at least one application thread has been created. It will start executing + * the highest priority thread created (or first created if multiple threads + * share the highest priority). + * + * Interrupts must still be disabled at this point. They must only be enabled + * when the first thread is restored and started by the architecture port's + * archFirstThreadRestore() routine. + * + * @return None + */ +void atomOSStart (void) +{ + ATOM_TCB *new_tcb; + + /** + * Enable the OS started flag. This stops routines like atomThreadCreate() + * attempting to schedule in a newly-created thread until the scheduler is + * up and running. + */ + atomOSStarted = TRUE; + + /** + * Application calls to atomThreadCreate() should have added at least one + * thread to the ready queue. Take the highest priority one off and + * schedule it in. If no threads were created, the OS will simply start + * the idle thread (the lowest priority allowed to be scheduled is the + * idle thread's priority, 255). + */ + new_tcb = tcbDequeuePriority (&tcbReadyQ, 255); + if (new_tcb) + { + /* Set the new currently-running thread pointer */ + curr_tcb = new_tcb; + + /* Restore and run the first thread */ + archFirstThreadRestore (new_tcb); + + /* Never returns to here, execution shifts to new thread context */ + } + else + { + /* No ready threads were found. atomOSInit() probably was not called */ + } + +} + + +/** + * \b atomIdleThread + * + * Entry point for idle thread. + * + * This thread must always be present, and will be the thread executed when + * no other threads are ready to run. It must not call any library routines + * which would cause it to block. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void atomIdleThread (uint32_t data) +{ + /* Loop forever */ + while (1) + { + /** \todo Provide user idle hooks*/ + } +} + + +/** + * \b tcbEnqueuePriority + * + * This is an internal function not for use by application code. + * + * Enqueues the TCB \c tcb_ptr on the TCB queue pointed to by \c tcb_queue_ptr. + * TCBs are placed on the queue in priority order. If there are existing TCBs + * at the same priority as the TCB to be enqueued, the enqueued TCB will be + * placed at the end of the same-priority TCBs. Calls to tcbDequeuePriority() + * will dequeue same-priority TCBs in FIFO order. + * + * \c tcb_queue_ptr may be modified by the routine if the enqueued TCB becomes + * the new list head. It is valid for tcb_queue_ptr to point to a NULL pointer, + * which is the case if the queue is currently empty. + * + * \b NOTE: Assumes that the caller is already in a critical section. + * + * @param[in,out] tcb_queue_ptr Pointer to TCB queue head pointer + * @param[in] tcb_ptr Pointer to TCB to enqueue + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_PARAM Bad parameters + */ +uint8_t tcbEnqueuePriority (ATOM_TCB **tcb_queue_ptr, ATOM_TCB *tcb_ptr) +{ + uint8_t status; + ATOM_TCB *prev_ptr, *next_ptr; + + /* Parameter check */ + if ((tcb_queue_ptr == NULL) || (tcb_ptr == NULL)) + { + /* Return error */ + status = ATOM_ERR_PARAM; + } + else + { + /* Walk the list and enqueue at the end of the TCBs at this priority */ + prev_ptr = next_ptr = *tcb_queue_ptr; + do + { + /* Insert if: + * next_ptr = NULL (we're at the head of an empty queue or at the tail) + * the next TCB in the list is lower priority than the one we're enqueuing. + */ + if ((next_ptr == NULL) || (next_ptr->priority > tcb_ptr->priority)) + { + /* Make this TCB the new listhead */ + if (next_ptr == *tcb_queue_ptr) + { + *tcb_queue_ptr = tcb_ptr; + tcb_ptr->prev_tcb = NULL; + tcb_ptr->next_tcb = next_ptr; + if (next_ptr) + next_ptr->prev_tcb = tcb_ptr; + } + /* Insert between two TCBs or at the tail */ + else + { + tcb_ptr->prev_tcb = prev_ptr; + tcb_ptr->next_tcb = next_ptr; + prev_ptr->next_tcb = tcb_ptr; + if (next_ptr) + next_ptr->prev_tcb = tcb_ptr; + } + + /* Quit the loop, we've finished inserting */ + break; + } + else + { + /* Not inserting here, try the next one */ + prev_ptr = next_ptr; + next_ptr = next_ptr->next_tcb; + } + + } + while (prev_ptr != NULL); + + /* Successful */ + status = ATOM_OK; + } + + return (status); +} + + +/** + * \b tcbDequeueHead + * + * This is an internal function not for use by application code. + * + * Dequeues the highest priority TCB on the queue pointed to by + * \c tcb_queue_ptr. + * + * The TCB will be removed from the queue. Same priority TCBs are dequeued in + * FIFO order. + * + * \c tcb_queue_ptr will be modified by the routine if a TCB is dequeued, + * as this will be the list head. It is valid for tcb_queue_ptr to point to a + * NULL pointer, which is the case if the queue is currently empty. In this + * case the function returns NULL. + * + * \b NOTE: Assumes that the caller is already in a critical section. + * + * @param[in,out] tcb_queue_ptr Pointer to TCB queue head pointer + * + * @return Pointer to highest priority TCB on queue, or NULL if queue empty + */ +ATOM_TCB *tcbDequeueHead (ATOM_TCB **tcb_queue_ptr) +{ + ATOM_TCB *ret_ptr; + + /* Parameter check */ + if (tcb_queue_ptr == NULL) + { + /* Return NULL */ + ret_ptr = NULL; + } + /* Check for an empty queue */ + else if (*tcb_queue_ptr == NULL) + { + /* Return NULL */ + ret_ptr = NULL; + } + /* Remove and return the listhead */ + else + { + ret_ptr = *tcb_queue_ptr; + *tcb_queue_ptr = ret_ptr->next_tcb; + if (*tcb_queue_ptr) + (*tcb_queue_ptr)->prev_tcb = NULL; + ret_ptr->next_tcb = ret_ptr->prev_tcb = NULL; + } + + return (ret_ptr); +} + + +/** + * \b tcbDequeueEntry + * + * This is an internal function not for use by application code. + * + * Dequeues a particular TCB from the queue pointed to by \c tcb_queue_ptr. + * + * The TCB will be removed from the queue. + * + * \c tcb_queue_ptr may be modified by the routine if the dequeued TCB was + * the list head. It is valid for tcb_queue_ptr to point to a NULL pointer, + * which is the case if the queue is currently empty. In this case the + * function returns NULL. + * + * \b NOTE: Assumes that the caller is already in a critical section. + * + * @param[in,out] tcb_queue_ptr Pointer to TCB queue head pointer + * @param[in] tcb_ptr Pointer to TCB to dequeue + * + * @return Pointer to the dequeued TCB, or NULL if entry wasn't found + */ +ATOM_TCB *tcbDequeueEntry (ATOM_TCB **tcb_queue_ptr, ATOM_TCB *tcb_ptr) +{ + ATOM_TCB *ret_ptr, *prev_ptr, *next_ptr; + + /* Parameter check */ + if (tcb_queue_ptr == NULL) + { + /* Return NULL */ + ret_ptr = NULL; + } + /* Check for an empty queue */ + else if (*tcb_queue_ptr == NULL) + { + /* Return NULL */ + ret_ptr = NULL; + } + /* Find and remove/return the specified entry */ + else + { + ret_ptr = NULL; + prev_ptr = next_ptr = *tcb_queue_ptr; + while (next_ptr) + { + /* Is this entry the one we're looking for? */ + if (next_ptr == tcb_ptr) + { + if (next_ptr == *tcb_queue_ptr) + { + /* We're removing the list head */ + *tcb_queue_ptr = next_ptr->next_tcb; + if (*tcb_queue_ptr) + (*tcb_queue_ptr)->prev_tcb = NULL; + } + else + { + /* We're removing a mid or tail TCB */ + prev_ptr->next_tcb = next_ptr->next_tcb; + if (next_ptr->next_tcb) + next_ptr->next_tcb->prev_tcb = prev_ptr; + } + ret_ptr = next_ptr; + ret_ptr->prev_tcb = ret_ptr->next_tcb = NULL; + break; + } + + /* Move on to the next in the list */ + prev_ptr = next_ptr; + next_ptr = next_ptr->next_tcb; + } + } + + return (ret_ptr); +} + + +/** + * \b tcbDequeuePriority + * + * This is an internal function not for use by application code. + * + * Dequeues the first TCB of the given priority or higher, from the queue + * pointed to by \c tcb_queue_ptr. Because the queue is ordered high priority + * first, we only ever dequeue the list head, if any. If the list head is + * lower priority than we wish to dequeue, then all following ones will also + * be lower priority and hence are not parsed. + * + * The TCB will be removed from the queue. Same priority TCBs will be dequeued + * in FIFO order. + * + * \c tcb_queue_ptr may be modified by the routine if the dequeued TCB was + * the list head. It is valid for tcb_queue_ptr to point to a NULL pointer, + * which is the case if the queue is currently empty. In this case the + * function returns NULL. + * + * \b NOTE: Assumes that the caller is already in a critical section. + * + * @param[in,out] tcb_queue_ptr Pointer to TCB queue head pointer + * @param[in] priority Minimum priority to qualify for dequeue + * + * @return Pointer to the dequeued TCB, or NULL if none found within priority + */ +ATOM_TCB *tcbDequeuePriority (ATOM_TCB **tcb_queue_ptr, uint8_t priority) +{ + ATOM_TCB *ret_ptr; + + /* Parameter check */ + if (tcb_queue_ptr == NULL) + { + /* Return NULL */ + ret_ptr = NULL; + } + /* Check for an empty queue */ + else if (*tcb_queue_ptr == NULL) + { + /* Return NULL */ + ret_ptr = NULL; + } + /* Check if the list head priority is within our range */ + else if ((*tcb_queue_ptr)->priority <= priority) + { + /* Remove the list head */ + ret_ptr = *tcb_queue_ptr; + *tcb_queue_ptr = (*tcb_queue_ptr)->next_tcb; + if (*tcb_queue_ptr) + { + (*tcb_queue_ptr)->prev_tcb = NULL; + ret_ptr->next_tcb = NULL; + } + } + else + { + /* No higher priority ready threads found */ + ret_ptr = NULL; + } + + return (ret_ptr); +} \ No newline at end of file diff --git a/kernel/atommutex.c b/kernel/atommutex.c new file mode 100755 index 0000000..dc40b8c --- /dev/null +++ b/kernel/atommutex.c @@ -0,0 +1,645 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#include +#include "atom.h" +#include "atommutex.h" +#include "atomtimer.h" +#include "atomuser.h" + + +/* Local data types */ + +typedef struct mutex_timer +{ + ATOM_TCB *tcb_ptr; /* Thread which is suspended with timeout */ + ATOM_MUTEX *mutex_ptr; /* Mutex the thread is suspended on */ +} MUTEX_TIMER; + + +/* Forward declarations */ + +static void atomMutexTimerCallback (POINTER cb_data); + + +/** + * \b atomMutexCreate + * + * Initialises a mutex object. + * + * Must be called before calling any other mutex library routines on a + * mutex. Objects can be deleted later using atomMutexDelete(). + * + * Does not set the owner of a mutex. atomMutexGet() must be called after + * creation in order to actually take ownership. + * + * Does not allocate storage, the caller provides the mutex object. + * + * This function can be called from interrupt context. + * + * @param[in] mutex Pointer to mutex object + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_PARAM Bad parameters + */ +uint8_t atomMutexCreate (ATOM_MUTEX *mutex) +{ + uint8_t status; + + /* Parameter check */ + if (mutex == NULL) + { + /* Bad mutex pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Start with no owner (unlocked) */ + mutex->owner = NULL; + + /* Reset the initial lock count */ + mutex->count = 0; + + /* Initialise the suspended threads queue */ + mutex->suspQ = NULL; + + /* Successful */ + status = ATOM_OK; + } + + return (status); +} + + +/** + * \b atomMutexDelete + * + * Deletes a mutex object. + * + * Any threads currently suspended on the mutex will be woken up with + * return status ATOM_ERR_DELETED. If called at thread context then the + * scheduler will be called during this function which may schedule in one + * of the woken threads depending on relative priorities. + * + * This function can be called from interrupt context, but loops internally + * waking up all threads blocking on the mutex, so the potential + * execution cycles cannot be determined in advance. + * + * @param[in] mutex Pointer to mutex object + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue + * @retval ATOM_ERR_TIMER Problem cancelling a timeout on a woken thread + */ +uint8_t atomMutexDelete (ATOM_MUTEX *mutex) +{ + uint8_t status; + CRITICAL_STORE; + ATOM_TCB *tcb_ptr; + uint8_t woken_threads = FALSE; + + /* Parameter check */ + if (mutex == NULL) + { + /* Bad mutex pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Default to success status unless errors occur during wakeup */ + status = ATOM_OK; + + /* Wake up all suspended tasks */ + while (1) + { + /* Enter critical region */ + CRITICAL_START (); + + /* Check if any threads are suspended */ + tcb_ptr = tcbDequeueHead (&mutex->suspQ); + + /* A thread is suspended on the mutex */ + if (tcb_ptr) + { + /* Return error status to the waiting thread */ + tcb_ptr->suspend_wake_status = ATOM_ERR_DELETED; + + /* Put the thread on the ready queue */ + if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* Quit the loop, returning error */ + status = ATOM_ERR_QUEUE; + break; + } + + /* If there's a timeout on this suspension, cancel it */ + if (tcb_ptr->suspend_timo_cb) + { + /* Cancel the callback */ + if (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* Quit the loop, returning error */ + status = ATOM_ERR_TIMER; + break; + } + + /* Flag as no timeout registered */ + tcb_ptr->suspend_timo_cb = NULL; + + } + + /* Exit critical region */ + CRITICAL_END (); + + /* Request a reschedule */ + woken_threads = TRUE; + } + + /* No more suspended threads */ + else + { + /* Exit critical region and quit the loop */ + CRITICAL_END (); + break; + } + } + + /* Call scheduler if any threads were woken up */ + if (woken_threads == TRUE) + { + /** + * Only call the scheduler if we are in thread context, otherwise + * it will be called on exiting the ISR by atomIntExit(). + */ + if (atomCurrentContext()) + atomSched (FALSE); + } + } + + return (status); +} + + +/** + * \b atomMutexGet + * + * Take the lock on a mutex. + * + * This takes ownership of a mutex if it is not currently owned. Ownership + * is held by this thread until a corresponding call to atomMutexPut() by + * the same thread. + * + * Can be called recursively by the original locking thread (owner). + * Recursive calls are counted, and ownership is not relinquished until + * the number of unlock (atomMutexPut()) calls by the owner matches the + * number of lock (atomMutexGet()) calls. + * + * No thread other than the owner can lock or unlock the mutex while it is + * locked by another thread. + * + * Depending on the \c timeout value specified the call will do one of + * the following if the mutex is already locked by another thread: + * + * \c timeout == 0 : Call will block until the mutex is available + * \c timeout > 0 : Call will block until available up to the specified timeout + * \c timeout == -1 : Return immediately if mutex is locked by another thread +* + * If the call needs to block and \c timeout is zero, it will block + * indefinitely until the owning thread calls atomMutexPut() or + * atomMutexDelete() is called on the mutex. + * + * If the call needs to block and \c timeout is non-zero, the call will only + * block for the specified number of system ticks after which time, if the + * thread was not already woken, the call will return with \c ATOM_TIMEOUT. + * + * If the call would normally block and \c timeout is -1, the call will + * return immediately with \c ATOM_WOULDBLOCK. + * + * This function can only be called from thread context. A mutex has the + * concept of an owner thread, so it is never valid to make a mutex call + * from interrupt context when there is no thread to associate with. + * + * @param[in] mutex Pointer to mutex object + * @param[in] timeout Max system ticks to block (0 = forever) + * + * @retval ATOM_OK Success + * @retval ATOM_TIMEOUT Mutex timed out before being woken + * @retval ATOM_WOULDBLOCK Called with timeout == -1 but count is zero + * @retval ATOM_ERR_DELETED Mutex was deleted while suspended + * @retval ATOM_ERR_CONTEXT Not called in thread context and attempted to block + * @retval ATOM_ERR_PARAM Bad parameter + * @retval ATOM_ERR_QUEUE Problem putting the thread on the suspend queue + * @retval ATOM_ERR_TIMER Problem registering the timeout + * @retval ATOM_ERR_OVF The recursive lock count would have overflowed (>255) + */ +uint8_t atomMutexGet (ATOM_MUTEX *mutex, int32_t timeout) +{ + CRITICAL_STORE; + uint8_t status; + MUTEX_TIMER timer_data; + ATOM_TIMER timer_cb; + ATOM_TCB *curr_tcb_ptr; + + /* Check parameters */ + if (mutex == NULL) + { + /* Bad mutex pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Get the current TCB */ + curr_tcb_ptr = atomCurrentContext(); + + /* Protect access to the mutex object and OS queues */ + CRITICAL_START (); + + /** + * Check we are at thread context. Because mutexes have the concept of + * owner threads, it is never valid to call here from an ISR, + * regardless of whether we will block. + */ + if (curr_tcb_ptr == NULL) + { + /* Exit critical region */ + CRITICAL_END (); + + /* Not currently in thread context, can't suspend */ + status = ATOM_ERR_CONTEXT; + } + + /* Otherwise if mutex is owned by another thread, block the calling thread */ + else if ((mutex->owner != NULL) && (mutex->owner != curr_tcb_ptr)) + { + /* If called with timeout >= 0, we should block */ + if (timeout >= 0) + { + /* Add current thread to the suspend list on this mutex */ + if (tcbEnqueuePriority (&mutex->suspQ, curr_tcb_ptr) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* There was an error putting this thread on the suspend list */ + status = ATOM_ERR_QUEUE; + } + else + { + /* Set suspended status for the current thread */ + curr_tcb_ptr->suspended = TRUE; + + /* Track errors */ + status = ATOM_OK; + + /* Register a timer callback if requested */ + if (timeout) + { + /* Fill out the data needed by the callback to wake us up */ + timer_data.tcb_ptr = curr_tcb_ptr; + timer_data.mutex_ptr = mutex; + + /* Fill out the timer callback request structure */ + timer_cb.cb_func = atomMutexTimerCallback; + timer_cb.cb_data = (POINTER)&timer_data; + timer_cb.cb_ticks = timeout; + + /** + * Store the timer details in the TCB so that we can + * cancel the timer callback if the mutex is put + * before the timeout occurs. + */ + curr_tcb_ptr->suspend_timo_cb = &timer_cb; + + /* Register a callback on timeout */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + /* Timer registration failed */ + status = ATOM_ERR_TIMER; + + /* Clean up and return to the caller */ + (void)tcbDequeueEntry (&mutex->suspQ, curr_tcb_ptr); + curr_tcb_ptr->suspended = FALSE; + curr_tcb_ptr->suspend_timo_cb = NULL; + } + } + + /* Set no timeout requested */ + else + { + /* No need to cancel timeouts on this one */ + curr_tcb_ptr->suspend_timo_cb = NULL; + } + + /* Exit critical region */ + CRITICAL_END (); + + /* Check no errors have occurred */ + if (status == ATOM_OK) + { + /** + * Current thread now blocking, schedule in a new + * one. We already know we are in thread context + * so can call the scheduler from here. + */ + atomSched (FALSE); + + /** + * Normal atomMutexPut() wakeups will set ATOM_OK status, + * while timeouts will set ATOM_TIMEOUT and mutex + * deletions will set ATOM_ERR_DELETED. */ + status = curr_tcb_ptr->suspend_wake_status; + + /** + * If we were woken up by another thread relinquishing + * the mutex and handing this thread ownership, then + * the relinquishing thread will set status to ATOM_OK + * and will make this thread the owner. Setting the + * owner before waking the thread ensures that no other + * thread can preempt and take ownership of the mutex + * between this thread being made ready to run, and + * actually being scheduled back in here. + */ + if (status == ATOM_OK) + { + /** + * Since this thread has just gained ownership, the + * lock count is zero and should be incremented + * once for this call. + */ + mutex->count++; + } + } + } + } + else + { + /* timeout == -1, requested not to block and mutex is owned by another thread */ + CRITICAL_END(); + status = ATOM_WOULDBLOCK; + } + } + else + { + /* Thread is not owned or is owned by us, we can claim ownership */ + + /* Increment the lock count, checking for count overflow */ + if (mutex->count == 255) + { + /* Don't increment, just return error status */ + status = ATOM_ERR_OVF; + } + else + { + /* Increment the count and return to the calling thread */ + mutex->count++; + + /* If the mutex is not locked, mark the calling thread as the new owner */ + if (mutex->owner == NULL) + { + mutex->owner = curr_tcb_ptr; + } + + /* Successful */ + status = ATOM_OK; + } + + /* Exit critical region */ + CRITICAL_END (); + } + } + + return (status); +} + + +/** + * \b atomMutexPut + * + * Give back the lock on a mutex. + * + * This checks that the mutex is owned by the calling thread, and decrements + * the recursive lock count. Once the lock count reaches zero, the lock is + * considered relinquished and no longer owned by this thread. + * + * If the lock is relinquished and there are threads blocking on the mutex, the + * call will wake up the highest priority thread suspended. Only one thread is + * woken per call to atomMutexPut(). If multiple threads of the same priority + * are suspended, they are woken in order of suspension (FIFO). + * + * This function can only be called from thread context. A mutex has the + * concept of an owner thread, so it is never valid to make a mutex call + * from interrupt context when there is no thread to associate with. + * + * @param[in] mutex Pointer to mutex object + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_PARAM Bad parameter + * @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue + * @retval ATOM_ERR_TIMER Problem cancelling a timeout for a woken thread + * @retval ATOM_ERR_OWNERSHIP Attempt to unlock mutex not owned by this thread + */ +uint8_t atomMutexPut (ATOM_MUTEX * mutex) +{ + uint8_t status; + CRITICAL_STORE; + ATOM_TCB *tcb_ptr, *curr_tcb_ptr; + + /* Check parameters */ + if (mutex == NULL) + { + /* Bad mutex pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Get the current TCB */ + curr_tcb_ptr = atomCurrentContext(); + + /* Protect access to the mutex object and OS queues */ + CRITICAL_START (); + + /* Check if the calling thread owns this mutex */ + if (mutex->owner != curr_tcb_ptr) + { + /* Exit critical region */ + CRITICAL_END (); + + /* Attempt to unlock by non-owning thread */ + status = ATOM_ERR_OWNERSHIP; + } + else + { + /* Lock is owned by this thread, decrement the recursive lock count */ + mutex->count--; + + /* Once recursive lock count reaches zero, we relinquish ownership */ + if (mutex->count == 0) + { + /* Relinquish ownership */ + mutex->owner = NULL; + + /* If any threads are blocking on this mutex, wake them now */ + if (mutex->suspQ) + { + /** + * Threads are woken up in priority order, with a FIFO system + * used on same priority threads. We always take the head, + * ordering is taken care of by an ordered list enqueue. + */ + tcb_ptr = tcbDequeueHead (&mutex->suspQ); + if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* There was a problem putting the thread on the ready queue */ + status = ATOM_ERR_QUEUE; + } + else + { + /* Set OK status to be returned to the waiting thread */ + tcb_ptr->suspend_wake_status = ATOM_OK; + + /* Set this thread as the new owner of the mutex */ + mutex->owner = tcb_ptr; + + /* If there's a timeout on this suspension, cancel it */ + if ((tcb_ptr->suspend_timo_cb != NULL) + && (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK)) + { + /* There was a problem cancelling a timeout on this mutex */ + status = ATOM_ERR_TIMER; + } + else + { + /* Flag as no timeout registered */ + tcb_ptr->suspend_timo_cb = NULL; + + /* Successful */ + status = ATOM_OK; + } + + /* Exit critical region */ + CRITICAL_END (); + + /** + * The scheduler may now make a policy decision to + * thread switch. We already know we are in thread + * context so can call the scheduler from here. + */ + atomSched (FALSE); + } + } + else + { + /** + * Relinquished ownership and no threads waiting. + * Nothing to do. + */ + + /* Exit critical region */ + CRITICAL_END (); + + /* Successful */ + status = ATOM_OK; + } + } + else + { + /** + * Decremented lock but still retain ownership due to + * recursion. Nothing to do. + */ + + /* Exit critical region */ + CRITICAL_END (); + + /* Successful */ + status = ATOM_OK; + } + } + } + + return (status); +} + + +/** + * \b atomMutexTimerCallback + * + * This is an internal function not for use by application code. + * + * Timeouts on suspended threads are notified by the timer system through + * this generic callback. The timer system calls us back with a pointer to + * the relevant \c MUTEX_TIMER object which is used to retrieve the + * mutex details. + * + * @param[in] cb_data Pointer to a MUTEX_TIMER object + */ +static void atomMutexTimerCallback (POINTER cb_data) +{ + MUTEX_TIMER *timer_data_ptr; + CRITICAL_STORE; + + /* Get the MUTEX_TIMER structure pointer */ + timer_data_ptr = (MUTEX_TIMER *)cb_data; + + /* Check parameter is valid */ + if (timer_data_ptr) + { + /* Enter critical region */ + CRITICAL_START (); + + /* Set status to indicate to the waiting thread that it timed out */ + timer_data_ptr->tcb_ptr->suspend_wake_status = ATOM_TIMEOUT; + + /* Flag as no timeout registered */ + timer_data_ptr->tcb_ptr->suspend_timo_cb = NULL; + + /* Remove this thread from the mutex's suspend list */ + (void)tcbDequeueEntry (&timer_data_ptr->mutex_ptr->suspQ, timer_data_ptr->tcb_ptr); + + /* Put the thread on the ready queue */ + (void)tcbEnqueuePriority (&tcbReadyQ, timer_data_ptr->tcb_ptr); + + /* Exit critical region */ + CRITICAL_END (); + + /** + * Note that we don't call the scheduler now as it will be called + * when we exit the ISR by atomIntExit(). + */ + } +} diff --git a/kernel/atommutex.h b/kernel/atommutex.h new file mode 100755 index 0000000..ee5272b --- /dev/null +++ b/kernel/atommutex.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ +#ifndef __ATOM_MUTEX_H +#define __ATOM_MUTEX_H + +typedef struct atom_mutex +{ + ATOM_TCB * suspQ; /* Queue of threads suspended on this mutex */ + ATOM_TCB * owner; /* Thread which currently owns the lock */ + uint8_t count; /* Recursive count of locks by the owner */ +} ATOM_MUTEX; + +extern uint8_t atomMutexCreate (ATOM_MUTEX *mutex); +extern uint8_t atomMutexDelete (ATOM_MUTEX *mutex); +extern uint8_t atomMutexGet (ATOM_MUTEX *mutex, int32_t timeout); +extern uint8_t atomMutexPut (ATOM_MUTEX *mutex); + +#endif /* __ATOM_MUTEX_H */ diff --git a/kernel/atomqueue.c b/kernel/atomqueue.c new file mode 100755 index 0000000..7d480e4 --- /dev/null +++ b/kernel/atomqueue.c @@ -0,0 +1,879 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#include +#include + +#include "atom.h" +#include "atomqueue.h" +#include "atomtimer.h" +#include "atomuser.h" + + +/* Local data types */ + +typedef struct queue_timer +{ + ATOM_TCB *tcb_ptr; /* Thread which is suspended with timeout */ + ATOM_QUEUE *queue_ptr; /* Queue the thread is interested in */ + ATOM_TCB **suspQ; /* TCB queue which thread is suspended on */ +} QUEUE_TIMER; + + +/* Forward declarations */ + +static uint8_t queue_remove (ATOM_QUEUE *qptr, uint8_t* msgptr); +static uint8_t queue_insert (ATOM_QUEUE *qptr, uint8_t* msgptr); +static void atomQueueTimerCallback (POINTER cb_data); + + +/** + * \b atomQueueCreate + * + * Initialises a queue object. + * + * Must be called before calling any other queue library routines on a + * queue. Objects can be deleted later using atomQueueDelete(). + * + * Does not allocate storage, the caller provides the queue object. + * + * Callers pass in their own buffer area for storing the queue messages while + * in transit between threads. The provided storage must be large enough to + * store (\c unit_size * \c max_num_mgs) bytes. i.e. the storage area will be + * used for up to \c max_num_msgs messages each of size \c unit_size. + * + * Queues use a fixed-size message. + * + * This function can be called from interrupt context. + * + * @param[in] qptr Pointer to queue object + * @param[in] buff_ptr Pointer to buffer storage area + * @param[in] unit_size Size in bytes of each queue message + * @param[in] max_num_msgs Maximum number of messages in the queue + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_PARAM Bad parameters + */ +uint8_t atomQueueCreate (ATOM_QUEUE *qptr, uint8_t *buff_ptr, uint32_t unit_size, uint32_t max_num_msgs) +{ + uint8_t status; + + /* Parameter check */ + if ((qptr == NULL) || (buff_ptr == NULL)) + { + /* Bad pointers */ + status = ATOM_ERR_PARAM; + } + else if ((unit_size == 0) || (max_num_msgs == 0)) + { + /* Bad values */ + status = ATOM_ERR_PARAM; + } + else + { + /* Store the queue details */ + qptr->buff_ptr = buff_ptr; + qptr->unit_size = unit_size; + qptr->max_num_msgs = max_num_msgs; + + /* Initialise the suspended threads queues */ + qptr->putSuspQ = NULL; + qptr->getSuspQ = NULL; + + /* Initialise the insert/remove pointers */ + qptr->insert_index = 0; + qptr->remove_index = 0; + qptr->num_msgs_stored = 0; + + /* Successful */ + status = ATOM_OK; + } + + return (status); +} + + +/** + * \b atomQueueDelete + * + * Deletes a queue object. + * + * Any threads currently suspended on the queue will be woken up with + * return status ATOM_ERR_DELETED. If called at thread context then the + * scheduler will be called during this function which may schedule in one + * of the woken threads depending on relative priorities. + * + * This function can be called from interrupt context, but loops internally + * waking up all threads blocking on the queue, so the potential + * execution cycles cannot be determined in advance. + * + * @param[in] qptr Pointer to queue object + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue + * @retval ATOM_ERR_TIMER Problem cancelling a timeout on a woken thread + */ +uint8_t atomQueueDelete (ATOM_QUEUE *qptr) +{ + uint8_t status; + CRITICAL_STORE; + ATOM_TCB *tcb_ptr; + uint8_t woken_threads = FALSE; + + /* Parameter check */ + if (qptr == NULL) + { + /* Bad pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Default to success status unless errors occur during wakeup */ + status = ATOM_OK; + + /* Wake up all suspended tasks */ + while (1) + { + /* Enter critical region */ + CRITICAL_START (); + + /* Check if any threads are suspended */ + if (((tcb_ptr = tcbDequeueHead (&qptr->getSuspQ)) != NULL) + || ((tcb_ptr = tcbDequeueHead (&qptr->putSuspQ)) != NULL)) + { + /* A thread is waiting on a suspend queue */ + + /* Return error status to the waiting thread */ + tcb_ptr->suspend_wake_status = ATOM_ERR_DELETED; + + /* Put the thread on the ready queue */ + if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* Quit the loop, returning error */ + status = ATOM_ERR_QUEUE; + break; + } + + /* If there's a timeout on this suspension, cancel it */ + if (tcb_ptr->suspend_timo_cb) + { + /* Cancel the callback */ + if (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* Quit the loop, returning error */ + status = ATOM_ERR_TIMER; + break; + } + + /* Flag as no timeout registered */ + tcb_ptr->suspend_timo_cb = NULL; + + } + + /* Exit critical region */ + CRITICAL_END (); + + /* Request a reschedule */ + woken_threads = TRUE; + } + + /* No more suspended threads */ + else + { + /* Exit critical region and quit the loop */ + CRITICAL_END (); + break; + } + } + + /* Call scheduler if any threads were woken up */ + if (woken_threads == TRUE) + { + /** + * Only call the scheduler if we are in thread context, otherwise + * it will be called on exiting the ISR by atomIntExit(). + */ + if (atomCurrentContext()) + atomSched (FALSE); + } + } + + return (status); +} + + +/** + * \b atomQueueGet + * + * Attempt to retrieve a message from a queue. + * + * If the queue is currently empty, the call will do one of the following + * depending on the \c timeout value specified: + * + * \c timeout == 0 : Call will block until a message is available + * \c timeout > 0 : Call will block until a message or the specified timeout + * \c timeout == -1 : Return immediately if no message is on the queue + * + * If a maximum timeout value is specified (\c timeout > 0), and no message + * is present on the queue for the specified number of system ticks, the + * call will return with \c ATOM_TIMEOUT. + * + * This function can only be called from interrupt context if the \c timeout + * parameter is -1 (in which case it does not block). + * + * @param[in] qptr Pointer to queue object + * @param[in] timeout Max system ticks to block (0 = forever, -1 = no block) + * @param[out] msgptr Pointer to which the received message will be copied + * + * @retval ATOM_OK Success + * @retval ATOM_TIMEOUT Queue wait timed out before being woken + * @retval ATOM_WOULDBLOCK Called with timeout == -1 but queue was empty + * @retval ATOM_ERR_DELETED Queue was deleted while suspended + * @retval ATOM_ERR_CONTEXT Not called in thread context and attempted to block + * @retval ATOM_ERR_PARAM Bad parameter + * @retval ATOM_ERR_QUEUE Problem putting the thread on the suspend queue + * @retval ATOM_ERR_TIMER Problem registering the timeout + */ +uint8_t atomQueueGet (ATOM_QUEUE *qptr, int32_t timeout, uint8_t *msgptr) +{ + CRITICAL_STORE; + uint8_t status; + QUEUE_TIMER timer_data; + ATOM_TIMER timer_cb; + ATOM_TCB *curr_tcb_ptr; + + /* Check parameters */ + if ((qptr == NULL) || (msgptr == NULL)) + { + /* Bad pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Protect access to the queue object and OS queues */ + CRITICAL_START (); + + /* If no messages on the queue, block the calling thread */ + if (qptr->num_msgs_stored == 0) + { + /* If called with timeout >= 0, we should block */ + if (timeout >= 0) + { + /* Queue is empty, block the calling thread */ + + /* Get the current TCB */ + curr_tcb_ptr = atomCurrentContext(); + + /* Check we are actually in thread context */ + if (curr_tcb_ptr) + { + /* Add current thread to the list suspended on receives */ + if (tcbEnqueuePriority (&qptr->getSuspQ, curr_tcb_ptr) == ATOM_OK) + { + /* Set suspended status for the current thread */ + curr_tcb_ptr->suspended = TRUE; + + /* Track errors */ + status = ATOM_OK; + + /* Register a timer callback if requested */ + if (timeout) + { + /** + * Fill out the data needed by the callback to + * wake us up. + */ + timer_data.tcb_ptr = curr_tcb_ptr; + timer_data.queue_ptr = qptr; + timer_data.suspQ = &qptr->getSuspQ; + + /* Fill out the timer callback request structure */ + timer_cb.cb_func = atomQueueTimerCallback; + timer_cb.cb_data = (POINTER)&timer_data; + timer_cb.cb_ticks = timeout; + + /** + * Store the timer details in the TCB so that we + * can cancel the timer callback if the queue is + * put before the timeout occurs. + */ + curr_tcb_ptr->suspend_timo_cb = &timer_cb; + + /* Register a callback on timeout */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + /* Timer registration failed */ + status = ATOM_ERR_TIMER; + + /* Clean up and return to the caller */ + (void)tcbDequeueEntry (&qptr->getSuspQ, curr_tcb_ptr); + curr_tcb_ptr->suspended = FALSE; + curr_tcb_ptr->suspend_timo_cb = NULL; + } + } + + /* Set no timeout requested */ + else + { + /* No need to cancel timeouts on this one */ + curr_tcb_ptr->suspend_timo_cb = NULL; + } + + /* Exit critical region */ + CRITICAL_END (); + + /* Check no errors occurred */ + if (status == ATOM_OK) + { + /** + * Current thread now blocking, schedule in a new + * one. We already know we are in thread context + * so can call the scheduler from here. + */ + atomSched (FALSE); + + /** + * Normal atomQueuePut() wakeups will set ATOM_OK + * status, while timeouts will set ATOM_TIMEOUT + * and queue deletions will set ATOM_ERR_DELETED. + */ + status = curr_tcb_ptr->suspend_wake_status; + + /** + * Check suspend_wake_status. If it is ATOM_OK + * then we were woken because a message has been + * put on the queue and we can now copy it out. + * Otherwise we were woken because we timed out + * waiting for a message, or the queue was + * deleted, so we should just quit. + */ + if (status == ATOM_OK) + { + /* Enter critical region */ + CRITICAL_START(); + + /* Copy the message out of the queue */ + status = queue_remove (qptr, msgptr); + + /* Exit critical region */ + CRITICAL_END(); + } + } + } + else + { + /* There was an error putting this thread on the suspend list */ + CRITICAL_END (); + status = ATOM_ERR_QUEUE; + } + } + else + { + /* Not currently in thread context, can't suspend */ + CRITICAL_END (); + status = ATOM_ERR_CONTEXT; + } + } + else + { + /* timeout == -1, requested not to block and queue is empty */ + CRITICAL_END(); + status = ATOM_WOULDBLOCK; + } + } + else + { + /* No need to block, there is a message to copy out of the queue */ + status = queue_remove (qptr, msgptr); + + /* Exit critical region */ + CRITICAL_END (); + + /** + * The scheduler may now make a policy decision to thread + * switch if we are currently in thread context. If we are + * in interrupt context it will be handled by atomIntExit(). + */ + if (atomCurrentContext()) + atomSched (FALSE); + } + } + + return (status); +} + + +/** + * \b atomQueuePut + * + * Attempt to put a message onto a queue. + * + * If the queue is currently full, the call will do one of the following + * depending on the \c timeout value specified: + * + * \c timeout == 0 : Call will block until space is available + * \c timeout > 0 : Call will block until space or the specified timeout + * \c timeout == -1 : Return immediately if the queue is full + * + * If a maximum timeout value is specified (\c timeout > 0), and no space + * is available on the queue for the specified number of system ticks, the + * call will return with \c ATOM_TIMEOUT. + * + * This function can only be called from interrupt context if the \c timeout + * parameter is -1 (in which case it does not block and may fail to post a + * message if the queue is full). + * + * @param[in] qptr Pointer to queue object + * @param[in] timeout Max system ticks to block (0 = forever, -1 = no block) + * @param[out] msgptr Pointer from which the message should be copied out + * + * @retval ATOM_OK Success + * @retval ATOM_WOULDBLOCK Called with timeout == -1 but queue was full + * @retval ATOM_TIMEOUT Queue wait timed out before being woken + * @retval ATOM_ERR_DELETED Queue was deleted while suspended + * @retval ATOM_ERR_CONTEXT Not called in thread context and attempted to block + * @retval ATOM_ERR_PARAM Bad parameter + * @retval ATOM_ERR_QUEUE Problem putting the thread on the suspend queue + * @retval ATOM_ERR_TIMER Problem registering the timeout + */ +uint8_t atomQueuePut (ATOM_QUEUE *qptr, int32_t timeout, uint8_t *msgptr) +{ + CRITICAL_STORE; + uint8_t status; + QUEUE_TIMER timer_data; + ATOM_TIMER timer_cb; + ATOM_TCB *curr_tcb_ptr; + + /* Check parameters */ + if ((qptr == NULL) || (msgptr == NULL)) + { + /* Bad pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Protect access to the queue object and OS queues */ + CRITICAL_START (); + + /* If queue is full, block the calling thread */ + if (qptr->num_msgs_stored == qptr->max_num_msgs) + { + /* If called with timeout >= 0, we should block */ + if (timeout >= 0) + { + /* Queue is full, block the calling thread */ + + /* Get the current TCB */ + curr_tcb_ptr = atomCurrentContext(); + + /* Check we are actually in thread context */ + if (curr_tcb_ptr) + { + /* Add current thread to the suspend list on sends */ + if (tcbEnqueuePriority (&qptr->putSuspQ, curr_tcb_ptr) == ATOM_OK) + { + /* Set suspended status for the current thread */ + curr_tcb_ptr->suspended = TRUE; + + /* Track errors */ + status = ATOM_OK; + + /* Register a timer callback if requested */ + if (timeout) + { + /** + * Fill out the data needed by the callback to + * wake us up. + */ + timer_data.tcb_ptr = curr_tcb_ptr; + timer_data.queue_ptr = qptr; + timer_data.suspQ = &qptr->putSuspQ; + + + /* Fill out the timer callback request structure */ + timer_cb.cb_func = atomQueueTimerCallback; + timer_cb.cb_data = (POINTER)&timer_data; + timer_cb.cb_ticks = timeout; + + /** + * Store the timer details in the TCB so that we + * can cancel the timer callback if a message is + * removed from the queue before the timeout + * occurs. + */ + curr_tcb_ptr->suspend_timo_cb = &timer_cb; + + /* Register a callback on timeout */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + /* Timer registration failed */ + status = ATOM_ERR_TIMER; + + /* Clean up and return to the caller */ + (void)tcbDequeueEntry (&qptr->putSuspQ, curr_tcb_ptr); + curr_tcb_ptr->suspended = FALSE; + curr_tcb_ptr->suspend_timo_cb = NULL; + } + } + + /* Set no timeout requested */ + else + { + /* No need to cancel timeouts on this one */ + curr_tcb_ptr->suspend_timo_cb = NULL; + } + + /* Exit critical region */ + CRITICAL_END (); + + /* Check timer registration was successful */ + if (status == ATOM_OK) + { + /** + * Current thread now blocking, schedule in a new + * one. We already know we are in thread context + * so can call the scheduler from here. + */ + atomSched (FALSE); + + /** + * Normal atomQueueGet() wakeups will set ATOM_OK + * status, while timeouts will set ATOM_TIMEOUT + * and queue deletions will set ATOM_ERR_DELETED. + */ + status = curr_tcb_ptr->suspend_wake_status; + + /** + * Check suspend_wake_status. If it is ATOM_OK + * then we were woken because a message has been + * removed from the queue and we can now add ours. + * Otherwise we were woken because we timed out + * waiting for a message, or the queue was + * deleted, so we should just quit. + */ + if (status == ATOM_OK) + { + /* Enter critical region */ + CRITICAL_START(); + + /* Copy the message into the queue */ + status = queue_insert (qptr, msgptr); + + /* Exit critical region */ + CRITICAL_END(); + } + } + } + else + { + /* There was an error putting this thread on the suspend list */ + CRITICAL_END (); + status = ATOM_ERR_QUEUE; + } + } + else + { + /* Not currently in thread context, can't suspend */ + CRITICAL_END (); + status = ATOM_ERR_CONTEXT; + } + } + else + { + /* timeout == -1, cannot block. Just return queue is full */ + CRITICAL_END(); + status = ATOM_WOULDBLOCK; + } + } + else + { + /* No need to block, there is space to copy into the queue */ + status = queue_insert (qptr, msgptr); + + /* Exit critical region */ + CRITICAL_END (); + + /** + * The scheduler may now make a policy decision to thread + * switch if we are currently in thread context. If we are + * in interrupt context it will be handled by atomIntExit(). + */ + if (atomCurrentContext()) + atomSched (FALSE); + } + } + + return (status); +} + + +/** + * \b atomQueueTimerCallback + * + * This is an internal function not for use by application code. + * + * Timeouts on suspended threads are notified by the timer system through + * this generic callback. The timer system calls us back with a pointer to + * the relevant \c QUEUE_TIMER object which is used to retrieve the + * queue details. + * + * @param[in] cb_data Pointer to a QUEUE_TIMER object + */ +static void atomQueueTimerCallback (POINTER cb_data) +{ + QUEUE_TIMER *timer_data_ptr; + CRITICAL_STORE; + + /* Get the QUEUE_TIMER structure pointer */ + timer_data_ptr = (QUEUE_TIMER *)cb_data; + + /* Check parameter is valid */ + if (timer_data_ptr) + { + /* Enter critical region */ + CRITICAL_START (); + + /* Set status to indicate to the waiting thread that it timed out */ + timer_data_ptr->tcb_ptr->suspend_wake_status = ATOM_TIMEOUT; + + /* Flag as no timeout registered */ + timer_data_ptr->tcb_ptr->suspend_timo_cb = NULL; + + /** + * Remove this thread from the queue's suspend list. Handles threads + * suspended on the receive list as well as the send list. + */ + (void)tcbDequeueEntry (timer_data_ptr->suspQ, timer_data_ptr->tcb_ptr); + + /* Put the thread on the ready queue */ + (void)tcbEnqueuePriority (&tcbReadyQ, timer_data_ptr->tcb_ptr); + + /* Exit critical region */ + CRITICAL_END (); + + /** + * Note that we don't call the scheduler now as it will be called + * when we exit the ISR by atomIntExit(). + */ + } +} + + +/** + * \b queue_remove + * + * This is an internal function not for use by application code. + * + * Removes a message from a queue. Assumes that there is a message present, + * which is already checked by the calling functions with interrupts locked + * out. + * + * Also wakes up a suspended thread if there are any waiting to send on the + * queue. + * + * Assumes interrupts are already locked out. + * + * @param[in] qptr Pointer to an ATOM_QUEUE object + * @param[in] msgptr Destination pointer for the message to be copied into + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_PARAM Bad parameter + * @retval ATOM_ERR_QUEUE Problem putting a thread on the ready queue + * @retval ATOM_ERR_TIMER Problem cancelling a timeout + */ +static uint8_t queue_remove (ATOM_QUEUE *qptr, uint8_t* msgptr) +{ + uint8_t status; + ATOM_TCB *tcb_ptr; + + /* Check parameters */ + if ((qptr == NULL) || (msgptr == NULL)) + { + /* Bad pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* There is a message on the queue, copy it out */ + memcpy (msgptr, (qptr->buff_ptr + qptr->remove_index), qptr->unit_size); + qptr->remove_index += qptr->unit_size; + qptr->num_msgs_stored--; + + /* Check if the remove index should now wrap to the beginning */ + if (qptr->remove_index >= (qptr->unit_size * qptr->max_num_msgs)) + qptr->remove_index = 0; + + /** + * If there are threads waiting to send, wake one up now. Waiting + * threads are woken up in priority order, with same-priority + * threads woken up in FIFO order. + */ + tcb_ptr = tcbDequeueHead (&qptr->putSuspQ); + if (tcb_ptr) + { + /* Move the waiting thread to the ready queue */ + if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) == ATOM_OK) + { + /* Set OK status to be returned to the waiting thread */ + tcb_ptr->suspend_wake_status = ATOM_OK; + + /* If there's a timeout on this suspension, cancel it */ + if ((tcb_ptr->suspend_timo_cb != NULL) + && (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK)) + { + /* There was a problem cancelling a timeout */ + status = ATOM_ERR_TIMER; + } + else + { + /* Flag as no timeout registered */ + tcb_ptr->suspend_timo_cb = NULL; + + /* Successful */ + status = ATOM_OK; + } + } + else + { + /** + * There was a problem putting the thread on the ready + * queue. + */ + status = ATOM_ERR_QUEUE; + } + } + else + { + /* There were no threads waiting to send */ + status = ATOM_OK; + } + } + + return (status); +} + + +/** + * \b queue_insert + * + * This is an internal function not for use by application code. + * + * Inserts a message onto a queue. Assumes that the queue has space for one + * message, which has already been checked by the calling function with + * interrupts locked out. + * + * Also wakes up a suspended thread if there are any waiting to receive on the + * queue. + * + * Assumes interrupts are already locked out. + * + * @param[in] qptr Pointer to an ATOM_QUEUE object + * @param[in] msgptr Source pointer for the message to be copied out of + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_PARAM Bad parameter + * @retval ATOM_ERR_QUEUE Problem putting a thread on the ready queue + * @retval ATOM_ERR_TIMER Problem cancelling a timeout + */ +static uint8_t queue_insert (ATOM_QUEUE *qptr, uint8_t* msgptr) +{ + uint8_t status; + ATOM_TCB *tcb_ptr; + + /* Check parameters */ + if ((qptr == NULL) || (msgptr == NULL)) + { + /* Bad pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* There is space in the queue, copy it in */ + memcpy ((qptr->buff_ptr + qptr->insert_index), msgptr, qptr->unit_size); + qptr->insert_index += qptr->unit_size; + qptr->num_msgs_stored++; + + /* Check if the insert index should now wrap to the beginning */ + if (qptr->insert_index >= (qptr->unit_size * qptr->max_num_msgs)) + qptr->insert_index = 0; + + /** + * If there are threads waiting to receive, wake one up now. Waiting + * threads are woken up in priority order, with same-priority + * threads woken up in FIFO order. + */ + tcb_ptr = tcbDequeueHead (&qptr->getSuspQ); + if (tcb_ptr) + { + /* Move the waiting thread to the ready queue */ + if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) == ATOM_OK) + { + /* Set OK status to be returned to the waiting thread */ + tcb_ptr->suspend_wake_status = ATOM_OK; + + /* If there's a timeout on this suspension, cancel it */ + if ((tcb_ptr->suspend_timo_cb != NULL) + && (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK)) + { + /* There was a problem cancelling a timeout */ + status = ATOM_ERR_TIMER; + } + else + { + /* Flag as no timeout registered */ + tcb_ptr->suspend_timo_cb = NULL; + + /* Successful */ + status = ATOM_OK; + } + } + else + { + /** + * There was a problem putting the thread on the ready + * queue. + */ + status = ATOM_ERR_QUEUE; + } + } + else + { + /* There were no threads waiting to send */ + status = ATOM_OK; + } + } + + return (status); +} diff --git a/kernel/atomqueue.h b/kernel/atomqueue.h new file mode 100755 index 0000000..1bf8660 --- /dev/null +++ b/kernel/atomqueue.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ +#ifndef __ATOM_QUEUE_H +#define __ATOM_QUEUE_H + +typedef struct atom_queue +{ + ATOM_TCB * putSuspQ; /* Queue of threads waiting to send */ + ATOM_TCB * getSuspQ; /* Queue of threads waiting to receive */ + uint8_t * buff_ptr; /* Pointer to queue data area */ + uint32_t unit_size; /* Size of each message */ + uint32_t max_num_msgs; /* Max number of storable messages */ + uint32_t insert_index; /* Next byte index to insert into */ + uint32_t remove_index; /* Next byte index to remove from */ + uint32_t num_msgs_stored;/* Number of messages stored */ +} ATOM_QUEUE; + +extern uint8_t atomQueueCreate (ATOM_QUEUE *qptr, uint8_t *buff_ptr, uint32_t unit_size, uint32_t max_num_msgs); +extern uint8_t atomQueueDelete (ATOM_QUEUE *qptr); +extern uint8_t atomQueueGet (ATOM_QUEUE *qptr, int32_t timeout, uint8_t *msgptr); +extern uint8_t atomQueuePut (ATOM_QUEUE *qptr, int32_t timeout, uint8_t *msgptr); + +#endif /* __ATOM_QUEUE_H */ diff --git a/kernel/atomsem.c b/kernel/atomsem.c new file mode 100755 index 0000000..7904138 --- /dev/null +++ b/kernel/atomsem.c @@ -0,0 +1,618 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#include +#include "atom.h" +#include "atomsem.h" +#include "atomtimer.h" +#include "atomuser.h" + + +/* Local data types */ + +typedef struct sem_timer +{ + ATOM_TCB *tcb_ptr; /* Thread which is suspended with timeout */ + ATOM_SEM *sem_ptr; /* Semaphore the thread is suspended on */ +} SEM_TIMER; + + +/* Forward declarations */ + +static void atomSemTimerCallback (POINTER cb_data); + + +/** + * \b atomSemCreate + * + * Initialises a semaphore object. + * + * Must be called before calling any other semaphore library routines on a + * semaphore. Objects can be deleted later using atomSemDelete(). + * + * Does not allocate storage, the caller provides the semaphore object. + * + * This function can be called from interrupt context. + * + * @param[in] sem Pointer to semaphore object + * @param[in] initial_count Initial count value + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_PARAM Bad parameters + */ +uint8_t atomSemCreate (ATOM_SEM *sem, uint8_t initial_count) +{ + uint8_t status; + + /* Parameter check */ + if (sem == NULL) + { + /* Bad semaphore pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Set the initial count */ + sem->count = initial_count; + + /* Initialise the suspended threads queue */ + sem->suspQ = NULL; + + /* Successful */ + status = ATOM_OK; + } + + return (status); +} + + +/** + * \b atomSemDelete + * + * Deletes a semaphore object. + * + * Any threads currently suspended on the semaphore will be woken up with + * return status ATOM_ERR_DELETED. If called at thread context then the + * scheduler will be called during this function which may schedule in one + * of the woken threads depending on relative priorities. + * + * This function can be called from interrupt context, but loops internally + * waking up all threads blocking on the semaphore, so the potential + * execution cycles cannot be determined in advance. + * + * @param[in] sem Pointer to semaphore object + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue + * @retval ATOM_ERR_TIMER Problem cancelling a timeout on a woken thread + */ +uint8_t atomSemDelete (ATOM_SEM *sem) +{ + uint8_t status; + CRITICAL_STORE; + ATOM_TCB *tcb_ptr; + uint8_t woken_threads = FALSE; + + /* Parameter check */ + if (sem == NULL) + { + /* Bad semaphore pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Default to success status unless errors occur during wakeup */ + status = ATOM_OK; + + /* Wake up all suspended tasks */ + while (1) + { + /* Enter critical region */ + CRITICAL_START (); + + /* Check if any threads are suspended */ + tcb_ptr = tcbDequeueHead (&sem->suspQ); + + /* A thread is suspended on the semaphore */ + if (tcb_ptr) + { + /* Return error status to the waiting thread */ + tcb_ptr->suspend_wake_status = ATOM_ERR_DELETED; + + /* Put the thread on the ready queue */ + if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* Quit the loop, returning error */ + status = ATOM_ERR_QUEUE; + break; + } + + /* If there's a timeout on this suspension, cancel it */ + if (tcb_ptr->suspend_timo_cb) + { + /* Cancel the callback */ + if (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* Quit the loop, returning error */ + status = ATOM_ERR_TIMER; + break; + } + + /* Flag as no timeout registered */ + tcb_ptr->suspend_timo_cb = NULL; + + } + + /* Exit critical region */ + CRITICAL_END (); + + /* Request a reschedule */ + woken_threads = TRUE; + } + + /* No more suspended threads */ + else + { + /* Exit critical region and quit the loop */ + CRITICAL_END (); + break; + } + } + + /* Call scheduler if any threads were woken up */ + if (woken_threads == TRUE) + { + /** + * Only call the scheduler if we are in thread context, otherwise + * it will be called on exiting the ISR by atomIntExit(). + */ + if (atomCurrentContext()) + atomSched (FALSE); + } + } + + return (status); +} + + +/** + * \b atomSemGet + * + * Perform a get operation on a semaphore. + * + * This decrements the current count value for the semaphore and returns. + * If the count value is already zero then the call will block until the + * count is incremented by another thread, or until the specified \c timeout + * is reached. Blocking threads will also be woken if the semaphore is + * deleted by another thread while blocking. + * + * Depending on the \c timeout value specified the call will do one of + * the following if the count value is zero: + * + * \c timeout == 0 : Call will block until the count is non-zero + * \c timeout > 0 : Call will block until non-zero up to the specified timeout + * \c timeout == -1 : Return immediately if the count is zero +* + * If the call needs to block and \c timeout is zero, it will block + * indefinitely until atomSemPut() or atomSemDelete() is called on the + * semaphore. + * + * If the call needs to block and \c timeout is non-zero, the call will only + * block for the specified number of system ticks after which time, if the + * thread was not already woken, the call will return with \c ATOM_TIMEOUT. + * + * If the call would normally block and \c timeout is -1, the call will + * return immediately with \c ATOM_WOULDBLOCK. + * + * This function can only be called from interrupt context if the \c timeout + * parameter is -1 (in which case it does not block). + * + * @param[in] sem Pointer to semaphore object + * @param[in] timeout Max system ticks to block (0 = forever) + * + * @retval ATOM_OK Success + * @retval ATOM_TIMEOUT Semaphore timed out before being woken + * @retval ATOM_WOULDBLOCK Called with timeout == -1 but count is zero + * @retval ATOM_ERR_DELETED Semaphore was deleted while suspended + * @retval ATOM_ERR_CONTEXT Not called in thread context and attempted to block + * @retval ATOM_ERR_PARAM Bad parameter + * @retval ATOM_ERR_QUEUE Problem putting the thread on the suspend queue + * @retval ATOM_ERR_TIMER Problem registering the timeout + */ +uint8_t atomSemGet (ATOM_SEM *sem, int32_t timeout) +{ + CRITICAL_STORE; + uint8_t status; + SEM_TIMER timer_data; + ATOM_TIMER timer_cb; + ATOM_TCB *curr_tcb_ptr; + + /* Check parameters */ + if (sem == NULL) + { + /* Bad semaphore pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Protect access to the semaphore object and OS queues */ + CRITICAL_START (); + + /* If count is zero, block the calling thread */ + if (sem->count == 0) + { + /* If called with timeout >= 0, we should block */ + if (timeout >= 0) + { + /* Count is zero, block the calling thread */ + + /* Get the current TCB */ + curr_tcb_ptr = atomCurrentContext(); + + /* Check we are actually in thread context */ + if (curr_tcb_ptr) + { + /* Add current thread to the suspend list on this semaphore */ + if (tcbEnqueuePriority (&sem->suspQ, curr_tcb_ptr) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* There was an error putting this thread on the suspend list */ + status = ATOM_ERR_QUEUE; + } + else + { + /* Set suspended status for the current thread */ + curr_tcb_ptr->suspended = TRUE; + + /* Track errors */ + status = ATOM_OK; + + /* Register a timer callback if requested */ + if (timeout) + { + /* Fill out the data needed by the callback to wake us up */ + timer_data.tcb_ptr = curr_tcb_ptr; + timer_data.sem_ptr = sem; + + /* Fill out the timer callback request structure */ + timer_cb.cb_func = atomSemTimerCallback; + timer_cb.cb_data = (POINTER)&timer_data; + timer_cb.cb_ticks = timeout; + + /** + * Store the timer details in the TCB so that we can + * cancel the timer callback if the semaphore is put + * before the timeout occurs. + */ + curr_tcb_ptr->suspend_timo_cb = &timer_cb; + + /* Register a callback on timeout */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + /* Timer registration failed */ + status = ATOM_ERR_TIMER; + + /* Clean up and return to the caller */ + (void)tcbDequeueEntry (&sem->suspQ, curr_tcb_ptr); + curr_tcb_ptr->suspended = FALSE; + curr_tcb_ptr->suspend_timo_cb = NULL; + } + } + + /* Set no timeout requested */ + else + { + /* No need to cancel timeouts on this one */ + curr_tcb_ptr->suspend_timo_cb = NULL; + } + + /* Exit critical region */ + CRITICAL_END (); + + /* Check no errors have occurred */ + if (status == ATOM_OK) + { + /** + * Current thread now blocking, schedule in a new + * one. We already know we are in thread context + * so can call the scheduler from here. + */ + atomSched (FALSE); + + /** + * Normal atomSemPut() wakeups will set ATOM_OK status, + * while timeouts will set ATOM_TIMEOUT and semaphore + * deletions will set ATOM_ERR_DELETED. + */ + status = curr_tcb_ptr->suspend_wake_status; + + /** + * If we have been woken up with ATOM_OK then + * another thread incremented the semaphore and + * handed control to this thread. In theory the + * the posting thread increments the counter and + * as soon as this thread wakes up we decrement + * the counter here, but to prevent another + * thread preempting this thread and decrementing + * the semaphore before this section was + * scheduled back in, we emulate the increment + * and decrement by not incrementing in the + * atomSemPut() and not decrementing here. The + * count remains zero throughout preventing other + * threads preempting before we decrement the + * count again. + */ + + } + } + } + else + { + /* Exit critical region */ + CRITICAL_END (); + + /* Not currently in thread context, can't suspend */ + status = ATOM_ERR_CONTEXT; + } + } + else + { + /* timeout == -1, requested not to block and count is zero */ + CRITICAL_END(); + status = ATOM_WOULDBLOCK; + } + } + else + { + /* Count is non-zero, just decrement it and return to calling thread */ + sem->count--; + + /* Exit critical region */ + CRITICAL_END (); + + /* Successful */ + status = ATOM_OK; + } + } + + return (status); +} + + +/** + * \b atomSemPut + * + * Perform a put operation on a semaphore. + * + * This increments the current count value for the semaphore and returns. + * + * If the count value was previously zero and there are threads blocking on the + * semaphore, the call will wake up the highest priority thread suspended. Only + * one thread is woken per call to atomSemPut(). If multiple threads of the + * same priority are suspended, they are woken in order of suspension (FIFO). + * + * This function can be called from interrupt context. + * + * @param[in] sem Pointer to semaphore object + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_OVF The semaphore count would have overflowed (>255) + * @retval ATOM_ERR_PARAM Bad parameter + * @retval ATOM_ERR_QUEUE Problem putting a woken thread on the ready queue + * @retval ATOM_ERR_TIMER Problem cancelling a timeout for a woken thread + */ +uint8_t atomSemPut (ATOM_SEM * sem) +{ + uint8_t status; + CRITICAL_STORE; + ATOM_TCB *tcb_ptr; + + /* Check parameters */ + if (sem == NULL) + { + /* Bad semaphore pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Protect access to the semaphore object and OS queues */ + CRITICAL_START (); + + /* If any threads are blocking on the semaphore, wake up one */ + if (sem->suspQ) + { + /** + * Threads are woken up in priority order, with a FIFO system + * used on same priority threads. We always take the head, + * ordering is taken care of by an ordered list enqueue. + */ + tcb_ptr = tcbDequeueHead (&sem->suspQ); + if (tcbEnqueuePriority (&tcbReadyQ, tcb_ptr) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* There was a problem putting the thread on the ready queue */ + status = ATOM_ERR_QUEUE; + } + else + { + /* Set OK status to be returned to the waiting thread */ + tcb_ptr->suspend_wake_status = ATOM_OK; + + /* If there's a timeout on this suspension, cancel it */ + if ((tcb_ptr->suspend_timo_cb != NULL) + && (atomTimerCancel (tcb_ptr->suspend_timo_cb) != ATOM_OK)) + { + /* There was a problem cancelling a timeout on this semaphore */ + status = ATOM_ERR_TIMER; + } + else + { + /* Flag as no timeout registered */ + tcb_ptr->suspend_timo_cb = NULL; + + /* Successful */ + status = ATOM_OK; + } + + /* Exit critical region */ + CRITICAL_END (); + + /** + * The scheduler may now make a policy decision to thread + * switch if we are currently in thread context. If we are + * in interrupt context it will be handled by atomIntExit(). + */ + if (atomCurrentContext()) + atomSched (FALSE); + } + } + + /* If no threads waiting, just increment the count and return */ + else + { + /* Check for count overflow */ + if (sem->count == 255) + { + /* Don't increment, just return error status */ + status = ATOM_ERR_OVF; + } + else + { + /* Increment the count and return success */ + sem->count++; + status = ATOM_OK; + } + + /* Exit critical region */ + CRITICAL_END (); + } + } + + return (status); +} + + +/** + * \b atomSemResetCount + * + * Set a new count value on a semaphore. + * + * Care must be taken when using this function, as there may be threads + * suspended on the semaphore. In general it should only be used once a + * semaphore is out of use. + * + * This function can be called from interrupt context. + * + * @param[in] sem Pointer to semaphore object + * @param[in] count New count value + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_PARAM Bad parameter + */ +uint8_t atomSemResetCount (ATOM_SEM *sem, uint8_t count) +{ + uint8_t status; + + /* Parameter check */ + if (sem == NULL) + { + /* Bad semaphore pointer */ + status = ATOM_ERR_PARAM; + } + else + { + /* Set the count */ + sem->count = count; + + /* Successful */ + status = ATOM_OK; + } + + return (status); + +} + + +/** + * \b atomSemTimerCallback + * + * This is an internal function not for use by application code. + * + * Timeouts on suspended threads are notified by the timer system through + * this generic callback. The timer system calls us back with a pointer to + * the relevant \c SEM_TIMER object which is used to retrieve the + * semaphore details. + * + * @param[in] cb_data Pointer to a SEM_TIMER object + */ +static void atomSemTimerCallback (POINTER cb_data) +{ + SEM_TIMER *timer_data_ptr; + CRITICAL_STORE; + + /* Get the SEM_TIMER structure pointer */ + timer_data_ptr = (SEM_TIMER *)cb_data; + + /* Check parameter is valid */ + if (timer_data_ptr) + { + /* Enter critical region */ + CRITICAL_START (); + + /* Set status to indicate to the waiting thread that it timed out */ + timer_data_ptr->tcb_ptr->suspend_wake_status = ATOM_TIMEOUT; + + /* Flag as no timeout registered */ + timer_data_ptr->tcb_ptr->suspend_timo_cb = NULL; + + /* Remove this thread from the semaphore's suspend list */ + (void)tcbDequeueEntry (&timer_data_ptr->sem_ptr->suspQ, timer_data_ptr->tcb_ptr); + + /* Put the thread on the ready queue */ + (void)tcbEnqueuePriority (&tcbReadyQ, timer_data_ptr->tcb_ptr); + + /* Exit critical region */ + CRITICAL_END (); + + /** + * Note that we don't call the scheduler now as it will be called + * when we exit the ISR by atomIntExit(). + */ + } +} diff --git a/kernel/atomsem.h b/kernel/atomsem.h new file mode 100755 index 0000000..b625048 --- /dev/null +++ b/kernel/atomsem.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef __ATOM_SEM_H +#define __ATOM_SEM_H + +typedef struct atom_sem +{ + ATOM_TCB * suspQ; /* Queue of threads suspended on this semaphore */ + uint8_t count; /* Semaphore count */ +} ATOM_SEM; + +extern uint8_t atomSemCreate (ATOM_SEM *sem, uint8_t initial_count); +extern uint8_t atomSemDelete (ATOM_SEM *sem); +extern uint8_t atomSemGet (ATOM_SEM *sem, int32_t timeout); +extern uint8_t atomSemPut (ATOM_SEM *sem); +extern uint8_t atomSemResetCount (ATOM_SEM *sem, uint8_t count); + +#endif /* __ATOM_SEM_H */ diff --git a/kernel/atomtimer.c b/kernel/atomtimer.c new file mode 100755 index 0000000..7916970 --- /dev/null +++ b/kernel/atomtimer.c @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#include +#include "atom.h" +#include "atomuser.h" + + +/* Data types */ + +/* Delay callbacks data structure */ +typedef struct delay_timer +{ + ATOM_TCB *tcb_ptr; /* Thread which is suspended with timeout */ + +} DELAY_TIMER; + + +/* Global data */ + +/* Local data */ + +/** Pointer to the head of the outstanding timers queue */ +static ATOM_TIMER *timer_queue = NULL; + +/** Current system tick count */ +static uint32_t system_ticks = 0; + + +/* Forward declarations */ +static void atomTimerCallbacks (void); +static void atomTimerDelayCallback (POINTER cb_data); + + +/** + * \b atomTimerRegister + * + * Register a timer callback. + * + * Callers should fill out and pass in a timer descriptor, containing + * the number of system ticks until they would like a callback, together + * with a callback function and optional parameter. The number of ticks + * must be greater than zero. + * + * On the relevant system tick count, the callback function will be + * called. + * + * These timers are used by some of the OS library routines, but they + * can also be used by application code requiring timer facilities at + * system tick resolution. + * + * This function can be called from interrupt context, but loops internally + * through the time list, so the potential execution cycles cannot be + * determined in advance. + * + * @param[in] timer_ptr Pointer to timer descriptor + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_PARAM Bad parameters + */ +uint8_t atomTimerRegister (ATOM_TIMER *timer_ptr) +{ + uint8_t status; + CRITICAL_STORE; + + /* Parameter check */ + if ((timer_ptr == NULL) || (timer_ptr->cb_func == NULL) + || (timer_ptr->cb_ticks == 0)) + { + /* Return error */ + status = ATOM_ERR_PARAM; + } + else + { + /* Protect the list */ + CRITICAL_START (); + + /* + * Enqueue in the list of timers. + * + * The list is not ordered, all timers are inserted at the start + * of the list. On each system tick increment the list is walked + * and the remaining ticks count for that timer is decremented. + * Once the remaining ticks reaches zero, the timer callback is + * made. + */ + if (timer_queue == NULL) + { + /* List is empty, insert new head */ + timer_ptr->next_timer = NULL; + timer_queue = timer_ptr; + } + else + { + /* List has at least one entry, enqueue new timer before */ + timer_ptr->next_timer = timer_queue; + timer_queue = timer_ptr; + } + + /* End of list protection */ + CRITICAL_END (); + + /* Successful */ + status = ATOM_OK; + } + + return (status); +} + + +/** + * \b atomTimerCancel + * + * Cancel a timer callback previously registered using atomTimerRegister(). + * + * This function can be called from interrupt context, but loops internally + * through the time list, so the potential execution cycles cannot be + * determined in advance. + * + * @param[in] timer_ptr Pointer to timer to cancel + * + * @retval ATOM_OK Success + * @retval ATOM_ERR_PARAM Bad parameters + * @retval ATOM_ERR_NOT_FOUND Timer registration was not found + */ +uint8_t atomTimerCancel (ATOM_TIMER *timer_ptr) +{ + uint8_t status = ATOM_ERR_NOT_FOUND; + ATOM_TIMER *prev_ptr, *next_ptr; + CRITICAL_STORE; + + /* Parameter check */ + if (timer_ptr == NULL) + { + /* Return error */ + status = ATOM_ERR_PARAM; + } + else + { + /* Protect the list */ + CRITICAL_START (); + + /* Walk the list to find the relevant timer */ + prev_ptr = next_ptr = timer_queue; + while (next_ptr) + { + /* Is this entry the one we're looking for? */ + if (next_ptr == timer_ptr) + { + if (next_ptr == timer_queue) + { + /* We're removing the list head */ + timer_queue = next_ptr->next_timer; + } + else + { + /* We're removing a mid or tail TCB */ + prev_ptr->next_timer = next_ptr->next_timer; + } + + /* Successful */ + status = ATOM_OK; + break; + } + + /* Move on to the next in the list */ + prev_ptr = next_ptr; + next_ptr = next_ptr->next_timer; + + } + + /* End of list protection */ + CRITICAL_END (); + } + + return (status); +} + + +/** + * \b atomTimeGet + * + * Returns the current system tick time. + * + * This function can be called from interrupt context. + * + * @retval Current system tick count + + */ +uint32_t atomTimeGet(void) +{ + return (system_ticks); +} + + +/** + * \b atomTimeSet + * + * This is an internal function not for use by application code. + * + * Sets the current system tick time. + * + * Currently only required for automated test suite to test + * clock behaviour. + * + * This function can be called from interrupt context. + * + * @param[in] new_time New system tick time value + * + * @return None + */ +void atomTimeSet(uint32_t new_time) +{ + system_ticks = new_time; +} + + +/** + * \b atomTimerTick + * + * System tick handler. + * + * User ports are responsible for calling this routine once per system tick. + * + * On each system tick this routine is called to do the following: + * 1. Increase the system tick count + * 2. Call back to any registered timer callbacks + * + * @return None + */ +void atomTimerTick (void) +{ + /* Only do anything if the OS is started */ + if (atomOSStarted) + { + /* Increment the system tick count */ + system_ticks++; + + /* Check for any callbacks that are due */ + atomTimerCallbacks (); + } +} + + +/** + * \b atomTimerDelay + * + * Suspend a thread for the given number of system ticks. + * + * Note that the wakeup time is the number of ticks from the current system + * tick, therefore, for a one tick delay, the thread may be woken up at any + * time between the atomTimerDelay() call and the next system tick. For + * a minimum number of ticks, you should specify minimum number of ticks + 1. + * + * This function can only be called from thread context. + * + * @param[in] ticks Number of system ticks to delay (must be > 0) + * + * @retval ATOM_OK Successful delay + * @retval ATOM_ERR_PARAM Bad parameter (ticks must be non-zero) + * @retval ATOM_ERR_CONTEXT Not called from thread context + */ +uint8_t atomTimerDelay (uint32_t ticks) +{ + ATOM_TCB *curr_tcb_ptr; + ATOM_TIMER timer_cb; + DELAY_TIMER timer_data; + CRITICAL_STORE; + uint8_t status; + + /* Get the current TCB */ + curr_tcb_ptr = atomCurrentContext(); + + /* Parameter check */ + if (ticks == 0) + { + /* Return error */ + status = ATOM_ERR_PARAM; + } + + /* Check we are actually in thread context */ + else if (curr_tcb_ptr == NULL) + { + /* Not currently in thread context, can't suspend */ + status = ATOM_ERR_CONTEXT; + } + + /* Otherwise safe to proceed */ + else + { + /* Protect the system queues */ + CRITICAL_START (); + + /* Set suspended status for the current thread */ + curr_tcb_ptr->suspended = TRUE; + + /* Register the timer callback */ + + /* Fill out the data needed by the callback to wake us up */ + timer_data.tcb_ptr = curr_tcb_ptr; + + /* Fill out the timer callback request structure */ + timer_cb.cb_func = atomTimerDelayCallback; + timer_cb.cb_data = (POINTER)&timer_data; + timer_cb.cb_ticks = ticks; + + /* Store the timeout callback details, though we don't use it */ + curr_tcb_ptr->suspend_timo_cb = &timer_cb; + + /* Register the callback */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + /* Exit critical region */ + CRITICAL_END (); + + /* Timer registration didn't work, won't get a callback */ + status = ATOM_ERR_TIMER; + } + else + { + /* Exit critical region */ + CRITICAL_END (); + + /* Successful timer registration */ + status = ATOM_OK; + + /* Current thread should now block, schedule in another */ + atomSched (FALSE); + } + } + + return (status); +} + + +/** + * \b atomTimerCallbacks + * + * This is an internal function not for use by application code. + * + * Find any callbacks that are due and call them up. + * + * @return None + */ +static void atomTimerCallbacks (void) +{ + ATOM_TIMER *prev_ptr, *next_ptr; + + /* + * Walk the list decrementing each timer's remaining ticks count and + * looking for due callbacks. + */ + prev_ptr = next_ptr = timer_queue; + while (next_ptr) + { + /* Is this entry due? */ + if (--(next_ptr->cb_ticks) == 0) + { + /* Remove the entry from the timer list */ + if (next_ptr == timer_queue) + { + /* We're removing the list head */ + timer_queue = next_ptr->next_timer; + } + else + { + /* We're removing a mid or tail timer */ + prev_ptr->next_timer = next_ptr->next_timer; + } + + /* Call the registered callback */ + if (next_ptr->cb_func) + { + next_ptr->cb_func (next_ptr->cb_data); + } + + /* Do not update prev_ptr, we have just removed this one */ + + } + + /* Entry is not due, leave it in there with its count decremented */ + else + { + /* + * Update prev_ptr to this entry. We will need it if we want + * to remove a mid or tail timer. + */ + prev_ptr = next_ptr; + } + + /* Move on to the next in the list */ + next_ptr = next_ptr->next_timer; + + } + +} + + +/** + * \b atomTimerDelayCallback + * + * This is an internal function not for use by application code. + * + * Callback for atomTimerDelay() calls. Wakes up the sleeping threads. + * + * @param[in] cb_data Callback parameter (DELAY_TIMER ptr for sleeping thread) + * + * @return None + */ +static void atomTimerDelayCallback (POINTER cb_data) +{ + DELAY_TIMER *timer_data_ptr; + CRITICAL_STORE; + + /* Get the DELAY_TIMER structure pointer */ + timer_data_ptr = (DELAY_TIMER *)cb_data; + + /* Check parameter is valid */ + if (timer_data_ptr) + { + /* Enter critical region */ + CRITICAL_START (); + + /* Put the thread on the ready queue */ + (void)tcbEnqueuePriority (&tcbReadyQ, timer_data_ptr->tcb_ptr); + + /* Exit critical region */ + CRITICAL_END (); + + /** + * Don't call the scheduler yet. The ISR exit routine will do this + * in case there are other callbacks to be made, which may also make + * threads ready. + */ + } +} + diff --git a/kernel/atomtimer.h b/kernel/atomtimer.h new file mode 100755 index 0000000..9e7ccf5 --- /dev/null +++ b/kernel/atomtimer.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef __ATOM_TIMER_H +#define __ATOM_TIMER_H + +#include "atomuser.h" + +/* Callback function prototype */ +typedef void ( * TIMER_CB_FUNC ) ( POINTER cb_data ) ; + +/* Data structures */ + +/* Timer descriptor */ +typedef struct atom_timer +{ + TIMER_CB_FUNC cb_func; /* Callback function */ + POINTER cb_data; /* Pointer to callback parameter/data */ + uint32_t cb_ticks; /* Ticks until callback */ + + /* Internal data */ + struct atom_timer *next_timer; /* Next timer in doubly-linked list */ + +} ATOM_TIMER; + +/* Function prototypes */ + +extern uint8_t atomTimerRegister (ATOM_TIMER *timer_ptr); +extern uint8_t atomTimerCancel (ATOM_TIMER *timer_ptr); +extern uint8_t atomTimerDelay (uint32_t ticks); +extern uint32_t atomTimeGet (void); +extern void atomTimeSet (uint32_t new_time); + +#endif /* __ATOM_TIMER_H */ diff --git a/kernel/atomuser-template.h b/kernel/atomuser-template.h new file mode 100755 index 0000000..5e63c1c --- /dev/null +++ b/kernel/atomuser-template.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef __ATOM_USER_H +#define __ATOM_USER_H + + +/* Required number of system ticks per second (normally 100 for 10ms tick) */ +#define SYSTEM_TICKS_PER_SEC 100 + + +/** + * Architecture-specific types. + * Uses the stdint.h naming convention, so if stdint.h is available on the + * platform it is simplest to include it from this header. + */ +#define uint8_t unsigned char +#define uint16_t unsigned short +#define uint32_t unsigned long +#define uint64_t unsigned long long +#define int8_t char +#define int16_t short +#define int32_t long +#define int64_t long long +#define POINTER void * + + +/* Critical region protection */ +#define CRITICAL_STORE uint8_t sreg +#define CRITICAL_START() sreg = SREG; cli(); +#define CRITICAL_END() SREG = sreg + + +#endif /* __ATOM_USER_H */ diff --git a/ports/avr/Doxyfile b/ports/avr/Doxyfile new file mode 100644 index 0000000..e6f6571 --- /dev/null +++ b/ports/avr/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 = atomthreads + +# 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 = doxygen-avr + +# 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 = 4 + +# 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 = NO + +# 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 = NO + +# 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 = + +# 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 = NO + +# 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 = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# 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 = YES + +# 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 = NO + +# 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/ports/avr/Makefile b/ports/avr/Makefile new file mode 100644 index 0000000..768edc1 --- /dev/null +++ b/ports/avr/Makefile @@ -0,0 +1,103 @@ +############ +# Settings # +############ + +# Build all test applications: +# make +# +# Program a test application using UISP (appname => test app e.g. sems1): +# make program app=appname + +# Location of build tools and atomthreads sources +KERNEL_DIR=../../kernel +TESTS_DIR=../../tests +CC=/usr/bin/avr-gcc +OBJCOPY=/usr/bin/avr-objcopy +UISP=/usr/bin/uisp +UISP_DEV=/dev/ttyUSB0 +PART=atmega16 + +# Directory for built objects +BUILD_DIR=build + +# Port/application object files +APP_OBJECTS = atomport.o uart.o tests-main.o +APP_ASM_OBJECTS = atomport-asm.o + +# Kernel object files +KERNEL_OBJECTS = atomkernel.o atomsem.o atommutex.o atomtimer.o atomqueue.o + +# Collection of built objects (excluding test applications) +ALL_OBJECTS = $(APP_OBJECTS) $(APP_ASM_OBJECTS) $(KERNEL_OBJECTS) +BUILT_OBJECTS = $(patsubst %,$(BUILD_DIR)/%,$(ALL_OBJECTS)) + +# Test object files (dealt with separately as only one per application build) +TEST_OBJECTS = $(notdir $(patsubst %.c,%.o,$(wildcard $(TESTS_DIR)/*.c))) + +# Target application filenames (.elf and .hex) for each test object +TEST_ELFS = $(patsubst %.o,%.elf,$(TEST_OBJECTS)) +TEST_HEXS = $(patsubst %.o,%.hex,$(TEST_OBJECTS)) + +# Search build/output directory for dependencies +vpath %.o ./$(BUILD_DIR) +vpath %.elf ./$(BUILD_DIR) +vpath %.hex ./$(BUILD_DIR) + +# GCC flags +CFLAGS=-g -mmcu=$(PART) -Wall -Werror + + +################# +# Build targets # +################# + +# All tests +all: $(BUILD_DIR) $(TEST_HEXS) Makefile + +# Make build/output directory +$(BUILD_DIR): + mkdir $(BUILD_DIR) + +# Test HEX files (one application build for each test) +$(TEST_HEXS): %.hex: %.elf + @echo Building $@ + $(OBJCOPY) -j .text -j .data -O ihex $(BUILD_DIR)/$< $(BUILD_DIR)/$@ + +# Test ELF files (one application build for each test) +$(TEST_ELFS): %.elf: %.o $(KERNEL_OBJECTS) $(APP_OBJECTS) $(APP_ASM_OBJECTS) + $(CC) $(CFLAGS) $(BUILD_DIR)/$(notdir $<) $(BUILT_OBJECTS) --output $(BUILD_DIR)/$@ -Wl,-Map,$(BUILD_DIR)/$(basename $@).map + +# Kernel objects builder +$(KERNEL_OBJECTS): %.o: $(KERNEL_DIR)/%.c + $(CC) -c $(CFLAGS) -I. $< -o $(BUILD_DIR)/$(notdir $@) + +# Test objects builder +$(TEST_OBJECTS): %.o: $(TESTS_DIR)/%.c + $(CC) -c $(CFLAGS) -I. -I$(KERNEL_DIR) $< -o $(BUILD_DIR)/$(notdir $@) + +# Application C objects builder +$(APP_OBJECTS): %.o: ./%.c + $(CC) -c $(CFLAGS) -I. -I$(KERNEL_DIR) -I$(TESTS_DIR) $< -o $(BUILD_DIR)/$(notdir $@) + +# Application asm objects builder +$(APP_ASM_OBJECTS): %.o: ./%.s + $(CC) -c $(CFLAGS) -x assembler-with-cpp -I. -I$(KERNEL_DIR) $< -o $(BUILD_DIR)/$(notdir $@) + +# .lst file builder +%.lst: %.c + $(CC) $(CFLAGS) -I. -I$(KERNEL_DIR) -I$(TESTS_DIR) -Wa,-al $< > $@ + +# Clean +clean: + rm -f *.o *.elf *.map *.hex *.bin *.lst + rm -rf doxygen-kernel + rm -rf doxygen-avr + rm -rf build + +# Send to STK500 +program : $(BUILD_DIR)/$(app).hex + $(UISP) -dprog=stk500 -dserial=$(UISP_DEV) -dpart=$(PART) --erase --upload --verify if=$(BUILD_DIR)/$(app).hex + +doxygen: + doxygen $(KERNEL_DIR)/Doxyfile + doxygen ./Doxyfile \ No newline at end of file diff --git a/ports/avr/atomport-asm.s b/ports/avr/atomport-asm.s new file mode 100644 index 0000000..cdf69fc --- /dev/null +++ b/ports/avr/atomport-asm.s @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2005, Atomthreads Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#include + + +.section .text + +/* + * \b archContextSwitch + * + * Architecture-specific context switch routine. + * + * Note that interrupts are always locked out when this routine is + * called. For cooperative switches, the scheduler will have entered + * a critical region. For preemptions (called from an ISR), the + * ISR will have disabled interrupts on entry. + * + * Note that this function might have been coded in C, but gcc + * was generating prologue and epilogue code to handle the parameters. + * Worse, with the naked attribute set it generated half of the + * prologue/epilogue. Rather than work around the gcc code generation, + * which may change from compiler version to compiler version, we + * just write this function in asm, where we have absolute control + * over the code generation. Important during register saves/restores. + * + * @param[in] old_tcb_ptr Pointer to the thread being scheduled out + * @param[in] new_tcb_ptr Pointer to the thread being scheduled in + * + * @return None + * + * void archContextSwitch (ATOM_TCB *old_tcb_ptr, ATOM_TCB *new_tcb_ptr) + */ +.global archContextSwitch +archContextSwitch: + + /** + * Parameter locations: + * old_tcb_ptr = R25-R24 + * new_tcb_ptr = R23-R22 + */ + + /** + * If this is a cooperative context switch (a thread has called us + * to schedule itself out), gcc will have saved any of the + * registers R18-R27 and R30-R31 which it does not want us to clobber. + * Any registers of that set which it did not need to save are safe + * not to be saved by us anyway. Hence for cooperative context + * switches we only need to save those registers which gcc expects + * us _not_ to modify, that is R2-R17 and R28-R29. + * + * If we were called from an interrupt routine (because a thread + * is being preemptively scheduled out), the situation is exactly + * the same. Any ISR which calls out to a subroutine will have + * similarly saved those registers which it needs us not to + * clobber. In the case of an interrupt, that is every single + * register of the set R18-R27 and R30-R31. (gcc cannot establish + * which of those registers actually need to be saved because + * the information is not available to an ISR). Again, we only + * need to save the registers R2-R17 and R28-29, because these + * are expected to be unclobbered by a subroutine. + * + * Note that in addition to saving R18-R27 and R30-R31, gcc also + * saves R0, R1 and SREG when entering ISRs. In the case of a + * cooperative context switch, it is not necessary to save these. + */ + + /** + * Save registers R2-R17, R28-R29. + */ + push r2 + push r3 + push r4 + push r5 + push r6 + push r7 + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + push r16 + push r17 + push r28 + push r29 + + /** + * Save the final stack pointer to the TCB. The parameter pointing to + * the old TCB is still untouched in R25-R24. We have saved R16/R17 + * and R28/R29 so we can use them for our own purposes now. We must be + * careful not to use R23-R22, however, as these still contain the + * other parameter, old_tcb_ptr. + */ + in r16,_SFR_IO_ADDR(SPL) /* Get the current SP into general regs */ + in r17,_SFR_IO_ADDR(SPH) /* R16/R17 which are now free to use. */ + + mov r28,r24 /* Move old_tcb_ptr param into the Y-regs so we */ + mov r29,r25 /* can access the TCB via a pointer. */ + + st Y,r16 /* Store SPH/SPL to new_tcb_ptr->tcb_save_ptr which */ + std Y+1,r17 /* is conveniently the first member of the TCB. */ + + + /** + * At this point, all of the current thread's context has been saved + * so we no longer care about keeping the contents of any registers. + * + * The stack frame if this is a cooperative switch looks as follows: + * + * + * + * + * + * || + * + * + * + * + * + * The stack frame if this was a preemptive switch looks as follows: + * + * // saved by ISR + * // + * // + * // + * // + * || // + * // + * // + * // + * // + * + * + * + * + * || + * + * + * + * + * + * + * In addition, the thread's stack pointer (after context-save) is + * stored in the thread's TCB. + */ + + /** + * We are now ready to restore the new thread's context. We switch + * our stack pointer to the new thread's stack pointer, and pop + * all of its context off the stack. When we have finished popping + * all registers (R2-R17 and R28-R29), we are ready to return. + * + * Note that any further registers that needed to be saved for the + * thread will be restored on exiting this function. If the new + * thread previously scheduled itself out cooperatively, the + * original calling function will restore any registers it chose + * to save. If the new thread was preempted, we will return to the + * ISR which will restore all other system registers, before + * returning to the interrupted thread. + */ + + /** + * Get the new thread's stack pointer off the TCB (new_tcb_ptr). + * new_tcb_ptr is still stored in the parameter registers, R23-R22. + * We are free to use any other registers, however, as we haven't + * yet popped any of the new thread's context off its stack. + */ + mov r28,r22 /* Move new_tcb_ptr into the Y-regs so we */ + mov r29,r23 /* can access the TCB via a pointer. */ + + ld r16,Y /* Load new_tcb_ptr->sp_save_ptr into R16/R17. */ + ldd r17,Y+1 /* It is conveniently the first member of the TCB. */ + + out _SFR_IO_ADDR(SPL),r16 /* Set our stack pointer to the new thread's */ + out _SFR_IO_ADDR(SPH),r17 /* stack pointer, from its TCB. */ + + /** + * Restore registers R2-R17, R28-R29. + */ + pop r29 + pop r28 + pop r17 + pop r16 + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop r7 + pop r6 + pop r5 + pop r4 + pop r3 + pop r2 + + /** + * The return address on the stack will now be the new thread's return + * address - i.e. although we just entered this function from a + * function called by the old thread, now that we have restored the new + * thread's context, we actually return from this function to wherever + * the new thread was when it was scheduled out. This could be either a + * regular C routine if the new thread previously scheduled itself out + * cooperatively, or it could be an ISR if this new thread was + * previously preempted (on exiting the ISR, execution will return to + * wherever the new thread was originally interrupted). + */ + + /** + * Note that we always just perform a RET here. Although we may + * come in from an ISR and leave through a regular C routine for + * another thread (and visa versa) this is OK, because we don't + * actually need to perform a RETI to tell the processor the + * interrupt is finished. The only extra thing that RETI does is to + * set the I bit (interrupts enabled). If we enter from a regular + * thread context, but leave through an ISR return address and a RETI, + * that just returns and handily enables interrupts for us. Similarly + * if we enter from an ISR and leave back into some thread context + * calls, interrupts will remain disabled through the regular RET + * calls, and we will reenable interrupts in the CRITICAL_END() call + * when we unlock interrupts. + */ + + ret + + +/** + * \b archFirstThreadRestore + * + * Architecture-specific function to restore and start the first thread. + * This is called by atomOSStart() when the OS is starting. + * + * This function will be largely similar to the latter half of + * archContextSwitch(). Its job is to restore the context for the + * first thread, and finally enable interrupts. + * + * It expects to see the context saved in the same way as if the + * thread has been previously scheduled out, and had its context + * saved. That is, archThreadContextInit() will have been called + * first (via atomThreadCreate()) to create a "fake" context save + * area, containing the relevant register-save values for a thread + * restore. + * + * Note that you can create more than one thread before starting + * the OS - only one thread is restored using this function, so + * all other threads are actually restored by archContextSwitch(). + * This is another reminder that the initial context set up by + * archThreadContextInit() must look the same whether restored by + * archFirstThreadRestore() or archContextSwitch(). + * + * @param[in] new_tcb_ptr Pointer to the thread being scheduled in + * + * @return None + * + * void archFirstThreadRestore (ATOM_TCB *new_tcb_ptr) + */ +.global archFirstThreadRestore +archFirstThreadRestore: + + /** + * Parameter locations: + * new_tcb_ptr = R25-R24 + */ + + /** + * First thread restores in the AVR port expect to see R2-R17 and + * R28-R29 stored as context. The context will look exactly like it + * would had a thread cooperatively scheduled itself out. That is, + * these registers will be stored on the stack, and above those will + * be the return address of the calling function. In this case we + * will have set up this "fake" context in archThreadContextInit(), + * and above these registers will be the return address of the thread + * entry point. A "ret" or "reti" instruction will therefore direct + * the processor to the thread entry point, by popping this "return + * address" off the stack. + */ + + /** + * Get the new thread's stack pointer off the TCB (new_tcb_ptr). + * new_tcb_ptr is stored in the parameter registers, R25-R24. + * We are free to use any other registers, however, as we haven't + * yet popped any of the new thread's context off its stack. + */ + mov r28,r24 /* Move new_tcb_ptr into the Y-regs so we */ + mov r29,r25 /* can access the TCB via a pointer. */ + + ld r16,Y /* Load new_tcb_ptr->sp_save_ptr into R16/R17. */ + ldd r17,Y+1 /* It is conveniently the first member of the TCB. */ + + out _SFR_IO_ADDR(SPL),r16 /* Set our stack pointer to the new thread's */ + out _SFR_IO_ADDR(SPH),r17 /* stack pointer, from its TCB. */ + + /** + * Restore registers R2-R17, R28-R29. + */ + pop r29 + pop r28 + pop r17 + pop r16 + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop r7 + pop r6 + pop r5 + pop r4 + pop r3 + pop r2 + + /** + * The "return address" left on the stack now will be the new + * thread's entry point. RETI will take us there as if we had + * actually been there before calling this subroutine, whereas + * the return address was actually set up by archThreadContextInit(). + * + * As discussed above, this function is responsible for enabling + * interrupts once all context has been restored. We can do this + * using a single RETI instruction (return and enable interrupts), + * but it is also safe at this point to have two separate + * instructions: + * sei // enable interrupts + * ret // return to new thread entry point + */ + reti + + diff --git a/ports/avr/atomport.c b/ports/avr/atomport.c new file mode 100644 index 0000000..52c6b41 --- /dev/null +++ b/ports/avr/atomport.c @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include + +#include "atom.h" +#include "atomport.h" + + +/** Forward declarations */ +static void thread_shell (void); + + +/** + * \b thread_shell + * + * Shell routine which is used to call all thread entry points. + * + * This routine is called whenever a new thread is starting, and is + * responsible for taking the entry point parameter off the TCB + * and passing this into the thread entry point, as well as enabling + * interrupts. This is an optional function for a port, as it is + * also possible to do this with a regular context restore if the + * appropriate registers are saved for context switches (parameter + * and interrupt enable). Alternatively a flag could be used to + * notify archFirstThreadRestore() and archContextSwitch() + * that they should this time restore the contents of the parameter + * registers and enable interrupts. After restoring a thread first + * time the context restore routines need not perform those + * operations again. This is discussed in more detail below. + * + * When starting new threads, ports must pass in the entry point + * function parameter, and enable interrupts. This can be handled by + * the first context restore for new threads if they are saved as + * part of the thread's context. However for the AVR port we have + * chosen to save only the minimum registers required for context + * switches. This reduces the cycle time required for all context + * switches during operation. This means, however, that we don't save + * the parameter registers R25/R24 or the SREG. These would otherwise + * be used when restoring a thread for the first time to pass the + * parameter to the entry point, and enabling interrupts in the SREG. + * + * A few of the possible ways round this are to: + * + * a) Save R25/R24 and SREG with the normal context - thus increasing + * the processor cycles required on each context switch. + * b) Use a thread shell routine which is used to start all threads. + * This routine can then pass the parameter and enable interrupts, + * without incurring any overhead on all context switches. + * c) Store a flag in a new thread's TCB to notify the normal context + * switch routine that it must (the first time a thread is restored + * only) pass the parameter, and enable interrupts. + * + * We have chosen to implement (b) in this case, as it does not affect + * normal context switch times just for the benefit of the first restore, + * and it does not incur extra complication to the thread restore + * routines through handling special cases. A thread shell is also handy + * for providing port users with a place to do any initialisation that + * must be done for each thread (e.g. opening stdio files etc). + * + * Other ports are free to implement whatever scheme they wish. In + * particular if you save all necessary registers on a context switch + * then you need not worry about any special requirements for + * starting threads for the first time. + * + * @return None + */ +static void thread_shell (void) +{ + ATOM_TCB *curr_tcb; + + /* Get the TCB of the thread being started */ + curr_tcb = atomCurrentContext(); + + /** + * Enable interrupts - these will not be enabled when a thread + * is first restored. + */ + sei(); + + /* Call the thread entry point */ + if (curr_tcb && curr_tcb->entry_point) + { + curr_tcb->entry_point(curr_tcb->entry_param); + } + + /* Not reached - threads should never return from the entry point */ + +} + + +/** + * \b archThreadContextInit + * + * Architecture-specific thread context initialisation routine. + * + * This function must set up a thread's context ready for restoring + * and running the thread via archFirstThreadRestore() or + * archContextSwitch(). + * + * The layout required to fill the correct register values is + * described in archContextSwitch(). Note that not all registers + * are restored by archContextSwitch() and archFirstThreadRestore() + * as this port takes advantage of the fact that not all registers + * must be stored by gcc-avr C subroutines. This means that we don't + * provide start values for those registers, as they are "don't cares". + * + * Because we don't actually save the parameter registers (R25-R24) + * for this particular architecture, we use a separate thread shell. + * The thread shell is always used as the thread entry point stored + * in the thread context, and it does the actual calling of the + * proper thread entry point, passing the thread entry parameter. + * This allows us to pass the entry parameter without actually + * storing it on the stack (the thread shell routine takes the + * entry point and parameter from the thread's TCB). On other ports + * you may instead choose to store the entry point and parameter + * in the thread context and use no thread shell routine. + * + * Similarly we use the thread shell in this case to enable interrupts. + * When a thread is restored and started for the first time, it must + * also enable interrupts. This might be done by setting up the + * appropriate value in the SREG register for enabled interrupts, which + * would then be restored when the thread is first started. But to + * reduce register-saves we do not save SREG on the AVR port, and + * instead we use the thread shell routine to enable interrupts the + * first time a thread is started. + * + * @param[in] tcb_ptr Pointer to the TCB of the thread being created + * @param[in] stack_top Pointer to the top of the new thread's stack + * @param[in] entry_point Pointer to the thread entry point function + * @param[in] entry_param Parameter to be passed to the thread entry point + * + * @return None + */ +void archThreadContextInit (ATOM_TCB *tcb_ptr, void *stack_top, void (*entry_point)(uint32_t), uint32_t entry_param) +{ + uint8_t *stack_ptr; + + /** Start at stack top */ + stack_ptr = (uint8_t *)stack_top; + + /** + * After restoring all of the context registers, the thread restore + * routines will perform a RET or RETI which expect to find the + * address of the calling routine on the stack. In this case (the + * first time a thread is run) we "return" to the entry point for + * the thread. That is, we store the thread entry point in the + * place that RET and RETI will look for the return address: the + * stack. + * + * Note that we are using the thread_shell() routine to start all + * threads, so we actually store the address of thread_shell() + * here. Other ports may store the real thread entry point here + * and call it directly from the thread restore routines. + * + * Because we are filling the stack from top to bottom, this goes + * on the stack first (at the top). + */ + *stack_ptr-- = (uint8_t)((uint16_t)thread_shell & 0xFF); + *stack_ptr-- = (uint8_t)(((uint16_t)thread_shell >> 8) & 0xFF); + + /** + * For the AVR port the parameter registers (R25-R24) are not + * saved and restored by the context switch routines. This means + * that they cannot be used to pass a parameter to the entry + * point the first time a thread is restored. Rather than incur + * the overhead of saving them (just for the benefit of starting + * threads) we can either use a flag here to notify the context + * restore routines (archThreadFirstRestore() and + * archContextSwitch()) that they should restore R25-R24 this + * one time, or we can use a thread shell routine which replaces + * that actual thread entry point. In this case we use a thread + * shell which is responsible for passing the parameters to the + * actual thread entry point, without adding extra processing + * to the context switch routines. + * + * Other ports may wish to store entry_param in the appropriate + * parameter registers when creating a thread's context, + * particularly if that port saves those registers anyway. + * + * Similarly, although interrupts must be enabled when starting + * new threads, we also defer this to the thread shell because + * we don't save the SREG contents for normal context switches. + * Other ports may choose to context switch the relevant + * interrupt enable register, so that the first context switch + * is able to enable interrupts during its normal context + * restore. + */ + + /** + * Store starting register values for R2-R17, R28-R29 + */ + *stack_ptr-- = 0x00; /* R2 */ + *stack_ptr-- = 0x00; /* R3 */ + *stack_ptr-- = 0x00; /* R4 */ + *stack_ptr-- = 0x00; /* R5 */ + *stack_ptr-- = 0x00; /* R6 */ + *stack_ptr-- = 0x00; /* R7 */ + *stack_ptr-- = 0x00; /* R8 */ + *stack_ptr-- = 0x00; /* R9 */ + *stack_ptr-- = 0x00; /* R10 */ + *stack_ptr-- = 0x00; /* R11 */ + *stack_ptr-- = 0x00; /* R12 */ + *stack_ptr-- = 0x00; /* R13 */ + *stack_ptr-- = 0x00; /* R14 */ + *stack_ptr-- = 0x00; /* R15 */ + *stack_ptr-- = 0x00; /* R16 */ + *stack_ptr-- = 0x00; /* R17 */ + *stack_ptr-- = 0x00; /* R28 */ + *stack_ptr-- = 0x00; /* R29 */ + + /** + * All thread context has now been initialised. Save the current + * stack pointer to the thread's TCB so it knows where to start + * looking when the thread is started. + */ + tcb_ptr->sp_save_ptr = stack_ptr; + +} + + +/** + * \b avrInitSystemTickTimer + * + * Initialise the system tick timer. Uses the AVR's timer1 facility. + * + * @return None + */ +void avrInitSystemTickTimer ( void ) +{ + /* Set timer 1 compare match value for configured system tick, + * with a prescaler of 256. We will get a compare match 1A + * interrupt on every system tick, in which we must call the + * OS's system tick handler. */ + OCR1A = (AVR_CPU_HZ / 256 / SYSTEM_TICKS_PER_SEC); + + /* Enable compare match 1A interrupt */ + TIMSK = _BV(OCIE1A); + + /* Set prescaler 256 */ + TCCR1B = _BV(CS12) | _BV(WGM12); +} + + +/** + * + * System tick ISR. + * + * This is responsible for regularly calling the OS system tick handler. + * The system tick handler checks if any timer callbacks are necessary, + * and runs the scheduler. + * + * The compiler automatically saves all registers necessary before calling + * out to a C routine. This will be (at least) R0, R1, SREG, R18-R27 and + * R30/R31. + * + * The system may decide to schedule in a new thread during the call to + * atomTimerTick(), in which case around half of the thread's context will + * already have been saved here, ready for when we return here when the + * interrupted thread is scheduled back in. The remaining context will be + * saved by the context switch routine. + * + * As with all interrupts, the ISR should call atomIntEnter() and + * atomIntExit() on entry and exit. This serves two purposes: + * + * a) To notify the OS that it is running in interrupt context + * b) To defer the scheduler until after the ISR is completed + * + * We defer all scheduling decisions until after the ISR has completed + * in case the interrupt handler makes more than one thread ready. + * + * @return None + */ +ISR (TIMER1_COMPA_vect) +{ + /* Call the interrupt entry routine */ + atomIntEnter(); + + /* Call the OS system tick handler */ + atomTimerTick(); + + /* Call the interrupt exit routine */ + atomIntExit(TRUE); +} + + +/** + * + * Default (no handler installed) ISR. + * + * Installs a default handler to be called if any interrupts occur for + * which we have not registered an ISR. This is empty and has only been + * included to handle user-created code which may enable interrupts. The + * core OS does not enable any interrupts other than the system timer + * tick interrupt. + * + * @return None + */ +ISR (BADISR_vect) +{ + /* Empty */ +} \ No newline at end of file diff --git a/ports/avr/atomport.h b/ports/avr/atomport.h new file mode 100644 index 0000000..426c0a4 --- /dev/null +++ b/ports/avr/atomport.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef __ATOM_PORT_H +#define __ATOM_PORT_H + +/* CPU Frequency */ +#define AVR_CPU_HZ 1000000 + +/* Function prototypes */ +void avrInitSystemTickTimer ( void ); + +#endif /* __ATOM_PORT_H */ diff --git a/ports/avr/atomuser.h b/ports/avr/atomuser.h new file mode 100644 index 0000000..8f611e0 --- /dev/null +++ b/ports/avr/atomuser.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef __ATOM_USER_H +#define __ATOM_USER_H + +#include +#include +#include + +/* Portable uint8_t and friends available from stdint.h on this platform */ +#include + + +/* Required number of system ticks per second (normally 100 for 10ms tick) */ +#define SYSTEM_TICKS_PER_SEC 100 + + +/** + * Architecture-specific types. + * Most of these are available from stdint.h on this platform, which is + * included above. + */ +#define POINTER void * + + +/* Critical region protection */ +#define CRITICAL_STORE uint8_t sreg +#define CRITICAL_START() sreg = SREG; cli(); +#define CRITICAL_END() SREG = sreg + + +#endif /* __ATOM_USER_H */ diff --git a/ports/avr/gdb.txt b/ports/avr/gdb.txt new file mode 100644 index 0000000..070a888 --- /dev/null +++ b/ports/avr/gdb.txt @@ -0,0 +1,6 @@ +delete +file atomapp.elf +target remote localhost:1212 +load +break main_thread_func +cont diff --git a/ports/avr/tests-main.c b/ports/avr/tests-main.c new file mode 100644 index 0000000..13c7876 --- /dev/null +++ b/ports/avr/tests-main.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#include + +#include + +#include "atom.h" +#include "atomport.h" +#include "atomtests.h" +#include "atomtimer.h" + +#include "uart.h" + + +/* Constants */ + +/* + * Idle thread stack size + * + * This needs to be large enough to handle any interrupt handlers + * and callbacks called by interrupt handlers (e.g. user-created + * timer callbacks) as well as the saving of all context when + * switching away from this thread. + * + * In this case, the idle stack is allocated on the BSS via the + * idle_thread_stack[] byte array. + */ +#define IDLE_STACK_SIZE_BYTES 128 + + +/* + * Startup code stack size + * + * This defines the size of stack allowed for the main() startup + * code before the OS is actually started. This needs to be large + * enough to manage the atomOSInit(), atomOSStart() and + * atomThreadCreate() calls which occur before the OS is started. + * + * In this case we use the default startup stack location used by + * avr-gcc of the top of RAM (defined as RAMEND). After the OS + * is started this allocation is no longer required,therefore + * you could alternatively use some location which you know that + * your application will not use until the OS is started. Note + * that you cannot use the idle thread or main thread stack here + * because the stack contexts of these threads are initialised + * during OS creation. + * + * Instead of reusing some application area, here we set aside + * 64 bytes of RAM for this purpose, because we call out to + * several different test applications, and do not know of any + * particular application locations which will be free to use. + */ +#define STARTUP_STACK_SIZE_BYTES 64 + + +/* + * Main thread stack size + * + * Here we utilise the space starting at 64 bytes below the startup + * stack for the Main application thread. Note that this is not a + * required OS kernel thread - you will replace this with your own + * application thread. + * + * In this case the Main thread is responsible for calling out to the + * test routines. Once a test routine has finished, the thread remains + * running and loops printing out an error message (if the test failed) + * or flashes a LED once per second (if the test passed). + * + * The Main thread stack generally needs to be larger than the idle + * thread stack, as not only does it need to store interrupt handler + * stack saves and context switch saves, but the application main thread + * will generally be carrying out more nested function calls and require + * stack for application code local variables etc. + * + * With all OS tests implemented to date on the AVR, the Main thread + * stack has not exceeded 147 bytes. Care must be taken to ensure that + * the data section, BSS section, and 64 byte startup section leave + * enough free space for the main thread. You can use the avr-size + * command to view the size of the BSS and data sections in your + * application ELF files. For example if you require a 196 byte main + * thread stack, then the data, BSS and startup stack combined must + * not exceed RAMSIZE-196 bytes. + */ + + +/* Local data */ + +/* Application threads' TCBs */ +static ATOM_TCB main_tcb; + +/* Idle thread's stack area */ +static uint8_t idle_thread_stack[IDLE_STACK_SIZE_BYTES]; + +/* STDIO stream */ +static FILE uart_stdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE); + +/* Forward declarations */ +static void main_thread_func (uint32_t data); + + +/** + * \b main + * + * Program entry point. + * + * Sets up the AVR hardware resources (system tick timer interrupt) necessary + * for the OS to be started. Creates an application thread and starts the OS. + */ +int main ( void ) +{ + int8_t status; + + /** + * Note: to protect OS structures and data during initialisation, + * interrupts must remain disabled until the first thread + * has been restored. They are reenabled at the very end of + * the first thread restore, at which point it is safe for a + * reschedule to take place. + */ + + /* Initialise the OS before creating our threads */ + status = atomOSInit(&idle_thread_stack[IDLE_STACK_SIZE_BYTES - 1]); + if (status == ATOM_OK) + { + /* Enable the system tick timer */ + avrInitSystemTickTimer(); + + /* Create an application thread */ + status = atomThreadCreate(&main_tcb, + TEST_THREAD_PRIO, main_thread_func, 0, + (POINTER)(RAMEND-STARTUP_STACK_SIZE_BYTES)); + if (status == ATOM_OK) + { + /** + * First application thread successfully created. It is + * now possible to start the OS. Execution will not return + * from atomOSStart(), which will restore the context of + * our application thread and start executing it. + * + * Note that interrupts are still disabled at this point. + * They will be enabled as we restore and execute our first + * thread in archFirstThreadRestore(). + */ + atomOSStart(); + } + } + + while (1) + ; + + /* There was an error starting the OS if we reach here */ + return (0); +} + + +/** + * \b main_thread_func + * + * Entry point for main application thread. + * + * This is the first thread that will be executed when the OS is started. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void main_thread_func (uint32_t data) +{ + uint32_t test_status; + + /* Enable all LEDs (STK500-specific) */ + DDRB = 0xFF; + PORTB = 0xFF; + + /* Initialise UART (9600bps) */ + if (uart_init(9600) != 0) + { + /* Error initialising UART */ + } + + /** + * Redirect stdout via the UART. Note that the UART write routine + * is protected via a semaphore, so the OS must be started before + * use of the UART. + */ + stdout = &uart_stdout; + + /* Put a message out on the UART */ + printf_P(PSTR("Go\n")); + + /* Start test. All tests use the same start API. */ + test_status = test_start(); + + /* Test finished, sleep forever */ + while (1) + { + /* Log test status */ + if (test_status == 0) + { + /* Toggle a LED (STK500-specific) */ + PORTB ^= (1 << 7); + } + else + { + printf_P (PSTR("Fail%d\n"), atomTimeGet()); + } + + /* Sleep for one second and log status again */ + atomTimerDelay(SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/ports/avr/uart.c b/ports/avr/uart.c new file mode 100644 index 0000000..d6edcb9 --- /dev/null +++ b/ports/avr/uart.c @@ -0,0 +1,85 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Joerg Wunsch + * ---------------------------------------------------------------------------- + * + * Stdio demo, UART implementation + * + * $Id: uart.c,v 1.1 2005/12/28 21:38:59 joerg_wunsch Exp $ + */ + +#include +#include + +#include + +#include "atom.h" +#include "atommutex.h" +#include "atomport.h" +#include "uart.h" + +/* + * Semaphore for single-threaded access to UART device + */ +static ATOM_MUTEX uart_mutex; + + +/* + * Initialize the UART to 9600 Bd, tx/rx, 8N1. + */ +int +uart_init(uint32_t baudrate) +{ + int status; + + /* Set up the UART device with the selected baudrate */ +#if AVR_CPU_HZ < 2000000UL && defined(U2X) + UCSRA = _BV(U2X); /* improve baud rate error by using 2x clk */ + UBRRL = (AVR_CPU_HZ / (8UL * baudrate)) - 1; +#else + UBRRL = (AVR_CPU_HZ / (16UL * baudrate)) - 1; +#endif + UCSRB = _BV(TXEN) | _BV(RXEN); /* tx/rx enable */ + + /* Create a mutex for single-threaded putchar() access */ + if (atomMutexCreate (&uart_mutex) != ATOM_OK) + { + status = -1; + } + else + { + status = 0; + } + + /* Finished */ + return (status); +} + +/* + * Send character c down the UART Tx, wait until tx holding register + * is empty. + */ +int +uart_putchar(char c, FILE *stream) +{ + + /* Block on private access to the UART */ + if (atomMutexGet(&uart_mutex, 0) == ATOM_OK) + { + /* Convert \n to \r\n */ + if (c == '\n') + uart_putchar('\r', stream); + + /* Wait until the UART is ready then send the character out */ + loop_until_bit_is_set(UCSRA, UDRE); + UDR = c; + + /* Return mutex access */ + atomMutexPut(&uart_mutex); + } + + return 0; +} diff --git a/ports/avr/uart.h b/ports/avr/uart.h new file mode 100644 index 0000000..15c0370 --- /dev/null +++ b/ports/avr/uart.h @@ -0,0 +1,25 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Joerg Wunsch + * ---------------------------------------------------------------------------- + * + * Stdio demo, UART declarations + * + * $Id: uart.h,v 1.1 2005/12/28 21:38:59 joerg_wunsch Exp $ + */ + +#include "atom.h" + + +/* + * Perform UART startup initialization. + */ +int uart_init(uint32_t baudrate); + +/* + * Send one character to the UART. + */ +int uart_putchar(char c, FILE *stream); diff --git a/tests/atomtests.h b/tests/atomtests.h new file mode 100644 index 0000000..da28159 --- /dev/null +++ b/tests/atomtests.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef __ATOM_TESTS_H +#define __ATOM_TESTS_H + +/* Include Atomthreads kernel API */ +#include "atom.h" + +/* Prerequisite include for ATOMLOG() macro (via printf) */ +#include + +/* Logger macro for viewing test results */ +#define ATOMLOG printf_P + +/* + * String location macro: for platforms which need to place strings in + * alternative locations, e.g. on avr-gcc strings can be placed in + * program space, saving SRAM. On most platforms this can expand to + * empty. + */ +#define _STR(x) PSTR(x) + +/* Default thread stack size (in bytes) */ +#define TEST_THREAD_STACK_SIZE 128 + +/* Default thread priority */ +#define TEST_THREAD_PRIO 16 + +/* API for starting each test */ +extern uint32_t test_start (void); + + +#endif /* __ATOM_TESTS_H */ diff --git a/tests/kern1.c b/tests/kern1.c new file mode 100644 index 0000000..baac8bb --- /dev/null +++ b/tests/kern1.c @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" + + +/* Test OS objects */ +static ATOM_TCB tcb1; +static uint8_t test_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start kernel test. + * + * This tests the handling of bad parameters within the public kernel APIs. + * + * Other than during initialisation, the only API that takes parameters + * which require checking (and that application code might call) is the + * thread create API. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + + /* Default to zero failures */ + failures = 0; + + /* atomThreadCreate: Pass a bad TCB pointer */ + if (atomThreadCreate (NULL, TEST_THREAD_PRIO, test_thread_func, 0, + &test_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Bad TCB check\n")); + failures++; + } + + /* atomThreadCreate: Pass a bad entry point */ + if (atomThreadCreate (&tcb1, TEST_THREAD_PRIO, NULL, 0, + &test_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Bad entry check\n")); + failures++; + } + + /* atomThreadCreate: Pass a bad stack pointer */ + if (atomThreadCreate (&tcb1, TEST_THREAD_PRIO, test_thread_func, 0, + NULL) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Bad stack ptr check\n")); + failures++; + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} diff --git a/tests/kern2.c b/tests/kern2.c new file mode 100644 index 0000000..371c94e --- /dev/null +++ b/tests/kern2.c @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" + + +/** + * \b test_start + * + * Start kernel test. + * + * This is a basic test of the thread context-switch functionality. It + * creates twenty local (byte) variables and schedules the thread out + * for one second. If context-switch save/restore is not implemented + * correctly, you might expect one or more of these local variables to + * be corrupted by the time the thread is scheduled back in. + * + * Note that this is a fairly unsophisticated test, and a lot depends on + * how the compiler deals with the variables, as well as what code is + * executed while the thread is scheduled out. It should flag up any + * major problems with the context-switch, however. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + uint8_t one = 1; + uint8_t two = 2; + uint8_t three = 3; + uint8_t four = 4; + uint8_t five = 5; + uint8_t six = 6; + uint8_t seven = 7; + uint8_t eight = 8; + uint8_t nine = 9; + uint8_t ten = 10; + uint8_t eleven = 11; + uint8_t twelve = 12; + uint8_t thirteen = 13; + uint8_t fourteen = 14; + uint8_t fifteen = 15; + uint8_t sixteen = 16; + uint8_t seventeen = 17; + uint8_t eighteen = 18; + uint8_t nineteen = 19; + uint8_t twenty = 20; + + /* Default to zero failures */ + failures = 0; + + /* Sleep for one second */ + atomTimerDelay(SYSTEM_TICKS_PER_SEC); + + /* Check all variables contain expected values */ + if (one != 1) + { + ATOMLOG (_STR("1(%d)\n"), one); + failures++; + } + if (two != 2) + { + ATOMLOG (_STR("2(%d)\n"), two); + failures++; + } + if (three != 3) + { + ATOMLOG (_STR("3(%d)\n"), three); + failures++; + } + if (four != 4) + { + ATOMLOG (_STR("4(%d)\n"), four); + failures++; + } + if (five != 5) + { + ATOMLOG (_STR("5(%d)\n"), five); + failures++; + } + if (six != 6) + { + ATOMLOG (_STR("6(%d)\n"), six); + failures++; + } + if (seven != 7) + { + ATOMLOG (_STR("7(%d)\n"), seven); + failures++; + } + if (eight != 8) + { + ATOMLOG (_STR("8(%d)\n"), eight); + failures++; + } + if (nine != 9) + { + ATOMLOG (_STR("9(%d)\n"), nine); + failures++; + } + if (ten != 10) + { + ATOMLOG (_STR("10(%d)\n"), ten); + failures++; + } + if (eleven != 11) + { + ATOMLOG (_STR("11(%d)\n"), eleven); + failures++; + } + if (twelve != 12) + { + ATOMLOG (_STR("12(%d)\n"), twelve); + failures++; + } + if (thirteen != 13) + { + ATOMLOG (_STR("13(%d)\n"), thirteen); + failures++; + } + if (fourteen != 14) + { + ATOMLOG (_STR("14(%d)\n"), fourteen); + failures++; + } + if (fifteen != 15) + { + ATOMLOG (_STR("15(%d)\n"), fifteen); + failures++; + } + if (sixteen != 16) + { + ATOMLOG (_STR("16(%d)\n"), sixteen); + failures++; + } + if (seventeen != 17) + { + ATOMLOG (_STR("17(%d)\n"), seventeen); + failures++; + } + if (eighteen != 18) + { + ATOMLOG (_STR("18(%d)\n"), eighteen); + failures++; + } + if (nineteen != 19) + { + ATOMLOG (_STR("19(%d)\n"), nineteen); + failures++; + } + if (twenty != 20) + { + ATOMLOG (_STR("20(%d)\n"), twenty); + failures++; + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} \ No newline at end of file diff --git a/tests/kern3.c b/tests/kern3.c new file mode 100644 index 0000000..9ca835e --- /dev/null +++ b/tests/kern3.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" + + +/* Test OS objects */ +static ATOM_TCB tcb1, tcb2; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Test global data (one per thread) */ +static volatile int running_flag[2]; +static volatile int sleep_request[2]; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start kernel test. + * + * This tests the scheduling of threads at different priorities, and + * preemption of lower priority threads by higher priority threads. + * + * Much of this functionality is already tested implicitly by the + * semaphore, mutex tests etc but we repeat it here within the kernel + * tests for completeness. + * + * Two threads are created at different priorities, with each thread + * setting a running flag whenever it runs. We check that when the + * higher priority thread is ready to run, only the higher priority + * thread's running flag is set (even though the lower priority + * thread should also be setting it at this time). This checks that + * the scheduler is correctly prioritising thread execution. + * + * The test also exercises preemption, by disabling setting of the + * running flag in the higher priority thread for a period. During + * this time the higher priority thread repeatedly sleeps for one + * system tick then wakes up to check the sleep-request flag again. + * Every time the higher priority thread wakes up, it has preempted + * the lower priority thread (which is always running). By ensuring + * that the higher priority thread is able to start running again + * after one of these periods (through checking the running flag) + * we prove that the preemption has worked. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + int i; + + /* Default to zero failures */ + failures = 0; + + /* Initialise global data */ + running_flag[0] = running_flag[1] = FALSE; + sleep_request[0] = sleep_request[1] = FALSE; + + /* Create low priority thread */ + if (atomThreadCreate (&tcb1, 253, test_thread_func, 0, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + ATOMLOG (_STR("Bad thread create\n")); + failures++; + } + + /* Create high priority thread */ + else if (atomThreadCreate (&tcb2, 252, test_thread_func, 1, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + ATOMLOG (_STR("Bad thread create\n")); + failures++; + } + + /* Repeat test a few times */ + for (i = 0; i < 8; i++) + { + /* Make the higher priority thread sleep */ + sleep_request[1] = TRUE; + + /* Sleep a little to make sure the thread sees the sleep request */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Reset the running flag for both threads */ + running_flag[0] = running_flag[1] = FALSE; + + /* Sleep a little to give any running threads time to set their flag */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Check only the low priority thread has run since we reset the flags */ + if ((running_flag[0] != TRUE) || (running_flag[1] != FALSE)) + { + ATOMLOG (_STR("Lo%d %d/%d\n"), i, running_flag[0], running_flag[1]); + failures++; + break; + } + else + { + /* + * We have confirmed that only the ready thread has been running. + * Now check that if we wake up the high priority thread, the + * low priority one stops running and only the high priority one + * does. + */ + + /* Tell the higher priority thread to stop sleeping */ + sleep_request[1] = FALSE; + + /* Reset the running flag for both threads */ + running_flag[0] = running_flag[1] = FALSE; + + /* Sleep a little to give any running threads time to set their flag */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Check only the high priority thread has run since we reset the flags */ + if ((running_flag[1] != TRUE) || (running_flag[0] != FALSE)) + { + ATOMLOG (_STR("Hi%d/%d\n"), running_flag[0], running_flag[1]); + failures++; + break; + } + else + { + /* + * We have confirmed that the high priority thread has preempted the + * low priority thread, and remain running while never scheduling + * the lower one back in. + */ + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data Thread ID (0 = low prio, 1 = high prio) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + int thread_id; + + /* Pull out thread ID */ + thread_id = (int)data; + + /* Run forever */ + while (1) + { + /* If this thread is requested to sleep, sleep until told to stop */ + if (sleep_request[thread_id]) + { + atomTimerDelay (1); + } + else + { + /* Otherwise set running flag for this thread */ + running_flag[thread_id] = TRUE; + } + } +} diff --git a/tests/kern4.c b/tests/kern4.c new file mode 100644 index 0000000..e390f5f --- /dev/null +++ b/tests/kern4.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" + + +/* Test OS objects */ +static ATOM_TCB tcb1, tcb2, tcb3, tcb4; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Test global data (one per thread) */ +static uint32_t volatile last_time; +static int volatile last_thread_id; +static volatile int switch_cnt; +static uint32_t volatile failure_cnt[4]; +static int volatile test_started; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start kernel test. + * + * This tests the round-robin timeslicing of same priority threads. + * + * Four threads are created (over and above the main test thread). + * The main test thread sleeps for the duration of the entire test. + * While the test is ongoing, all four threads are running + * continuously at the same priority. They each check that whenever + * the system tick (atomTimeGet()) changes, it has moved on 1 tick + * since the last time the tick was checked, and that the previous + * thread is the one created before itself. In the case of the first + * thread created, the previous thread should be the last thread + * created. This proves that on every tick the four threads get a + * schedule timeslice equally, and in the same order throughout the + * duration of the test. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + + /* Default to zero failures */ + failures = 0; + + /* Initialise global data */ + last_time = 0; + last_thread_id = -1; + failure_cnt[0] = failure_cnt[1] = failure_cnt[2] = failure_cnt[3] = 0; + + /* Set test as not started until all threads are ready to go */ + test_started = FALSE; + + /* + * Create all four threads at the same priority as each other. + * They are given a lower priority than this thread, however, + * to ensure that once this thread wakes up to stop the test it + * can do so without confusing the scheduling tests by having + * a spell in which this thread was run. + */ + if (atomThreadCreate (&tcb1, TEST_THREAD_PRIO + 1, test_thread_func, 0, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + ATOMLOG (_STR("Bad thread create\n")); + failures++; + } + else if (atomThreadCreate (&tcb2, TEST_THREAD_PRIO + 1, test_thread_func, 1, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + ATOMLOG (_STR("Bad thread create\n")); + failures++; + } + else if (atomThreadCreate (&tcb3, TEST_THREAD_PRIO + 1, test_thread_func, 2, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + ATOMLOG (_STR("Bad thread create\n")); + failures++; + } + else if (atomThreadCreate (&tcb4, TEST_THREAD_PRIO + 1, test_thread_func, 3, + &test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + ATOMLOG (_STR("Bad thread create\n")); + failures++; + } + + /* Start the test */ + test_started = TRUE; + + /* Sleep for 5 seconds during test */ + atomTimerDelay (5 * SYSTEM_TICKS_PER_SEC); + + /* Stop the test */ + test_started = FALSE; + + /* Sleep for tests to complete */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Count any failures from test threads */ + failures += failure_cnt[0] + failure_cnt[1] + failure_cnt[2] + failure_cnt[3]; + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data Thread ID (0 to 3) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + int thread_id, expected_thread; + int time_error, thread_error; + uint32_t new_time; + CRITICAL_STORE; + + /* Pull out thread ID */ + thread_id = (int)data; + + /* Run forever */ + while (1) + { + /* Check if test is currently in operation */ + if (test_started) + { + /* + * If the system time has ticked over, check that the currently + * running thread is not the one that was running last tick. + */ + + /* Default to no error this time */ + time_error = thread_error = FALSE; + + /* Do the whole operation with interrupts locked out */ + CRITICAL_START(); + + /* Check if time has ticked over */ + new_time = atomTimeGet(); + + /* Only perform the check if this is not the first thread to run */ + if ((last_time != 0) && (last_thread_id != -1)) + { + /* Check if the time has ticked over */ + if (new_time != last_time) + { + /* Check time only ticked over by 1 */ + if ((new_time - last_time) != 1) + { + time_error = 1; + } + + /* + * We are expecting the previous thread to be our thread_id + * minus one. + */ + expected_thread = thread_id - 1; + if (expected_thread == -1) + { + expected_thread = 3; + } + + /* Check that the last thread was the expected one */ + if (last_thread_id != expected_thread) + { + thread_error = TRUE; + } + + /* Increment the switch count */ + switch_cnt++; + } + } + + /* Store the currently-running thread as the last-running */ + last_thread_id = thread_id; + last_time = new_time; + + /* Finished with the interrupt lockout */ + CRITICAL_END(); + + /* If we got an error above, increment the total failure count */ + if (test_started && (thread_error || time_error)) + { + failure_cnt[thread_id]++; + ATOMLOG(_STR("T%d\n"), thread_id); + } + } + } +} diff --git a/tests/mutex1.c b/tests/mutex1.c new file mode 100644 index 0000000..6ca9e47 --- /dev/null +++ b/tests/mutex1.c @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atommutex.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test OS objects */ +static ATOM_MUTEX mutex1; +static ATOM_TCB tcb1, tcb2; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Test result tracking */ +static volatile int g_result; + + +/* Forward declarations */ +static void test1_thread_func (uint32_t data); +static void test2_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start mutex test. + * + * This test exercises the mutex creation and deletion APIs, including + * waking threads blocking on a mutex if the mutex is deleted. + * Deletion wakeups are tested twice: once for a thread which is blocking + * with a timeout and once for a thread which is blocking with no timeout. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + uint32_t i; + + /* Default to zero failures */ + failures = 0; + + /* Test creation and deletion of mutexes: good values */ + for (i = 0; i < 1000; i++) + { + if (atomMutexCreate (&mutex1) == ATOM_OK) + { + if (atomMutexDelete (&mutex1) == ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Error deleting mutex\n")); + failures++; + break; + } + } + else + { + /* Fail */ + ATOMLOG (_STR("Error creating mutex\n")); + failures++; + break; + } + } + + /* Test creation and deletion of mutexes: creation checks */ + if (atomMutexCreate (NULL) != ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Bad mutex creation checks\n")); + failures++; + } + + /* Test creation and deletion of mutexes: deletion checks */ + if (atomMutexDelete (NULL) != ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Bad mutex deletion checks\n")); + failures++; + } + + /* Test wakeup of threads on mutex deletion (thread blocking with no timeout) */ + g_result = 0; + if (atomMutexCreate (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test mutex\n")); + failures++; + } + + /* Take the mutex so that the test thread will block */ + else if (atomMutexGet (&mutex1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error taking mutex\n")); + failures++; + } + + else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test1_thread_func, 0, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 1\n")); + failures++; + } + else + { + + /* + * We have created a mutex and taken ownership ourselves. We + * want to see that the other thread is woken up if its mutex + * is deleted. This is indicated through g_result being set. + */ + + /* Wait for the other thread to start blocking on mutex1 */ + if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Failed timer delay\n")); + failures++; + } + else + { + /* The other thread will be blocking on mutex1 now, delete mutex1 */ + if (atomMutexDelete(&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Failed mutex1 delete\n")); + failures++; + } + else + { + /* Mutex1 deleted. The thread should now wake up and set g_result. */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + if (g_result == 0) + { + ATOMLOG (_STR("Notify fail\n")); + failures++; + } + else + { + /* Success */ + } + } + } + } + + /* Test wakeup of threads on semaphore deletion (thread blocking with timeout) */ + g_result = 0; + if (atomMutexCreate (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test mutex\n")); + failures++; + } + + /* Take the mutex so that the test thread will block */ + else if (atomMutexGet (&mutex1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error taking mutex\n")); + failures++; + } + + else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test2_thread_func, 0, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 2\n")); + failures++; + } + else + { + + /* + * We have created a mutex and taken ownership ourselves. We + * want to see that the other thread is woken up if its mutex + * is deleted. This is indicated through g_result being set. + */ + + /* Wait for the other thread to start blocking on mutex1 */ + if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Failed timer delay\n")); + failures++; + } + else + { + /* The other thread will be blocking on mutex1 now, delete mutex1 */ + if (atomMutexDelete(&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Failed mutex1 delete\n")); + failures++; + } + else + { + /* Mutex1 deleted. The thread should now wake up and set g_result. */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + if (g_result == 0) + { + ATOMLOG (_STR("Notify fail\n")); + failures++; + } + else + { + /* Success */ + } + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} + + +/** + * \b test1_thread_func + * + * Entry point for test thread 1. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test1_thread_func (uint32_t data) +{ + uint8_t status; + + /* + * Wait on mutex1 with no timeout. We are expecting to be woken up + * by the main thread while blocking. + */ + status = atomMutexGet(&mutex1, 0); + if (status != ATOM_ERR_DELETED) + { + ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status); + } + else + { + /* We were woken due to deletion as expected, set g_result to notify success */ + g_result = 1; + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} + + +/** + * \b test2_thread_func + * + * Entry point for test thread 2. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test2_thread_func (uint32_t data) +{ + uint8_t status; + + /* + * Wait on mutex1 with timeout. We are expecting to be woken up + * by the main thread while blocking. + */ + status = atomMutexGet(&mutex1, (5 * SYSTEM_TICKS_PER_SEC)); + if (status != ATOM_ERR_DELETED) + { + ATOMLOG (_STR("Test2 thread woke without deletion (%d)\n"), status); + } + else + { + /* We were woken due to deletion as expected, set g_result to notify success */ + g_result = 1; + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} diff --git a/tests/mutex2.c b/tests/mutex2.c new file mode 100644 index 0000000..ab97e2b --- /dev/null +++ b/tests/mutex2.c @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atommutex.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test OS objects */ +static ATOM_MUTEX mutex1, mutex2; +static ATOM_TCB tcb1; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Test result tracking */ +static volatile int g_result, g_owned; + + +/* Forward declarations */ +static void testCallback (POINTER cb_data); +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start mutex test. + * + * This test exercises the atomMutexGet() and atomMutexPut() APIs including + * forcing the various error indications which can be returned from the + * APIs to ensure that handling for these corner cases have been correctly + * implemented. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + uint8_t status; + ATOM_TIMER timer_cb; + int count; + + /* Default to zero failures */ + failures = 0; + + /* Test parameter checks */ + if (atomMutexGet (NULL, 0) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Get param failed\n")); + failures++; + } + if (atomMutexPut (NULL) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Put param failed\n")); + failures++; + } + + /* Test atomMutexGet() can not be called from interrupt context */ + g_result = 0; + if (atomMutexCreate (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test mutex1\n")); + failures++; + } + else + { + /* Fill out the timer callback request structure */ + timer_cb.cb_func = testCallback; + timer_cb.cb_data = NULL; + timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC; + + /* Request the timer callback to run in one second */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("Error registering timer\n")); + failures++; + } + + /* Wait two seconds for g_result to be set indicating success */ + else + { + atomTimerDelay (2 * SYSTEM_TICKS_PER_SEC); + if (g_result != 1) + { + ATOMLOG (_STR("Context check failed\n")); + failures++; + } + } + + /* Delete the test mutex */ + if (atomMutexDelete (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Mutex1 delete failed\n")); + failures++; + } + } + + /* Create mutex1 which will be owned by us */ + if (atomMutexCreate (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test mutex 1\n")); + failures++; + } + + /* Create mutex2 which will be owned by another thread */ + else if (atomMutexCreate (&mutex2) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test mutex 2\n")); + failures++; + } + + /* Create a test thread, the sole purpose of which is to own mutex2 */ + g_owned = 0; + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 0, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 1\n")); + failures++; + } + + /* Sleep until the test thread owns mutex2 */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + if (g_owned == 0) + { + ATOMLOG (_STR("Thread own fail\n")); + failures++; + } + + /* Test wait on mutex with timeout - should timeout while owned by another thread */ + if ((status = atomMutexGet (&mutex2, SYSTEM_TICKS_PER_SEC)) != ATOM_TIMEOUT) + { + ATOMLOG (_STR("Get %d\n"), status); + failures++; + } + else + { + /* Success */ + } + + /* Test wait on mutex with no blocking - should return that owned by another thread */ + if ((status = atomMutexGet (&mutex2, -1)) != ATOM_WOULDBLOCK) + { + ATOMLOG (_STR("Wouldblock err %d\n"), status); + failures++; + } + + /* Test wait on mutex with no blocking when mutex is available */ + if (atomMutexGet (&mutex1, -1) != ATOM_OK) + { + ATOMLOG (_STR("Error taking mutex1\n")); + failures++; + } + else + { + /* Relinquish ownership of mutex1 */ + if (atomMutexPut (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error posting mutex\n")); + failures++; + } + } + + /* Test for lock count overflows with too many gets */ + count = 255; + while (count--) + { + if (atomMutexGet (&mutex1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error getting mutex1\n")); + failures++; + break; + } + } + + /* The lock count should overflow this time */ + if (atomMutexGet (&mutex1, 0) != ATOM_ERR_OVF) + { + ATOMLOG (_STR("Error tracking overflow\n")); + failures++; + } + else + { + /* Success */ + } + + /* Delete the test mutexes */ + if (atomMutexDelete (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error deleting mutex1\n")); + failures++; + } + if (atomMutexDelete (&mutex2) != ATOM_OK) + { + ATOMLOG (_STR("Error deleting mutex2\n")); + failures++; + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} + + +/** + * \b testCallback + * + * Attempt an atomMutexGet() on mutex1 from interrupt context. + * Should receive an ATOM_ERR_CONTEXT error. Sets g_result if passes. + * + * @param[in] cb_data Not used + */ +static void testCallback (POINTER cb_data) +{ + /* Check the return value from atomMutexGet() */ + if (atomMutexGet(&mutex1, 0) == ATOM_ERR_CONTEXT) + { + /* Received the error we expected, set g_result to notify success */ + g_result = 1; + } + else + { + /* Did not get expected error, don't set g_result signifying fail */ + } + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + + /* + * Take mutex2 so that main thread can test mutex APIs on a mutex + * which it does not own. + */ + status = atomMutexGet(&mutex2, 0); + if (status != ATOM_OK) + { + ATOMLOG (_STR("Mutex get (%d)\n"), status); + } + else + { + /* We took ownership of mutex2, set g_owned to notify success */ + g_owned = 1; + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} diff --git a/tests/mutex3.c b/tests/mutex3.c new file mode 100644 index 0000000..6d53245 --- /dev/null +++ b/tests/mutex3.c @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atommutex.h" + + +/* Test OS objects */ +static ATOM_MUTEX mutex1; +static ATOM_TCB tcb1, tcb2, tcb3, tcb4; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE]; + +/* Data updated by threads */ +static volatile uint8_t wake_cnt; +static volatile uint8_t wake_order[4]; + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start mutex test. + * + * With multiple threads blocking on a single mutex, this test confirms that + * they are woken in order when the mutex is released. The correct order for + * waking is that the higher priority threads are woken first, followed by the + * lower priority threads. Where multiple threads of the same priority are + * waiting, the threads are woken in FIFO order (the order in which they started + * waiting on the mutex). + * + * To test this we create four threads which all wait on a single mutex. + * One pair of threads are running at high priority, with the other pair at a + * lower priority: + * + * Thread 1: low prio thread A + * Thread 2: low prio thread B + * Thread 3: high prio thread A + * Thread 4: high prio thread B + * + * The threads are forced to start blocking on the same mutex in the + * above order. + * + * We expect to see them woken up in the following order: + * 3, 4, 1, 2 + * + * This proves the multiple blocking thread ordering in terms of both + * the priority-queueing and same-priority-FIFO-queueing. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + + /* Default to zero failures */ + failures = 0; + + /* Create mutex */ + if (atomMutexCreate (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test mutex 1\n")); + failures++; + } + + /* Take ownership of the mutex so all threads will block to begin with */ + else if (atomMutexGet (&mutex1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Get error\n")); + failures++; + } + + /* Start the threads */ + else + { + /* Create Thread 1 (lower priority thread A) */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO+1, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the mutex */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Create Thread 2 (lower priority thread B) */ + if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO+1, test_thread_func, 2, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the mutex */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Create Thread 3 (higher priority thread A) */ + if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the mutex */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Create Thread 4 (higher priority thread B) */ + if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4, + &test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the mutex */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* All four threads will now be blocking on mutex1 */ + + /* + * Initialise wake count, used by threads to determine + * what order they were woken in. + */ + wake_cnt = 0; + + /* + * Release the mutex. This will wake up one of the threads blocking + * on it. That thread will take ownership of the mutex, and note the + * order at which it was woken, before releasing the mutex. This in + * turn will wake up the next thread blocking on the mutex until all + * four test threads have taken and released the mutex, noting their + * wake order. + */ + if (atomMutexPut (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Post fail\n")); + failures++; + } + + /* Sleep to give all four threads time to complete */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC / 4); + + /* All four threads now woken up, check they woke in correct order */ + if ((wake_order[0] != 3) && (wake_order[1] != 4) + && (wake_order[2] != 1) && (wake_order[3] != 2)) + { + ATOMLOG (_STR("Bad order %d,%d,%d,%d\n"), + wake_order[0], wake_order[1], wake_order[2], wake_order[3]); + failures++; + } + + /* Delete mutex, test finished */ + if (atomMutexDelete (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. The same thread entry point is used for all + * four test threads, with the thread number/ID (1-4) passed as the entry + * point parameter. + * + * @param[in] data Thread number (1,2,3,4) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t thread_id; + + /* Thread ID is passed through the function parameter */ + thread_id = (uint8_t)data; + + /* + * Wait for mutex1 to be posted. At creation of all test threads the mutex + * is owned by the parent thread, so all four threads will block here. + */ + if (atomMutexGet (&mutex1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Get fail\n")); + } + else + { + /* + * Store our thread ID in the array using the current + * wake_cnt order. The threads are holding ownership + * of a mutex here, which provides protection for this + * global data. + */ + wake_order[wake_cnt++] = thread_id; + + /* Release the mutex so that the next thread wakes up */ + if (atomMutexPut (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Put fail\n")); + } + + } + + /* Loop forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/mutex4.c b/tests/mutex4.c new file mode 100644 index 0000000..b86c522 --- /dev/null +++ b/tests/mutex4.c @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atommutex.h" +#include "atomsem.h" + + +/* Number of test loops for stress-test */ +#define NUM_TEST_LOOPS 10000 + + +/* Test OS objects */ +static ATOM_MUTEX mutex1; +static ATOM_SEM sem1; +static ATOM_TCB tcb1, tcb2, tcb3, tcb4; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* + * Global failure count (can be updated by test threads but is + * protected by an interrupt lockout). + */ +static volatile int g_failures; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start mutex test. + * + * Stress-tests mutex Get and Put operations. Four threads are created which are + * continually Getting and Putting the same mutex, with no time delays between + * each Get/Put. + * + * @retval Number of g_failures + */ +uint32_t test_start (void) +{ + CRITICAL_STORE; + int finish_cnt; + + /* Default to zero g_failures */ + g_failures = 0; + + /* Create mutex to stress */ + if (atomMutexCreate (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating mutex\n")); + g_failures++; + } + /* Create sem to receive thread-finished notification */ + else if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating sem\n")); + g_failures++; + } + else + { + /* Take ownership of the mutex to ensure all threads wait for now */ + if (atomMutexGet (&mutex1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error taking mutex\n")); + g_failures++; + } + + /* Create Thread 1 */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Create Thread 2 */ + if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 2, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Create Thread 3 */ + if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Create Thread 4 */ + if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4, + &test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Release ownership of the mutex to kick the threads off */ + if (atomMutexPut (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error putting mutex\n")); + g_failures++; + } + + /* + * All four threads will now be performing Gets/Puts on mutex1. + * When they have finished they will post sem1, so we wait + * until sem1 is posted four times. + */ + finish_cnt = 0; + while (1) + { + /* + * Attempt to Get sem1. When we have managed to get + * the semaphore four times, it must have been posted + * by all four threads. + */ + if (atomSemGet (&sem1, 0) == ATOM_OK) + { + /* Increment our count of finished threads */ + finish_cnt++; + + /* Check if all four threads have now posted sem1 */ + if (finish_cnt == 4) + { + break; + } + } + } + + /* Delete OS objects, test finished */ + if (atomMutexDelete (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + } + + /* Log final status */ + if (g_failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), g_failures); + } + + /* Quit */ + return g_failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. The same thread entry point is used for all + * four test threads. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint32_t loop_cnt; + uint8_t status; + CRITICAL_STORE; + + /* Run a Get/Put pair many times */ + loop_cnt = NUM_TEST_LOOPS; + while (loop_cnt--) + { + if ((status = atomMutexGet (&mutex1, 0)) != ATOM_OK) + { + /* Error getting mutex, notify the status code */ + ATOMLOG (_STR("G%d\n"), status); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + break; + } + else if ((status = atomMutexPut (&mutex1)) != ATOM_OK) + { + /* Error putting mutex, notify the status code */ + ATOMLOG (_STR("P%d\n"), status); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + break; + } + } + + /* Post sem1 to notify the main thread we're finished */ + if (atomSemPut (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Sem1 putfail\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Loop forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/mutex5.c b/tests/mutex5.c new file mode 100644 index 0000000..5c1586e --- /dev/null +++ b/tests/mutex5.c @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atommutex.h" + + +/* Test OS objects */ +static ATOM_MUTEX mutex1; +static ATOM_TCB tcb1; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Global shared data protected by mutex */ +static volatile int shared_data; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start mutex test. + * + * This tests basic usage of a mutex. Whichever thread holds the + * mutex can modify the global variable "shared_data". + * + * The main thread first takes the mutex, then creates a second + * thread. The second thread should block on the mutex until the + * main thread releases it. The test checks that the global + * "shared_data" is not modified by the second thread until the + * main thread releases the mutex. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + int i; + + /* Default to zero failures */ + failures = 0; + + /* Create mutex */ + if (atomMutexCreate (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating mutex\n")); + failures++; + } + else + { + /* Initialise the shared_data to zero */ + shared_data = 0; + + /* Take the mutex to ensure only this thread can modify shared_data */ + if (atomMutexGet (&mutex1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error taking mutex\n")); + failures++; + } + + /* Create second thread */ + else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* + * The second thread has now been created and should block on + * the mutex until we release it. We wait a while and check that + * shared_data has not been modified. + */ + for (i = 0; i < 4; i++) + { + /* + * Sleep for a while to give the second thread a chance to + * modify shared_data, thought it shouldn't until we + * release the mutex. + */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Check shared data. The second thread always sets it to one. */ + if (shared_data != 0) + { + ATOMLOG (_STR("Shared data modified\n")); + failures++; + break; + } + } + + /* Check successful so far */ + if (failures == 0) + { + /* + * Release the mutex, which will allow the second thread to + * wake and start modifying shared_data. + */ + if (atomMutexPut (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Failed release\n")); + failures++; + } + + /* + * Wait a little while then check that shared_data has + * been modified. + */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + if (shared_data != 1) + { + ATOMLOG (_STR("Expected modify\n")); + failures++; + } + + /* + * Release and take the mutex again a few times to ensure + * that the mutex continues to protect shared_data. + */ + for (i = 0; i < 4; i++) + { + /* + * Take the mutex again, to prevent second thread accessing + * shared_data. + */ + if (atomMutexGet (&mutex1, SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Retake %d\n"), i); + failures++; + break; + } + else + { + /* + * Set shared_data to 0 and wait to ensure that the + * second thread doesn't modify it while we have the + * mutex again. + */ + shared_data = 0; + + /* Wait a while to give second thread potential to run */ + atomTimerDelay(SYSTEM_TICKS_PER_SEC/4); + + /* + * Check that shared_data has not been modified while we + * own the mutex. + */ + if (shared_data != 0) + { + /* Thread is still modifying the data */ + ATOMLOG (_STR("Still modifying\n")); + failures++; + break; + } + + /* + * Release the mutex, which will allow the second thread to + * wake and start modifying shared_data again. + */ + if (atomMutexPut (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Failed release\n")); + failures++; + } + } + } + } + + /* Delete mutex, test finished */ + if (atomMutexDelete (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + + /* Repeatedly attempt to get the mutex and set shared_data to 1 */ + while (1) + { + /* Block on the mutex */ + if ((status = atomMutexGet (&mutex1, 0)) != ATOM_OK) + { + /* Error getting mutex, notify the status code */ + ATOMLOG (_STR("G%d\n"), status); + break; + } + + /* Got the mutex */ + else + { + /* Set shared_data to signify that we think we have the mutex */ + shared_data = 1; + + /* Release the mutex allowing the main thread to take it again */ + if ((status = atomMutexPut (&mutex1)) != ATOM_OK) + { + /* Error putting mutex, notify the status code */ + ATOMLOG (_STR("P%d\n"), status); + break; + } + } + + } + + /* Loop forever - we only reach here on error */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/mutex6.c b/tests/mutex6.c new file mode 100644 index 0000000..978810c --- /dev/null +++ b/tests/mutex6.c @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atommutex.h" + + +/* Number of times to lock the mutex during test */ +#define TEST_LOCK_CNT 250 + + +/* Test OS objects */ +static ATOM_MUTEX mutex1; +static ATOM_TCB tcb1; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Global shared data protected by mutex */ +static volatile int shared_data; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start mutex test. + * + * This tests the lock count of a mutex. The mutex object should + * count the number of times a thread has locked the mutex and + * not fully release it for use by another thread until it has + * been released the same number of times it was locked. +* + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + int i; + + /* Default to zero failures */ + failures = 0; + + /* Create mutex */ + if (atomMutexCreate (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating mutex\n")); + failures++; + } + else + { + /* Initialise the shared_data to zero */ + shared_data = 0; + + /* Take the mutex several times */ + for (i = 0; i < TEST_LOCK_CNT; i++) + { + if (atomMutexGet (&mutex1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error taking mutex\n")); + failures++; + break; + } + + } + + /* Create second thread */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* + * The second thread has now been created and should block on + * the mutex until we release it. We wait a while and check that + * shared_data has not been modified. + */ + for (i = 0; i < 4; i++) + { + /* + * Sleep for a while to give the second thread a chance to + * modify shared_data, thought it shouldn't until we + * release the mutex enough times. + */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Check shared data. The second thread always sets it to one. */ + if (shared_data != 0) + { + ATOMLOG (_STR("Shared data modified\n")); + failures++; + break; + } + } + + /* Check successful so far */ + if (failures == 0) + { + /* + * Release the mutex TEST_LOCK_CNT-1 times, after which we + * should still own the mutex (until we release one more time). + */ + for (i = 0; i < TEST_LOCK_CNT-1; i++) + { + if (atomMutexPut (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Failed release\n")); + failures++; + } + + } + + /* + * Wait a little while then check that shared_data has + * not been modified (we should still own the mutex). + */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + if (shared_data != 0) + { + ATOMLOG (_STR("Expected unmodified\n")); + failures++; + } + + /* + * Release the mutex one more time, after which we should no + * longer own the mutex (and wake up the second thread). + */ + if (atomMutexPut (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Failed release\n")); + failures++; + } + + /* + * Wait a little while then check that shared_data has + * been modified by the second thread. + */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + if (shared_data != 1) + { + ATOMLOG (_STR("Expected modified\n")); + failures++; + } + } + + /* + * Finally attempt to release the mutex one more time, while + * we no longer own the mutex. Either the second thread will + * have ownership of it, or no thread will have ownership. + * In both cases we expect to get an ownership error when we + * attempt to release it. + */ + if (atomMutexPut (&mutex1) != ATOM_ERR_OWNERSHIP) + { + ATOMLOG (_STR("Failed locked+1 release\n")); + failures++; + } + + /* Delete mutex, test finished */ + if (atomMutexDelete (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + + /* Repeatedly attempt to get the mutex and set shared_data to 1 */ + while (1) + { + /* Block on the mutex */ + if ((status = atomMutexGet (&mutex1, 0)) != ATOM_OK) + { + /* Error getting mutex, notify the status code */ + ATOMLOG (_STR("G%d\n"), status); + break; + } + + /* Got the mutex */ + else + { + /* Set shared_data to signify that we think we have the mutex */ + shared_data = 1; + + /* Release the mutex allowing the main thread to take it again */ + if ((status = atomMutexPut (&mutex1)) != ATOM_OK) + { + /* Error putting mutex, notify the status code */ + ATOMLOG (_STR("P%d\n"), status); + break; + } + } + + } + + /* Loop forever - we only reach here on error */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/mutex7.c b/tests/mutex7.c new file mode 100644 index 0000000..d23e61f --- /dev/null +++ b/tests/mutex7.c @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atommutex.h" + + +/* Test OS objects */ +static ATOM_MUTEX mutex1; +static ATOM_TCB tcb1; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Global shared data */ +static volatile int shared_data; + + +/* Forward declarations */ +static void testCallback (POINTER cb_data); +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start mutex test. + * + * This tests the ownership checks of the mutex library. Only threads + * which own a mutex can release it. It should not be possible to + * release a mutex if it is not owned by any thread, is owned by a + * different thread, or at interrupt context. We test here that all + * three cases are trapped. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + ATOM_TIMER timer_cb; + + /* Default to zero failures */ + failures = 0; + + /* Create mutex */ + if (atomMutexCreate (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating mutex\n")); + failures++; + } + else + { + /* Initialise the shared_data to zero */ + shared_data = 0; + + /* Attempt to release the mutex when not owned by any thread */ + if (atomMutexPut (&mutex1) != ATOM_ERR_OWNERSHIP) + { + ATOMLOG (_STR("Release error\n")); + failures++; + } + + /* Create second thread */ + else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* + * The second thread has now been created and should take ownership + * of the mutex. We wait a while and check that shared_data has been + * modified, which proves to us that the thread has taken the mutex. + */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + if (shared_data != 1) + { + ATOMLOG (_STR("Shared data unmodified\n")); + failures++; + } + + /* Check successful so far */ + if (failures == 0) + { + /* + * Attempt to release the mutex again now that it is owned + * by another thread. + */ + if (atomMutexPut (&mutex1) != ATOM_ERR_OWNERSHIP) + { + ATOMLOG (_STR("Release error 2\n")); + failures++; + } + + /* Finally check that the mutex cannot be released from an ISR */ + + /* Fill out the timer callback request structure */ + timer_cb.cb_func = testCallback; + timer_cb.cb_data = NULL; + timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC; + + /* Request the timer callback to run in one second */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("Error registering timer\n")); + failures++; + } + + /* + * Wait two seconds for shared_date to be set to 2 + * indicating success. This happens if the timer + * callback received the expected ownership error + * when attempting to release the mutex. + */ + else + { + atomTimerDelay (2 * SYSTEM_TICKS_PER_SEC); + if (shared_data != 2) + { + ATOMLOG (_STR("Context check failed\n")); + failures++; + } + } + } + + /* Delete mutex, test finished */ + if (atomMutexDelete (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + + /* Block on the mutex */ + if ((status = atomMutexGet (&mutex1, 0)) != ATOM_OK) + { + /* Error getting mutex, notify the status code */ + ATOMLOG (_STR("G%d\n"), status); + } + + /* Got the mutex */ + else + { + /* Set shared_data to signify that we think we have the mutex */ + shared_data = 1; + } + + /* Loop forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} + + +/** + * \b testCallback + * + * Attempt an atomMutexPut() on mutex1 from interrupt context. + * Should receive an ATOM_ERR_OWNERSHIP error. Sets shared_data + * to 2 if passes. + * + * @param[in] cb_data Not used + */ +static void testCallback (POINTER cb_data) +{ + /* Check the return value from atomMutexPut() */ + if (atomMutexPut(&mutex1) == ATOM_ERR_OWNERSHIP) + { + /* Received the error we expected, set shared_data to notify success */ + shared_data = 2; + } + else + { + /* Did not get expected error, don't set shared_data signifying fail */ + } + +} diff --git a/tests/mutex8.c b/tests/mutex8.c new file mode 100644 index 0000000..5ca5745 --- /dev/null +++ b/tests/mutex8.c @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atommutex.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test OS objects */ +static ATOM_MUTEX mutex1; +static ATOM_TCB tcb1, tcb2, tcb3; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Test results */ +static volatile int pass_flag[3]; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start mutex test. + * + * This test verifies the mutex deletion API, by deleting a mutex + * on which multiple threads are blocking, and checking that all three + * are woken up with an appropriate error code. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + int i; + + /* Default to zero failures */ + failures = 0; + + /* Initialise pass status for all three threads to FALSE */ + for (i = 0; i < 3; i++) + { + pass_flag[i] = FALSE; + } + + /* Test wakeup of three threads on mutex deletion */ + if (atomMutexCreate (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating mutex\n")); + failures++; + } + + else + { + /* Take the mutex to ensure that all three test threads will block */ + if (atomMutexGet (&mutex1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error taking mutex\n")); + failures++; + } + + /* Create test thread 1 */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 0, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 1\n")); + failures++; + } + + /* Create test thread 2 */ + else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 1, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 2\n")); + failures++; + } + + /* Create test thread 3 */ + else if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 2, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 3\n")); + failures++; + } + + /* Test threads now created */ + else + { + /* Wait a while for threads to start blocking on mutex1 */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Delete mutex1 now that all three threads should be blocking */ + if (atomMutexDelete (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Delete fail\n")); + failures++; + } + else + { + /* Wait a while for all three threads to wake up */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Check that all three threads have passed */ + if ((pass_flag[0] != TRUE) || (pass_flag[1] != TRUE) || (pass_flag[2] != TRUE)) + { + ATOMLOG (_STR("Thread fail\n")); + failures++; + } + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} + + +/** + * \b test_thread_func + * + * Entry point for test threads. + * + * @param[in] data Thread ID (0-2) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + int thread_id; + + /* Pull out the passed thread ID */ + thread_id = (int)data; + + /* + * Wait on mutex1 with timeout. We are expecting to be woken up + * by the main thread while blocking. + */ + status = atomMutexGet(&mutex1, (5 * SYSTEM_TICKS_PER_SEC)); + if (status != ATOM_ERR_DELETED) + { + ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status); + } + else + { + /* We were woken due to deletion as expected, set pass_flag to notify success */ + pass_flag[thread_id] = TRUE; + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/mutex9.c b/tests/mutex9.c new file mode 100644 index 0000000..7a8bacb --- /dev/null +++ b/tests/mutex9.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atommutex.h" + + +/* Test OS objects */ +static ATOM_MUTEX mutex1; +static ATOM_TCB tcb1; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Global shared data protected by mutex */ +static volatile int shared_data; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start mutex test. + * + * This tests timeouts on a mutex. We make a thread block with timeout + * on a mutex, and test that sufficient time has actually passed as + * was requested by the timeout parameter. + * + * The main thread creates a second thread which will immediately take + * ownership of the mutex. The test checks that the correct timeout + * occurs when the first thread blocks on the mutex which is already + * owned (by the second thread). + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + uint32_t start_time, end_time; + + /* Default to zero failures */ + failures = 0; + + /* Create mutex */ + if (atomMutexCreate (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating mutex\n")); + failures++; + } + else + { + /* Initialise the shared_data to zero */ + shared_data = 0; + + /* Create second thread */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* + * The second thread has now been created and should take ownership + * of the mutex. We wait a while and check that shared_data has been + * modified, which proves to us that the thread has taken the mutex. + */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + if (shared_data != 1) + { + ATOMLOG (_STR("Shared data unmodified\n")); + failures++; + } + + /* Check successful so far */ + if (failures == 0) + { + /* Take note of the start time */ + start_time = atomTimeGet(); + + /* Block on the mutex with two second timeout */ + if (atomMutexGet (&mutex1, 2 * SYSTEM_TICKS_PER_SEC) != ATOM_TIMEOUT) + { + ATOMLOG (_STR("Failed get\n")); + failures++; + } + + /* Take note of the end time */ + end_time = atomTimeGet(); + + /* Now check that two seconds have passed */ + if ((end_time < (start_time + (2 * SYSTEM_TICKS_PER_SEC))) + || (end_time > (start_time + (2 * SYSTEM_TICKS_PER_SEC) + 1))) + { + ATOMLOG (_STR("Bad time\n")); + failures++; + } + } + + /* Delete mutex, test finished */ + if (atomMutexDelete (&mutex1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + + /* Block on the mutex */ + if ((status = atomMutexGet (&mutex1, 0)) != ATOM_OK) + { + /* Error getting mutex, notify the status code */ + ATOMLOG (_STR("G%d\n"), status); + } + + /* Got the mutex */ + else + { + /* Set shared_data to signify that we think we have the mutex */ + shared_data = 1; + } + + /* Loop forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} diff --git a/tests/queue1.c b/tests/queue1.c new file mode 100644 index 0000000..c8ac594 --- /dev/null +++ b/tests/queue1.c @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomqueue.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test queue size */ +#define QUEUE_ENTRIES 16 + + +/* Test OS objects */ +static ATOM_QUEUE queue1; +static uint8_t queue1_storage[QUEUE_ENTRIES]; + + +/** + * \b test_start + * + * Start queue test. + * + * This test exercises the queue creation and deletion APIs. + * + * Testing of deletion while threads are actually blocking in + * queue APIs is tested in queue2.c and queue3.c. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + uint32_t i; + + /* Default to zero failures */ + failures = 0; + + /* Test creation and deletion of queues: good values */ + for (i = 0; i < 1000; i++) + { + if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) == ATOM_OK) + { + if (atomQueueDelete (&queue1) == ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Error deleting queue\n")); + failures++; + break; + } + } + else + { + /* Fail */ + ATOMLOG (_STR("Error creating queue\n")); + failures++; + break; + } + } + + /* Test creation and deletion of queues: creation checks */ + if (atomQueueCreate (NULL, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Bad queue ptr check\n")); + failures++; + } + if (atomQueueCreate (&queue1, NULL, sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Bad buff ptr check\n")); + failures++; + } + if (atomQueueCreate (&queue1, &queue1_storage[0], 0, QUEUE_ENTRIES) != ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Bad size check\n")); + failures++; + } + if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), 0) != ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Bad entries check\n")); + failures++; + } + + /* Test creation and deletion of queues: deletion checks */ + if (atomQueueDelete (NULL) != ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Bad queue deletion checks\n")); + failures++; + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} diff --git a/tests/queue10.c b/tests/queue10.c new file mode 100644 index 0000000..3a5defd --- /dev/null +++ b/tests/queue10.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomqueue.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test queue size */ +#define QUEUE_ENTRIES 8 + + +/* Test OS objects */ +static ATOM_QUEUE queue1; +static uint32_t queue1_storage[QUEUE_ENTRIES]; + + +/* Test message values (more values than can fit in an entire 8 message queue) */ +uint32_t test_values[] = +{ + 0x12345678, + 0xFF000000, + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xF000000F, + 0x0F0000F0, + 0x00F00F00, + 0x000FF000, + 0x87654321, + 0xABCD0000, + 0x0000CDEF +}; + + +/** + * \b test_start + * + * Start queue test. + * + * This tests basic operation of queues. + * + * Messages are posted to and received from the queue and checked + * against expected values. To ensure correct ordering, queue posts + * and receives are done in blocks of different amounts, such that + * there will already be different numbers of messages in the queue + * whenever messages are posted and received. + * + * We test using 4-byte messages. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures, tx_count, rx_count; + uint32_t msg; + + /* Default to zero failures */ + failures = 0; + + /* Create test queue */ + if (atomQueueCreate (&queue1, (uint8_t *)&queue1_storage[0], sizeof(queue1_storage[0]), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test queue\n")); + failures++; + } + + else + { + /* Reset tx/rx counts */ + tx_count = rx_count = 0; + + /* Post 2 messages to the queue */ + for (; tx_count < 2; tx_count++) + { + msg = test_values[tx_count]; + if (atomQueuePut (&queue1, 0, (uint8_t *)&msg) != ATOM_OK) + { + ATOMLOG (_STR("Failed post\n")); + failures++; + } + } + + /* Receive 1 message from the queue */ + for (; rx_count < 2; rx_count++) + { + if (atomQueueGet (&queue1, 0, (uint8_t *)&msg) != ATOM_OK) + { + ATOMLOG (_STR("Failed get\n")); + failures++; + } + else if (msg != test_values[rx_count]) + { + ATOMLOG (_STR("Val%d\n"), rx_count); + failures++; + } + } + + /* Post 3 messages to the queue */ + for (; tx_count < 5; tx_count++) + { + msg = test_values[tx_count]; + if (atomQueuePut (&queue1, 0, (uint8_t *)&msg) != ATOM_OK) + { + ATOMLOG (_STR("Failed post\n")); + failures++; + } + } + + /* Receive 2 messages from the queue */ + for (; rx_count < 3; rx_count++) + { + if (atomQueueGet (&queue1, 0, (uint8_t *)&msg) != ATOM_OK) + { + ATOMLOG (_STR("Failed get\n")); + failures++; + } + else if (msg != test_values[rx_count]) + { + ATOMLOG (_STR("Val%d\n"), rx_count); + failures++; + } + } + + /* Post 5 messages to the queue */ + for (; tx_count < 10; tx_count++) + { + msg = test_values[tx_count]; + if (atomQueuePut (&queue1, 0, (uint8_t *)&msg) != ATOM_OK) + { + ATOMLOG (_STR("Failed post\n")); + failures++; + } + } + + /* Receive 3 messages from the queue */ + for (; rx_count < 6; rx_count++) + { + if (atomQueueGet (&queue1, 0, (uint8_t *)&msg) != ATOM_OK) + { + ATOMLOG (_STR("Failed get\n")); + failures++; + } + else if (msg != test_values[rx_count]) + { + ATOMLOG (_STR("Val%d\n"), rx_count); + failures++; + } + } + + /* Post 2 messages to the queue */ + for (; tx_count < 12; tx_count++) + { + msg = test_values[tx_count]; + if (atomQueuePut (&queue1, 0, (uint8_t *)&msg) != ATOM_OK) + { + ATOMLOG (_STR("Failed post\n")); + failures++; + } + } + + /* Receive 6 messages from the queue */ + for (; rx_count < 12; rx_count++) + { + if (atomQueueGet (&queue1, 0, (uint8_t *)&msg) != ATOM_OK) + { + ATOMLOG (_STR("Failed get\n")); + failures++; + } + else if (msg != test_values[rx_count]) + { + ATOMLOG (_STR("Val%d\n"), rx_count); + failures++; + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} diff --git a/tests/queue2.c b/tests/queue2.c new file mode 100644 index 0000000..9a73f0b --- /dev/null +++ b/tests/queue2.c @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomqueue.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test queue size */ +#define QUEUE_ENTRIES 16 + + +/* Test OS objects */ +static ATOM_QUEUE queue1; +static ATOM_TCB tcb1, tcb2; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t queue1_storage[QUEUE_ENTRIES]; + + +/* Test result tracking */ +static volatile int g_result; + + +/* Forward declarations */ +static void test1_thread_func (uint32_t data); +static void test2_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start queue test. + * + * This test exercises queue deletion, waking threads blocking on a queue + * in atomQueueGet() if the queue is deleted. + * + * Deletion wakeups are tested twice: once for a thread which is blocking + * in atomQueueGet() with a timeout and once for a thread which is + * blocking in atomQueueGet() with no timeout. + * + * Deletion of threads blocking in atomQueuePut() are tested in queue3.c. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + + /* Default to zero failures */ + failures = 0; + + /* Test wakeup of threads on queue deletion (thread blocking with no timeout) */ + g_result = 0; + if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test queue\n")); + failures++; + } + + /* Create a test thread that will block because the queue is empty */ + else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test1_thread_func, 0, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 1\n")); + failures++; + } + else + { + + /* + * We have created an empty queue. We want to see that the other + * thread is woken up if its queue is deleted. This is indicated + * through g_result being set. + */ + + /* Wait for the other thread to start blocking on queue1 */ + if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Failed timer delay\n")); + failures++; + } + else + { + /* The other thread will be blocking on queue1 now, delete queue1 */ + if (atomQueueDelete(&queue1) != ATOM_OK) + { + ATOMLOG (_STR("Failed queue1 delete\n")); + failures++; + } + else + { + /* Queue1 deleted. The thread should now wake up and set g_result. */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + if (g_result == 0) + { + ATOMLOG (_STR("Notify fail\n")); + failures++; + } + else + { + /* Success */ + } + } + } + } + + /* Test wakeup of threads on semaphore deletion (thread blocking with timeout) */ + g_result = 0; + if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test queue\n")); + failures++; + } + + /* Create a test thread that will block because the queue is empty */ + else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test2_thread_func, 0, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 2\n")); + failures++; + } + else + { + + /* + * We have created an empty queue. We want to see that the other + * thread is woken up if its queue is deleted. This is indicated + * through g_result being set. + */ + + /* Wait for the other thread to start blocking on queue1 */ + if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Failed timer delay\n")); + failures++; + } + else + { + /* The other thread will be blocking on queue1 now, delete queue1 */ + if (atomQueueDelete(&queue1) != ATOM_OK) + { + ATOMLOG (_STR("Failed queue1 delete\n")); + failures++; + } + else + { + /* Queue1 deleted. The thread should now wake up and set g_result. */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + if (g_result == 0) + { + ATOMLOG (_STR("Notify fail\n")); + failures++; + } + else + { + /* Success */ + } + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} + + +/** + * \b test1_thread_func + * + * Entry point for test thread 1. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test1_thread_func (uint32_t data) +{ + uint8_t status, msg; + + /* + * Wait on queue1 with no timeout. We are expecting to be woken up + * by the main thread while blocking. + */ + status = atomQueueGet(&queue1, 0, &msg); + if (status != ATOM_ERR_DELETED) + { + ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status); + } + else + { + /* We were woken due to deletion as expected, set g_result to notify success */ + g_result = 1; + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} + + +/** + * \b test2_thread_func + * + * Entry point for test thread 2. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test2_thread_func (uint32_t data) +{ + uint8_t status, msg; + + /* + * Wait on queue1 with timeout. We are expecting to be woken up + * by the main thread while blocking. + */ + status = atomQueueGet(&queue1, (5 * SYSTEM_TICKS_PER_SEC), &msg); + if (status != ATOM_ERR_DELETED) + { + ATOMLOG (_STR("Test2 thread woke without deletion (%d)\n"), status); + } + else + { + /* We were woken due to deletion as expected, set g_result to notify success */ + g_result = 1; + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} diff --git a/tests/queue3.c b/tests/queue3.c new file mode 100644 index 0000000..0d2bbae --- /dev/null +++ b/tests/queue3.c @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomqueue.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test queue size */ +#define QUEUE_ENTRIES 16 + + +/* Test OS objects */ +static ATOM_QUEUE queue1; +static ATOM_TCB tcb1, tcb2; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t queue1_storage[QUEUE_ENTRIES]; + + +/* Test result tracking */ +static volatile int g_result; + + +/* Forward declarations */ +static void test1_thread_func (uint32_t data); +static void test2_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start queue test. + * + * This test exercises queue deletion, waking threads blocking on a queue + * in atomQueuePut() if the queue is deleted. + * + * Deletion wakeups are tested twice: once for a thread which is blocking + * in atomQueuePut() with a timeout and once for a thread which is + * blocking in atomQueuePut() with no timeout. + * + * Deletion of threads blocking in atomQueueGet() are tested in queue2.c. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures, i; + uint8_t msg; + + /* Default to zero failures */ + failures = 0; + + /* Set a test value for posting to the queue */ + msg = 0x66; + + /* Test wakeup of threads on queue deletion (thread blocking with no timeout) */ + g_result = 0; + if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test queue\n")); + failures++; + } + + /* Successful queue creation */ + else + { + /* Fill up all entries */ + for (i = 0; i < QUEUE_ENTRIES; i++) + { + if (atomQueuePut (&queue1, 0, &msg) != ATOM_OK) + { + ATOMLOG (_STR("Error filling queue\n")); + failures++; + } + } + + /* Create a test thread that will block because the queue is full */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test1_thread_func, 0, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 1\n")); + failures++; + } + else + { + + /* + * We have created and filled a queue. We want to see that the other + * thread is woken up if its queue is deleted. This is indicated + * through g_result being set. + */ + + /* Wait for the other thread to start blocking on queue1 */ + if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Failed timer delay\n")); + failures++; + } + else + { + /* The other thread will be blocking on queue1 now, delete queue1 */ + if (atomQueueDelete(&queue1) != ATOM_OK) + { + ATOMLOG (_STR("Failed queue1 delete\n")); + failures++; + } + else + { + /* Queue1 deleted. The thread should now wake up and set g_result. */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + if (g_result == 0) + { + ATOMLOG (_STR("Notify fail\n")); + failures++; + } + else + { + /* Success */ + } + } + } + } + } + + /* Test wakeup of threads on semaphore deletion (thread blocking with timeout) */ + g_result = 0; + if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test queue\n")); + failures++; + } + + /* Successful queue creation */ + else + { + /* Fill up all entries */ + for (i = 0; i < QUEUE_ENTRIES; i++) + { + if (atomQueuePut (&queue1, 0, &msg) != ATOM_OK) + { + ATOMLOG (_STR("Error filling queue\n")); + failures++; + } + } + + /* Create a test thread that will block because the queue is full */ + if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test2_thread_func, 0, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 2\n")); + failures++; + } + else + { + + /* + * We have created and filled a queue. We want to see that the other + * thread is woken up if its queue is deleted. This is indicated + * through g_result being set. + */ + + /* Wait for the other thread to start blocking on queue1 */ + if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Failed timer delay\n")); + failures++; + } + else + { + /* The other thread will be blocking on queue1 now, delete queue1 */ + if (atomQueueDelete(&queue1) != ATOM_OK) + { + ATOMLOG (_STR("Failed queue1 delete\n")); + failures++; + } + else + { + /* Queue1 deleted. The thread should now wake up and set g_result. */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + if (g_result == 0) + { + ATOMLOG (_STR("Notify fail\n")); + failures++; + } + else + { + /* Success */ + } + } + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} + + +/** + * \b test1_thread_func + * + * Entry point for test thread 1. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test1_thread_func (uint32_t data) +{ + uint8_t status, msg; + + /* Set a test value for posting to the queue */ + msg = 0x66; + + /* + * Post to queue1 with no timeout. The queue should be full so + * we are expecting to block. We should then be woken up by the + * main thread while blocking. + */ + status = atomQueuePut(&queue1, 0, &msg); + if (status != ATOM_ERR_DELETED) + { + ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status); + } + else + { + /* We were woken due to deletion as expected, set g_result to notify success */ + g_result = 1; + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} + + +/** + * \b test2_thread_func + * + * Entry point for test thread 2. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test2_thread_func (uint32_t data) +{ + uint8_t status, msg; + + /* Set a test value for posting to the queue */ + msg = 0x66; + + /* + * Post to queue1 with timeout. The queue should be full so + * we are expecting to block. We should then be woken up by the + * main thread while blocking. + */ + status = atomQueuePut(&queue1, (5 * SYSTEM_TICKS_PER_SEC), &msg); + if (status != ATOM_ERR_DELETED) + { + ATOMLOG (_STR("Test2 thread woke without deletion (%d)\n"), status); + } + else + { + /* We were woken due to deletion as expected, set g_result to notify success */ + g_result = 1; + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} diff --git a/tests/queue4.c b/tests/queue4.c new file mode 100644 index 0000000..0ee4233 --- /dev/null +++ b/tests/queue4.c @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomqueue.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Number of queue entries */ +#define QUEUE_ENTRIES 8 + + +/* Test OS objects */ +static ATOM_QUEUE queue1, queue2; +static uint8_t queue1_storage[QUEUE_ENTRIES]; +static uint8_t queue2_storage[QUEUE_ENTRIES]; + + +/* Test result tracking */ +static volatile int g_result; + + +/* Forward declarations */ +static void testCallbackGet (POINTER cb_data); +static void testCallbackPut (POINTER cb_data); + + +/** + * \b test_start + * + * Start queue test. + * + * This test exercises the atomQueueGet() and atomQueuePut() APIs + * particularly forcing the various error indications which can be + * returned from the APIs to ensure that handling for these corner + * cases has been correctly implemented. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + uint8_t msg; + ATOM_TIMER timer_cb; + int count; + + /* Default to zero failures */ + failures = 0; + + /* Create two test queues: queue1 is empty, queue2 is full */ + + /* Empty queue1 creation */ + if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Queue1 create\n")); + failures++; + } + + /* Full queue2 creation */ + if (atomQueueCreate (&queue2, &queue2_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Queue2 create\n")); + failures++; + } + else + { + /* Fill the queue */ + msg = 0x66; + for (count = 0; count < QUEUE_ENTRIES; count++) + { + /* Add one message at a time */ + if (atomQueuePut (&queue2, 0, &msg) != ATOM_OK) + { + ATOMLOG (_STR("Queue2 put\n")); + failures++; + } + } + } + + /* Test parameter checks */ + if (atomQueueGet (NULL, 0, &msg) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Get queue param failed\n")); + failures++; + } + if (atomQueueGet (&queue1, 0, NULL) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Get msg param failed\n")); + failures++; + } + if (atomQueuePut (NULL, 0, &msg) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Put queue param failed\n")); + failures++; + } + if (atomQueuePut (&queue1, 0, NULL) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Put msg param failed\n")); + failures++; + } + + /* Test atomQueueGet() can not be called from interrupt context */ + g_result = 0; + + /* Fill out the timer callback request structure */ + timer_cb.cb_func = testCallbackGet; + timer_cb.cb_data = NULL; + timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC; + + /* Request the timer callback to run in one second */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("Error registering timer\n")); + failures++; + } + + /* Wait two seconds for g_result to be set indicating success */ + else + { + atomTimerDelay (2 * SYSTEM_TICKS_PER_SEC); + if (g_result != 1) + { + ATOMLOG (_STR("Get context check failed\n")); + failures++; + } + } + + /* Test atomQueuePut() can not be called from interrupt context */ + g_result = 0; + + /* Fill out the timer callback request structure */ + timer_cb.cb_func = testCallbackPut; + timer_cb.cb_data = NULL; + timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC; + + /* Request the timer callback to run in one second */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("Error registering timer\n")); + failures++; + } + + /* Wait two seconds for g_result to be set indicating success */ + else + { + atomTimerDelay (2 * SYSTEM_TICKS_PER_SEC); + if (g_result != 1) + { + ATOMLOG (_STR("Put context check failed\n")); + failures++; + } + } + + /* Test ATOM_TIMEOUT is returned for Get/Put calls with timeout */ + + /* Attempt atomQueueGet() on empty queue to force timeout */ + if (atomQueueGet (&queue1, SYSTEM_TICKS_PER_SEC, &msg) != ATOM_TIMEOUT) + { + ATOMLOG (_STR("Timeout q1 failed\n")); + failures++; + } + + /* Attempt atomQueuePut() on full queue to force timeout */ + msg = 0x66; + if (atomQueuePut (&queue2, SYSTEM_TICKS_PER_SEC, &msg) != ATOM_TIMEOUT) + { + ATOMLOG (_STR("Timeout q2 failed\n")); + failures++; + } + + /* Test ATOM_WOULDBLOCK is returned for Get/Put calls with -1 timeout */ + + /* Attempt atomQueueGet() on empty queue to force block */ + if (atomQueueGet (&queue1, -1, &msg) != ATOM_WOULDBLOCK) + { + ATOMLOG (_STR("Timeout q1 failed\n")); + failures++; + } + + /* Attempt atomQueuePut() on full queue to force block */ + msg = 0x66; + if (atomQueuePut (&queue2, -1, &msg) != ATOM_WOULDBLOCK) + { + ATOMLOG (_STR("Timeout q2 failed\n")); + failures++; + } + + /* Delete the test queues */ + if (atomQueueDelete (&queue1) != ATOM_OK) + { + ATOMLOG (_STR("Error deleting q1\n")); + failures++; + } + if (atomQueueDelete (&queue2) != ATOM_OK) + { + ATOMLOG (_STR("Error deleting q2\n")); + failures++; + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} + + +/** + * \b testCallbackGet + * + * Attempt an atomQueueGet() on (empty) queue1 from interrupt context. + * Should receive an ATOM_ERR_CONTEXT error. Sets g_result if passes. + * + * @param[in] cb_data Not used + */ +static void testCallbackGet (POINTER cb_data) +{ + uint8_t msg; + + /* Check the return value from atomQueueGet() */ + if (atomQueueGet(&queue1, 0, &msg) == ATOM_ERR_CONTEXT) + { + /* Received the error we expected, set g_result to notify success */ + g_result = 1; + } + else + { + /* Did not get expected error, don't set g_result signifying fail */ + } + +} + + +/** + * \b testCallbackPut + * + * Attempt an atomQueuePut() on (full) queue2 from interrupt context. + * Should receive an ATOM_ERR_CONTEXT error. Sets g_result if passes. + * + * @param[in] cb_data Not used + */ +static void testCallbackPut (POINTER cb_data) +{ + uint8_t msg; + + /* Check the return value from atomQueuePut() */ + msg = 0x66; + if (atomQueuePut(&queue2, 0, &msg) == ATOM_ERR_CONTEXT) + { + /* Received the error we expected, set g_result to notify success */ + g_result = 1; + } + else + { + /* Did not get expected error, don't set g_result signifying fail */ + } + +} diff --git a/tests/queue5.c b/tests/queue5.c new file mode 100644 index 0000000..1f7eea4 --- /dev/null +++ b/tests/queue5.c @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atomqueue.h" + + +/* Number of queue entries */ +#define QUEUE_ENTRIES 8 + + +/* Test OS objects */ +static ATOM_QUEUE queue1; +static uint8_t queue1_storage[QUEUE_ENTRIES]; +static ATOM_TCB tcb1, tcb2, tcb3, tcb4; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE]; + +/* Data updated by threads */ +static volatile uint8_t wake_cnt; +static volatile uint8_t wake_order[4]; + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start queue test. + * + * With multiple threads blocking on a single queue, this test confirms that + * they are woken in order when the queue is posted. The correct order for + * waking is that the higher priority threads are woken first, followed by the + * lower priority threads. Where multiple threads of the same priority are + * waiting, the threads are woken in FIFO order (the order in which they started + * waiting on the queue). + * + * To test this we create four threads which all wait on a single queue. + * One pair of threads are running at high priority, with the other pair at a + * lower priority: + * + * Thread 1: low prio thread A + * Thread 2: low prio thread B + * Thread 3: high prio thread A + * Thread 4: high prio thread B + * + * The threads are forced to start blocking on the same queue in the + * above order. + * + * We expect to see them woken up in the following order: + * 3, 4, 1, 2 + * + * This proves the multiple blocking thread ordering in terms of both + * the priority-queueing and same-priority-FIFO-queueing. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures, count; + uint8_t msg; + + /* Default to zero failures */ + failures = 0; + + /* Create empty queue */ + if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(uint8_t), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test q1\n")); + failures++; + } + + /* Start the threads */ + else + { + /* + * The test threads all start by calling atomQueueGet() to receive + * a message from the queue. Because the queue is empty, all test + * threads should immediately block on the queue (until a message + * is posted to it). + */ + + /* Create Thread 1 (lower priority thread A) */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO+1, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the queue */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Create Thread 2 (lower priority thread B) */ + if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO+1, test_thread_func, 2, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the queue */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Create Thread 3 (higher priority thread A) */ + if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the queue */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Create Thread 4 (higher priority thread B) */ + if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4, + &test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the queue */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* All four threads will now be blocking on queue1 */ + + /* + * Initialise wake count, used by threads to determine + * what order they were woken in. + */ + wake_cnt = 0; + + /* Loop waking all four threads */ + for (count = 0; count < 4; count++) + { + /* + * Post a message to the queue. This will wake up one of the threads + * blocking on it (because it is currently empty). That thread will + * wake up, note the order at which it was woken, then go to sleep + * forever leaving the queue empty again. This is done four times so + * that all four threads are woken, noting their wake order. + */ + msg = 0x66; + if (atomQueuePut (&queue1, 0, &msg) != ATOM_OK) + { + ATOMLOG (_STR("Post fail\n")); + failures++; + } + + /* + * Sleep to give the thread time to wake up and modify the shared + * global data wake_cnt and wake_order[]. We deliberately do not + * use a mutex for protecting access to this shared data, as we + * are testing the queue module in isolation here. + */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC / 4); + + } + + /* All four threads now woken up, check they woke in correct order */ + if ((wake_order[0] != 3) && (wake_order[1] != 4) + && (wake_order[2] != 1) && (wake_order[3] != 2)) + { + ATOMLOG (_STR("Bad order %d,%d,%d,%d\n"), + wake_order[0], wake_order[1], wake_order[2], wake_order[3]); + failures++; + } + + /* Delete queue, test finished */ + if (atomQueueDelete (&queue1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. The same thread entry point is used for all + * four test threads, with the thread number/ID (1-4) passed as the entry + * point parameter. + * + * @param[in] data Thread number (1,2,3,4) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t thread_id; + uint8_t msg; + + /* Thread ID is passed through the function parameter */ + thread_id = (uint8_t)data; + + /* + * Wait for a message to appear on queue1. At creation of all test + * threads the queue is empty, so all four threads will block here. + */ + if (atomQueueGet (&queue1, 0, &msg) != ATOM_OK) + { + ATOMLOG (_STR("Get fail\n")); + } + else + { + /* + * Store our thread ID in the array using the current + * wake_cnt order. The threads are woken with large + * pauses between, which provides protection for this + * global data. This allows us to test queues without + * assuming a working implementation of a mutex (or + * similar protection mechanism). + */ + wake_order[wake_cnt++] = thread_id; + + } + + /* Loop forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/queue6.c b/tests/queue6.c new file mode 100644 index 0000000..64343e2 --- /dev/null +++ b/tests/queue6.c @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomqueue.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test queue size */ +#define QUEUE_ENTRIES 8 + + +/* Test OS objects */ +static ATOM_QUEUE queue1; +static uint32_t queue1_storage[QUEUE_ENTRIES]; +static ATOM_TCB tcb1; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Test message values (more values than can fit in an entire 8 message queue) */ +uint32_t test_values[] = +{ + 0x12345678, + 0xFF000000, + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xF000000F, + 0x0F0000F0, + 0x00F00F00, + 0x000FF000, + 0x87654321, + 0xABCD0000, + 0x0000CDEF +}; + +/* Test result tracking */ +static volatile int g_result; + + +/* Forward declarations */ +static void test1_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start queue test. + * + * This tests basic operation of queues. + * + * The main test thread creates a second thread and posts + * a series of messages to the second thread. The message + * values are checked against the expected values. + * + * We test using 4-byte messages. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures, count; + int num_entries; + uint32_t msg; + + /* Default to zero failures */ + failures = 0; + g_result = 0; + + /* Create test queue */ + if (atomQueueCreate (&queue1, (uint8_t *)&queue1_storage[0], sizeof(queue1_storage[0]), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test queue\n")); + failures++; + } + + /* Create a test thread that will block because the queue is empty */ + else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO + 1, test1_thread_func, 0, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 1\n")); + failures++; + } + else + { + + /* + * We have created an empty queue and a thread which should now + * be blocking on the queue. The test thread is lower priority + * than us. + */ + + /* Wait for the other thread to start blocking on queue1 */ + if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Failed timer delay\n")); + failures++; + } + else + { + /* + * Post all entries in the test array to the queue. + * Because the second thread is lower priority than + * us, we will post 8 messages until the queue is + * full without waking up the second thread at all. + * At that point, we will block and the second + * thread will remove one message from the queue. + * With a spare entry in the queue, this thread + * will wake up again and post another message. + * This will continue until this thread has posted + * all messages, at which point the second thread + * will drain all remaining messages from the + * queue. + * + * Through this scheme we are able to test posting + * to the queue at all possible fill levels. + */ + num_entries = sizeof(test_values) / sizeof(test_values[0]); + for (count = 0; count < num_entries; count++) + { + /* Increment through and post all test values to the queue */ + msg = test_values[count]; + if (atomQueuePut (&queue1, 0, (uint8_t *)&msg) != ATOM_OK) + { + ATOMLOG (_STR("Failed post\n")); + failures++; + } + } + + /* Sleep a while for the second thread to finish */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + + /* Check that the second thread has found all test values */ + if (g_result != 1) + { + ATOMLOG (_STR("Bad test vals\n")); + failures++; + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} + + +/** + * \b test1_thread_func + * + * Entry point for test thread 1. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test1_thread_func (uint32_t data) +{ + uint32_t msg; + int num_entries, count, failures; + + /* Default to no errors */ + failures = 0; + + /* + * Loop receiving messages until we have received the number of + * values in the test array. + */ + num_entries = sizeof(test_values) / sizeof(test_values[0]); + for (count = 0; count < num_entries; count++) + { + /* Receive a value from the queue */ + if (atomQueueGet (&queue1, 0, (uint8_t *)&msg) != ATOM_OK) + { + ATOMLOG (_STR("Failed get\n")); + failures++; + } + + /* Check that we received the expected value */ + else if (msg != test_values[count]) + { + ATOMLOG (_STR("Val%d\n"), count); + failures++; + } + } + + /* + * Set g_result to indicate success if we had no failures. + * Thread-protection is not required on g_result because it + * is only ever set by this thread. + */ + if (failures == 0) + { + /* No failures */ + g_result = 1; + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} diff --git a/tests/queue7.c b/tests/queue7.c new file mode 100644 index 0000000..b06127d --- /dev/null +++ b/tests/queue7.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomqueue.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test queue size */ +#define QUEUE_ENTRIES 8 + + +/* Test OS objects */ +static ATOM_QUEUE queue1; +static uint8_t queue1_storage[QUEUE_ENTRIES]; +static ATOM_TCB tcb1, tcb2, tcb3; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Test results */ +static volatile int pass_flag[3]; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start queue test. + * + * This test verifies the queue deletion API, by deleting a queue + * on which multiple threads are blocking, and checking that all three + * are woken up with an appropriate error code. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + int i; + + /* Default to zero failures */ + failures = 0; + + /* Initialise pass status for all three threads to FALSE */ + for (i = 0; i < 3; i++) + { + pass_flag[i] = FALSE; + } + + /* Test wakeup of three threads on queue deletion */ + if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(queue1_storage[0]), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Error creating Q\n")); + failures++; + } + + else + { + /* The queue is empty so all three test threads will block */ + + /* Create test thread 1 */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 0, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 1\n")); + failures++; + } + + /* Create test thread 2 */ + else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 1, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 2\n")); + failures++; + } + + /* Create test thread 3 */ + else if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 2, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 3\n")); + failures++; + } + + /* Test threads now created */ + else + { + /* Wait a while for threads to start blocking on queue1 */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Delete queue1 now that all three threads should be blocking */ + if (atomQueueDelete (&queue1) != ATOM_OK) + { + ATOMLOG (_STR("Delete fail\n")); + failures++; + } + else + { + /* Wait a while for all three threads to wake up */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Check that all three threads have passed */ + if ((pass_flag[0] != TRUE) || (pass_flag[1] != TRUE) || (pass_flag[2] != TRUE)) + { + ATOMLOG (_STR("Thread fail\n")); + failures++; + } + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} + + +/** + * \b test_thread_func + * + * Entry point for test threads. + * + * @param[in] data Thread ID (0-2) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + uint8_t msg; + int thread_id; + + /* Pull out the passed thread ID */ + thread_id = (int)data; + + /* + * Wait on queue1 with timeout. We are expecting to be woken up + * by the main thread while blocking. + */ + status = atomQueueGet(&queue1, (5 * SYSTEM_TICKS_PER_SEC), &msg); + if (status != ATOM_ERR_DELETED) + { + ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status); + } + else + { + /* We were woken due to deletion as expected, set pass_flag to notify success */ + pass_flag[thread_id] = TRUE; + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/queue8.c b/tests/queue8.c new file mode 100644 index 0000000..b50883c --- /dev/null +++ b/tests/queue8.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atomqueue.h" + + +/* Test queue size */ +#define QUEUE_ENTRIES 8 + + +/* Test OS objects */ +static ATOM_QUEUE queue1; +static uint8_t queue1_storage[QUEUE_ENTRIES]; + + +/** + * \b test_start + * + * Start queue test. + * + * This tests timeouts on a queue. We make a thread block with timeout + * on a queue, and test that sufficient time has actually passed as + * was requested by the timeout parameter. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + uint32_t start_time, end_time; + uint8_t msg; + + /* Default to zero failures */ + failures = 0; + + /* Create queue */ + if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(queue1_storage[0]), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Error creating Q\n")); + failures++; + } + + else + { + /* The queue is empty so atomQueueGet() calls will block */ + + /* Take note of the start time */ + start_time = atomTimeGet(); + + /* Block on the queue with two second timeout */ + if (atomQueueGet (&queue1, 2 * SYSTEM_TICKS_PER_SEC, &msg) != ATOM_TIMEOUT) + { + ATOMLOG (_STR("Failed get\n")); + failures++; + } + + /* Take note of the end time */ + end_time = atomTimeGet(); + + /* Now check that two seconds have passed */ + if ((end_time < (start_time + (2 * SYSTEM_TICKS_PER_SEC))) + || (end_time > (start_time + (2 * SYSTEM_TICKS_PER_SEC) + 1))) + { + ATOMLOG (_STR("Bad time\n")); + failures++; + } + + /* Delete queue, test finished */ + if (atomQueueDelete (&queue1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} diff --git a/tests/queue9.c b/tests/queue9.c new file mode 100644 index 0000000..bfddc85 --- /dev/null +++ b/tests/queue9.c @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atomqueue.h" +#include "atomsem.h" + + +/* Number of test loops for stress-test */ +#define NUM_TEST_LOOPS 10000 + + +/* Test queue size */ +#define QUEUE_ENTRIES 8 + + +/* Test OS objects */ +static ATOM_QUEUE queue1; +static uint8_t queue1_storage[QUEUE_ENTRIES]; +static ATOM_SEM sem1; +static ATOM_TCB tcb1, tcb2, tcb3, tcb4; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* + * Global failure count (can be updated by test threads but is + * protected by an interrupt lockout). + */ +static volatile int g_failures; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start queue test. + * + * Stress-tests queue Get and Put operations. Four threads are created which are + * continually Putting and Getting the same queue, with no time delays between + * each Get/Put. Because all threads are at the same priority this ensures that + * on timeslices when threads are rescheduled there are several context-switch + * points, while the threads may be part-way through queue API calls. + * + * @retval Number of g_failures + */ +uint32_t test_start (void) +{ + CRITICAL_STORE; + int finish_cnt; + + /* Default to zero g_failures */ + g_failures = 0; + + /* Create queue to stress */ + if (atomQueueCreate (&queue1, &queue1_storage[0], sizeof(queue1_storage[0]), QUEUE_ENTRIES) != ATOM_OK) + { + ATOMLOG (_STR("Error creating Q\n")); + g_failures++; + } + + /* Create sem to receive thread-finished notification */ + else if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating sem\n")); + g_failures++; + } + else + { + /* Create Thread 1 */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Create Thread 2 */ + if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 2, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Create Thread 3 */ + if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Create Thread 4 */ + if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4, + &test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* + * All four threads will now be performing Gets/Puts on queue1. + * When they have finished they will post sem1, so we wait + * until sem1 is posted four times. + */ + finish_cnt = 0; + while (1) + { + /* + * Attempt to Get sem1. When we have managed to get + * the semaphore four times, it must have been posted + * by all four threads. + */ + if (atomSemGet (&sem1, 0) == ATOM_OK) + { + /* Increment our count of finished threads */ + finish_cnt++; + + /* Check if all four threads have now posted sem1 */ + if (finish_cnt == 4) + { + break; + } + } + } + + /* Delete OS objects, test finished */ + if (atomQueueDelete (&queue1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + } + + /* Log final status */ + if (g_failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), g_failures); + } + + /* Quit */ + return g_failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. The same thread entry point is used for all + * four test threads. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint32_t loop_cnt; + uint8_t status; + uint8_t msg; + CRITICAL_STORE; + + /* Run a Put/Get pair many times */ + loop_cnt = NUM_TEST_LOOPS; + while (loop_cnt--) + { + /* Put a message in the queue */ + msg = 0x66; + if ((status = atomQueuePut (&queue1, 0, &msg)) != ATOM_OK) + { + /* Error putting mutex, notify the status code */ + ATOMLOG (_STR("P%d\n"), status); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + break; + } + + /* Retrieve a messages from the queue */ + if ((status = atomQueueGet (&queue1, 0, &msg)) != ATOM_OK) + { + /* Error getting queue msg, notify the status code */ + ATOMLOG (_STR("G%d\n"), status); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + break; + } + } + + /* Post sem1 to notify the main thread we're finished */ + if (atomSemPut (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Sem1 putfail\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Loop forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/sem1.c b/tests/sem1.c new file mode 100644 index 0000000..554d34c --- /dev/null +++ b/tests/sem1.c @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomsem.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test OS objects */ +static ATOM_SEM sem1, sem2; +static ATOM_TCB tcb1, tcb2; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Forward declarations */ +static void test1_thread_func (uint32_t data); +static void test2_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start semaphore test. + * + * This test exercises the semaphore creation and deletion APIs, including + * waking threads blocking on a semaphore if the semaphore is deleted. + * Deletion wakeups are tested twice: once for a thread which is blocking + * with a timeout and once for a thread which is blocking with no timeout. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + uint32_t i; + uint8_t status; + + /* Default to zero failures */ + failures = 0; + + /* Test creation and deletion of semaphores: good values */ + for (i = 0; i < 1000; i++) + { + if (atomSemCreate (&sem1, 0) == ATOM_OK) + { + if (atomSemDelete (&sem1) == ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Error deleting semaphore\n")); + failures++; + break; + } + } + else + { + /* Fail */ + ATOMLOG (_STR("Error creating semaphore\n")); + failures++; + break; + } + } + + /* Test creation and deletion of semaphores: creation checks */ + if (atomSemCreate (NULL, 0) != ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Bad semaphore creation checks\n")); + failures++; + } + + /* Test creation and deletion of semaphores: deletion checks */ + if (atomSemDelete (NULL) != ATOM_OK) + { + /* Success */ + } + else + { + /* Fail */ + ATOMLOG (_STR("Bad semaphore deletion checks\n")); + failures++; + } + + /* Test wakeup of threads on semaphore deletion (thread blocking with no timeout) */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + + else if (atomSemCreate (&sem2, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 2\n")); + failures++; + } + + else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test1_thread_func, 0, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 1\n")); + failures++; + } + else + { + + /* + * We have created two semaphores. sem1 is for the other thread + * to wait on, which we will delete from this thread. We want + * to see that the other thread is woken up if its semaphore + * is deleted. This is indicated through sem2 being posted + * back to us. + */ + + /* Wait for the other thread to start blocking on sem1 */ + if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Failed timer delay\n")); + failures++; + } + else + { + /* The other thread will be blocking on sem1 now, delete sem1 */ + if (atomSemDelete(&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Failed sem1 delete\n")); + failures++; + } + else + { + /* Sem1 deleted. The thread should now wake up and post sem2. */ + if ((status = atomSemGet (&sem2, (5*SYSTEM_TICKS_PER_SEC))) != ATOM_OK) + { + ATOMLOG (_STR("Notify fail (%d)\n"), status); + failures++; + } + else + { + /* Success */ + + /* Clean up the last remaining semaphore */ + if (atomSemDelete (&sem2) != ATOM_OK) + { + ATOMLOG (_STR("Failed sem2 delete\n")); + failures++; + } + } + } + } + } + + /* Test wakeup of threads on semaphore deletion (thread blocking with timeout) */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + else if (atomSemCreate (&sem2, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 2\n")); + failures++; + } + else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test2_thread_func, 0, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 2\n")); + failures++; + } + else + { + /* + * We have created two semaphores. sem1 is for the other thread + * to wait on, which we will delete from this thread. We want + * to see that the other thread is woken up if its semaphore + * is deleted. This is indicated through sem2 being posted + * back to us. + */ + + /* Wait for the other thread to start blocking on sem1 */ + if (atomTimerDelay(SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Failed timer delay\n")); + failures++; + } + else + { + /* The other thread will be blocking on sem1 now, delete sem1 */ + if (atomSemDelete(&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Failed sem1 delete\n")); + failures++; + } + else + { + /* Sem1 deleted. The thread should now wake up and post sem2. */ + if ((status = atomSemGet (&sem2, (5*SYSTEM_TICKS_PER_SEC))) != ATOM_OK) + { + ATOMLOG (_STR("Notify fail (%d)\n"), status); + failures++; + } + else + { + /* Success */ + + /* Clean up the last remaining semaphore */ + if (atomSemDelete (&sem2) != ATOM_OK) + { + ATOMLOG (_STR("Failed sem2 delete\n")); + failures++; + } + } + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} + + +/** + * \b test1_thread_func + * + * Entry point for test thread 1. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test1_thread_func (uint32_t data) +{ + uint8_t status; + + /* + * Wait on sem1 with no timeout. We are expecting to be woken up + * by the main thread while blocking. + */ + status = atomSemGet(&sem1, 0); + if (status != ATOM_ERR_DELETED) + { + ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status); + } + else + { + /* We were woken due to deletion as expected, post sem2 to notify success */ + if ((status = atomSemPut(&sem2)) != ATOM_OK) + { + ATOMLOG (_STR("Error posting sem2 on wakeup (%d)\n"), status); + } + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} + + +/** + * \b test2_thread_func + * + * Entry point for test thread 2. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test2_thread_func (uint32_t data) +{ + uint8_t status; + + /* + * Wait on sem1 with timeout. We are expecting to be woken up + * by the main thread while blocking. + */ + status = atomSemGet(&sem1, (5 * SYSTEM_TICKS_PER_SEC)); + if (status != ATOM_ERR_DELETED) + { + ATOMLOG (_STR("Test2 thread woke without deletion (%d)\n"), status); + } + else + { + /* We were woken due to deletion as expected, post sem2 to notify success */ + if ((status = atomSemPut(&sem2)) != ATOM_OK) + { + ATOMLOG (_STR("Error posting sem2 on wakeup\n")); + } + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} diff --git a/tests/sem2.c b/tests/sem2.c new file mode 100644 index 0000000..7b89e47 --- /dev/null +++ b/tests/sem2.c @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomsem.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test OS objects */ +static ATOM_SEM sem1, sem2; + + +/* Forward declarations */ +static void testCallback (POINTER cb_data); + + +/** + * \b test_start + * + * Start semaphore test. + * + * This test exercises the atomSemGet() and atomSemPut() APIs including + * forcing the various error indications which can be returned from the + * APIs to ensure that handling for these corner cases have been correctly + * implemented. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + uint8_t status; + ATOM_TIMER timer_cb; + + /* Default to zero failures */ + failures = 0; + + /* Test semaphore wait timeouts */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + else + { + /* Wait on semaphore with timeout */ + if ((status = atomSemGet (&sem1, SYSTEM_TICKS_PER_SEC)) != ATOM_TIMEOUT) + { + ATOMLOG (_STR("Timo %d\n"), status); + failures++; + } + else + { + /* Test semaphore still operates correctly after timeout */ + if (atomSemPut (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Put failed\n")); + failures++; + } + else + { + /* Count should now be 1 */ + if (atomSemGet (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Get failed\n")); + failures++; + } + else + { + /* Delete it, test finished */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + } + } + } + + /* Test semaphore blocks if count is zero */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + else + { + /* Wait on semaphore with timeout */ + if ((status = atomSemGet (&sem1, SYSTEM_TICKS_PER_SEC)) != ATOM_TIMEOUT) + { + ATOMLOG (_STR("Timo %d\n"), status); + failures++; + } + else + { + /* Delete it, test finished */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + } + + /* Test semaphore does not block if count is one */ + if (atomSemCreate (&sem1, 1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + else + { + /* Wait on semaphore with timeout */ + if ((status = atomSemGet (&sem1, SYSTEM_TICKS_PER_SEC)) != ATOM_OK) + { + ATOMLOG (_STR("Get %d\n"), status); + failures++; + } + else + { + /* Delete it, test finished */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + } + + /* Test parameter checks */ + if (atomSemGet (NULL, 0) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Get param failed\n")); + failures++; + } + if (atomSemPut (NULL) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Put param failed\n")); + failures++; + } + + /* Test atomSemGet() cannot be called from interrupt context */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test sem1\n")); + failures++; + } + else if (atomSemCreate (&sem2, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test sem2\n")); + failures++; + } + else + { + /* Fill out the timer callback request structure */ + timer_cb.cb_func = testCallback; + timer_cb.cb_data = NULL; + timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC; + + /* Request the timer callback to run in one second */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("Error registering timer\n")); + failures++; + } + + /* Wait up to two seconds for sem2 to be posted indicating success */ + else if (atomSemGet (&sem2, 2 * SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Context check failed\n")); + failures++; + } + + /* Delete the two test semaphores */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Sem1 delete failed\n")); + failures++; + } + if (atomSemDelete (&sem2) != ATOM_OK) + { + ATOMLOG (_STR("Sem2 delete failed\n")); + failures++; + } + } + + /* Test for ATOM_WOULDBLOCK */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test sem1\n")); + failures++; + } + else + { + /* Semaphore count is zero so will block */ + if ((status = atomSemGet (&sem1, -1)) != ATOM_WOULDBLOCK) + { + ATOMLOG (_STR("Wouldblock err %d\n"), status); + failures++; + } + + /* Delete the test semaphore */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Sem1 delete failed\n")); + failures++; + } + + } + + /* Test no timeout */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test sem1\n")); + failures++; + } + else + { + /* Increment the semaphore */ + if (atomSemPut (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Error on put\n")); + failures++; + } + + /* Semaphore count is one so will not block */ + if (atomSemGet (&sem1, -1) != ATOM_OK) + { + ATOMLOG (_STR("Error on get\n")); + failures++; + } + + /* Delete the test semaphore */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Sem1 delete failed\n")); + failures++; + } + + } + + /* Test for semaphore counter overflows with too many puts */ + if (atomSemCreate (&sem1, 255) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test sem1\n")); + failures++; + } + else + { + /* Increment the semaphore (expect this to overflow the count at 256) */ + if (atomSemPut (&sem1) != ATOM_ERR_OVF) + { + ATOMLOG (_STR("Failed to detect overflow\n")); + failures++; + } + + /* Delete the test semaphore */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Sem1 delete failed\n")); + failures++; + } + + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} + + +/** + * \b testCallback + * + * Attempt an atomSemGet() on sem1 from interrupt context. + * Should receive an ATOM_ERR_CONTEXT error. Posts sem2 if successful. + * + * @param[in] cb_data Not used + */ +static void testCallback (POINTER cb_data) +{ + /* Check the return value from atomSemGet() */ + if (atomSemGet(&sem1, 0) == ATOM_ERR_CONTEXT) + { + /* Received the error we expected, post sem2 to notify success */ + atomSemPut(&sem2); + } + else + { + /* + * Did not get the expected error, don't post sem2 and the test + * thread will time out, signifying an error. + */ + } + +} \ No newline at end of file diff --git a/tests/sem3.c b/tests/sem3.c new file mode 100644 index 0000000..561efa3 --- /dev/null +++ b/tests/sem3.c @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atomsem.h" + + +/* Test OS objects */ +static ATOM_SEM sem1; +static ATOM_TCB tcb1, tcb2, tcb3, tcb4; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Data updated by threads */ +static volatile uint8_t wake_cnt; +static volatile uint8_t wake_order[4]; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start semaphore test. + * + * With multiple threads blocking on a single semaphore, this test confirms that + * they are woken in order when the semaphore is posted. The correct order for + * waking is that the higher priority threads are woken first, followed by the + * lower priority threads. Where multiple threads of the same priority are + * waiting, the threads are woken in FIFO order (the order in which they started + * waiting on the semaphore). + * + * To test this we create four threads which all wait on a single semaphore. + * One pair of threads are running at high priority, with the other pair at a + * lower priority: + * + * Thread 1: low prio thread A + * Thread 2: low prio thread B + * Thread 3: high prio thread A + * Thread 4: high prio thread B + * + * The threads are forced to start blocking on the same semaphore in the + * above order (the semaphore is initialised with count 0 to ensure any + * threads calling atomSemGet() will block). + * + * We expect to see them woken up in the following order: + * 3, 4, 1, 2 + * + * This proves the multiple blocking thread ordering in terms of both + * the priority-queueing and same-priority-FIFO-queueing. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + int i; + + /* Default to zero failures */ + failures = 0; + + /* Create sem with count zero (so that all threads will block) */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + else + { + /* Create Thread 1 (lower priority thread A) */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO+1, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the semaphore */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Create Thread 2 (lower priority thread B) */ + if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO+1, test_thread_func, 2, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the semaphore */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Create Thread 3 (higher priority thread A) */ + if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the semaphore */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Create Thread 4 (higher priority thread B) */ + if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4, + &test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* Delay to ensure the thread will start blocking on the semaphore */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* All four threads will now be blocking on sem1 */ + + /* + * Initialise wake count, used by threads to determine + * what order they were woken in. + */ + wake_cnt = 0; + + /* + * Wake the four threads up in order, leaving some time between + * each wake up for them to deal with global data in a + * thread-safe fashion. + */ + for (i = 0; i < 4; i++) + { + /* Post semaphore to wake one of the threads up */ + if (atomSemPut (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Post fail\n")); + failures++; + } + + /* Sleep to give the thread time to manipulate global data */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC / 4); + } + + /* All four threads now woken up, check they woke in correct order */ + if ((wake_order[0] != 3) || (wake_order[1] != 4) + || (wake_order[2] != 1) || (wake_order[3] != 2)) + { + ATOMLOG (_STR("Bad order %d,%d,%d,%d\n"), + wake_order[0], wake_order[1], wake_order[2], wake_order[3]); + failures++; + } + + /* Delete semaphore, test finished */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. The same thread entry point is used for all + * four test threads, with the thread number/ID (1-4) passed as the entry + * point parameter. + * + * @param[in] data Thread number (1,2,3,4) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t thread_id; + + /* Thread ID is passed through the function parameter */ + thread_id = (uint8_t)data; + + /* + * Wait for sem1 to be posted. At creation of all test threads + * the semaphore count is zero, so all four threads will block + * here. + */ + if (atomSemGet (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Thread sem fail\n")); + } + else + { + /* + * Store our thread ID in the array using the current + * wake_cnt order. The threads are deliberately woken up + * some time apart to ensure that no protection is required + * on this global data. + */ + wake_order[wake_cnt++] = thread_id; + } + + /* Loop forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/sem4.c b/tests/sem4.c new file mode 100644 index 0000000..d5ac8d6 --- /dev/null +++ b/tests/sem4.c @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atomsem.h" + + +/* Number of test loops for stress-test */ +#define NUM_TEST_LOOPS 10000 + + +/* Test OS objects */ +static ATOM_SEM sem1, sem2; +static ATOM_TCB tcb1, tcb2, tcb3, tcb4; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test4_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* + * Global failure count (can be updated by test threads but is + * protected by an interrupt lockout). + */ +static volatile int g_failures; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start semaphore test. + * + * Stress-tests semaphore Get and Put operations. Four threads are created which are + * continually Getting and Putting the same semaphore, with no time delays between + * each Get/Put. A single semaphore is used, which is initially created with a count + * of two, meaning that at any one time there should be threads blocking and not + * blocking. This ensures that the stress-test covers multiple threads accessing the + * semaphore APIs simultaneously, as well as usage of the APIs while other threads + * are blocking on the semaphore. + * + * @retval Number of g_failures + */ +uint32_t test_start (void) +{ + CRITICAL_STORE; + int finish_cnt; + + /* Default to zero g_failures */ + g_failures = 0; + + /* Create sem to stress with count two */ + if (atomSemCreate (&sem1, 2) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + g_failures++; + } + /* Create sem to receive thread-finished notification */ + else if (atomSemCreate (&sem2, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + g_failures++; + } + else + { + /* Create Thread 1 */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Create Thread 2 */ + if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 2, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Create Thread 3 */ + if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Create Thread 4 */ + if (atomThreadCreate(&tcb4, TEST_THREAD_PRIO, test_thread_func, 4, + &test4_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* + * All four threads will now be performing Gets/Puts on sem1. + * When they have finished they will post sem2, so we wait + * until sem2 is posted four times. + */ + finish_cnt = 0; + while (1) + { + /* + * Attempt to Get sem2. When we have managed to get + * the semaphore four times, it must have been posted + * by all four threads. + */ + if (atomSemGet (&sem2, 0) == ATOM_OK) + { + /* Increment our count of finished threads */ + finish_cnt++; + + /* Check if all four threads have now posted sem2 */ + if (finish_cnt == 4) + { + break; + } + } + } + + /* Delete semaphores, test finished */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + if (atomSemDelete (&sem2) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + } + + /* Log final status */ + if (g_failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), g_failures); + } + + /* Quit */ + return g_failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. The same thread entry point is used for all + * four test threads. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint32_t loop_cnt; + uint8_t status; + CRITICAL_STORE; + + /* Run a Get/Put pair many times */ + loop_cnt = NUM_TEST_LOOPS; + while (loop_cnt--) + { + if ((status = atomSemGet (&sem1, 0)) != ATOM_OK) + { + /* Error getting semaphore, notify the status code */ + ATOMLOG (_STR("G%d\n"), status); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + break; + } + else if ((status = atomSemPut (&sem1)) != ATOM_OK) + { + /* Error putting semaphore, notify the status code */ + ATOMLOG (_STR("P%d\n"), status); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + break; + } + } + + /* Post sem2 to notify the main thread we're finished */ + if (atomSemPut (&sem2) != ATOM_OK) + { + ATOMLOG (_STR("Sem2 putfail\n")); + CRITICAL_START (); + g_failures++; + CRITICAL_END (); + } + + /* Loop forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/sem5.c b/tests/sem5.c new file mode 100644 index 0000000..56ae937 --- /dev/null +++ b/tests/sem5.c @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atomsem.h" + + +/* Test OS objects */ +static ATOM_SEM sem1, sem2; +static ATOM_TCB tcb1; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start semaphore test. + * + * This tests basic synchronisation between threads using a semaphore. + * + * A second thread is created, which blocks on a semaphore until posted + * by the main thread. Testing all aspects of this transaction proves + * the basic usage of semaphores as a synchronisation mechanism + * between threads. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + + /* Default to zero failures */ + failures = 0; + + /* Create sem with count zero for second thread to block on */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + /* Create sem to receive test-passed notification */ + else if (atomSemCreate (&sem2, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + else + { + /* Create second thread */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* + * The second thread has now been created and should block on + * sem1 until we increment it to 1 (sem1 was created with a + * count of zero). Once the second thread has stopped blocking + * on sem1, it will post sem2 to notify us that it has finished. + * + * We can test the count of sem2 before and after posting sem1 + * to ensure that it was the posting of sem1 which caused the + * second thread to wake up. + */ + + /* + * Wait a little while, then check the second thread hasn't + * already posted sem2, i.e. did not wait until sem1 was posted. + */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + if (atomSemGet (&sem2, -1) != ATOM_WOULDBLOCK) + { + ATOMLOG (_STR("Did not wait\n")); + failures++; + } + + /* Post sem1 to stop the second thread blocking */ + else if (atomSemPut (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Put fail\n")); + failures++; + } + + /* Now check that the second thread has woken up and posted sem2 */ + else + { + /* Give the thread some time to wake up and post sem2 */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Check that the second thread has now woken and posted sem2 */ + if (atomSemGet (&sem2, -1) != ATOM_OK) + { + ATOMLOG (_STR("Sem2 not posted\n")); + failures++; + } + + } + + /* Delete semaphores, test finished */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + if (atomSemDelete (&sem2) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + + /* Block on sem1. Main thread will post when we should wake up. */ + if ((status = atomSemGet (&sem1, 0)) != ATOM_OK) + { + /* Error getting semaphore, notify the status code */ + ATOMLOG (_STR("G%d\n"), status); + } + + /* Post sem2 to notify that we received the sem1 notification */ + else if ((status = atomSemPut (&sem2)) != ATOM_OK) + { + /* Error putting semaphore, notify the status code */ + ATOMLOG (_STR("P%d\n"), status); + } + + /* Loop forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/sem6.c b/tests/sem6.c new file mode 100644 index 0000000..ab7c398 --- /dev/null +++ b/tests/sem6.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atomsem.h" + + +/* Semaphore count */ +#define INITIAL_SEM_COUNT 10 + + +/* Test OS objects */ +static ATOM_SEM sem1, sem2; +static ATOM_TCB tcb1; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start semaphore test. + * + * This tests basic counting semaphore operation between two threads. + * + * A semaphore is created with a count of 10. A second thread then + * ensures that it can decrement the semaphore 10 times before + * it can no longer be decremented. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + + /* Default to zero failures */ + failures = 0; + + /* Create sem with count ten for second thread to decrement */ + if (atomSemCreate (&sem1, INITIAL_SEM_COUNT) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + /* Create sem to receive test-passed notification */ + else if (atomSemCreate (&sem2, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + else + { + /* Check that sem2 doesn't already have a positive count */ + if (atomSemGet (&sem2, -1) != ATOM_WOULDBLOCK) + { + ATOMLOG (_STR("Sem2 already put\n")); + failures++; + } + + /* Create second thread */ + else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* + * The second thread has now been created and will attempt to + * decrement sem1 ten times, then finally check that it cannot + * decrement it any further. If this passes then the second + * thread will post sem2 to notify us that the test has passed. + */ + else + { + /* Give the second thread one second to post sem2 */ + if (atomSemGet (&sem2, SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Sem2 not posted\n")); + failures++; + } + + } + + /* Delete semaphores, test finished */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + if (atomSemDelete (&sem2) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + int count; + int failures; + + /* + * Attempt to decrement sem1 ten times, which should happen immediately + * each time. + */ + failures = 0; + count = INITIAL_SEM_COUNT; + while (count--) + { + /* Decrement sem1 */ + if ((status = atomSemGet (&sem1, -1)) != ATOM_OK) + { + /* Error decrementing semaphore, notify the status code */ + ATOMLOG (_STR("G%d\n"), status); + failures++; + } + } + + /* Check above stage was successful */ + if (failures == 0) + { + /* Sem1 should now have a count of zero, and not allow a decrement */ + if ((status = atomSemGet (&sem1, -1)) != ATOM_WOULDBLOCK) + { + /* Error getting semaphore, notify the status code */ + ATOMLOG (_STR("W%d\n"), status); + } + + /* Post sem2 to notify that the test passed */ + else if ((status = atomSemPut (&sem2)) != ATOM_OK) + { + /* Error putting semaphore, notify the status code */ + ATOMLOG (_STR("P%d\n"), status); + } + } + + /* Loop forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/sem7.c b/tests/sem7.c new file mode 100644 index 0000000..af79548 --- /dev/null +++ b/tests/sem7.c @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atomsem.h" + + +/* Test OS objects */ +static ATOM_SEM sem1; +static ATOM_TCB tcb1; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Global shared data protected by mutex */ +static volatile int shared_data; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start semaphore test. + * + * This tests usage of a semaphore for basic mutual exclusion type + * operation. Note that Atomthreads has a more fully-featured real + * mutex implementation in the mutex module. + * + * The semaphore sem1 is initialised with a count of 1. Whichever + * thread holds this semaphore can then modify the global variable + * "shared_data". + * + * The main thread first takes the "mutex" sem1, then creates a + * second thread. The second thread should block on the sem1 mutex + * until the main thread releases it. The test checks that the + * global "shared_data" is not modified by the second thread + * until the main thread releases the mutex. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + int i; + + /* Default to zero failures */ + failures = 0; + + /* Create sem with count one for mutex purposes */ + if (atomSemCreate (&sem1, 1) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + else + { + /* Initialise the shared_data to zero */ + shared_data = 0; + + /* Take the mutex to ensure only this thread can modify shared_data */ + if (atomSemGet (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error taking mutex\n")); + failures++; + } + + /* Create second thread */ + else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread\n")); + failures++; + } + + /* + * The second thread has now been created and should block on + * the "mutex" sem1 (which now has count zero) until we release + * it. We wait a while and check that shared_data has not been + * modified. + */ + for (i = 0; i < 4; i++) + { + /* + * Sleep for a while to give the second thread a chance to + * modify shared_data, thought it shouldn't until we + * release the mutex. + */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Check shared data. The second thread always sets it to one. */ + if (shared_data != 0) + { + ATOMLOG (_STR("Shared data modified\n")); + failures++; + break; + } + } + + /* Check successful so far */ + if (failures == 0) + { + /* + * Release the mutex, which will allow the second thread to + * wake and start modifying shared_data. + */ + if (atomSemPut (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Failed release\n")); + failures++; + } + + /* + * Wait a little while then check that shared_data has + * been modified. + */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + if (shared_data != 1) + { + ATOMLOG (_STR("Expected modify\n")); + failures++; + } + + /* + * Release and take the mutex again a few times to ensure + * that the mutex continues to protect shared_data. + */ + for (i = 0; i < 4; i++) + { + /* + * Take the mutex again, to prevent second thread accessing + * shared_data. + */ + if (atomSemGet (&sem1, SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Retake %d\n"), i); + failures++; + break; + } + else + { + /* + * Set shared_data to 0 and wait to ensure that the + * second thread doesn't modify it while we have the + * mutex again. + */ + shared_data = 0; + + /* Wait a while to give second thread potential to run */ + atomTimerDelay(SYSTEM_TICKS_PER_SEC/4); + + /* + * Check that shared_data has not been modified while we + * own the mutex. + */ + if (shared_data != 0) + { + /* Thread is still modifying the data */ + ATOMLOG (_STR("Still modifying\n")); + failures++; + break; + } + + /* + * Release the mutex, which will allow the second thread to + * wake and start modifying shared_data again. + */ + if (atomSemPut (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Failed release\n")); + failures++; + } + + } + } + } + + /* Delete semaphore, test finished */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data Unused (optional thread entry parameter) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + + /* Repeatedly attempt to get the mutex and set shared_data to 1 */ + while (1) + { + /* Block on the mutex sem1 */ + if ((status = atomSemGet (&sem1, 0)) != ATOM_OK) + { + /* Error getting semaphore, notify the status code */ + ATOMLOG (_STR("G%d\n"), status); + break; + } + + /* Got the mutex */ + else + { + /* Set shared_data to signify that we think we have the mutex */ + shared_data = 1; + + /* Release the mutex allowing the main thread to take it again */ + if ((status = atomSemPut (&sem1)) != ATOM_OK) + { + /* Error putting semaphore, notify the status code */ + ATOMLOG (_STR("P%d\n"), status); + break; + } + } + + } + + /* Loop forever - we only reach here on error */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/sem8.c b/tests/sem8.c new file mode 100644 index 0000000..1456b30 --- /dev/null +++ b/tests/sem8.c @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" +#include "atomsem.h" + + +/* Test OS objects */ +static ATOM_SEM sem1; +static ATOM_TCB tcb1, tcb2, tcb3; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Test running flag */ +static volatile int test_running; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); +static void testCallback (POINTER cb_data); + + +/** + * \b test_start + * + * Start semaphore test. + * + * This stress-tests atomSemGet()/atomSemPut() with a single thread + * continually calling atomSemGet() and several contexts continually + * calling atomSemPut(). This stresses in particular the atomSemPut() + * API, with three threads at different priorities posting + * simultaneously, as well as a timer callback posting it from + * interrupt context. In all cases the same semaphore is posted. + * + * This tests the thread-safety and interrupt-safety of the semaphore + * APIs, and particularly the atomSemPut() function. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + uint32_t end_time; + ATOM_TIMER timer_cb; + + /* Default to zero failures */ + failures = 0; + + /* Create sem with count of zero */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + else + { + /* Set the test running flag */ + test_running = TRUE; + + /* + * Fill out a timer callback request structure. Pass the timer + * structure itself so that the callback can requeue the request. + */ + timer_cb.cb_func = testCallback; + timer_cb.cb_data = &timer_cb; + timer_cb.cb_ticks = 1; + + /* + * Request a timer callback to run in one tick's time. The callback + * will automatically queue another so that this happens repeatedly + * until the test is flagged as finished. + */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("Error registering timer\n")); + failures++; + } + + /* Create thread 1: Higher priority than main thread so should sleep */ + else if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO - 1, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 1\n")); + failures++; + } + + /* Create thread 2: Same priority as main thread so should not sleep */ + else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 0, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 2\n")); + failures++; + } + + /* Create thread 3: Same priority as main thread so should not sleep */ + else if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO + 1, test_thread_func, 0, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 3\n")); + failures++; + } + + /* The test threads have now all been created */ + else + { + /* + * Continually decrement the semaphore while the test threads + * and timer callbacks are continually incrementing it. The + * test finishes after this runs without error for 5 seconds. + */ + end_time = atomTimeGet() + (5 * SYSTEM_TICKS_PER_SEC); + while (atomTimeGet() < end_time) + { + /* Decrement the semaphore */ + if (atomSemGet (&sem1, SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("SemGet\n")); + failures++; + break; + } + } + + /* Test finished, stop the other threads and timer callbacks */ + test_running = FALSE; + + /* + * Wait before finishing: a timer callback could be due + * shortly, and we allocated the timer structure off the + * local call stack. + */ + atomTimerDelay(2); + } + + /* Delete semaphores, test finished */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete failed\n")); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. + * + * @param[in] data sleep_flag passed through here + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + int sleep_flag, count, failures; + + /* Were we requested to sleep occasionally? */ + sleep_flag = (int)data; + + /* Run until the main thread sets the finish flag or we get an error */ + failures = 0; + while ((test_running == TRUE) && (failures == 0)) + { + /* Post the semaphore 50 times */ + count = 50; + while (count--) + { + /* + * Post the semaphore. Allow overflows as these are likely + * to occur when so many threads are posting the same + * semaphore continually. + */ + status = atomSemPut (&sem1); + if ((status != ATOM_OK) && (status != ATOM_ERR_OVF)) + { + ATOMLOG (_STR("Put\n")); + failures++; + break; + } + } + + /* + * If requested to do so, sleep for a tick. This only happens on threads which + * are higher priority than the main test thread, and is necessary to allow + * the main thread to actually run. For better stress-testing, same or lower + * priority threads do not sleep. + */ + if (sleep_flag) + { + atomTimerDelay (1); + } + } + + /* Loop forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} + + +/** + * \b testCallback + * + * Post the semaphore from interrupt context. This will be occurring while + * atomSemPut() calls for the other threads are in progress, because it is + * called by the system tick ISR. This tests that the APIs lockout + * interrupts where necessary. + * + * Automatically requeues itself for one tick in the future, so this + * continually fires until the finish flag is set. + * + * @param[in] cb_data Pointer to the original ATOM_TIMER structure + */ +static void testCallback (POINTER cb_data) +{ + ATOM_TIMER *ptimer; + uint8_t status; + + /* Pull out the original timer request */ + ptimer = (ATOM_TIMER *)cb_data; + + /* Post sem1 */ + status = atomSemPut (&sem1); + if ((status != ATOM_OK) && (status != ATOM_ERR_OVF)) + { + /* Error */ + } + + /* Enqueue another timer callback in one tick's time */ + if (test_running == TRUE) + { + /* Update the callback time and requeue */ + ptimer->cb_ticks = 1; + if (atomTimerRegister (ptimer) != ATOM_OK) + { + } + } + else + { + /* Test finished, no more will be queued */ + } + +} \ No newline at end of file diff --git a/tests/sem9.c b/tests/sem9.c new file mode 100644 index 0000000..203033e --- /dev/null +++ b/tests/sem9.c @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomsem.h" +#include "atomtests.h" +#include "atomuser.h" + + +/* Test OS objects */ +static ATOM_SEM sem1; +static ATOM_TCB tcb1, tcb2, tcb3; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Test results */ +static volatile int pass_flag[3]; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start semaphore test. + * + * This test verifies the semaphore deletion API, by deleting a semaphore + * on which multiple threads are blocking, and checking that all three + * are woken up with an appropriate error code. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + int i; + + /* Default to zero failures */ + failures = 0; + + /* Initialise pass status for all three threads to FALSE */ + for (i = 0; i < 3; i++) + { + pass_flag[i] = FALSE; + } + + /* Test wakeup of three threads on semaphore deletion */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("Error creating test semaphore 1\n")); + failures++; + } + + else + { + /* Create test thread 1 */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 0, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 1\n")); + failures++; + } + + /* Create test thread 2 */ + else if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 1, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 2\n")); + failures++; + } + + /* Create test thread 3 */ + else if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 2, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Error creating test thread 3\n")); + failures++; + } + + /* Test threads now created */ + else + { + /* Wait a while for threads to start blocking on sem1 */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Delete sem1 now that all three threads should be blocking */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete fail\n")); + failures++; + } + else + { + /* Wait a while for all three threads to wake up */ + atomTimerDelay (SYSTEM_TICKS_PER_SEC/4); + + /* Check that all three threads have passed */ + if ((pass_flag[0] != TRUE) || (pass_flag[1] != TRUE) || (pass_flag[2] != TRUE)) + { + ATOMLOG (_STR("Thread fail\n")); + failures++; + } + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; +} + + +/** + * \b test_thread_func + * + * Entry point for test threads. + * + * @param[in] data Thread ID (0-2) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t status; + int thread_id; + + /* Pull out the passed thread ID */ + thread_id = (int)data; + + /* + * Wait on sem1 with timeout. We are expecting to be woken up + * by the main thread while blocking. + */ + status = atomSemGet(&sem1, (5 * SYSTEM_TICKS_PER_SEC)); + if (status != ATOM_ERR_DELETED) + { + ATOMLOG (_STR("Test1 thread woke without deletion (%d)\n"), status); + } + else + { + /* We were woken due to deletion as expected, set pass_flag to notify success */ + pass_flag[thread_id] = TRUE; + } + + /* Wait forever */ + while (1) + { + atomTimerDelay (SYSTEM_TICKS_PER_SEC); + } +} \ No newline at end of file diff --git a/tests/test-template.c b/tests/test-template.c new file mode 100644 index 0000000..537e272 --- /dev/null +++ b/tests/test-template.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" + + +/** + * \b test_start + * + * Start test. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + + /* Default to zero failures */ + failures = 0; + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} \ No newline at end of file diff --git a/tests/timer1.c b/tests/timer1.c new file mode 100644 index 0000000..c65b846 --- /dev/null +++ b/tests/timer1.c @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomsem.h" +#include "atomtimer.h" +#include "atomtests.h" + + +/* Test OS objects */ +static ATOM_SEM sem1; + + +/* Forward declarations */ +static void testCallback (POINTER cb_data); + + +/** + * \b test_start + * + * Start timer test. + * + * This test exercises the atomTimerDelay() API. It checks that + * the correct time delay is used, and also checks that error + * checking is correctly implemented within the API. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + uint32_t start_time, end_time; + ATOM_TIMER timer_cb; + + /* Default to zero failures */ + failures = 0; + + /* Test parameter-checks */ + if (atomTimerDelay(0) != ATOM_ERR_PARAM) + { + ATOMLOG(_STR("Param\n")); + failures++; + } + + /* Create a semaphore for receiving test notification */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("SemCreate\n")); + failures++; + } + else + { + /* + * Create a timer callback which will attempt to + * call atomTimerDelay() at interrupt context. + */ + timer_cb.cb_func = testCallback; + timer_cb.cb_data = NULL; + timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC; + + /* Request the timer callback to run in one second */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("TimerRegister\n")); + failures++; + } + + /* Wait up to two seconds for sem1 to be posted indicating success */ + else if (atomSemGet (&sem1, 2 * SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Context check\n")); + failures++; + } + + /* Delete the test semaphore */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete\n")); + failures++; + } + } + + /* + * Test that a 1 tick delay returns when system time + * has increased by exactly 1 tick. + */ + + /* + * We first delay by 1 tick to ensure that the thread + * is running at the start of a new tick, which should + * ensure that the time does not tick over between + * setting start_time and actually calling + * atomTimerDelay(). + */ + atomTimerDelay(1); + + /* Record the start time */ + start_time = atomTimeGet(); + + /* Request a 1 tick sleep */ + if (atomTimerDelay(1) != ATOM_OK) + { + ATOMLOG (_STR("Delay1\n")); + failures++; + } + else + { + /* Record the time we woke up */ + end_time = atomTimeGet(); + + /* Check that time has advanced by exactly 1 tick */ + if ((end_time - start_time) != 1) + { + ATOMLOG (_STR("Tick1:%d\n"), (end_time-start_time)); + failures++; + } + } + + /* + * Test that a 2 tick delay returns when system time + * has increased by exactly 2 ticks. + */ + + /* + * We first delay by 1 tick to ensure that the thread + * is running at the start of a new tick, which should + * ensure that the time does not tick over between + * setting start_time and actually calling + * atomTimerDelay(). + */ + atomTimerDelay(1); + + /* Record the start time */ + start_time = atomTimeGet(); + + /* Request a 2 tick sleep */ + if (atomTimerDelay(2) != ATOM_OK) + { + ATOMLOG (_STR("Delay2\n")); + failures++; + } + else + { + /* Record the time we woke up */ + end_time = atomTimeGet(); + + /* Check that time has advanced by exactly 2 ticks */ + if ((end_time - start_time) != 2) + { + ATOMLOG (_STR("Tick2:%d\n"), (end_time-start_time)); + failures++; + } + } + + /* + * Test that a 500 tick delay returns when system time + * has increased by exactly 500 ticks. + */ + + /* + * We first delay by 1 tick to ensure that the thread + * is running at the start of a new tick, which should + * ensure that the time does not tick over between + * setting start_time and actually calling + * atomTimerDelay(). + */ + atomTimerDelay(1); + + /* Record the start time */ + start_time = atomTimeGet(); + + /* Request a 500 tick sleep */ + if (atomTimerDelay(500) != ATOM_OK) + { + ATOMLOG (_STR("Delay500\n")); + failures++; + } + else + { + /* Record the time we woke up */ + end_time = atomTimeGet(); + + /* Check that time has advanced by exactly 500 ticks */ + if ((end_time - start_time) != 500) + { + ATOMLOG (_STR("Tick500:%d\n"), (end_time-start_time)); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b testCallback + * + * Attempt an atomTimerDelay() call from interrupt context. + * Should receive an ATOM_ERR_CONTEXT error. Posts sem1 if successful. + * + * @param[in] cb_data Not used + */ +static void testCallback (POINTER cb_data) +{ + /* Check the return value from atomTimerDelay() */ + if (atomTimerDelay(1) == ATOM_ERR_CONTEXT) + { + /* Received the error we expected, post sem1 to notify success */ + atomSemPut(&sem1); + } + else + { + /* + * Did not get the expected error, don't post sem1 and the test + * thread will time out, signifying an error. + */ + } + +} \ No newline at end of file diff --git a/tests/timer2.c b/tests/timer2.c new file mode 100644 index 0000000..1696d42 --- /dev/null +++ b/tests/timer2.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtimer.h" +#include "atomtests.h" + + +/* Test period (in seconds) */ +#define TEST_PERIOD_SECS 10 + + +/* Test OS objects */ +static ATOM_TCB tcb1, tcb2, tcb3; +static uint8_t test1_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test2_thread_stack[TEST_THREAD_STACK_SIZE]; +static uint8_t test3_thread_stack[TEST_THREAD_STACK_SIZE]; + + +/* Per-thread failure counts */ +static volatile int g_failure_cnt[3]; + + +/* Forward declarations */ +static void test_thread_func (uint32_t data); + + +/** + * \b test_start + * + * Start timer test. + * + * Tests that atomTimerDelay() delays for the correct time + * period when used by three threads simultanously. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + + /* Default to zero failures */ + failures = 0; + g_failure_cnt[0] = g_failure_cnt[1] = g_failure_cnt[2] = 0; + + /* Create Thread 1 */ + if (atomThreadCreate(&tcb1, TEST_THREAD_PRIO, test_thread_func, 1, + &test1_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Thread1\n")); + failures++; + } + + /* Create Thread 2 */ + if (atomThreadCreate(&tcb2, TEST_THREAD_PRIO, test_thread_func, 2, + &test2_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Thread2\n")); + failures++; + } + + /* Create Thread 3 */ + if (atomThreadCreate(&tcb3, TEST_THREAD_PRIO, test_thread_func, 3, + &test3_thread_stack[TEST_THREAD_STACK_SIZE - 1]) != ATOM_OK) + { + /* Fail */ + ATOMLOG (_STR("Thread3\n")); + failures++; + } + + /* Sleep for 10 seconds allowing the three threads to run tests */ + if (atomTimerDelay(TEST_PERIOD_SECS * SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Period\n")); + failures++; + } + + /* Add the per-thread failure count to the main count */ + failures += g_failure_cnt[0] + g_failure_cnt[1] + g_failure_cnt[2]; + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b test_thread_func + * + * Entry point for test thread. The same thread entry point is used for all + * three test threads, with the thread number/ID (1-3) passed as the entry + * point parameter. + * + * @param[in] data Thread number (1,2,3) + * + * @return None + */ +static void test_thread_func (uint32_t data) +{ + uint8_t thread_id; + uint32_t start_time, end_time; + + /* Thread ID is passed through the function parameter */ + thread_id = (uint8_t)data; + + /* + * Sleep for 1 tick to ensure that the thread starts near + * a timer tick boundary. This ensures that the system + * tick does not advance between the atomTimeGet() call + * and the actual atomTimerDelay() call being made. + */ + atomTimerDelay (1); + + /* Loop running the test forever */ + while (1) + { + /* Record the start time */ + start_time = atomTimeGet(); + + /* Sleep for n ticks, where n is the thread ID */ + if (atomTimerDelay(thread_id) != ATOM_OK) + { + g_failure_cnt[thread_id-1]++; + } + else + { + /* Record the time we woke up */ + end_time = atomTimeGet(); + + /* Check that time has advanced by exactly n ticks */ + if ((end_time - start_time) != thread_id) + { + g_failure_cnt[thread_id-1]++; + } + } + } +} \ No newline at end of file diff --git a/tests/timer3.c b/tests/timer3.c new file mode 100644 index 0000000..84e3fb3 --- /dev/null +++ b/tests/timer3.c @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomsem.h" +#include "atomtimer.h" +#include "atomtests.h" + + +/* Test OS objects */ +static ATOM_SEM sem1; + + +/* Forward declarations */ +static void testCallback (POINTER cb_data); + + +/** + * \b test_start + * + * Start timer test. + * + * This test exercises the atomTimerRegister() API. It tests that bad + * parameters are trapped, and that timer callbacks occur at the + * correct time. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + ATOM_TIMER timer_cb; + uint32_t expected_time; + + /* Default to zero failures */ + failures = 0; + + /* Create a semaphore for receiving test notifications */ + if (atomSemCreate (&sem1, 0) != ATOM_OK) + { + ATOMLOG (_STR("SemCreate\n")); + failures++; + } + + /* Test that bad parameters are trapped */ + + /* NULL parameter check */ + if (atomTimerRegister(NULL) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Param1\n")); + failures++; + } + + /* NULL callback function */ + timer_cb.cb_ticks = 1; + timer_cb.cb_func = NULL; + if (atomTimerRegister(&timer_cb) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Param2\n")); + failures++; + } + + /* Zero ticks */ + timer_cb.cb_ticks = 0; + timer_cb.cb_func = testCallback; + if (atomTimerRegister(&timer_cb) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Param3\n")); + failures++; + } + + /* Request a callback in 1 tick */ + + /* + * Sleep for one tick first to ensure we start near a + * tick boundary. This should ensure that the timer + * tick does not advance while we are setting up the + * timer but before registering the timer. + */ + atomTimerDelay(1); + + /* Request a callback in one tick time */ + timer_cb.cb_ticks = 1; + + /* We pass our testCallback() the expected end time */ + timer_cb.cb_func = testCallback; + expected_time = atomTimeGet() + timer_cb.cb_ticks; + timer_cb.cb_data = &expected_time; + + /* Register the timer callback */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg1\n")); + failures++; + } + + /* Wait up to 5 ticks for sem1 to be posted indicating success */ + else if (atomSemGet (&sem1, 5) != ATOM_OK) + { + ATOMLOG (_STR("Tick1\n")); + failures++; + } + + + /* Request a callback in 2 ticks */ + + /* + * Sleep for one tick first to ensure we start near a + * tick boundary. This should ensure that the timer + * tick does not advance while we are setting up the + * timer but before registering the timer. + */ + atomTimerDelay(1); + + /* Request a callback in 2 ticks time */ + timer_cb.cb_ticks = 2; + + /* We pass our testCallback() the expected end time */ + timer_cb.cb_func = testCallback; + expected_time = atomTimeGet() + timer_cb.cb_ticks; + timer_cb.cb_data = &expected_time; + + /* Register the timer callback */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg2\n")); + failures++; + } + + /* Wait up to 5 ticks for sem1 to be posted indicating success */ + else if (atomSemGet (&sem1, 5) != ATOM_OK) + { + ATOMLOG (_STR("Tick2\n")); + failures++; + } + + + /* Request a callback in 500 ticks */ + + /* + * Sleep for one tick first to ensure we start near a + * tick boundary. This should ensure that the timer + * tick does not advance while we are setting up the + * timer but before registering the timer. + */ + atomTimerDelay(1); + + /* Request a callback in 500 ticks time */ + timer_cb.cb_ticks = 500; + + /* We pass our testCallback() the expected end time */ + timer_cb.cb_func = testCallback; + expected_time = atomTimeGet() + timer_cb.cb_ticks; + timer_cb.cb_data = &expected_time; + + /* Register the timer callback */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg500\n")); + failures++; + } + + /* Wait up to 600 ticks for sem1 to be posted indicating success */ + else if (atomSemGet (&sem1, 600) != ATOM_OK) + { + ATOMLOG (_STR("Tick500\n")); + failures++; + } + + /* Delete the test semaphore */ + if (atomSemDelete (&sem1) != ATOM_OK) + { + ATOMLOG (_STR("Delete\n")); + failures++; + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b testCallback + * + * Check the time at which this timer callback occurs matches + * the time we expected. Post sem1 if correct. + * + * @param[in] cb_data Not used + */ +static void testCallback (POINTER cb_data) +{ + uint32_t expected_end_time; + + /* Pull out the expected end time */ + expected_end_time = *(uint32_t *)cb_data; + + /* + * Check the callback time (now) matches the time + * we expected the callback. + */ + if (atomTimeGet() == expected_end_time) + { + /* Called back when we expected, post sem1 to notify success */ + atomSemPut(&sem1); + } + else + { + /* + * Not called at expected time, don't post sem1 and the test + * thread will time out, signifying an error. + */ + } + +} \ No newline at end of file diff --git a/tests/timer4.c b/tests/timer4.c new file mode 100644 index 0000000..7a7a93c --- /dev/null +++ b/tests/timer4.c @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtimer.h" +#include "atomtests.h" + + +/* Test OS objects */ +static ATOM_TIMER timer_cb[4]; + + +/* Global test data */ +static uint32_t cb_ticks[4]; + + +/* Forward declarations */ +static void testCallback (POINTER cb_data); + + +/** + * \b test_start + * + * Start timer test. + * + * This test exercises the atomTimerRegister() API, particularly the + * linked lists used internally for managing an ordered list of timers. + * + * Several timers are registered out of order (in terms of the callback + * time) and we check that the callbacks are called at the expected + * times. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + int i; + + /* Default to zero failures */ + failures = 0; + + /* + * Fill out four timer request structures. Callbacks are + * requested starting in one second, with the others + * at 1 tick intervals thereafter. + */ + for (i = 0; i < 4; i++) + { + /* + * testCallback() is passed the expected + * callback time via cb_data. + */ + cb_ticks[i] = SYSTEM_TICKS_PER_SEC + i; + timer_cb[i].cb_ticks = cb_ticks[i]; + timer_cb[i].cb_func = testCallback; + timer_cb[i].cb_data = &cb_ticks[i]; + } + + /* Register the timers in a different order */ + if (atomTimerRegister (&timer_cb[1]) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg1\n")); + failures++; + } + else if (atomTimerRegister (&timer_cb[3]) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg3\n")); + failures++; + } + else if (atomTimerRegister (&timer_cb[2]) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg2\n")); + failures++; + } + else if (atomTimerRegister (&timer_cb[0]) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg0\n")); + failures++; + } + else + { + /* Successfully registered timers */ + + /* Wait two seconds for callbacks to complete */ + if (atomTimerDelay(2 * SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Wait\n")); + failures++; + } + else + { + /* + * The callbacks should have cleared down cb_ticks[x] + * to zero if they were called at the expected + * system time. + */ + for (i = 0; i < 4; i++) + { + /* Check the callback has zeroed the area */ + if (cb_ticks[i] != 0) + { + ATOMLOG (_STR("Clear%d\n"), i); + failures++; + } + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b testCallback + * + * Check the time at which this timer callback occurs matches + * the time we expected. Clear down the expected time location + * if correct. + * + * @param[in] cb_data Not used + */ +static void testCallback (POINTER cb_data) +{ + uint32_t expected_end_time; + + /* Pull out the expected end time */ + expected_end_time = *(uint32_t *)cb_data; + + /* + * Check the callback time (now) matches the time + * we expected the callback. + */ + if (atomTimeGet() == expected_end_time) + { + /* Called back when we expected, clear the passed location */ + *(uint32_t *)cb_data = 0; + } + else + { + /* Not called at expected time, don't clear the location */ + } + +} \ No newline at end of file diff --git a/tests/timer5.c b/tests/timer5.c new file mode 100644 index 0000000..03e7e82 --- /dev/null +++ b/tests/timer5.c @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomsem.h" +#include "atomtimer.h" +#include "atomtests.h" + + +/* Global test data */ +static volatile int callback_ran_flag; + + +/* Forward declarations */ +static void testCallback (POINTER cb_data); + + +/** + * \b test_start + * + * Start timer test. + * + * This test exercises the atomTimerCancel() API. It tests that bad + * parameters are trapped, and that it can be used to cancel an + * in-progress timer callback request. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + ATOM_TIMER timer_cb; + + /* Default to zero failures */ + failures = 0; + + /* Test parameter checks */ + if (atomTimerCancel(NULL) != ATOM_ERR_PARAM) + { + ATOMLOG (_STR("Param\n")); + failures++; + } + + /* Test cancel when timer not registered */ + if (atomTimerCancel(&timer_cb) != ATOM_ERR_NOT_FOUND) + { + ATOMLOG (_STR("NotFound\n")); + failures++; + } + + /* Test a callback can be cancelled */ + callback_ran_flag = FALSE; + + /* Request a callback in one second, no callback param required */ + timer_cb.cb_ticks = SYSTEM_TICKS_PER_SEC; + timer_cb.cb_func = testCallback; + + /* Register the timer callback */ + if (atomTimerRegister (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg\n")); + failures++; + } + else + { + /* Successfully registered for one second's time */ + + /* Cancel the callback */ + if (atomTimerCancel (&timer_cb) != ATOM_OK) + { + ATOMLOG (_STR("TimerCancel\n")); + failures++; + } + else + { + /* Successfully cancelled the callback */ + + /* Wait two seconds, and check callback did not occur */ + if (atomTimerDelay(2 * SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Wait\n")); + failures++; + } + else + { + /* The ran flag should still be FALSE */ + if (callback_ran_flag != FALSE) + { + ATOMLOG (_STR("Called back\n")); + failures++; + } + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b testCallback + * + * Set a flag to say we ran. Expected not to run for a pass. + * + * @param[in] cb_data Not used + */ +static void testCallback (POINTER cb_data) +{ + /* Callback was called */ + callback_ran_flag = TRUE; +} \ No newline at end of file diff --git a/tests/timer6.c b/tests/timer6.c new file mode 100644 index 0000000..267d127 --- /dev/null +++ b/tests/timer6.c @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtimer.h" +#include "atomtests.h" + + +/* Test OS objects */ +static ATOM_TIMER timer_cb[4]; + + +/* Global test data */ +static int callback_ran_flag[4]; + + +/* Forward declarations */ +static void testCallback (POINTER cb_data); + + +/** + * \b test_start + * + * Start timer test. + * + * This test exercises the atomTimerCancel() API, particularly its + * behaviour when there are several timers registered. Four timers + * are registered, two of which are cancelled, and the test confirms + * that only the two which are not cancelled are called back. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + int failures; + int i; + + /* Default to zero failures */ + failures = 0; + + /* Clear down the ran flag for all four timers */ + for (i = 0; i < 4; i++) + { + callback_ran_flag[i] = FALSE; + } + + /* + * Fill out four timer request structures. Callbacks are + * requested starting in one second, with the others + * at 1 tick intervals thereafter. + */ + for (i = 0; i < 4; i++) + { + /* + * testCallback() is passed a pointer to the flag it + * should set to notify that it has run. + */ + timer_cb[i].cb_ticks = SYSTEM_TICKS_PER_SEC + i; + timer_cb[i].cb_func = testCallback; + timer_cb[i].cb_data = &callback_ran_flag[i]; + } + + /* Register all four timers */ + for (i = 0; i < 4; i++) + { + if (atomTimerRegister (&timer_cb[i]) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg\n")); + failures++; + } + } + + /* Check timers were successfully created */ + if (failures == 0) + { + /* Cancel two of the callbacks */ + if (atomTimerCancel (&timer_cb[1]) != ATOM_OK) + { + ATOMLOG (_STR("Cancel1\n")); + failures++; + } + if (atomTimerCancel (&timer_cb[2]) != ATOM_OK) + { + ATOMLOG (_STR("Cancel2\n")); + failures++; + } + + /* Wait two seconds for callbacks to complete */ + if (atomTimerDelay(2 * SYSTEM_TICKS_PER_SEC) != ATOM_OK) + { + ATOMLOG (_STR("Wait\n")); + failures++; + } + else + { + /* + * We should now find that timer callbacks 0 and 3 + * have run, but 1 and 2 did not (due to cancellation). + */ + if ((callback_ran_flag[0] != TRUE) || (callback_ran_flag[3] != TRUE) + || (callback_ran_flag[1] != FALSE) || (callback_ran_flag[2] != FALSE)) + { + ATOMLOG (_STR("Cancellations\n")); + failures++; + } + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b testCallback + * + * Set a flag to say we ran. Some of the callbacks are + * expected to execute this, while those that are + * cancelled should not. + * + * @param[in] cb_data Not used + */ +static void testCallback (POINTER cb_data) +{ + /* Callback was called */ + *(int *)cb_data = TRUE; +} \ No newline at end of file diff --git a/tests/timer7.c b/tests/timer7.c new file mode 100644 index 0000000..2b08bb8 --- /dev/null +++ b/tests/timer7.c @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2010, Kelvin Lawson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. No personal names or organizations' names associated with the + * Atomthreads project may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS + * "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 THE PROJECT OR CONTRIBUTORS 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. + */ + + +#include "atom.h" +#include "atomtests.h" + + +/* Test OS objects */ +static ATOM_TIMER timer_cb[4]; + + +/* Global test data */ +static volatile uint32_t cb_order[4]; +static int cb_cnt = 0; +static uint32_t cb_time[4]; + + +/* Forward declarations */ +static void testCallback (POINTER cb_data); + + +/** + * \b test_start + * + * Start timer test. + * + * Test the behaviour of the timer subsystem on a system clock rollover. + * + * Sets the system clock to just before rollover and registers several + * timers. Tests that all timer callbacks occur and that they occur in + * in the correct order, when they span a timer rollover. + * + * @retval Number of failures + */ +uint32_t test_start (void) +{ + CRITICAL_STORE; + int i, failures; + + /* Default to zero failures */ + failures = 0; + + /* + * Lockout interrupts while registering to ensure that the clock does + * not roll over under us while we are registering our test timers. + */ + CRITICAL_START (); + + /* Set the clock to rollover - 6 */ + atomTimeSet (0xFFFFFFFA); + + /* Timer in 2 ticks (pre-rollover): should be called back first */ + timer_cb[0].cb_ticks = 2; + timer_cb[0].cb_func = testCallback; + timer_cb[0].cb_data = (POINTER)0; + if (atomTimerRegister (&timer_cb[0]) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg0\n")); + failures++; + } + + /* Timer in 10 ticks (post-rollover): should be called back last */ + timer_cb[1].cb_ticks = 10; + timer_cb[1].cb_func = testCallback; + timer_cb[1].cb_data = (POINTER)3; + if (atomTimerRegister (&timer_cb[1]) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg1\n")); + failures++; + } + + /* Timer in 4 ticks (pre-rollover): should be called back second */ + timer_cb[2].cb_ticks = 4; + timer_cb[2].cb_func = testCallback; + timer_cb[2].cb_data = (POINTER)1; + if (atomTimerRegister (&timer_cb[2]) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg2\n")); + failures++; + } + + /* Timer in 9 ticks (post-rollover): should be called back third */ + timer_cb[3].cb_ticks = 9; + timer_cb[3].cb_func = testCallback; + timer_cb[3].cb_data = (POINTER)2; + if (atomTimerRegister (&timer_cb[3]) != ATOM_OK) + { + ATOMLOG (_STR("TimerReg3\n")); + failures++; + } + + /* Initialise the cb_order delay to known values */ + for (i = 0; i < 4; i++) + { + cb_order[i] = 99; + } + + /* Unlock interrupts and let the test begin */ + CRITICAL_END (); + + /* + * Wait 20 ticks for the callbacks to complete. Also tests another + * timer registration via atomTimerDelay() for us. + */ + atomTimerDelay (20); + + /* Check the order the callbacks came in matched our expectations */ + for (i = 0; i < 4; i++) + { + if (cb_order[i] != i) + { + ATOMLOG (_STR("T%d=%d\n"), i, cb_order[i]); + failures++; + } + } + + /* Log final status */ + if (failures == 0) + { + ATOMLOG (_STR("Pass\n")); + } + else + { + ATOMLOG (_STR("Fail(%d)\n"), failures); + } + + /* Quit */ + return failures; + +} + + +/** + * \b testCallback + * + * Timer callback. Store our cb_data value in cb_order[]. + * This allows us to check that the callback order was as expectd. + * + * @param[in] cb_data Not used + */ +static void testCallback (POINTER cb_data) +{ + int expected_order; + + /* Pull out the expected ordere */ + expected_order = (int)cb_data; + + /* Store our callback order in cb_order[] */ + cb_order[cb_cnt] = expected_order; + + /* Store the current time for debug purposes */ + cb_time[cb_cnt] = atomTimeGet(); + + /* Interrupts are locked out so we can modify cb_cnt without protection */ + cb_cnt++; +}