diff --git a/Findsel4-tutorials.cmake b/Findsel4-tutorials.cmake new file mode 100644 index 0000000000..37a5b4ecdb --- /dev/null +++ b/Findsel4-tutorials.cmake @@ -0,0 +1,61 @@ +# +# Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# + +set(SEL4_TUTORIALS_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE STRING "") +mark_as_advanced(SEL4_TUTORIALS_DIR) + +# Include cmake tutorial helper functions +include(${SEL4_TUTORIALS_DIR}/cmake/helpers.cmake) + +macro(sel4_tutorials_regenerate_tutorial tutorial_dir) + # generate tutorial sources into directory + + GenerateTutorial(${tutorial_dir}) + if("${CMAKE_CURRENT_LIST_FILE}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt") + get_property(tute_hack GLOBAL PROPERTY DONE_TUTE_HACK) + if(NOT tute_hack) + set_property(GLOBAL PROPERTY DONE_TUTE_HACK TRUE) + # We are in the main project phase and regenerating the tutorial + # may have updated the file calling us. So we do some magic... + include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt) + return() + endif() + endif() + +endmacro() + +macro(sel4_tutorials_import_libsel4tutorials) + add_subdirectory(${SEL4_TUTORIALS_DIR}/libsel4tutorials libsel4tutorials) +endmacro() + +macro(sel4_tutorials_setup_roottask_tutorial_environment) + + find_package(seL4 REQUIRED) + find_package(elfloader-tool REQUIRED) + find_package(musllibc REQUIRED) + find_package(util_libs REQUIRED) + find_package(seL4_libs REQUIRED) + + sel4_import_kernel() + elfloader_import_project() + + # This sets up environment build flags and imports musllibc and runtime libraries. + musllibc_setup_build_environment_with_sel4runtime() + sel4_import_libsel4() + util_libs_import_libraries() + sel4_libs_import_libraries() + sel4_tutorials_import_libsel4tutorials() + +endmacro() + +macro(sel4_tutorials_setup_capdl_tutorial_environment) + sel4_tutorials_setup_roottask_tutorial_environment() + capdl_import_project() + CapDLToolInstall(install_capdl_tool CAPDL_TOOL_BINARY) +endmacro() + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(camkes-tool DEFAULT_MSG SEL4_TUTORIALS_DIR) diff --git a/__pycache__/common.cpython-311.pyc b/__pycache__/common.cpython-311.pyc new file mode 100644 index 0000000000..008a85cf9d Binary files /dev/null and b/__pycache__/common.cpython-311.pyc differ diff --git a/_data/projects/sel4-tutorials.yml b/_data/projects/sel4-tutorials.yml index ee35aa28c4..fac4c9b17a 100644 --- a/_data/projects/sel4-tutorials.yml +++ b/_data/projects/sel4-tutorials.yml @@ -66,7 +66,7 @@ components: section: sel4 - name: notifications - display_name: "Notification" + display_name: "Notifications" description: "seL4 notification tutorial" maintainer: "seL4 Foundation" status: "active" diff --git a/cmake/helpers.cmake b/cmake/helpers.cmake new file mode 100644 index 0000000000..695da851ee --- /dev/null +++ b/cmake/helpers.cmake @@ -0,0 +1,257 @@ +# +# Copyright 2018, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# + +# Helper that takes a filename and makes the directory where that file would go if +function(EnsureDir filename) + get_filename_component(dir "${filename}" DIRECTORY) + file(MAKE_DIRECTORY "${dir}") +endfunction(EnsureDir) + +# Wrapper around `file(RENAME` that ensures the rename succeeds by creating the destination +# directory if it does not exist +function(Rename src dest) + EnsureDir("${dest}") + file(RENAME "${src}" "${dest}") +endfunction(Rename) + +# Wrapper around using `cmake -E copy` that tries to ensure the copy succeeds by first +# creating the destination directory if it does not exist +function(Copy src dest) + EnsureDir("${dest}") + execute_process( + COMMAND + ${CMAKE_COMMAND} -E copy "${src}" "${dest}" + RESULT_VARIABLE exit_status + ) + if(NOT ("${exit_status}" EQUAL 0)) + message(FATAL_ERROR "Failed to copy ${src} to ${dest}") + endif() +endfunction(Copy) + +# Return non-zero if files one and two are not the same. +function(DiffFiles res one two) + execute_process( + COMMAND + diff -q "${one}" "${two}" + RESULT_VARIABLE exit_status + OUTPUT_VARIABLE OUTPUT + ERROR_VARIABLE OUTPUT + ) + set(${res} ${exit_status} PARENT_SCOPE) +endfunction() + +# Try to update output and old with the value of input +# Only update output with input if input != old +# Fail if input != old and output != old +function(CopyIfUpdated input output old) + if(EXISTS ${output}) + DiffFiles(template_updated ${input} ${old}) + DiffFiles(instance_updated ${output} ${old}) + if(template_updated AND instance_updated) + message( + FATAL_ERROR + "Template has been updated and the instantiated tutorial has been updated. \ + Changes would be lost if proceeded." + ) + endif() + set(do_update ${template_updated}) + else() + set(do_update TRUE) + endif() + if(do_update) + Copy(${input} ${output}) + Copy(${input} ${old}) + endif() + file(REMOVE ${input}) + +endfunction() + +# Update all the unmodified files in target dir with source_dir if they haven't changed with respect to old_dir +# Report a conflict if a file in source_dir and target_dir are different with respect to old_dir +function(UpdateGeneratedFiles source_dir target_dir old_dir files) + + separate_arguments(file_list NATIVE_COMMAND ${files}) + foreach(file ${file_list}) + string( + REPLACE + ${source_dir} + ${old_dir} + old + ${file} + ) + string( + REPLACE + ${source_dir} + ${target_dir} + target + ${file} + ) + CopyIfUpdated(${file} ${target} ${old}) + endforeach() + +endfunction() + +# Run the command located in .tute_config in the input_dir. +# This command is expected to generate files in output_dir and report +# a list of dependent input files and generated output files. +# the reported input files will be added as a cmake dependency and will +# trigger the generation if they are modified. +function(ExecuteGenerationProcess input_dir output_dir generated_files) + set(input_files ${CMAKE_CURRENT_BINARY_DIR}/input_files) + set(output_files ${CMAKE_CURRENT_BINARY_DIR}/output_files) + + include(${input_dir}/.tute_config) + + execute_process( + COMMAND + ${CMAKE_COMMAND} -E env ${TUTE_COMMAND} + OUTPUT_VARIABLE OUTPUT + ERROR_VARIABLE OUTPUT + RESULT_VARIABLE res + ) + if(res) + message(FATAL_ERROR "Failed to render: ${TUTE_COMMAND}, ${OUTPUT}") + endif() + # Set cmake to regenerate if any of the input files to the TUTE_COMMAND are updated + file(READ "${input_files}" files) + separate_arguments(file_list NATIVE_COMMAND ${files}) + if("${CMAKE_CURRENT_LIST_FILE}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt") + set_property( + DIRECTORY "${CMAKE_SOURCE_DIR}" + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS "${file_list}" + ) + endif() + file(READ "${output_files}" files) + set(${generated_files} ${files} PARENT_SCOPE) +endfunction() + +# Generate a tutorial in dir. +# This will run a CMAKE_COMMAND in .tute_config from inside dir. +# It will copy the generated files into dir. +# If GenerateTutorial is rerun, any generated files that change +# will be updated in dir, unless there is a conflict. A conflict +# is when the file in dir has been modified since the last generation. +function(GenerateTutorial full_path) + get_filename_component(dir ${full_path} NAME) + get_filename_component(base_dir ${full_path} DIRECTORY) + + # Implement include_guard() functionality within a function that + # can only be called once. Using include_guard() is considered dangerous + # within functions as it will be affected by other include_guard() calls + # within functions within the same file. + get_property(gen_tutorial GLOBAL PROPERTY GenerateTutorialONCE) + if(NOT gen_tutorial) + set_property(GLOBAL PROPERTY GenerateTutorialONCE TRUE) + else() + # If already generated the tutorial in this run we don't need to do so again. + return() + endif() + + if(EXISTS ${base_dir}/${dir}/.tute_config) + set(output_dir ${CMAKE_CURRENT_BINARY_DIR}/.tutegen/${dir}/gen) + set(old_output_dir ${CMAKE_CURRENT_BINARY_DIR}/.tutegen/${dir}/old) + set(target_dir ${base_dir}/${dir}) + + ExecuteGenerationProcess(${base_dir}/${dir} ${output_dir} generated_files) + UpdateGeneratedFiles(${output_dir} ${target_dir} ${old_output_dir} ${generated_files}) + endif() + if(NOT EXISTS ${base_dir}/${dir}/CMakeLists.txt) + message( + FATAL_ERROR + "Could not find: ${base_dir}/${dir}/CMakeLists.txt" + "It is required that ${base_dir}/${dir} contains a CMakeLists.txt" + ) + endif() + +endfunction() + +find_package(capdl REQUIRED) +file(GLOB_RECURSE capdl_python ${PYTHON_CAPDL_PATH}/*.py) +set(PYTHON3 "python3" CACHE INTERNAL "") + +set(python_with_capdl ${CMAKE_COMMAND} -E env PYTHONPATH=${PYTHON_CAPDL_PATH} ${PYTHON3}) +set(capdl_linker_tool ${python_with_capdl} ${CAPDL_LINKER_TOOL}) + +function(DeclareCDLRootImage cdl cdl_target) + cmake_parse_arguments(PARSE_ARGV 2 CDLROOTTASK "" "" "ELF;ELF_DEPENDS") + if(NOT "${CDLROOTTASK_UNPARSED_ARGUMENTS}" STREQUAL "") + message(FATAL_ERROR "Unknown arguments to DeclareCDLRootImage") + endif() + + CapDLToolCFileGen( + ${cdl_target}_cspec + ${cdl_target}_cspec.c + FALSE + "$" + ${cdl} + "${CAPDL_TOOL_BINARY}" + MAX_IRQS + ${CapDLLoaderMaxIRQs} + DEPENDS + ${cdl_target} + install_capdl_tool + "${CAPDL_TOOL_BINARY}" + ) + + # Ask the CapDL tool to generate an image with our given copied/mangled instances + BuildCapDLApplication( + C_SPEC + "${cdl_target}_cspec.c" + ELF + ${CDLROOTTASK_ELF} + DEPENDS + ${CDLROOTTASK_ELF_DEPENDS} + ${cdl_target}_cspec + OUTPUT + "capdl-loader" + ) + include(rootserver) + DeclareRootserver("capdl-loader") +endfunction() + +function(cdl_ld outfile output_target) + cmake_parse_arguments(PARSE_ARGV 2 CDL_LD "" "" "ELF;KEYS;MANIFESTS;DEPENDS") + if(NOT "${CDL_LD_UNPARSED_ARGUMENTS}" STREQUAL "") + message(FATAL_ERROR "Unknown arguments to cdl_ld") + endif() + + add_custom_command( + OUTPUT "${outfile}" + COMMAND + ${capdl_linker_tool} + --arch=${KernelSel4Arch} + --object-sizes $ gen_cdl + --manifest-in ${CDL_LD_MANIFESTS} + --elffile ${CDL_LD_ELF} + --keys ${CDL_LD_KEYS} + --outfile ${outfile} + DEPENDS ${CDL_LD_ELF} ${capdl_python} ${CDL_LD_MANIFESTS} + ) + add_custom_target(${output_target} DEPENDS "${outfile}") + add_dependencies(${output_target} ${CDL_LD_DEPENDS} object_sizes) + +endfunction() + +function(cdl_pp manifest_in target) + cmake_parse_arguments(PARSE_ARGV 2 CDL_PP "" "" "ELF;CFILE;DEPENDS") + if(NOT "${CDL_PP_UNPARSED_ARGUMENTS}" STREQUAL "") + message(FATAL_ERROR "Unknown arguments to cdl_pp") + endif() + + add_custom_command( + OUTPUT ${CDL_PP_CFILE} + COMMAND + ${capdl_linker_tool} + --arch=${KernelSel4Arch} + --object-sizes $ build_cnode + --manifest-in=${manifest_in} + --elffile ${CDL_PP_ELF} + --ccspace ${CDL_PP_CFILE} + DEPENDS ${capdl_python} ${manifest_in} object_sizes + ) + add_custom_target(${target} DEPENDS ${CDL_PP_CFILE}) +endfunction() diff --git a/common.py b/common.py new file mode 100644 index 0000000000..743ff2cf8b --- /dev/null +++ b/common.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# +# Copyright 2018, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# + +import os +import sys +import sh +import logging + +# Define how to configure each platform in terms of arguments passed to cmake +PLAT_CONFIG = { + 'pc99': ['-DTUT_BOARD=pc', '-DTUT_ARCH=x86_64'], + 'zynq7000': ['-DAARCH32=TRUE', '-DTUT_BOARD=zynq7000'], +} + +CAMKES_VM_CONFIG = { + 'pc99': ['-DTUT_BOARD=pc', '-DTUT_ARCH=x86_64', "-DFORCE_IOMMU=ON"], +} + +ALL_CONFIGS = PLAT_CONFIG.keys() + +# Declare each tutorial and the configs they support +TUTORIALS = { + 'hello-world': ALL_CONFIGS, + 'ipc': ALL_CONFIGS, + 'libraries-1': ALL_CONFIGS, + 'libraries-2': ALL_CONFIGS, + 'libraries-3': ALL_CONFIGS, + 'libraries-4': ALL_CONFIGS, + 'hello-camkes-0': ALL_CONFIGS, + 'hello-camkes-1': ALL_CONFIGS, + 'hello-camkes-2': ALL_CONFIGS, + 'hello-camkes-timer': ['zynq7000'], + 'capabilities': ALL_CONFIGS, + 'untyped': ALL_CONFIGS, + 'mapping': ['pc99'], + 'camkes-vm-linux': ['pc99'], + 'camkes-vm-crossvm': ['pc99'], + 'threads': ALL_CONFIGS, + 'notifications': ['pc99'], + 'mcs': ALL_CONFIGS, + 'interrupts': ['zynq7000'], + 'fault-handlers': ALL_CONFIGS, +} + +ALL_TUTORIALS = TUTORIALS.keys() + + +def get_tutorial_dir(): + '''Return directory containing sel4 tutorials repo''' + return os.path.dirname(os.path.realpath(__file__)) + + +def get_project_root(): + '''Returns the path to the root directory of this project''' + # assume default location of this project in projects/sel4-tutorials + return os.path.join(get_tutorial_dir(), '..', '..') + + +def _init_build_directory(config, initialised, directory, tute_directory, output=None, config_dict=PLAT_CONFIG): + args = [] + if not initialised: + tute_dir = "-DTUTORIAL_DIR=" + os.path.basename(tute_directory) + args = ['-G', 'Ninja'] + config_dict[config] + [tute_dir] + \ + ["-C", "../projects/sel4-tutorials/settings.cmake"] + return sh.cmake(args + [tute_directory], _cwd=directory, _out=output, _err=output) + + +def _init_tute_directory(config, tut, solution, task, directory, output=None): + if config == "pc99": + arch = "x86_64" + elif config == "zynq7000": + arch = "aarch32" + with open(os.path.join(directory, ".tute_config"), 'w') as file: + file.write("set(TUTE_COMMAND \"%s\")" % + ';'.join(["PYTHONPATH=${PYTHON_CAPDL_PATH}", "python3", os.path.join(get_tutorial_dir(), "template.py"), + "--tut-file", os.path.join(get_tutorial_dir(), + "tutorials/%s/%s" % (tut, tut)), + "--out-dir", "${output_dir}", + "--input-files", "${input_files}", + "--output-files", "${output_files}", + "--arch", arch, + "--rt" if tut == "mcs" else "", + "--task;%s" % task if task else "", + "--solution" if solution else ""])) + return + + +def init_directories(config, tut, solution, task, initialised, tute_directory, build_directory, output=None): + os.chdir(tute_directory) + _init_tute_directory(config, tut, solution, task, tute_directory, output=sys.stdout) + os.chdir(build_directory) + config_dict = None + if "camkes-vm" in tut: + config_dict = CAMKES_VM_CONFIG + else: + config_dict = PLAT_CONFIG + return _init_build_directory(config, initialised, build_directory, tute_directory, output, config_dict=config_dict) + + +def set_log_level(verbose, quiet): + if verbose: + logging.basicConfig(level=logging.DEBUG) + elif quiet: + logging.basicConfig(level=logging.ERROR) + else: + logging.basicConfig(level=logging.INFO) + + +def setup_logger(name): + logger = logging.getLogger(name) + ch = logging.StreamHandler(sys.stdout) + formatter = logging.Formatter('%(message)s') + ch.setFormatter(formatter) + logger.addHandler(ch) + return logger diff --git a/docs/CAmkESTutorial.pdf b/docs/CAmkESTutorial.pdf new file mode 100644 index 0000000000..8a76624ce8 Binary files /dev/null and b/docs/CAmkESTutorial.pdf differ diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000000..7fd82e0c0f --- /dev/null +++ b/docs/design.md @@ -0,0 +1,117 @@ + + +# seL4-tutorials Design + +The seL4-tutorials project is a set tutorials for learning how to use seL4 as +well as some tools for developing and keeping the tutorials up-to-date. + +## Motivation + +The seL4-tutorials aim to educate people about using seL4 and reduce its steep +learning curve. +seL4 is a verified microkernel. This means that its APIs are both low-level, +due to the microkernel philosophy, and not easy to use, due to the higher priority +of other design goals such as verification and security. seL4 seeks wide adoption +but having a very steep learning curve for people starting out is a significant +friction. + +Some of the problems the tutorials try and solve: +- Introduce and explain unfamiliar system primitives: Some of seL4's primitives + are unfamiliar or counter-intuitive for people that haven't already worked on + similar projects in the past. +- Lack of people available to ask questions: There aren't enough people with seL4 + knowledge to be able to easily find someone who can answer specific questions. +- Support different kernel configurations/platforms and versions: Due to the low-level + API, each platform or kernel configuration can have different APIs and behavior. + Additionally, on-going research into new APIs and mechanisms can lead to different + kernel implementations with different behavior. +- Establish a baseline of common assumed knowledge: When training someone to use + seL4, it is helpful to be able to assume a baseline of understanding. Having worked + through a standard set of training material can help establish this. +- Being able to deep-dive on a particular topic and step through a worked-example + can make it easy to seek out knowledge more precisely instead of having to commit + to more upfront learning. +- Keeping education material up-to-date with moving-target of kernel development. + +## Goals + +The tooling and tutorials in sel4-tutorials should have the following properties: +- Tutorials should be specific to seL4 or related concepts. +- A tutorial should have a stated purpose and outcomes about what is being conveyed. +- A tutorial should have a stated assumed set of starting knowledge, such as assuming + completion of certain other tutorials, and be possible to complete without an + unreasonable amount of additional knowledge. +- A tutorial should be able to be completed in a reasonably short amount of time. + It should be possible to work through a couple tutorials within a couple hours. +- A tutorial should have useful outcomes that allow someone to accomplish something + with their new knowledge. +- It should be possible to machine-check that code examples in tutorials successfully + build and run. This includes code-examples in intermediate states during the tutorial. +- It should be possible for tutorials to support different kernel versions, configurations + and platforms and also have tutorials that are specific to certain platforms, versions + and configurations. +- It should be possible to work through tutorials independently to enable self-learning. +- As much as possible, tutorials should be workable with a minimum set of hardware + dependencies. So apart from tutorials that focus on specialised hardware features, it + should be possible to use a hardware simulator (Qemu) for a test platform. +- Tutorials need to have balance of expressive (english) and precise (code) communication. +- Tutorials can target different levels of assumed knowledge and should be used to + demonstrate complex concepts also. + +## Features + +This project has the following components/features: + +### Tutorials + +There are a collection of tutorials that are used to learn about seL4 conecpts. +Tutorials can be worked through sequentially with access to a machine and an installation +of the required seL4 dependencies. Each new tutorial started creates a new source folder +and build configuration that can be used to modify and build the sources. This makes it +easy to take what is produced in a tutorial and create a new project repository around it +and continue developing it into a longer term project. + +It is also possible to read through a tutorial +from start to end without needing to setup a machine and perform the tasks. The docsite +hosts this second version of the tutorials in a web-rendered format. + +A tutorial's source format is a markdown file that gets interpreted by the tutorial tooling +to produce a workable tutorial. + +### Tooling + +There is tooling that processes a tutorial source file and can produce a static file +for reading only the tutorial documentation, or it can be used to generate a tutorial +source and build directory in some completion state. The tooling can also be used +to step-forward or step-back a tutorial to be able to start a tutorial in an intermediate +state and also machine-check that all steps in a tutorial work consistently. + +#### Literate programming + +The source format of the tutorials follow a literate programming paradigm where the main +format is markdown and code examples are embedded within the markdown source. A tutorial +file is processed from top to bottom and produces an internal representation of the +tutorial as a sequence of tasks that have a start and finish state. The tooling can then +render a source directory of the tutorial at a particular state and also a version of the +tutorial document with examples relevant to that state. This approach tries to strike +a balance between representing a tutorial as a sequence of distinct steps towards +achieving a goal while still maintaining a big-picture representation. + +#### Multiple tasks + +A tutorial tries to teach the process of how an example is developed. To be able to +easily maintain that the process component of a tutorial stays consistently up-to-date, +the tooling understands that a tutorial has multiple steps. This enables testing to +check that a tutorial's code components are still correct throughout the tutorial with +minimal duplication and also an additional debugging feature during self-study where +a step can be skipped forwards and back to check how a task is supposed to be approached. + +#### Revision control + +Storing the tutorials in revision control alongside the dependencies that are used +to build and run them ensures that it will be possible to obtain a working version +of the tutorials for any previous version of the kernel and libraries. diff --git a/docs/seL4-APILib-details.pdf b/docs/seL4-APILib-details.pdf new file mode 100644 index 0000000000..c948985c40 Binary files /dev/null and b/docs/seL4-APILib-details.pdf differ diff --git a/docs/seL4-APILib-overview.pdf b/docs/seL4-APILib-overview.pdf new file mode 100644 index 0000000000..b4d07d3f85 Binary files /dev/null and b/docs/seL4-APILib-overview.pdf differ diff --git a/docs/seL4-Overview.pdf b/docs/seL4-Overview.pdf new file mode 100644 index 0000000000..5463edcfb0 Binary files /dev/null and b/docs/seL4-Overview.pdf differ diff --git a/docs/seL4-devices.pdf b/docs/seL4-devices.pdf new file mode 100644 index 0000000000..7b846a136a Binary files /dev/null and b/docs/seL4-devices.pdf differ diff --git a/docs/seL4-realtime.pdf b/docs/seL4-realtime.pdf new file mode 100644 index 0000000000..9b9ef4ec6e Binary files /dev/null and b/docs/seL4-realtime.pdf differ diff --git a/docs/seL4RTTutorial_Guide.txt b/docs/seL4RTTutorial_Guide.txt new file mode 100644 index 0000000000..e375d12635 --- /dev/null +++ b/docs/seL4RTTutorial_Guide.txt @@ -0,0 +1,29 @@ +### Server 1 ### + +TODO 1: Explain that you can give a scheduling context while making the thread, + but you can also bind it after. For this tutorial we bind after, the next + one binds on configure. + +TODO 2: Might have to reiterate that TCB != SC + +TODO 3: Pretty straightforward, make sure they set budget = period to keep it + simple for now, and pick a reasonable number (100 seems to work fine). + +TODO 4: After they've done this one, explain that they have an active server, + same as the seL4 tutorial. They can try run it in QEMU. + Note that there will be an error at runtime since the first reply + in the server, ignore this for now + +Rest should be straightforward. + +If you have time, once interesting thing to show is with the ReplyRecv in the +server. After the whole thing is working, split up the reply and recv into +two invocations (seL4_Reply and seL4_Recv) and explain that it doesn't work, +since the server reliquishes its SC before it can wait on the endpoint. + +### Server 2 ### + +The code being written is mostly a repeat of the previous one, but everything is +active here. The most important part is to change the budget / period parameters +and see the different results. Some examples are provided at the top of the file +(TODO 4). diff --git a/docs/training-intro.pdf b/docs/training-intro.pdf new file mode 100644 index 0000000000..99ed54d9ba Binary files /dev/null and b/docs/training-intro.pdf differ diff --git a/init.py b/init.py new file mode 100755 index 0000000000..31a5bfcaee --- /dev/null +++ b/init.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# +# Copyright 201*, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# + +# Wrapper script to setup a build directory ready for doing the tutorials + +import common +import sys +import argparse +import logging +import os +import sh +import tempfile + + +def main(): + parser = argparse.ArgumentParser( + description="Initialize a build directory for completing a tutorial. Invoke from " + "an empty sub directory, or the tutorials directory, in which case a " + "new build directory will be created for you.") + + parser.add_argument('--verbose', action='store_true', + help="Output everything including debug info", default=False) + parser.add_argument('--quiet', action='store_true', + help="Suppress output except for tutorial completion information", default=True) + parser.add_argument('--plat', type=str, choices=common.ALL_CONFIGS) + parser.add_argument('--tut', type=str, choices=common.ALL_TUTORIALS, required=True) + parser.add_argument('--solution', action='store_true', + help="Generate pre-made solutions", default=False) + parser.add_argument('--task', help="Generate pre-made solutions") + parser.add_argument('tutedir', nargs='?', default=os.getcwd()) + + args = parser.parse_args() + common.setup_logger(__name__) + common.set_log_level(args.verbose, True) + # Additional config/tutorial combination validation + if not args.plat: + # just pick the first platform that works for this tutorial + args.plat = list(common.TUTORIALS[args.tut])[0] + + if args.plat not in common.TUTORIALS[args.tut]: + logging.error("Tutorial %s not supported by platform %s. Valid platforms are %s: ", + args.tut, args.plat, common.TUTORIALS[args.tut]) + return -1 + # Check that the current working directory is empty. If not create a suitably + # named build directory and switch to it + dir_contents = os.listdir(args.tutedir) + initialised = False + if ".tute_config" in dir_contents: + initialised = True + if len(dir_contents) != 0 and ".tute_config" not in dir_contents: + # Check that we are in the tutorial root directory before we decide to start + # Making new directories + if not os.access(os.getcwd() + "/init", os.X_OK): + logging.error("Current dir %s is invalid" % os.getcwd()) + parser.print_help(sys.stderr) + return -1 + tute_dir = os.path.join(os.getcwd(), args.tut) + if os.path.exists(tute_dir): + tute_dir = tempfile.mkdtemp(dir=os.getcwd(), prefix=('%s' % (args.tut))) + else: + os.mkdir(tute_dir) + os.chdir(tute_dir) + else: + tute_dir = args.tutedir + # Check that our parent directory is an expected tutorial root directory + if not os.access(os.getcwd() + "/../init", os.X_OK): + logging.error("Parent directory is not tutorials root directory") + return -1 + # Initialize cmake. Output will be supressed as it defaults to the background + build_dir = "%s_build" % tute_dir + if not initialised: + os.mkdir(build_dir) + + result = common.init_directories(args.plat, args.tut, args.solution, + args.task, initialised, tute_dir, build_dir, sys.stdout) + if result.exit_code != 0: + logging.error("Failed to initialize build directory.") + return -1 + # Inform the user about any subdirectory we might have made + print("Tutorials created in subdirectory \"%s\"." % os.path.basename(tute_dir)) + print("Build directory initialised in \"%s\"." % os.path.basename(build_dir)) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/libsel4tutorials/CMakeLists.txt b/libsel4tutorials/CMakeLists.txt new file mode 100644 index 0000000000..6f9d9f5bd8 --- /dev/null +++ b/libsel4tutorials/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# Copyright 2018, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# + +cmake_minimum_required(VERSION 3.8.2) + +add_library(sel4tutorials STATIC EXCLUDE_FROM_ALL src/constructors.c src/alloc.c) + +target_link_libraries( + sel4tutorials + sel4_autoconf + sel4runtime + muslc + sel4 + sel4platsupport + sel4muslcsys + sel4runtime_Config +) + +# We force a dependency on the constructor symbol otherwise the linker won't link in the file +target_link_libraries(sel4tutorials -Wl,-u -Wl,register_debug_putchar) +target_include_directories(sel4tutorials PUBLIC include) diff --git a/libsel4tutorials/include/sel4tutorials/alloc.h b/libsel4tutorials/include/sel4tutorials/alloc.h new file mode 100644 index 0000000000..145a54713f --- /dev/null +++ b/libsel4tutorials/include/sel4tutorials/alloc.h @@ -0,0 +1,28 @@ +/* + * Copyright 2018, Data61, CSIRO (ABN 41 687 119 230). + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* A very simple, inefficient, non-freeing allocator for doing the tutorials */ + +/* + * Allocate a slot from boot info. Allocates slots from info->empty.start. + * This will not work if slots in the bootinfo empty range have already been used. + * + * @return a cslot that has not already been returned by this function, from the range + * specified in info->empty + */ +seL4_CPtr alloc_slot(seL4_BootInfo *info); + +/* + * Create an object of the desired type and size. + * + * This function iterates through the info->untyped capability range, attempting to + * retype into an object of the provided type and size, until a successful allocation is made. + * + * @param type of the object to create + * @param size of the object to create. Unused if the object is not variably sized. + * @return a cslot containing the newly created untyped. + */ +seL4_CPtr alloc_object(seL4_BootInfo *info, seL4_Word type, seL4_Word size_bits); \ No newline at end of file diff --git a/libsel4tutorials/src/alloc.c b/libsel4tutorials/src/alloc.c new file mode 100644 index 0000000000..f3e1c34242 --- /dev/null +++ b/libsel4tutorials/src/alloc.c @@ -0,0 +1,39 @@ +/* + * Copyright 2018, Data61, CSIRO (ABN 41 687 119 230). + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +seL4_CPtr alloc_slot(seL4_BootInfo *info) +{ + ZF_LOGF_IF(info->empty.start == info->empty.end, "No CSlots left!"); + seL4_CPtr next_free_slot = info->empty.start++; + return next_free_slot; +} + +/* a very simple allocation function that iterates through the untypeds in boot info until + a retype succeeds */ +seL4_CPtr alloc_object(seL4_BootInfo *info, seL4_Word type) +{ + seL4_CPtr cslot = alloc_slot(info); + + /* keep trying to retype until we succeed */ + seL4_Error error = seL4_NotEnoughMemory; + for (seL4_CPtr untyped = info->untyped.start; untyped < info->untyped.end; untyped++) { + seL4_UntypedDesc *desc = &info->untypedList[untyped - info->untyped.start]; + if (!desc->isDevice) { + seL4_Error error = seL4_Untyped_Retype(untyped, type, 0, seL4_CapInitThreadCNode, 0, 0, cslot, 1); + if (error == seL4_NoError) { + return cslot; + } else if (error != seL4_NotEnoughMemory) { + ZF_LOGF_IF(error != seL4_NotEnoughMemory, "Failed to allocate untyped"); + } + } + } + + ZF_LOGF_IF(error == seL4_NotEnoughMemory, "Out of untyped memory"); + return cslot; +} diff --git a/libsel4tutorials/src/constructors.c b/libsel4tutorials/src/constructors.c new file mode 100644 index 0000000000..3b5a184465 --- /dev/null +++ b/libsel4tutorials/src/constructors.c @@ -0,0 +1,37 @@ +/* + * Copyright 2018, Data61, CSIRO (ABN 41 687 119 230). + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* Include Kconfig variables. */ +#include + +#include +#include +#include + +/* allow printf to use kernel debug printing */ +size_t kernel_putchar_write(void *data, size_t count) +{ +#ifdef CONFIG_DEBUG_BUILD + char *cdata = (char *)data; + for (size_t i = 0; i < count; i++) { + seL4_DebugPutChar(cdata[i]); + } +#endif + return count; +} + +void CONSTRUCTOR(200) register_debug_putchar(void) +{ + sel4muslcsys_register_stdio_write_fn(kernel_putchar_write); +} + +/* set a thread's name for debugging purposes */ +void name_thread(seL4_CPtr tcb, char *name) +{ +#ifdef SEL4_DEBUG_KERNEL + seL4_DebugNameThread(tcb, name); +#endif +} diff --git a/settings.cmake b/settings.cmake new file mode 100644 index 0000000000..da07102e41 --- /dev/null +++ b/settings.cmake @@ -0,0 +1,91 @@ +# +# Copyright 2018, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# + +include_guard(GLOBAL) + +set(project_dir "${CMAKE_CURRENT_LIST_DIR}/../../") +file(GLOB project_modules ${project_dir}/projects/*) +list( + APPEND + CMAKE_MODULE_PATH + ${project_dir}/kernel + ${project_dir}/tools/seL4/cmake-tool/helpers/ + ${project_dir}/tools/seL4/elfloader-tool/ + ${project_modules} +) +set(POLLY_DIR ${project_dir}/tools/polly CACHE INTERNAL "") + +include(application_settings) + +# Deal with the top level target-triplet variables. +if(NOT TUT_BOARD) + message( + FATAL_ERROR + "Please select a board to compile for. Choose either pc or zynq7000\n\t`-DTUT_BOARD=`" + ) +endif() + +# Set arch and board specific kernel parameters here. +if(${TUT_BOARD} STREQUAL "pc") + set(KernelArch "x86" CACHE STRING "" FORCE) + set(KernelPlatform "pc99" CACHE STRING "" FORCE) + if(${TUT_ARCH} STREQUAL "ia32") + set(KernelSel4Arch "ia32" CACHE STRING "" FORCE) + elseif(${TUT_ARCH} STREQUAL "x86_64") + set(KernelSel4Arch "x86_64" CACHE STRING "" FORCE) + else() + message(FATAL_ERROR "Unsupported PC architecture ${TUT_ARCH}") + endif() +elseif(${TUT_BOARD} STREQUAL "zynq7000") + # Do a quick check and warn the user if they haven't set + # -DARM/-DAARCH32/-DAARCH64. + if( + (NOT ARM) + AND (NOT AARCH32) + AND ((NOT CROSS_COMPILER_PREFIX) OR ("${CROSS_COMPILER_PREFIX}" STREQUAL "")) + ) + message( + WARNING + "The target machine is an ARM machine. Unless you've defined -DCROSS_COMPILER_PREFIX, you may need to set one of:\n\t-DARM/-DAARCH32/-DAARCH64" + ) + endif() + + set(KernelArch "arm" CACHE STRING "" FORCE) + set(KernelSel4Arch "aarch32" CACHE STRING "" FORCE) + set(KernelPlatform "zynq7000" CACHE STRING "" FORCE) + ApplyData61ElfLoaderSettings(${KernelPlatform} ${KernelSel4Arch}) +else() + message(FATAL_ERROR "Unsupported board ${TUT_BOARD}.") +endif() + +include(${project_dir}/kernel/configs/seL4Config.cmake) +set(CapDLLoaderMaxObjects 20000 CACHE STRING "" FORCE) +set(KernelRootCNodeSizeBits 16 CACHE STRING "") + +# For the tutorials that do initialize the plat support serial printing they still +# just want to use the kernel debug putchar if it exists +set(LibSel4PlatSupportUseDebugPutChar true CACHE BOOL "" FORCE) + +# Just let the regular abort spin without calling DebugHalt to prevent needless +# confusing output from the kernel for a tutorial +set(LibSel4MuslcSysDebugHalt FALSE CACHE BOOL "" FORCE) + +# Only configure a single domain for the domain scheduler +set(KernelNumDomains 1 CACHE STRING "" FORCE) + +# We must build the debug kernel because the tutorials rely on seL4_DebugPutChar +# and they don't initialize a platsupport driver. +ApplyCommonReleaseVerificationSettings(FALSE FALSE) + +# We will attempt to generate a simulation script, so try and generate a simulation +# compatible configuration +ApplyCommonSimulationSettings(${KernelSel4Arch}) +if(FORCE_IOMMU) + set(KernelIOMMU ON CACHE BOOL "" FORCE) +endif() + +find_package(sel4-tutorials REQUIRED) +sel4_tutorials_regenerate_tutorial(${project_dir}/${TUTORIAL_DIR}) diff --git a/template.py b/template.py new file mode 100755 index 0000000000..2df060ea2e --- /dev/null +++ b/template.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# +# Copyright 2018, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# +from __future__ import print_function +import os + +from jinja2 import Environment, FileSystemLoader +import argparse +import sys +import sh +import tools +from tools import tutorialstate, context +from yaml import load, dump +try: + from yaml import CLoader as Loader, CDumper as Dumper +except ImportError: + from yaml import Loader, Dumper + +from io import StringIO + + +def build_render_list(args): + ''' + This function is pretty gross. It will likely be removed once the + arguments that it understands are standardised as tutorials are ported + to a consistent way. It figures out what initial file + to render based on the tut_file argument. The current behavior + is to choose a .yaml or .md file if the exact filepath is passed in. + If it is a yaml file, then the yaml file is loaded and its contents + returned as data. If it is a md file, then this is added to the render + list in data and returned. + ''' + (dirname, leaf) = os.path.split(args.tut_file) + data = {} + + yaml_file = "" + md_file = "" + if leaf.endswith(".yaml"): + yaml_file = args.tut_file + elif leaf.endswith(".md"): + md_file = args.tut_file + else: + yaml_file = os.path.join(dirname, "%s.yaml" % leaf) + md_file = os.path.join(dirname, "%s.md" % leaf) + if os.path.isfile(yaml_file): + with open(yaml_file, 'r') as stream: + data = load(stream, Loader=Loader) + # Save yaml file to input deps file + if args.input_files: + print(stream.name, file=args.input_files) + elif os.path.isfile(md_file): + data['render'] = [leaf if leaf.endswith(".md") else "%s.md" % leaf] + else: + print("Could not find a file to process", file=sys.stderr) + return data + + +def render_file(args, env, state, file): + ''' + Render a file. Any side effects that don't involve writing out to the filesystem + should be captured by state. Any file that is touched should be added to the output_files + file, and any files that are read should be added to the input_files file. This + is for dependency tracking + ''' + filename = os.path.join(args.out_dir, file) + + # Create required directories + if not os.path.exists(os.path.dirname(filename)): + os.makedirs(os.path.dirname(filename)) + + with open(os.path.join(os.path.split(args.tut_file)[0], file), 'r') as in_stream, \ + open(filename, 'w') as out_stream: + + # Save dependencies to deps files + if args.input_files and args.output_files: + print(in_stream.name, file=args.input_files) + print(out_stream.name, file=args.output_files) + + # process template file + input = in_stream.read() + template = env.from_string(input) + + if (args.__getattribute__("docsite")): + s = StringIO(input) + lines = input.split('\n') + + i = 0 + for line in s: + lines[i] = line.replace("https://docs.sel4.systems/Tutorials/", "/Tutorials/") + i = i + 1 + + new_text = ''.join(lines) + template = env.from_string(str(new_text)) + + out_stream.write(template.render(context.get_context(args, state))) + + +def save_script_imports(args): + ''' + We save this file and every .py file in ./tools/ to the input_files + dependency file. + ''' + if args.input_files: + print(os.path.realpath(__file__), file=args.input_files) + tools_dir = os.path.join(os.path.dirname(__file__), "tools") + for i in os.listdir(tools_dir): + if i.endswith(".py"): + print(os.path.realpath(os.path.join(tools_dir, i)), file=args.input_files) + + +def main(): + parser = argparse.ArgumentParser(description='Tutorial script template parser. Template is read from ' + 'stdin and outout is placed in stdout') + parser.add_argument('-s', '--solution', action='store_true', default=False) + parser.add_argument('--docsite', action='store_true') + parser.add_argument('--tut-file') + parser.add_argument('--arch', default="x86_64") + parser.add_argument('--rt', action='store_true') + parser.add_argument('--task') + parser.add_argument('--out-dir') + parser.add_argument('--input-files', type=argparse.FileType('w')) + parser.add_argument('--output-files', type=argparse.FileType('w')) + args = parser.parse_args() + + # Save this script and its imports to input deps file + save_script_imports(args) + + # Read list of files to generate into dict + data = build_render_list(args) + + # Build our rendering environment. + env = Environment(loader=FileSystemLoader(os.path.dirname(__file__)), + block_start_string='/*-', + block_end_string='-*/', + variable_start_string='/*?', + variable_end_string='?*/', + comment_start_string='/*#', + comment_end_string='#*/') + env.filters.update(context.get_filters()) + + # Init our tutorial state. + state = tutorialstate.TuteState(args.task, args.solution, args.arch, args.rt) + + # Render all of the files. + # We use a gross while True loop to allow state.additional_files to + # be appended to as it is processed. + for file in data['render']: + render_file(args, env, state, file) + + while True: + if not state.additional_files: + break + file = state.additional_files.pop(0) + render_file(args, env, state, file) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/test.py b/test.py new file mode 100755 index 0000000000..12c6e0463c --- /dev/null +++ b/test.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# +# Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# + +# Builds and runs all tutorial solutions, comparing output with expected +# completion text. + +import sys +import os +import argparse +import re +import pexpect +import subprocess +import tempfile +import logging +import signal +import psutil +import shutil +import os.path +import xml.sax.saxutils +import sh +import tools +from tools import expect +import common + +# this assumes this script is in a directory inside the tutorial directory +TUTORIAL_DIR = common.get_tutorial_dir() +TOP_LEVEL_DIR = common.get_project_root() + + +def print_pexpect_failure(failure): + if failure == pexpect.EOF: + print("EOF received before completion text") + elif failure == pexpect.TIMEOUT: + print("Test timed out") + + +def run_single_test_iteration(build_dir, solution, logfile): + # Build + result = sh.ninja(_out=logfile, _cwd=build_dir) + if result.exit_code != 0: + logging.error("Failed to build. Not deleting build directory %s" % build_dir) + sys.exit(1) + + check = sh.Command(os.path.join(build_dir, "check")) + if solution: + result = check(_out=logfile, _cwd=build_dir) + else: + # We check the start state if not solution + result = check("--start", _out=logfile, _cwd=build_dir) + for proc in psutil.process_iter(): + if "qemu" in proc.name(): + proc.kill() + return result.exit_code + + +def run_single_test(config, tutorial, temp_file): + """ + Builds and runs the solution to a given tutorial application for a given + configuration, checking that the result matches the completion text + """ + # Create temporary directory for working in (make this a common helper to share with init.py) + tute_dir = tempfile.mkdtemp(dir=TOP_LEVEL_DIR, prefix=tutorial) + build_dir = "%s_build" % tute_dir + os.mkdir(build_dir) + + # Initialize directories + result = common.init_directories(config, tutorial, False, None, + False, tute_dir, build_dir, temp_file) + if result.exit_code != 0: + logging.error("Failed to initialize tute directory. Not deleting tute directory %s" % build_dir) + sys.exit(1) + + tasks = open(os.path.join(tute_dir, ".tasks"), 'r').read() + for task in tasks.strip().split('\n'): + for solution in [False, True]: + print("Testing: task: %s solution: %s" % (task, "True" if solution else "False")) + result = common.init_directories( + config, tutorial, solution, task, True, tute_dir, build_dir, temp_file) + if result.exit_code != 0: + logging.error( + "Failed to initialize tute directory. Not deleting tute directory %s" % build_dir) + sys.exit(1) + if run_single_test_iteration(build_dir, solution, temp_file): + print("") + return 1 + else: + logging.info("Success!") + + shutil.rmtree(build_dir) + shutil.rmtree(tute_dir) + + +def run_tests(tests): + """ + Builds and runs a list of tests + """ + + print('') + for (config, app) in tests: + print("" % (config, app)) + temp_file = tempfile.NamedTemporaryFile(delete=True, mode='w+', encoding='utf-8') + try: + run_single_test(config, app, temp_file) + except: + temp_file.seek(0) + print(temp_file.read()) + raise + print('') + + +def main(): + parser = argparse.ArgumentParser( + description="Runs all tests for the tutorials") + + parser.add_argument('--verbose', action='store_true', + help="Output everything including debug info") + parser.add_argument('--quiet', action='store_true', + help="Suppress output except for junit xml") + parser.add_argument('--config', type=str, choices=common.ALL_CONFIGS) + parser.add_argument('--app', type=str, choices=common.ALL_TUTORIALS) + + args = parser.parse_args() + + common.setup_logger(__name__) + common.set_log_level(args.verbose, args.quiet) + + # Generate all the tests (restricted by app and config) + tests = [] + for tutorial in common.ALL_TUTORIALS: + for config in common.TUTORIALS[tutorial]: + if (args.app is None or args.app == tutorial) and \ + (args.config is None or args.config == config): + tests = tests + [(config, tutorial)] + + run_tests(tests) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/__pycache__/__init__.cpython-311.pyc b/tools/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000..69b6b43d65 Binary files /dev/null and b/tools/__pycache__/__init__.cpython-311.pyc differ diff --git a/tools/__pycache__/expect.cpython-311.pyc b/tools/__pycache__/expect.cpython-311.pyc new file mode 100644 index 0000000000..00fba04ca0 Binary files /dev/null and b/tools/__pycache__/expect.cpython-311.pyc differ diff --git a/tools/__pycache__/tutorialstate.cpython-311.pyc b/tools/__pycache__/tutorialstate.cpython-311.pyc new file mode 100644 index 0000000000..99d42f48d3 Binary files /dev/null and b/tools/__pycache__/tutorialstate.cpython-311.pyc differ diff --git a/tools/context.py b/tools/context.py new file mode 100644 index 0000000000..31a2b34176 --- /dev/null +++ b/tools/context.py @@ -0,0 +1,457 @@ +# +# Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# + +from __future__ import print_function + +import inspect +import os +import stat +from pickle import dumps +import yaml +from jinja2 import pass_context + +from . import macros +from capdl import ObjectType, ObjectRights, Cap, lookup_architecture +from .tutorialstate import TaskContentType + + +class TutorialFilters: + """ + Class containing all tutorial filters. Add new static functions here to be included in the tutorial jinja2 context + """ + + @staticmethod + @pass_context + def File(context, content, filename, **kwargs): + """ + Declare content to be written directly to a file + """ + args = context['args'] + if args.out_dir and not args.docsite: + filename = os.path.join(args.out_dir, filename) + if not os.path.exists(os.path.dirname(filename)): + os.makedirs(os.path.dirname(filename)) + + elf_file = open(filename, 'w') + print(filename, file=args.output_files) + elf_file.write(content) + if "mode" in kwargs: + if "executable" == kwargs["mode"]: + st = os.stat(filename) + os.chmod(filename, st.st_mode | stat.S_IEXEC) + + return content + + @staticmethod + @pass_context + def TaskContent(context, content, task_name, content_type, subtask=None, completion=None): + """ + Declare task content for a task. Optionally takes content type argument + """ + if not content_type or content_type not in TaskContentType: + raise Exception("Invalid content type") + + state = context["state"] + task = state.get_task(task_name) + task.set_content(content_type, content, subtask) + if completion: + task.set_completion(content_type, completion) + return content + + @staticmethod + @pass_context + def TaskCompletion(context, content, task_name, content_type): + """ + Declare completion text for a particular content_type + """ + if not content_type or content_type not in TaskContentType: + raise Exception("Invalid content type") + state = context["state"] + task = state.get_task(task_name) + task.set_completion(content_type, content) + return content + + @staticmethod + @pass_context + def ExcludeDocs(context, content): + """ + Hides the contents from the documentation. Side effects from other functions + and filters will still occur + """ + return "" + + @staticmethod + @pass_context + def ELF(context, content, name, passive=False): + """ + Declares a ELF object containing content with name. + """ + state = context['state'] + args = context['args'] + stash = state.stash + + if args.out_dir and not args.docsite: + filename = os.path.join(args.out_dir, "%s.c" % name) + if not os.path.exists(os.path.dirname(filename)): + os.makedirs(os.path.dirname(filename)) + + elf_file = open(filename, 'w') + print(filename, file=args.output_files) + + elf_file.write(content) + objects = stash.allocator_state.obj_space + # The following allocates objects for the main thread, its IPC buffer and stack. + stack_name = "stack" + ipc_name = "mainIpcBuffer" + number_stack_frames = 16 + frames = [objects.alloc(ObjectType.seL4_FrameObject, name='stack_%d_%s_obj' % (i, name), label=name, size=4096) + for i in range(number_stack_frames)] + + sizes = [4096] * (number_stack_frames) + caps = [Cap(frame, read=True, write=True, grant=False) for frame in frames] + stash.current_addr_space.add_symbol_with_caps(stack_name, sizes, caps) + stash.current_region_symbols.append((stack_name, sum(sizes), 'size_12bit')) + + ipc_frame = objects.alloc(ObjectType.seL4_FrameObject, + name='ipc_%s_obj' % (name), label=name, size=4096) + caps = [Cap(ipc_frame, read=True, write=True, grant=False)] + sizes = [4096] + stash.current_addr_space.add_symbol_with_caps(ipc_name, sizes, caps) + stash.current_region_symbols.append((ipc_name, sum(sizes), 'size_12bit')) + + tcb = objects.alloc(ObjectType.seL4_TCBObject, name='tcb_%s' % (name)) + tcb['ipc_buffer_slot'] = Cap(ipc_frame, read=True, write=True, grant=False) + cap = Cap(stash.current_cspace.cnode) + tcb['cspace'] = cap + stash.current_cspace.cnode.update_guard_size_caps.append(cap) + tcb['vspace'] = Cap(stash.current_addr_space.vspace_root) + tcb.sp = "get_vaddr(\'%s\') + %d" % (stack_name, sum(sizes)) + tcb.addr = "get_vaddr(\'%s\')" % (ipc_name) + tcb.ip = "get_vaddr(\'%s\')" % ("_start") + # This initialises the main thread's stack so that a normal _start routine provided by libmuslc can be used + # The capdl loader app takes the first 4 arguments of .init and sets registers to them, + # argc = 2, + # argv[0] = get_vaddr("progname") which is a string of the program name, + # argv[1] = 1 This could be changed to anything, + # 0, 0, null terminates the argument vector and null terminates the empty environment string vector + # 32, is an aux vector key of AT_SYSINFO + # get_vaddr(\"sel4_vsyscall\") is the address of the SYSINFO table + # 0, 0, null terminates the aux vectors. + tcb.init = "[0,0,0,0,2,get_vaddr(\"progname\"),1,0,0,32,get_vaddr(\"sel4_vsyscall\"),0,0]" + if not passive and stash.rt: + sc = objects.alloc(ObjectType.seL4_SchedContextObject, + name='sc_%s_obj' % (name), label=name) + sc.size_bits = 8 + tcb['sc_slot'] = Cap(sc) + stash.current_cspace.alloc(tcb) + stash.finish_elf(name, "%s.c" % name) + + print("end") + return content + + +class TutorialFunctions: + """Class containing all tutorial functions. Add new static functions here to be included in the tutorial jinja2 + context """ + + @staticmethod + @pass_context + def ExternalFile(context, filename): + """ + Declare an additional file to be processed by the template renderer. + """ + state = context['state'] + state.additional_files.append(filename) + return + + @staticmethod + @pass_context + def include_task(context, task_name, subtask=None): + """ + Prints a task out + """ + state = context["state"] + task = state.get_task(task_name) + content = state.print_task(task, subtask) + if not task: + raise Exception("No content found for {0} {1}".format(task_name, str(subtask or ''))) + + @staticmethod + @pass_context + def include_task_type_replace(context, task_names): + """ + Takes a list of task names and displays only the one that is + active in the tutorial + """ + def normalise_task_name(task_name): + subtask = None + if isinstance(task_name, tuple): + # Subtask + subtask = task_name[1] + task_name = task_name[0] + return (task_name, subtask) + + if not isinstance(task_names, list): + task_names = [task_names] + state = context["state"] + + for (i, name) in enumerate(task_names): + (name, subtask) = normalise_task_name(name) + task = state.get_task(name) + if task > state.get_current_task(): + if i == 0: + # If we aren't up to any of the tasks yet we return nothing + return "" + # Use previous task + (name, subtask) = normalise_task_name(task_names[i-1]) + task = state.get_task(name) + elif task == state.get_current_task() or i is len(task_names) - 1: + # Use task as it is either the current task or there aren't more tasks to check + pass + else: + # Check next task + continue + + # We have a task, now we want to print it + content = state.print_task(task, subtask) + if not content: + # If the start of task isn't defined then we print the previous task + if i > 0: + (name, subtask) = normalise_task_name(task_names[i-1]) + task = state.get_task(name) + content = state.print_task(task, subtask) + return str(content or '') + + raise Exception("Could not find thing") + + @staticmethod + @pass_context + def include_task_type_append(context, task_names): + """ + Takes a list of task_names and appends the task content based on the position in + the tutorial. + """ + if not isinstance(task_names, list): + task_names = [task_names] + args = context['args'] + state = context["state"] + + result = [] + for i in task_names: + subtask = None + if isinstance(i, tuple): + # Subclass + subtask = i[1] + i = i[0] + task = state.get_task(i) + if task <= state.current_task: + print(task.name) + content = state.print_task(task, subtask) + if not content: + if state.solution: + raise Exception("No content found for {0} {1}".format( + task, str(subtask or ''))) + result.append(str(content or '')) + return '\n'.join(result) + + @staticmethod + @pass_context + def declare_task_ordering(context, task_names): + """ + Declare the list of tasks that the tutorial contains. + Their ordering in the array implies their ordering in the tutorial. + """ + state = context['state'] + state.declare_tasks(task_names) + args = context['args'] + if args.out_dir and not args.docsite: + filename = os.path.join(args.out_dir, ".tasks") + print(filename, file=args.output_files) + if not os.path.exists(os.path.dirname(filename)): + os.makedirs(os.path.dirname(filename)) + task_file = open(filename, 'w') + for i in task_names: + print(i, file=task_file) + return "" + + @staticmethod + @pass_context + def capdl_alloc_obj(context, obj_type, obj_name, **kwargs): + state = context['state'] + stash = state.stash + obj = None + if obj_type and obj_name: + obj = stash.allocator_state.obj_space.alloc(obj_type, obj_name, **kwargs) + return obj + + @staticmethod + @pass_context + def capdl_alloc_cap(context, obj_type, obj_name, symbol, **kwargs): + """ + Alloc a cap and emit a symbol for it. + """ + state = context['state'] + stash = state.stash + obj = TutorialFunctions.capdl_alloc_obj(context, obj_type, obj_name) + slot = stash.current_cspace.alloc(obj, **kwargs) + stash.current_cap_symbols.append((symbol, slot)) + return "extern seL4_CPtr %s;" % symbol + + @staticmethod + @pass_context + def capdl_elf_cspace(context, elf_name, cap_symbol): + return TutorialFunctions.capdl_alloc_cap(context, ObjectType.seL4_CapTableObject, "cnode_%s" % elf_name, cap_symbol) + + @staticmethod + @pass_context + def capdl_elf_vspace(context, elf_name, cap_symbol): + pd_type = lookup_architecture("x86_64").vspace().object + return TutorialFunctions.capdl_alloc_cap(context, pd_type, "vspace_%s" % elf_name, cap_symbol) + + @staticmethod + @pass_context + def capdl_elf_tcb(context, elf_name, cap_symbol): + return TutorialFunctions.capdl_alloc_cap(context, ObjectType.seL4_TCBObject, "tcb_%s" % elf_name, cap_symbol) + + @staticmethod + @pass_context + def capdl_elf_sc(context, elf_name, cap_symbol): + return TutorialFunctions.capdl_alloc_cap(context, ObjectType.seL4_SchedContextObject, "sc_%s" % elf_name, cap_symbol) + + @staticmethod + @pass_context + def capdl_sched_control(context, cap_symbol): + return TutorialFunctions.capdl_alloc_cap(context, ObjectType.seL4_SchedControl, "sched_control", cap_symbol) + + @staticmethod + @pass_context + def capdl_irq_control(context, cap_symbol): + return TutorialFunctions.capdl_alloc_cap(context, ObjectType.seL4_IRQControl, "irq_control", cap_symbol) + + @staticmethod + @pass_context + def capdl_empty_slot(context, cap_symbol): + return TutorialFunctions.capdl_alloc_cap(context, None, None, cap_symbol) + + @staticmethod + @pass_context + def capdl_declare_stack(context, size_bytes, stack_base_sym, stack_top_sym=None): + state = context['state'] + stash = state.stash + stash.current_region_symbols.append((stack_base_sym, size_bytes, "size_12bit")) + return "\n".join([ + "extern const char %s[%d];" % (stack_base_sym, size_bytes), + "" if stack_top_sym is None else "static const uintptr_t %s = (const uintptr_t)&%s + sizeof(%s);" % ( + stack_top_sym, stack_base_sym, stack_base_sym) + ]) + + @staticmethod + @pass_context + def capdl_declare_frame(context, cap_symbol, symbol, size=4096): + state = context['state'] + stash = state.stash + + obj = TutorialFunctions.capdl_alloc_obj( + context, ObjectType.seL4_FrameObject, cap_symbol, size=size) + cap_symbol = TutorialFunctions.capdl_alloc_cap( + context, ObjectType.seL4_FrameObject, cap_symbol, cap_symbol, read=True, write=True, grant=True) + stash.current_addr_space.add_symbol_with_caps( + symbol, [size], [Cap(obj, read=True, write=True, grant=True)]) + stash.current_region_symbols.append((symbol, size, "size_12bit")) + return "\n".join([ + cap_symbol, + "extern const char %s[%d];" % (symbol, size), + ]) + + @staticmethod + @pass_context + def capdl_declare_ipc_buffer(context, cap_symbol, symbol): + return TutorialFunctions.capdl_declare_frame(context, cap_symbol, symbol) + + @staticmethod + @pass_context + def write_manifest(context, manifest='manifest.yaml', allocator="allocators.pickle"): + state = context['state'] + args = context['args'] + stash = state.stash + if args.out_dir and not args.docsite: + manifest = os.path.join(args.out_dir, manifest) + allocator = os.path.join(args.out_dir, allocator) + if not os.path.exists(os.path.dirname(manifest)): + os.makedirs(os.path.dirname(manifest)) + + manifest_file = open(manifest, 'w') + allocator_file = open(allocator, 'wb') + print(manifest, file=args.output_files) + print(allocator, file=args.output_files) + + data = {"cap_symbols": stash.cap_symbols, "region_symbols": stash.region_symbols} + + manifest_file.write(yaml.dump(data)) + allocator_file.write(dumps(stash.allocator_state)) + return "" + + +''' +These are for potential extra template functions, that haven't been required +by templating requirements yet. +@pass_context +def show_if_task(context, task_names): + pass + +@pass_context +def show_before_task(context, task_names): + pass + + +@pass_context +def show_after_task(context, task_names): + pass + +@pass_context +def hide_if_task(context, task_names): + pass + +@pass_context +def hide_before_task(context, task_names): + pass + + +@pass_context +def hide_after_task(context, task_names): + pass + +''' + + +def get_context(args, state): + context = { + "solution": args.solution, + "args": args, + "state": state, + "TaskContentType": TaskContentType, + "macros": macros, + 'tab': "\t", + } + + # add all capDL object types + context.update(ObjectType.__members__.items()) + + # add all capDL object rights + context.update(ObjectRights.__members__.items()) + + # add all the static functions in TutorialFunctions. To + # add a new function to the context, add it as a static method to + # the TutorialFunctions class above. + context.update(inspect.getmembers(TutorialFunctions, predicate=inspect.isfunction)) + + return context + + +def get_filters(): + # add all static functions from the TutorialFilters class. To add a new + # filter, add a new static function to the TutorialFilters class above. + return inspect.getmembers(TutorialFilters, predicate=inspect.isfunction) diff --git a/tools/expect.py b/tools/expect.py new file mode 100755 index 0000000000..8866d7c6de --- /dev/null +++ b/tools/expect.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# +# Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# + +import os +import sys +import pexpect +import argparse + +# This file contains a function that wraps a simulation call in a pexpect +# context and matches for completion or failure strings. Can also be templated +# by CMake to create a custom script that takes no arguments. + + +# List of strings whose appearence in test output indicates test failure +FAILURE_TEXTS = [ + pexpect.EOF, + pexpect.TIMEOUT, + "Ignoring call to sys_exit_group" +] + + +def simulate_with_checks(dir, completion_text, failure_list=FAILURE_TEXTS, logfile=sys.stdout): + + test = pexpect.spawnu("python3", args=["simulate"], cwd=dir) + test.logfile = logfile + for i in completion_text.split('\n') + ["\n"]: + expect_strings = [i] + failure_list + result = test.expect(expect_strings, timeout=10) + + # result is the index in the completion text list corresponding to the + # text that was produced + if result != 0: + return result + return 0 + + +def main(): + finish_completion_text = """@FINISH_COMPLETION_TEXT@""" + start_completion_text = """@START_COMPLETION_TEXT@""" + parser = argparse.ArgumentParser( + description="Initialize a build directory for completing a tutorial. Invoke from " + "an empty sub directory, or the tutorials directory, in which case a " + "new build directory will be created for you.") + + parser.add_argument('--text', default=finish_completion_text, + help="Output everything including debug info") + parser.add_argument('--start', action='store_true', + help="Output everything including debug info") + args = parser.parse_args() + if args.start: + completion_text = start_completion_text + else: + completion_text = args.text + build_dir = os.path.dirname(__file__) + result = simulate_with_checks(build_dir, completion_text) + if result == 0: + print("Success!") + elif result <= len(FAILURE_TEXTS): + print("Failure! {0}".format(FAILURE_TEXTS[result - 1])) + else: + print("Unknown reason for failure") + + return result + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/macros.py b/tools/macros.py new file mode 100644 index 0000000000..529e0848bd --- /dev/null +++ b/tools/macros.py @@ -0,0 +1,71 @@ +# +# Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# + +from .tutorialstate import TaskContentType + + +def ninja_block(): + """Print ninja code block""" + return ''' +```sh +# In build directory +ninja +```''' + + +def simulate_block(): + """Print simulate code block""" + return ''' +```sh +# In build directory +./simulate +```''' + + +def ninja_simulate_block(): + """Print simulate and ninja code block""" + return ''' +```sh +# In build directory +ninja && ./simulate +```''' + + +def cmake_check_script(state): + return '''set(FINISH_COMPLETION_TEXT "%s") +set(START_COMPLETION_TEXT "%s") +configure_file(${SEL4_TUTORIALS_DIR}/tools/expect.py ${CMAKE_BINARY_DIR}/check @ONLY) +include(simulation) +GenerateSimulateScript() +''' % (state.print_completion(TaskContentType.COMPLETED), state.print_completion(TaskContentType.BEFORE)) + + +def tutorial_init(name): + return '''```sh +# For instructions about obtaining the tutorial sources see https://docs.sel4.systems/Tutorials/get-the-tutorials +# +# Follow these instructions to initialise the tutorial +# initialising the build directory with a tutorial exercise +./init --tut %s +# building the tutorial exercise +cd %s_build +ninja +``` +''' % (name, name) + + +def tutorial_init_with_solution(name): + return '''```sh +# For instructions about obtaining the tutorial sources see https://docs.sel4.systems/Tutorials/get-the-tutorials +# +# Follow these instructions to initialise the tutorial +# initialising the build directory with a tutorial exercise +./init --solution --tut %s +# building the tutorial exercise +cd %s_build +ninja +``` +''' % (name, name) diff --git a/tools/tutorialstate.py b/tools/tutorialstate.py new file mode 100644 index 0000000000..a05d2f2851 --- /dev/null +++ b/tools/tutorialstate.py @@ -0,0 +1,224 @@ +# +# Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) +# +# SPDX-License-Identifier: BSD-2-Clause +# + +from enum import Enum +import functools + +from capdl import AllocatorState, ObjectAllocator, AddressSpaceAllocator, CSpaceAllocator, ObjectType, lookup_architecture + + +class TaskContentType(Enum): + ''' + Task content type enum for describing when task content should be shown. + BEFORE refers to content that should be shown before a task has been completed. + COMPLETED refers to what the code should be when completed. Functions that + print tasks out will either print BEFORE or COMPLETED, but not both. + ALL is content that should be printed in both cases. + ''' + BEFORE = 1 + COMPLETED = 2 + ALL = 3 + + +@functools.total_ordering +class Task(object): + ''' + Data object representing a task. A task contains content to be rendered during + generation and completion text used to check if an assembled program is outputting + as expected. + subtasks are for describing different spacial locations where a task should output content + tasks that output data to the same location should have the same subtask referring to that data. + ''' + + def __init__(self, name, index): + self.index = index + self.name = name + self.subtask_content = {} + self.content = {} + self.completion = {} + + def __lt__(self, other): + return self.index < other.index + + def __eq__(self, other): + return self.index == other.index + + def set_content(self, content_type, content, subtask=None): + ''' + Set content of a certain content_type for a task or subtask + ''' + if subtask: + if subtask not in self.subtask_content: + self.subtask_content[subtask] = {} + self.subtask_content[subtask][content_type] = content + else: + self.content[content_type] = content + + def get_content(self, content_type, subtask=None): + ''' + Return the content for a particular subtask and content type + ''' + if subtask: + subtask_content = self.subtask_content.get(subtask) + return subtask_content.get(content_type) if subtask_content else None + return self.content.get(content_type) + + def set_completion(self, content_type, content): + ''' + Set completion text for a task. + It doesn't make sense for this to take a subtask + ''' + self.completion[content_type] = content + + def get_completion(self, content_type): + ''' + Get completion text for a task + ''' + return self.completion.get(content_type) + + +class TuteState(object): + ''' + Internal state of the tutorial that is being generated. + It is assumed that the jinja template is evaluated from top to bottom + like a program and that multiple templates are generated in a deterministic + ordering. Because of this, code snippets are built up as the templates are + processed and then used to generate tutorial files and other metadata. + + The state currently tracks the ordered list of tasks in the tutorial, + the additional files to process, the current task that the tutorial is + being rendered for and whether the rendering is in solution mode or not. + Solution mode means that the solution for the current task will be generated + instead of its starting state. Generally, the starting state of task 2 will be + the same as the solution state of task 1, but this may not always be the case. + ''' + + def __init__(self, current_task, solution_mode, arch, rt): + self.tasks = {} + self.additional_files = [] + self.current_task = current_task + self.solution = solution_mode + self.stash = Stash(arch, rt) + + def declare_tasks(self, task_names): + ''' + Declare the total tasks for the tutorial. + Currently this can only be called once and declare all tutorials in one go. + ''' + for (index, name) in enumerate(task_names): + self.tasks[name] = Task(name, index) + try: + self.current_task = self.tasks[self.current_task] + except KeyError: + if self.solution: + self.current_task = self.get_task_by_index(len(self.tasks) - 1) + else: + self.current_task = self.get_task_by_index(0) + return + + def get_task(self, name): + ''' + Get a Task based on its name + ''' + return self.tasks[name] + + def get_current_task(self): + ''' + Get a Task based on its name + ''' + return self.current_task + + def is_current_task(self, task): + ''' + Is a task the current task of the tutorial + ''' + return task == self.current_task + + def get_task_by_index(self, id): + ''' + Get a task by its index in the tutorial + ''' + for (k, v) in self.tasks.items(): + if v.index == id: + return v + return None + + def print_task(self, task, subtask=None): + ''' + Print a task or subtask content. + If solution mode is active, then the COMPLETED content will be returned + if the current_task is less than or equal to the provided task otherwise the BEFORE content + will be returned. If solution mode is not active, then the COMPLETED content will be returned + if the current_task is less than the provided task otherwise the BEFORE content will be returned. + ''' + if self.solution: + key = TaskContentType.COMPLETED if task <= self.current_task else TaskContentType.BEFORE + else: + key = TaskContentType.COMPLETED if task < self.current_task else TaskContentType.BEFORE + content = task.get_content(key, subtask) + if not content: + content = task.get_content(TaskContentType.ALL, subtask) + + return content + + def print_completion(self, content_type): + ''' + Return the completion text for a particular content type of the current task. + If a completion text hasn't been defined for the BEFORE stage of a task, use the + completion text from the COMPLETED part of the previous task + ''' + def task_get_completion(task, key): + ret = task.get_completion(key) + if not ret: + ret = task.get_completion(TaskContentType.ALL) + return ret + + key = content_type + completion = task_get_completion(self.current_task, key) + if not completion and key == TaskContentType.BEFORE: + assert self.current_task.index > 0 + completion = task_get_completion(self.get_task_by_index( + self.current_task.index-1), TaskContentType.COMPLETED) + # Reraise the error if we weren't requesting BEFORE. We require completion text defined for every stage + if not completion: + raise Exception("Failed to find completion for task {0}".format(self.current_task.name)) + return completion + + +class Stash(object): + def __init__(self, arch, rt): + self.rt = rt + objects = ObjectAllocator() + objects.spec.arch = arch + self.allocator_state = AllocatorState(objects) + self.current_cspace = None + self.current_addr_space = None + + # The following variables are for tracking symbols to render before compilation + self.elfs = {} + self.current_cap_symbols = [] + self.cap_symbols = {} + self.current_region_symbols = [] + self.region_symbols = {} + + def start_elf(self, name): + cnode = self.allocator_state.obj_space.alloc( + ObjectType.seL4_CapTableObject, "cnode_%s" % name) + arch = self.allocator_state.obj_space.spec.arch.capdl_name() + pd = self.allocator_state.obj_space.alloc( + lookup_architecture(arch).vspace().object, "vspace_%s" % name) + self.current_cspace = CSpaceAllocator(cnode) + self.current_addr_space = AddressSpaceAllocator(None, pd) + self.current_cap_symbols = [] + self.current_region_symbols = [] + + def finish_elf(self, name, filename): + self.allocator_state.addr_spaces[name] = self.current_addr_space + self.allocator_state.cspaces[name] = self.current_cspace + self.allocator_state.pds[name] = self.current_addr_space.vspace_root + self.elfs[name] = {"filename": filename} + self.cap_symbols[name] = self.current_cap_symbols + self.region_symbols[name] = self.current_region_symbols diff --git a/zynq_timer_driver/include/timer_driver/driver.h b/zynq_timer_driver/include/timer_driver/driver.h new file mode 100644 index 0000000000..47e32d6a87 --- /dev/null +++ b/zynq_timer_driver/include/timer_driver/driver.h @@ -0,0 +1,18 @@ +/* + * Copyright 2020, Data61, CSIRO (ABN 41 687 119 230). + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +typedef struct timer_drv { + uint32_t *reg_base; + int timer_id; +} timer_drv_t; + +int timer_init(timer_drv_t *timer_drv, int timer_id, void *reg_base); +int timer_set_timeout(timer_drv_t *timer_drv, uint64_t ns, bool periodic); +int timer_handle_irq(timer_drv_t *timer_drv); +int timer_start(timer_drv_t *timer_drv); +int timer_stop(timer_drv_t *timer_drv); diff --git a/zynq_timer_driver/src/driver.c b/zynq_timer_driver/src/driver.c new file mode 100644 index 0000000000..ba17b2f337 --- /dev/null +++ b/zynq_timer_driver/src/driver.c @@ -0,0 +1,173 @@ +/* + * Copyright 2020, Data61, CSIRO (ABN 41 687 119 230). + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* Basic driver for the TTC timers on the ZYNQ7000 */ + +#include + +#define CLK_CTRL_OFFSET 0x00 +#define CNT_CTRL_OFFSET 0x0C +#define CNT_VAL_OFFSET 0x18 +#define INTERVAL_OFFSET 0x24 +#define MATCH0_OFFSET 0x30 +#define INT_STS_OFFSET 0x54 +#define INT_EN_OFFSET 0x60 + +#define CLK_CTRL_PRESCALE_VAL(N) (((N) & 0xf) << 1) +#define CLK_CTRL_PRESCALE_ENABLE BIT(0) +#define CLK_CTRL_PRESCALE_MASK (CLK_CTRL_PRESCALE_VAL(0xf) | CLK_CTRL_PRESCALE_ENABLE) + +#define CNT_CTRL_STOP BIT(0) /* Stop the counter */ +#define CNT_CTRL_INT BIT(1) /* Set timer to interval mode */ +#define CNT_CTRL_MATCH BIT(3) /* Generate interrupt when counter matches value in match register */ +#define CNT_CTRL_RST BIT(4) /* Reset counter value and restart counting */ + +#define INT_EN_INTERVAL BIT(0) /* Enable interval interrupts */ +#define INT_EN_MATCH0 BIT(1) /* Enable match 1 interrupt */ + +#define INTERVAL_CNT_WIDTH 16 +#define INTERVAL_CNT_MAX (BIT(INTERVAL_CNT_WIDTH) - 1) + +#define PCLK_FREQ 111000000UL +#define PRESCALE_MAX 0xf + +static inline uint32_t timer_get_register(timer_drv_t *timer_drv, size_t offset) +{ + assert(timer_drv); + volatile uint32_t *target_reg = (void *) timer_drv->reg_base + offset + (timer_drv->timer_id * 4); + return (uint32_t)(*target_reg); +} + +static inline void timer_set_register(timer_drv_t *timer_drv, size_t offset, uint32_t value) +{ + assert(timer_drv); + volatile uint32_t *target_reg = (void *) timer_drv->reg_base + offset + (timer_drv->timer_id * 4); + *target_reg = value; +} + +static int timer_set_freq_for_ns(timer_drv_t *timer_drv, uint64_t ns, uint64_t *ret_interval) +{ + assert(ret_interval); + + uint64_t required_freq = freq_cycles_and_ns_to_hz(INTERVAL_CNT_MAX, ns); + + /* Find a prescale value that gets us close to the required frequency */ + int prescale = 0; + uint64_t curr_freq = PCLK_FREQ; + for (prescale = 0; curr_freq > required_freq; prescale++, curr_freq >>= 1); + if (prescale > PRESCALE_MAX) { + /* Couldn't find a suitable value for the prescale */ + return -1; + } + + /* Set the prescale */ + uint32_t to_set = timer_get_register(timer_drv, CLK_CTRL_OFFSET) & ~CLK_CTRL_PRESCALE_MASK; + if (prescale > 0) { + to_set |= CLK_CTRL_PRESCALE_ENABLE | CLK_CTRL_PRESCALE_VAL(prescale - 1); + } else { + to_set &= ~CLK_CTRL_PRESCALE_ENABLE; + } + timer_set_register(timer_drv, CLK_CTRL_OFFSET, to_set); + + *ret_interval = freq_ns_and_hz_to_cycles(ns, curr_freq); + assert(*ret_interval <= INTERVAL_CNT_MAX); + + return 0; +} + +static void timer_periodic_timeout(timer_drv_t *timer_drv, uint64_t counter_val) +{ + timer_set_register(timer_drv, INTERVAL_OFFSET, counter_val); + uint32_t cnt_ctrl = timer_get_register(timer_drv, CNT_CTRL_OFFSET); + cnt_ctrl |= CNT_CTRL_INT; + timer_set_register(timer_drv, CNT_CTRL_OFFSET, cnt_ctrl); + timer_set_register(timer_drv, INT_EN_OFFSET, INT_EN_INTERVAL); +} + +static void timer_oneshot_timeout(timer_drv_t *timer_drv, uint64_t counter_val) +{ + /* Set the match register to the interval that we calculated */ + uint32_t match_to_set = (counter_val + timer_get_register(timer_drv, CNT_VAL_OFFSET)) % BIT(INTERVAL_CNT_WIDTH); + timer_set_register(timer_drv, MATCH0_OFFSET, match_to_set); + + /* Turn off interval interrupts and turn on the match interrupt */ + uint32_t cnt_ctrl = timer_get_register(timer_drv, CNT_CTRL_OFFSET); + cnt_ctrl &= ~CNT_CTRL_INT; + timer_set_register(timer_drv, CNT_CTRL_OFFSET, cnt_ctrl); + timer_set_register(timer_drv, INT_EN_OFFSET, INT_EN_MATCH0); +} + +int timer_set_timeout(timer_drv_t *timer_drv, uint64_t ns, bool periodic) +{ + assert(timer_drv); + uint64_t counter_interval = 0; + + int error = timer_set_freq_for_ns(timer_drv, ns, &counter_interval); + if (error) { + return -1; + } + + if (periodic) { + timer_periodic_timeout(timer_drv, counter_interval); + } else { + timer_oneshot_timeout(timer_drv, counter_interval); + } + + return 0; +} + +int timer_handle_irq(timer_drv_t *timer_drv) +{ + assert(timer_drv); + /* Read the interrupt status register to clear the interrupt bit */ + FORCE_READ(timer_drv->reg_base + (INT_STS_OFFSET / sizeof(uint32_t))); + + uint32_t int_en = timer_get_register(timer_drv, INT_EN_OFFSET); + int_en &= ~INT_EN_MATCH0; + timer_set_register(timer_drv, INT_EN_OFFSET, int_en); + + return 0; +} + +int timer_start(timer_drv_t *timer_drv) +{ + assert(timer_drv); + uint32_t cnt_ctrl = timer_get_register(timer_drv, CNT_CTRL_OFFSET); + cnt_ctrl &= ~CNT_CTRL_STOP; + timer_set_register(timer_drv, CNT_CTRL_OFFSET, cnt_ctrl); + return 0; +} + +int timer_stop(timer_drv_t *timer_drv) +{ + assert(timer_drv); + uint32_t cnt_ctrl = timer_get_register(timer_drv, CNT_CTRL_OFFSET); + cnt_ctrl |= CNT_CTRL_STOP; + timer_set_register(timer_drv, CNT_CTRL_OFFSET, cnt_ctrl); + return 0; +} + +int timer_init(timer_drv_t *timer_drv, int timer_id, void *reg_base) +{ + assert(timer_drv); + assert(reg_base); + assert(0 <= timer_id && timer_id <= 2); + + timer_drv->reg_base = reg_base; + timer_drv->timer_id = timer_id; + + timer_set_register(timer_drv, INT_EN_OFFSET, 0); + FORCE_READ(timer_drv->reg_base + (INT_STS_OFFSET / sizeof(uint32_t))); /* Force a read to clear the register */ + uint32_t set_value = CNT_CTRL_STOP | CNT_CTRL_INT | CNT_CTRL_MATCH | CNT_CTRL_RST; + timer_set_register(timer_drv, CNT_CTRL_OFFSET, set_value); + timer_set_register(timer_drv, CLK_CTRL_OFFSET, set_value); + set_value = INT_EN_INTERVAL; + timer_set_register(timer_drv, INT_EN_OFFSET, set_value); + set_value = INTERVAL_CNT_MAX; + timer_set_register(timer_drv, INTERVAL_OFFSET, INTERVAL_CNT_MAX); + + return 0; +}