blob: c2ac0fd6c6d75b28059d24234a440a98fe38c9c3 [file] [log] [blame]
# Copyright 2023 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/testing/golden_files.gni")
import("//build/zircon/c_utils.gni")
# Internal template used to preprocess a DTS file.
#
# Parameters
#
# * sources
# - Required: DTS file to preprocess.
# - Type: list(path). Must only have one entry.
#
# * deps
# - Optional: Usual GN meaning.
#
template("_preprocessed_dts") {
toolchain_utils_action(target_name) {
forward_variables_from(invoker,
[
"sources",
"deps",
])
assert([ sources[0] ] == sources, "Only one source file can be passed.")
utils = [ "cc" ]
source_root_relative = get_path_info(rebase_path(sources[0], "//"), "dir")
source_name = get_path_info(sources[0], "name")
# This is done to remove the '.S' extension added to the files and also
# place it in a relatively equivalent path to that of the source. This is
# required to allow includes that contain the full source path.
outputs =
[ "$target_gen_dir/$target_name/$source_root_relative/$source_name" ]
depfile = "$target_gen_dir/$source_name.d"
configs = [
"//build/config:relative_paths",
"//build/config:default_include_dirs",
]
args = [
"-undef",
"-MD",
"-MF",
rebase_path(depfile, root_build_dir),
"-E",
"-P",
"{{include_dirs}}",
"{{defines}}",
"-x",
"assembler-with-cpp",
rebase_path(sources[0], root_build_dir),
"-o",
rebase_path(outputs[0], root_build_dir),
]
}
}
# Defines a devicetree source file that can be included by other devicetree files.
#
# Parameters
#
# * sources
# - Required: Device tree source include file (.dtsi/.dtsi.S). Source files
# that include C header files should end with the extension `.dtsi.S` and
# it will be preprocessed by C compiler.
# - Type: list(path). Must only have one entry.
#
# * deps
# - Optional: Other Devicetree fragment targets referenced by this fragment.
# - Type: list(label)
# - Default: []
#
template("devicetree_fragment") {
dtsi_file = invoker.sources
assert([ dtsi_file[0] ] == invoker.sources,
"Devicetree fragment can only have one source file")
extension = get_path_info(dtsi_file[0], "extension")
dtsi_extension =
get_path_info(get_path_info(dtsi_file[0], "name"), "extension")
assert(extension == "dtsi" || (extension == "S" && dtsi_extension == "dtsi"),
"Devicetree fragment can only have .dtsi or .dtsi.S source file.")
not_needed([ "dtsi_extension" ])
if (extension == "S") {
processed_dts_target = "_devicetree_fragment.$target_name.preprocessed"
_preprocessed_dts(processed_dts_target) {
forward_variables_from(invoker,
[
"sources",
"deps",
])
}
processed_dts_outputs = get_target_outputs(":$processed_dts_target")
processed_dts_output_dir = get_path_info(processed_dts_outputs[0], "dir")
includes = [
# For dtsi includes referring to paths relative to `//`.
"$target_gen_dir/$processed_dts_target",
# For dtsi includes referring to current directory.
"$processed_dts_output_dir",
]
} else {
includes = [ get_path_info(dtsi_file[0], "dir") ]
}
group(target_name) {
forward_variables_from(invoker, [ "deps" ])
if (!defined(deps)) {
deps = []
}
if (extension == "S") {
deps += [ ":$processed_dts_target" ]
}
metadata = {
devicetree_barrier = []
devicetree_includes = includes
}
}
}
# Defines a devicetree blob builder.
#
# Parameters
#
# * sources
# - Required: Device tree source file (.dts/.dts.S). Source file that
# include C header files should end with the extension `.dts.S` and it
# will be preprocessed by C compiler before invoking the devicetree compiler.
# - Type: List(path). Must only have one entry.
#
# * deps
# - Optional: Usual GN meaning.
#
# * outputs
# - Optional: The singleton list containing the output blob path. This value
# can be accessed as `get_target_outputs(target_name)`.
# - Type: list(path). Must only have one entry.
# - Default: target_out_dir + "/" + get_path_info(sources[0], "name) + ".dtb"
#
template("dtb") {
dts_file = invoker.sources
assert([ dts_file[0] ] == invoker.sources,
"devicetree can only have a single input file")
extension = get_path_info(dts_file[0], "extension")
dts_extension = get_path_info(get_path_info(dts_file[0], "name"), "extension")
assert(extension == "dts" || (extension == "S" && dts_extension == "dts"),
"Only .dts or .dts.S source files accepted.")
not_needed([ "dts_extension" ])
processed_dts_target = "_dtb.$target_name.preprocessed"
dts_include_dirs_target = "_dtb.$target_name.includes"
dts_include_dirs_filename = dts_include_dirs_target + ".txt"
# Each devicetree_fragment() call puts a devicetree in a directory tree that
# mimics the source tree, so that we can properly restrict /include/
# statements in DTC. This generates a file containing paths to all of these
# directory trees, which we use below when calling DTC.
generated_file(dts_include_dirs_target) {
forward_variables_from(invoker, [ "deps" ])
data_keys = [ "devicetree_includes" ]
outputs = [ "$target_gen_dir/$dts_include_dirs_filename" ]
output_conversion = "list lines"
rebase = root_build_dir
}
# This passes the DTS files through the preprocessor to handle any C constants.
_preprocessed_dts(processed_dts_target) {
forward_variables_from(invoker,
[
"sources",
"deps",
])
}
# This actually compiles the DTB.
action(target_name) {
deps = [ ":$dts_include_dirs_target" ]
if (extension == "S") {
deps += [ ":$processed_dts_target" ]
sources = get_target_outputs(":$processed_dts_target")
# Extract name from <name>.dts.S.
source_name = get_path_info(sources[0], "name")
source_name = get_path_info(source_name, "name")
} else {
sources = invoker.sources
source_name = get_path_info(sources[0], "name")
}
depfile = "$target_gen_dir/$source_name.d"
script = "//build/devicetree/dtc.sh"
inputs = [ "//prebuilt/third_party/dtc/${host_platform}/dtc" ]
include_dirs = get_target_outputs(":$dts_include_dirs_target")
sources += include_dirs
if (defined(invoker.outputs)) {
outputs = invoker.outputs
} else {
outputs = [ "$target_out_dir/$source_name.dtb" ]
}
args = [
rebase_path(inputs[0], root_build_dir),
rebase_path(include_dirs[0], root_build_dir),
"-i",
rebase_path("//", root_build_dir),
rebase_path(sources[0], root_build_dir),
"-o",
rebase_path(outputs[0], root_build_dir),
"-d",
rebase_path(depfile, root_build_dir),
]
}
}
# Defines a devicetree blob decompiler.
#
# Parameters
#
# * sources
# - Required: Device tree blob file (.dtb).
# - Type: List(path). Must only have one entry.
#
# * deps
# - Optional: Usual GN meaning.
#
# * outputs
# - Optional: The singleton list containing the output dts path. This value
# can be accessed as `get_target_outputs(target_name)`.
# - Type: list(path). Must only have one entry.
# - Default: target_out_dir + "/" + get_path_info(sources[0], "name) + ".dts"
#
template("dts") {
action(target_name) {
forward_variables_from(invoker,
[
"sources",
"deps",
])
assert([ sources[0] ] == sources, "Only one input file accepted.")
assert(get_path_info(sources[0], "extension") == "dtb",
"Only .dtb source files accepted.")
source_name = get_path_info(sources[0], "name")
script = "//build/devicetree/dtc.sh"
inputs = [ "//prebuilt/third_party/dtc/${host_platform}/dtc" ]
if (defined(invoker.outputs)) {
outputs = invoker.outputs
} else {
outputs = [ "$target_out_dir/$source_name.dts" ]
}
args = [
rebase_path(inputs[0], root_build_dir),
"--",
"-I",
"dtb",
"-O",
"dts",
"--sort",
rebase_path(sources[0], root_build_dir),
"-o",
rebase_path(outputs[0], root_build_dir),
]
}
}
# Defines a devicetree to be built into a devicetree blob.
#
# There are three public targets -
# 1. The main target compiles the devicetree source and runs all the necessary
# verification checks.
# 2. The target "$target_name.dtb" can be used to get the output devicetree
# blob using `get_target_output("$target_name.dtb")`.
#
# Parameters
#
# * sources
# - Required: Device tree source file (.dts)
# - Type: List(path). Must only have one entry.
#
# * golden
# - Required: Path to the golden device tree source file
# (created by the template if not existing). It will be compared against
# the current dts after resolving all includes. This is useful to review
# changes done to the device tree either directly by the dts or
# indirectly by the includes and headers.
#
# * deps
# - Optional: Other devicetree fragments and header file targets referenced
# by the source.
# - Type: list(label)
# - Default: []
#
# * outputs
# - Optional: The singleton list containing the output blob path. This value
# can be accessed as `get_target_outputs(target_name)`.
# - Type: list(path). Must only have one entry.
# - Default: target_out_dir + "/" + target_name + ".dtb"
#
template("devicetree") {
compiled_dtb_target = "$target_name.dtb"
decompiled_dts_target = "_devicetree.$target_name.decompiled"
golden_dts_target = "_devicetree.$target_name.golden"
# Compile the dts
dtb(compiled_dtb_target) {
forward_variables_from(invoker,
[
"sources",
"deps",
"public_deps",
])
if (defined(invoker.outputs)) {
outputs = invoker.outputs
} else {
outputs = [ "$target_out_dir/$compiled_dtb_target" ]
}
}
# Decompile the compiled DTS so we can use it for the golden comparison.
# We use the decompiled DTS instead of the original DTS as all includes and
# C headers are resolved in the final dtb.
dts(decompiled_dts_target) {
deps = [ ":$compiled_dtb_target" ]
sources = get_target_outputs(":$compiled_dtb_target")
}
# Compare golden dts and decompiled dts file.
golden_files(golden_dts_target) {
deps = [ ":$decompiled_dts_target" ]
dts_outputs = get_target_outputs(":$decompiled_dts_target")
comparisons = [
{
forward_variables_from(invoker, [ "golden" ])
candidate = dts_outputs[0]
},
]
}
group(target_name) {
deps = [
":$compiled_dtb_target",
":$golden_dts_target",
]
}
}