diff --git a/CMakeLists.txt b/CMakeLists.txt index a282b51163b77..01f6c65254291 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -413,6 +413,13 @@ add_subdirectory(subsys) add_subdirectory(drivers) add_subdirectory(tests) +# Add all generated zephyr sources in the same context as the zephyr library +# is created. Assure all sub directories that might invoke code generation +# are processed before. +get_property(zephyr_generated_sources GLOBAL PROPERTY zephyr_generated_sources_property) +set_source_files_properties(${zephyr_generated_sources} PROPERTIES GENERATED 1) +target_sources(zephyr PRIVATE ${zephyr_generated_sources}) + set(syscall_macros_h ${ZEPHYR_BINARY_DIR}/include/generated/syscall_macros.h) add_custom_target(syscall_macros_h_target DEPENDS ${syscall_macros_h}) diff --git a/boards/arm/96b_argonkey/96b_argonkey.dts b/boards/arm/96b_argonkey/96b_argonkey.dts index 873dcc35456e9..6662593a84811 100644 --- a/boards/arm/96b_argonkey/96b_argonkey.dts +++ b/boards/arm/96b_argonkey/96b_argonkey.dts @@ -63,6 +63,7 @@ compatible = "st,lsm6dsl-spi"; reg = <1>; spi-max-frequency = <1000000>; + cs-gpios = <&gpioc 2 0>; irq-gpios = <&gpiob 1 0>; label = "LSM6DSL_SPI"; }; @@ -75,6 +76,13 @@ &i2c1 { status = "ok"; clock-frequency = ; + + /* ST Microelectronics LSM6DSL accel/gyro sensor */ + lsm6dsl@1 { + compatible = "st,lsm6dsl"; + reg = <1>; + label = "LSM6DSL_I2C"; + }; }; &i2c2 { diff --git a/cmake/extensions.cmake b/cmake/extensions.cmake index 5d50d9ea45094..6bea301213fe2 100644 --- a/cmake/extensions.cmake +++ b/cmake/extensions.cmake @@ -304,6 +304,191 @@ macro(get_property_and_add_prefix result target property prefix) endforeach() endmacro() +# 1.3 generate_inc_* + +# These functions are useful if there is a need to generate a file +# that can be included into the application at build time. The file +# can also be compressed automatically when embedding it. +# +# See tests/application_development/gen_inc_file for an example of +# usage. +function(generate_inc_file + source_file # The source file to be converted to hex + generated_file # The generated file + ) + add_custom_command( + OUTPUT ${generated_file} + COMMAND + ${PYTHON_EXECUTABLE} + ${ZEPHYR_BASE}/scripts/file2hex.py + ${ARGN} # Extra arguments are passed to file2hex.py + --file ${source_file} + > ${generated_file} # Does pipe redirection work on Windows? + DEPENDS ${source_file} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) +endfunction() + +function(generate_inc_file_for_gen_target + target # The cmake target that depends on the generated file + source_file # The source file to be converted to hex + generated_file # The generated file + gen_target # The generated file target we depend on + # Any additional arguments are passed on to file2hex.py + ) + generate_inc_file(${source_file} ${generated_file} ${ARGN}) + + # Ensure 'generated_file' is generated before 'target' by creating a + # dependency between the two targets + + add_dependencies(${target} ${gen_target}) +endfunction() + +function(get_unique_generated_target_name + generated_file # The generated file + generated_target_name # the unique name + ) + string(RANDOM LENGTH 8 random_chars) + get_filename_component(basename ${generated_file} NAME) + string(REPLACE "." "_" basename ${basename}) + string(REPLACE "@" "_" basename ${basename}) + set(generated_target_name "gen_${basename}_${random_chars}" PARENT_SCOPE) +endfunction() + +function(generate_inc_file_for_target + target # The cmake target that depends on the generated file + source_file # The source file to be converted to hex + generated_file # The generated file + # Any additional arguments are passed on to file2hex.py + ) + # Ensure 'generated_file' is generated before 'target' by creating a + # 'custom_target' for it and setting up a dependency between the two + # targets + get_unique_generated_target_name(${generated_file} generated_target_name) + add_custom_target(${generated_target_name} DEPENDS ${generated_file}) + generate_inc_file_for_gen_target(${target} ${source_file} ${generated_file} + ${generated_target_name} ${ARGN}) +endfunction() + +function(target_sources_codegen + target # The cmake target that depends on the generated file + ) + set(options) + set(oneValueArgs) + set(multiValueArgs CODEGEN_DEFINES DEPENDS) + cmake_parse_arguments(CODEGEN "${options}" "${oneValueArgs}" + "${multiValueArgs}" ${ARGN}) + # prepend -D to all defines + string(REGEX REPLACE "([^;]+)" "-D;\\1" + CODEGEN_CODEGEN_DEFINES "${CODEGEN_CODEGEN_DEFINES}") + # Get all the files that make up codegen for dependency + file(GLOB CODEGEN_SOURCES + ${ZEPHYR_BASE}/scripts/dts/edtsdevice.py + ${ZEPHYR_BASE}/scripts/dts/edtsdatabase.py + ${ZEPHYR_BASE}/scripts/codegen/modules/*.py + ${ZEPHYR_BASE}/scripts/codegen/templates/drivers/*.py + ${ZEPHYR_BASE}/scripts/codegen/*.py + ${ZEPHYR_BASE}/scripts/gen_code.py) + + message(STATUS "Will generate for target ${target}") + # Generated file must be generated to the current binary directory. + # Otherwise this would trigger CMake issue #14633: + # https://gitlab.kitware.com/cmake/cmake/issues/14633 + foreach(arg ${CODEGEN_UNPARSED_ARGUMENTS}) + get_filename_component(generated_file_name ${arg} NAME_WE) + if(IS_ABSOLUTE ${arg}) + set(template_file ${arg}) + set(generated_file ${CMAKE_CURRENT_BINARY_DIR}/${generated_file_name}_devices.c) + else() + set(template_file ${CMAKE_CURRENT_SOURCE_DIR}/${generated_file_name}_devices.c.in) + set(generated_file ${CMAKE_CURRENT_BINARY_DIR}/${generated_file_name}_devices.c) + endif() + get_filename_component(template_dir ${template_file} DIRECTORY) + get_filename_component(generated_dir ${generated_file} DIRECTORY) + + if(IS_DIRECTORY ${template_file}) + message(FATAL_ERROR "target_sources_codegen() was called on a directory") + endif() + + # Generate file from template + message(STATUS " from '${template_file}'") + message(STATUS " to '${generated_file}'") + execute_process( + COMMAND + ${PYTHON_EXECUTABLE} + ${ZEPHYR_BASE}/scripts/gen_code.py + ${CODEGEN_CODEGEN_DEFINES} + -D "PROJECT_NAME=${PROJECT_NAME}" + -D "PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + -D "PROJECT_BINARY_DIR=${PROJECT_BINARY_DIR}" + -D "CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}" + -D "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}" + -D "CMAKE_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}" + -D "CMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}" + -D "CMAKE_CURRENT_LIST_DIR=${CMAKE_CURRENT_LIST_DIR}" + -D "CMAKE_FILES_DIRECTORY=${CMAKE_FILES_DIRECTORY}" + -D "CMAKE_PROJECT_NAME=${CMAKE_PROJECT_NAME}" + -D "CMAKE_SYSTEM=${CMAKE_SYSTEM}" + -D "CMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}" + -D "CMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}" + -D "CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}" + -D "CMAKE_C_COMPILER=${CMAKE_C_COMPILER}" + -D "CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + -D "CMAKE_COMPILER_IS_GNUCC=${CMAKE_COMPILER_IS_GNUCC}" + -D "CMAKE_COMPILER_IS_GNUCXX=${CMAKE_COMPILER_IS_GNUCXX}" + -D "GENERATED_DTS_BOARD_H=${GENERATED_DTS_BOARD_H}" + -D "GENERATED_DTS_BOARD_CONF=${GENERATED_DTS_BOARD_CONF}" + -D "GENERATED_EDTS=${GENERATED_EDTS}" + --input "${template_file}" + --output "${generated_file}" + --log "${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/CodeGen.log" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + if("${target}" STREQUAL "zephyr") + # zephyr is special + get_unique_generated_target_name(${generated_file} generated_target_name) + add_custom_target(${generated_target_name} DEPENDS ${generated_file}) + add_dependencies(zephyr ${generated_target_name}) + # Remember all the files that are generated for zephyr. + # target_sources(zephyr PRIVATE ${zephyr_generated_sources}) + # is executed in the top level CMakeFile.txt context. + get_property(zephyr_generated_sources GLOBAL PROPERTY zephyr_generated_sources_property) + list(APPEND zephyr_generated_sources ${generated_file}) + set_property(GLOBAL PROPERTY zephyr_generated_sources_property "${zephyr_generated_sources}") + # Add template directory to include path to allow includes with + # relative path in generated file to work + zephyr_include_directories(${template_dir}) + else() + zephyr_include_directories(${generated_dir}) + target_sources(${target} PRIVATE ${arg}) + + # Add template directory to include path to allow includes with + # relative path in generated file to work + target_include_directories(${target} PRIVATE ${template_dir}) + endif() + endforeach() +endfunction() + +function(zephyr_sources_codegen) + target_sources_codegen(zephyr ${ARGN}) +endfunction() + +function(zephyr_sources_codegen_ifdef feature_toggle) + if(${${feature_toggle}}) + zephyr_sources_codegen(${ARGN}) + endif() +endfunction() + +function(zephyr_library_sources_codegen) + target_sources_codegen(${ZEPHYR_CURRENT_LIBRARY} ${ARGN}) +endfunction() + +function(zephyr_library_sources_codegen_ifdef feature_toggle) + if(${${feature_toggle}}) + zephyr_library_sources_codegen(${ARGN}) + endif() +endfunction() + # 1.2 zephyr_library_* # # Zephyr libraries use CMake's library concept and a set of @@ -357,6 +542,13 @@ macro(zephyr_library_named name) # This is a macro because we need add_library() to be executed # within the scope of the caller. set(ZEPHYR_CURRENT_LIBRARY ${name}) + + if("${name}" STREQUAL "zephyr") + # We have to mark all the generated files "GENERATED" in this context + get_property(zephyr_generated_files GLOBAL PROPERTY zephyr_generated_files_property) + set_source_files_properties(${zephyr_generated_files} PROPERTIES GENERATED 1) + endif() + add_library(${name} STATIC "") zephyr_append_cmake_library(${name}) diff --git a/doc/contribute/contribute_guidelines.rst b/doc/contribute/contribute_guidelines.rst index c362bb3a05d0e..d044cc556059a 100644 --- a/doc/contribute/contribute_guidelines.rst +++ b/doc/contribute/contribute_guidelines.rst @@ -326,8 +326,11 @@ On Linux systems, you can install uncrustify with For Windows installation instructions see the `sourceforge listing for uncrustify `_. +Best coding practises +********************* + Coding Style -************ +============ Use these coding guidelines to ensure that your development complies with the project's style and naming conventions. @@ -363,6 +366,25 @@ it to contain: set -e exec exec git diff --cached | ${ZEPHYR_BASE}/scripts/checkpatch.pl - +Keep the code simple +==================== + +Keep the code as simple as possible. + +Code-generation preprocessing tools provide a convenient way to +simplify some repetitive or parameterized coding tasks. While Zephyr +development allows use of such tools, we recommend keeping this +use to a minimum and when it provides an appropriate and simple +coding solution that follows these rules: + +* Use code generation - by preprocessor, :ref:`codegen`, or other - only for + problems that cannot be solved in the source language. +* Limit code generation to declarative data. Avoid generating control logic + whenever possible. +* Use the preprocessor for code generation as the primary tool. +* Use :ref:`codegen` only if the preprocessor can not provide a simple solution. +* Use CMake only if :ref:`codegen` can not be used. + .. _Contribution workflow: Contribution Workflow diff --git a/doc/subsystems/codegen/build.rst b/doc/subsystems/codegen/build.rst new file mode 100644 index 0000000000000..4ecc1ccf359b8 --- /dev/null +++ b/doc/subsystems/codegen/build.rst @@ -0,0 +1,29 @@ +.. + Copyright (c) 2018 Bobby Noelte + SPDX-License-Identifier: Apache-2.0 + +.. _codegen_build: + +Code Generation in the Build Process +#################################### + +Code generation has to be invoked as part of the build process. Zephyr uses +`CMake `_ as the tool to manage building the project. + +A file that contains inline code generation has to be added to the project +by one of the following commands in a :file:`CMakeList.txt` file. + +.. function:: zephyr_sources_codegen(codegen_file.c [CODEGEN_DEFINES defines..] [DEPENDS target.. file..]) + +.. function:: zephyr_sources_codegen_ifdef(ifguard codegen_file.c [CODEGEN_DEFINES defines..] [DEPENDS target.. file..]) + +.. function:: zephyr_library_sources_codegen(codegen_file.c [CODEGEN_DEFINES defines..] [DEPENDS target.. file..]) + +.. function:: zephyr_library_sources_codegen_ifdef(ifguard codegen_file.c [CODEGEN_DEFINES defines..] [DEPENDS target.. file..]) + +The arguments given by the ``CODEGEN_DEFINES`` keyword have to be of the form +``define_name=define_value``. The arguments become globals in the python +snippets and can be accessed by ``define_name``. + +Dependencies given by the ``DEPENDS`` key word are added to the dependencies +of the generated file. diff --git a/doc/subsystems/codegen/codegen.rst b/doc/subsystems/codegen/codegen.rst new file mode 100644 index 0000000000000..cc7b1bb3eed38 --- /dev/null +++ b/doc/subsystems/codegen/codegen.rst @@ -0,0 +1,90 @@ +.. + Copyright (c) 2004-2015 Ned Batchelder + SPDX-License-Identifier: MIT + Copyright (c) 2018 Bobby Noelte + SPDX-License-Identifier: Apache-2.0 + +.. _codegen_intro: + +Introduction +############ + +Python snippets that are inlined in a source file are used as code generators. +The tool to scan the source file for the Python snippets and process them is +Codegen. Codegen and part of this documentation is based on +`Cog `_ from Ned Batchelder. + +The processing of source files is controlled by the CMake extension functions: +zephyr_sources_codegen(..) or zephyr_library_sources_codegen(..). The generated +source files are added to the Zephyr sources. During build the source files are +processed by Codegen and the generated source files are written to the CMake +binary directory. + +The inlined Python snippets can contain any Python code, they are regular +Python scripts. All Python snippets in a source file and all Python snippets of +included template files are treated as a python script with a common set of +global Python variables. Global data created in one snippet can be used in +another snippet that is processed later on. This feature could be used, for +example, to customize included template files. + +An inlined Python snippet can always access the codegen module. The codegen +module encapsulates and provides all the functions to retrieve information +(options, device tree properties, CMake variables, config properties) and to +output the generated code. + +Codegen transforms files in a very simple way: it finds chunks of Python code +embedded in them, executes the Python code, and places its output combined with +the original file into the generated file. The original file can contain +whatever text you like around the Python code. It will usually be source code. + +For example, if you run this file through Codegen: + +:: + + /* This is my C file. */ + ... + /** + * @code{.codegen} + * fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] + * for fn in fnames: + * codegen.outl("void %s();" % fn) + * @endcode{.codegen} + */ + /** @code{.codeins}@endcode */ + ... + +it will come out like this: + +:: + + /* This is my C file. */ + ... + /** + * @code{.codegen} + * fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] + * for fn in fnames: + * codegen.outl("void %s();" % fn) + * @endcode{.codegen} + */ + void DoSomething(); + void DoAnotherThing(); + void DoLastThing(); + /** @code{.codeins}@endcode */ + ... + +Lines with ``@code{.codegen}`` or ``@code{.codeins}@endcode`` are marker lines. +The lines between ``@code{.codegen}`` and ``@endcode{.codegen}`` are the +generator Python code. The lines between ``@endcode{.codegen}`` and +``@code{.codeins}@endcode`` are the output from the generator. + +When Codegen runs, it discards the last generated Python output, executes the +generator Python code, and writes its generated output into the file. All text +lines outside of the special markers are passed through unchanged. + +The Codegen marker lines can contain any text in addition to the marker tokens. +This makes it possible to hide the generator Python code from the source file. + +In the sample above, the entire chunk of Python code is a C comment, so the +Python code can be left in place while the file is treated as C code. + + diff --git a/doc/subsystems/codegen/codegen_principle.png b/doc/subsystems/codegen/codegen_principle.png new file mode 100644 index 0000000000000..b1f340731f3ac Binary files /dev/null and b/doc/subsystems/codegen/codegen_principle.png differ diff --git a/doc/subsystems/codegen/codegen_principle_access.png b/doc/subsystems/codegen/codegen_principle_access.png new file mode 100644 index 0000000000000..e0319d463cd8a Binary files /dev/null and b/doc/subsystems/codegen/codegen_principle_access.png differ diff --git a/doc/subsystems/codegen/codegen_principle_import.png b/doc/subsystems/codegen/codegen_principle_import.png new file mode 100644 index 0000000000000..e8c681833a0b9 Binary files /dev/null and b/doc/subsystems/codegen/codegen_principle_import.png differ diff --git a/doc/subsystems/codegen/codegen_principle_include.png b/doc/subsystems/codegen/codegen_principle_include.png new file mode 100644 index 0000000000000..1b58b41dd0098 Binary files /dev/null and b/doc/subsystems/codegen/codegen_principle_include.png differ diff --git a/doc/subsystems/codegen/functions.rst b/doc/subsystems/codegen/functions.rst new file mode 100644 index 0000000000000..45b56c1ab39e2 --- /dev/null +++ b/doc/subsystems/codegen/functions.rst @@ -0,0 +1,216 @@ +.. + Copyright (c) 2004-2015 Ned Batchelder + SPDX-License-Identifier: MIT + Copyright (c) 2018 Bobby Noelte + SPDX-License-Identifier: Apache-2.0 + +.. _codegen_functions: + +Code Generation Functions +######################### + +A module called ``codegen`` provides the core functions for inline +code generation. It encapsulates all the functions to retrieve information +(options, device tree properties, CMake variables, config properties) and +to output the generated code. + +.. contents:: + :depth: 2 + :local: + :backlinks: top + +The ``codegen`` module is automatically imported by all code snippets. No +explicit import is necessary. + +Output +------ + +.. function:: codegen.out(sOut=’’ [, dedent=False][, trimblanklines=False]) + + Writes text to the output. + + :param sOut: The string to write to the output. + :param dedent: If dedent is True, then common initial white space is + removed from the lines in sOut before adding them to the + output. + :param trimblanklines: If trimblanklines is True, + then an initial and trailing blank line are removed + from sOut before adding them to the output. + + ``dedent`` and ``trimblanklines`` make it easier to use + multi-line strings, and they are only are useful for multi-line strings: + + :: + + codegen.out(""" + These are lines I + want to write into my source file. + """, dedent=True, trimblanklines=True) + +.. function:: codegen.outl + + Same as codegen.out, but adds a trailing newline. + +.. attribute:: codegen.inFile + + An attribute, the path of the input file. + +.. attribute:: codegen.outFile + + An attribute, the path of the output file. + +.. attribute:: codegen.firstLineNum + + An attribute, the line number of the first line of Python code in the + generator. This can be used to distinguish between two generators in the + same input file, if needed. + +.. attribute:: codegen.previous + + An attribute, the text output of the previous run of this generator. This + can be used for whatever purpose you like, including outputting again with + codegen.out() + +The codegen module also provides a set of convenience functions: + + +Code generation module import +----------------------------- + +.. function:: codegen.module_import(module_name) + + Import a module from the codegen/modules package. + + After import the module's functions and variables can be accessed by + module_name.func() and module_name.var. + + :param module_name: Module to import. Specified without any path. + + See :ref:`codegen_modules` for the available modules. + +Template file inclusion +----------------------- + +.. function:: codegen.out_include(include_file) + + Write the text from include_file to the output. The :file:`include_file` + is processed by Codegen. Inline code generation in ``include_file`` + can access the globals defined in the ``including source file`` before + inclusion. The ``including source file`` can access the globals defined in + the ``include_file`` (after inclusion). + + :param include_file: path of include file, either absolute path or relative + to current directory or relative to templates directory + (e.g. 'templates/drivers/simple_tmpl.c') + + See :ref:`codegen_templates` for the templates in the Codegen templates + folders. + +.. function:: codegen.guard_include() + + Prevent the current file to be included by ``codegen.out_include()`` + when called the next time. + +Configuration property access +----------------------------- + +.. function:: codegen.config_property(property_name [, default=""]) + + Get the value of a configuration property from :file:`autoconf.h`. If + ``property_name`` is not given in :file:`autoconf.h` the default value is + returned. + +CMake variable access +--------------------- + +.. function:: codegen.cmake_variable(variable_name [, default=""]) + + Get the value of a CMake variable. If variable_name is not provided to + Codegen by CMake the default value is returned. The following variables + are provided to Codegen: + + - "PROJECT_NAME" + - "PROJECT_SOURCE_DIR" + - "PROJECT_BINARY_DIR" + - "CMAKE_SOURCE_DIR" + - "CMAKE_BINARY_DIR" + - "CMAKE_CURRENT_SOURCE_DIR" + - "CMAKE_CURRENT_BINARY_DIR" + - "CMAKE_CURRENT_LIST_DIR" + - "CMAKE_FILES_DIRECTORY" + - "CMAKE_PROJECT_NAME" + - "CMAKE_SYSTEM" + - "CMAKE_SYSTEM_NAME" + - "CMAKE_SYSTEM_VERSION" + - "CMAKE_SYSTEM_PROCESSOR" + - "CMAKE_C_COMPILER" + - "CMAKE_CXX_COMPILER" + - "CMAKE_COMPILER_IS_GNUCC" + - "CMAKE_COMPILER_IS_GNUCXX" + - "GENERATED_DTS_BOARD_H" + - "GENERATED_DTS_BOARD_CONF" + +.. function:: codegen.cmake_cache_variable(variable_name [, default=""]) + + Get the value of a CMake variable from CMakeCache.txt. If variable_name + is not given in CMakeCache.txt the default value is returned. + +Extended device tree database access +------------------------------------ + +.. function:: codegen.edts() + + Get the extended device tree database. + + :return: extended device tree database + +Guarding chunks of source code +------------------------------ + +.. function:: codegen.outl_guard_config(property_name) + + Write a guard (#if [guard]) C preprocessor directive to output. + + If there is a configuration property of the given name the property value + is used as guard value, otherwise it is set to 0. + + :param property_name: Name of the configuration property. + +.. function:: codegen.outl_unguard_config(property_name) + + Write an unguard (#endif) C preprocessor directive to output. + + This is the closing command for codegen.outl_guard_config(). + + :param property_name: Name of the configuration property. + +Error handling +-------------- + +.. function:: codegen.error(msg='Error raised by codegen.' [, frame_index=0] [, snippet_lineno=0]) + + Raise a codegen.Error exception. + + Instead of raising standard python errors, codegen generators can use + this function. Extra information is added that maps the python snippet + line seen by the Python interpreter to the line of the file that inlines + the python snippet. + + :param msg: Exception message. + :param frame_index: Call frame index. The call frame offset of the function + calling codegen.error(). Zero if directly called in a + snippet. Add one for every level of function call. + :param snippet_lineno: Line number within snippet. + +Logging +------- + +.. function:: codegen.log(message [, message_type=None] [, end="\n"] [, logonly=True]) + +.. function:: codegen.msg(msg) + + Prints msg to stdout with a “Message: ” prefix. + +.. function:: codegen.prout(s [, end="\n"]) + +.. function:: codegen.prerr(s [, end="\n"]) diff --git a/doc/subsystems/codegen/index.rst b/doc/subsystems/codegen/index.rst new file mode 100644 index 0000000000000..129749e02530d --- /dev/null +++ b/doc/subsystems/codegen/index.rst @@ -0,0 +1,29 @@ +.. + Copyright (c) 2018 Bobby Noelte + SPDX-License-Identifier: Apache-2.0 + +.. _codegen: + +Inline Code Generation +###################### + +For some repetitive or parameterized coding tasks, it's convenient to +use a code generating tool to build C code fragments, instead of writing +(or editing) that source code by hand. Such a tool can also access CMake build +parameters and device tree information to generate source code automatically +tailored and tuned to a specific project configuration. + +The Zephyr project supports a code generating tool that processes embedded +Python "snippets" inlined in your source files. It can be used, for example, +to generate source code that creates and fills data structures, adapts +programming logic, creates configuration-specific code fragments, and more. + +.. toctree:: + :maxdepth: 1 + + codegen + functions + modules + templates + build + principle diff --git a/doc/subsystems/codegen/modules.rst b/doc/subsystems/codegen/modules.rst new file mode 100644 index 0000000000000..3a53d5d8ad5e2 --- /dev/null +++ b/doc/subsystems/codegen/modules.rst @@ -0,0 +1,322 @@ +.. + Copyright (c) 2018 Bobby Noelte + SPDX-License-Identifier: Apache-2.0 + +.. _codegen_modules: + +Code Generation Modules +####################### + +Code generation modules provide supporting functions for code generation. + +.. contents:: + :depth: 2 + :local: + :backlinks: top + +Modules have to be imported to gain access to the module's functions +and variables. + + :: + + /* This file uses modules. */ + ... + /** + * @code{.codegen} + * codegen.import_module('my_special_module') + * my_special_module.do_everything(): + * @endcode{.codegen} + */ + /** @code{.codeins}@endcode */ + ... + +Device declare module +********************* + +:: + + codegen.import_module('devicedeclare') + +The devicedeclare module provides functions to generate driver device +instantiations. + +Driver info templates +--------------------- + +The device declaration functions work on templates that feature placeholder +substitution. + +Device instance property placeholders: + +- ${device-name}: device instance name. + Name is generated by the declaration function. +- ${driver-name}: device instance driver name. + Name is taken from the device tree node property 'label'. +- ${device-data}: device instance data structure name. + Name is generated by the declaration function. +- ${device-config-info}: device instance configuration structure name. + Name is generated by the declaration function. +- ${device-config-irq}: device instance interrupt configuration function name. + Name is generated by the declaration function. + +Device instance device tree property placeholders: + +* ${[path to DTS property]}: device instance device tree node property. + The property path supports every node property that is documented in the + node yaml bindings. It also supports yaml heuristics, like 'bus-master' and + will use documented '"#cells"'. + +Device tree property placeholders: + +- ${[device id]:[path to DTS property]}: device node property value. + The device node property is defined by the property path of the device + given by the device id. + The device id is usually also taken from a DTS property e.g. + ${${clocks/0/provider}:device-name}. + +KConfig configuration parameter placeholders: + +- ${CONFIG_[configuration parameter]}: KConfig configuration parameter value. + +Declaration of device instances +------------------------------- + +.. function:: devicedeclare.device_declare(compatibles, init_prio_flag, kernel_level, irq_func, init_func, api, data_struct, config_struct) + + Generate device instances code for all devices activated ('status' = 'ok') + in the board device tree file matching the provided compatibles. + + Most of the parameters aim at filling the DEVICE_AND_API_INIT macro. + Other parameters are there to help code generation to fit driver specifics. + + Instance code will only be generated if the Kconfig variable is set. The + variable name is build with the device node label name (e.g: CONFIG_I2C_1). + + :param compatibles: List of compatibles supported by the driver + (e.g. ['st,stm32-spi-fifo', 'st,stm32-spi']) + :param init_prio_flag: Flag for driver activation priority + (e.g. CONFIG_KERNEL_INIT_PRIORITY_DEVICE) + :param kernel_level: Flag for driver activation priority (e.g. POST_KERNEL) + :param irq_func: Two elements python dict providing driver isr function + prefix (e.g. 'irq_func' \: 'stm32_i2c_isr') and flag to be + used for driver IRQ code control + (e.g. 'irq_flag' \: 'CONFIG_I2C_STM32_INTERRUPT'). + If irq_func is 'None', no IRQ code is generated. + 'device_declare' will generate as much IRQ code as + declared by device node. + If the 'interrupts-names' property is provided in the node, + isr names will be generated using matching values as + function postfixes. + :param init_func: Name of the driver init function (e.g. 'i2c_stm32_init'). + :param api: Name of the driver api structure (e.g. 'api_funcs'). + :param data_struct: Two elements python list providing elements for driver + '_data' structure generation. + :param config_struct: Two elements python list providing elements for driver + '_config' structure generation. + +'data_struct' and 'config_struct' will be processed the same way: + +* First element (e.g. 'i2c_stm32_config') should be the structure name. +* Second element is a 'c' template code between triple double quotes (""" """). + It should provide the expected code to be generated for the structure. + For instance: + +.. code-block:: python + + """ + .i2c = (I2C_TypeDef *)${reg/0/address/0}, + .pclken = { + .enr = ${clocks/0/bits}, + .bus = ${clocks/0/bus}, + }, + #ifdef CONFIG_I2C_STM32_INTERRUPT + .irq_config_func = st_stm32_i2c_v1_${node_index}_config_irq, + #endif + .bitrate = ${clock-frequency}, + """ + +If the second element of 'data_struct' or 'config_struct' list is not provided, +an empty structure is generated. + +Finally, for the above depicted example, 'device_declare' will generate, +for device instance 'I2C1': + +.. code-block:: c + + #ifdef CONFIG_I2C_STM32_INTERRUPT + DEVICE_DECLARE(st_stm32_i2c_v1_i2c_1); + static void st_stm32_i2c_v1_i2c_1_config_irq(struct device *dev) + { + IRQ_CONNECT(31, + 0, + stm32_i2c_isr_event, + DEVICE_GET(st_stm32_i2c_v1_i2c_1), + 0); + irq_enable(31); + IRQ_CONNECT(32, + 0, + stm32_i2c_isr_error, + DEVICE_GET(st_stm32_i2c_v1_i2c_1), + 0); + irq_enable(32); + } + #endif /* CONFIG_I2C_STM32_INTERRUPT */ + + static const struct i2c_stm32_config st_stm32_i2c_v1_i2c_1_config = { + .i2c = (I2C_TypeDef *)0x40005400, + .pclken = { + .enr = 131072, + .bus = 2, + }, + #ifdef CONFIG_I2C_STM32_INTERRUPT + .irq_config_func = st_stm32_i2c_v1_i2c_1_config_irq, + #endif + .bitrate = 400000, + }; + + static struct i2c_stm32_data st_stm32_i2c_v1_i2c_1_data = {}; + + DEVICE_AND_API_INIT(st_stm32_i2c_v1_i2c_1, + "I2C_1", + i2c_stm32_init, + &st_stm32_i2c_v1_i2c_1_data, + &st_stm32_i2c_v1_i2c_1_config, + POST_KERNEL, + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &api_funcs); + +Raw declaration of a single device instance +------------------------------------------- + +.. function:: devicedeclare.device_declare_single(device_config, driver_name, device_init, device_levels device_prio, device_api, device_info) + + Generate device instances code for a device instance that: + + - matches the driver name and that + - is activated ('status' = 'ok') in the board device tree file and that is + - configured by Kconfig. + + The ``device_declare_single`` function enables a detailed control of the + device info definition. It's primary use is for complex device instance + initialisation that can not be accomplished by ``device_declare``. + + :param device_config: + Configuration variables for device instantiation. + (e.g. 'CONFIG_SPI_0') + :param driver_name: + Driver name for device instantiation. + (e.g. 'SPI_0') + :param device_init: + Device initialisation function. + (e.g. 'spi_stm32_init') + :param device_level: + Driver initialisation level. + (e.g. 'PRE_KERNEL_1') + :param device_prios: + Driver initialisation priority definition. + (e.g. 32) + :param device_api: + Identifier of the device api. + (e.g. 'spi_stm32_driver_api') + :param device_info: + Device info template for device driver config, data and interrupt + initialisation. + :param device_defaults: + Default property values. + (e.g. { 'label' : 'My default label' }) + +Raw declaration of multiple device instances +-------------------------------------------- + +.. function:: devicedeclare.device_declare_multi(device_configs, driver_names, device_inits, device_levels, device_prios, device_api, device_info) + + Generate device instances code for all device instances that: + + - match the driver names and that + - are activated ('status' = 'ok') in the board device tree file and that are + - configured by Kconfig. + + The ``device_declare_multi`` function enables a detailed control of the + device info definition. It's primary use is for complex device instance + initialisation that can not be accomplished by ``device_declare``. + + :param device_configs: + A list of configuration variables for device instantiation. + (e.g. ['CONFIG_SPI_0', 'CONFIG_SPI_1']) + :param driver_names: + A list of driver names for device instantiation. The list shall be + ordered the same way as the list of device configs. + (e.g. ['SPI_0', 'SPI_1']) + :param device_inits: + A list of device initialisation functions or a one single function. + The list shall be ordered as the list of device configs. + (e.g. 'spi_stm32_init') + :param device_levels: + A list of driver initialisation levels or one single level definition. + The list shall be ordered as the list of device configs. + (e.g. 'PRE_KERNEL_1') + :param device_prios: + A list of driver initialisation priorities or one single priority + definition. The list shall be ordered as the list of device configs. + (e.g. 32) + :param device_api: + Identifier of the device api. + (e.g. 'spi_stm32_driver_api') + :param device_info: + Device info template for device driver config, data and interrupt + initialisation. + :param device_defaults: + Default property values. + (e.g. { 'label' : 'My default label' }) + +Example: + +.. code-block:: C + + /** + * @code{.codegen} + * codegen.import_module('devicedeclare') + * + * device_configs = ['CONFIG_SPI_{}'.format(x) for x in range(1, 4)] + * driver_names = ['SPI_{}'.format(x) for x in range(1, 4)] + * device_inits = 'spi_stm32_init' + * device_levels = 'POST_KERNEL' + * device_prios = 'CONFIG_SPI_INIT_PRIORITY' + * device_api = 'spi_stm32_driver_api' + * device_info = \ + * """ + * #if CONFIG_SPI_STM32_INTERRUPT + * DEVICE_DECLARE(${device-name}); + * static void ${device-config-irq}(struct device *dev) + * { + * IRQ_CONNECT(${interrupts/0/irq}, ${interrupts/0/priority}, \\ + * spi_stm32_isr, \\ + * DEVICE_GET(${device-name}), 0); + * irq_enable(${interrupts/0/irq}); + * } + * #endif + * static const struct spi_stm32_config ${device-config-info} = { + * .spi = (SPI_TypeDef *)${reg/0/address/0}, + * .pclken.bus = ${clocks/0/bus}, + * .pclken.enr = ${clocks/0/bits}, + * #if CONFIG_SPI_STM32_INTERRUPT + * .config_irq = ${device-config-irq}, + * #endif + * }; + * static struct spi_stm32_data ${device-data} = { + * SPI_CONTEXT_INIT_LOCK(${device-data}, ctx), + * SPI_CONTEXT_INIT_SYNC(${device-data}, ctx), + * }; + * """ + * + * devicedeclare.device_declare_multi( \ + * device_configs, + * driver_names, + * device_inits, + * device_levels, + * device_prios, + * device_api, + * device_info) + * @endcode{.codegen} + */ + /** @code{.codeins}@endcode */ diff --git a/doc/subsystems/codegen/principle.rst b/doc/subsystems/codegen/principle.rst new file mode 100644 index 0000000000000..4fa4a0b3d2700 --- /dev/null +++ b/doc/subsystems/codegen/principle.rst @@ -0,0 +1,48 @@ +.. + Copyright (c) 2018 Bobby Noelte + SPDX-License-Identifier: Apache-2.0 + +.. _codegen_principle: + +Code Generation Principle +######################### + +How code generation works in Zephyr. + +.. contents:: + :depth: 2 + :local: + :backlinks: top + +Principle +--------- + +.. image:: codegen_principle.png + :width: 500px + :align: center + :alt: Principle + +Inclusion of other inline code +------------------------------ + +.. image:: codegen_principle_include.png + :width: 500px + :align: center + :alt: Include other inline code + +Access to Zephyr data +--------------------- + +.. image:: codegen_principle_access.png + :width: 500px + :align: center + :alt: Access Zephyr data + +Import of Python modules +------------------------ + +.. image:: codegen_principle_import.png + :width: 500px + :align: center + :alt: Import Python modules + diff --git a/doc/subsystems/codegen/templates.rst b/doc/subsystems/codegen/templates.rst new file mode 100644 index 0000000000000..da9f3a5ad12b8 --- /dev/null +++ b/doc/subsystems/codegen/templates.rst @@ -0,0 +1,37 @@ +.. + Copyright (c) 2018 Bobby Noelte + SPDX-License-Identifier: Apache-2.0 + +.. _codegen_templates: + +Code Generation Templates +######################### + +Code generation templates provide sopisticated code generation functions. + +.. contents:: + :depth: 2 + :local: + :backlinks: top + +Templates have to be included to gain access to the template's functions +and variables. + + :: + + /* This file uses templates. */ + ... + /** + * @code{.codegen} + * template_in_var = 1 + * codegen.out_include('templates/template_tmpl.c') + * if template_out_var not None: + * codegen.outl("int x = %s;" % template_out_var) + * @endcode{.codegen} + */ + /** @code{.codeins}@endcode */ + ... + + + + diff --git a/doc/subsystems/subsystems.rst b/doc/subsystems/subsystems.rst index 64a659b48d9c9..8f444e8be6859 100644 --- a/doc/subsystems/subsystems.rst +++ b/doc/subsystems/subsystems.rst @@ -11,6 +11,7 @@ to applications. bluetooth/bluetooth.rst c_library + codegen/index dfu logging/index tracing/index diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index 6eff42c950ffa..c3f4b42f0c255 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -18,14 +18,9 @@ zephyr_library_sources_ifdef(CONFIG_I2C_SBCON i2c_sbcon.c) zephyr_library_sources_ifdef(CONFIG_I2C_NIOS2 i2c_nios2.c) zephyr_library_sources_ifdef(CONFIG_I2C_GECKO i2c_gecko.c) -zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V1 - i2c_ll_stm32_v1.c - i2c_ll_stm32.c - ) -zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V2 - i2c_ll_stm32_v2.c - i2c_ll_stm32.c - ) +zephyr_library_sources_codegen_ifdef(CONFIG_I2C_STM32 i2c_ll_stm32.c) +zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V1 i2c_ll_stm32_v1.c) +zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V2 i2c_ll_stm32_v2.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE i2c_handlers.c) diff --git a/drivers/i2c/i2c_ll_stm32.c b/drivers/i2c/i2c_ll_stm32.c index 9757d1e0d5ce8..a82b2069321bd 100644 --- a/drivers/i2c/i2c_ll_stm32.c +++ b/drivers/i2c/i2c_ll_stm32.c @@ -211,182 +211,4 @@ static int i2c_stm32_init(struct device *dev) return 0; } -#ifdef CONFIG_I2C_1 - -#ifdef CONFIG_I2C_STM32_INTERRUPT -static void i2c_stm32_irq_config_func_1(struct device *port); -#endif - -static const struct i2c_stm32_config i2c_stm32_cfg_1 = { - .i2c = (I2C_TypeDef *)CONFIG_I2C_1_BASE_ADDRESS, - .pclken = { - .enr = CONFIG_I2C_1_CLOCK_BITS, - .bus = CONFIG_I2C_1_CLOCK_BUS, - }, -#ifdef CONFIG_I2C_STM32_INTERRUPT - .irq_config_func = i2c_stm32_irq_config_func_1, -#endif - .bitrate = CONFIG_I2C_1_BITRATE, -}; - -static struct i2c_stm32_data i2c_stm32_dev_data_1; - -DEVICE_AND_API_INIT(i2c_stm32_1, CONFIG_I2C_1_NAME, &i2c_stm32_init, - &i2c_stm32_dev_data_1, &i2c_stm32_cfg_1, - POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, - &api_funcs); - -#ifdef CONFIG_I2C_STM32_INTERRUPT -static void i2c_stm32_irq_config_func_1(struct device *dev) -{ -#ifdef CONFIG_I2C_STM32_COMBINED_INTERRUPT - IRQ_CONNECT(CONFIG_I2C_1_COMBINED_IRQ, CONFIG_I2C_1_COMBINED_IRQ_PRI, - stm32_i2c_combined_isr, DEVICE_GET(i2c_stm32_1), 0); - irq_enable(CONFIG_I2C_1_COMBINED_IRQ); -#else - IRQ_CONNECT(CONFIG_I2C_1_EVENT_IRQ, CONFIG_I2C_1_EVENT_IRQ_PRI, - stm32_i2c_event_isr, DEVICE_GET(i2c_stm32_1), 0); - irq_enable(CONFIG_I2C_1_EVENT_IRQ); - - IRQ_CONNECT(CONFIG_I2C_1_ERROR_IRQ, CONFIG_I2C_1_ERROR_IRQ_PRI, - stm32_i2c_error_isr, DEVICE_GET(i2c_stm32_1), 0); - irq_enable(CONFIG_I2C_1_ERROR_IRQ); -#endif -} -#endif - -#endif /* CONFIG_I2C_1 */ - -#ifdef CONFIG_I2C_2 - -#ifdef CONFIG_I2C_STM32_INTERRUPT -static void i2c_stm32_irq_config_func_2(struct device *port); -#endif - -static const struct i2c_stm32_config i2c_stm32_cfg_2 = { - .i2c = (I2C_TypeDef *)CONFIG_I2C_2_BASE_ADDRESS, - .pclken = { - .enr = CONFIG_I2C_2_CLOCK_BITS, - .bus = CONFIG_I2C_2_CLOCK_BUS, - }, -#ifdef CONFIG_I2C_STM32_INTERRUPT - .irq_config_func = i2c_stm32_irq_config_func_2, -#endif - .bitrate = CONFIG_I2C_2_BITRATE, -}; - -static struct i2c_stm32_data i2c_stm32_dev_data_2; - -DEVICE_AND_API_INIT(i2c_stm32_2, CONFIG_I2C_2_NAME, &i2c_stm32_init, - &i2c_stm32_dev_data_2, &i2c_stm32_cfg_2, - POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, - &api_funcs); - -#ifdef CONFIG_I2C_STM32_INTERRUPT -static void i2c_stm32_irq_config_func_2(struct device *dev) -{ -#ifdef CONFIG_I2C_STM32_COMBINED_INTERRUPT - IRQ_CONNECT(CONFIG_I2C_2_COMBINED_IRQ, CONFIG_I2C_2_COMBINED_IRQ_PRI, - stm32_i2c_combined_isr, DEVICE_GET(i2c_stm32_2), 0); - irq_enable(CONFIG_I2C_2_COMBINED_IRQ); -#else - IRQ_CONNECT(CONFIG_I2C_2_EVENT_IRQ, CONFIG_I2C_2_EVENT_IRQ_PRI, - stm32_i2c_event_isr, DEVICE_GET(i2c_stm32_2), 0); - irq_enable(CONFIG_I2C_2_EVENT_IRQ); - - IRQ_CONNECT(CONFIG_I2C_2_ERROR_IRQ, CONFIG_I2C_2_ERROR_IRQ_PRI, - stm32_i2c_error_isr, DEVICE_GET(i2c_stm32_2), 0); - irq_enable(CONFIG_I2C_2_ERROR_IRQ); -#endif -} -#endif - -#endif /* CONFIG_I2C_2 */ - -#ifdef CONFIG_I2C_3 - -#ifndef I2C3_BASE -#error "I2C_3 is not available on the platform that you selected" -#endif /* I2C3_BASE */ - -#ifdef CONFIG_I2C_STM32_INTERRUPT -static void i2c_stm32_irq_config_func_3(struct device *port); -#endif - -static const struct i2c_stm32_config i2c_stm32_cfg_3 = { - .i2c = (I2C_TypeDef *)CONFIG_I2C_3_BASE_ADDRESS, - .pclken = { - .enr = CONFIG_I2C_3_CLOCK_BITS, - .bus = CONFIG_I2C_3_CLOCK_BUS, - }, -#ifdef CONFIG_I2C_STM32_INTERRUPT - .irq_config_func = i2c_stm32_irq_config_func_3, -#endif - .bitrate = CONFIG_I2C_3_BITRATE, -}; - -static struct i2c_stm32_data i2c_stm32_dev_data_3; - -DEVICE_AND_API_INIT(i2c_stm32_3, CONFIG_I2C_3_NAME, &i2c_stm32_init, - &i2c_stm32_dev_data_3, &i2c_stm32_cfg_3, - POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, - &api_funcs); - -#ifdef CONFIG_I2C_STM32_INTERRUPT -static void i2c_stm32_irq_config_func_3(struct device *dev) -{ - IRQ_CONNECT(CONFIG_I2C_3_EVENT_IRQ, CONFIG_I2C_3_EVENT_IRQ_PRI, - stm32_i2c_event_isr, DEVICE_GET(i2c_stm32_3), 0); - irq_enable(CONFIG_I2C_3_EVENT_IRQ); - - IRQ_CONNECT(CONFIG_I2C_3_ERROR_IRQ, CONFIG_I2C_3_ERROR_IRQ_PRI, - stm32_i2c_error_isr, DEVICE_GET(i2c_stm32_3), 0); - irq_enable(CONFIG_I2C_3_ERROR_IRQ); -} -#endif - -#endif /* CONFIG_I2C_3 */ - -#ifdef CONFIG_I2C_4 - -#ifndef I2C4_BASE -#error "I2C_4 is not available on the platform that you selected" -#endif /* I2C4_BASE */ - -#ifdef CONFIG_I2C_STM32_INTERRUPT -static void i2c_stm32_irq_config_func_4(struct device *port); -#endif - -static const struct i2c_stm32_config i2c_stm32_cfg_4 = { - .i2c = (I2C_TypeDef *)CONFIG_I2C_4_BASE_ADDRESS, - .pclken = { - .enr = CONFIG_I2C_4_CLOCK_BITS, - .bus = CONFIG_I2C_4_CLOCK_BUS, - }, -#ifdef CONFIG_I2C_STM32_INTERRUPT - .irq_config_func = i2c_stm32_irq_config_func_4, -#endif - .bitrate = CONFIG_I2C_4_BITRATE, -}; - -static struct i2c_stm32_data i2c_stm32_dev_data_4; - -DEVICE_AND_API_INIT(i2c_stm32_4, CONFIG_I2C_4_NAME, &i2c_stm32_init, - &i2c_stm32_dev_data_4, &i2c_stm32_cfg_4, - POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, - &api_funcs); - -#ifdef CONFIG_I2C_STM32_INTERRUPT -static void i2c_stm32_irq_config_func_4(struct device *dev) -{ - IRQ_CONNECT(CONFIG_I2C_4_EVENT_IRQ, CONFIG_I2C_4_EVENT_IRQ_PRI, - stm32_i2c_event_isr, DEVICE_GET(i2c_stm32_4), 0); - irq_enable(CONFIG_I2C_4_EVENT_IRQ); - - IRQ_CONNECT(CONFIG_I2C_4_ERROR_IRQ, CONFIG_I2C_4_ERROR_IRQ_PRI, - stm32_i2c_error_isr, DEVICE_GET(i2c_stm32_4), 0); - irq_enable(CONFIG_I2C_4_ERROR_IRQ); -} -#endif - -#endif /* CONFIG_I2C_4 */ +#include diff --git a/drivers/i2c/i2c_ll_stm32.h b/drivers/i2c/i2c_ll_stm32.h index 528064f56d9ba..3d9d764dbffe2 100644 --- a/drivers/i2c/i2c_ll_stm32.h +++ b/drivers/i2c/i2c_ll_stm32.h @@ -55,10 +55,10 @@ s32_t stm32_i2c_msg_read(struct device *dev, struct i2c_msg *msg, u8_t *flg, s32_t stm32_i2c_configure_timing(struct device *dev, u32_t clk); int i2c_stm32_runtime_configure(struct device *dev, u32_t config); -void stm32_i2c_event_isr(void *arg); -void stm32_i2c_error_isr(void *arg); +void stm32_i2c_isr_event(void *arg); +void stm32_i2c_isr_error(void *arg); #ifdef CONFIG_I2C_STM32_COMBINED_INTERRUPT -void stm32_i2c_combined_isr(void *arg); +void stm32_i2c_isr_combined(void *arg); #endif #ifdef CONFIG_I2C_SLAVE diff --git a/drivers/i2c/i2c_ll_stm32_devices.c.in b/drivers/i2c/i2c_ll_stm32_devices.c.in new file mode 100644 index 0000000000000..4db899ccf8f83 --- /dev/null +++ b/drivers/i2c/i2c_ll_stm32_devices.c.in @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 Linaro Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Split configs for compat v1 and v2 to demonstrate multi-compat ability */ + +/** + * @code{.codegen} + * codegen.import_module('devicedeclare') + * + * device_data = ('i2c_stm32_data') + * + * device_config_v1 = ('i2c_stm32_config', + * """ + * .i2c = (I2C_TypeDef *)${get_reg_addr()}, + * .pclken = { + * .enr = ${get_property('clocks/0/bits')}, + * .bus = ${get_property('clocks/0/bus')}, + * }, + * #ifdef CONFIG_I2C_STM32_INTERRUPT + * .irq_config_func = ${get_unique_id()}_config_irq, + * #endif + * .bitrate = ${get_property('clock-frequency')}, + * """) + * + * device_config_v2 = ('i2c_stm32_config', + * """ + * .i2c = (I2C_TypeDef *)${get_reg_addr()}, + * .pclken = { + * .enr = ${get_property('clocks/0/bits')}, + * .bus = ${get_property('clocks/0/bus')}, + * }, + * #ifdef CONFIG_I2C_STM32_INTERRUPT + * .irq_config_func = ${get_unique_id()}_config_irq, + * #endif + * .bitrate = ${get_property('clock-frequency')}, + * """) + * + * devicedeclare.device_declare('st,stm32-i2c-v1', + * 'CONFIG_KERNEL_INIT_PRIORITY_DEVICE', + * 'POST_KERNEL', + * {'irq_func':'stm32_i2c_isr','irq_flag': 'CONFIG_I2C_STM32_INTERRUPT'}, + * 'i2c_stm32_init', + * 'api_funcs', + * device_data, + * device_config_v1 + * ) + * + * devicedeclare.device_declare('st,stm32-i2c-v2', + * 'CONFIG_KERNEL_INIT_PRIORITY_DEVICE', + * 'POST_KERNEL', + * {'irq_func':'stm32_i2c_isr','irq_flag': 'CONFIG_I2C_STM32_INTERRUPT'}, + * 'i2c_stm32_init', + * 'api_funcs', + * device_data, + * device_config_v2 + * ) + * + * @endcode{.codegen} + */ +/** @code{.codeins}@endcode */ diff --git a/drivers/i2c/i2c_ll_stm32_v1.c b/drivers/i2c/i2c_ll_stm32_v1.c index 085af54cd9dd4..90133efecc1da 100644 --- a/drivers/i2c/i2c_ll_stm32_v1.c +++ b/drivers/i2c/i2c_ll_stm32_v1.c @@ -171,7 +171,7 @@ static inline void handle_btf(I2C_TypeDef *i2c, struct i2c_stm32_data *data) } } -void stm32_i2c_event_isr(void *arg) +void stm32_i2c_isr_event(void *arg) { const struct i2c_stm32_config *cfg = DEV_CFG((struct device *)arg); struct i2c_stm32_data *data = DEV_DATA((struct device *)arg); @@ -192,7 +192,7 @@ void stm32_i2c_event_isr(void *arg) } } -void stm32_i2c_error_isr(void *arg) +void stm32_i2c_isr_error(void *arg) { const struct i2c_stm32_config *cfg = DEV_CFG((struct device *)arg); struct i2c_stm32_data *data = DEV_DATA((struct device *)arg); diff --git a/drivers/i2c/i2c_ll_stm32_v2.c b/drivers/i2c/i2c_ll_stm32_v2.c index a01acd8b788a9..a732ef07d82ff 100644 --- a/drivers/i2c/i2c_ll_stm32_v2.c +++ b/drivers/i2c/i2c_ll_stm32_v2.c @@ -343,7 +343,7 @@ static int stm32_i2c_error(struct device *dev) } #ifdef CONFIG_I2C_STM32_COMBINED_INTERRUPT -void stm32_i2c_combined_isr(void *arg) +void stm32_i2c_isr_combined(void *arg) { struct device *dev = (struct device *) arg; @@ -354,14 +354,14 @@ void stm32_i2c_combined_isr(void *arg) } #else -void stm32_i2c_event_isr(void *arg) +void stm32_i2c_isr_event(void *arg) { struct device *dev = (struct device *) arg; stm32_i2c_event(dev); } -void stm32_i2c_error_isr(void *arg) +void stm32_i2c_isr_error(void *arg) { struct device *dev = (struct device *) arg; @@ -467,6 +467,7 @@ int stm32_i2c_msg_read(struct device *dev, struct i2c_msg *msg, } #else /* !CONFIG_I2C_STM32_INTERRUPT */ + static inline void msg_done(struct device *dev, unsigned int current_msg_flags) { const struct i2c_stm32_config *cfg = DEV_CFG(dev); diff --git a/drivers/sensor/lsm6dsl/CMakeLists.txt b/drivers/sensor/lsm6dsl/CMakeLists.txt index b42cba8f09e0f..57823a9354a5f 100644 --- a/drivers/sensor/lsm6dsl/CMakeLists.txt +++ b/drivers/sensor/lsm6dsl/CMakeLists.txt @@ -1,6 +1,6 @@ zephyr_library() -zephyr_library_sources_ifdef(CONFIG_LSM6DSL lsm6dsl.c) +zephyr_library_sources_codegen_ifdef(CONFIG_LSM6DSL lsm6dsl.c) zephyr_library_sources_ifdef(CONFIG_LSM6DSL_SPI lsm6dsl_spi.c) zephyr_library_sources_ifdef(CONFIG_LSM6DSL_I2C lsm6dsl_i2c.c) zephyr_library_sources_ifdef(CONFIG_LSM6DSL_TRIGGER lsm6dsl_trigger.c) diff --git a/drivers/sensor/lsm6dsl/lsm6dsl.c b/drivers/sensor/lsm6dsl/lsm6dsl.c index 8a71f516016ed..3f4bcdfb3564f 100644 --- a/drivers/sensor/lsm6dsl/lsm6dsl.c +++ b/drivers/sensor/lsm6dsl/lsm6dsl.c @@ -772,13 +772,6 @@ static int lsm6dsl_init_chip(struct device *dev) return 0; } -static struct lsm6dsl_config lsm6dsl_config = { -#ifdef CONFIG_LSM6DSL_SPI - .comm_master_dev_name = CONFIG_LSM6DSL_SPI_MASTER_DEV_NAME, -#else - .comm_master_dev_name = CONFIG_LSM6DSL_I2C_MASTER_DEV_NAME, -#endif -}; static int lsm6dsl_init(struct device *dev) { @@ -795,6 +788,12 @@ static int lsm6dsl_init(struct device *dev) #ifdef CONFIG_LSM6DSL_SPI lsm6dsl_spi_init(dev); #else + data->i2c_addr = config->i2c_slave_addr; + if (!data->i2c_addr) { + SYS_LOG_DBG("I2C slave address not provided"); + return -EINVAL; + } + lsm6dsl_i2c_init(dev); #endif @@ -821,8 +820,4 @@ static int lsm6dsl_init(struct device *dev) } -static struct lsm6dsl_data lsm6dsl_data; - -DEVICE_AND_API_INIT(lsm6dsl, CONFIG_LSM6DSL_DEV_NAME, lsm6dsl_init, - &lsm6dsl_data, &lsm6dsl_config, POST_KERNEL, - CONFIG_SENSOR_INIT_PRIORITY, &lsm6dsl_api_funcs); +#include diff --git a/drivers/sensor/lsm6dsl/lsm6dsl.h b/drivers/sensor/lsm6dsl/lsm6dsl.h index 3aee094d59083..08cd252767d53 100644 --- a/drivers/sensor/lsm6dsl/lsm6dsl.h +++ b/drivers/sensor/lsm6dsl/lsm6dsl.h @@ -605,6 +605,15 @@ struct lsm6dsl_config { char *comm_master_dev_name; +#ifdef CONFIG_LSM6DSL_SPI + char *irq_gpio_port; + u32_t irq_gpio_pin; + char *cs_gpio_port; + u32_t cs_gpio_pin; + u32_t frequency; +#else + u16_t i2c_slave_addr; +#endif }; struct lsm6dsl_data; @@ -622,6 +631,7 @@ struct lsm6dsl_transfer_function { struct lsm6dsl_data { struct device *comm_master; + u16_t i2c_addr; int accel_sample_x; int accel_sample_y; int accel_sample_z; diff --git a/drivers/sensor/lsm6dsl/lsm6dsl_devices.c.in b/drivers/sensor/lsm6dsl/lsm6dsl_devices.c.in new file mode 100644 index 0000000000000..db5597dfd52b3 --- /dev/null +++ b/drivers/sensor/lsm6dsl/lsm6dsl_devices.c.in @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + + + +/** + * @code{.codegen} + * codegen.import_module('devicedeclare') + * + * device_data = ('lsm6dsl_data') + * + * device_config_spi = ('lsm6dsl_config', + * """ + * .comm_master_dev_name = "${get_parent().get_property('label')}", + * .irq_gpio_port = "${get_controller('irq-gpios/0').get_property('label')}", + * .irq_gpio_pin = ${get_property('irq-gpios/0/pin')}, + * .cs_gpio_port = "${get_controller('cs-gpios/0').get_property('label')}", + * .cs_gpio_pin = ${get_property('cs-gpios/0/pin')}, + * .frequency = ${get_property('spi-max-frequency')}, + * """) + * + * device_config_i2c = ('lsm6dsl_config', + * """ + * .comm_master_dev_name = "${get_parent().get_property('label')}", + * .i2c_slave_addr = ${get_reg_addr()} + * """) + * + * devicedeclare.device_declare('st,lsm6dsl-spi', + * 'CONFIG_SENSOR_INIT_PRIORITY', + * 'POST_KERNEL', + * None, + * 'lsm6dsl_init', + * 'lsm6dsl_api_funcs', + * device_data, + * device_config_spi + * ) + * + * devicedeclare.device_declare('st,lsm6dsl', + * 'CONFIG_SENSOR_INIT_PRIORITY', + * 'POST_KERNEL', + * None, + * 'lsm6dsl_init', + * 'lsm6dsl_api_funcs', + * device_data, + * device_config_i2c + * ) + * + * @endcode{.codegen} + */ +/** @code{.codeins}@endcode */ diff --git a/drivers/sensor/lsm6dsl/lsm6dsl_i2c.c b/drivers/sensor/lsm6dsl/lsm6dsl_i2c.c index a23b7e2e67970..221a99045c2ae 100644 --- a/drivers/sensor/lsm6dsl/lsm6dsl_i2c.c +++ b/drivers/sensor/lsm6dsl/lsm6dsl_i2c.c @@ -7,13 +7,13 @@ * SPDX-License-Identifier: Apache-2.0 */ + #include #include #include #include "lsm6dsl.h" -static u16_t lsm6dsl_i2c_slave_addr = CONFIG_LSM6DSL_I2C_ADDR; #define LOG_LEVEL CONFIG_SENSOR_LOG_LEVEL LOG_MODULE_DECLARE(LSM6DSL); @@ -21,28 +21,28 @@ LOG_MODULE_DECLARE(LSM6DSL); static int lsm6dsl_i2c_read_data(struct lsm6dsl_data *data, u8_t reg_addr, u8_t *value, u8_t len) { - return i2c_burst_read(data->comm_master, lsm6dsl_i2c_slave_addr, + return i2c_burst_read(data->comm_master, data->i2c_addr, reg_addr, value, len); } static int lsm6dsl_i2c_write_data(struct lsm6dsl_data *data, u8_t reg_addr, u8_t *value, u8_t len) { - return i2c_burst_write(data->comm_master, lsm6dsl_i2c_slave_addr, + return i2c_burst_write(data->comm_master, data->i2c_addr, reg_addr, value, len); } static int lsm6dsl_i2c_read_reg(struct lsm6dsl_data *data, u8_t reg_addr, u8_t *value) { - return i2c_reg_read_byte(data->comm_master, lsm6dsl_i2c_slave_addr, + return i2c_reg_read_byte(data->comm_master, data->i2c_addr, reg_addr, value); } static int lsm6dsl_i2c_update_reg(struct lsm6dsl_data *data, u8_t reg_addr, u8_t mask, u8_t value) { - return i2c_reg_update_byte(data->comm_master, lsm6dsl_i2c_slave_addr, + return i2c_reg_update_byte(data->comm_master, data->i2c_addr, reg_addr, mask, value); } diff --git a/drivers/sensor/lsm6dsl/lsm6dsl_spi.c b/drivers/sensor/lsm6dsl/lsm6dsl_spi.c index eba7143d2d2f1..ed16ac8d9383e 100644 --- a/drivers/sensor/lsm6dsl/lsm6dsl_spi.c +++ b/drivers/sensor/lsm6dsl/lsm6dsl_spi.c @@ -7,6 +7,7 @@ * SPDX-License-Identifier: Apache-2.0 */ + #include #include #include "lsm6dsl.h" diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index c626a1409c329..7c8b1bf48d7d5 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -2,7 +2,7 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_SPI_DW spi_dw.c) zephyr_library_sources_ifdef(CONFIG_SPI_INTEL spi_intel.c) -zephyr_library_sources_ifdef(CONFIG_SPI_STM32 spi_ll_stm32.c) +zephyr_library_sources_codegen_ifdef(CONFIG_SPI_STM32 spi_ll_stm32.c) zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_DSPI spi_mcux_dspi.c) zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_LPSPI spi_mcux_lpspi.c) zephyr_library_sources_ifdef(CONFIG_SPI_SAM spi_sam.c) diff --git a/drivers/spi/spi_ll_stm32.c b/drivers/spi/spi_ll_stm32.c index 8290297f6c895..addffa67d5540 100644 --- a/drivers/spi/spi_ll_stm32.c +++ b/drivers/spi/spi_ll_stm32.c @@ -477,121 +477,4 @@ static int spi_stm32_init(struct device *dev) return 0; } -#ifdef CONFIG_SPI_1 - -#ifdef CONFIG_SPI_STM32_INTERRUPT -static void spi_stm32_irq_config_func_1(struct device *port); -#endif - -static const struct spi_stm32_config spi_stm32_cfg_1 = { - .spi = (SPI_TypeDef *) CONFIG_SPI_1_BASE_ADDRESS, - .pclken = { -#ifdef CONFIG_SOC_SERIES_STM32F0X - .enr = LL_APB1_GRP2_PERIPH_SPI1, - .bus = STM32_CLOCK_BUS_APB1_2 -#else - .enr = LL_APB2_GRP1_PERIPH_SPI1, - .bus = STM32_CLOCK_BUS_APB2 -#endif - }, -#ifdef CONFIG_SPI_STM32_INTERRUPT - .irq_config = spi_stm32_irq_config_func_1, -#endif -}; - -static struct spi_stm32_data spi_stm32_dev_data_1 = { - SPI_CONTEXT_INIT_LOCK(spi_stm32_dev_data_1, ctx), - SPI_CONTEXT_INIT_SYNC(spi_stm32_dev_data_1, ctx), -}; - -DEVICE_AND_API_INIT(spi_stm32_1, CONFIG_SPI_1_NAME, &spi_stm32_init, - &spi_stm32_dev_data_1, &spi_stm32_cfg_1, - POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, - &api_funcs); - -#ifdef CONFIG_SPI_STM32_INTERRUPT -static void spi_stm32_irq_config_func_1(struct device *dev) -{ - IRQ_CONNECT(CONFIG_SPI_1_IRQ, CONFIG_SPI_1_IRQ_PRI, - spi_stm32_isr, DEVICE_GET(spi_stm32_1), 0); - irq_enable(CONFIG_SPI_1_IRQ); -} -#endif - -#endif /* CONFIG_SPI_1 */ - -#ifdef CONFIG_SPI_2 - -#ifdef CONFIG_SPI_STM32_INTERRUPT -static void spi_stm32_irq_config_func_2(struct device *port); -#endif - -static const struct spi_stm32_config spi_stm32_cfg_2 = { - .spi = (SPI_TypeDef *) CONFIG_SPI_2_BASE_ADDRESS, - .pclken = { - .enr = LL_APB1_GRP1_PERIPH_SPI2, - .bus = STM32_CLOCK_BUS_APB1 - }, -#ifdef CONFIG_SPI_STM32_INTERRUPT - .irq_config = spi_stm32_irq_config_func_2, -#endif -}; - -static struct spi_stm32_data spi_stm32_dev_data_2 = { - SPI_CONTEXT_INIT_LOCK(spi_stm32_dev_data_2, ctx), - SPI_CONTEXT_INIT_SYNC(spi_stm32_dev_data_2, ctx), -}; - -DEVICE_AND_API_INIT(spi_stm32_2, CONFIG_SPI_2_NAME, &spi_stm32_init, - &spi_stm32_dev_data_2, &spi_stm32_cfg_2, - POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, - &api_funcs); - -#ifdef CONFIG_SPI_STM32_INTERRUPT -static void spi_stm32_irq_config_func_2(struct device *dev) -{ - IRQ_CONNECT(CONFIG_SPI_2_IRQ, CONFIG_SPI_2_IRQ_PRI, - spi_stm32_isr, DEVICE_GET(spi_stm32_2), 0); - irq_enable(CONFIG_SPI_2_IRQ); -} -#endif - -#endif /* CONFIG_SPI_2 */ - -#ifdef CONFIG_SPI_3 - -#ifdef CONFIG_SPI_STM32_INTERRUPT -static void spi_stm32_irq_config_func_3(struct device *port); -#endif - -static const struct spi_stm32_config spi_stm32_cfg_3 = { - .spi = (SPI_TypeDef *) CONFIG_SPI_3_BASE_ADDRESS, - .pclken = { - .enr = LL_APB1_GRP1_PERIPH_SPI3, - .bus = STM32_CLOCK_BUS_APB1 - }, -#ifdef CONFIG_SPI_STM32_INTERRUPT - .irq_config = spi_stm32_irq_config_func_3, -#endif -}; - -static struct spi_stm32_data spi_stm32_dev_data_3 = { - SPI_CONTEXT_INIT_LOCK(spi_stm32_dev_data_3, ctx), - SPI_CONTEXT_INIT_SYNC(spi_stm32_dev_data_3, ctx), -}; - -DEVICE_AND_API_INIT(spi_stm32_3, CONFIG_SPI_3_NAME, &spi_stm32_init, - &spi_stm32_dev_data_3, &spi_stm32_cfg_3, - POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, - &api_funcs); - -#ifdef CONFIG_SPI_STM32_INTERRUPT -static void spi_stm32_irq_config_func_3(struct device *dev) -{ - IRQ_CONNECT(CONFIG_SPI_3_IRQ, CONFIG_SPI_3_IRQ_PRI, - spi_stm32_isr, DEVICE_GET(spi_stm32_3), 0); - irq_enable(CONFIG_SPI_3_IRQ); -} -#endif - -#endif /* CONFIG_SPI_3 */ +#include diff --git a/drivers/spi/spi_ll_stm32_devices.c.in b/drivers/spi/spi_ll_stm32_devices.c.in new file mode 100644 index 0000000000000..f276466d4f01c --- /dev/null +++ b/drivers/spi/spi_ll_stm32_devices.c.in @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @code{.codegen} + * codegen.import_module('devicedeclare') + * + * device_data = ( 'spi_stm32_data', + * """ + * SPI_CONTEXT_INIT_LOCK(${get_unique_id()}_data, ctx), + * SPI_CONTEXT_INIT_SYNC(${get_unique_id()}_data, ctx), + * """ + * ) + * + * + * device_config = ( 'spi_stm32_config', + * """ + * .spi = (SPI_TypeDef *)${get_reg_addr()}, + * .pclken.bus = ${get_property('clocks/0/bus')}, + * .pclken.enr = ${get_property('clocks/0/bits')}, + * #ifdef CONFIG_SPI_STM32_INTERRUPT + * .irq_config = ${get_unique_id()}_config_irq, + * #endif + * """) + * + * devicedeclare.device_declare(['st,stm32-spi', 'st,stm32-spi-fifo'], + * 'CONFIG_SPI_INIT_PRIORITY', + * 'POST_KERNEL', + * {'irq_func':'spi_stm32_isr','irq_flag': 'CONFIG_SPI_STM32_INTERRUPT'}, + * 'spi_stm32_init', + * 'api_funcs', + * device_data, + * device_config + * ) + * + * @endcode{.codegen} + */ +/** @code{.codeins}@endcode */ diff --git a/dts/bindings/sensor/st,lsm6dsl-spi.yaml b/dts/bindings/sensor/st,lsm6dsl-spi.yaml index 3a1e20b0db241..a26d44d63bf03 100644 --- a/dts/bindings/sensor/st,lsm6dsl-spi.yaml +++ b/dts/bindings/sensor/st,lsm6dsl-spi.yaml @@ -22,4 +22,9 @@ properties: type: compound category: required generation: define, use-prop-name + + cs-gpios: + type: compound + category: required + generation: define, use-prop-name ... diff --git a/scripts/codegen/cmake.py b/scripts/codegen/cmake.py new file mode 100644 index 0000000000000..eedc0be56c07f --- /dev/null +++ b/scripts/codegen/cmake.py @@ -0,0 +1,191 @@ +# Copyright (c) 2018 Open Source Foundries Limited. +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 +# +# CMakeCacheEntry and CMakeCache are taken from scripts/zephyr_run.py. +# + +import os +import sys +import re +from collections import OrderedDict +from pathlib import Path + + +class CMakeCacheEntry: + '''Represents a CMake cache entry. + This class understands the type system in a CMakeCache.txt, and + converts the following cache types to Python types: + Cache Type Python type + ---------- ------------------------------------------- + FILEPATH str + PATH str + STRING str OR list of str (if ';' is in the value) + BOOL bool + INTERNAL str OR list of str (if ';' is in the value) + ---------- ------------------------------------------- + ''' + + # Regular expression for a cache entry. + # + # CMake variable names can include escape characters, allowing a + # wider set of names than is easy to match with a regular + # expression. To be permissive here, use a non-greedy match up to + # the first colon (':'). This breaks if the variable name has a + # colon inside, but it's good enough. + CACHE_ENTRY = re.compile( + r'''(?P.*?) # name + :(?PFILEPATH|PATH|STRING|BOOL|INTERNAL) # type + =(?P.*) # value + ''', re.X) + + @classmethod + def _to_bool(cls, val): + # Convert a CMake BOOL string into a Python bool. + # + # "True if the constant is 1, ON, YES, TRUE, Y, or a + # non-zero number. False if the constant is 0, OFF, NO, + # FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in + # the suffix -NOTFOUND. Named boolean constants are + # case-insensitive. If the argument is not one of these + # constants, it is treated as a variable." + # + # https://cmake.org/cmake/help/v3.0/command/if.html + val = val.upper() + if val in ('ON', 'YES', 'TRUE', 'Y'): + return True + elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''): + return False + elif val.endswith('-NOTFOUND'): + return False + else: + try: + v = int(val) + return v != 0 + except ValueError as exc: + raise ValueError('invalid bool {}'.format(val)) from exc + + @classmethod + def from_line(cls, line, line_no): + # Comments can only occur at the beginning of a line. + # (The value of an entry could contain a comment character). + if line.startswith('//') or line.startswith('#'): + return None + + # Whitespace-only lines do not contain cache entries. + if not line.strip(): + return None + + m = cls.CACHE_ENTRY.match(line) + if not m: + return None + + name, type_, value = (m.group(g) for g in ('name', 'type', 'value')) + if type_ == 'BOOL': + try: + value = cls._to_bool(value) + except ValueError as exc: + args = exc.args + ('on line {}: {}'.format(line_no, line),) + raise ValueError(args) from exc + elif type_ == 'STRING' or type_ == 'INTERNAL': + # If the value is a CMake list (i.e. is a string which + # contains a ';'), convert to a Python list. + if ';' in value: + value = value.split(';') + + return CMakeCacheEntry(name, value) + + def __init__(self, name, value): + self.name = name + self.value = value + + def __str__(self): + fmt = 'CMakeCacheEntry(name={}, value={})' + return fmt.format(self.name, self.value) + + +class CMakeCache: + '''Parses and represents a CMake cache file.''' + + def __init__(self, cache_file): + self.load(cache_file) + + def load(self, cache_file): + entries = [] + with open(str(cache_file), 'r') as cache: + for line_no, line in enumerate(cache): + entry = CMakeCacheEntry.from_line(line, line_no) + if entry: + entries.append(entry) + self._entries = OrderedDict((e.name, e) for e in entries) + + def get(self, name, default=None): + entry = self._entries.get(name) + if entry is not None: + return entry.value + else: + return default + + def get_list(self, name, default=None): + if default is None: + default = [] + entry = self._entries.get(name) + if entry is not None: + value = entry.value + if isinstance(value, list): + return value + elif isinstance(value, str): + return [value] + else: + msg = 'invalid value {} type {}' + raise RuntimeError(msg.format(value, type(value))) + else: + return default + + def __getitem__(self, name): + return self._entries[name].value + + def __setitem__(self, name, entry): + if not isinstance(entry, CMakeCacheEntry): + msg = 'improper type {} for value {}, expecting CMakeCacheEntry' + raise TypeError(msg.format(type(entry), entry)) + self._entries[name] = entry + + def __delitem__(self, name): + del self._entries[name] + + def __iter__(self): + return iter(self._entries.values()) + + +class CMakeMixin(object): + __slots__ = [] + + _cmake_cache = None + + def cmake_variable(self, variable_name, default=""): + variable_value = self.options.defines.get(variable_name, default) + if variable_value == "": + raise self._get_error_exception( + "CMake variable '{}' not defined.".format(variable_name), 1) + return variable_value + + def cmake_cache_variable(self, variable_name, default=""): + if self._cmake_cache is None: + cache_file = self.cmake_variable("CMAKE_BINARY_DIR") + cache_file = Path(cache_file).joinpath("CMakeCache.txt") + if not cache_file.is_file(): + raise self._get_error_exception( + "CMake cache file '{}' does not exist or is no file.". + format(cache_file), 1) + self._cmake_cache = CMakeCache(cache_file) + try: + return self._cmake_cache.get(variable_name) + except: + if default == "": + raise self._get_error_exception( + "CMake variable '{}' not defined in cache file.". + format(variable_name), 1) + return default + diff --git a/scripts/codegen/codegen.py b/scripts/codegen/codegen.py new file mode 100644 index 0000000000000..296ae8101d66a --- /dev/null +++ b/scripts/codegen/codegen.py @@ -0,0 +1,630 @@ +# Copyright 2004-2016, Ned Batchelder. +# http://nedbatchelder.com/code/cog +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: MIT + +import sys +import os +import imp +import inspect +import re +from traceback import TracebackException + +from .whiteutils import * +from .filereader import NumberedFileReader +from .options import Options, OptionsMixin +from .generic import GenericMixin +from .guard import GuardMixin +from .config import ConfigMixin +from .cmake import CMakeMixin +from .zephyr import ZephyrMixin +from .edts import EDTSMixin +from .include import IncludeMixin +from .log import LogMixin +from .error import ErrorMixin, Error +from .output import OutputMixin +from .importmodule import ImportMixin +from .redirectable import Redirectable, RedirectableMixin + +class CodeGenerator(OptionsMixin, GenericMixin, ConfigMixin, + CMakeMixin, ZephyrMixin, EDTSMixin, GuardMixin, + IncludeMixin, LogMixin, ErrorMixin, OutputMixin, + ImportMixin, RedirectableMixin): + + code_start_marker = b'@code{.codegen}' + code_end_marker = b'@endcode{.codegen}' + code_insert_marker = b'@code{.codeins}@endcode' + + def __init__(self, processor, globals={}, + output_file=None, snippet_file = None): + self._stdout = sys.stdout + self._stderr = sys.stderr + self._outstring = '' + # code snippet markers and lines + self._start_marker = self.code_start_marker.decode('utf-8') + self._end_marker = self.code_end_marker.decode('utf-8') + self._insert_marker = self.code_insert_marker.decode('utf-8') + self.markers = [] + self.lines = [] + # The processor that is using this generator + self.processor = processor + self.options = processor.options + # All generators of a file usually work on the same global namespace + self.generator_globals = globals + self._output_file = output_file + # The file that contains the snippet + self._snippet_file = snippet_file + # The snippet this generator works on + self._snippet = None + # the snippet start offset in original file + self._snippet_offset = None + # the tab size in the snippet + self._snippet_tabsize = 8 + # the current evaluation start offset in the original file + # may be different to snippet offset during evaluation + self._eval_offset = None + # the previous output + self._previous = '' + + def line_is_start_marker(self, s): + return self._start_marker in s + + def line_is_end_marker(self, s): + return self._end_marker in s and not self.line_is_insert_marker(s) + + def line_is_insert_marker(self, s): + return self._insert_marker in s + + def parse_start_marker(self, l, snippet_offset): + self._snippet_offset = snippet_offset + self._eval_offset = snippet_offset + self.markers.append(l.expandtabs(self._snippet_tabsize)) + + def parse_end_marker(self, l): + self.markers.append(l.expandtabs(self._snippet_tabsize)) + + def parse_line(self, l, single_line_snippet=False): + l = l.expandtabs(self._snippet_tabsize).strip('\n') + if single_line_snippet: + # single line snippets contain the markers - remove them + beg = l.find(self._start_marker) + end = l.find(self._end_marker) + if beg > end: + self.lines.append(l) + self.error("Codegen code markers inverted", + frame_index = -2) + else: + l = l[beg+len(self._start_marker):end].strip() + self.lines.append(l) + + def _out(self, output='', dedent=False, trimblanklines=False): + if trimblanklines and ('\n' in output): + lines = output.split('\n') + if lines[0].strip() == '': + del lines[0] + if lines and lines[-1].strip() == '': + del lines[-1] + output = '\n'.join(lines)+'\n' + if dedent: + output = reindentBlock(output) + self._outstring += output + + ## + # @brief Re-indent a code block. + # + # Take a block of text as a string or list of lines. + # Remove any common prefix and whitespace indentation. + # Re-indent using new_indent + # + # @param lines + # @param common_prefix + # @param new_indent + # @return indented code block as a single string. + def _reindent_code(self, lines, common_prefix = '', new_indent=''): + if not isinstance(lines, list): + lines = lines.split('\n') + # remove common prefix + code_lines = [] # all code lines + code_no_white_lines = [] # code lines excluding white space lines + for line in lines: + line = line.expandtabs() + if common_prefix: + line = line.replace(common_prefix, '', 1) + code_lines.append(line) + if line and not line.isspace(): + code_no_white_lines.append(line) + # remove common white space and re-indent + out_lines = [] + common_white = os.path.commonprefix(code_no_white_lines) + common_white = re.search(r'\s*', common_white).group(0) + for line in code_lines: + if common_white: + line = line.replace(common_white, '', 1) + if line and new_indent: + line = new_indent + line + out_lines.append(line) + return '\n'.join(out_lines) + + ## + # @brief Extract the executable Python code from the generator. + # + def _get_code(self, fname, snippet_offset): + # If the markers and lines all have the same prefix + # (end-of-line comment chars, for example), + # then remove it from all the lines. + common_prefix = os.path.commonprefix(self.markers + self.lines) + if not common_prefix: + # there may be a prefix error + # just do some heuristics + if fname.endswith( + ('.h', '.hxx', '.c', '.cpp', '.cxx')): + # assume C/C++ comments + for line in (self.markers + self.lines): + if line.strip().startswith('*'): + common_prefix = '*' + break + elif line.strip().startswith('//'): + common_prefix = '//' + break + if common_prefix: + # there should be a common prefix -> error + lines = list() # lines with correct prefix + for lineno, line in enumerate(self.lines): + if not line.strip().startswith(common_prefix): + print("Codegen: Comment prefix may miss in codegen snippet (+{}) in '{}'.".format( + snippet_offset, fname)) + line_start = lineno - 5 + if line_start < 0: + line_start = 0 + line_end = lineno + 5 + if line_end > len(self.lines): + line_end = len(self.lines) + for i in range(line_start, line_end): + snippet_lineno = i + 2 + input_lineno = snippet_offset + snippet_lineno + print("#{} (+{}, line {}): {}".format( + input_lineno, snippet_offset, snippet_lineno, self.lines[i])) + else: + lines.append(line) + if len(lines) >= int(len(self.lines) / 2): + common_prefix = os.path.commonprefix(lines) + print("Codegen: Assuming comment prefix '{}' for codegen snippet (+{}) in '{}'.".format( + common_prefix, snippet_offset, fname)) + else: + common_prefix = '' + if common_prefix: + self.markers = [ line.replace(common_prefix, '', 1) for line in self.markers ] + code = self._reindent_code(self.lines, common_prefix) + return code + + # Make sure the "codegen" (alias cog) module has our state. + def _set_module_state(self): + restore_state = {} + module_states = self.processor.module_states + module = self.processor.cogmodule + # General module values + restore_state['inFile'] = getattr(module, 'inFile', None) + module.inFile = self._snippet_file + restore_state['outFile'] = getattr(module, 'outFile', None) + module.outFile = self._output_file + restore_state['firstLineNum'] = getattr(module, 'firstLineNum', None) + module.firstLineNum = self._snippet_offset + restore_state['previous'] = getattr(module, 'previous', None) + module.previous = self._previous + # CodeGenerator access + restore_state['options'] = getattr(module, 'options', None) + module.options = self.options + restore_state['Error'] = getattr(module, 'Error', None) + module.Error = self._get_error_exception + # Look for the Mixin classes + for base_cls in inspect.getmro(CodeGenerator): + if "Mixin" in base_cls.__name__: + for member_name, member_value in inspect.getmembers(base_cls): + if member_name.startswith('_'): + continue + if inspect.isroutine(member_value): + restore_state[member_name] = \ + getattr(module, member_name, None) + setattr(module, member_name, + getattr(self, member_name)) + module_states.append(restore_state) + + def _restore_module_state(self): + module_states = self.processor.module_states + module = self.processor.cogmodule + restore_state = module_states.pop() + # General module values + module.inFile = restore_state['inFile'] + module.outFile = restore_state['outFile'] + module.firstLineNum = restore_state['firstLineNum'] + module.previous = restore_state['previous'] + # CodeGenerator access + module.options = restore_state['options'] + module.Error = restore_state['Error'] + # Look for the Mixin classes + for base_cls in inspect.getmro(CodeGenerator): + if "Mixin" in base_cls.__name__: + for member_name, member_value in inspect.getmembers(base_cls): + if member_name.startswith('_'): + continue + if inspect.isroutine(member_value): + setattr(module, member_name, restore_state[member_name]) + + ## + # @brief snippet id to be used in logging and error reporting + # + # Accounts for extra lines added during evaluation + # + def _get_snippet_id(self): + return "+{}".format(self._eval_offset) + + ## + # @brief get snippet line number from evaluation line number + # + # Accounts for extra lines added during evaluation + # + # @param eval_lineno line number as reported from python code eval + def _get_snippet_lineno(self, eval_lineno): + return int(eval_lineno) + self._eval_offset - self._snippet_offset + + def _list_snippet(self): + if not self._snippet: + return None + listing = "" + for i, line in enumerate(self._snippet.splitlines()): + eval_lineno = i + 2 + input_lineno = self._eval_offset + eval_lineno + if i > 0: + listing += "\n" + listing += "#{} ({}, line {}): {}".format( + input_lineno, self._get_snippet_id(), eval_lineno, line) + return listing + + def _list_lines(self): + if len(self.lines) == 0: + return None + listing = "" + for i, line in enumerate(self.lines): + eval_lineno = i + 2 + input_lineno = self._eval_offset + eval_lineno + if i > 0: + listing += "\n" + listing += "#{} ({}, line {}): {}".format( + input_lineno, self._get_snippet_id(), eval_lineno, line) + return listing + + ## + # @brief evaluate + # + def evaluate(self): + self._snippet = self._get_code(self._snippet_file, self._snippet_offset) + if not self._snippet: + return '' + + # we add an extra line 'import codegen' + # to snippet so account for that + self._eval_offset = self._snippet_offset - 1 + + # In Python 2.2, the last line has to end in a newline. + eval_code = "import codegen\n" + self._snippet + "\n" + eval_fname = "{} {}".format(self._snippet_file, self._get_snippet_id()) + + try: + code = compile(eval_code, eval_fname, 'exec') + except: + exc_type, exc_value, exc_tb = sys.exc_info() + exc_traceback = TracebackException(exc_type, exc_value, exc_tb) + self.error( + "compile exception '{}' within snippet in {}".format( + exc_value, self._snippet_file), + frame_index = -2, + snippet_lineno = exc_traceback.lineno) + + # Make sure the "codegen" (alias cog) module has our state. + self._set_module_state() + + self._outstring = '' + try: + eval(code, self.generator_globals) + except: + exc_type, exc_value, exc_tb = sys.exc_info() + if exc_type is Error: + # Exception raise by CodeGen means + raise + # Not raised by Codegen means - add some info + print("Codegen: eval exception within codegen snippet ({}) in {}".format( + self._get_snippet_id(), self._snippet_file)) + for i, line in enumerate(self._snippet.splitlines()): + eval_lineno = i + 2 + input_lineno = self._eval_offset + eval_lineno + print("#{} ({}, line {}): {}".format(input_lineno, self._get_snippet_id(), eval_lineno, line)) + raise + finally: + self._restore_module_state() + + # We need to make sure that the last line in the output + # ends with a newline, or it will be joined to the + # end-output line, ruining cog's idempotency. + if self._outstring and self._outstring[-1] != '\n': + self._outstring += '\n' + + # end of evaluation - no extra offset anymore + self._eval_offset = self._snippet_offset + + # figure out the right whitespace prefix for the output + prefOut = whitePrefix(self.markers) + self._previous = reindentBlock(self._outstring, prefOut) + return self._previous + +## +# @brief The code generation processor +# +class CodeGen(Redirectable): + + def __init__(self): + Redirectable.__init__(self) + # Stack of module states + self.module_states = [] + self.options = Options() + # assure codegen module is installed + self._install_codegen_module() + + ## + # @brief Is this a trailing line after an end spec marker. + # + # @todo Make trailing end spec line detection dependent on + # type of text or file type. + # + # @param s line + # + def _is_end_spec_trailer(self, s): + return '*/' in s + + def _install_codegen_module(self): + """ Magic mumbo-jumbo so that imported Python modules + can say "import codegen" and get our state. + + Make us the module, and not our parent cog. + """ + self.cogmodule = imp.new_module('codegen') + self.cogmodule.path = [] + sys.modules['codegen'] = self.cogmodule + + def openOutputFile(self, fname): + """ Open an output file, taking all the details into account. + """ + opts = {} + mode = "w" + opts['encoding'] = self.options.sEncoding + if self.options.bNewlines: + opts['newline'] = "\n" + fdir = os.path.dirname(fname) + if os.path.dirname(fdir) and not os.path.exists(fdir): + os.makedirs(fdir) + return open(fname, mode, **opts) + + def openInputFile(self, fname): + """ Open an input file. """ + if fname == "-": + return sys.stdin + else: + opts = {} + opts['encoding'] = self.options.sEncoding + return open(fname, "r", **opts) + + ## + # @brief Process an input file object to an output file object. + # + # May be called recursively + # + # @param fIn input file object, or file name + # @param fOut output file object, or file name + # @param fname [optional] + # @param globals [optional] + # + def process_file(self, fIn, fOut, fname=None, globals=None): + + fInToClose = fOutToClose = None + # Convert filenames to files. + if isinstance(fIn, (str, bytes)): + # Open the input file. + sFileIn = fIn + fIn = fInToClose = self.openInputFile(fIn) + elif hasattr(fIn, 'name'): + sFileIn = fIn.name + else: + sFileIn = fname or '' + if isinstance(fOut, (str, bytes)): + # Open the output file. + sFileOut = fOut + fOut = fOutToClose = self.openOutputFile(fOut) + elif hasattr(fOut, 'name'): + sFileOut = fOut.name + else: + sFileOut = fname or '' + + try: + fIn = NumberedFileReader(fIn) + + bSawCog = False + + # The globals dict we will use for this file. + if globals is None: + globals = {} + # list of include files that are guarded against inclusion + globals['_guard_include'] = [] + + # If there are any global defines, put them in the globals. + globals.update(self.options.defines) + + # global flag for code generation + globals['_generate_code'] = True + + # loop over generator chunks + l = fIn.readline() + gen = None + while l and globals['_generate_code']: + + if gen is None: + # have a generator ready + # for marker check and error reporting + gen = CodeGenerator(self, globals, sFileOut, str(sFileIn)) + gen.setOutput(stdout=self._stdout) + + # Find the next spec begin + while l and not gen.line_is_start_marker(l): + if gen.line_is_end_marker(l): + gen._snippet_offset = fIn.linenumber() + gen.error("Unexpected '%s'" % gen._end_marker, + frame_index=-1, snippet_lineno=0) + if gen.line_is_insert_marker(l): + gen._snippet_offset = fIn.linenumber() + gen.error("Unexpected '%s'" % gen._insert_marker, + frame_index=-1, snippet_lineno=0) + fOut.write(l) + l = fIn.readline() + if not l: + break + if not self.options.bDeleteCode: + fOut.write(l) + + # l is the begin spec + firstLineNum = fIn.linenumber() + # Start parsing the inline code spec + # Assure a new generator is in use + gen = CodeGenerator(self, globals, sFileOut, str(sFileIn)) + gen.setOutput(stdout=self._stdout) + gen.parse_start_marker(l, firstLineNum) + + gen.log('s{}: process {} #{}'.format(len(self.module_states), sFileIn, firstLineNum)) + # If the spec begin is also a spec end, then process the single + # line of code inside. + if gen.line_is_end_marker(l): + gen.parse_line(l, True) + # next line + l = fIn.readline() + else: + # Deal with an ordinary code block. + l = fIn.readline() + + # Get all the lines in the spec + while l and not gen.line_is_end_marker(l): + gen.parse_line(l) + if gen.line_is_start_marker(l): + gen.error("Code followed by unexpected '%s'" % gen._start_marker, + frame_index = -2, + snippet_lineno = fIn.linenumber() - firstLineNum) + if gen.line_is_insert_marker(l): + gen.error("Code followed by unexpected '%s'" % gen._insert_marker, + frame_index = -2, + snippet_lineno = fIn.linenumber() - firstLineNum) + if not self.options.bDeleteCode: + fOut.write(l) + l = fIn.readline() + if not l: + gen.error("Codegen block begun but never ended.", + frame_index = -2, snippet_lineno = 0) + # write out end spec line + if not self.options.bDeleteCode: + fOut.write(l) + gen.parse_end_marker(l) + # next line - may be trailing end spec line + l = fIn.readline() + if self._is_end_spec_trailer(l) and not gen.line_is_insert_marker(l): + fOut.write(l) + l = fIn.readline() + + # Eat all the lines in the output section. + while l and not gen.line_is_insert_marker(l): + if gen.line_is_start_marker(l): + gen.error("Unexpected '%s'" % gen._start_marker, + frame_index = -2, + snippet_lineno = fIn.linenumber() - firstLineNum) + if gen.line_is_end_marker(l): + gen.error("Unexpected '%s'" % gen._end_marker, + frame_index = -2, + snippet_lineno = fIn.linenumber() - firstLineNum) + l = fIn.readline() + + if not l: + # We reached end of file before we found the end output line. + gen.error("Missing '%s' before end of file." % gen._insert_marker, + frame_index = -2, + snippet_lineno = fIn.linenumber() - firstLineNum) + + # Write the output of the spec to be the new output if we're + # supposed to generate code. + if not self.options.bNoGenerate: + sGen = gen.evaluate() + fOut.write(sGen) + if not globals['_generate_code']: + # generator code snippet stopped code generation + break + + bSawCog = True + + if not self.options.bDeleteCode: + fOut.write(l) + l = fIn.readline() + + if not bSawCog and self.options.bWarnEmpty: + self.warning("no codegen code found in %s" % sFileIn) + finally: + if fInToClose: + fInToClose.close() + if fOutToClose: + fOutToClose.close() + + def saveIncludePath(self): + self.savedInclude = self.options.includePath[:] + self.savedSysPath = sys.path[:] + + def restoreIncludePath(self): + self.options.includePath = self.savedInclude + self.cogmodule.path = self.options.includePath + sys.path = self.savedSysPath + + def addToIncludePath(self, includePath): + self.cogmodule.path.extend(includePath) + sys.path.extend(includePath) + + ## + # @brief process one file through CodeGen + # + # @param sFile file name + # + def _process_one_file(self, sFile): + """ Process one filename through cog. + """ + + self.saveIncludePath() + bNeedNewline = False + + try: + self.addToIncludePath(self.options.includePath) + # Since we know where the input file came from, + # push its directory onto the include path. + self.addToIncludePath([os.path.dirname(sFile)]) + + # How we process the file depends on where the output is going. + if self.options.sOutputName: + self.process_file(sFile, self.options.sOutputName, sFile) + else: + self.process_file(sFile, self.stdout, sFile) + finally: + self.restoreIncludePath() + + def callableMain(self, argv): + """ All of command-line codegen, but in a callable form. + This is used by main. + argv is the equivalent of sys.argv. + """ + argv = argv[1:] + + self.options.parse_args(argv) + + if self.options.input_file is None: + raise FileNotFoundError("No files to process") + + self._process_one_file(self.options.input_file) diff --git a/scripts/codegen/config.py b/scripts/codegen/config.py new file mode 100644 index 0000000000000..02c45f7a68ca3 --- /dev/null +++ b/scripts/codegen/config.py @@ -0,0 +1,56 @@ +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +import os +import sys +import shlex +from pathlib import Path + +class ConfigMixin(object): + __slots__ = [] + + _autoconf = None + _autoconf_filename = None + + def _autoconf_assure(self): + if self._autoconf is None: + autoconf_file = self.cmake_variable("PROJECT_BINARY_DIR", None) + if autoconf_file is None: + raise self._get_error_exception( + "CMake variable PROJECT_BINARY_DIR not defined to codegen.", 2) + autoconf_file = Path(autoconf_file).joinpath('include/generated/autoconf.h') + if not autoconf_file.is_file(): + raise self._get_error_exception( + "Generated configuration {} not found/ no access.". + format(autoconf_file), 2) + autoconf = {} + with open(str(autoconf_file)) as autoconf_fd: + for line in autoconf_fd: + if not line.startswith('#'): + continue + if " " not in line: + continue + key, value = shlex.split(line)[1:] + autoconf[key] = value + self._autoconf = autoconf + self._autoconf_filename = str(autoconf_file) + + def config_property(self, property_name, default=""): + self._autoconf_assure() + property_value = self._autoconf.get(property_name, default) + if property_value == "": + raise self._get_error_exception( + "Config property '{}' not defined.".format(property_name), 1) + return property_value + + ## + # @brief Get all config properties. + # + # The property names are the ones autoconf.conf. + # + # @return A dictionary of config properties. + # + def config_properties(self): + self._autoconf_assure() + return self._autoconf diff --git a/scripts/codegen/edts.py b/scripts/codegen/edts.py new file mode 100644 index 0000000000000..7a1eaa63ad9d6 --- /dev/null +++ b/scripts/codegen/edts.py @@ -0,0 +1,31 @@ +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +from pathlib import Path +from dts.edtsdatabase import EDTSDatabase + +class EDTSMixin(object): + __slots__ = [] + + _edts = None + + def _edts_assure(self): + if self._edts is None: + edts_file = self.cmake_variable("GENERATED_EDTS") + edts_file = Path(edts_file) + if not edts_file.is_file(): + raise self._get_error_exception( + "Generated extended device tree database file '{}' not found/ no access.". + format(edts_file), 2) + self._edts = EDTSDatabase() + self._edts.load(str(edts_file)) + + ## + # @brief Get the extended device tree database. + # + # @return Extended device tree database. + # + def edts(self): + self._edts_assure() + return self._edts diff --git a/scripts/codegen/error.py b/scripts/codegen/error.py new file mode 100644 index 0000000000000..56cf4ddb2a6b5 --- /dev/null +++ b/scripts/codegen/error.py @@ -0,0 +1,68 @@ +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +import inspect +from pathlib import Path + +class Error(Exception): + pass + +class ErrorMixin(object): + __slots__ = [] + + ## + # @brief Get code generation error exception + # + # @note only for 'raise codegen.Error(msg)' in snippet + # + # @param msg exception message + # @param frame_index [optional] call frame index + # @param snippet_lineno [optional] line number within snippet + # @return code generation exception object + # + def _get_error_exception(self, msg, frame_index = 0, + snippet_lineno = 0): + if frame_index >= 0: + # There are frames to get data from + frame_index += 1 + frame = inspect.currentframe() + try: + while frame_index > 0: + frame = frame.f_back + frame_index -= 1 + (filename, snippet_lineno, function, code_context, index) = \ + inspect.getframeinfo(frame) + except: + pass + finally: + del frame + input_lineno = self._snippet_offset + self._get_snippet_lineno(snippet_lineno) + error_msg = "{} #{} ({}, line {}): {}".format( + Path(self._snippet_file).name, + input_lineno, self._get_snippet_id(), + snippet_lineno, msg) + listing = self._list_snippet() + if listing: + error_msg = listing + '\n' + error_msg + else: + listing = self._list_lines() + if listing: + error_msg= listing + '\n' + error_msg + return Error(error_msg) + + ## + # @brief Raise Error exception. + # + # Extra information is added that maps the python snippet + # line seen by the Python interpreter to the line of the file + # that inlines the python snippet. + # + # @param msg [optional] exception message + # @param frame_index [optional] call frame index + # @param snippet_lineno [optional] line number within snippet + # + def error(self, msg = 'Error raised by codegen generator.', + frame_index = 0, snippet_lineno = 0): + frame_index += 1 + raise self._get_error_exception(msg, frame_index, snippet_lineno) diff --git a/scripts/codegen/filereader.py b/scripts/codegen/filereader.py new file mode 100644 index 0000000000000..c567a5ca24dfc --- /dev/null +++ b/scripts/codegen/filereader.py @@ -0,0 +1,20 @@ +# Copyright 2004-2016, Ned Batchelder. +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: MIT + +class NumberedFileReader: + """ A decorator for files that counts the readline()'s called. + """ + def __init__(self, f): + self.f = f + self.n = 0 + + def readline(self): + l = self.f.readline() + if l: + self.n += 1 + return l + + def linenumber(self): + return self.n diff --git a/scripts/codegen/generic.py b/scripts/codegen/generic.py new file mode 100644 index 0000000000000..07e89bcaeed57 --- /dev/null +++ b/scripts/codegen/generic.py @@ -0,0 +1,51 @@ +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +from pathlib import Path + +class GenericMixin(object): + __slots__ = [] + + @staticmethod + def path_walk(top, topdown = False, followlinks = False): + """ + See Python docs for os.walk, exact same behavior but it yields Path() + instances instead + + Form: http://ominian.com/2016/03/29/os-walk-for-pathlib-path/ + """ + names = list(top.iterdir()) + + dirs = (node for node in names if node.is_dir() is True) + nondirs = (node for node in names if node.is_dir() is False) + + if topdown: + yield top, dirs, nondirs + + for name in dirs: + if followlinks or name.is_symlink() is False: + for x in path_walk(name, topdown, followlinks): + yield x + + if topdown is not True: + yield top, dirs, nondirs + + # + # @param marker Marker as b'my-marker' + # + @staticmethod + def template_files(top, marker, suffix='.c'): + sources = [] + for path, directory_names, file_names in CodeGen.path_walk(top): + sources.extend([x for x in file_names if x.suffix == suffix]) + + templates = [] + for source_file in sources: + if os.stat(source_file).st_size == 0: + continue + with open(source_file, 'rb', 0) as source_file_fd: + s = mmap.mmap(source_file_fd.fileno(), 0, access=mmap.ACCESS_READ) + if s.find(marker) != -1: + templates.append(source_file) + return templates diff --git a/scripts/codegen/guard.py b/scripts/codegen/guard.py new file mode 100644 index 0000000000000..0cf8a11a22eb6 --- /dev/null +++ b/scripts/codegen/guard.py @@ -0,0 +1,16 @@ +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +class GuardMixin(object): + __slots__ = [] + + + def outl_guard_config(self, property_name): + is_config = self.config_property(property_name, 0) + self.outl("#if {} // Guard({}) {}".format( + is_config, is_config, property_name)) + + def outl_unguard_config(self, property_name): + is_config = self.config_property(property_name, 0) + self.outl("#endif // Guard({}) {}".format(is_config, property_name)) diff --git a/scripts/codegen/importmodule.py b/scripts/codegen/importmodule.py new file mode 100644 index 0000000000000..3f4e632059d9e --- /dev/null +++ b/scripts/codegen/importmodule.py @@ -0,0 +1,37 @@ +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +import sys +import os +import importlib +from pathlib import Path + +class ImportMixin(object): + __slots__ = [] + + ## + # @brief Import a CodeGen module. + # + # Import a module from the codegen/modules package. + # + # @param name Module to import. Specified without any path. + # + def import_module(self, name): + try: + module_file = self.zephyr_path().joinpath( + "scripts/codegen/modules/{}.py".format(name)).resolve() + except FileNotFoundError: + # Python 3.4/3.5 will throw this exception + # Python >= 3.6 will not throw this exception + module_file = self.zephyr_path().joinpath( + "scripts/codegen/modules/{}.py".format(name)) + if not module_file.is_file(): + raise self._get_error_exception( + "Module file '{}' of module '{}' does not exist or is no file.". + format(module_file, name), 1) + sys.path.append(os.path.dirname(str(module_file))) + module = importlib.import_module(name) + sys.path.pop() + self.generator_globals[name] = module + diff --git a/scripts/codegen/include.py b/scripts/codegen/include.py new file mode 100644 index 0000000000000..54ea51d48eba9 --- /dev/null +++ b/scripts/codegen/include.py @@ -0,0 +1,67 @@ +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +from pathlib import Path +import io + +from .error import Error + +class IncludeMixin(object): + __slots__ = [] + + def out_include(self, include_file): + try: + input_file = Path(include_file).resolve() + except FileNotFoundError: + # Python 3.4/3.5 will throw this exception + # Python >= 3.6 will not throw this exception + input_file = Path(include_file) + if not input_file.is_file(): + # don't resolve upfront + input_file = Path(include_file) + # try to find the file in the templates directory + expanded_file_path = self.zephyr_path().joinpath( + "scripts/codegen/templates") + if 'templates' in input_file.parts: + templates_seen = False + else: + # assume the path starts after templates + templates_seen = True + # append the remaining part behind templates + for part in input_file.parts: + if templates_seen: + expanded_file_path = expanded_file_path.joinpath(part) + elif part is 'templates': + templates_seen = True + if expanded_file_path.is_file(): + input_file = expanded_file_path + if not input_file.is_file(): + raise self._get_error_exception( + "Include file '{}' does not exist or is no file.". + format(input_file), 1) + if str(input_file) in self.generator_globals['_guard_include']: + self.log('------- include guarded {} - multiple inclusion of include file.'. + format(str(input_file))) + else: + output_fd = io.StringIO() + # delete inline code in included files + delete_code = self.processor.options.bDeleteCode + self.processor.options.bDeleteCode = True + self.log('------- include start {}'.format(input_file)) + self.processor.process_file(str(input_file), output_fd, + globals=self.generator_globals) + self.log(output_fd.getvalue()) + self.log('------- include end {}'.format(input_file)) + self.processor.options.bDeleteCode = delete_code + self.out(output_fd.getvalue()) + + def guard_include(self): + if self._snippet_file in self.generator_globals['_guard_include']: + # This should never happen + raise self._get_error_exception( + "Multiple inclusion of guarded include file '{}'.". + format(self._snippet_file), 1) + self.log('------- include guard {}'.format(self._snippet_file)) + self.generator_globals['_guard_include'].append(self._snippet_file) + diff --git a/scripts/codegen/log.py b/scripts/codegen/log.py new file mode 100644 index 0000000000000..7ee81bad6ac6e --- /dev/null +++ b/scripts/codegen/log.py @@ -0,0 +1,41 @@ +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +from pathlib import Path + +class LogMixin(object): + __slots__ = [] + + _log_fd = None + + def log(self, message, message_type=None, end="\n", logonly=True): + if self._log_fd is None: + if self.options.log_file is not None: + log_file = Path(self.options.log_file) + if not log_file.is_file(): + # log file will be created + # add preamble + preamble = "My preamble\n{}".format(message) + self._log_fd = open(str(log_file), 'a') + if message_type is None: + message_type = '' + else: + message_type = message_type+': ' + if self._log_fd is not None: + for line in message.splitlines(): + self._log_fd.write("{}{}{}".format(message_type, line, end)) + if not logonly: + print(message_type+message, file=self._stderr, end=end) + + def msg(self, s): + self.log(s, message_type='message', logonly=False) + + def warning(self, s): + self.log(s, message_type='warning', logonly=False) + + def prout(self, s, end="\n"): + self.log(s, message_type=None, end=end, logonly=False) + + def prerr(self, s, end="\n"): + self.log(s, message_type='error', end=end, logonly=False) diff --git a/scripts/codegen/modules/__init__.py b/scripts/codegen/modules/__init__.py new file mode 100644 index 0000000000000..270dcebaa5f4e --- /dev/null +++ b/scripts/codegen/modules/__init__.py @@ -0,0 +1 @@ +from .modules import * diff --git a/scripts/codegen/modules/devicedeclare.py b/scripts/codegen/modules/devicedeclare.py new file mode 100644 index 0000000000000..a7bcbc03994f3 --- /dev/null +++ b/scripts/codegen/modules/devicedeclare.py @@ -0,0 +1,417 @@ +# Copyright (c) 2018 Linaro Limited +# Copyright (c) 2018 Bobby Noelte +# +# SPDX-License-Identifier: Apache-2.0 + +import pprint +import re +import codegen + +from string import Template + +_device_and_api_init_tmpl = \ + 'DEVICE_AND_API_INIT( \\\n' + \ + '\t${device-name}, \\\n' + \ + '\t"${driver-name}", \\\n' + \ + '\t${device-init}, \\\n' + \ + '\t&${device-data}, \\\n' + \ + '\t&${device-config-info}, \\\n' + \ + '\t${device-level}, \\\n' + \ + '\t${device-prio}, \\\n' + \ + '\t&${device-api});' + +## +# Aliases for EDTS property paths. +_property_path_aliases = [ + ('reg/0/address/0', 'reg/address'), + ('reg/0/size/0', 'reg/size'), +] + +## +# @brief Get device name +# +# Device name is generated from +# - device compatible +# - bus master address if the device is connected to a bus master +# - device address +# - parent device address if the device does not have a device address +def _device_name(device_id): + device = codegen.edts().get_device_by_device_id(device_id) + device_name = device.get_property('compatible/0', None) + if device_name is None: + codegen.error("No compatible property for device id '{}'." + .format(device_id)) + + # bus_master_device_id = device.get_property('bus/master', None) + # if bus_master_device_id is not None: + # bus_master_device = codegen.edts().get_device_by_device_id(bus_master_device_id) + # reg = bus_master_device.get_property('reg') + # try: + # # use always the first key to get first address inserted into dict + # # because reg_index may be number or name + # # reg//address/ : address + # for reg_index in reg: + # for address_index in reg[reg_index]['address']: + # bus = reg[reg_index]['address'][address_index] + # device_name += '_' + hex(bus)[2:].zfill(8) + # break + # break + # except: + # # this device is missing the register directive + # codegen.error("No bus master register address property for device id '{}'." + # .format(bus_master_device_id)) + + reg = device.get_property('reg', None) + if reg is None: + # no reg property - take the reg property of the parent device + parent_device_id = device.get_property('parent-device', None) + if parent_device_id: + parent_device = codegen.edts().get_device_by_device_id(parent_device_id) + reg = parent_device.get_property(parent_device_id, 'reg', None) + device_address = None + if reg is not None: + try: + # use always the first key to get first address inserted into dict + # because reg_index may be number or name + # reg//address/ : address + for reg_index in reg: + for address_index in reg[reg_index]['address']: + address = reg[reg_index]['address'][address_index] + device_address = hex(address)[2:].zfill(8) + break + break + except: + # this device is missing the register directive + pass + if device_address is None: + # use label instead of address + device_address = device.get_property('label','') + # Warn about missing reg property + codegen.log("No register address property for device id '{}'." + .format(device_id), "warning", "\n") + device_name += '_' + device_address + + device_name = device_name.replace("-", "_"). \ + replace(",", "_"). \ + replace(";", "_"). \ + replace("@", "_"). \ + replace("#", "_"). \ + replace("&", "_"). \ + replace("/", "_"). \ + lower() + return device_name + +class _DeviceLocalTemplate(Template): + # pattern is ${} + # never starts with / + # extend default pattern by '-' '/' ',' + idpattern = r'[_a-z][_a-z0-9\-/,]*' + +class _DeviceCustomTemplate(Template): + # pattern is ${} + # never starts with / + # extend default pattern by '-' '/' ',', '(', ')', '.' + idpattern = r'[_a-z][_a-z0-9\-/,()\'.]*' + +class _DeviceGlobalTemplate(Template): + # pattern is ${:} + # device ID is the same as node address + # always starts with / + # extend default pattern by '-', '@', '/', ':' + idpattern = r'/[_a-z0-9\-/,@:]*' + + +## +# @brief Substitude values in device template +# +def _device_custom_template_substitute(template, device_id): + + class CustomMapping: + def __getitem__(self, key): + # Need to play with device so it could be visible in eval() + bogus = device.get_unique_id() + cmd = 'device' + '.' + key + return eval(cmd) + + # add device properties from device tree + device = codegen.edts().get_device_by_device_id(device_id) + + substituted = _DeviceCustomTemplate(template).safe_substitute(CustomMapping()) + + return substituted + +## +# @brief Substitude values in device template +# +def _device_template_substitute(template, device_id, preset={}): + + # substitute device local placeholders ${}, config, ... + mapping = {} + # add preset mapping + mapping.update(preset) + # add device properties from device tree + device = codegen.edts().get_device_by_device_id(device_id) + mapping.update(device.properties_flattened()) + # add specific device declaration vars/ constants + mapping['device-name'] = mapping.get('device-name', + _device_name(device_id)) + mapping['driver-name'] = mapping.get('driver-name', + device.get_property('label').strip('"')) + mapping['device-data'] = mapping.get('device-data', + "{}_data".format(device.get_unique_id())) + mapping['device-config-info'] = mapping.get('device-config-info', + "{}_config".format(device.get_unique_id())) + mapping['device-config-irq'] = mapping.get('device-config-irq', + "{}_config_irq".format(device.get_unique_id())) + substituted = _DeviceLocalTemplate(template).safe_substitute(mapping) + + # substitute device global placeholders ${:} + # + # we need a second substitude to allow for device indirections + # ${${}:} + mapping = {} + for device_id in codegen.edts()['devices']: + path_prefix = device_id + ':' + device = codegen.edts().get_device_by_device_id(device_id) + mapping.update(device.properties_flattened(path_prefix)) + # add specific device declaration vars/ constants + try: + mapping[path_prefix + 'device-name'] = _device_name(device_id) + mapping[path_prefix + 'driver-name'] = \ + codegen.edts().device_property(device_id, 'label').strip('"') + except: + # will be obvious if any of this is needed, just skip here + pass + + # add aliases to mapping + aliases_mapping = {} + for property_path, property_value in mapping.items(): + for alias_property_path, alias in _property_path_aliases: + if property_path.endswith(alias_property_path): + property_path = property_path[:-len(alias_property_path)] \ + + alias + aliases_mapping[property_path] = property_value + mapping.update(aliases_mapping) + + substituted = _DeviceGlobalTemplate(substituted).safe_substitute(mapping) + + return substituted + + +# +# @return True if device is declared, False otherwise +def device_declare_single(device_config, + driver_name, + device_init, + device_level, + device_prio, + device_api, + device_info, + device_defaults = {}): + device_configured = codegen.config_property(device_config, '') + + codegen.outl('\n#ifdef {}\n'.format(device_config)) + + device_id = codegen.edts().get_device_id_by_label(driver_name) + if device_id is None: + # this should not happen + raise codegen.Error("Did not find driver name '{}'.".format(driver_name)) + + # Presets for mapping this device data to template + preset = device_defaults + preset['device-init'] = device_init + preset['device-level'] = device_level + preset['device-prio'] = device_prio + preset['device-api'] = device_api + preset['device-config'] = device_config + preset['driver-name'] = driver_name.strip('"') + + # + # device info + if device_info: + device_info = _device_template_substitute(device_info, device_id, + preset) + device_info = _device_custom_template_substitute(device_info, device_id) + codegen.outl(device_info) + # + # device init + codegen.outl(_device_template_substitute(_device_and_api_init_tmpl, + device_id, preset)) + + codegen.outl('') + codegen.outl('#endif /* {} */\n'.format(device_config)) + + return True + +## +# @param device_configs +# A list of configuration variables for device instantiation. +# (e.g. ['CONFIG_SPI_0', 'CONFIG_SPI_1']) +# @param driver_names +# A list of driver names for device instantiation. The list shall be ordered +# as the list of device configs. +# (e.g. ['SPI_0', 'SPI_1']) +# @param device_inits +# A list of device initialisation functions or a one single function. The +# list shall be ordered as the list of device configs. +# (e.g. 'spi_stm32_init') +# @param device_levels +# A list of driver initialisation levels or one single level definition. The +# list shall be ordered as the list of device configs. +# (e.g. 'PRE_KERNEL_1') +# @param device_prios +# A list of driver initialisation priorities or one single priority +# definition. The list shall be ordered as the list of device configs. +# (e.g. 32) +# @param device_api +# Identifier of the device api. +# (e.g. 'spi_stm32_driver_api') +# @param device_info +# Device info template for device driver config, data and interrupt +# initialisation. +# @param device_defaults +# Device default property values. `device_defaults` is a dictionary of +# property path : property value. +# +def device_declare_multi(device_configs, + driver_names, + device_inits, + device_levels, + device_prios, + device_api, + device_info, + device_defaults = {}): + devices_declared = [] + for i, device_config in enumerate(device_configs): + driver_name = driver_names[i] + if isinstance(device_inits, str): + device_init = device_inits + else: + try: + device_init = device_inits[i] + except: + device_init = device_inits + if isinstance(device_levels, str): + device_level = device_levels + else: + try: + device_level = device_levels[i] + except: + device_level = device_levels + if isinstance(device_prios, str): + device_prio = device_prios + else: + try: + device_prio = device_prios[i] + except: + device_prio = device_prios + + device_declared = device_declare_single(device_config, + driver_name, + device_init, + device_level, + device_prio, + device_api, + device_info, + device_defaults) + devices_declared.append(str(device_declared)) + + if 'True' not in devices_declared: + err = "No active device found for {} = {} and {}.".format( + ', '.join(device_configs), ', '.join(devices_declared), + ', '.join(driver_names)) + codegen.log(err) + raise codegen.Error(err) + + +def _device_generate_struct(type_of_struct, _struct): + if _struct is None and type_of_struct == 'config': + return 'static const int ${device-config-info}[] = {};\n' + elif _struct is None and type_of_struct == 'data': + return 'static int ${device-data}[] = {};\n' + + struct = "" + # convert _struct into a list. Struct might have only one element + if type(_struct) is str: + _struct = [_struct] + else: + _struct = list(_struct) + + if type_of_struct == 'config': + struct += 'static const struct {} ${{device-config-info}} = {{\n'.format(_struct[0]) + elif type_of_struct == 'data': + struct += 'static struct {} ${{device-data}} = {{\n'.format(_struct[0]) + else: + msg("Not expected") + + if len(_struct) > 1: + struct += _struct[1] + + struct += '};\n\n' + return struct + + +def _device_generate_irq_bootstrap(irq_names, irq_flag, irq_func): + irq_bootstrap_info = \ + '#ifdef {}\n'.format(irq_flag) + \ + 'DEVICE_DECLARE(${device-name});\n' + \ + 'static void ${device-config-irq}(struct device *dev)\n' + \ + '{\n' + for index, irq_name in enumerate(irq_names): + irq_name = irq_names[index] + irq_num = '${{get_property(\'interrupts/{}/irq\')}}'.format(index) + irq_prio = '${{get_property(\'interrupts/{}/priority\')}}'.format(index) + irq_bootstrap_info += \ + '\tIRQ_CONNECT({},\n'.format(irq_num) + \ + '\t\t{},\n'.format(irq_prio) + if len(irq_names) == 1 and irq_name == '0': + # Only one irq and no name associated. Keep it simple name + irq_bootstrap_info += '\t\t{},\n'.format(irq_func) + else: + irq_bootstrap_info += '\t\t{}_{},\n'.format(irq_func, irq_name) + irq_bootstrap_info += \ + '\t\tDEVICE_GET(${device-name}),\n' + \ + '\t\t0);\n' + \ + '\tirq_enable({});\n\n'.format(irq_num) + irq_bootstrap_info += \ + '}\n' + \ + '#endif /* {} */\n\n'.format(irq_flag) + return irq_bootstrap_info + + +def device_declare(compatibles, init_prio_flag, kernel_level, irq_func, + init_func, api, data_struct, config_struct): + + config_struct = _device_generate_struct('config', config_struct) + data_struct = _device_generate_struct('data', data_struct) + + if api is None: + api = "(*(const int *)0)" + + for device_id in codegen.edts().get_device_ids_by_compatible(compatibles): + device_info = "" + device = codegen.edts().get_device_by_device_id(device_id) + driver_name = device.get_property('label') + device_config = "CONFIG_{}".format(driver_name.strip('"')) + interrupts = device.get_property('interrupts', None) + + + if interrupts is not None: + try: + irq_names = list(interrupts[dev]['name'] for dev in interrupts) + except: + irq_names = list(interrupts) + + device_info += _device_generate_irq_bootstrap( + irq_names, irq_func['irq_flag'], irq_func['irq_func']) + + device_info += config_struct + device_info += data_struct + + device_declare_single(device_config, + driver_name, + init_func, + kernel_level, + init_prio_flag, + api, + device_info) diff --git a/scripts/codegen/options.py b/scripts/codegen/options.py new file mode 100644 index 0000000000000..312509353852d --- /dev/null +++ b/scripts/codegen/options.py @@ -0,0 +1,139 @@ +# Copyright 2004-2016, Ned Batchelder. +# http://nedbatchelder.com/code/cog +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: MIT + +import os +import argparse + +class Options(object): + + @staticmethod + def is_valid_directory(parser, arg): + if not os.path.isdir(arg): + parser.error('The directory {} does not exist!'.format(arg)) + else: + # File directory so return the directory + return arg + + @staticmethod + def is_valid_file(parser, arg): + if not os.path.isfile(arg): + parser.error('The file {} does not exist!'.format(arg)) + else: + # File exists so return the file + return arg + + def __init__(self): + # Defaults for argument values. + self.args = [] + self.includePath = [] + self.defines = {} + self.bNoGenerate = False + self.sOutputName = None + self.bWarnEmpty = False + self.bDeleteCode = False + self.bNewlines = False + self.sEncoding = "utf-8" + self.verbosity = 2 + + self._parser = argparse.ArgumentParser( + description='Generate code with inlined Python code.') + self._parser.add_argument('-d', '--delete-code', + dest='bDeleteCode', action='store_true', + help='Delete the generator code from the output file.') + self._parser.add_argument('-D', '--define', nargs=1, metavar='DEFINE', + dest='defines', action='append', + help='Define a global string available to your generator code.') + self._parser.add_argument('-e', '--warn-empty', + dest='bWarnEmpty', action='store_true', + help='Warn if a file has no generator code in it.') + self._parser.add_argument('-U', '--unix-newlines', + dest='bNewlines', action='store_true', + help='Write the output with Unix newlines (only LF line-endings).') + self._parser.add_argument('-I', '--include', nargs=1, metavar='DIR', + dest='includePath', action='append', + type=lambda x: self.is_valid_directory(self._parser, x), + help='Add DIR to the list of directories for data files and modules.') + self._parser.add_argument('-n', '--encoding', nargs=1, + dest='sEncoding', action='store', metavar='ENCODING', + type=lambda x: self.is_valid_file(self._parser, x), + help='Use ENCODING when reading and writing files.') + self._parser.add_argument('-i', '--input', nargs=1, metavar='FILE', + dest='input_file', action='store', + type=lambda x: self.is_valid_file(self._parser, x), + help='Get the input from FILE.') + self._parser.add_argument('-o', '--output', nargs=1, metavar='FILE', + dest='sOutputName', action='store', + help='Write the output to FILE.') + self._parser.add_argument('-l', '--log', nargs=1, metavar='FILE', + dest='log_file', action='store', + help='Log to FILE.') + + def __str__(self): + sb = [] + for key in self.__dict__: + sb.append("{key}='{value}'".format(key=key, value=self.__dict__[key])) + return ', '.join(sb) + + def __repr__(self): + return self.__str__() + + + def __eq__(self, other): + """ Comparison operator for tests to use. + """ + return self.__dict__ == other.__dict__ + + def clone(self): + """ Make a clone of these options, for further refinement. + """ + return copy.deepcopy(self) + + def parse_args(self, argv): + args = self._parser.parse_args(argv) + # set options + self.bDeleteCode = args.bDeleteCode + self.bWarnEmpty = args.bWarnEmpty + self.bNewlines = args.bNewlines + if args.includePath is None: + self.includePath = [] + else: + self.includePath = args.includePath + self.sEncoding = args.sEncoding + if args.input_file is None: + self.input_file = None + else: + self.input_file = args.input_file[0] + if args.sOutputName is None: + self.sOutputName = None + else: + self.sOutputName = args.sOutputName[0] + if args.log_file is None: + self.log_file = None + else: + self.log_file = args.log_file[0] + self.defines = {} + if args.defines is not None: + for define in args.defines: + d = define[0].split('=') + if len(d) > 1: + value = d[1] + else: + value = None + self.defines[d[0]] = value + + def addToIncludePath(self, dirs): + """ Add directories to the include path. + """ + dirs = dirs.split(os.pathsep) + self.includePath.extend(dirs) + + + +class OptionsMixin(object): + __slots__ = [] + + def option(self, option_name): + return getattr(self.options, option_name) diff --git a/scripts/codegen/output.py b/scripts/codegen/output.py new file mode 100644 index 0000000000000..647002b3ed53f --- /dev/null +++ b/scripts/codegen/output.py @@ -0,0 +1,19 @@ +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + + +class OutputMixin(object): + __slots__ = [] + + def msg(self, s): + self.prout("Message: "+s) + + def out(self, sOut='', dedent=False, trimblanklines=False): + self._out(sOut, dedent, trimblanklines) + + def outl(self, sOut='', **kw): + """ The cog.outl function. + """ + self._out(sOut, **kw) + self._out('\n') diff --git a/scripts/codegen/redirectable.py b/scripts/codegen/redirectable.py new file mode 100644 index 0000000000000..3cdc49b25e5f2 --- /dev/null +++ b/scripts/codegen/redirectable.py @@ -0,0 +1,31 @@ +# Copyright 2004-2016, Ned Batchelder. +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: MIT + +import sys + +class RedirectableMixin(object): + __slots__ = [] + + def setOutput(self, stdout=None, stderr=None): + """ Assign new files for standard out and/or standard error. + """ + if stdout: + self._stdout = stdout + if stderr: + self._stderr = stderr + + def prout(self, s, end="\n"): + print(s, file=self._stdout, end=end) + + def prerr(self, s, end="\n"): + print(s, file=self._stderr, end=end) + + +class Redirectable(RedirectableMixin): + """ An object with its own stdout and stderr files. + """ + def __init__(self): + self._stdout = sys.stdout + self._stderr = sys.stderr diff --git a/scripts/codegen/whiteutils.py b/scripts/codegen/whiteutils.py new file mode 100644 index 0000000000000..2a128e4ab88f7 --- /dev/null +++ b/scripts/codegen/whiteutils.py @@ -0,0 +1,72 @@ +# Copyright 2004-2016, Ned Batchelder. +# http://nedbatchelder.com/code/cog +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: MIT + +import re + +def b(s): + return s.encode("latin-1") + +def whitePrefix(strings): + """ Determine the whitespace prefix common to all non-blank lines + in the argument list. + """ + # Remove all blank lines from the list + strings = [s for s in strings if s.strip() != ''] + + if not strings: return '' + + # Find initial whitespace chunk in the first line. + # This is the best prefix we can hope for. + pat = r'\s*' + if isinstance(strings[0], (bytes, )): + pat = pat.encode("utf8") + prefix = re.match(pat, strings[0]).group(0) + + # Loop over the other strings, keeping only as much of + # the prefix as matches each string. + for s in strings: + for i in range(len(prefix)): + if prefix[i] != s[i]: + prefix = prefix[:i] + break + return prefix + +def reindentBlock(lines, newIndent=''): + """ Take a block of text as a string or list of lines. + Remove any common whitespace indentation. + Re-indent using newIndent, and return it as a single string. + """ + sep, nothing = '\n', '' + if isinstance(lines, (bytes, )): + sep, nothing = b('\n'), b('') + if isinstance(lines, (str, bytes)): + lines = lines.split(sep) + oldIndent = whitePrefix(lines) + outLines = [] + for l in lines: + if oldIndent: + l = l.replace(oldIndent, nothing, 1) + if l and newIndent: + l = newIndent + l + outLines.append(l) + return sep.join(outLines) + +def commonPrefix(strings): + """ Find the longest string that is a prefix of all the strings. + """ + if not strings: + return '' + prefix = strings[0] + for s in strings: + if len(s) < len(prefix): + prefix = prefix[:len(s)] + if not prefix: + return '' + for i in range(len(prefix)): + if prefix[i] != s[i]: + prefix = prefix[:i] + break + return prefix diff --git a/scripts/codegen/zephyr.py b/scripts/codegen/zephyr.py new file mode 100644 index 0000000000000..3366b2643ba87 --- /dev/null +++ b/scripts/codegen/zephyr.py @@ -0,0 +1,12 @@ +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +from pathlib import Path + +class ZephyrMixin(object): + __slots__ = [] + + @staticmethod + def zephyr_path(): + return Path(__file__).resolve().parents[2] diff --git a/scripts/dts/edtsdatabase.py b/scripts/dts/edtsdatabase.py index 83666b37d8680..f881e0c35c839 100755 --- a/scripts/dts/edtsdatabase.py +++ b/scripts/dts/edtsdatabase.py @@ -11,7 +11,10 @@ import argparse import json import pprint -from edtsdevice import EDTSDevice +try: + from dts.edtsdevice import EDTSDevice +except: + from edtsdevice import EDTSDevice ## # @brief ETDS Database consumer @@ -92,7 +95,7 @@ def get_device_id_by_label(self, label): # @return (dict)device def get_device_by_device_id(self, device_id): try: - return EDTSDevice(self._edts['devices'][device_id]) + return EDTSDevice(self._edts['devices'], device_id) except: return None diff --git a/scripts/dts/edtsdevice.py b/scripts/dts/edtsdevice.py index 1929ede118e3f..28f3ff36c2a26 100755 --- a/scripts/dts/edtsdevice.py +++ b/scripts/dts/edtsdevice.py @@ -5,8 +5,9 @@ # class EDTSDevice: - def __init__(self, device): - self.device = device + def __init__(self, devices, dev_id): + self.all = devices + self.device = devices[dev_id] ## # @brief Get device tree property value for the device of the given device id. @@ -128,3 +129,27 @@ def get_irq_prop_types(self): # @return device unique id string def get_unique_id(self): return self.get_property('unique_id') + + def get_controller(self, property_path): + device_id = self.device['device-id'] + property_value = self.device + property_path_elems = property_path.strip("'").split('/') + # This method is made to find a controller after given path + # we expect only ditcs in the path and ['controller'] at the end. + for elem_index, key in enumerate(property_path_elems): + if isinstance(property_value, dict): + property_value = property_value.get(key, None) + controller = property_value.get('controller', None) + + # How to return a meaningfull error here? + # if controller is None: + + return EDTSDevice(self.all, controller) + + def get_parent(self, parent_type='bus'): + parent_device_id = '' + + for comp in self.device['device-id'].split('/')[1:-1]: + parent_device_id += '/' + comp + + return EDTSDevice(self.all, parent_device_id) diff --git a/scripts/dts/extract/reg.py b/scripts/dts/extract/reg.py index 5836d504df0e0..f40c07f578155 100644 --- a/scripts/dts/extract/reg.py +++ b/scripts/dts/extract/reg.py @@ -45,8 +45,8 @@ def populate_edts(self, node_address): # Newer versions of dtc might have the reg propertly look like # reg = <1 2>, <3 4>; # So we need to flatten the list in that case - if isinstance(props[0], list): - props = [item for sublist in props for item in sublist] + if isinstance(reg[0], list): + reg = [item for sublist in reg for item in sublist] (nr_address_cells, nr_size_cells) = get_addr_size_cells(node_address) diff --git a/scripts/dts_sanity/edts_ref.json b/scripts/dts_sanity/edts_ref.json index e051924d4f117..07d646b4e8fec 100644 --- a/scripts/dts_sanity/edts_ref.json +++ b/scripts/dts_sanity/edts_ref.json @@ -534,4 +534,4 @@ "unique_id": "REFSOC_SPI_44000000_REFSOC_SENSOR_SPI_0" } } -} \ No newline at end of file +} diff --git a/scripts/gen_code.py b/scripts/gen_code.py new file mode 100755 index 0000000000000..36719134ef740 --- /dev/null +++ b/scripts/gen_code.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018 Bobby Noelte. +# +# SPDX-License-Identifier: Apache-2.0 + +import sys + +from codegen.codegen import CodeGen + +if __name__ == '__main__': + ret = CodeGen().callableMain(sys.argv) + sys.exit(ret) diff --git a/tests/drivers/build_all/sensors_i_z.conf b/tests/drivers/build_all/sensors_i_z.conf index f0cb73bed6b1c..caa3fe6cad77d 100644 --- a/tests/drivers/build_all/sensors_i_z.conf +++ b/tests/drivers/build_all/sensors_i_z.conf @@ -15,7 +15,7 @@ CONFIG_LPS25HB=y CONFIG_LSM303DLHC_ACCEL=y CONFIG_LSM303DLHC_MAGN=y CONFIG_LSM6DS0=y -CONFIG_LSM6DSL=y +CONFIG_LSM6DSL=n CONFIG_LSM9DS0_GYRO=y CONFIG_LSM9DS0_MFD=y CONFIG_MAX30101=y diff --git a/tests/drivers/build_all/sensors_trigger_i_z.conf b/tests/drivers/build_all/sensors_trigger_i_z.conf index dd8bf8e39bb43..2713ac8249549 100644 --- a/tests/drivers/build_all/sensors_trigger_i_z.conf +++ b/tests/drivers/build_all/sensors_trigger_i_z.conf @@ -12,7 +12,7 @@ CONFIG_LIS2MDL=y CONFIG_LIS2MDL_TRIGGER_OWN_THREAD=y CONFIG_LIS3DH=y CONFIG_LIS3DH_TRIGGER_OWN_THREAD=y -CONFIG_LSM6DSL=y +CONFIG_LSM6DSL=n CONFIG_LSM6DSL_TRIGGER_OWN_THREAD=y CONFIG_LSM9DS0_GYRO=y CONFIG_LSM9DS0_GYRO_TRIGGERS=y