Browse Source

cmake: dts: import devicetree symbols into CMake

This commit introduces devicetree API in CMake so that devicetree
properties and register block can be used in the CMake build system.

The script scripts/dts/gen_dts_cmake.py processes the edt.pickle and
generates a corresponding devicetree property map in a devicetree_target
that is then used in CMake.

The following devicetree API has been made available in Zephyr CMake:
- dt_nodelabel(<var> NODELABEL <label>)
- dt_node_exists(<var> PATH <path>)
- dt_node_has_status(<var> PATH <path> STATUS <status>)
- dt_prop(<var> PATH <path> PROPERTY <prop>)
- dt_prop(<var> PATH <path> INDEX <idx> PROPERTY <prop>)
- dt_num_regs(<var> PATH <path>)
- dt_reg_addr(<var> PATH <path> [INDEX <idx>])
- dt_reg_size(<var> PATH <path> [INDEX <idx>])
- dt_has_chosen(<var> PROPERTY <prop>)
- dt_chosen(<var> PROPERTY <prop>)

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
Signed-off-by: Torsten Rasmussen <Torsten.Rasmussen@nordicsemi.no>
pull/37516/head
Torsten Rasmussen 4 years ago committed by Anas Nashif
parent
commit
fcf720941f
  1. 19
      cmake/dts.cmake
  2. 434
      cmake/extensions.cmake
  3. 143
      scripts/dts/gen_dts_cmake.py

19
cmake/dts.cmake

@ -27,6 +27,10 @@ set(DTS_POST_CPP ${PROJECT_BINARY_DIR}/${BOARD}.dts.pre.tmp) @@ -27,6 +27,10 @@ set(DTS_POST_CPP ${PROJECT_BINARY_DIR}/${BOARD}.dts.pre.tmp)
# This is relative to each element of DTS_ROOT.
set(VENDOR_PREFIXES dts/bindings/vendor-prefixes.txt)
# Devicetree in CMake.
set(DTS_CMAKE_SCRIPT ${ZEPHYR_BASE}/scripts/dts/gen_dts_cmake.py)
set(DTS_CMAKE ${PROJECT_BINARY_DIR}/dts.cmake)
set_ifndef(DTS_SOURCE ${BOARD_DIR}/${BOARD}.dts)
zephyr_file(APPLICATION_ROOT DTS_ROOT)
@ -175,6 +179,7 @@ if(SUPPORTS_DTS) @@ -175,6 +179,7 @@ if(SUPPORTS_DTS)
CMAKE_CONFIGURE_DEPENDS
${include_files}
${GEN_DEFINES_SCRIPT}
${DTS_CMAKE_SCRIPT}
)
#
@ -250,6 +255,20 @@ if(SUPPORTS_DTS) @@ -250,6 +255,20 @@ if(SUPPORTS_DTS)
message(STATUS "Generated device_extern.h: ${DEVICE_EXTERN_H}")
endif()
execute_process(
COMMAND ${PYTHON_EXECUTABLE} ${DTS_CMAKE_SCRIPT}
--edt-pickle ${EDT_PICKLE}
--cmake-out ${DTS_CMAKE}
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
RESULT_VARIABLE ret
)
if(NOT "${ret}" STREQUAL "0")
message(FATAL_ERROR "gen_dts_cmake.py failed with return code: ${ret}")
else()
message(STATUS "Including generated dts.cmake file: ${DTS_CMAKE}")
include(${DTS_CMAKE})
endif()
else()
file(WRITE ${DEVICETREE_UNFIXED_H} "/* WARNING. THIS FILE IS AUTO-GENERATED. DO NOT MODIFY! */")
file(WRITE ${DEVICE_EXTERN_H} "/* WARNING. THIS FILE IS AUTO-GENERATED. DO NOT MODIFY! */")

434
cmake/extensions.cmake

