[PATCH] test/py: mtd: Add tests for mtd command
Love Kumar
love.kumar at amd.com
Tue May 26 09:37:49 CEST 2026
Add test cases for mtd commands to verify list, erase, write, read
and dump operations on NOR flash and binary-file data integrity.
This test relies on boardenv_* configurations to run it for single or
multiple MTD partitions.
Signed-off-by: Love Kumar <love.kumar at amd.com>
---
doc/develop/pytest/test_mtd.rst | 10 +
test/py/tests/test_mtd.py | 648 ++++++++++++++++++++++++++++++++
2 files changed, 658 insertions(+)
create mode 100644 doc/develop/pytest/test_mtd.rst
create mode 100644 test/py/tests/test_mtd.py
diff --git a/doc/develop/pytest/test_mtd.rst b/doc/develop/pytest/test_mtd.rst
new file mode 100644
index 000000000000..70d469af5caa
--- /dev/null
+++ b/doc/develop/pytest/test_mtd.rst
@@ -0,0 +1,10 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+test_mtd
+========
+
+.. automodule:: test_mtd
+ :synopsis:
+ :member-order: bysource
+ :members:
+ :undoc-members:
diff --git a/test/py/tests/test_mtd.py b/test/py/tests/test_mtd.py
new file mode 100644
index 000000000000..947eba7426c6
--- /dev/null
+++ b/test/py/tests/test_mtd.py
@@ -0,0 +1,648 @@
+# SPDX-License-Identifier: GPL-2.0
+# (C) Copyright 2026, Advanced Micro Devices, Inc.
+
+"""
+Note: This test relies on boardenv_* containing configuration values to
+define one or more MTD partitions on which to exercise the 'mtd' U-Boot
+command. The test reads env__mtd_partitions itself and loops over every
+entry. Without this configuration the test is automatically skipped.
+
+It exercises the 'mtd' subcommands (list, erase, write, read, dump) and
+a binary-file data integrity round-trip (tftp + write + read + cmp.b).
+The suite works for single and stacked flash configurations. Partitions
+whose detected MTD type is not 'NOR flash' are logged and skipped
+per-partition.
+
+For Example:
+
+# List of MTD partitions to test. partition_name is the MTD partition
+# name passed to every 'mtd' subcommand; flash_part_name is optional
+# and, when set, is verified against 'SF: Detected <name>' lines in
+# 'mtd list' output.
+#
+# partition_name - MTD partition name (required)
+# flash_part_name - expected flash chip name (optional)
+# expected_size - partition size in bytes (optional cross-check)
+# expected_erasesize - erase-block size in bytes (optional cross-check)
+# writeable - bool; if False only non-destructive tests run
+# timeout - per-partition command timeout for long ops
+env__mtd_partitions = [
+ {
+ 'partition_name': 'qspi-fsbl-uboot',
+ 'flash_part_name': 'mt25qu512a',
+ 'expected_size': 0x1000000,
+ 'expected_erasesize': 0x10000,
+ 'writeable': True,
+ },
+ {
+ 'partition_name': 'rootfs_b',
+ 'flash_part_name': 'mt25qu512a',
+ 'expected_size': 0x1000000,
+ 'writeable': True,
+ },
+ {
+ 'partition_name': 'rootfs_a-rootfs_c-concat',
+ 'flash_part_name': 'mt25qu512a',
+ 'expected_size': 0x6000000,
+ 'writeable': True,
+ },
+]
+
+# Binary-file data integrity test configuration. When set,
+# test_mtd_bin_integrity fetches the file over TFTP once, then writes
+# it to every writeable partition large enough to hold it and verifies
+# byte-for-byte equality via 'cmp.b'. When unset, that test is skipped.
+#
+# bin_file - TFTP filename of the binary blob
+# bin_size - byte size of the binary blob
+# tftp_addr - RAM address used as the TFTP destination and source
+# for 'mtd write'
+# readback_addr - RAM address used to read the data back from flash
+env__mtd_bin_test = {
+ 'bin_file': 'BOOT_SINGLE.BIN',
+ 'tftp_addr': 0x200000,
+ 'bin_size': 0x1E3300,
+ 'readback_addr': 0x5000000,
+}
+
+# Optional. If omitted, tests use the defaults shown below.
+# iteration - randomized iteration count per test (default 3)
+# default_timeout - default per-command timeout in milliseconds
+# (default 1000000)
+env__mtd_test_settings = {
+ 'iteration': 3,
+ 'default_timeout': 1000000,
+}
+"""
+
+import random
+import re
+import pytest
+import test_net
+import utils
+
+MTD_LIST_HEADER = 'List of MTD devices:'
+SF_DETECTED = 'SF: Detected'
+SUPPORTED_TYPE = 'NOR flash'
+EXPECTED_ERASE = 'eraseblock(s)'
+EXPECTED_WRITE = 'Writing'
+WRITE_FAILURE = 'Failure while writing'
+EXPECTED_READ = 'Reading'
+READ_FAILURE = 'Failure while reading'
+EXPECTED_DUMP = 'Dump'
+EXPECTED_COMPARE = 'were the same'
+TFTP_DONE = 'Bytes transferred = '
+
+DEFAULT_TIMEOUT = 1000000
+DEFAULT_ITERATION = 3
+
+def parse_mtd_list(output):
+ """Parse 'mtd list' output into a dict keyed by device name.
+
+ Each value has 'type', 'block_size', 'min_io' and a 'partitions'
+ dict mapping name -> (start, end) for the device-level span and
+ any nested partition lines.
+ """
+ devices = {}
+ current = None
+ for line in output.splitlines():
+ match = re.match(r'^\* (\S+)\s*$', line)
+ if match:
+ current = match.group(1)
+ devices[current] = {
+ 'type': None,
+ 'block_size': 0,
+ 'min_io': 0,
+ 'partitions': {},
+ }
+ continue
+ if current is None:
+ continue
+ match = re.match(r'^\s*- type: (.+?)\s*$', line)
+ if match:
+ devices[current]['type'] = match.group(1)
+ continue
+ match = re.match(r'^\s*- block size: (0x[0-9a-f]+) bytes\s*$', line)
+ if match:
+ devices[current]['block_size'] = int(match.group(1), 16)
+ continue
+ match = re.match(r'^\s*- min I/O: (0x[0-9a-f]+) bytes\s*$', line)
+ if match:
+ devices[current]['min_io'] = int(match.group(1), 16)
+ continue
+ match = re.search(r'0x([0-9a-f]+)-0x([0-9a-f]+) : "([^"]+)"', line)
+ if match:
+ start = int(match.group(1), 16)
+ end = int(match.group(2), 16)
+ name = match.group(3)
+ devices[current]['partitions'][name] = (start, end)
+ return devices
+
+def parse_flash_parts(output):
+ """Return the list of flash names from 'SF: Detected <name>' lines."""
+ return re.findall(rf'{SF_DETECTED} (\S+) with', output)
+
+def find_part_info(parsed, partition_name):
+ """Look up a partition across all parsed devices.
+
+ Returns (dev_info, start, end) or (None, None, None) if not found.
+ """
+ for dev_info in parsed.values():
+ if partition_name in dev_info['partitions']:
+ start, end = dev_info['partitions'][partition_name]
+ return dev_info, start, end
+ return None, None, None
+
+def get_iterations(ubman):
+ """Per-test iteration count from env__mtd_test_settings (default 3)."""
+ settings = ubman.config.env.get('env__mtd_test_settings', {})
+ return settings.get('iteration', DEFAULT_ITERATION)
+
+def get_default_timeout(ubman):
+ """Default command timeout in ms from env__mtd_test_settings.
+
+ Defaults to DEFAULT_TIMEOUT (1000000) if unset.
+ """
+ settings = ubman.config.env.get('env__mtd_test_settings', {})
+ return settings.get('default_timeout', DEFAULT_TIMEOUT)
+
+def mtd_prepare(ubman, config):
+ """Probe an MTD partition and return a parameter dict.
+
+ Returns None (after logging via ubman.log.info) if the partition
+ is not present or its detected type is not 'NOR flash'. A missing
+ partition_name in the config triggers pytest.fail.
+ """
+ partition_name = config.get('partition_name')
+ if not partition_name:
+ pytest.fail(
+ "env__mtd_partitions entry missing required key "
+ "'partition_name'"
+ )
+
+ output = do_list(ubman)
+ parsed = parse_mtd_list(output)
+ if not parsed:
+ pytest.fail(f'mtd list returned no devices; output was:\n{output}')
+
+ dev_info, start, end = find_part_info(parsed, partition_name)
+ if dev_info is None:
+ ubman.log.info(f'skip {partition_name!r}: not present in mtd list')
+ return None
+
+ mtd_type = dev_info['type']
+ if mtd_type != SUPPORTED_TYPE:
+ ubman.log.info(
+ f'skip {partition_name!r}: type {mtd_type!r} unsupported '
+ f'(only {SUPPORTED_TYPE!r} is supported)'
+ )
+ return None
+
+ size = end - start
+ erasesize = dev_info['block_size']
+ writesize = dev_info['min_io']
+ if erasesize == 0 or writesize == 0:
+ pytest.fail(
+ f'mtd list reported invalid geometry for {partition_name!r}: '
+ f'erasesize={erasesize:#x} writesize={writesize:#x}'
+ )
+
+ expected_size = config.get('expected_size')
+ if expected_size and expected_size != size:
+ pytest.fail(
+ f'Size mismatch for {partition_name!r}: expected '
+ f'{expected_size:#x}, got {size:#x}'
+ )
+ expected_erasesize = config.get('expected_erasesize')
+ if expected_erasesize and expected_erasesize != erasesize:
+ pytest.fail(
+ f'Erase size mismatch for {partition_name!r}: expected '
+ f'{expected_erasesize:#x}, got {erasesize:#x}'
+ )
+
+ return {
+ 'name': partition_name,
+ 'size': size,
+ 'start': start,
+ 'erasesize': erasesize,
+ 'writesize': writesize,
+ 'type': mtd_type,
+ 'ram_base': utils.find_ram_base(ubman),
+ 'timeout': config.get('timeout', get_default_timeout(ubman)),
+ 'writeable': config.get('writeable', False),
+ 'flash_part_name': config.get('flash_part_name'),
+ }
+
+def rand_aligned_offset(size, align):
+ """Random offset that is a multiple of 'align' inside [0, size-align]."""
+ if size <= align:
+ return 0
+ return random.randrange(0, size - align + 1, align)
+
+def rand_aligned_range(part, kind):
+ """Random aligned (offset, length) inside the partition.
+
+ kind is 'erase' (erase-block aligned) or 'io' (page aligned). The
+ caller must have verified the partition holds at least one unit.
+ """
+ if kind == 'erase':
+ align = part['erasesize']
+ elif kind == 'io':
+ align = max(part['writesize'], 1)
+ else:
+ raise ValueError(f'Unknown alignment kind: {kind!r}')
+
+ size = part['size']
+ off = rand_aligned_offset(size, align)
+ remaining = size - off
+ if remaining <= align:
+ length = align
+ else:
+ length = random.randrange(align, remaining + 1, align)
+ return off, length
+
+def run_op(ubman, part, cmd, exp_str=None, not_exp_str=None, exp_rc=0):
+ """Run a U-Boot command and check its output and return code.
+
+ exp_str / not_exp_str (when not None) must / must not appear in the
+ output. exp_rc is the expected echo $? value; pass -1 to skip the
+ rc check. 'part' provides the timeout; pass None to use the default.
+ Returns (output, rc_string).
+ """
+ timeout = part['timeout'] if part else DEFAULT_TIMEOUT
+ with ubman.temporary_timeout(timeout):
+ output = ubman.run_command(cmd)
+ if exp_str is not None:
+ assert exp_str in output, (
+ f'Expected {exp_str!r} in output of {cmd!r}, got:\n{output}'
+ )
+ if not_exp_str is not None:
+ assert not_exp_str not in output, (
+ f'Unexpected {not_exp_str!r} in output of {cmd!r}'
+ )
+ rc_str = ubman.run_command('echo $?')
+ if exp_rc >= 0:
+ assert rc_str.endswith(str(exp_rc)), (
+ f'Expected rc {exp_rc} for {cmd!r}, got {rc_str!r}'
+ )
+ return output, rc_str
+
+def do_list(ubman):
+ """Run 'mtd list' and return the raw output."""
+ timeout = get_default_timeout(ubman)
+ with ubman.temporary_timeout(timeout):
+ output = ubman.run_command('mtd list')
+ assert MTD_LIST_HEADER in output, (
+ f'Expected {MTD_LIST_HEADER!r} in mtd list output, got:\n{output}'
+ )
+ return output
+
+def do_erase(ubman, part, off, size, exp_rc=0):
+ """Run 'mtd erase <name> <off> <size>'. off/size must be aligned."""
+ cmd = f'mtd erase {part["name"]} {off:#x} {size:#x}'
+ exp = EXPECTED_ERASE if exp_rc == 0 else None
+ return run_op(ubman, part, cmd, exp_str=exp, exp_rc=exp_rc)
+
+def do_write(ubman, part, addr, off, size, exp_rc=0):
+ """Run 'mtd write <name> <addr> <off> <size>'."""
+ cmd = f'mtd write {part["name"]} {addr:#x} {off:#x} {size:#x}'
+ exp = EXPECTED_WRITE if exp_rc == 0 else None
+ not_exp = WRITE_FAILURE if exp_rc == 0 else None
+ return run_op(
+ ubman, part, cmd, exp_str=exp, not_exp_str=not_exp, exp_rc=exp_rc,
+ )
+
+def do_read(ubman, part, addr, off, size, exp_rc=0):
+ """Run 'mtd read <name> <addr> <off> <size>'."""
+ cmd = f'mtd read {part["name"]} {addr:#x} {off:#x} {size:#x}'
+ exp = EXPECTED_READ if exp_rc == 0 else None
+ not_exp = READ_FAILURE if exp_rc == 0 else None
+ return run_op(
+ ubman, part, cmd, exp_str=exp, not_exp_str=not_exp, exp_rc=exp_rc,
+ )
+
+def do_dump(ubman, part, off=None, size=None):
+ """Run 'mtd dump' and verify a hex dump line is produced."""
+ cmd = f'mtd dump {part["name"]}'
+ if off is not None:
+ cmd += f' {off:#x}'
+ if size is not None:
+ cmd += f' {size:#x}'
+ output, _ = run_op(ubman, part, cmd, exp_str=EXPECTED_DUMP)
+ assert re.search(
+ r'0x[0-9a-f]{8}:\s+(?:[0-9a-f]{2}\s+){8,}', output
+ ), (
+ f'Expected hex dump line, got:\n{output}'
+ )
+ return output
+
+def round_trip_verify(ubman, part, off, size, pattern=None):
+ """Erase, write a RAM pattern, read it back, and CRC-compare.
+
+ The source pattern goes to part['ram_base'] and the readback area
+ is part['ram_base'] + part['size'] so the two never overlap. Both
+ off and size must be erase-block aligned. Returns the source CRC.
+ """
+ src = part['ram_base']
+ dst = part['ram_base'] + part['size']
+ if pattern is None:
+ pattern = random.randint(1, 0xfe)
+ ubman.run_command(f'mw.b {src:#x} {pattern:#x} {size:#x}')
+ src_crc = utils.crc32(ubman, src, size)
+ do_erase(ubman, part, off, size)
+ do_write(ubman, part, src, off, size)
+ do_read(ubman, part, dst, off, size)
+ dst_crc = utils.crc32(ubman, dst, size)
+ assert src_crc == dst_crc, (
+ f'CRC mismatch after round-trip on {part["name"]!r}: '
+ f'src={src_crc} dst={dst_crc} off={off:#x} size={size:#x}'
+ )
+ return src_crc
+
+def setup_network(ubman):
+ """Bring up the network (try DHCP, fall back to static), or skip."""
+ test_net.test_net_dhcp(ubman)
+ if not test_net.net_set_up:
+ test_net.test_net_setup_static(ubman)
+ if not test_net.net_set_up:
+ pytest.skip('Network setup failed; cannot fetch binary file')
+
+def get_configs(ubman):
+ """Return env__mtd_partitions or skip the test if not configured."""
+ configs = ubman.config.env.get('env__mtd_partitions', None)
+ if not configs:
+ pytest.skip('No MTD partitions configured')
+ return configs
+
+ at pytest.mark.buildconfigspec('cmd_mtd')
+def test_mtd_list(ubman):
+ """Verify 'mtd list' shows every partition and expected flash part."""
+ configs = get_configs(ubman)
+ output = do_list(ubman)
+ parsed = parse_mtd_list(output)
+ detected_flash = parse_flash_parts(output)
+
+ for config in configs:
+ partition_name = config.get('partition_name')
+ if not partition_name:
+ pytest.fail(
+ "env__mtd_partitions entry missing 'partition_name'"
+ )
+ dev_info, start, end = find_part_info(parsed, partition_name)
+ assert dev_info is not None, (
+ f'Partition {partition_name!r} not in mtd list output:\n'
+ f'{output}'
+ )
+ expected_erasesize = config.get('expected_erasesize')
+ if expected_erasesize:
+ assert dev_info['block_size'] == expected_erasesize, (
+ f'Erase size mismatch for {partition_name!r}: '
+ f'expected {expected_erasesize:#x}, got '
+ f'{dev_info["block_size"]:#x}'
+ )
+ expected_size = config.get('expected_size')
+ if expected_size:
+ assert (end - start) == expected_size, (
+ f'Size mismatch for {partition_name!r}: expected '
+ f'{expected_size:#x}, got {end - start:#x}'
+ )
+ flash_part_name = config.get('flash_part_name')
+ if flash_part_name:
+ assert flash_part_name in detected_flash, (
+ f'Expected flash part {flash_part_name!r} not found '
+ f'in SF: Detected lines: {detected_flash}'
+ )
+
+ at pytest.mark.buildconfigspec('cmd_mtd')
+ at pytest.mark.buildconfigspec('cmd_bdi')
+def test_mtd_erase_block(ubman):
+ """Erase random aligned ranges in every writeable partition."""
+ configs = get_configs(ubman)
+ ran_any = False
+ for config in configs:
+ part = mtd_prepare(ubman, config)
+ if part is None:
+ continue
+ if not part['writeable']:
+ ubman.log.info(f'skip {part["name"]!r}: not marked writeable')
+ continue
+ if part['size'] < part['erasesize']:
+ ubman.log.info(
+ f'skip {part["name"]!r}: smaller than one erase block'
+ )
+ continue
+ for _ in range(get_iterations(ubman)):
+ off, size = rand_aligned_range(part, 'erase')
+ do_erase(ubman, part, off, size)
+ ran_any = True
+ if not ran_any:
+ pytest.skip('No writeable partition large enough for this test')
+
+ at pytest.mark.buildconfigspec('cmd_mtd')
+def test_mtd_erase_all(ubman):
+ """Erase every writeable partition in full."""
+ configs = get_configs(ubman)
+ ran_any = False
+ for config in configs:
+ part = mtd_prepare(ubman, config)
+ if part is None:
+ continue
+ if not part['writeable']:
+ ubman.log.info(f'skip {part["name"]!r}: not marked writeable')
+ continue
+ if part['size'] < part['erasesize']:
+ ubman.log.info(
+ f'skip {part["name"]!r}: smaller than one erase block'
+ )
+ continue
+ # Round down to whole erase blocks: partition size is not
+ # guaranteed to be a multiple of the erase block.
+ full = (part['size'] // part['erasesize']) * part['erasesize']
+ for _ in range(get_iterations(ubman)):
+ do_erase(ubman, part, 0, full)
+ ran_any = True
+ if not ran_any:
+ pytest.skip('No writeable partition large enough for this test')
+
+ at pytest.mark.buildconfigspec('cmd_mtd')
+ at pytest.mark.buildconfigspec('cmd_bdi')
+ at pytest.mark.buildconfigspec('cmd_memory')
+ at pytest.mark.buildconfigspec('cmd_crc32')
+def test_mtd_write_read_random(ubman):
+ """Round-trip a random pattern at random aligned ranges."""
+ configs = get_configs(ubman)
+ ran_any = False
+ for config in configs:
+ part = mtd_prepare(ubman, config)
+ if part is None:
+ continue
+ if not part['writeable']:
+ ubman.log.info(f'skip {part["name"]!r}: not marked writeable')
+ continue
+ if part['size'] < part['erasesize']:
+ ubman.log.info(
+ f'skip {part["name"]!r}: smaller than one erase block'
+ )
+ continue
+ for _ in range(get_iterations(ubman)):
+ off, size = rand_aligned_range(part, 'erase')
+ round_trip_verify(ubman, part, off, size)
+ ran_any = True
+ if not ran_any:
+ pytest.skip('No writeable partition large enough for this test')
+
+ at pytest.mark.buildconfigspec('cmd_mtd')
+ at pytest.mark.buildconfigspec('cmd_bdi')
+ at pytest.mark.buildconfigspec('cmd_memory')
+ at pytest.mark.buildconfigspec('cmd_crc32')
+def test_mtd_write_twice(ubman):
+ """Round-trip small, medium, full, and a partition-midpoint write."""
+ configs = get_configs(ubman)
+ ran_any = False
+ for config in configs:
+ part = mtd_prepare(ubman, config)
+ if part is None:
+ continue
+ if not part['writeable']:
+ ubman.log.info(f'skip {part["name"]!r}: not marked writeable')
+ continue
+ if part['size'] < part['erasesize']:
+ ubman.log.info(
+ f'skip {part["name"]!r}: smaller than one erase block'
+ )
+ continue
+ erasesize = part['erasesize']
+ full = (part['size'] // erasesize) * erasesize
+ mid_upper = max(erasesize + 1, full // 2)
+ sizes = [
+ erasesize,
+ random.randrange(erasesize, mid_upper + 1, erasesize),
+ full,
+ ]
+ for chunk in sizes:
+ round_trip_verify(ubman, part, 0, chunk)
+
+ # Write a short region centred on the partition midpoint. For
+ # concat partitions this straddles the inner boundary.
+ op_size = min(erasesize * 4, full)
+ op_size = (op_size // erasesize) * erasesize
+ half = (op_size // 2 // erasesize) * erasesize
+ if half == 0:
+ half = erasesize
+ midpoint = (full // 2 // erasesize) * erasesize
+ straddle_off = max(midpoint - half, 0)
+ if op_size >= erasesize and straddle_off + op_size <= full:
+ round_trip_verify(ubman, part, straddle_off, op_size)
+ ran_any = True
+ if not ran_any:
+ pytest.skip('No writeable partition large enough for this test')
+
+ at pytest.mark.buildconfigspec('cmd_mtd')
+def test_mtd_dump(ubman):
+ """Verify 'mtd dump' produces a hex dump (default + explicit size)."""
+ configs = get_configs(ubman)
+ ran_any = False
+ for config in configs:
+ part = mtd_prepare(ubman, config)
+ if part is None:
+ continue
+ align = max(part['writesize'], 1)
+ units_total = part['size'] // align
+ if units_total == 0:
+ ubman.log.info(
+ f'skip {part["name"]!r}: smaller than one I/O unit'
+ )
+ continue
+
+ # Default-size dump: writesize bytes from a random aligned
+ # offset.
+ off = rand_aligned_offset(part['size'], align)
+ do_dump(ubman, part, off=off)
+
+ # Explicit-size dump: 1..4 aligned units, with the offset
+ # picked after the size so off + size stays within the
+ # partition.
+ units = random.randint(1, min(4, units_total))
+ size = units * align
+ off = random.randint(0, units_total - units) * align
+ do_dump(ubman, part, off=off, size=size)
+ ran_any = True
+ if not ran_any:
+ pytest.skip('No partition available for dump')
+
+ at pytest.mark.buildconfigspec('cmd_mtd')
+ at pytest.mark.buildconfigspec('cmd_bdi')
+ at pytest.mark.buildconfigspec('cmd_memory')
+ at pytest.mark.buildconfigspec('net_legacy', 'net_lwip')
+def test_mtd_bin_integrity(ubman):
+ """Write a binary blob to flash and byte-compare it back.
+
+ Fetches the binary over TFTP once, then writes it to every
+ writeable partition that is large enough to hold it. Each write
+ is followed by reading the data back into a different RAM region
+ and comparing byte-for-byte via 'cmp.b'.
+ """
+ bin_cfg = ubman.config.env.get('env__mtd_bin_test')
+ if not bin_cfg:
+ pytest.skip('No env__mtd_bin_test configured')
+ configs = get_configs(ubman)
+
+ bin_file = bin_cfg.get('bin_file')
+ bin_size = bin_cfg.get('bin_size')
+ tftp_addr = bin_cfg.get('tftp_addr')
+ readback_addr = bin_cfg.get('readback_addr')
+ missing = [
+ key for key, val in (
+ ('bin_file', bin_file),
+ ('bin_size', bin_size),
+ ('tftp_addr', tftp_addr),
+ ('readback_addr', readback_addr),
+ ) if val is None
+ ]
+ if missing:
+ pytest.fail(f'env__mtd_bin_test missing required keys: {missing}')
+
+ setup_network(ubman)
+ cmd = f'tftpb {tftp_addr:#x} {bin_file}'
+ with ubman.temporary_timeout(get_default_timeout(ubman)):
+ output = ubman.run_command(cmd)
+ if 'TIMEOUT' in output:
+ pytest.fail(f'TFTP timed out fetching {bin_file!r}')
+ if TFTP_DONE not in output:
+ pytest.fail(
+ f'TFTP of {bin_file!r} did not complete, output:\n{output}'
+ )
+
+ ran_any = False
+ for config in configs:
+ part = mtd_prepare(ubman, config)
+ if part is None:
+ continue
+ if not part['writeable']:
+ ubman.log.info(f'skip {part["name"]!r}: not marked writeable')
+ continue
+ erasesize = part['erasesize']
+ erase_size = ((bin_size + erasesize - 1) // erasesize) * erasesize
+ if erase_size > part['size']:
+ ubman.log.info(
+ f'skip {part["name"]!r}: binary ({bin_size:#x}) does '
+ f'not fit in partition ({part["size"]:#x})'
+ )
+ continue
+
+ do_erase(ubman, part, 0, erase_size)
+ do_write(ubman, part, tftp_addr, 0, bin_size)
+ # Wipe the readback area so a stale match cannot pass as success.
+ ubman.run_command(f'mw.b {readback_addr:#x} 0x00 {bin_size:#x}')
+ do_read(ubman, part, readback_addr, 0, bin_size)
+ cmp_cmd = f'cmp.b {tftp_addr:#x} {readback_addr:#x} {bin_size:#x}'
+ cmp_out = ubman.run_command(cmp_cmd)
+ assert EXPECTED_COMPARE in cmp_out, (
+ f'Binary mismatch on {part["name"]!r}: cmp.b reported '
+ f'{cmp_out!r}'
+ )
+ ran_any = True
+ if not ran_any:
+ pytest.skip(
+ 'No writeable partition large enough for the binary file'
+ )
--
2.23.0
More information about the U-Boot
mailing list