[PATCH v3] test/py: nand: Add tests for NAND flash device
Love Kumar
love.kumar at amd.com
Tue May 5 09:40:25 CEST 2026
Add tests for nand commands to test various NAND flash operations such
as erase, write and read.
Signed-off-by: Love Kumar <love.kumar at amd.com>
---
Changes in v3:
- Use the current test/py APIs (ubman fixture and utils helpers)
- Refactor nand_pre_commands() to return a dictionary
- Accept multi-chip nand info output (e.g. "Device 0: 2x nand0")
- Add Sphinx-rendered documentation for the test
- Update copyright year
Changes in v2:
- Fix the deprecation warning issue for invalid escape sequence
---
doc/develop/pytest/test_nand.rst | 8 ++
test/py/tests/test_nand.py | 237 +++++++++++++++++++++++++++++++
2 files changed, 245 insertions(+)
create mode 100644 doc/develop/pytest/test_nand.rst
create mode 100644 test/py/tests/test_nand.py
diff --git a/doc/develop/pytest/test_nand.rst b/doc/develop/pytest/test_nand.rst
new file mode 100644
index 000000000000..7d16ae859e1e
--- /dev/null
+++ b/doc/develop/pytest/test_nand.rst
@@ -0,0 +1,8 @@
+test_nand
+=========
+
+.. automodule:: test_nand
+ :synopsis:
+ :member-order: bysource
+ :members:
+ :undoc-members:
diff --git a/test/py/tests/test_nand.py b/test/py/tests/test_nand.py
new file mode 100644
index 000000000000..d6086f0983ef
--- /dev/null
+++ b/test/py/tests/test_nand.py
@@ -0,0 +1,237 @@
+# SPDX-License-Identifier: GPL-2.0
+# (C) Copyright 2023-2026, Advanced Micro Devices, Inc.
+
+"""
+Note: This test relies on boardenv_* containing configuration values to define
+the nand device total size and timeout available for testing. Without this, the
+test will be automatically skipped. This test will be also skipped if the NAND
+flash device is not detected.
+
+For example:
+
+.. code-block:: python
+
+ # Setup env__nand_device_test to set the NAND flash total size and timeout.
+ env__nand_device_test = {
+ 'size': '8192 MB',
+ 'timeout': 100000,
+ }
+"""
+
+import pytest
+import random
+import re
+import utils
+
+def nand_pre_commands(ubman):
+ """Probe the NAND flash device and gather geometry from `nand info`.
+
+ Args:
+ ubman: A U-Boot console connection.
+
+ Returns:
+ A dictionary with the following keys:
+ page_size: NAND page size in bytes.
+ erase_size: NAND erase (sector) size in bytes.
+ sector_size: NAND erase (sector) size in KiB.
+ total_size: Usable NAND size in bytes (bad blocks subtracted).
+ timeout: Timeout in milliseconds for long-running operations.
+ """
+
+ f = ubman.config.env.get('env__nand_device_test', None)
+ if not f:
+ pytest.skip('No env file to read for NAND device test')
+
+ total_size = f.get('size', None)
+ timeout = f.get('timeout')
+
+ if not total_size:
+ pytest.skip('NAND device size not recognized')
+
+ output = ubman.run_command('nand info')
+ if not re.search(r'Device 0:\s*\S*\s*nand0', output):
+ pytest.skip('No NAND device available')
+
+ m = re.search('Page size (.+?) b', output)
+ if not m:
+ pytest.fail('NAND page size not recognized')
+ try:
+ page_size = int(m.group(1))
+ except ValueError:
+ pytest.fail('NAND page size not recognized')
+
+ m = re.search('sector size (.+?) KiB', output)
+ if not m:
+ pytest.fail('NAND erase size not recognized')
+ try:
+ erase_size = int(m.group(1))
+ sector_size = erase_size
+ except ValueError:
+ pytest.fail('NAND erase size not recognized')
+
+ erase_size *= 1024
+
+ output = ubman.run_command('nand bad')
+ if not 'bad blocks:' in output:
+ pytest.skip('No NAND device available')
+
+ count = 0
+ m = re.search(r'bad blocks:([\n\s\s\d\w]*)', output)
+ if m:
+ print(m.group(1))
+ var = m.group(1).split()
+ count = len(var)
+ print('bad blocks count= ' + str(count))
+
+ m = re.search('(.+?) MB', total_size)
+ if not m:
+ pytest.fail('NAND size not recognized')
+ try:
+ total_size = int(m.group(1))
+ total_size *= 1024 * 1024
+ print('Total size is: ' + str(total_size) + ' B')
+ total_size -= count * sector_size * 1024
+ print('New Total size is: ' + str(total_size) + ' B')
+ except ValueError:
+ pytest.fail('NAND size not recognized')
+
+ return {
+ 'page_size': page_size,
+ 'erase_size': erase_size,
+ 'sector_size': sector_size,
+ 'total_size': total_size,
+ 'timeout': timeout,
+ }
+
+ at pytest.mark.buildconfigspec('cmd_nand')
+ at pytest.mark.buildconfigspec('cmd_bdi')
+ at pytest.mark.buildconfigspec('cmd_memory')
+def test_nand_read_twice(ubman):
+ """This test reads the whole NAND flash twice, random_size till full flash
+ size, random till page size.
+ """
+
+ nand_params = nand_pre_commands(ubman)
+ page_size = nand_params['page_size']
+ total_size = nand_params['total_size']
+ expected_read = 'read: OK'
+
+ for size in (
+ random.randint(4, page_size),
+ random.randint(4, total_size),
+ total_size,
+ ):
+ addr = utils.find_ram_base(ubman)
+
+ output = ubman.run_command(
+ 'nand read %x 0 %x' % (addr + total_size, size)
+ )
+ assert expected_read in output
+
+ output = ubman.run_command('crc32 %x %x' % (addr + total_size, size))
+ m = re.search('==> (.+?)', output)
+ if not m:
+ pytest.fail('CRC32 failed')
+ expected_crc32 = m.group(1)
+
+ output = ubman.run_command(
+ 'nand read %x 0 %x' % (addr + total_size + 10, size)
+ )
+ assert expected_read in output
+
+ output = ubman.run_command(
+ 'crc32 %x %x' % (addr + total_size + 10, size)
+ )
+ assert expected_crc32 in output
+
+ at pytest.mark.buildconfigspec('cmd_nand')
+ at pytest.mark.buildconfigspec('cmd_bdi')
+ at pytest.mark.buildconfigspec('cmd_memory')
+def test_nand_write_twice(ubman):
+ """This test does the random writes till page size, size and full size"""
+
+ nand_params = nand_pre_commands(ubman)
+ page_size = nand_params['page_size']
+ erase_size = nand_params['erase_size']
+ total_size = nand_params['total_size']
+ expected_write = 'written: OK'
+ expected_read = 'read: OK'
+ expected_erase = '100% complete.'
+ old_size = 0
+
+ for size in (
+ random.randint(4, page_size),
+ random.randint(page_size, total_size),
+ total_size,
+ ):
+ offset = page_size
+ addr = utils.find_ram_base(ubman)
+ size = size - old_size
+ output = ubman.run_command('crc32 %x %x' % (addr + total_size, 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 + 1)
+ old_size *= page_size
+
+ if old_size + size > total_size:
+ size = total_size - old_size
+
+ eraseoffset = int(old_size / erase_size)
+ eraseoffset *= erase_size
+
+ erasesize = int(size / erase_size + 1)
+ erasesize *= erase_size
+
+ output = ubman.run_command(
+ 'nand erase.spread %x %x' % (eraseoffset, erasesize)
+ )
+ assert expected_erase in output
+
+ output = ubman.run_command(
+ 'nand write %x %x %x' % (addr + total_size, old_size, size)
+ )
+ assert expected_write in output
+ output = ubman.run_command(
+ 'nand read %x %x %x' % (addr + total_size + offset, old_size, size)
+ )
+ assert expected_read in output
+ output = ubman.run_command(
+ 'crc32 %x %x' % (addr + total_size + offset, size)
+ )
+ assert expected_crc32 in output
+ old_size = size
+
+ at pytest.mark.buildconfigspec('cmd_nand')
+def test_nand_erase_block(ubman):
+ """Erase the NAND flash one erase block at a time."""
+
+ nand_params = nand_pre_commands(ubman)
+ erase_size = nand_params['erase_size']
+ total_size = nand_params['total_size']
+
+ expected_erase = '100% complete.'
+ for start in range(0, total_size, erase_size):
+ output = ubman.run_command(
+ 'nand erase.spread %x %x' % (start, erase_size)
+ )
+ assert expected_erase in output
+
+ at pytest.mark.buildconfigspec('cmd_nand')
+def test_nand_erase_all(ubman):
+ """Erase the entire NAND flash in a single operation."""
+
+ nand_params = nand_pre_commands(ubman)
+ total_size = nand_params['total_size']
+ timeout = nand_params['timeout']
+
+ expected_erase = '100% complete.'
+ with ubman.temporary_timeout(timeout):
+ output = ubman.run_command(
+ 'nand erase.spread 0 ' + str(hex(total_size))
+ )
+ assert expected_erase in output
--
2.43.0
More information about the U-Boot
mailing list