@ -19,6 +19,8 @@ @@ -19,6 +19,8 @@
# 3.3.1 Toolchain integration
# 3.4. Debugging CMake
# 3.5. File system management
# 4. Devicetree extensions
# 4.1 dt_*
########################################################
# 1. Zephyr-aware extensions
@ -2267,3 +2269,435 @@ function(target_byproducts) @@ -2267,3 +2269,435 @@ function(target_byproducts)
COMMENT "Logical command for additional byproducts on target: ${TB_TARGET}"
)
endfunction()
########################################################
# 4. Zephyr devicetree function
########################################################
# 4.1. dt_*
#
# The following methods are for retrieving devicetree information in CMake.
#
# Note: In CMake we refer to the nodes using the node's path, therefore there
# is no dt_path(...) function for obtaining a node identifier.
# Usage:
# dt_nodelabel(<var> NODELABEL <label>)
#
# Function for retrieving the node path for the node having nodelabel
# <label>.
#
# Example devicetree fragment:
#
# / {
# soc {
# nvic: interrupt-controller@e000e100 { ... };
# };
# };
#
# Example usage:
#
# # Sets 'nvic_path' to "/soc/interrupt-controller@e000e100"
# dt_nodelabel(nvic_path NODELABEL "nvic")
#
# The node's path will be returned in the <var> parameter.
# <var> will be undefined if node does not exist.
#
# <var> : Return variable where the node path will be stored
# NODELABEL <label> : Node label
function(dt_nodelabel var)
set(req_single_args "NODELABEL")
cmake_parse_arguments(DT_LABEL "" "${req_single_args}" "" ${ARGN})
if(${ARGV0} IN_LIST req_single_args)
message(FATAL_ERROR "dt_nodelabel(${ARGV0} ...) missing return parameter.")
endif()
foreach(arg ${req_single_args})
if(NOT DEFINED DT_LABEL_${arg})
message(FATAL_ERROR "dt_nodelabel(${ARGV0} ...) "
"missing required argument: ${arg}"
)
endif()
endforeach()
get_target_property(${var} devicetree_target "DT_NODELABEL|${DT_LABEL_NODELABEL}")
if(${${var}} STREQUAL ${var}-NOTFOUND)
set(${var})
endif()
set(${var} ${${var}} PARENT_SCOPE)
endfunction()
# Usage:
# dt_node_exists(<var> PATH <path>)
#
# Tests whether a node with path <path> exists in the devicetree.
#
# The result of the check, either TRUE or FALSE, will be returned in
# the <var> parameter.
#
# <var> : Return variable where the check result will be returned
# PATH <path> : Node path
function(dt_node_exists var)
set(req_single_args "PATH")
cmake_parse_arguments(DT_NODE "" "${req_single_args}" "" ${ARGN})
if(${ARGV0} IN_LIST req_single_args)
message(FATAL_ERROR "dt_node_existsl(${ARGV0} ...) missing return parameter.")
endif()
foreach(arg ${req_single_args})
if(NOT DEFINED DT_NODE_${arg})
message(FATAL_ERROR "dt_node_exists(${ARGV0} ...) "
"missing required argument: ${arg}"
)
endif()
endforeach()
get_target_property(${var} devicetree_target "DT_NODE|${DT_NODE_PATH}")
if(${var})
set(${var} ${${var}} PARENT_SCOPE)
else()
set(${var} FALSE PARENT_SCOPE)
endif()
endfunction()
# Usage:
# dt_node_has_status(<var> PATH <path> STATUS <status>)
#
# Tests whether <path> refers to a node which:
# - exists in the devicetree, and
# - has a status property matching the <status> argument
# (a missing status or an ok status is treated as if it
# were okay instead)
#
# The result of the check, either TRUE or FALSE, will be returned in
# the <var> parameter.
#
# <var> : Return variable where the check result will be returned
# PATH <path> : Node path
# STATUS <status> : Status to check
function(dt_node_has_status var)
set(req_single_args "PATH;STATUS")
cmake_parse_arguments(DT_NODE "" "${req_single_args}" "" ${ARGN})
if(${ARGV0} IN_LIST req_single_args)
message(FATAL_ERROR "dt_node_has_status(${ARGV0} ...) missing return parameter.")
endif()
foreach(arg ${req_single_args})
if(NOT DEFINED DT_NODE_${arg})
message(FATAL_ERROR "dt_node_has_status(${ARGV0} ...) "
"missing required argument: ${arg}"
)
endif()
endforeach()
dt_node_exists(${var} PATH ${DT_NODE_PATH})
if(NOT ${${var}})
set(${var} FALSE PARENT_SCOPE)
endif()
dt_prop(${var} PATH ${DT_NODE_PATH} PROPERTY status)
if(NOT DEFINED ${var} OR "${${var}}" STREQUAL ok)
set(${var} okay)
endif()
if(${var} STREQUAL ${DT_NODE_STATUS})
set(${var} TRUE PARENT_SCOPE)
else()
set(${var} FALSE PARENT_SCOPE)
endif()
endfunction()
# Usage:
#
# dt_prop(<var> PATH <path> PROPERTY <prop> [INDEX <idx>])
#
# Get a devicetree property value. The value will be returned in the
# <var> parameter.
#
# This function currently only supports properties with the following
# devicetree binding types: string, int, boolean, array, uint8-array,
# string-array, path.
#
# For array valued properties (including uint8-array and
# string-array), the entire array is returned as a CMake list unless
# INDEX is given. If INDEX is given, just the array element at index
# <idx> is returned.
#
# The property value will be returned in the <var> parameter if the
# node exists and has a property <prop> with one of the above types.
# <var> will be undefined otherwise.
#
# To test if the property is defined before using it, use DEFINED on
# the return <var>, like this:
#
# dt_prop(reserved_ranges PATH "/soc/gpio@deadbeef" PROPERTY "gpio-reserved-ranges")
# if(DEFINED reserved_ranges)
# # Node exists and has the "gpio-reserved-ranges" property.
# endif()
#
# To distinguish a missing node from a missing property, combine
# dt_prop() and dt_node_exists(), like this:
#
# dt_node_exists(node_exists PATH "/soc/gpio@deadbeef")
# dt_prop(reserved_ranges PATH "/soc/gpio@deadbeef" PROPERTY "gpio-reserved-ranges")
# if(DEFINED reserved_ranges)
# # Node "/soc/gpio@deadbeef" exists and has the "gpio-reserved-ranges" property
# elseif(node_exists)
# # Node exists, but doesn't have the property, or the property has an unsupported type.
# endif()
#
# <var> : Return variable where the property value will be stored
# PATH <path> : Node path
# PROPERTY <prop>: Property for which a value should be returned, as it
# appears in the DTS source
# INDEX <idx> : Optional index when retrieving a value in an array property
function(dt_prop var)
set(req_single_args "PATH;PROPERTY")
set(single_args "INDEX")
cmake_parse_arguments(DT_PROP "" "${req_single_args};${single_args}" "" ${ARGN})
if(${ARGV0} IN_LIST req_single_args)
message(FATAL_ERROR "dt_prop(${ARGV0} ...) missing return parameter.")
endif()
foreach(arg ${req_single_args})
if(NOT DEFINED DT_PROP_${arg})
message(FATAL_ERROR "dt_prop(${ARGV0} ...) "
"missing required argument: ${arg}"
)
endif()
endforeach()
get_property(exists TARGET devicetree_target
PROPERTY "DT_PROP|${DT_PROP_PATH}|${DT_PROP_PROPERTY}"
SET
)
if(NOT exists)
set(${var} PARENT_SCOPE)
return()
endif()
get_target_property(val devicetree_target
"DT_PROP|${DT_PROP_PATH}|${DT_PROP_PROPERTY}"
)
if(DEFINED DT_PROP_INDEX)
list(GET val ${DT_PROP_INDEX} element)
set(${var} "${element}" PARENT_SCOPE)
else()
set(${var} "${val}" PARENT_SCOPE)
endif()
endfunction()
# Usage:
# dt_num_regs(<var> PATH <path>)
#
# Get the number of register blocks in the node's reg property;
# this may be zero.
#
# The value will be returned in the <var> parameter.
#
# <var> : Return variable where the property value will be stored
# PATH <path> : Node path
function(dt_num_regs var)
set(req_single_args "PATH")
cmake_parse_arguments(DT_REG "" "${req_single_args}" "" ${ARGN})
if(${ARGV0} IN_LIST req_single_args)
message(FATAL_ERROR "dt_num_regs(${ARGV0} ...) missing return parameter.")
endif()
foreach(arg ${req_single_args})
if(NOT DEFINED DT_REG_${arg})
message(FATAL_ERROR "dt_num_regs(${ARGV0} ...) "
"missing required argument: ${arg}"
)
endif()
endforeach()
get_target_property(${var} devicetree_target "DT_REG|${DT_REG_PATH}|NUM")
set(${var} ${${var}} PARENT_SCOPE)
endfunction()
# Usage:
# dt_reg_addr(<var> PATH <path> [INDEX <idx>])
#
# Get the base address of the register block at index <idx>.
# If <idx> is omitted, then the value at index 0 will be returned.
#
# The value will be returned in the <var> parameter.
#
# Results can be:
# - The base address of the register block
# - <var> will be undefined if node does not exists or does not have a register
# block at the requested index.
#
# <var> : Return variable where the address value will be stored
# PATH <path> : Node path
# INDEX <idx> : Index number
function(dt_reg_addr var)
set(req_single_args "PATH")
set(single_args "INDEX")
cmake_parse_arguments(DT_REG "" "${req_single_args};${single_args}" "" ${ARGN})
if(${ARGV0} IN_LIST req_single_args)
message(FATAL_ERROR "dt_reg_addr(${ARGV0} ...) missing return parameter.")
endif()
foreach(arg ${req_single_args})
if(NOT DEFINED DT_REG_${arg})
message(FATAL_ERROR "dt_reg_addr(${ARGV0} ...) "
"missing required argument: ${arg}"
)
endif()
endforeach()
if(NOT DEFINED DT_REG_INDEX)
set(DT_REG_INDEX 0)
endif()
get_target_property(${var}_list devicetree_target "DT_REG|${DT_REG_PATH}|ADDR")
list(GET ${var}_list ${DT_REG_INDEX} ${var})
if("${var}" STREQUAL NONE)
set(${var})
endif()
set(${var} ${${var}} PARENT_SCOPE)
endfunction()
# Usage:
# dt_reg_size(<var> PATH <path> [INDEX <idx>])
#
# Get the size of the register block at index <idx>.
# If INDEX is omitted, then the value at index 0 will be returned.
#
# The value will be returned in the <value> parameter.
#
# <var> : Return variable where the size value will be stored
# PATH <path> : Node path
# INDEX <idx> : Index number
function(dt_reg_size var)
set(req_single_args "PATH")
set(single_args "INDEX")
cmake_parse_arguments(DT_REG "" "${req_single_args};${single_args}" "" ${ARGN})
if(${ARGV0} IN_LIST req_single_args)
message(FATAL_ERROR "dt_reg_addr(${ARGV0} ...) missing return parameter.")
endif()
foreach(arg ${req_single_args})
if(NOT DEFINED DT_REG_${arg})
message(FATAL_ERROR "dt_reg_addr(${ARGV0} ...) "
"missing required argument: ${arg}"
)
endif()
endforeach()
if(NOT DEFINED DT_REG_INDEX)
set(DT_REG_INDEX 0)
endif()
get_target_property(${var}_list devicetree_target "DT_REG|${DT_REG_PATH}|SIZE")
list(GET ${var}_list ${DT_REG_INDEX} ${var})
if("${var}" STREQUAL NONE)
set(${var})
endif()
set(${var} ${${var}} PARENT_SCOPE)
endfunction()
# Usage:
# dt_has_chosen(<var> PROPERTY <prop>)
#
# Test if the devicetree's /chosen node has a given property
# <prop> which contains the path to a node.
#
# Example devicetree fragment:
#
# chosen {
# foo = &bar;
# };
#
# Example usage:
#
# # Sets 'result' to TRUE
# dt_has_chosen(result PROPERTY "foo")
#
# # Sets 'result' to FALSE
# dt_has_chosen(result PROPERTY "baz")
#
# The result of the check, either TRUE or FALSE, will be stored in the
# <var> parameter.
#
# <var> : Return variable
# PROPERTY <prop> : Chosen property
function(dt_has_chosen var)
set(req_single_args "PROPERTY")
cmake_parse_arguments(DT_CHOSEN "" "${req_single_args}" "" ${ARGN})
if(${ARGV0} IN_LIST req_single_args)
message(FATAL_ERROR "dt_has_chosen(${ARGV0} ...) missing return parameter.")
endif()
foreach(arg ${req_single_args})
if(NOT DEFINED DT_CHOSEN_${arg})
message(FATAL_ERROR "dt_has_chosen(${ARGV0} ...) "
"missing required argument: ${arg}"
)
endif()
endforeach()
get_target_property(exists devicetree_target "DT_CHOSEN|${DT_CHOSEN_PROPERTY}")
if(${exists} STREQUAL exists-NOTFOUND)
set(${var} FALSE PARENT_SCOPE)
else()
set(${var} TRUE PARENT_SCOPE)
endif()
endfunction()
# Usage:
# dt_chosen(<var> PROPERTY <prop>)
#
# Get a node path for a /chosen node property.
#
# the node path will be returned in the <value> parameter.
#
# <var> : Return variable where the node path will be stored
# PROPERTY <prop> : Chosen property
function(dt_chosen var)
set(req_single_args "PROPERTY")
cmake_parse_arguments(DT_CHOSEN "" "${req_single_args}" "" ${ARGN})
if(${ARGV0} IN_LIST req_single_args)
message(FATAL_ERROR "dt_has_chosen(${ARGV0} ...) missing return parameter.")
endif()
foreach(arg ${req_single_args})
if(NOT DEFINED DT_CHOSEN_${arg})
message(FATAL_ERROR "dt_chosen(${ARGV0} ...) "
"missing required argument: ${arg}"
)
endif()
endforeach()
get_target_property(${var} devicetree_target "DT_CHOSEN|${DT_CHOSEN_PROPERTY}")
if(${${var}} STREQUAL ${var}-NOTFOUND)
set(${var} PARENT_SCOPE)
else()
set(${var} ${${var}} PARENT_SCOPE)
endif()
endfunction()

