Merge branch '2024-12-19-assorted-tooling-updates' into next
This brings in assortment of updates to our python tooling, from Paul
HENRYS <paul.henrys_ext@softathome.com>
diff --git a/include/image.h b/include/image.h
index 9be5acd..cfe3c97 100644
--- a/include/image.h
+++ b/include/image.h
@@ -1788,6 +1788,21 @@
const unsigned char *data, int data_len,
unsigned char **cipher, int *cipher_len);
+ /**
+ * add_cipher_data() - Add cipher data to the FIT and device tree
+ *
+ * This is used to add the ciphered data to the FIT and other cipher
+ * related information (key and initialization vector) to a device tree.
+ *
+ * @info: Pointer to image cipher information.
+ * @keydest: Pointer to a device tree where the key and IV can be
+ * stored. keydest can be NULL when the key is retrieved at
+ * runtime by another mean.
+ * @fit: Pointer to the FIT image.
+ * @node_noffset: Offset where the cipher information are stored in the
+ * FIT.
+ * return: 0 on success, a negative error code otherwise.
+ */
int (*add_cipher_data)(struct image_cipher_info *info,
void *keydest, void *fit, int node_noffset);
diff --git a/lib/aes/aes-encrypt.c b/lib/aes/aes-encrypt.c
index e74e35e..90e1407 100644
--- a/lib/aes/aes-encrypt.c
+++ b/lib/aes/aes-encrypt.c
@@ -84,6 +84,13 @@
char name[128];
int ret = 0;
+ if (!keydest && !info->ivname) {
+ /* At least, store the IV in the FIT image */
+ ret = fdt_setprop(fit, node_noffset, "iv",
+ info->iv, info->cipher->iv_len);
+ goto done;
+ }
+
/* Either create or overwrite the named cipher node */
parent = fdt_subnode_offset(keydest, 0, FIT_CIPHER_NODENAME);
if (parent == -FDT_ERR_NOTFOUND) {
diff --git a/tools/binman/btool/mkimage.py b/tools/binman/btool/mkimage.py
index 78d3301..3f84220 100644
--- a/tools/binman/btool/mkimage.py
+++ b/tools/binman/btool/mkimage.py
@@ -22,7 +22,7 @@
# pylint: disable=R0913
def run(self, reset_timestamp=False, output_fname=None, external=False,
- pad=None, align=None, priv_keys_dir=None):
+ pad=None, align=None, keys_dir=None):
"""Run mkimage
Args:
@@ -34,7 +34,7 @@
other things to be easily added later, if required, such as
signatures
align: Bytes to use for alignment of the FIT and its external data
- priv_keys_dir: Path to directory containing private keys
+ keys_dir: Path to directory containing private and encryption keys
version: True to get the mkimage version
"""
args = []
@@ -46,8 +46,8 @@
args += ['-B', f'{align:x}']
if reset_timestamp:
args.append('-t')
- if priv_keys_dir:
- args += ['-k', f'{priv_keys_dir}']
+ if keys_dir:
+ args += ['-k', f'{keys_dir}']
if output_fname:
args += ['-F', output_fname]
return self.run_cmd(*args)
diff --git a/tools/binman/control.py b/tools/binman/control.py
index 542c2b4..e73c598 100644
--- a/tools/binman/control.py
+++ b/tools/binman/control.py
@@ -526,7 +526,7 @@
if node.name.startswith('template'):
node.Delete()
-def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt, use_expanded):
+def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt, use_expanded, indir):
"""Prepare the images to be processed and select the device tree
This function:
@@ -543,6 +543,7 @@
use_expanded: True to use expanded versions of entries, if available.
So if 'u-boot' is called for, we use 'u-boot-expanded' instead. This
is needed if update_fdt is True (although tests may disable it)
+ indir: List of directories where input files can be found
Returns:
OrderedDict of images:
@@ -558,7 +559,9 @@
# Get the device tree ready by compiling it and copying the compiled
# output into a file in our output directly. Then scan it for use
# in binman.
- dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
+ if indir is None:
+ indir = []
+ dtb_fname = fdt_util.EnsureCompiled(dtb_fname, indir=indir)
fname = tools.get_output_filename('u-boot.dtb.out')
tools.write_file(fname, tools.read_file(dtb_fname))
dtb = fdt.FdtScan(fname)
@@ -846,7 +849,7 @@
state.SetThreads(args.threads)
images = PrepareImagesAndDtbs(dtb_fname, args.image,
- args.update_fdt, use_expanded)
+ args.update_fdt, use_expanded, args.indir)
if args.test_section_timeout:
# Set the first image to timeout, used in testThreadTimeout()
diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst
index e918162..83068ba 100644
--- a/tools/binman/entries.rst
+++ b/tools/binman/entries.rst
@@ -871,6 +871,13 @@
-k flag. All the keys required for signing FIT must be available at
time of signing and must be located in single include directory.
+ fit,encrypt
+ Enable data encryption in FIT images via mkimage. If the property
+ is found, the keys path is detected among binman include
+ directories and passed to mkimage via -k flag. All the keys
+ required for encrypting the FIT must be available at the time of
+ encrypting and must be located in a single include directory.
+
Substitutions
~~~~~~~~~~~~~
@@ -892,6 +899,9 @@
Sequence number of the default fdt, as provided by the 'default-dt'
entry argument
+DEFAULT-NAME:
+ Name of the default fdt, as provided by the 'default-dt' entry argument
+
Available operations
~~~~~~~~~~~~~~~~~~~~
@@ -953,6 +963,21 @@
This tells binman to create nodes `config-1` and `config-2`, i.e. a config
for each of your two files.
+It is also possible to use NAME in the node names so that the FDT files name
+will be used instead of the sequence number. This can be useful to identify
+easily at runtime in U-Boot, the config to be used::
+
+ configurations {
+ default = "@config-DEFAULT-NAME";
+ @config-NAME {
+ description = "NAME";
+ firmware = "atf";
+ loadables = "uboot";
+ fdt = "fdt-NAME";
+ fit,compatible; // optional
+ };
+ };
+
Note that if no devicetree files are provided (with '-a of-list' as above)
then no nodes will be generated.
diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py
index b5afbda..803fb66 100644
--- a/tools/binman/etype/fit.py
+++ b/tools/binman/etype/fit.py
@@ -110,6 +110,13 @@
available at time of signing and must be located in single include
directory.
+ fit,encrypt
+ Enable data encryption in FIT images via mkimage. If the property
+ is found, the keys path is detected among binman include
+ directories and passed to mkimage via -k flag. All the keys
+ required for encrypting the FIT must be available at the time of
+ encrypting and must be located in a single include directory.
+
Substitutions
~~~~~~~~~~~~~
@@ -131,6 +138,9 @@
Sequence number of the default fdt, as provided by the 'default-dt'
entry argument
+ DEFAULT-NAME:
+ Name of the default fdt, as provided by the 'default-dt' entry argument
+
Available operations
~~~~~~~~~~~~~~~~~~~~
@@ -192,6 +202,21 @@
This tells binman to create nodes `config-1` and `config-2`, i.e. a config
for each of your two files.
+ It is also possible to use NAME in the node names so that the FDT files name
+ will be used instead of the sequence number. This can be useful to identify
+ easily at runtime in U-Boot, the config to be used::
+
+ configurations {
+ default = "@config-DEFAULT-NAME";
+ @config-NAME {
+ description = "NAME";
+ firmware = "atf";
+ loadables = "uboot";
+ fdt = "fdt-NAME";
+ fit,compatible; // optional
+ };
+ };
+
Note that if no devicetree files are provided (with '-a of-list' as above)
then no nodes will be generated.
@@ -452,6 +477,8 @@
self._fdt_dir = fdt_util.GetString(self._node, 'fit,fdt-list-dir')
if self._fdt_dir:
indir = tools.get_input_filename(self._fdt_dir)
+ if indir:
+ tools.append_input_dirs(indir)
fdts = glob.glob('*.dtb', root_dir=indir)
self._fdts = [os.path.splitext(f)[0] for f in sorted(fdts)]
else:
@@ -518,14 +545,14 @@
# are removed from self._entries later.
self._priv_entries = dict(self._entries)
- def _get_priv_keys_dir(self, data):
- """Detect private keys path among binman include directories
+ def _get_keys_dir(self, data):
+ """Detect private and encryption keys path among binman include directories
Args:
data: FIT image in binary format
Returns:
- str: Single path containing all private keys found or None
+ str: Single path containing all keys found or None
Raises:
ValueError: Filename 'rsa2048.key' not found in input path
@@ -533,11 +560,14 @@
"""
def _find_keys_dir(node):
for subnode in node.subnodes:
- if subnode.name.startswith('signature'):
+ if (subnode.name.startswith('signature') or
+ subnode.name.startswith('cipher')):
if subnode.props.get('key-name-hint') is None:
continue
hint = subnode.props['key-name-hint'].value
- name = tools.get_input_filename(f"{hint}.key")
+ name = tools.get_input_filename(
+ f"{hint}.key" if subnode.name.startswith('signature')
+ else f"{hint}.bin")
path = os.path.dirname(name)
if path not in paths:
paths.append(path)
@@ -587,8 +617,9 @@
align = self._fit_props.get('fit,align')
if align is not None:
args.update({'align': fdt_util.fdt32_to_cpu(align.value)})
- if self._fit_props.get('fit,sign') is not None:
- args.update({'priv_keys_dir': self._get_priv_keys_dir(data)})
+ if (self._fit_props.get('fit,sign') is not None or
+ self._fit_props.get('fit,encrypt') is not None):
+ args.update({'keys_dir': self._get_keys_dir(data)})
if self.mkimage.run(reset_timestamp=True, output_fname=output_fname,
**args) is None:
if not self.GetAllowMissing():
@@ -663,6 +694,7 @@
f"not found in fdt list: {', '.join(self._fdts)}")
seq = self._fdts.index(default_dt)
val = val[1:].replace('DEFAULT-SEQ', str(seq + 1))
+ val = val.replace('DEFAULT-NAME', self._fit_default_dt)
fsw.property_string(pname, val)
return
elif pname.startswith('fit,'):
@@ -729,6 +761,7 @@
# Generate nodes for each FDT
for seq, fdt_fname in enumerate(self._fdts):
node_name = node.name[1:].replace('SEQ', str(seq + 1))
+ node_name = node_name.replace('NAME', fdt_fname)
if self._fdt_dir:
fname = os.path.join(self._fdt_dir, fdt_fname + '.dtb')
else:
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index 156567a..a553ca9 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -4233,56 +4233,69 @@
self.assertEqual(SCP_DATA, data[:len(SCP_DATA)])
def CheckFitFdt(self, dts='170_fit_fdt.dts', use_fdt_list=True,
- default_dt=None):
+ default_dt=None, use_seq_num=True):
"""Check an image with an FIT with multiple FDT images"""
- def _CheckFdt(seq, expected_data):
+ def _CheckFdt(val, expected_data):
"""Check the FDT nodes
Args:
- seq: Sequence number to check (0 or 1)
+ val: Sequence number to check (0 or 1) or fdt name
expected_data: Expected contents of 'data' property
"""
- name = 'fdt-%d' % seq
+ name = 'fdt-%s' % val
fnode = dtb.GetNode('/images/%s' % name)
self.assertIsNotNone(fnode)
self.assertEqual({'description','type', 'compression', 'data'},
set(fnode.props.keys()))
self.assertEqual(expected_data, fnode.props['data'].bytes)
- self.assertEqual('fdt-test-fdt%d.dtb' % seq,
- fnode.props['description'].value)
+ description = (
+ 'fdt-test-fdt%s.dtb' % val if len(val) == 1 else
+ 'fdt-%s.dtb' % val
+ )
+ self.assertEqual(description, fnode.props['description'].value)
self.assertEqual(fnode.subnodes[0].name, 'hash')
- def _CheckConfig(seq, expected_data):
+ def _CheckConfig(val, expected_data):
"""Check the configuration nodes
Args:
- seq: Sequence number to check (0 or 1)
+ val: Sequence number to check (0 or 1) or fdt name
expected_data: Expected contents of 'data' property
"""
cnode = dtb.GetNode('/configurations')
self.assertIn('default', cnode.props)
- self.assertEqual('config-2', cnode.props['default'].value)
+ default = (
+ 'config-2' if len(val) == 1 else
+ 'config-test-fdt2'
+ )
+ self.assertEqual(default, cnode.props['default'].value)
- name = 'config-%d' % seq
+ name = 'config-%s' % val
fnode = dtb.GetNode('/configurations/%s' % name)
self.assertIsNotNone(fnode)
self.assertEqual({'description','firmware', 'loadables', 'fdt'},
set(fnode.props.keys()))
- self.assertEqual('conf-test-fdt%d.dtb' % seq,
- fnode.props['description'].value)
- self.assertEqual('fdt-%d' % seq, fnode.props['fdt'].value)
+ description = (
+ 'conf-test-fdt%s.dtb' % val if len(val) == 1 else
+ 'conf-%s.dtb' % val
+ )
+ self.assertEqual(description, fnode.props['description'].value)
+ self.assertEqual('fdt-%s' % val, fnode.props['fdt'].value)
entry_args = {
'default-dt': 'test-fdt2',
}
+ extra_indirs = None
if use_fdt_list:
entry_args['of-list'] = 'test-fdt1 test-fdt2'
if default_dt:
entry_args['default-dt'] = default_dt
+ if use_fdt_list:
+ extra_indirs = [os.path.join(self._indir, TEST_FDT_SUBDIR)]
data = self._DoReadFileDtb(
dts,
entry_args=entry_args,
- extra_indirs=[os.path.join(self._indir, TEST_FDT_SUBDIR)])[0]
+ extra_indirs=extra_indirs)[0]
self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):])
fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)]
@@ -4291,13 +4304,22 @@
fnode = dtb.GetNode('/images/kernel')
self.assertIn('data', fnode.props)
- # Check all the properties in fdt-1 and fdt-2
- _CheckFdt(1, TEST_FDT1_DATA)
- _CheckFdt(2, TEST_FDT2_DATA)
+ if use_seq_num == True:
+ # Check all the properties in fdt-1 and fdt-2
+ _CheckFdt('1', TEST_FDT1_DATA)
+ _CheckFdt('2', TEST_FDT2_DATA)
- # Check configurations
- _CheckConfig(1, TEST_FDT1_DATA)
- _CheckConfig(2, TEST_FDT2_DATA)
+ # Check configurations
+ _CheckConfig('1', TEST_FDT1_DATA)
+ _CheckConfig('2', TEST_FDT2_DATA)
+ else:
+ # Check all the properties in fdt-1 and fdt-2
+ _CheckFdt('test-fdt1', TEST_FDT1_DATA)
+ _CheckFdt('test-fdt2', TEST_FDT2_DATA)
+
+ # Check configurations
+ _CheckConfig('test-fdt1', TEST_FDT1_DATA)
+ _CheckConfig('test-fdt2', TEST_FDT2_DATA)
def testFitFdt(self):
"""Test an image with an FIT with multiple FDT images"""
@@ -7899,6 +7921,55 @@
entry_args=entry_args,
extra_indirs=[test_subdir])[0]
+
+ def testSimpleFitEncryptedData(self):
+ """Test an image with a FIT containing data to be encrypted"""
+ data = tools.read_file(self.TestFile("aes256.bin"))
+ self._MakeInputFile("keys/aes256.bin", data)
+
+ keys_subdir = os.path.join(self._indir, "keys")
+ data = self._DoReadFileDtb(
+ '343_fit_encrypt_data.dts',
+ extra_indirs=[keys_subdir])[0]
+
+ fit = fdt.Fdt.FromData(data)
+ fit.Scan()
+
+ # Extract the encrypted data and the Initialization Vector from the FIT
+ node = fit.GetNode('/images/u-boot')
+ subnode = fit.GetNode('/images/u-boot/cipher')
+ data_size_unciphered = int.from_bytes(fit.GetProps(node)['data-size-unciphered'].bytes,
+ byteorder='big')
+ self.assertEqual(data_size_unciphered, len(U_BOOT_NODTB_DATA))
+
+ # Retrieve the key name from the FIT removing any null byte
+ key_name = fit.GetProps(subnode)['key-name-hint'].bytes.replace(b'\x00', b'')
+ with open(self.TestFile(key_name.decode('ascii') + '.bin'), 'rb') as file:
+ key = file.read()
+ iv = fit.GetProps(subnode)['iv'].bytes.hex()
+ enc_data = fit.GetProps(node)['data'].bytes
+ outdir = tools.get_output_dir()
+ enc_data_file = os.path.join(outdir, 'encrypted_data.bin')
+ tools.write_file(enc_data_file, enc_data)
+ data_file = os.path.join(outdir, 'data.bin')
+
+ # Decrypt the encrypted data from the FIT and compare the data
+ tools.run('openssl', 'enc', '-aes-256-cbc', '-nosalt', '-d', '-in',
+ enc_data_file, '-out', data_file, '-K', key.hex(), '-iv', iv)
+ with open(data_file, 'r') as file:
+ dec_data = file.read()
+ self.assertEqual(U_BOOT_NODTB_DATA, dec_data.encode('ascii'))
+
+ def testSimpleFitEncryptedDataMissingKey(self):
+ """Test an image with a FIT containing data to be encrypted but with a missing key"""
+ with self.assertRaises(ValueError) as e:
+ self._DoReadFile('344_fit_encrypt_data_no_key.dts')
+
+ self.assertIn("Filename 'aes256.bin' not found in input path", str(e.exception))
+
+ def testFitFdtName(self):
+ """Test an image with an FIT with multiple FDT images using NAME"""
+ self.CheckFitFdt('345_fit_fdt_name.dts', use_seq_num=False)
if __name__ == "__main__":
unittest.main()
diff --git a/tools/binman/test/343_fit_encrypt_data.dts b/tools/binman/test/343_fit_encrypt_data.dts
new file mode 100644
index 0000000..d70de34
--- /dev/null
+++ b/tools/binman/test/343_fit_encrypt_data.dts
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ fit {
+ fit,encrypt;
+ description = "Test a FIT with encrypted data";
+ #address-cells = <1>;
+
+ images {
+ u-boot {
+ description = "U-Boot";
+ type = "firmware";
+ arch = "arm64";
+ os = "U-Boot";
+ compression = "none";
+ load = <00000000>;
+ entry = <00000000>;
+ cipher {
+ algo = "aes256";
+ key-name-hint = "aes256";
+ };
+ u-boot-nodtb {
+ };
+ };
+ fdt-1 {
+ description = "Flattened Device Tree blob";
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ cipher {
+ algo = "aes256";
+ key-name-hint = "aes256";
+ };
+ };
+ };
+
+ configurations {
+ default = "conf-1";
+ conf-1 {
+ description = "Boot U-Boot with FDT blob";
+ firmware = "u-boot";
+ fdt = "fdt-1";
+ };
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/344_fit_encrypt_data_no_key.dts b/tools/binman/test/344_fit_encrypt_data_no_key.dts
new file mode 100644
index 0000000..d70de34
--- /dev/null
+++ b/tools/binman/test/344_fit_encrypt_data_no_key.dts
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ fit {
+ fit,encrypt;
+ description = "Test a FIT with encrypted data";
+ #address-cells = <1>;
+
+ images {
+ u-boot {
+ description = "U-Boot";
+ type = "firmware";
+ arch = "arm64";
+ os = "U-Boot";
+ compression = "none";
+ load = <00000000>;
+ entry = <00000000>;
+ cipher {
+ algo = "aes256";
+ key-name-hint = "aes256";
+ };
+ u-boot-nodtb {
+ };
+ };
+ fdt-1 {
+ description = "Flattened Device Tree blob";
+ type = "flat_dt";
+ arch = "arm64";
+ compression = "none";
+ cipher {
+ algo = "aes256";
+ key-name-hint = "aes256";
+ };
+ };
+ };
+
+ configurations {
+ default = "conf-1";
+ conf-1 {
+ description = "Boot U-Boot with FDT blob";
+ firmware = "u-boot";
+ fdt = "fdt-1";
+ };
+ };
+ };
+ };
+};
diff --git a/tools/binman/test/345_fit_fdt_name.dts b/tools/binman/test/345_fit_fdt_name.dts
new file mode 100644
index 0000000..631a8e5
--- /dev/null
+++ b/tools/binman/test/345_fit_fdt_name.dts
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ u-boot {
+ };
+ fit {
+ description = "test-desc";
+ #address-cells = <1>;
+ fit,fdt-list = "of-list";
+
+ images {
+ kernel {
+ description = "Vanilla Linux kernel";
+ type = "kernel";
+ arch = "ppc";
+ os = "linux";
+ compression = "gzip";
+ load = <00000000>;
+ entry = <00000000>;
+ hash-1 {
+ algo = "crc32";
+ };
+ hash-2 {
+ algo = "sha1";
+ };
+ u-boot {
+ };
+ };
+ @fdt-NAME {
+ description = "fdt-NAME.dtb";
+ type = "flat_dt";
+ compression = "none";
+ hash {
+ algo = "sha256";
+ };
+ };
+ };
+
+ configurations {
+ default = "@config-DEFAULT-NAME";
+ @config-NAME {
+ description = "conf-NAME.dtb";
+ firmware = "uboot";
+ loadables = "atf";
+ fdt = "fdt-NAME";
+ };
+ };
+ };
+ u-boot-nodtb {
+ };
+ };
+};
diff --git a/tools/binman/test/aes256.bin b/tools/binman/test/aes256.bin
new file mode 100644
index 0000000..09b8bf6
--- /dev/null
+++ b/tools/binman/test/aes256.bin
@@ -0,0 +1 @@
+1234567890abcdefghijklmnopqrstuv
\ No newline at end of file
diff --git a/tools/dtoc/fdt_util.py b/tools/dtoc/fdt_util.py
index f1f7056..d5ecc42 100644
--- a/tools/dtoc/fdt_util.py
+++ b/tools/dtoc/fdt_util.py
@@ -55,7 +55,7 @@
out = out << 32 | fdt32_to_cpu(val[1])
return out
-def EnsureCompiled(fname, tmpdir=None, capture_stderr=False):
+def EnsureCompiled(fname, tmpdir=None, capture_stderr=False, indir=None):
"""Compile an fdt .dts source file into a .dtb binary blob if needed.
Args:
@@ -63,6 +63,7 @@
left alone
tmpdir: Temporary directory for output files, or None to use the
tools-module output directory
+ indir: List of directories where input files can be found
Returns:
Filename of resulting .dtb file
@@ -79,6 +80,8 @@
dtb_output = tools.get_output_filename('source.dtb')
search_paths = [os.path.join(os.getcwd(), 'include')]
+ if indir is not None:
+ search_paths += indir
root, _ = os.path.splitext(fname)
cc, args = tools.get_target_compile_tool('cc')
args += ['-E', '-P', '-x', 'assembler-with-cpp', '-D__ASSEMBLY__']
diff --git a/tools/image-host.c b/tools/image-host.c
index 5e01b85..16389bd 100644
--- a/tools/image-host.c
+++ b/tools/image-host.c
@@ -535,7 +535,7 @@
* size values
* And, if needed, write the iv in the FIT file
*/
- if (keydest) {
+ if (keydest || (!keydest && !info.ivname)) {
ret = info.cipher->add_cipher_data(&info, keydest, fit, node_noffset);
if (ret) {
fprintf(stderr,
diff --git a/tools/u_boot_pylib/tools.py b/tools/u_boot_pylib/tools.py
index 187725b..0499a75 100644
--- a/tools/u_boot_pylib/tools.py
+++ b/tools/u_boot_pylib/tools.py
@@ -123,6 +123,22 @@
indir = dirname
tout.debug("Using input directories %s" % indir)
+def append_input_dirs(dirname):
+ """Append a list of input directories to the current list of input
+ directories
+
+ Args:
+ dirname: a list of paths to input directories to use for obtaining
+ files needed by binman to place in the image.
+ """
+ global indir
+
+ for dir in dirname:
+ if dirname not in indir:
+ indir.append(dirname)
+
+ tout.debug("Updated input directories %s" % indir)
+
def get_input_filename(fname, allow_missing=False):
"""Return a filename for use as input.