summaryrefslogtreecommitdiff
path: root/test/py
diff options
context:
space:
mode:
Diffstat (limited to 'test/py')
-rw-r--r--test/py/conftest.py60
-rw-r--r--test/py/tests/test_android/test_ab.py31
-rw-r--r--test/py/tests/test_bootstage.py9
-rw-r--r--test/py/tests/test_efi_capsule/test_capsule_firmware_fit.py2
-rw-r--r--test/py/tests/test_efi_capsule/test_capsule_firmware_raw.py8
-rw-r--r--test/py/tests/test_efi_capsule/test_capsule_firmware_signed_fit.py2
-rw-r--r--test/py/tests/test_efi_capsule/test_capsule_firmware_signed_raw.py4
-rw-r--r--test/py/tests/test_efi_capsule/version.dtso6
-rw-r--r--test/py/tests/test_efi_fit.py2
-rw-r--r--test/py/tests/test_efi_loader.py58
-rw-r--r--test/py/tests/test_efi_selftest.py2
-rw-r--r--test/py/tests/test_net_boot.py2
-rw-r--r--test/py/tests/test_sleep.py4
-rw-r--r--test/py/tests/test_spi.py696
-rw-r--r--test/py/tests/test_upl.py38
-rw-r--r--test/py/tests/test_ut.py94
-rw-r--r--test/py/u_boot_console_base.py39
-rw-r--r--test/py/u_boot_spawn.py88
18 files changed, 1015 insertions, 130 deletions
diff --git a/test/py/conftest.py b/test/py/conftest.py
index fc9dd3a83f8..46a410cf268 100644
--- a/test/py/conftest.py
+++ b/test/py/conftest.py
@@ -24,6 +24,7 @@ import pytest
import re
from _pytest.runner import runtestprotocol
import sys
+from u_boot_spawn import BootFail, Timeout, Unexpected, handle_exception
# Globals: The HTML log file, and the connection to the U-Boot console.
log = None
@@ -115,14 +116,36 @@ def run_build(config, source_dir, build_dir, board_type, log):
runner.close()
log.status_pass('OK')
-def pytest_xdist_setupnodes(config, specs):
- """Clear out any 'done' file from a previous build"""
- global build_done_file
- build_dir = config.getoption('build_dir')
+def get_details(config):
+ """Obtain salient details about the board and directories to use
+
+ Args:
+ config (pytest.Config): pytest configuration
+
+ Returns:
+ tuple:
+ str: Board type (U-Boot build name)
+ str: Identity for the lab board
+ str: Build directory
+ str: Source directory
+ """
board_type = config.getoption('board_type')
+ board_identity = config.getoption('board_identity')
+ build_dir = config.getoption('build_dir')
+
source_dir = os.path.dirname(os.path.dirname(TEST_PY_DIR))
+ default_build_dir = source_dir + '/build-' + board_type
if not build_dir:
- build_dir = source_dir + '/build-' + board_type
+ build_dir = default_build_dir
+
+ return board_type, board_identity, build_dir, source_dir
+
+def pytest_xdist_setupnodes(config, specs):
+ """Clear out any 'done' file from a previous build"""
+ global build_done_file
+
+ build_dir = get_details(config)[2]
+
build_done_file = Path(build_dir) / 'build.done'
if build_done_file.exists():
os.remove(build_done_file)
@@ -161,17 +184,10 @@ def pytest_configure(config):
global console
global ubconfig
- source_dir = os.path.dirname(os.path.dirname(TEST_PY_DIR))
+ board_type, board_identity, build_dir, source_dir = get_details(config)
- 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')
@@ -239,6 +255,7 @@ def pytest_configure(config):
ubconfig.board_identity = board_identity
ubconfig.gdbserver = gdbserver
ubconfig.dtb = build_dir + '/arch/sandbox/dts/test.dtb'
+ ubconfig.connection_ok = True
env_vars = (
'board_type',
@@ -405,8 +422,21 @@ def u_boot_console(request):
Returns:
The fixture value.
"""
-
- console.ensure_spawned()
+ if not ubconfig.connection_ok:
+ pytest.skip('Cannot get target connection')
+ return None
+ try:
+ console.ensure_spawned()
+ except OSError as err:
+ handle_exception(ubconfig, console, log, err, 'Lab failure', True)
+ except Timeout as err:
+ handle_exception(ubconfig, console, log, err, 'Lab timeout', True)
+ except BootFail as err:
+ handle_exception(ubconfig, console, log, err, 'Boot fail', True,
+ console.get_spawn_output())
+ except Unexpected:
+ handle_exception(ubconfig, console, log, err, 'Unexpected test output',
+ False)
return console
anchors = {}
diff --git a/test/py/tests/test_android/test_ab.py b/test/py/tests/test_android/test_ab.py
index c79cb07fda3..9bf1a0eb00a 100644
--- a/test/py/tests/test_android/test_ab.py
+++ b/test/py/tests/test_android/test_ab.py
@@ -54,22 +54,45 @@ def ab_disk_image(u_boot_console):
di = ABTestDiskImage(u_boot_console)
return di
+def ab_dump(u_boot_console, slot_num, crc):
+ output = u_boot_console.run_command('bcb ab_dump host 0#misc')
+ header, slot0, slot1 = output.split('\r\r\n\r\r\n')
+ slots = [slot0, slot1]
+ slot_suffixes = ['_a', '_b']
+
+ header = dict(map(lambda x: map(str.strip, x.split(':')), header.split('\r\r\n')))
+ assert header['Bootloader Control'] == '[misc]'
+ assert header['Active Slot'] == slot_suffixes[slot_num]
+ assert header['Magic Number'] == '0x42414342'
+ assert header['Version'] == '1'
+ assert header['Number of Slots'] == '2'
+ assert header['Recovery Tries Remaining'] == '0'
+ assert header['CRC'] == '{} (Valid)'.format(crc)
+
+ slot = dict(map(lambda x: map(str.strip, x.split(':')), slots[slot_num].split('\r\r\n\t- ')[1:]))
+ assert slot['Priority'] == '15'
+ assert slot['Tries Remaining'] == '6'
+ assert slot['Successful Boot'] == '0'
+ assert slot['Verity Corrupted'] == '0'
+
@pytest.mark.boardspec('sandbox')
@pytest.mark.buildconfigspec('android_ab')
-@pytest.mark.buildconfigspec('cmd_ab_select')
+@pytest.mark.buildconfigspec('cmd_bcb')
@pytest.mark.requiredtool('sgdisk')
def test_ab(ab_disk_image, u_boot_console):
- """Test the 'ab_select' command."""
+ """Test the 'bcb 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')
+ output = u_boot_console.run_command('bcb 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
+ ab_dump(u_boot_console, 0, '0xd438d1b9')
- output = u_boot_console.run_command('ab_select slot_name host 0:1')
+ output = u_boot_console.run_command('bcb 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
+ ab_dump(u_boot_console, 1, '0x011ec016')
diff --git a/test/py/tests/test_bootstage.py b/test/py/tests/test_bootstage.py
index a9eb9f0b4a1..bd71a1af3a2 100644
--- a/test/py/tests/test_bootstage.py
+++ b/test/py/tests/test_bootstage.py
@@ -33,7 +33,7 @@ def test_bootstage_report(u_boot_console):
@pytest.mark.buildconfigspec('bootstage')
@pytest.mark.buildconfigspec('cmd_bootstage')
@pytest.mark.buildconfigspec('bootstage_stash')
-def test_bootstage_stash(u_boot_console):
+def test_bootstage_stash_and_unstash(u_boot_console):
f = u_boot_console.config.env.get('env__bootstage_cmd_file', None)
if not f:
pytest.skip('No bootstage environment file is defined')
@@ -55,13 +55,8 @@ def test_bootstage_stash(u_boot_console):
# Check expected string in last column of output
output_last_col = ''.join([i.split()[-1] for i in output.split('\n')])
assert expected_text in output_last_col
- return addr, size
-@pytest.mark.buildconfigspec('bootstage')
-@pytest.mark.buildconfigspec('cmd_bootstage')
-@pytest.mark.buildconfigspec('bootstage_stash')
-def test_bootstage_unstash(u_boot_console):
- addr, size = test_bootstage_stash(u_boot_console)
+ # Check that unstash works as expected
u_boot_console.run_command('bootstage unstash %x %x' % (addr, size))
output = u_boot_console.run_command('echo $?')
assert output.endswith('0')
diff --git a/test/py/tests/test_efi_capsule/test_capsule_firmware_fit.py b/test/py/tests/test_efi_capsule/test_capsule_firmware_fit.py
index 11bcdc2bb29..a726c71c113 100644
--- a/test/py/tests/test_efi_capsule/test_capsule_firmware_fit.py
+++ b/test/py/tests/test_efi_capsule/test_capsule_firmware_fit.py
@@ -147,7 +147,7 @@ class TestEfiCapsuleFirmwareFit():
verify_content(u_boot_console, '150000', 'u-boot-env:Old')
else:
# ensure that SANDBOX_UBOOT_IMAGE_GUID is in the ESRT.
- assert '3673B45D-6A7C-46F3-9E60-ADABB03F7937' in ''.join(output)
+ assert '985F2937-7C2E-5E9A-8A5E-8E063312964B' in ''.join(output)
assert 'ESRT: fw_version=5' in ''.join(output)
assert 'ESRT: lowest_supported_fw_version=3' in ''.join(output)
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 f3a2dff5c2c..8a790405c7c 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
@@ -145,10 +145,10 @@ class TestEfiCapsuleFirmwareRaw:
'efidebug capsule esrt'])
# ensure that SANDBOX_UBOOT_ENV_IMAGE_GUID is in the ESRT.
- assert '5A7021F5-FEF2-48B4-AABA-832E777418C0' in ''.join(output)
+ assert '9E339473-C2EB-530A-A69B-0CD6BBBED40E' in ''.join(output)
# ensure that SANDBOX_UBOOT_IMAGE_GUID is in the ESRT.
- assert '09D7CF52-0720-4710-91D1-08469B7FE9C8' in ''.join(output)
+ assert '985F2937-7C2E-5E9A-8A5E-8E063312964B' in ''.join(output)
check_file_removed(u_boot_console, disk_img, capsule_files)
@@ -199,12 +199,12 @@ class TestEfiCapsuleFirmwareRaw:
verify_content(u_boot_console, '150000', 'u-boot-env:Old')
else:
# ensure that SANDBOX_UBOOT_IMAGE_GUID is in the ESRT.
- assert '09D7CF52-0720-4710-91D1-08469B7FE9C8' in ''.join(output)
+ assert '985F2937-7C2E-5E9A-8A5E-8E063312964B' in ''.join(output)
assert 'ESRT: fw_version=5' in ''.join(output)
assert 'ESRT: lowest_supported_fw_version=3' in ''.join(output)
# ensure that SANDBOX_UBOOT_ENV_IMAGE_GUID is in the ESRT.
- assert '5A7021F5-FEF2-48B4-AABA-832E777418C0' in ''.join(output)
+ assert '9E339473-C2EB-530A-A69B-0CD6BBBED40E' in ''.join(output)
assert 'ESRT: fw_version=10' in ''.join(output)
assert 'ESRT: lowest_supported_fw_version=7' in ''.join(output)
diff --git a/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_fit.py b/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_fit.py
index 44a58baa310..debbce8bdbd 100644
--- a/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_fit.py
+++ b/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_fit.py
@@ -157,7 +157,7 @@ class TestEfiCapsuleFirmwareSignedFit():
'efidebug capsule esrt'])
# ensure that SANDBOX_UBOOT_IMAGE_GUID is in the ESRT.
- assert '3673B45D-6A7C-46F3-9E60-ADABB03F7937' in ''.join(output)
+ assert '46610520-469E-59DC-A8DD-C11832B877EA' in ''.join(output)
assert 'ESRT: fw_version=5' in ''.join(output)
assert 'ESRT: lowest_supported_fw_version=3' in ''.join(output)
diff --git a/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_raw.py b/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_raw.py
index 83a10e160b8..439bd71b3a7 100644
--- a/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_raw.py
+++ b/test/py/tests/test_efi_capsule/test_capsule_firmware_signed_raw.py
@@ -151,12 +151,12 @@ class TestEfiCapsuleFirmwareSignedRaw():
'efidebug capsule esrt'])
# ensure that SANDBOX_UBOOT_IMAGE_GUID is in the ESRT.
- assert '09D7CF52-0720-4710-91D1-08469B7FE9C8' in ''.join(output)
+ assert '985F2937-7C2E-5E9A-8A5E-8E063312964B' in ''.join(output)
assert 'ESRT: fw_version=5' in ''.join(output)
assert 'ESRT: lowest_supported_fw_version=3' in ''.join(output)
# ensure that SANDBOX_UBOOT_ENV_IMAGE_GUID is in the ESRT.
- assert '5A7021F5-FEF2-48B4-AABA-832E777418C0' in ''.join(output)
+ assert '9E339473-C2EB-530A-A69B-0CD6BBBED40E' in ''.join(output)
assert 'ESRT: fw_version=10' in ''.join(output)
assert 'ESRT: lowest_supported_fw_version=7' in ''.join(output)
diff --git a/test/py/tests/test_efi_capsule/version.dtso b/test/py/tests/test_efi_capsule/version.dtso
index 07850cc6064..3aebb5b64fb 100644
--- a/test/py/tests/test_efi_capsule/version.dtso
+++ b/test/py/tests/test_efi_capsule/version.dtso
@@ -8,17 +8,17 @@
image1 {
lowest-supported-version = <3>;
image-index = <1>;
- image-type-id = "09D7CF52-0720-4710-91D1-08469B7FE9C8";
+ image-type-id = "985F2937-7C2E-5E9A-8A5E-8E063312964B";
};
image2 {
lowest-supported-version = <7>;
image-index = <2>;
- image-type-id = "5A7021F5-FEF2-48B4-AABA-832E777418C0";
+ image-type-id = "9E339473-C2EB-530A-A69B-0CD6BBBED40E";
};
image3 {
lowest-supported-version = <3>;
image-index = <1>;
- image-type-id = "3673B45D-6A7C-46F3-9E60-ADABB03F7937";
+ image-type-id = "46610520-469E-59DC-A8DD-C11832B877EA";
};
};
};
diff --git a/test/py/tests/test_efi_fit.py b/test/py/tests/test_efi_fit.py
index 0ad483500f8..550058a30fd 100644
--- a/test/py/tests/test_efi_fit.py
+++ b/test/py/tests/test_efi_fit.py
@@ -119,7 +119,7 @@ FDT_DATA = '''
'''
@pytest.mark.buildconfigspec('bootm_efi')
-@pytest.mark.buildconfigspec('cmd_bootefi_hello_compile')
+@pytest.mark.buildconfigspec('BOOTEFI_HELLO_COMPILE')
@pytest.mark.buildconfigspec('fit')
@pytest.mark.notbuildconfigspec('generate_acpi_table')
@pytest.mark.requiredtool('dtc')
diff --git a/test/py/tests/test_efi_loader.py b/test/py/tests/test_efi_loader.py
index 85473a9049b..707b2c9e795 100644
--- a/test/py/tests/test_efi_loader.py
+++ b/test/py/tests/test_efi_loader.py
@@ -45,11 +45,18 @@ env__efi_loader_helloworld_file = {
'crc32': 'c2244b26', # CRC32 check sum
'addr': 0x40400000, # load address
}
+
+# False if the helloworld EFI over HTTP boot test should be performed.
+# If HTTP boot testing is not possible or desired, set this variable to True or
+# ommit it.
+env__efi_helloworld_net_http_test_skip = True
"""
import pytest
import u_boot_utils
+PROTO_TFTP, PROTO_HTTP = range(0, 2)
+
net_set_up = False
def test_efi_pre_commands(u_boot_console):
@@ -110,10 +117,10 @@ def test_efi_setup_static(u_boot_console):
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
+def fetch_file(u_boot_console, env_conf, proto):
+ """Grab an env described file via TFTP or HTTP and return its address
- A file as described by an env config <env_conf> is downloaded from the TFTP
+ A file as described by an env config <env_conf> is downloaded from the
server. The address to that file is returned.
"""
if not net_set_up:
@@ -128,7 +135,13 @@ def fetch_tftp_file(u_boot_console, env_conf):
addr = u_boot_utils.find_ram_base(u_boot_console)
fn = f['fn']
- output = u_boot_console.run_command('tftpboot %x %s' % (addr, fn))
+ if proto == PROTO_TFTP:
+ cmd = 'tftpboot'
+ elif proto == PROTO_HTTP:
+ cmd = 'wget'
+ else:
+ assert False
+ output = u_boot_console.run_command('%s %x %s' % (cmd, addr, fn))
expected_text = 'Bytes transferred = '
sz = f.get('size', None)
if sz:
@@ -147,22 +160,40 @@ def fetch_tftp_file(u_boot_console, env_conf):
return addr
+def do_test_efi_helloworld_net(u_boot_console, proto):
+ addr = fetch_file(u_boot_console, 'env__efi_loader_helloworld_file', proto)
+
+ 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('of_control')
-@pytest.mark.buildconfigspec('cmd_bootefi_hello_compile')
-def test_efi_helloworld_net(u_boot_console):
+@pytest.mark.buildconfigspec('bootefi_hello_compile')
+@pytest.mark.buildconfigspec('cmd_tftpboot')
+def test_efi_helloworld_net_tftp(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')
+ do_test_efi_helloworld_net(u_boot_console, PROTO_TFTP);
- 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('of_control')
+@pytest.mark.buildconfigspec('cmd_bootefi_hello_compile')
+@pytest.mark.buildconfigspec('cmd_wget')
+def test_efi_helloworld_net_http(u_boot_console):
+ """Run the helloworld.efi binary via HTTP.
+
+ The helloworld.efi file is downloaded from the HTTP server and is executed
+ using the fallback device tree at $fdtcontroladdr.
+ """
+ if u_boot_console.config.env.get('env__efi_helloworld_net_http_test_skip', True):
+ pytest.skip('helloworld.efi HTTP test is not enabled!')
+
+ do_test_efi_helloworld_net(u_boot_console, PROTO_HTTP);
@pytest.mark.buildconfigspec('cmd_bootefi_hello')
def test_efi_helloworld_builtin(u_boot_console):
@@ -178,6 +209,7 @@ def test_efi_helloworld_builtin(u_boot_console):
@pytest.mark.buildconfigspec('of_control')
@pytest.mark.buildconfigspec('cmd_bootefi')
+@pytest.mark.buildconfigspec('cmd_tftpboot')
def test_efi_grub_net(u_boot_console):
"""Run the grub.efi binary via TFTP.
@@ -185,7 +217,7 @@ def test_efi_grub_net(u_boot_console):
executed.
"""
- addr = fetch_tftp_file(u_boot_console, 'env__efi_loader_grub_file')
+ addr = fetch_file(u_boot_console, 'env__efi_loader_grub_file', PROTO_TFTP)
u_boot_console.run_command('bootefi %x' % addr, wait_for_prompt=False)
diff --git a/test/py/tests/test_efi_selftest.py b/test/py/tests/test_efi_selftest.py
index 43f24245582..310d8ed294a 100644
--- a/test/py/tests/test_efi_selftest.py
+++ b/test/py/tests/test_efi_selftest.py
@@ -58,7 +58,7 @@ def test_efi_selftest_watchdog_reboot(u_boot_console):
u_boot_console.run_command(cmd='bootefi selftest', wait_for_prompt=False)
if u_boot_console.p.expect(['resetting', 'U-Boot']):
raise Exception('Reset failed in \'watchdog reboot\' test')
- u_boot_console.restart_uboot()
+ u_boot_console.run_command(cmd='', send_nl=False, wait_for_reboot=True)
@pytest.mark.buildconfigspec('cmd_bootefi_selftest')
def test_efi_selftest_text_input(u_boot_console):
diff --git a/test/py/tests/test_net_boot.py b/test/py/tests/test_net_boot.py
index 63309fe82e1..d7d74356928 100644
--- a/test/py/tests/test_net_boot.py
+++ b/test/py/tests/test_net_boot.py
@@ -75,7 +75,7 @@ env__net_pxe_bootable_file = {
'check_pattern': 'ERROR',
}
-# False or omitted if a PXE boot test should be tested.
+# False 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
diff --git a/test/py/tests/test_sleep.py b/test/py/tests/test_sleep.py
index 66a57434bff..8965fc3fea9 100644
--- a/test/py/tests/test_sleep.py
+++ b/test/py/tests/test_sleep.py
@@ -27,7 +27,7 @@ def test_sleep(u_boot_console):
if not sleep_skip:
pytest.skip('sleep is not accurate')
- if u_boot_console.config.buildconfig.get('config_cmd_misc', 'n') != 'y':
+ if u_boot_console.config.buildconfig.get('config_cmd_sleep', 'n') != 'y':
pytest.skip('sleep command not supported')
# 3s isn't too long, but is enough to cross a few second boundaries.
@@ -42,7 +42,7 @@ def test_sleep(u_boot_console):
# margin is hopefully enough to account for any system overhead.
assert elapsed < (sleep_time + sleep_margin)
-@pytest.mark.buildconfigspec("cmd_misc")
+@pytest.mark.buildconfigspec("cmd_time")
def test_time(u_boot_console):
"""Test the time command, and validate that it gives approximately the
correct amount of command execution time."""
diff --git a/test/py/tests/test_spi.py b/test/py/tests/test_spi.py
new file mode 100644
index 00000000000..3160d58540f
--- /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 = None
+ 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 = None
+ 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_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 05e15830590..39aa1035e34 100644
--- a/test/py/tests/test_ut.py
+++ b/test/py/tests/test_ut.py
@@ -1,6 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
-# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+"""
+Unit-test runner
+
+Provides a test_ut() function which is used by conftest.py to run each unit
+test one at a time, as well setting up some files needed by the tests.
+# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+"""
import collections
import getpass
import gzip
@@ -44,8 +50,8 @@ def setup_image(cons, mmc_dev, part_type, second_part=False):
if second_part:
spec += '\ntype=c'
- u_boot_utils.run_and_log(cons, 'qemu-img create %s 20M' % fname)
- u_boot_utils.run_and_log(cons, 'sudo sfdisk %s' % fname,
+ u_boot_utils.run_and_log(cons, f'qemu-img create {fname} 20M')
+ u_boot_utils.run_and_log(cons, f'sudo sfdisk {fname}',
stdin=spec.encode('utf-8'))
return fname, mnt
@@ -61,13 +67,13 @@ def mount_image(cons, fname, mnt, fstype):
Returns:
str: Name of loop device used
"""
- out = u_boot_utils.run_and_log(cons, 'sudo losetup --show -f -P %s' % fname)
+ out = u_boot_utils.run_and_log(cons, f'sudo losetup --show -f -P {fname}')
loop = out.strip()
part = f'{loop}p1'
u_boot_utils.run_and_log(cons, f'sudo mkfs.{fstype} {part}')
opts = ''
if fstype == 'vfat':
- opts += f' -o uid={os.getuid()},gid={os.getgid()}'
+ opts += f' -o uid={os.getuid()},gid={os.getgid()}'
u_boot_utils.run_and_log(cons, f'sudo mount -o loop {part} {mnt}{opts}')
u_boot_utils.run_and_log(cons, f'sudo chown {getpass.getuser()} {mnt}')
return loop
@@ -82,9 +88,7 @@ def copy_prepared_image(cons, mmc_dev, fname):
"""
infname = os.path.join(cons.config.source_dir,
f'test/py/tests/bootstd/mmc{mmc_dev}.img.xz')
- u_boot_utils.run_and_log(
- cons,
- ['sh', '-c', 'xz -dc %s >%s' % (infname, fname)])
+ u_boot_utils.run_and_log(cons, ['sh', '-c', f'xz -dc {infname} >{fname}'])
def setup_bootmenu_image(cons):
"""Create a 20MB disk image with a single ext4 partition
@@ -101,9 +105,6 @@ def setup_bootmenu_image(cons):
loop = mount_image(cons, fname, mnt, 'ext4')
mounted = True
- vmlinux = 'Image'
- initrd = 'uInitrd'
- dtbdir = 'dtb'
script = '''# DO NOT EDIT THIS FILE
#
# Please edit /boot/armbianEnv.txt to set supported parameters
@@ -177,12 +178,12 @@ booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}
# Recompile with:
# mkimage -C none -A arm -T script -d /boot/boot.cmd /boot/boot.scr
-''' % (mmc_dev)
+'''
bootdir = os.path.join(mnt, 'boot')
mkdir_cond(bootdir)
cmd_fname = os.path.join(bootdir, 'boot.cmd')
scr_fname = os.path.join(bootdir, 'boot.scr')
- with open(cmd_fname, 'w') as outf:
+ with open(cmd_fname, 'w', encoding='ascii') as outf:
print(script, file=outf)
infname = os.path.join(cons.config.source_dir,
@@ -212,13 +213,12 @@ booti ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}
complete = True
except ValueError as exc:
- print('Falled to create image, failing back to prepared copy: %s',
- str(exc))
+ print(f'Falled to create image, failing back to prepared copy: {exc}')
finally:
if mounted:
- u_boot_utils.run_and_log(cons, 'sudo umount --lazy %s' % mnt)
+ u_boot_utils.run_and_log(cons, f'sudo umount --lazy {mnt}')
if loop:
- u_boot_utils.run_and_log(cons, 'sudo losetup -d %s' % loop)
+ u_boot_utils.run_and_log(cons, f'sudo losetup -d {loop}')
if not complete:
copy_prepared_image(cons, mmc_dev, fname)
@@ -254,32 +254,32 @@ label Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
ext = os.path.join(mnt, 'extlinux')
mkdir_cond(ext)
- with open(os.path.join(ext, 'extlinux.conf'), 'w') as fd:
+ conf = os.path.join(ext, 'extlinux.conf')
+ with open(conf, 'w', encoding='ascii') as fd:
print(script, file=fd)
inf = os.path.join(cons.config.persistent_data_dir, 'inf')
with open(inf, 'wb') as fd:
fd.write(gzip.compress(b'vmlinux'))
- u_boot_utils.run_and_log(cons, 'mkimage -f auto -d %s %s' %
- (inf, os.path.join(mnt, vmlinux)))
+ u_boot_utils.run_and_log(
+ cons, f'mkimage -f auto -d {inf} {os.path.join(mnt, vmlinux)}')
- with open(os.path.join(mnt, initrd), 'w') as fd:
+ with open(os.path.join(mnt, initrd), 'w', encoding='ascii') as fd:
print('initrd', file=fd)
mkdir_cond(os.path.join(mnt, dtbdir))
- dtb_file = os.path.join(mnt, '%s/sandbox.dtb' % dtbdir)
+ dtb_file = os.path.join(mnt, f'{dtbdir}/sandbox.dtb')
u_boot_utils.run_and_log(
- cons, 'dtc -o %s' % dtb_file, stdin=b'/dts-v1/; / {};')
+ cons, f'dtc -o {dtb_file}', stdin=b'/dts-v1/; / {};')
complete = True
except ValueError as exc:
- print('Falled to create image, failing back to prepared copy: %s',
- str(exc))
+ print(f'Falled to create image, failing back to prepared copy: {exc}')
finally:
if mounted:
- u_boot_utils.run_and_log(cons, 'sudo umount --lazy %s' % mnt)
+ u_boot_utils.run_and_log(cons, f'sudo umount --lazy {mnt}')
if loop:
- u_boot_utils.run_and_log(cons, 'sudo losetup -d %s' % loop)
+ u_boot_utils.run_and_log(cons, f'sudo losetup -d {loop}')
if not complete:
copy_prepared_image(cons, mmc_dev, fname)
@@ -303,7 +303,8 @@ def setup_cros_image(cons):
Return:
bytes: Packed-kernel data
"""
- kern_part = os.path.join(cons.config.result_dir, 'kern-part-{arch}.bin')
+ kern_part = os.path.join(cons.config.result_dir,
+ f'kern-part-{arch}.bin')
u_boot_utils.run_and_log(
cons,
f'futility vbutil_kernel --pack {kern_part} '
@@ -332,7 +333,7 @@ def setup_cros_image(cons):
mmc_dev = 5
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'qemu-img create {fname} 20M')
#mnt = os.path.join(cons.config.persistent_data_dir, 'mnt')
#mkdir_cond(mnt)
u_boot_utils.run_and_log(cons, f'cgpt create {fname}')
@@ -381,20 +382,20 @@ def setup_cros_image(cons):
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}')
- '''We expect something like this:
- 8239 2048 1 Basic data
- 45 2048 2 ChromeOS kernel
- 8238 1 3 ChromeOS rootfs
- 2093 2048 4 ChromeOS kernel
- 8237 1 5 ChromeOS rootfs
- 41 1 6 ChromeOS kernel
- 42 1 7 ChromeOS rootfs
- 4141 2048 8 Basic data
- 43 1 9 ChromeOS reserved
- 44 1 10 ChromeOS reserved
- 40 1 11 ChromeOS firmware
- 6189 2048 12 EFI System Partition
- '''
+
+ # We expect something like this:
+ # 8239 2048 1 Basic data
+ # 45 2048 2 ChromeOS kernel
+ # 8238 1 3 ChromeOS rootfs
+ # 2093 2048 4 ChromeOS kernel
+ # 8237 1 5 ChromeOS rootfs
+ # 41 1 6 ChromeOS kernel
+ # 42 1 7 ChromeOS rootfs
+ # 4141 2048 8 Basic data
+ # 43 1 9 ChromeOS reserved
+ # 44 1 10 ChromeOS reserved
+ # 40 1 11 ChromeOS firmware
+ # 6189 2048 12 EFI System Partition
# Create a dict (indexed by partition number) containing the above info
for line in out.splitlines():
@@ -446,7 +447,7 @@ def setup_android_image(cons):
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'qemu-img create {fname} 20M')
u_boot_utils.run_and_log(cons, f'cgpt create {fname}')
ptr = 40
@@ -498,11 +499,12 @@ def setup_android_image(cons):
with open(fname, 'wb') as outf:
outf.write(disk_data)
- print('wrote to {}'.format(fname))
+ print(f'wrote to {fname}')
return fname
def setup_cedit_file(cons):
+ """Set up a .dtb file for use with testing expo and configuration editor"""
infname = os.path.join(cons.config.source_dir,
'test/boot/files/expo_layout.dts')
inhname = os.path.join(cons.config.source_dir,
@@ -584,7 +586,7 @@ def test_ut(u_boot_console, ut_subtest):
# ut hush hush_test_simple_dollar prints "Unknown command" on purpose.
with u_boot_console.disable_check('unknown_command'):
output = u_boot_console.run_command('ut ' + ut_subtest)
- assert('Unknown command \'quux\' - try \'help\'' in output)
+ assert 'Unknown command \'quux\' - try \'help\'' in output
else:
output = u_boot_console.run_command('ut ' + ut_subtest)
assert output.endswith('Failures: 0')
diff --git a/test/py/u_boot_console_base.py b/test/py/u_boot_console_base.py
index 76a550d45a1..d8d0bdf9fd4 100644
--- a/test/py/u_boot_console_base.py
+++ b/test/py/u_boot_console_base.py
@@ -14,6 +14,7 @@ import pytest
import re
import sys
import u_boot_spawn
+from u_boot_spawn import BootFail, Timeout, Unexpected, handle_exception
# 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]*\\))')
@@ -26,6 +27,9 @@ pattern_error_please_reset = re.compile('### ERROR ### Please RESET the board ##
PAT_ID = 0
PAT_RE = 1
+# Timeout before expecting the console to be ready (in milliseconds)
+TIMEOUT_MS = 30000
+
bad_pattern_defs = (
('spl_signon', pattern_u_boot_spl_signon),
('main_signon', pattern_u_boot_main_signon),
@@ -109,7 +113,7 @@ class ConsoleBase(object):
Can only usefully be called by sub-classes.
Args:
- log: A mulptiplex_log.Logfile object, to which the U-Boot output
+ log: A multiplexed_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
@@ -186,13 +190,13 @@ class ConsoleBase(object):
m = self.p.expect([pattern_u_boot_spl_signon] +
self.bad_patterns)
if m != 0:
- raise Exception('Bad pattern found on SPL console: ' +
+ raise BootFail('Bad pattern found on SPL console: ' +
self.bad_pattern_ids[m - 1])
env_spl_banner_times -= 1
m = self.p.expect([pattern_u_boot_main_signon] + self.bad_patterns)
if m != 0:
- raise Exception('Bad pattern found on console: ' +
+ raise BootFail('Bad pattern found on console: ' +
self.bad_pattern_ids[m - 1])
self.u_boot_version_string = self.p.after
while True:
@@ -203,13 +207,9 @@ class ConsoleBase(object):
if m == 1:
self.p.send(' ')
continue
- raise Exception('Bad pattern found on console: ' +
+ raise BootFail('Bad pattern found on console: ' +
self.bad_pattern_ids[m - 2])
- except Exception as ex:
- self.log.error(str(ex))
- self.cleanup_spawn()
- raise
finally:
self.log.timestamp()
@@ -275,7 +275,7 @@ class ConsoleBase(object):
m = self.p.expect([chunk] + self.bad_patterns)
if m != 0:
self.at_prompt = False
- raise Exception('Bad pattern found on console: ' +
+ raise BootFail('Bad pattern found on console: ' +
self.bad_pattern_ids[m - 1])
if not wait_for_prompt:
return
@@ -285,16 +285,20 @@ class ConsoleBase(object):
m = self.p.expect([self.prompt_compiled] + self.bad_patterns)
if m != 0:
self.at_prompt = False
- raise Exception('Bad pattern found on console: ' +
+ raise BootFail('Missing prompt 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()
+ except Timeout as exc:
+ handle_exception(self.config, self, self.log, exc, 'Lab failure',
+ True)
+ raise
+ except BootFail as exc:
+ handle_exception(self.config, self, self.log, exc, 'Boot fail',
+ True, self.get_spawn_output())
raise
finally:
self.log.timestamp()
@@ -351,8 +355,9 @@ class ConsoleBase(object):
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])
+ raise Unexpected(
+ "Unexpected pattern found on console (exp '{text}': " +
+ self.bad_pattern_ids[m - 1])
def drain_console(self):
"""Read from and log the U-Boot console for a short time.
@@ -422,7 +427,7 @@ class ConsoleBase(object):
# Reset the console timeout value as some tests may change
# its default value during the execution
if not self.config.gdbserver:
- self.p.timeout = 30000
+ self.p.timeout = TIMEOUT_MS
return
try:
self.log.start_section('Starting U-Boot')
@@ -433,7 +438,7 @@ class ConsoleBase(object):
# 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.timeout = TIMEOUT_MS
self.p.logfile_read = self.logstream
if expect_reset:
loop_num = 2
diff --git a/test/py/u_boot_spawn.py b/test/py/u_boot_spawn.py
index 97e95e07c80..24d369035e5 100644
--- a/test/py/u_boot_spawn.py
+++ b/test/py/u_boot_spawn.py
@@ -8,6 +8,7 @@ Logic to spawn a sub-process and interact with its stdio.
import os
import re
import pty
+import pytest
import signal
import select
import time
@@ -16,6 +17,54 @@ import traceback
class Timeout(Exception):
"""An exception sub-class that indicates that a timeout occurred."""
+class BootFail(Exception):
+ """An exception sub-class that indicates that a boot failure occurred.
+
+ This is used when a bad pattern is seen when waiting for the boot prompt.
+ It is regarded as fatal, to avoid trying to boot the again and again to no
+ avail.
+ """
+
+class Unexpected(Exception):
+ """An exception sub-class that indicates that unexpected test was seen."""
+
+
+def handle_exception(ubconfig, console, log, err, name, fatal, output=''):
+ """Handle an exception from the console
+
+ Exceptions can occur when there is unexpected output or due to the board
+ crashing or hanging. Some exceptions are likely fatal, where retrying will
+ just chew up time to no available. In those cases it is best to cause
+ further tests be skipped.
+
+ Args:
+ ubconfig (ArbitraryAttributeContainer): ubconfig object
+ log (Logfile): Place to log errors
+ console (ConsoleBase): Console to clean up, if fatal
+ err (Exception): Exception which was thrown
+ name (str): Name of problem, to log
+ fatal (bool): True to abort all tests
+ output (str): Extra output to report on boot failure. This can show the
+ target's console output as it tried to boot
+ """
+ msg = f'{name}: '
+ if fatal:
+ msg += 'Marking connection bad - no other tests will run'
+ else:
+ msg += 'Assuming that lab is healthy'
+ print(msg)
+ log.error(msg)
+ log.error(f'Error: {err}')
+
+ if output:
+ msg += f'; output {output}'
+
+ if fatal:
+ ubconfig.connection_ok = False
+ console.cleanup_spawn()
+ pytest.exit(msg)
+
+
class Spawn:
"""Represents the stdio of a freshly created sub-process. Commands may be
sent to the process, and responses waited for.
@@ -137,6 +186,32 @@ class Spawn:
os.write(self.fd, data.encode(errors='replace'))
+ def receive(self, num_bytes):
+ """Receive data from the sub-process's stdin.
+
+ Args:
+ num_bytes (int): Maximum number of bytes to read
+
+ Returns:
+ str: The data received
+
+ Raises:
+ ValueError if U-Boot died
+ """
+ try:
+ c = os.read(self.fd, num_bytes).decode(errors='replace')
+ except OSError as err:
+ # With sandbox, try to detect when U-Boot exits when it
+ # shouldn't and explain why. This is much more friendly than
+ # just dying with an I/O error
+ if self.decode_signal and err.errno == 5: # I/O error
+ alive, _, info = self.checkalive()
+ if alive:
+ raise err
+ raise ValueError('U-Boot exited with %s' % info)
+ raise
+ return c
+
def expect(self, patterns):
"""Wait for the sub-process to emit specific data.
@@ -193,18 +268,7 @@ class Spawn:
events = self.poll.poll(poll_maxwait)
if not events:
raise Timeout()
- try:
- c = os.read(self.fd, 1024).decode(errors='replace')
- except OSError as err:
- # With sandbox, try to detect when U-Boot exits when it
- # shouldn't and explain why. This is much more friendly than
- # just dying with an I/O error
- if self.decode_signal and err.errno == 5: # I/O error
- alive, _, info = self.checkalive()
- if alive:
- raise err
- raise ValueError('U-Boot exited with %s' % info)
- raise
+ c = self.receive(1024)
if self.logfile_read:
self.logfile_read.write(c)
self.buf += c