[PATCH v4 5/5] test/py: Create a test for launching UEFI binaries from FIT images
Heinrich Schuchardt
xypron.glpk at gmx.de
Sun Dec 29 17:49:48 CET 2019
On 12/29/19 11:22 AM, Heinrich Schuchardt wrote:
> On 12/24/19 5:05 PM, Cristian Ciocaltea wrote:
>> This test verifies the implementation of the 'bootm' extension that
>> handles UEFI binaries inside FIT images (enabled via CONFIG_BOOTM_EFI).
>>
>> Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea at gmail.com>
>
> Thanks a lot for devising this test.
>
> ---
>
> You are using variable env__efi_fit_tftp_file. To run the test on Gitlab
> and Travis CI a patch will be needed for:
>
> https://github.com/swarren/uboot-test-hooks.git
>
> I hope
>
> https://github.com/xypron/uboot-test-hooks/commit/20dcd721437dd5f7d7d3d235f7112246f43305d2
>
>
> will do the job.
>
> Once we have this applied we will have to adjust the config files for QEMU.
>
> ---
>
> I have been trying to run the test on qemu_arm64_defconfig using the
> following lines in u_boot_boardenv_qemu_arm64.py:
>
> env__efi_fit_tftp_file = {
> "fn": "helloworld.efi",
> "size": 4480,
> "crc32": "19f9c0ab",
> }
>
> I got an error:
>
> test/py/tests/test_efi_fit.py:417: in launch_efi
> addr = load_fit_from_host(fit) if is_sandbox else
> load_fit_from_tftp(fit)
> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
> addr = f.get('addr', None)
> if not addr:
> > addr = u_boot_utils.find_ram_base(cons)
> E NameError: name 'u_boot_utils' is not defined
>
>
> When I provided addr:
>
> env__efi_fit_tftp_file = {
> "fn": "helloworld.efi",
> "size": 4480,
> "crc32": "19f9c0ab",
> "addr": 0x40400000,
> }
>
> I got the following error:
>
> => tftpboot 1073741824 helloworld.efi
> TFTP error: trying to overwrite reserved memory...
>
> I would have expected a command
>
> tftpboot 40400000 helloworld.efi
>
> to be issued.
>
> Same error with bootm:
>
> => bootm 1077936128#config-efi-nofdt
> "Synchronous Abort" handler, esr 0x96000010
> elr: 000000000001c36c lr : 00000000000140f4 (reloc)
>
> Please, fix the lines indicated below and verify that you can actually
> execute this test on the QEMU platform.
>
> https://github.com/xypron/u-boot-build/tree/qemu-arm64/u-boot-test
>
> contains the files I use to run Python tests on qemu_arm64_defconfig.
>
>> ---
>> test/py/tests/test_efi_fit.py | 459 ++++++++++++++++++++++++++++++++++
>> 1 file changed, 459 insertions(+)
>> create mode 100644 test/py/tests/test_efi_fit.py
>>
>> diff --git a/test/py/tests/test_efi_fit.py
>> b/test/py/tests/test_efi_fit.py
>> new file mode 100644
>> index 0000000000..e1f0e42694
>> --- /dev/null
>> +++ b/test/py/tests/test_efi_fit.py
>> @@ -0,0 +1,459 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +# Copyright (c) 2019, Cristian Ciocaltea <cristian.ciocaltea at gmail.com>
>> +#
>> +# 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.
>> +
>> +import os.path
>> +import pytest
>> +import u_boot_utils as util
>
> "as util" causes an error if you use u_boot_utils.* below. Below I
> indicate the places to change.
>
>> +
>> +"""
>> +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': '$kernel_addr_r', # Loading address, optional
addr must be an integer not a string. Otherwise this does not match your
function call
addr = util.find_ram_base(cons)
Best regards
Heinrich
>> + 'dn': 'tftp/root/dir', # TFTP root directory path, optional
>> +}
>> +"""
>> +
>> +# 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 = <0>;
>> +
>> + model = "%(sys-arch)s %(fdt_type)s EFI FIT Boot Test";
>> + compatible = "%(sys-arch)s";
>> +
>> + reset at 0 {
>> + compatible = "%(sys-arch)s,reset";
>> + reg = <0>;
>> + };
>> +};
>> +'''
>> +
>> + at pytest.mark.buildconfigspec('bootm_efi')
>> + at pytest.mark.buildconfigspec('cmd_bootefi_hello_compile')
>> + at pytest.mark.buildconfigspec('fit')
>> + at pytest.mark.notbuildconfigspec('generate_acpi_table')
>> + at 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(fname):
>> + """Compute the path of a given (temporary) file.
>> +
>> + Args:
>> + fname: The name of a file within U-Boot build dir.
>> + Return:
>> + The computed file path.
>> + """
>> +
>> + return os.path.join(cons.config.build_dir, fname)
>> +
>> + 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 fd:
>> + fd.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 fd:
>> + fd.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(f):
>> + """Load the FIT image using the 'host load' command and
>> return its address.
>> +
>> + Args:
>> + f: 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 = f.get('addr', None)
>> + if not addr:
>> + addr = u_boot_utils.find_ram_base(cons)
>
> %s/u_boot_utils/util/
>
>
>> +
>> + output = cons.run_command(
>> + 'host load hostfs - %s %s/%s' % (addr, f['dn'],
>> f['fn']))
>> + expected_text = ' bytes read'
>> + sz = f.get('size', None)
>> + if sz:
>> + expected_text = '%d' % sz + expected_text
>> + assert(expected_text in output)
>> +
>> + return addr
>> +
>> + def load_fit_from_tftp(f):
>> + """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:
>> + f: 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 = f.get('addr', None)
>> + if not addr:
>> + addr = u_boot_utils.find_ram_base(cons)
>
> %s/u_boot_utils/util/
>
>
>> +
>> + fn = f['fn']
>> + output = cons.run_command('tftpboot %s %s' % (addr, fn))
>
> You have to pass addr as hexadecimal number.
>
> output = cons.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 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,
>> + 'addr': '${kernel_addr_r}',
>> + }
>> + 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')
>> +
>> + sz = fit.get('size', None)
>> + if not sz:
>> + 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.
>> + cons.run_command(
>> + 'bootm %s#%s' % (addr, fit_config),
>> wait_for_prompt=False)
>
> You have to pass the address as hexadecimal number.
>
> 'bootm %x#%s' % (addr, fit_config), wait_for_prompt=False)
>
> Best regards
>
> Heinrich
>
>> + if enable_fdt:
>> + cons.wait_for('Booting using the fdt blob')
>> + cons.wait_for('Hello, world')
>> + cons.wait_for('## Application terminated, r = 0')
>> + 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()
>>
>
>
More information about the U-Boot
mailing list