diff options
Diffstat (limited to 'test/py/tests')
-rw-r--r-- | test/py/tests/test_dm.py | 5 | ||||
-rw-r--r-- | test/py/tests/test_efi_capsule/conftest.py | 6 | ||||
-rw-r--r-- | test/py/tests/test_efi_capsule/test_capsule_firmware_raw.py | 21 | ||||
-rw-r--r-- | test/py/tests/test_efi_capsule/version.dtso (renamed from test/py/tests/test_efi_capsule/version.dts) | 0 | ||||
-rw-r--r-- | test/py/tests/test_efi_secboot/conftest.py | 10 | ||||
-rw-r--r-- | test/py/tests/test_efi_secboot/test_authvar.py | 4 | ||||
-rw-r--r-- | test/py/tests/test_efi_secboot/test_signed.py | 10 | ||||
-rw-r--r-- | test/py/tests/test_fpga.py | 12 | ||||
-rw-r--r-- | test/py/tests/test_help.py | 6 | ||||
-rw-r--r-- | test/py/tests/test_log.py | 11 | ||||
-rw-r--r-- | test/py/tests/test_net.py | 5 | ||||
-rw-r--r-- | test/py/tests/test_net_boot.py | 400 | ||||
-rw-r--r-- | test/py/tests/test_spi.py | 696 | ||||
-rw-r--r-- | test/py/tests/test_tpm2.py | 2 | ||||
-rw-r--r-- | test/py/tests/test_trace.py | 6 | ||||
-rw-r--r-- | test/py/tests/test_upl.py | 38 | ||||
-rw-r--r-- | test/py/tests/test_ut.py | 80 |
17 files changed, 1278 insertions, 34 deletions
diff --git a/test/py/tests/test_dm.py b/test/py/tests/test_dm.py index 68d4ea12235..be94971e455 100644 --- a/test/py/tests/test_dm.py +++ b/test/py/tests/test_dm.py @@ -13,8 +13,11 @@ def test_dm_compat(u_boot_console): for line in response[:-1].split('\n')[2:]) response = u_boot_console.run_command('dm compat') + bad_drivers = set() for driver in drivers: - assert driver in response + if not driver in response: + bad_drivers.add(driver) + assert not bad_drivers # check sorting - output looks something like this: # testacpi 0 [ ] testacpi_drv |-- acpi-test diff --git a/test/py/tests/test_efi_capsule/conftest.py b/test/py/tests/test_efi_capsule/conftest.py index 80b12977d6f..61eab5112a1 100644 --- a/test/py/tests/test_efi_capsule/conftest.py +++ b/test/py/tests/test_efi_capsule/conftest.py @@ -53,7 +53,7 @@ def efi_capsule_data(request, u_boot_config): # Update dtb to add the version information check_call('cd %s; ' - 'cp %s/test/py/tests/test_efi_capsule/version.dts .' + 'cp %s/test/py/tests/test_efi_capsule/version.dtso .' % (data_dir, u_boot_config.source_dir), shell=True) if capsule_auth_enabled: @@ -61,13 +61,13 @@ def efi_capsule_data(request, u_boot_config): 'cp %s/arch/sandbox/dts/test.dtb test_sig.dtb' % (data_dir, u_boot_config.build_dir), shell=True) check_call('cd %s; ' - 'dtc -@ -I dts -O dtb -o version.dtbo version.dts; ' + 'dtc -@ -I dts -O dtb -o version.dtbo version.dtso; ' 'fdtoverlay -i test_sig.dtb ' '-o test_ver.dtb version.dtbo' % (data_dir), shell=True) else: check_call('cd %s; ' - 'dtc -@ -I dts -O dtb -o version.dtbo version.dts; ' + 'dtc -@ -I dts -O dtb -o version.dtbo version.dtso; ' 'fdtoverlay -i %s/arch/sandbox/dts/test.dtb ' '-o test_ver.dtb version.dtbo' % (data_dir, u_boot_config.build_dir), shell=True) diff --git a/test/py/tests/test_efi_capsule/test_capsule_firmware_raw.py b/test/py/tests/test_efi_capsule/test_capsule_firmware_raw.py index a5b5c8a3853..f3a2dff5c2c 100644 --- a/test/py/tests/test_efi_capsule/test_capsule_firmware_raw.py +++ b/test/py/tests/test_efi_capsule/test_capsule_firmware_raw.py @@ -76,7 +76,7 @@ class TestEfiCapsuleFirmwareRaw: self, u_boot_config, u_boot_console, efi_capsule_data): """ Test Case 2 Update U-Boot and U-Boot environment on SPI Flash but with OsIndications unset - No update should happen + No update should happen unless CONFIG_EFI_IGNORE_OSINDICATIONS is set 0x100000-0x150000: U-Boot binary (but dummy) 0x150000-0x200000: U-Boot environment (but dummy) """ @@ -91,16 +91,27 @@ class TestEfiCapsuleFirmwareRaw: # reboot u_boot_console.restart_uboot() + ignore_os_indications = u_boot_config.buildconfig.get( + 'config_efi_ignore_osindications') + need_reboot = True if ignore_os_indications else False + + capsule_auth = u_boot_config.buildconfig.get( + 'config_efi_capsule_authenticate') + 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: - exec_manual_update(u_boot_console, disk_img, capsule_files, False) + exec_manual_update(u_boot_console, disk_img, capsule_files, need_reboot) - check_file_exist(u_boot_console, disk_img, capsule_files) + if not ignore_os_indications: + check_file_exist(u_boot_console, disk_img, capsule_files) - verify_content(u_boot_console, '100000', 'u-boot:Old') - verify_content(u_boot_console, '150000', 'u-boot-env:Old') + expected = 'u-boot:New' if (ignore_os_indications and not capsule_auth) else 'u-boot:Old' + verify_content(u_boot_console, '100000', expected) + + expected = 'u-boot-env:New' if (ignore_os_indications and not capsule_auth) else 'u-boot-env:Old' + verify_content(u_boot_console, '150000', expected) def test_efi_capsule_fw3( self, u_boot_config, u_boot_console, efi_capsule_data): diff --git a/test/py/tests/test_efi_capsule/version.dts b/test/py/tests/test_efi_capsule/version.dtso index 07850cc6064..07850cc6064 100644 --- a/test/py/tests/test_efi_capsule/version.dts +++ b/test/py/tests/test_efi_capsule/version.dtso diff --git a/test/py/tests/test_efi_secboot/conftest.py b/test/py/tests/test_efi_secboot/conftest.py index ff7ac7c8101..0fa0747fc76 100644 --- a/test/py/tests/test_efi_secboot/conftest.py +++ b/test/py/tests/test_efi_secboot/conftest.py @@ -64,6 +64,12 @@ def efi_boot_env(request, u_boot_config): 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) + # db2 (APPEND_WRITE) + check_call('cd %s; openssl req -x509 -sha256 -newkey rsa:2048 -subj /CN=TEST_db2/ -keyout db2.key -out db2.crt -nodes -days 365' + % mnt_point, shell=True) + check_call('cd %s; %scert-to-efi-sig-list -g %s db2.crt db2.esl; %ssign-efi-sig-list -a -c KEK.crt -k KEK.key db db2.esl db2.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) @@ -84,6 +90,10 @@ def efi_boot_env(request, u_boot_config): check_call('cd %s; %scert-to-efi-hash-list -g %s -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_hash2 (digest of TEST_db2 certificate, with APPEND_WRITE) + check_call('cd %s; %scert-to-efi-hash-list -g %s -s 256 db2.crt dbx_hash2.crl; %ssign-efi-sig-list -a -c KEK.crt -k KEK.key dbx dbx_hash2.crl dbx_hash2.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), diff --git a/test/py/tests/test_efi_secboot/test_authvar.py b/test/py/tests/test_efi_secboot/test_authvar.py index f99b8270a64..d5aeb650480 100644 --- a/test/py/tests/test_efi_secboot/test_authvar.py +++ b/test/py/tests/test_efi_secboot/test_authvar.py @@ -183,7 +183,7 @@ class TestEfiAuthVar(object): assert 'db:' in ''.join(output) output = u_boot_console.run_command_list([ - 'fatload host 0:1 4000000 db1.auth', + 'fatload host 0:1 4000000 db2.auth', 'setenv -e -nv -bs -rt -a -i 4000000:$filesize db']) assert 'Failed to set EFI variable' in ''.join(output) @@ -197,7 +197,7 @@ class TestEfiAuthVar(object): 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', + 'fatload host 0:1 4000000 db2.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) diff --git a/test/py/tests/test_efi_secboot/test_signed.py b/test/py/tests/test_efi_secboot/test_signed.py index 5000a4ab7b6..f604138a356 100644 --- a/test/py/tests/test_efi_secboot/test_signed.py +++ b/test/py/tests/test_efi_secboot/test_signed.py @@ -177,7 +177,7 @@ class TestEfiSignedImage(object): 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', + 'fatload host 0:1 4000000 db2.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([ @@ -201,7 +201,7 @@ class TestEfiSignedImage(object): 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', + 'fatload host 0:1 4000000 dbx_hash2.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([ @@ -223,7 +223,7 @@ class TestEfiSignedImage(object): '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', - 'fatload host 0:1 4000000 db1.auth', + 'fatload host 0:1 4000000 db2.auth', 'setenv -e -nv -bs -rt -at -a -i 4000000:$filesize db', 'fatload host 0:1 4000000 dbx_hash1.auth', 'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx']) @@ -300,7 +300,7 @@ class TestEfiSignedImage(object): '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', - 'fatload host 0:1 4000000 db1.auth', + 'fatload host 0:1 4000000 db2.auth', 'setenv -e -nv -bs -rt -at -a -i 4000000:$filesize db', 'fatload host 0:1 4000000 dbx_hash384.auth', 'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx']) @@ -323,7 +323,7 @@ class TestEfiSignedImage(object): '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', - 'fatload host 0:1 4000000 db1.auth', + 'fatload host 0:1 4000000 db2.auth', 'setenv -e -nv -bs -rt -at -a -i 4000000:$filesize db', 'fatload host 0:1 4000000 dbx_hash512.auth', 'setenv -e -nv -bs -rt -at -i 4000000:$filesize dbx']) diff --git a/test/py/tests/test_fpga.py b/test/py/tests/test_fpga.py index ca7ef8ea40d..460ff227f6f 100644 --- a/test/py/tests/test_fpga.py +++ b/test/py/tests/test_fpga.py @@ -256,7 +256,7 @@ def test_fpga_loadbp(u_boot_console): @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('legacy_image_format') def test_fpga_loadmk_fail(u_boot_console): f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy') @@ -275,7 +275,7 @@ def test_fpga_loadmk_fail(u_boot_console): @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('legacy_image_format') def test_fpga_loadmk_legacy(u_boot_console): f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy') @@ -289,7 +289,7 @@ def test_fpga_loadmk_legacy(u_boot_console): @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('legacy_image_format') 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') @@ -306,7 +306,7 @@ def test_fpga_loadmk_legacy_variable_fpga(u_boot_console): @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('legacy_image_format') 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') @@ -323,7 +323,7 @@ def test_fpga_loadmk_legacy_variable_fpgadata(u_boot_console): @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('legacy_image_format') def test_fpga_loadmk_legacy_variable(u_boot_console): f, dev, addr, bit, bit_size = load_file_from_var(u_boot_console, 'mkimage_legacy') @@ -342,7 +342,7 @@ def test_fpga_loadmk_legacy_variable(u_boot_console): @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('legacy_image_format') @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') diff --git a/test/py/tests/test_help.py b/test/py/tests/test_help.py index 153133cf28f..2325ff69229 100644 --- a/test/py/tests/test_help.py +++ b/test/py/tests/test_help.py @@ -7,7 +7,11 @@ import pytest def test_help(u_boot_console): """Test that the "help" command can be executed.""" - u_boot_console.run_command('help') + lines = u_boot_console.run_command('help') + if u_boot_console.config.buildconfig.get('config_cmd_2048', 'n') == 'y': + assert lines.splitlines()[0] == "2048 - The 2048 game" + else: + assert lines.splitlines()[0] == "? - alias for 'help'" @pytest.mark.boardspec('sandbox') def test_help_no_devicetree(u_boot_console): diff --git a/test/py/tests/test_log.py b/test/py/tests/test_log.py index 140dcb9aa2b..79808674bbe 100644 --- a/test/py/tests/test_log.py +++ b/test/py/tests/test_log.py @@ -27,13 +27,16 @@ def test_log_format(u_boot_console): cons = u_boot_console with cons.log.section('format'): - run_with_format('all', 'NOTICE.arch,file.c:123-func() msg') + pad = int(u_boot_console.config.buildconfig.get('config_logf_func_pad')) + padding = ' ' * (pad - len('func')) + + run_with_format('all', f'NOTICE.arch,file.c:123-{padding}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('fm', f'{padding}func() msg') + run_with_format('clfm', f'NOTICE.arch,{padding}func() msg') + run_with_format('FLfm', f'file.c:123-{padding}func() msg') run_with_format('lm', 'NOTICE. msg') run_with_format('m', 'msg') diff --git a/test/py/tests/test_net.py b/test/py/tests/test_net.py index 038a473b239..ad143c19b0d 100644 --- a/test/py/tests/test_net.py +++ b/test/py/tests/test_net.py @@ -254,7 +254,7 @@ def test_net_network_discovery(u_boot_console): assert 'Set gatewayip6:' in output assert '0000:0000:0000:0000:0000:0000:0000:0000' not in output -@pytest.mark.buildconfigspec('cmd_net') +@pytest.mark.buildconfigspec('cmd_tftpboot') def test_net_tftpboot(u_boot_console): """Test the tftpboot command. @@ -335,7 +335,6 @@ def test_net_nfs(u_boot_console): output = u_boot_console.run_command('crc32 %x $filesize' % addr) assert expected_crc in output -@pytest.mark.buildconfigspec("cmd_net") @pytest.mark.buildconfigspec("cmd_pxe") def test_net_pxe_get(u_boot_console): """Test the pxe get command. @@ -391,7 +390,7 @@ def test_net_pxe_get(u_boot_console): assert "Config file 'default.boot' found" in output @pytest.mark.buildconfigspec("cmd_crc32") -@pytest.mark.buildconfigspec("cmd_net") +@pytest.mark.buildconfigspec("cmd_tftpboot") @pytest.mark.buildconfigspec("cmd_tftpput") def test_net_tftpput(u_boot_console): """Test the tftpput command. diff --git a/test/py/tests/test_net_boot.py b/test/py/tests/test_net_boot.py new file mode 100644 index 00000000000..63309fe82e1 --- /dev/null +++ b/test/py/tests/test_net_boot.py @@ -0,0 +1,400 @@ +# SPDX-License-Identifier: GPL-2.0 +# (C) Copyright 2023, Advanced Micro Devices, Inc. + +import pytest +import u_boot_utils +import test_net +import re + +""" +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: + +# Details regarding a boot image file that may be read from a TFTP server. This +# variable may be omitted or set to None if TFTP boot testing is not possible +# or desired. +env__net_tftp_bootable_file = { + 'fn': 'image.ub', + 'addr': 0x10000000, + 'size': 5058624, + 'crc32': 'c2244b26', + 'pattern': 'Linux', + 'config': 'config@2', + 'timeout': 50000, + 'check_type': 'boot_error', + 'check_pattern': 'ERROR', +} + +# False or omitted if a TFTP boot test should be tested. +# If TFTP boot testing is not possible or desired, set this variable to True. +# For example: If FIT image is not proper to boot +env__tftp_boot_test_skip = False + +# Here is the example of FIT image configurations: +configurations { + default = "config@1"; + config@1 { + description = "Boot Linux kernel with config@1"; + kernel = "kernel@0"; + fdt = "fdt@0"; + ramdisk = "ramdisk@0"; + hash@1 { + algo = "sha1"; + }; + }; + config@2 { + description = "Boot Linux kernel with config@2"; + kernel = "kernel@1"; + fdt = "fdt@1"; + ramdisk = "ramdisk@1"; + hash@1 { + algo = "sha1"; + }; + }; +}; + +# Details regarding a file that may be read from a TFTP server. This variable +# may be omitted or set to None if PXE testing is not possible or desired. +env__net_pxe_bootable_file = { + 'fn': 'default', + 'addr': 0x10000000, + 'size': 74, + 'timeout': 50000, + 'pattern': 'Linux', + 'valid_label': '1', + 'invalid_label': '2', + 'exp_str_invalid': 'Skipping install for failure retrieving', + 'local_label': '3', + 'exp_str_local': 'missing environment variable: localcmd', + 'empty_label': '4', + 'exp_str_empty': 'No kernel given, skipping boot', + 'check_type': 'boot_error', + 'check_pattern': 'ERROR', +} + +# False or omitted if a PXE boot test should be tested. +# If PXE boot testing is not possible or desired, set this variable to True. +# For example: If pxe configuration file is not proper to boot +env__pxe_boot_test_skip = False + +# Here is the example of pxe configuration file ordered based on the execution +# flow: +1) /tftpboot/pxelinux.cfg/default-arm-zynqmp + + menu include pxelinux.cfg/default-arm + timeout 50 + + default Linux + +2) /tftpboot/pxelinux.cfg/default-arm + + menu title Linux boot selections + menu include pxelinux.cfg/default + + label install + menu label Invalid boot + kernel kernels/install.bin + append console=ttyAMA0,38400 debug earlyprintk + initrd initrds/uzInitrdDebInstall + + label local + menu label Local boot + append root=/dev/sdb1 + localboot 1 + + label boot + menu label Empty boot + +3) /tftpboot/pxelinux.cfg/default + + label Linux + menu label Boot kernel + kernel Image + fdt system.dtb + initrd rootfs.cpio.gz.u-boot +""" + +def setup_networking(u_boot_console): + test_net.test_net_dhcp(u_boot_console) + if not test_net.net_set_up: + test_net.test_net_setup_static(u_boot_console) + +def setup_tftpboot_boot(u_boot_console): + f = u_boot_console.config.env.get('env__net_tftp_bootable_file', None) + if not f: + pytest.skip('No TFTP bootable file to read') + + setup_networking(u_boot_console) + addr = f.get('addr', None) + if not addr: + addr = u_boot_utils.find_ram_base(u_boot_console) + + fn = f['fn'] + timeout = f.get('timeout', 50000) + + with u_boot_console.temporary_timeout(timeout): + 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) + output = u_boot_console.run_command('crc32 %x $filesize' % addr) + if expected_crc: + assert expected_crc in output + + pattern = f.get('pattern') + chk_type = f.get('check_type', 'boot_error') + chk_pattern = re.compile(f.get('check_pattern', 'ERROR')) + config = f.get('config', None) + + return addr, timeout, pattern, chk_type, chk_pattern, config + +@pytest.mark.buildconfigspec('cmd_tftpboot') +def test_net_tftpboot_boot(u_boot_console): + """Boot the loaded image + + A boot file (fit image) is downloaded from the TFTP server and booted using + bootm command with the default fit configuration, its boot log pattern 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 u_boot_console.config.env.get('env__tftp_boot_test_skip', True): + pytest.skip('TFTP boot test is not enabled!') + + addr, timeout, pattern, chk_type, chk_pattern, imcfg = setup_tftpboot_boot( + u_boot_console + ) + + if imcfg: + bootcmd = 'bootm %x#%s' % (addr, imcfg) + else: + bootcmd = 'bootm %x' % addr + + with u_boot_console.enable_check( + chk_type, chk_pattern + ), u_boot_console.temporary_timeout(timeout): + try: + # wait_for_prompt=False makes the core code not wait for the U-Boot + # prompt code to be seen, since it won't be on a successful kernel + # boot + u_boot_console.run_command(bootcmd, wait_for_prompt=False) + + # Wait for boot log pattern + u_boot_console.wait_for(pattern) + finally: + # This forces the console object to be shutdown, so any subsequent + # test will reset the board back into U-Boot. We want to force this + # no matter whether the kernel boot passed or failed. + u_boot_console.drain_console() + u_boot_console.cleanup_spawn() + +def setup_pxe_boot(u_boot_console): + f = u_boot_console.config.env.get('env__net_pxe_bootable_file', None) + if not f: + pytest.skip('No PXE bootable file to read') + + setup_networking(u_boot_console) + bootfile = u_boot_console.run_command('echo $bootfile') + if not bootfile: + bootfile = '<NULL>' + + return f, bootfile + +@pytest.mark.buildconfigspec('cmd_pxe') +def test_net_pxe_boot(u_boot_console): + """Test the pxe boot command. + + A pxe configuration file is downloaded from the TFTP server and interpreted + to boot the images mentioned in pxe configuration file. + + The details of the file to download are provided by the boardenv_* file; + see the comment at the beginning of this file. + """ + if u_boot_console.config.env.get('env__pxe_boot_test_skip', True): + pytest.skip('PXE boot test is not enabled!') + + f, bootfile = setup_pxe_boot(u_boot_console) + addr = f.get('addr', None) + timeout = f.get('timeout', u_boot_console.p.timeout) + fn = f['fn'] + + if addr: + u_boot_console.run_command('setenv pxefile_addr_r %x' % addr) + + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command('pxe get') + + expected_text = 'Bytes transferred = ' + sz = f.get('size', None) + if sz: + expected_text += '%d' % sz + assert 'TIMEOUT' not in output + assert expected_text in output + assert f"Config file '{bootfile}' found" in output + + pattern = f.get('pattern') + chk_type = f.get('check_type', 'boot_error') + chk_pattern = re.compile(f.get('check_pattern', 'ERROR')) + + if not addr: + pxe_boot_cmd = 'pxe boot' + else: + pxe_boot_cmd = 'pxe boot %x' % addr + + with u_boot_console.enable_check( + chk_type, chk_pattern + ), u_boot_console.temporary_timeout(timeout): + try: + u_boot_console.run_command(pxe_boot_cmd, wait_for_prompt=False) + u_boot_console.wait_for(pattern) + finally: + u_boot_console.drain_console() + u_boot_console.cleanup_spawn() + +@pytest.mark.buildconfigspec('cmd_pxe') +def test_net_pxe_boot_config(u_boot_console): + """Test the pxe boot command by selecting different combination of labels + + A pxe configuration file is downloaded from the TFTP server and interpreted + to boot the images mentioned in pxe configuration file. + + The details of the file to download are provided by the boardenv_* file; + see the comment at the beginning of this file. + """ + if u_boot_console.config.env.get('env__pxe_boot_test_skip', True): + pytest.skip('PXE boot test is not enabled!') + + f, bootfile = setup_pxe_boot(u_boot_console) + addr = f.get('addr', None) + timeout = f.get('timeout', u_boot_console.p.timeout) + fn = f['fn'] + local_label = f['local_label'] + empty_label = f['empty_label'] + exp_str_local = f['exp_str_local'] + exp_str_empty = f['exp_str_empty'] + + if addr: + u_boot_console.run_command('setenv pxefile_addr_r %x' % addr) + + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command('pxe get') + + expected_text = 'Bytes transferred = ' + sz = f.get('size', None) + if sz: + expected_text += '%d' % sz + assert 'TIMEOUT' not in output + assert expected_text in output + assert f"Config file '{bootfile}' found" in output + + pattern = f.get('pattern') + chk_type = f.get('check_type', 'boot_error') + chk_pattern = re.compile(f.get('check_pattern', 'ERROR')) + + if not addr: + pxe_boot_cmd = 'pxe boot' + else: + pxe_boot_cmd = 'pxe boot %x' % addr + + with u_boot_console.enable_check( + chk_type, chk_pattern + ), u_boot_console.temporary_timeout(timeout): + try: + u_boot_console.run_command(pxe_boot_cmd, wait_for_prompt=False) + + # pxe config is loaded where multiple labels are there and need to + # select particular label to boot and check for expected string + # In this case, local label is selected and it should look for + # localcmd env variable and if that variable is not defined it + # should not boot it and come out to u-boot prompt + u_boot_console.wait_for('Enter choice:') + u_boot_console.run_command(local_label, wait_for_prompt=False) + expected_str = u_boot_console.p.expect([exp_str_local]) + assert ( + expected_str == 0 + ), f'Expected string: {exp_str_local} did not match!' + + # In this case, empty label is selected and it should look for + # kernel image path and if it is not set it should fail it and load + # default label to boot + u_boot_console.run_command(pxe_boot_cmd, wait_for_prompt=False) + u_boot_console.wait_for('Enter choice:') + u_boot_console.run_command(empty_label, wait_for_prompt=False) + expected_str = u_boot_console.p.expect([exp_str_empty]) + assert ( + expected_str == 0 + ), f'Expected string: {exp_str_empty} did not match!' + + u_boot_console.wait_for(pattern) + finally: + u_boot_console.drain_console() + u_boot_console.cleanup_spawn() + +@pytest.mark.buildconfigspec('cmd_pxe') +def test_net_pxe_boot_config_invalid(u_boot_console): + """Test the pxe boot command by selecting invalid label + + A pxe configuration file is downloaded from the TFTP server and interpreted + to boot the images mentioned in pxe configuration file. + + The details of the file to download are provided by the boardenv_* file; + see the comment at the beginning of this file. + """ + if u_boot_console.config.env.get('env__pxe_boot_test_skip', True): + pytest.skip('PXE boot test is not enabled!') + + f, bootfile = setup_pxe_boot(u_boot_console) + addr = f.get('addr', None) + timeout = f.get('timeout', u_boot_console.p.timeout) + fn = f['fn'] + invalid_label = f['invalid_label'] + exp_str_invalid = f['exp_str_invalid'] + + if addr: + u_boot_console.run_command('setenv pxefile_addr_r %x' % addr) + + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command('pxe get') + + expected_text = 'Bytes transferred = ' + sz = f.get('size', None) + if sz: + expected_text += '%d' % sz + assert 'TIMEOUT' not in output + assert expected_text in output + assert f"Config file '{bootfile}' found" in output + + pattern = f.get('pattern') + if not addr: + pxe_boot_cmd = 'pxe boot' + else: + pxe_boot_cmd = 'pxe boot %x' % addr + + with u_boot_console.temporary_timeout(timeout): + try: + u_boot_console.run_command(pxe_boot_cmd, wait_for_prompt=False) + + # pxe config is loaded where multiple labels are there and need to + # select particular label to boot and check for expected string + # In this case invalid label is selected, it should load invalid + # label and if it fails it should load the default label to boot + u_boot_console.wait_for('Enter choice:') + u_boot_console.run_command(invalid_label, wait_for_prompt=False) + expected_str = u_boot_console.p.expect([exp_str_invalid]) + assert ( + expected_str == 0 + ), f'Expected string: {exp_str_invalid} did not match!' + + u_boot_console.wait_for(pattern) + finally: + u_boot_console.drain_console() + u_boot_console.cleanup_spawn() diff --git a/test/py/tests/test_spi.py b/test/py/tests/test_spi.py new file mode 100644 index 00000000000..c81eca5fba6 --- /dev/null +++ b/test/py/tests/test_spi.py @@ -0,0 +1,696 @@ +# SPDX-License-Identifier: GPL-2.0 +# (C) Copyright 2024, Advanced Micro Devices, Inc. + +""" +Note: This test relies on boardenv_* containing configuration values to define +spi minimum and maximum frequencies at which the flash part can operate on and +these tests run at different spi frequency randomised values in the range +multiple times based on the user defined iteration value. +It also defines the SPI bus number containing the SPI-flash chip, SPI +chip-select, SPI mode, SPI flash part name and timeout parameters. If minimum +and maximum frequency is not defined, it will run on freq 0 by default. + +Without the boardenv_* configuration, this test will be automatically skipped. + +It also relies on configuration values for supported flashes for lock and +unlock cases for SPI family flash. It will run lock-unlock cases only for the +supported flash parts. + +For Example: + +# Details of SPI device test parameters required for SPI device testing: + +# bus - SPI bus number to init the flash device +# chip_select - SPI chip select number to init the flash device +# min_freq - Minimum frequency in hz at which the flash part can operate, set 0 +# or None for default frequency +# max_freq - Maximum frequency in hz at which the flash part can operate, set 0 +# or None for default frequency +# mode - SPI mode to init the flash device +# part_name - SPI flash part name to be detected +# timeout - Default timeout to run the sf commands +# iteration - No of iteration to run SPI flash test + +env__spi_device_test = { + 'bus': 0, + 'chip_select': 0, + 'min_freq': 10000000, + 'max_freq': 100000000, + 'mode': 0, + 'part_name': 'n25q00a', + 'timeout': 100000, + 'iteration': 5, +} + +# supported_flash - Flash parts name which support lock-unlock functionality +env__spi_lock_unlock = { + 'supported_flash': 'mt25qu512a, n25q00a, n25q512ax3', +} +""" + +import random +import re +import pytest +import u_boot_utils + +SPI_DATA = {} +EXPECTED_ERASE = 'Erased: OK' +EXPECTED_WRITE = 'Written: OK' +EXPECTED_READ = 'Read: OK' +EXPECTED_ERASE_ERRORS = [ + 'Erase operation failed', + 'Attempted to modify a protected sector', + 'Erased: ERROR', + 'is protected and cannot be erased', + 'ERROR: flash area is locked', +] +EXPECTED_WRITE_ERRORS = [ + 'ERROR: flash area is locked', + 'Program operation failed', + 'Attempted to modify a protected sector', + 'Written: ERROR', +] + +def get_params_spi(u_boot_console): + ''' Get SPI device test parameters from boardenv file ''' + f = u_boot_console.config.env.get('env__spi_device_test', None) + if not f: + pytest.skip('No env file to read for SPI family device test') + + bus = f.get('bus', 0) + cs = f.get('chip_select', 0) + mode = f.get('mode', 0) + part_name = f.get('part_name', None) + timeout = f.get('timeout', None) + + if not part_name: + pytest.skip('No env file to read SPI family flash part name') + + return bus, cs, mode, part_name, timeout + +def spi_find_freq_range(u_boot_console): + '''Find out minimum and maximum frequnecies that SPI device can operate''' + f = u_boot_console.config.env.get('env__spi_device_test', None) + if not f: + pytest.skip('No env file to read for SPI family device test') + + min_f = f.get('min_freq', None) + max_f = f.get('max_freq', None) + iterations = f.get('iteration', 1) + + if not min_f: + min_f = 0 + if not max_f: + max_f = 0 + + max_f = max(max_f, min_f) + + return min_f, max_f, iterations + +def spi_pre_commands(u_boot_console, freq): + ''' Find out SPI family flash memory parameters ''' + bus, cs, mode, part_name, timeout = get_params_spi(u_boot_console) + + output = u_boot_console.run_command(f'sf probe {bus}:{cs} {freq} {mode}') + if not 'SF: Detected' in output: + pytest.fail('No SPI device available') + + if not part_name in output: + pytest.fail('SPI flash part name not recognized') + + m = re.search('page size (.+?) Bytes', output) + if m: + try: + page_size = int(m.group(1)) + except ValueError: + pytest.fail('SPI page size not recognized') + + m = re.search('erase size (.+?) KiB', output) + if m: + try: + erase_size = int(m.group(1)) + except ValueError: + pytest.fail('SPI erase size not recognized') + + erase_size *= 1024 + + m = re.search('total (.+?) MiB', output) + if m: + try: + total_size = int(m.group(1)) + except ValueError: + pytest.fail('SPI total size not recognized') + + total_size *= 1024 * 1024 + + m = re.search('Detected (.+?) with', output) + if m: + try: + flash_part = m.group(1) + assert flash_part == part_name + except ValueError: + pytest.fail('SPI flash part not recognized') + + global SPI_DATA + SPI_DATA = { + 'page_size': page_size, + 'erase_size': erase_size, + 'total_size': total_size, + 'flash_part': flash_part, + 'timeout': timeout, + } + +def get_page_size(): + ''' Get the SPI page size from spi data ''' + return SPI_DATA['page_size'] + +def get_erase_size(): + ''' Get the SPI erase size from spi data ''' + return SPI_DATA['erase_size'] + +def get_total_size(): + ''' Get the SPI total size from spi data ''' + return SPI_DATA['total_size'] + +def get_flash_part(): + ''' Get the SPI flash part name from spi data ''' + return SPI_DATA['flash_part'] + +def get_timeout(): + ''' Get the SPI timeout from spi data ''' + return SPI_DATA['timeout'] + +def spi_erase_block(u_boot_console, erase_size, total_size): + ''' Erase SPI flash memory block wise ''' + for start in range(0, total_size, erase_size): + output = u_boot_console.run_command(f'sf erase {hex(start)} {hex(erase_size)}') + assert EXPECTED_ERASE in output + +@pytest.mark.buildconfigspec('cmd_sf') +def test_spi_erase_block(u_boot_console): + ''' Test case to check SPI erase functionality by erasing memory regions + block-wise ''' + + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + spi_erase_block(u_boot_console, get_erase_size(), get_total_size()) + i = i + 1 + +def spi_write_twice(u_boot_console, page_size, erase_size, total_size, timeout): + ''' Random write till page size, random till size and full size ''' + addr = u_boot_utils.find_ram_base(u_boot_console) + + old_size = 0 + for size in ( + random.randint(4, page_size), + random.randint(page_size, total_size), + total_size, + ): + offset = random.randint(4, page_size) + offset = offset & ~3 + size = size & ~3 + size = size - old_size + output = u_boot_console.run_command(f'crc32 {hex(addr + total_size)} {hex(size)}') + m = re.search('==> (.+?)$', output) + if not m: + pytest.fail('CRC32 failed') + + expected_crc32 = m.group(1) + if old_size % page_size: + old_size = int(old_size / page_size) + old_size *= page_size + + if size % erase_size: + erasesize = int(size / erase_size + 1) + erasesize *= erase_size + + eraseoffset = int(old_size / erase_size) + eraseoffset *= erase_size + + timeout = 100000000 + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf erase {hex(eraseoffset)} {hex(erasesize)}' + ) + assert EXPECTED_ERASE in output + + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf write {hex(addr + total_size)} {hex(old_size)} {hex(size)}' + ) + assert EXPECTED_WRITE in output + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf read {hex(addr + total_size + offset)} {hex(old_size)} {hex(size)}' + ) + assert EXPECTED_READ in output + output = u_boot_console.run_command( + f'crc32 {hex(addr + total_size + offset)} {hex(size)}' + ) + assert expected_crc32 in output + old_size = size + +@pytest.mark.buildconfigspec('cmd_bdi') +@pytest.mark.buildconfigspec('cmd_sf') +@pytest.mark.buildconfigspec('cmd_memory') +def test_spi_write_twice(u_boot_console): + ''' Test to write data with random size twice for SPI ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + spi_write_twice( + u_boot_console, + get_page_size(), + get_erase_size(), + get_total_size(), + get_timeout() + ) + i = i + 1 + +def spi_write_continues(u_boot_console, page_size, erase_size, total_size, timeout): + ''' Write with random size of data to continue SPI write case ''' + spi_erase_block(u_boot_console, erase_size, total_size) + addr = u_boot_utils.find_ram_base(u_boot_console) + + output = u_boot_console.run_command(f'crc32 {hex(addr + 0x10000)} {hex(total_size)}') + m = re.search('==> (.+?)$', output) + if not m: + pytest.fail('CRC32 failed') + expected_crc32 = m.group(1) + + old_size = 0 + for size in ( + random.randint(4, page_size), + random.randint(page_size, total_size), + total_size, + ): + size = size & ~3 + size = size - old_size + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf write {hex(addr + 0x10000 + old_size)} {hex(old_size)} {hex(size)}' + ) + assert EXPECTED_WRITE in output + old_size += size + + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf read {hex(addr + 0x10000 + total_size)} 0 {hex(total_size)}' + ) + assert EXPECTED_READ in output + + output = u_boot_console.run_command( + f'crc32 {hex(addr + 0x10000 + total_size)} {hex(total_size)}' + ) + assert expected_crc32 in output + +@pytest.mark.buildconfigspec('cmd_bdi') +@pytest.mark.buildconfigspec('cmd_sf') +@pytest.mark.buildconfigspec('cmd_memory') +def test_spi_write_continues(u_boot_console): + ''' Test to write more random size data for SPI ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + spi_write_twice( + u_boot_console, + get_page_size(), + get_erase_size(), + get_total_size(), + get_timeout(), + ) + i = i + 1 + +def spi_read_twice(u_boot_console, page_size, total_size, timeout): + ''' Read the whole SPI flash twice, random_size till full flash size, + random till page size ''' + for size in random.randint(4, page_size), random.randint(4, total_size), total_size: + addr = u_boot_utils.find_ram_base(u_boot_console) + size = size & ~3 + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf read {hex(addr + total_size)} 0 {hex(size)}' + ) + assert EXPECTED_READ in output + output = u_boot_console.run_command(f'crc32 {hex(addr + total_size)} {hex(size)}') + m = re.search('==> (.+?)$', output) + if not m: + pytest.fail('CRC32 failed') + expected_crc32 = m.group(1) + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf read {hex(addr + total_size + 10)} 0 {hex(size)}' + ) + assert EXPECTED_READ in output + output = u_boot_console.run_command( + f'crc32 {hex(addr + total_size + 10)} {hex(size)}' + ) + assert expected_crc32 in output + +@pytest.mark.buildconfigspec('cmd_sf') +@pytest.mark.buildconfigspec('cmd_bdi') +@pytest.mark.buildconfigspec('cmd_memory') +def test_spi_read_twice(u_boot_console): + ''' Test to read random data twice from SPI ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + spi_read_twice(u_boot_console, get_page_size(), get_total_size(), get_timeout()) + i = i + 1 + +def spi_erase_all(u_boot_console, total_size, timeout): + ''' Erase the full chip SPI ''' + start = 0 + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command(f'sf erase {start} {hex(total_size)}') + assert EXPECTED_ERASE in output + +@pytest.mark.buildconfigspec('cmd_sf') +def test_spi_erase_all(u_boot_console): + ''' Test to check full chip erase for SPI ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + spi_erase_all(u_boot_console, get_total_size(), get_timeout()) + i = i + 1 + +def flash_ops( + u_boot_console, ops, start, size, offset=0, exp_ret=0, exp_str='', not_exp_str='' +): + ''' Flash operations: erase, write and read ''' + + f = u_boot_console.config.env.get('env__spi_device_test', None) + if not f: + timeout = 1000000 + + timeout = f.get('timeout', 1000000) + + if ops == 'erase': + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command(f'sf erase {hex(start)} {hex(size)}') + else: + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf {ops} {hex(offset)} {hex(start)} {hex(size)}' + ) + + if exp_str: + assert exp_str in output + if not_exp_str: + assert not_exp_str not in output + + ret_code = u_boot_console.run_command('echo $?') + if exp_ret >= 0: + assert ret_code.endswith(str(exp_ret)) + + return output, ret_code + +def spi_unlock_exit(u_boot_console, addr, size): + ''' Unlock the flash before making it fail ''' + u_boot_console.run_command(f'sf protect unlock {hex(addr)} {hex(size)}') + assert False, 'FAIL: Flash lock is unable to protect the data!' + +def find_prot_region(lock_addr, lock_size): + ''' Get the protected and un-protected region of flash ''' + total_size = get_total_size() + erase_size = get_erase_size() + + if lock_addr < (total_size // 2): + sect_num = (lock_addr + lock_size) // erase_size + x = 1 + while x < sect_num: + x *= 2 + prot_start = 0 + prot_size = x * erase_size + unprot_start = prot_start + prot_size + unprot_size = total_size - unprot_start + else: + sect_num = (total_size - lock_addr) // erase_size + x = 1 + while x < sect_num: + x *= 2 + prot_start = total_size - (x * erase_size) + prot_size = total_size - prot_start + unprot_start = 0 + unprot_size = prot_start + + return prot_start, prot_size, unprot_start, unprot_size + +def protect_ops(u_boot_console, lock_addr, lock_size, ops="unlock"): + ''' Run the command to lock or Unlock the flash ''' + u_boot_console.run_command(f'sf protect {ops} {hex(lock_addr)} {hex(lock_size)}') + output = u_boot_console.run_command('echo $?') + if ops == "lock" and not output.endswith('0'): + u_boot_console.run_command(f'sf protect unlock {hex(lock_addr)} {hex(lock_size)}') + assert False, "sf protect lock command exits with non-zero return code" + assert output.endswith('0') + +def erase_write_ops(u_boot_console, start, size): + ''' Basic erase and write operation for flash ''' + addr = u_boot_utils.find_ram_base(u_boot_console) + flash_ops(u_boot_console, 'erase', start, size, 0, 0, EXPECTED_ERASE) + flash_ops(u_boot_console, 'write', start, size, addr, 0, EXPECTED_WRITE) + +def spi_lock_unlock(u_boot_console, lock_addr, lock_size): + ''' Lock unlock operations for SPI family flash ''' + addr = u_boot_utils.find_ram_base(u_boot_console) + erase_size = get_erase_size() + + # Find the protected/un-protected region + prot_start, prot_size, unprot_start, unprot_size = find_prot_region(lock_addr, lock_size) + + # Check erase/write operation before locking + erase_write_ops(u_boot_console, prot_start, prot_size) + + # Locking the flash + protect_ops(u_boot_console, lock_addr, lock_size, 'lock') + + # Check erase/write operation after locking + output, ret_code = flash_ops(u_boot_console, 'erase', prot_start, prot_size, 0, -1) + if not any(error in output for error in EXPECTED_ERASE_ERRORS) or ret_code.endswith( + '0' + ): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + output, ret_code = flash_ops( + u_boot_console, 'write', prot_start, prot_size, addr, -1 + ) + if not any(error in output for error in EXPECTED_WRITE_ERRORS) or ret_code.endswith( + '0' + ): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + # Check locked sectors + sect_lock_start = random.randrange(prot_start, (prot_start + prot_size), erase_size) + if prot_size > erase_size: + sect_lock_size = random.randrange( + erase_size, (prot_start + prot_size - sect_lock_start), erase_size + ) + else: + sect_lock_size = erase_size + sect_write_size = random.randint(1, sect_lock_size) + + output, ret_code = flash_ops( + u_boot_console, 'erase', sect_lock_start, sect_lock_size, 0, -1 + ) + if not any(error in output for error in EXPECTED_ERASE_ERRORS) or ret_code.endswith( + '0' + ): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + output, ret_code = flash_ops( + u_boot_console, 'write', sect_lock_start, sect_write_size, addr, -1 + ) + if not any(error in output for error in EXPECTED_WRITE_ERRORS) or ret_code.endswith( + '0' + ): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + # Check unlocked sectors + if unprot_size != 0: + sect_unlock_start = random.randrange( + unprot_start, (unprot_start + unprot_size), erase_size + ) + if unprot_size > erase_size: + sect_unlock_size = random.randrange( + erase_size, (unprot_start + unprot_size - sect_unlock_start), erase_size + ) + else: + sect_unlock_size = erase_size + sect_write_size = random.randint(1, sect_unlock_size) + + output, ret_code = flash_ops( + u_boot_console, 'erase', sect_unlock_start, sect_unlock_size, 0, -1 + ) + if EXPECTED_ERASE not in output or ret_code.endswith('1'): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + output, ret_code = flash_ops( + u_boot_console, 'write', sect_unlock_start, sect_write_size, addr, -1 + ) + if EXPECTED_WRITE not in output or ret_code.endswith('1'): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + # Unlocking the flash + protect_ops(u_boot_console, lock_addr, lock_size, 'unlock') + + # Check erase/write operation after un-locking + erase_write_ops(u_boot_console, prot_start, prot_size) + + # Check previous locked sectors + sect_lock_start = random.randrange(prot_start, (prot_start + prot_size), erase_size) + if prot_size > erase_size: + sect_lock_size = random.randrange( + erase_size, (prot_start + prot_size - sect_lock_start), erase_size + ) + else: + sect_lock_size = erase_size + sect_write_size = random.randint(1, sect_lock_size) + + flash_ops( + u_boot_console, 'erase', sect_lock_start, sect_lock_size, 0, 0, EXPECTED_ERASE + ) + flash_ops( + u_boot_console, + 'write', + sect_lock_start, + sect_write_size, + addr, + 0, + EXPECTED_WRITE, + ) + +@pytest.mark.buildconfigspec('cmd_bdi') +@pytest.mark.buildconfigspec('cmd_sf') +@pytest.mark.buildconfigspec('cmd_memory') +def test_spi_lock_unlock(u_boot_console): + ''' Test to check the lock-unlock functionality for SPI family flash ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + flashes = u_boot_console.config.env.get('env__spi_lock_unlock', False) + if not flashes: + pytest.skip('No supported flash list for lock/unlock provided') + + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + total_size = get_total_size() + flash_part = get_flash_part() + + flashes_list = flashes.get('supported_flash', None).split(',') + flashes_list = [x.strip() for x in flashes_list] + if flash_part not in flashes_list: + pytest.skip('Detected flash does not support lock/unlock') + + # For lower half of memory + lock_addr = random.randint(0, (total_size // 2) - 1) + lock_size = random.randint(1, ((total_size // 2) - lock_addr)) + spi_lock_unlock(u_boot_console, lock_addr, lock_size) + + # For upper half of memory + lock_addr = random.randint((total_size // 2), total_size - 1) + lock_size = random.randint(1, (total_size - lock_addr)) + spi_lock_unlock(u_boot_console, lock_addr, lock_size) + + # For entire flash + lock_addr = random.randint(0, total_size - 1) + lock_size = random.randint(1, (total_size - lock_addr)) + spi_lock_unlock(u_boot_console, lock_addr, lock_size) + + i = i + 1 + +@pytest.mark.buildconfigspec('cmd_bdi') +@pytest.mark.buildconfigspec('cmd_sf') +@pytest.mark.buildconfigspec('cmd_memory') +def test_spi_negative(u_boot_console): + ''' Negative tests for SPI ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + total_size = get_total_size() + erase_size = get_erase_size() + page_size = get_page_size() + addr = u_boot_utils.find_ram_base(u_boot_console) + i = 0 + while i < loop: + # Erase negative test + start = random.randint(0, total_size) + esize = erase_size + + # If erasesize is not multiple of flash's erase size + while esize % erase_size == 0: + esize = random.randint(0, total_size - start) + + error_msg = 'Erased: ERROR' + flash_ops( + u_boot_console, 'erase', start, esize, 0, 1, error_msg, EXPECTED_ERASE + ) + + # If eraseoffset exceeds beyond flash size + eoffset = random.randint(total_size, (total_size + int(0x1000000))) + error_msg = 'Offset exceeds device limit' + flash_ops( + u_boot_console, 'erase', eoffset, esize, 0, 1, error_msg, EXPECTED_ERASE + ) + + # If erasesize exceeds beyond flash size + esize = random.randint((total_size - start), (total_size + int(0x1000000))) + error_msg = 'ERROR: attempting erase past flash size' + flash_ops( + u_boot_console, 'erase', start, esize, 0, 1, error_msg, EXPECTED_ERASE + ) + + # If erase size is 0 + esize = 0 + error_msg = 'ERROR: Invalid size 0' + flash_ops( + u_boot_console, 'erase', start, esize, 0, 1, error_msg, EXPECTED_ERASE + ) + + # If erasesize is less than flash's page size + esize = random.randint(0, page_size) + start = random.randint(0, (total_size - page_size)) + error_msg = 'Erased: ERROR' + flash_ops( + u_boot_console, 'erase', start, esize, 0, 1, error_msg, EXPECTED_ERASE + ) + + # Write/Read negative test + # if Write/Read size exceeds beyond flash size + offset = random.randint(0, total_size) + size = random.randint((total_size - offset), (total_size + int(0x1000000))) + error_msg = 'Size exceeds partition or device limit' + flash_ops( + u_boot_console, 'write', offset, size, addr, 1, error_msg, EXPECTED_WRITE + ) + flash_ops( + u_boot_console, 'read', offset, size, addr, 1, error_msg, EXPECTED_READ + ) + + # if Write/Read offset exceeds beyond flash size + offset = random.randint(total_size, (total_size + int(0x1000000))) + size = random.randint(0, total_size) + error_msg = 'Offset exceeds device limit' + flash_ops( + u_boot_console, 'write', offset, size, addr, 1, error_msg, EXPECTED_WRITE + ) + flash_ops( + u_boot_console, 'read', offset, size, addr, 1, error_msg, EXPECTED_READ + ) + + # if Write/Read size is 0 + offset = random.randint(0, 2) + size = 0 + error_msg = 'ERROR: Invalid size 0' + flash_ops( + u_boot_console, 'write', offset, size, addr, 1, error_msg, EXPECTED_WRITE + ) + flash_ops( + u_boot_console, 'read', offset, size, addr, 1, error_msg, EXPECTED_READ + ) + + i = i + 1 diff --git a/test/py/tests/test_tpm2.py b/test/py/tests/test_tpm2.py index 1d654cd4a23..75f5d31fc67 100644 --- a/test/py/tests/test_tpm2.py +++ b/test/py/tests/test_tpm2.py @@ -257,7 +257,7 @@ def test_tpm2_pcr_read(u_boot_console): updates = int(re.findall(r'\d+', str)[0]) # Check the output value - assert 'PCR #10 content' in read_pcr + assert 'PCR #10 sha256 32 byte 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') diff --git a/test/py/tests/test_trace.py b/test/py/tests/test_trace.py index 7c5696ce747..ec1e624722c 100644 --- a/test/py/tests/test_trace.py +++ b/test/py/tests/test_trace.py @@ -12,7 +12,7 @@ import u_boot_utils as util TMPDIR = '/tmp/test_trace' # Decode a function-graph line -RE_LINE = re.compile(r'.*0\.\.\.\.\. \s*([0-9.]*): func.*[|](\s*)(\S.*)?([{};])$') +RE_LINE = re.compile(r'.*0\.\.\.\.\.? \s*([0-9.]*): func.*[|](\s*)(\S.*)?([{};])$') def collect_trace(cons): @@ -175,7 +175,7 @@ def check_funcgraph(cons, fname, proftool, map_fname, trace_dat): # Then look for this: # u-boot-1 0..... 282.101375: funcgraph_exit: 0.006 us | } # Then check for this: - # u-boot-1 0..... 282.101375: funcgraph_entry: 0.000 us | initcall_is_event(); + # u-boot-1 0..... 282.101375: funcgraph_entry: 0.000 us | calc_reloc_ofs(); expected_indent = None found_start = False @@ -199,7 +199,7 @@ def check_funcgraph(cons, fname, proftool, map_fname, trace_dat): # The next function after initf_bootstage() exits should be # initcall_is_event() - assert upto == 'initcall_is_event()' + assert upto == 'calc_reloc_ofs()' # Now look for initf_dm() and dm_timer_init() so we can check the bootstage # time diff --git a/test/py/tests/test_upl.py b/test/py/tests/test_upl.py new file mode 100644 index 00000000000..3164bda6b71 --- /dev/null +++ b/test/py/tests/test_upl.py @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2024 Google LLC +# +# Test addition of Universal Payload + +import os + +import pytest +import u_boot_utils + +@pytest.mark.boardspec('sandbox_vpl') +def test_upl_handoff(u_boot_console): + """Test of UPL handoff + + This works by starting up U-Boot VPL, which gets to SPL and then sets up a + UPL handoff using the FIT containing U-Boot proper. It then jumps to U-Boot + proper and runs a test to check that the parameters are correct. + + The entire FIT is loaded into memory in SPL (in upl_load_from_image()) so + that it can be inpected in upl_test_info_norun + """ + cons = u_boot_console + ram = os.path.join(cons.config.build_dir, 'ram.bin') + fdt = os.path.join(cons.config.build_dir, 'u-boot.dtb') + + # Remove any existing RAM file, so we don't have old data present + if os.path.exists(ram): + os.remove(ram) + flags = ['-m', ram, '-d', fdt, '--upl'] + cons.restart_uboot_with_flags(flags, use_dtb=False) + + # Make sure that Universal Payload is detected in U-Boot proper + output = cons.run_command('upl info') + assert 'UPL state: active' == output + + # Check the FIT offsets look correct + output = cons.run_command('ut upl -f upl_test_info_norun') + assert 'Failures: 0' in output diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index c169c835e38..05e15830590 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -11,6 +11,7 @@ import pytest import u_boot_utils # pylint: disable=E0611 from tests import fs_helper +from test_android import test_abootimg def mkdir_cond(dirname): """Create a directory if it doesn't already exist @@ -423,6 +424,83 @@ def setup_cros_image(cons): return fname +def setup_android_image(cons): + """Create a 20MB disk image with Android partitions""" + Partition = collections.namedtuple('part', 'start,size,name') + parts = {} + disk_data = None + + def set_part_data(partnum, data): + """Set the contents of a disk partition + + This updates disk_data by putting data in the right place + + Args: + partnum (int): Partition number to set + data (bytes): Data for that partition + """ + nonlocal disk_data + + start = parts[partnum].start * sect_size + disk_data = disk_data[:start] + data + disk_data[start + len(data):] + + mmc_dev = 7 + fname = os.path.join(cons.config.source_dir, f'mmc{mmc_dev}.img') + u_boot_utils.run_and_log(cons, 'qemu-img create %s 20M' % fname) + u_boot_utils.run_and_log(cons, f'cgpt create {fname}') + + ptr = 40 + + # Number of sectors in 1MB + sect_size = 512 + sect_1mb = (1 << 20) // sect_size + + required_parts = [ + {'num': 1, 'label':'misc', 'size': '1M'}, + {'num': 2, 'label':'boot_a', 'size': '4M'}, + {'num': 3, 'label':'boot_b', 'size': '4M'}, + {'num': 4, 'label':'vendor_boot_a', 'size': '4M'}, + {'num': 5, 'label':'vendor_boot_b', 'size': '4M'}, + ] + + for part in required_parts: + size_str = part['size'] + if 'M' in size_str: + size = int(size_str[:-1]) * sect_1mb + else: + size = int(size_str) + u_boot_utils.run_and_log( + cons, + f"cgpt add -i {part['num']} -b {ptr} -s {size} -l {part['label']} -t basicdata {fname}") + ptr += size + + u_boot_utils.run_and_log(cons, f'cgpt boot -p {fname}') + out = u_boot_utils.run_and_log(cons, f'cgpt show -q {fname}') + + # Create a dict (indexed by partition number) containing the above info + for line in out.splitlines(): + start, size, num, name = line.split(maxsplit=3) + parts[int(num)] = Partition(int(start), int(size), name) + + with open(fname, 'rb') as inf: + disk_data = inf.read() + + test_abootimg.AbootimgTestDiskImage(cons, 'bootv4.img', test_abootimg.boot_img_hex) + boot_img = os.path.join(cons.config.result_dir, 'bootv4.img') + with open(boot_img, 'rb') as inf: + set_part_data(2, inf.read()) + + test_abootimg.AbootimgTestDiskImage(cons, 'vendor_boot.img', test_abootimg.vboot_img_hex) + vendor_boot_img = os.path.join(cons.config.result_dir, 'vendor_boot.img') + with open(vendor_boot_img, 'rb') as inf: + set_part_data(4, inf.read()) + + with open(fname, 'wb') as outf: + outf.write(disk_data) + + print('wrote to {}'.format(fname)) + + return fname def setup_cedit_file(cons): infname = os.path.join(cons.config.source_dir, @@ -470,6 +548,7 @@ def test_ut_dm_init(u_boot_console): fh.write(data) @pytest.mark.buildconfigspec('cmd_bootflow') +@pytest.mark.buildconfigspec('sandbox') def test_ut_dm_init_bootstd(u_boot_console): """Initialise data for bootflow tests""" @@ -477,6 +556,7 @@ def test_ut_dm_init_bootstd(u_boot_console): setup_bootmenu_image(u_boot_console) setup_cedit_file(u_boot_console) setup_cros_image(u_boot_console) + setup_android_image(u_boot_console) # Restart so that the new mmc1.img is picked up u_boot_console.restart_uboot() |