143
scripts/dts/gen_dts_cmake.py

@ -0,0 +1,143 @@ @@ -0,0 +1,143 @@
#!/usr/bin/env python3
# Copyright (c) 2021 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
'''
This script uses edtlib and the devicetree data in the build directory
to generate a CMake file which contains devicetree data.
That data can then be used in the rest of the build system.
The generated CMake file looks like this:
add_custom_target(devicetree_target)
set_target_properties(devicetree_target PROPERTIES
"DT_PROP|/soc|compatible" "vnd,soc;")
...
It defines a special CMake target, and saves various values in the
devicetree as CMake target properties.
Be careful:
"Property" here can refer to a CMake target property or a
DTS property. DTS property values are stored inside
CMake target properties, along with other devicetree data.
The build system includes this generated file early on, so
devicetree values can be used at CMake processing time.
Accss is not done directly, but with Zephyr CMake extension APIs,
like this:
# sets 'compat' to "vnd,soc" in CMake
dt_prop(compat PATH "/soc" PROPERTY compatible INDEX 0)
This is analogous to how DTS values are encoded as C macros,
which can be read in source code using C APIs like
DT_PROP(node_id, foo) from devicetree.h.
'''
import argparse
import os
import pickle
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), 'python-devicetree',
'src'))
def parse_args():
# Returns parsed command-line arguments
parser = argparse.ArgumentParser()
parser.add_argument("--cmake-out", required=True,
help="path to write the CMake property file")
parser.add_argument("--edt-pickle", required=True,
help="path to read the pickled edtlib.EDT object from")
return parser.parse_args()
def main():
args = parse_args()
with open(args.edt_pickle, 'rb') as f:
edt = pickle.load(f)
# In what looks like an undocumented implementation detail, CMake
# target properties are stored in a C++ standard library map whose
# keys and values are each arbitrary strings, so we can use
# whatever we want as target property names.
#
# We therefore use '|' as a field separator character below within
# because it's not a valid character in DTS node paths or property
# names. This lets us store the "real" paths and property names
# without conversion to lowercase-and-underscores like we have to
# do in C.
#
# If CMake adds restrictions on target property names later, we
# can just tweak the generated file to use a more restrictive
# property encoding, perhaps reusing the same style documented in
# macros.bnf for C macros.
cmake_props = []
for node in edt.chosen_nodes:
path = edt.chosen_nodes[node].path
cmake_props.append(f'"DT_CHOSEN|{node}" "{path}"')
for node in edt.nodes:
cmake_props.append(f'"DT_NODE|{node.path}" TRUE')
for label in node.labels:
cmake_props.append(f'"DT_NODELABEL|{label}" "{node.path}"')
for item in node.props:
# We currently do not support phandles for edt -> cmake conversion.
if "phandle" not in node.props[item].type:
if "array" in node.props[item].type:
# Convert array to CMake list
cmake_value = ''
for val in node.props[item].val:
cmake_value = f'{cmake_value}{val};'
else:
cmake_value = node.props[item].val
# Encode node's property 'item' as a CMake target property
# with a name like 'DT_PROP|<path>|<property>'.
cmake_prop = f'DT_PROP|{node.path}|{item}'
cmake_props.append(f'"{cmake_prop}" "{cmake_value}"')
if node.regs is not None:
cmake_props.append(f'"DT_REG|{node.path}|NUM" "{len(node.regs)}"')
cmake_addr = ''
cmake_size = ''
for reg in node.regs:
if reg.addr is None:
cmake_addr = f'{cmake_addr}NONE;'
else:
cmake_addr = f'{cmake_addr}{hex(reg.addr)};'
if reg.size is None:
cmake_size = f'{cmake_size}NONE;'
else:
cmake_size = f'{cmake_size}{hex(reg.size)};'
cmake_props.append(f'"DT_REG|{node.path}|ADDR" "{cmake_addr}"')
cmake_props.append(f'"DT_REG|{node.path}|SIZE" "{cmake_size}"')
with open(args.cmake_out, "w", encoding="utf-8") as cmake_file:
print('add_custom_target(devicetree_target)', file=cmake_file)
print(file=cmake_file)
for prop in cmake_props:
print(
f'set_target_properties(devicetree_target PROPERTIES {prop})',
file=cmake_file
)
if __name__ == "__main__":
main()
Loading…
Cancel
Save