[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