binman support for Xilinx signing

buildman minor fixes
 -----BEGIN PGP SIGNATURE-----
 
 iQFFBAABCgAvFiEEslwAIq+Gp8wWVbYnfxc6PpAIreYFAmTOp5wRHHNqZ0BjaHJv
 bWl1bS5vcmcACgkQfxc6PpAIreYApAf+P45nQXX2L0lFpu5vJQ+Xy1VHDh+OE5sU
 yVWm2A0zc7L0TyLn4fX5PGF+1C7gZjYGQ6I9OVroS58fXR5o6WjCelCMhE1wxL5i
 AgHKmrUUw6XUalF317RquRWDPLAD6KZ1T8XUgjF2PnBwd2RwMH4zW/Ls73UFBf+4
 gIi0zlqLsX8zSeFAyitRWobM4gJT4PUAxz7jexBEk9y3EjOufcLqpdl2Y4JAxTtl
 82AYy9nJbPyFrT9lIGP9nfPenVPGKL6wdzc5C/sGLeRlutNKgrJTnjWRkJV+ThPx
 mIZVSzwi2cbYnMpq1nEGnj+cH6R73xxybj17MVzevyAQNXAZcZfQ+Q==
 =Nbd9
 -----END PGP SIGNATURE-----

Merge tag 'dm-pull-5aug23' of https://source.denx.de/u-boot/custodians/u-boot-dm

binman support for Xilinx signing
buildman minor fixes
This commit is contained in:
Tom Rini 2023-08-05 22:11:04 -04:00
commit b1a8ef746f
13 changed files with 569 additions and 18 deletions

View file

