From patchwork Tue Mar 17 02:12:47 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AKASHI Takahiro X-Patchwork-Id: 243729 List-Id: U-Boot discussion From: takahiro.akashi at linaro.org (AKASHI Takahiro) Date: Tue, 17 Mar 2020 11:12:47 +0900 Subject: [RFC 14/14] test/py: add efi capsule test In-Reply-To: <20200317021247.5849-1-takahiro.akashi@linaro.org> References: <20200317021247.5849-1-takahiro.akashi@linaro.org> Message-ID: <20200317021247.5849-15-takahiro.akashi@linaro.org> We will have two pytest scenarios for capsule update here. * for firmware update (based on simple firmware management protocol) * for variable update Both can run on sandbox build and gives you some idea about how they should work on production system. Signed-off-by: AKASHI Takahiro --- test/py/tests/test_efi_capsule/conftest.py | 109 ++++++++++++++ test/py/tests/test_efi_capsule/defs.py | 21 +++ .../test_efi_capsule/test_capsule_firmware.py | 102 +++++++++++++ .../test_efi_capsule/test_capsule_variable.py | 141 ++++++++++++++++++ test/py/tests/test_efi_capsule/uboot_env.its | 25 ++++ 5 files changed, 398 insertions(+) create mode 100644 test/py/tests/test_efi_capsule/conftest.py create mode 100644 test/py/tests/test_efi_capsule/defs.py create mode 100644 test/py/tests/test_efi_capsule/test_capsule_firmware.py create mode 100644 test/py/tests/test_efi_capsule/test_capsule_variable.py create mode 100644 test/py/tests/test_efi_capsule/uboot_env.its diff --git a/test/py/tests/test_efi_capsule/conftest.py b/test/py/tests/test_efi_capsule/conftest.py new file mode 100644 index 000000000000..5d50df95cdf0 --- /dev/null +++ b/test/py/tests/test_efi_capsule/conftest.py @@ -0,0 +1,109 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2020, Linaro Limited +# Author: AKASHI Takahiro + +import os +import os.path +import pytest +import re +from subprocess import call, check_call, check_output, CalledProcessError +from defs import * + +# from test/py/conftest.py +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 + +# +# Fixture for UEFI secure boot test +# + at 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 + """ + image_path = u_boot_config.persistent_data_dir + image_path = image_path + '/' + EFI_BOOTDEV_IMAGE_NAME + image_size = EFI_BOOTDEV_IMAGE_SIZE + part_size = EFI_BOOTDEV_PART_SIZE + fs_type = EFI_BOOTDEV_FS_TYPE + + try: + # non_root = tool_is_in_path('udisksctl') + non_root = False + + # create a disk/partition + check_call('dd if=/dev/zero of=%s bs=1MiB count=%d' + % (image_path, image_size), shell=True) + check_call('sgdisk %s -n 1:0:+%dMiB' + % (image_path, part_size), shell=True) + # create a file system + check_call('dd if=/dev/zero of=%s.tmp bs=1MiB count=%d' + % (image_path, part_size), shell=True) + check_call('mkfs -t %s %s.tmp' % (fs_type, image_path), shell=True) + check_call('dd if=%s.tmp of=%s bs=1MiB seek=1 count=%d conv=notrunc' + % (image_path, image_path, 1), shell=True) + check_call('rm %s.tmp' % image_path, shell=True) + if non_root: + out_data = check_output('udisksctl loop-setup -f %s -o %d' + % (image_path, 1048576), shell=True).decode() + m = re.search('(?<= as )(.*)\.', out_data) + loop_dev = m.group(1) + # print('loop device is: %s' % loop_dev) + out_data = check_output('udisksctl info -b %s' + % loop_dev, shell=True).decode() + m = re.search('MountPoints:[ \t]+(.*)', out_data) + mnt_point = m.group(1) + else: + loop_dev = check_output('sudo losetup -o 1MiB --sizelimit %dMiB --show -f %s | tr -d "\n"' + % (part_size, image_path), shell=True).decode() + mnt_point = '/mnt' + check_call('sudo mount -t %s -o umask=000 %s %s' + % (fs_type, loop_dev, mnt_point), shell=True) + + # print('1: mount point is: %s' % mnt_point) + + # Add variable capsules + check_call('mkdir -p %s%s' % (mnt_point, CAPSULE_DATA_DIR), shell=True) + check_call('mkdir -p %s%s' % (mnt_point, CAPSULE_INSTALL_DIR), shell=True) + check_call('%s/tools/mkeficapsule -v 1 %s%s/tESt01' + % (u_boot_config.build_dir, mnt_point, + CAPSULE_DATA_DIR), shell=True) + check_call('%s/tools/mkeficapsule -v 2 %s%s/Test02' + % (u_boot_config.build_dir, + mnt_point, CAPSULE_DATA_DIR), shell=True) + check_call('%s/tools/mkeficapsule -v 3 %s%s/TeST03' + % (u_boot_config.build_dir, + mnt_point, CAPSULE_DATA_DIR), shell=True) + + # Create its for FIT image + check_call('sed -e \"s?BINFILE?%s%s/%s?\" %s/test/py/tests/test_efi_capsule/uboot_env.its > %s%s/uboot_env.its' + % (mnt_point, CAPSULE_DATA_DIR, FW_BIN, + u_boot_config.source_dir, + mnt_point, CAPSULE_DATA_DIR), shell=True) + + if non_root: + call('udisksctl unmount -b %s' % loop_dev, shell=True) + # not needed + # check_call('udisksctl loop-delete -b %s' % loop_dev, shell=True) + else: + call('sudo umount %s' % loop_dev, shell=True) + call('sudo losetup -d %s' % loop_dev, shell=True) + + except CalledProcessError as e: + pytest.skip('Setup failed: %s' % e.cmd) + return + else: + yield image_path + finally: + print('1: mount point is: %s' % mnt_point) + # call('rm -f %s' % image_path, shell=True) diff --git a/test/py/tests/test_efi_capsule/defs.py b/test/py/tests/test_efi_capsule/defs.py new file mode 100644 index 000000000000..c2083b59c19f --- /dev/null +++ b/test/py/tests/test_efi_capsule/defs.py @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0+ + +# Disk image name +EFI_BOOTDEV_IMAGE_NAME='test_efi_capsule.img' + +# Size in MiB +EFI_BOOTDEV_IMAGE_SIZE=16 +EFI_BOOTDEV_PART_SIZE=8 + +# Partition file system type +EFI_BOOTDEV_FS_TYPE='vfat' + +# Owner guid +GUID='11111111-2222-3333-4444-123456789abc' + +# Directories +CAPSULE_DATA_DIR='/EFI/CapsuleTestData' +CAPSULE_INSTALL_DIR='/EFI/UpdateCapsule' + +# +FW_BIN='spi_sf.bin' diff --git a/test/py/tests/test_efi_capsule/test_capsule_firmware.py b/test/py/tests/test_efi_capsule/test_capsule_firmware.py new file mode 100644 index 000000000000..52f8fa108ebd --- /dev/null +++ b/test/py/tests/test_efi_capsule/test_capsule_firmware.py @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2020, Linaro Limited +# Author: AKASHI Takahiro +# +# U-Boot UEFI: Capsule Update for Simple FIT Image Test + +""" +This test verifies capsule-on-disk firmware update +""" + +import pytest +import re +from defs import * +from subprocess import call, check_call, check_output, CalledProcessError + + at pytest.mark.boardspec('sandbox') + at pytest.mark.buildconfigspec('efi_capsule_fit_simple') + at pytest.mark.buildconfigspec('efi_capsule_on_disk') + at pytest.mark.buildconfigspec('dfu') + at pytest.mark.buildconfigspec('dfu_sf') + at pytest.mark.buildconfigspec('dfu_tftp') + at pytest.mark.buildconfigspec('cmd_efidebug') + at pytest.mark.buildconfigspec('cmd_fat') + at pytest.mark.buildconfigspec('cmd_nvedit_efi') + at pytest.mark.slow +class TestEfiCapsuleFirmwareSimple(object): + def test_efi_capsule_fw1(self, u_boot_config, u_boot_console, efi_capsule_data): + """ + Test Case 1 - Update U-Boot environment on SPI Flash + """ + 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 1 TEST host 0:1 /helloworld.efi ""', + 'efidebug boot order 1', + 'env set dfu_alt_info sf raw 0 0x200000', + 'env set FW_STATUS This is Old environment', + 'env print FW_STATUS', + 'env save']) + assert('Old environment' in ''.join(output)) + + output = u_boot_console.run_command_list([ + 'env set FW_STATUS This is New environment', + 'env export -c 5000000', + 'fatwrite host 0:1 5000000 %s/%s $filesize' + % (CAPSULE_DATA_DIR, FW_BIN), + 'setenv -e -guid 39b68c46-f7fb-441b-b6ec-16b0f69821f3 Capsule0000', + 'fatls host 0:1 %s' % CAPSULE_DATA_DIR + ]) + assert(('%s' % FW_BIN) in ''.join(output)) + + + try: + part_size = EFI_BOOTDEV_PART_SIZE + fs_type = EFI_BOOTDEV_FS_TYPE + mnt_point = '/mnt' + + loop_dev = check_output('sudo losetup -o 1MiB --sizelimit %dMiB --show -f %s | tr -d "\n"' + % (part_size, disk_img), shell=True).decode() + check_call('sudo mount -t %s -o umask=000 %s %s' + % (fs_type, loop_dev, mnt_point), shell=True) + check_call('%s/tools/mkimage -f %s%s/uboot_env.its %s%s/uboot_env.itb' + % (u_boot_config.build_dir, + mnt_point, CAPSULE_DATA_DIR, + mnt_point, CAPSULE_DATA_DIR), shell=True) + check_call('%s/tools/mkeficapsule -f %s%s/uboot_env.itb %s%s/Test01' + % (u_boot_config.build_dir, + mnt_point, CAPSULE_DATA_DIR, + mnt_point, CAPSULE_INSTALL_DIR), shell=True) + check_call('sudo umount %s' % loop_dev, shell=True) + check_call('sudo losetup -d %s' % loop_dev, shell=True) + except CalledProcessError: + assert('failed to create firmware capsule') + + u_boot_console.restart_uboot() + + with u_boot_console.log.section('Test Case 1-b, after reboot'): + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'env print FW_STATUS']) + assert('Old environment' in ''.join(output)) + + output = u_boot_console.run_command('fatls host 0:1 %s' + % CAPSULE_INSTALL_DIR) + assert('Test01' in output) + + output = u_boot_console.run_command('env print -e -all Capsule0000') + # assert('Capsule0000 not found' in output) + + # output = u_boot_console.run_command('printenv -e -all Capsule0000') + # assert('00000000: 01' in output) + + u_boot_console.restart_uboot() + + with u_boot_console.log.section('Test Case 1-c, after reboot'): + output = u_boot_console.run_command('env print FW_STATUS') + assert('New environment' in ''.join(output)) + + output = u_boot_console.run_command( + 'fatls host 0:1 %s/Test01' % CAPSULE_INSTALL_DIR) + assert('' == output) diff --git a/test/py/tests/test_efi_capsule/test_capsule_variable.py b/test/py/tests/test_efi_capsule/test_capsule_variable.py new file mode 100644 index 000000000000..baf4329e9a27 --- /dev/null +++ b/test/py/tests/test_efi_capsule/test_capsule_variable.py @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2020, Linaro Limited +# Author: AKASHI Takahiro +# +# U-Boot UEFI: Capsule Update for Variables Test + +""" +This test verifies capsule-on-disk variable update +""" + +import pytest +import re +from defs import * + + at pytest.mark.boardspec('sandbox') + at pytest.mark.buildconfigspec('efi_capsule_update_variable') + at pytest.mark.buildconfigspec('efi_capsule_on_disk') + at pytest.mark.buildconfigspec('cmd_fat') + at pytest.mark.buildconfigspec('cmd_nvedit_efi') + at pytest.mark.slow +class TestEfiCapsuleVariable(object): + def test_efi_capsule_var1(self, u_boot_console, efi_capsule_data): + """ + Test Case 1 - Add a new UEFI variable + """ + u_boot_console.restart_uboot() + + disk_img = efi_capsule_data + with u_boot_console.log.section('Test Case 1, before reboot'): + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'efidebug boot add 1 TEST host 0:1 /helloworld.efi ""', + 'efidebug boot order 1', + 'setenv -e -guid 39b68c46-f7fb-441b-b6ec-16b0f69821f3 Capsule0000', + 'setenv -e -guid 39b68c46-f7fb-441b-b6ec-16b0f69821f3 Capsule0001', + 'setenv -e -guid 39b68c46-f7fb-441b-b6ec-16b0f69821f3 Capsule0002', + 'setenv -e Boot0003', + 'saveenv', + 'fatload host 0:1 0x5000000 %s/tESt01' % CAPSULE_DATA_DIR, + 'fatwrite host 0:1 0x5000000 %s/tESt01 $filesize' % CAPSULE_INSTALL_DIR, + 'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR]) + assert('tESt01' in ''.join(output)) + + u_boot_console.restart_uboot() + + with u_boot_console.log.section('Test Case 1, after reboot'): + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'printenv -e Boot0003']) + assert('Boot0003:' in ''.join(output)) + + output = u_boot_console.run_command( + 'printenv -e -all') + + output = u_boot_console.run_command( + 'efidebug capsule result') + assert('CapsuleLast is' in output) + assert('Capsule status: 0x0' in output) + + output = u_boot_console.run_command( + 'fatls host 0:1 %s/tESt01' % CAPSULE_INSTALL_DIR) + u_boot_console.run_command('fatrm host 0:1 %s/tESt01' + % CAPSULE_INSTALL_DIR) + assert('' == output) + + def test_efi_capsule_var2(self, u_boot_console, efi_capsule_data): + """ + Test Case 2 - Modify/Delete existing variables + """ + u_boot_console.restart_uboot() + + disk_img = efi_capsule_data + with u_boot_console.log.section('Test Case 2, before reboot'): + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'efidebug boot add 1 TEST1 host 0:1 /helloworld.efi ""', + 'efidebug boot add 2 TEST2 host 0:1 /helloworld.efi ""', + 'efidebug boot order 1 2', + 'saveenv', + 'fatload host 0:1 0x5000000 %s/Test02' % CAPSULE_DATA_DIR, + 'fatwrite host 0:1 0x5000000 %s/Test02 $filesize' % CAPSULE_INSTALL_DIR, + 'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR]) + assert('Test02' in ''.join(output)) + + u_boot_console.restart_uboot() + + with u_boot_console.log.section('Test Case 2, after reboot'): + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'printenv -e BootOrder']) + assert('00000000: 03 00 02 00' in ''.join(output)) + + output = u_boot_console.run_command( + 'printenv -e Boot0001') + assert('"Boot0001" not defined' in output) + + output = u_boot_console.run_command( + 'efidebug capsule result') + assert('CapsuleLast is' in output) + assert('Capsule status: 0x0' in output) + + output = u_boot_console.run_command( + 'fatls host 0:1 %s/Test02' % CAPSULE_INSTALL_DIR) + u_boot_console.run_command('fatrm host 0:1 %s/Test02' % CAPSULE_INSTALL_DIR) + assert('' == ''.join(output)) + + def test_efi_capsule_var3(self, u_boot_console, efi_capsule_data): + """ + Test Case 3 - Modify a non-existing variable + """ + u_boot_console.restart_uboot() + + disk_img = efi_capsule_data + with u_boot_console.log.section('Test Case 3, before reboot'): + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'efidebug boot add 1 TEST host 0:1 /helloworld.efi ""', + 'efidebug boot order 1', + 'saveenv', + 'fatload host 0:1 0x5000000 %s/TeST03' % CAPSULE_DATA_DIR, + 'fatwrite host 0:1 0x5000000 %s/TeST03 $filesize' % CAPSULE_INSTALL_DIR, + 'fatls host 0:1 %s' % CAPSULE_INSTALL_DIR]) + assert('TeST03' in ''.join(output)) + + u_boot_console.restart_uboot() + + with u_boot_console.log.section('Test Case 3, after reboot'): + output = u_boot_console.run_command_list([ + 'host bind 0 %s' % disk_img, + 'printenv -e FooVar']) + assert('"FooVar" not defined' in ''.join(output)) + + output = u_boot_console.run_command( + 'efidebug capsule result') + assert('CapsuleLast is' in output) + assert('Capsule status: 0x0' in output) + + output = u_boot_console.run_command( + 'fatls host 0:1 %s/TeST03' % CAPSULE_INSTALL_DIR) + u_boot_console.run_command('fatrm host 0:1 %s/TeST03' % CAPSULE_INSTALL_DIR) + assert('' == ''.join(output)) diff --git a/test/py/tests/test_efi_capsule/uboot_env.its b/test/py/tests/test_efi_capsule/uboot_env.its new file mode 100644 index 000000000000..a4484db45834 --- /dev/null +++ b/test/py/tests/test_efi_capsule/uboot_env.its @@ -0,0 +1,25 @@ +/* + * 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 { + sf at 0 { + description = "U-Boot environment on SPI Flash"; + data = /incbin/("BINFILE"); + compression = "none"; + type = "firmware"; + arch = "sandbox"; + load = <0>; + hash-1 { + algo = "sha1"; + }; + }; + }; +};