@ -27,7 +27,7 @@ const char *const type_name[] = {
"test",
/* Events related to driver model */
"dm_post_init",
"dm_post_init_f",
"dm_pre_probe",
"dm_post_probe",
"dm_pre_remove",

View file

@ -208,7 +208,7 @@ Using `fdt_add_pubkey` the key can be injected to the SPL independent of
Bintool: bootgen: Sign ZynqMP FSBL image
---------------------------------------------
----------------------------------------
This bintool supports running `bootgen` in order to sign a SPL for ZynqMP
devices.

View file

@ -0,0 +1,137 @@
# SPDX-License-Identifier: GPL-2.0+
# Copyright (C) 2023 Weidmüller Interface GmbH & Co. KG
# Lukas Funke <lukas.funke@weidmueller.com>
#
"""Bintool implementation for bootgen
bootgen allows creating bootable SPL for Zynq(MP)
Documentation is available via:
https://www.xilinx.com/support/documents/sw_manuals/xilinx2022_1/ug1283-bootgen-user-guide.pdf
Source code is available at:
https://github.com/Xilinx/bootgen
"""
from binman import bintool
from u_boot_pylib import tools
# pylint: disable=C0103
class Bintoolbootgen(bintool.Bintool):
"""Generate bootable fsbl image for zynq/zynqmp
This bintools supports running Xilinx "bootgen" in order
to generate a bootable, authenticated image form an SPL.
"""
def __init__(self, name):
super().__init__(name, 'Xilinx Bootgen',
version_regex=r'^\*\*\*\*\*\* *Xilinx Bootgen *(.*)',
version_args='-help')
# pylint: disable=R0913
def sign(self, arch, spl_elf_fname, pmufw_elf_fname,
psk_fname, ssk_fname, fsbl_config, auth_params, keysrc_enc,
output_fname):
"""Sign SPL elf file and bundle it with PMU firmware into an image
The method bundels the SPL together with a 'Platform Management Unit'
(PMU)[1] firmware into a single bootable image. The image in turn is
signed with the provided 'secondary secret key' (ssk), which in turn is
signed with the 'primary secret key' (psk). In order to verify the
authenticity of the ppk, it's hash has to be fused into the device
itself.
In Xilinx terms the SPL is usually called 'FSBL'
(First Stage Boot Loader). The jobs of the SPL and the FSBL are mostly
the same: load bitstream, bootstrap u-boot.
Args:
arch (str): Xilinx SoC architecture. Currently only 'zynqmp' is
supported.
spl_elf_fname (str): Filename of SPL ELF file. The filename must end
with '.elf' in order for bootgen to recognized it as an ELF
file. Otherwise the start address field is missinterpreted.
pmufw_elf_fname (str): Filename PMU ELF firmware.
psk_fname (str): Filename of the primary secret key (psk). The psk
is a .pem file which holds the RSA private key used for signing
the secondary secret key.
ssk_fname (str): Filename of the secondary secret key. The ssk
is a .pem file which holds the RSA private key used for signing
the actual boot firmware.
fsbl_config (str): FSBL config options. A string list of fsbl config
options. Valid values according to [2] are:
"bh_auth_enable": Boot Header Authentication Enable: RSA
authentication of the bootimage is done
excluding the verification of PPK hash and SPK ID. This is
useful for debugging before bricking a device.
"auth_only": Boot image is only RSA signed. FSBL should not be
decrypted. See the
Zynq UltraScale+ Device Technical Reference Manual (UG1085)
for more information.
There are more options which relate to PUF (physical unclonable
functions). Please refer to Xilinx manuals for further info.
auth_params (str): Authentication parameter. A semicolon separated
list of authentication parameters. Valid values according to [3]
are:
"ppk_select=<0|1>" - Select which ppk to use
"spk_id=<32-bit spk id>" - Specifies which SPK can be
used or revoked, default is 0x0
"spk_select=<spk-efuse/user-efuse>" - To differentiate spk and
user efuses.
"auth_header" - To authenticate headers when no partition
is authenticated.
keysrc_enc (str): This specifies the Key source for encryption.
Valid values according to [3] are:
"bbram_red_key" - RED key stored in BBRAM
"efuse_red_key" - RED key stored in eFUSE
"efuse_gry_key" - Grey (Obfuscated) Key stored in eFUSE.
"bh_gry_key" - Grey (Obfuscated) Key stored in boot header
"bh_blk_key" - Black Key stored in boot header
"efuse_blk_key" - Black Key stored in eFUSE
"kup_key" - User Key
output_fname (str): Filename where bootgen should write the result
Returns:
str: Bootgen output from stdout
[1] https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841724/PMU+Firmware
[2] https://docs.xilinx.com/r/en-US/ug1283-bootgen-user-guide/fsbl_config
[3] https://docs.xilinx.com/r/en-US/ug1283-bootgen-user-guide/auth_params
[4] https://docs.xilinx.com/r/en-US/ug1283-bootgen-user-guide/keysrc_encryption
"""
_fsbl_config = f"[fsbl_config] {fsbl_config}" if fsbl_config else ""
_auth_params = f"[auth_params] {auth_params}" if auth_params else ""
_keysrc_enc = f"[keysrc_encryption] {keysrc_enc}" if keysrc_enc else ""
bif_template = f"""u_boot_spl_aes_rsa: {{
[pskfile] {psk_fname}
[sskfile] {ssk_fname}
{_keysrc_enc}
{_fsbl_config}
{_auth_params}
[ bootloader,
authentication = rsa,
destination_cpu=a53-0] {spl_elf_fname}
[pmufw_image] {pmufw_elf_fname}
}}"""
args = ["-arch", arch]
bif_fname = tools.get_output_filename('bootgen-in.sign.bif')
tools.write_file(bif_fname, bif_template, False)
args += ["-image", bif_fname, '-w', '-o', output_fname]
return self.run_cmd(*args)
def fetch(self, method):
"""Fetch bootgen from git"""
if method != bintool.FETCH_BUILD:
return None
result = self.build_from_git(
'https://github.com/Xilinx/bootgen',
'all',
'bootgen')
return result

View file

@ -2667,3 +2667,78 @@ may be used instead.
.. _etype_xilinx_bootgen:
Entry: xilinx-bootgen: Signed SPL boot image for Xilinx ZynqMP devices
----------------------------------------------------------------------
Properties / Entry arguments:
- auth-params: (Optional) Authentication parameters passed to bootgen
- fsbl-config: (Optional) FSBL parameters passed to bootgen
- keysrc-enc: (Optional) Key source when using decryption engine
- pmufw-filename: Filename of PMU firmware. Default: pmu-firmware.elf
- psk-key-name-hint: Name of primary secret key to use for signing the
secondardy public key. Format: .pem file
- ssk-key-name-hint: Name of secondardy secret key to use for signing
the boot image. Format: .pem file
The etype is used to create a boot image for Xilinx ZynqMP
devices.
Information for signed images:
In AMD/Xilinx SoCs, two pairs of public and secret keys are used
- primary and secondary. The function of the primary public/secret key pair
is to authenticate the secondary public/secret key pair.
The function of the secondary key is to sign/verify the boot image. [1]
AMD/Xilinx uses the following terms for private/public keys [1]:
PSK = Primary Secret Key (Used to sign Secondary Public Key)
PPK = Primary Public Key (Used to verify Secondary Public Key)
SSK = Secondary Secret Key (Used to sign the boot image/partitions)
SPK = Used to verify the actual boot image
The following example builds a signed boot image. The fuses of
the primary public key (ppk) should be fused together with the RSA_EN flag.
Example node::
spl {
filename = "boot.signed.bin";
xilinx-bootgen {
psk-key-name-hint = "psk0";
ssk-key-name-hint = "ssk0";
auth-params = "ppk_select=0", "spk_id=0x00000000";
u-boot-spl-nodtb {
};
u-boot-spl-pubkey-dtb {
algo = "sha384,rsa4096";
required = "conf";
key-name-hint = "dev";
};
};
};
For testing purposes, e.g. if no RSA_EN should be fused, one could add
the "bh_auth_enable" flag in the fsbl-config field. This will skip the
verification of the ppk fuses and boot the image, even if ppk hash is
invalid.
Example node::
xilinx-bootgen {
psk-key-name-hint = "psk0";
psk-key-name-hint = "ssk0";
...
fsbl-config = "bh_auth_enable";
...
};
[1] https://docs.xilinx.com/r/en-US/ug1283-bootgen-user-guide/Using-Authentication

View file

@ -0,0 +1,225 @@
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2023 Weidmueller GmbH
# Written by Lukas Funke <lukas.funke@weidmueller.com>
#
# Entry-type module for Zynq(MP) boot images (boot.bin)
#
import tempfile
from collections import OrderedDict
from binman import elf
from binman.etype.section import Entry_section
from dtoc import fdt_util
from u_boot_pylib import tools
from u_boot_pylib import command
# pylint: disable=C0103
class Entry_xilinx_bootgen(Entry_section):
"""Signed SPL boot image for Xilinx ZynqMP devices
Properties / Entry arguments:
- auth-params: (Optional) Authentication parameters passed to bootgen
- fsbl-config: (Optional) FSBL parameters passed to bootgen
- keysrc-enc: (Optional) Key source when using decryption engine
- pmufw-filename: Filename of PMU firmware. Default: pmu-firmware.elf
- psk-key-name-hint: Name of primary secret key to use for signing the
secondardy public key. Format: .pem file
- ssk-key-name-hint: Name of secondardy secret key to use for signing
the boot image. Format: .pem file
The etype is used to create a boot image for Xilinx ZynqMP
devices.
Information for signed images:
In AMD/Xilinx SoCs, two pairs of public and secret keys are used
- primary and secondary. The function of the primary public/secret key pair
is to authenticate the secondary public/secret key pair.
The function of the secondary key is to sign/verify the boot image. [1]
AMD/Xilinx uses the following terms for private/public keys [1]:
PSK = Primary Secret Key (Used to sign Secondary Public Key)
PPK = Primary Public Key (Used to verify Secondary Public Key)
SSK = Secondary Secret Key (Used to sign the boot image/partitions)
SPK = Used to verify the actual boot image
The following example builds a signed boot image. The fuses of
the primary public key (ppk) should be fused together with the RSA_EN flag.
Example node::
spl {
filename = "boot.signed.bin";
xilinx-bootgen {
psk-key-name-hint = "psk0";
ssk-key-name-hint = "ssk0";
auth-params = "ppk_select=0", "spk_id=0x00000000";
u-boot-spl-nodtb {
};
u-boot-spl-pubkey-dtb {
algo = "sha384,rsa4096";
required = "conf";
key-name-hint = "dev";
};
};
};
For testing purposes, e.g. if no RSA_EN should be fused, one could add
the "bh_auth_enable" flag in the fsbl-config field. This will skip the
verification of the ppk fuses and boot the image, even if ppk hash is
invalid.
Example node::
xilinx-bootgen {
psk-key-name-hint = "psk0";
psk-key-name-hint = "ssk0";
...
fsbl-config = "bh_auth_enable";
...
};
[1] https://docs.xilinx.com/r/en-US/ug1283-bootgen-user-guide/Using-Authentication
"""
def __init__(self, section, etype, node):
super().__init__(section, etype, node)
self._auth_params = None
self._entries = OrderedDict()
self._filename = None
self._fsbl_config = None
self._keysrc_enc = None
self._pmufw_filename = None
self._psk_key_name_hint = None
self._ssk_key_name_hint = None
self.align_default = None
self.bootgen = None
self.required_props = ['pmufw-filename',
'psk-key-name-hint',
'ssk-key-name-hint']
def ReadNode(self):
"""Read properties from the xilinx-bootgen node"""
super().ReadNode()
self._auth_params = fdt_util.GetStringList(self._node,
'auth-params')
self._filename = fdt_util.GetString(self._node, 'filename')
self._fsbl_config = fdt_util.GetStringList(self._node,
'fsbl-config')
self._keysrc_enc = fdt_util.GetString(self._node,
'keysrc-enc')
self._pmufw_filename = fdt_util.GetString(self._node, 'pmufw-filename')
self._psk_key_name_hint = fdt_util.GetString(self._node,
'psk-key-name-hint')
self._ssk_key_name_hint = fdt_util.GetString(self._node,
'ssk-key-name-hint')
self.ReadEntries()
@classmethod
def _ToElf(cls, data, output_fname):
"""Convert SPL object file to bootable ELF file
Args:
data (bytearray): u-boot-spl-nodtb + u-boot-spl-pubkey-dtb obj file
data
output_fname (str): Filename of converted FSBL ELF file
"""
platform_elfflags = {"aarch64":
["-B", "aarch64", "-O", "elf64-littleaarch64"],
# amd64 support makes no sense for the target
# platform, but we include it here to enable
# testing on hosts
"x86_64":
["-B", "i386", "-O", "elf64-x86-64"]
}
gcc, args = tools.get_target_compile_tool('cc')
args += ['-dumpmachine']
stdout = command.output(gcc, *args)
# split target machine triplet (arch, vendor, os)
arch, _, _ = stdout.split('-')
spl_elf = elf.DecodeElf(tools.read_file(
tools.get_input_filename('spl/u-boot-spl')), 0)
# Obj file to swap data and text section (rename-section)
with tempfile.NamedTemporaryFile(prefix="u-boot-spl-pubkey-",
suffix=".o.tmp",
dir=tools.get_output_dir())\
as tmp_obj:
input_objcopy_fname = tmp_obj.name
# Align packed content to 4 byte boundary
pad = bytearray(tools.align(len(data), 4) - len(data))
tools.write_file(input_objcopy_fname, data + pad)
# Final output elf file which contains a valid start address
with tempfile.NamedTemporaryFile(prefix="u-boot-spl-pubkey-elf-",
suffix=".o.tmp",
dir=tools.get_output_dir())\
as tmp_elf_obj:
input_ld_fname = tmp_elf_obj.name
objcopy, args = tools.get_target_compile_tool('objcopy')
args += ["--rename-section", ".data=.text",
"-I", "binary"]
args += platform_elfflags[arch]
args += [input_objcopy_fname, input_ld_fname]
command.run(objcopy, *args)
ld, args = tools.get_target_compile_tool('ld')
args += [input_ld_fname, '-o', output_fname,
"--defsym", f"_start={hex(spl_elf.entry)}",
"-Ttext", hex(spl_elf.entry)]
command.run(ld, *args)
def BuildSectionData(self, required):
"""Pack node content, and create bootable, signed ZynqMP boot image
The method collects the content of this node (usually SPL + dtb) and
converts them to an ELF file. The ELF file is passed to the
Xilinx bootgen tool which packs the SPL ELF file together with
Platform Management Unit (PMU) firmware into a bootable image
for ZynqMP devices. The image is signed within this step.
The result is a bootable, signed SPL image for Xilinx ZynqMP devices.
"""
data = super().BuildSectionData(required)
bootbin_fname = self._filename if self._filename else \
tools.get_output_filename(
f'boot.{self.GetUniqueName()}.bin')
pmufw_elf_fname = tools.get_input_filename(self._pmufw_filename)
psk_fname = tools.get_input_filename(self._psk_key_name_hint + ".pem")
ssk_fname = tools.get_input_filename(self._ssk_key_name_hint + ".pem")
fsbl_config = ";".join(self._fsbl_config) if self._fsbl_config else None
auth_params = ";".join(self._auth_params) if self._auth_params else None
spl_elf_fname = tools.get_output_filename('u-boot-spl-pubkey.dtb.elf')
# We need to convert to node content (see above) into an ELF
# file in order to be processed by bootgen.
self._ToElf(bytearray(data), spl_elf_fname)
# Call Bootgen in order to sign the SPL
if self.bootgen.sign('zynqmp', spl_elf_fname, pmufw_elf_fname,
psk_fname, ssk_fname, fsbl_config,
auth_params, self._keysrc_enc, bootbin_fname) is None:
# Bintool is missing; just use empty data as the output
self.record_missing_bintool(self.bootgen)
data = tools.get_bytes(0, 1024)
else:
data = tools.read_file(bootbin_fname)
self.SetContents(data)
return data
# pylint: disable=C0116
def AddBintools(self, btools):
super().AddBintools(btools)
self.bootgen = self.AddBintool(btools, 'bootgen')

View file

@ -6974,7 +6974,7 @@ fdt fdtmap Extract the devicetree blob from the fdtmap
entry_args = {
'atf-bl31-path': 'bl31.elf',
}
data = self._DoReadFileDtb('291_template_phandle.dts',
data = self._DoReadFileDtb('309_template_phandle.dts',
entry_args=entry_args)
fname = tools.get_output_filename('image.bin')
out = tools.run('dumpimage', '-l', fname)
@ -6990,7 +6990,7 @@ fdt fdtmap Extract the devicetree blob from the fdtmap
'atf-bl31-path': 'bl31.elf',
}
with self.assertRaises(ValueError) as e:
self._DoReadFileDtb('292_template_phandle_dup.dts',
self._DoReadFileDtb('310_template_phandle_dup.dts',
entry_args=entry_args)
self.assertIn(
'Duplicate phandle 1 in nodes /binman/image/fit/images/atf/atf-bl31 and /binman/image-2/fit/images/atf/atf-bl31',
@ -7139,5 +7139,82 @@ fdt fdtmap Extract the devicetree blob from the fdtmap
self.assertEqual(fdt_util.GetString(key_node, "key-name-hint"),
"key")
def testXilinxBootgenSigning(self):
"""Test xilinx-bootgen etype"""
bootgen = bintool.Bintool.create('bootgen')
self._CheckBintool(bootgen)
data = tools.read_file(self.TestFile("key.key"))
self._MakeInputFile("psk.pem", data)
self._MakeInputFile("ssk.pem", data)
self._SetupPmuFwlElf()
self._SetupSplElf()
self._DoReadFileRealDtb('307_xilinx_bootgen_sign.dts')
image_fname = tools.get_output_filename('image.bin')
# Read partition header table and check if authentication is enabled
bootgen_out = bootgen.run_cmd("-arch", "zynqmp",
"-read", image_fname, "pht").splitlines()
attributes = {"authentication": None,
"core": None,
"encryption": None}
for l in bootgen_out:
for a in attributes.keys():
if a in l:
m = re.match(fr".*{a} \[([^]]+)\]", l)
attributes[a] = m.group(1)
self.assertTrue(attributes['authentication'] == "rsa")
self.assertTrue(attributes['core'] == "a53-0")
self.assertTrue(attributes['encryption'] == "no")
def testXilinxBootgenSigningEncryption(self):
"""Test xilinx-bootgen etype"""
bootgen = bintool.Bintool.create('bootgen')
self._CheckBintool(bootgen)
data = tools.read_file(self.TestFile("key.key"))
self._MakeInputFile("psk.pem", data)
self._MakeInputFile("ssk.pem", data)
self._SetupPmuFwlElf()
self._SetupSplElf()
self._DoReadFileRealDtb('308_xilinx_bootgen_sign_enc.dts')
image_fname = tools.get_output_filename('image.bin')
# Read boot header in order to verify encryption source and
# encryption parameter
bootgen_out = bootgen.run_cmd("-arch", "zynqmp",
"-read", image_fname, "bh").splitlines()
attributes = {"auth_only":
{"re": r".*auth_only \[([^]]+)\]", "value": None},
"encryption_keystore":
{"re": r" *encryption_keystore \(0x28\) : (.*)",
"value": None},
}
for l in bootgen_out:
for a in attributes.keys():
if a in l:
m = re.match(attributes[a]['re'], l)
attributes[a] = m.group(1)
# Check if fsbl-attribute is set correctly
self.assertTrue(attributes['auth_only'] == "true")
# Check if key is stored in efuse
self.assertTrue(attributes['encryption_keystore'] == "0xa5c3c5a3")
def testXilinxBootgenMissing(self):
"""Test that binman still produces an image if bootgen is missing"""
data = tools.read_file(self.TestFile("key.key"))
self._MakeInputFile("psk.pem", data)
self._MakeInputFile("ssk.pem", data)
self._SetupPmuFwlElf()
self._SetupSplElf()
with test_util.capture_sys_output() as (_, stderr):
self._DoTestFile('307_xilinx_bootgen_sign.dts',
force_missing_bintools='bootgen')
err = stderr.getvalue()
self.assertRegex(err,
"Image 'image'.*missing bintools.*: bootgen")
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
xilinx-bootgen {
auth-params = "ppk_select=0", "spk_id=0x00000000";
pmufw-filename = "pmu-firmware.elf";
psk-key-name-hint = "psk";
ssk-key-name-hint = "ssk";
u-boot-spl-nodtb {
};
u-boot-spl-dtb {
};
};
};
};

View file

@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0+
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
binman {
xilinx-bootgen {
auth-params = "ppk_select=0", "spk_id=0x00000000";
fsbl-config = "auth_only";
keysrc-enc = "efuse_red_key";
pmufw-filename = "pmu-firmware.elf";
psk-key-name-hint = "psk";
ssk-key-name-hint = "ssk";
u-boot-spl-nodtb {
};
u-boot-spl-dtb {
};
};
};
};

View file

@ -377,16 +377,9 @@ class MaintainersDatabase:
Args:
linenum (int): Current line number
"""
added = False
if targets:
for target in targets:
self.database[target] = (status, maintainers)
added = True
if not added and (status != '-' and maintainers):
leaf = fname[len(srcdir) + 1:]
if leaf != 'MAINTAINERS':
self.warnings.append(
f'WARNING: orphaned defconfig in {leaf} ending at line {linenum + 1}')
targets = []
maintainers = []

View file

@ -610,6 +610,9 @@ def do_buildman(args, toolchains=None, make_func=None, brds=None,
toolchains = get_toolchains(toolchains, col, args.override_toolchain,
args.fetch_arch, args.list_tool_chains,
args.verbose)
if isinstance(toolchains, int):
return toolchains
output_dir = setup_output_dir(
args.output_dir, args.work_in_output, args.branch,
args.no_subdirs, col, clean_dir)

View file

@ -926,10 +926,7 @@ Active aarch64 armv8 - armltd total_compute board2
tools.write_file(main, data, binary=False)
params_list, warnings = self._boards.build_board_list(config_dir, src)
self.assertEquals(2, len(params_list))
self.assertEquals(
["WARNING: no maintainers for 'board0'",
'WARNING: orphaned defconfig in boards/board0/MAINTAINERS ending at line 4',
], warnings)
self.assertEquals(["WARNING: no maintainers for 'board0'"], warnings)
# Mark a board as orphaned - this should give a warning
lines = ['S: Orphaned' if line.startswith('S') else line
@ -969,9 +966,7 @@ Active aarch64 armv8 - armltd total_compute board2
tools.write_file(main, both_data + extra, binary=False)
params_list, warnings = self._boards.build_board_list(config_dir, src)
self.assertEquals(2, len(params_list))
self.assertEquals(
['WARNING: orphaned defconfig in boards/board0/MAINTAINERS ending at line 16'],
warnings)
self.assertFalse(warnings)
# Add another TARGET to the Kconfig
tools.write_file(main, both_data, binary=False)