From: Love Kumar Date: Wed, 31 Jul 2024 09:24:12 +0000 (+0530) Subject: test/py: spi: Add tests for SPI flash device X-Git-Url: http://git.dujemihanovic.xyz/projects?a=commitdiff_plain;h=ce6895dd127ef86b851f3188288ea0ea3bd8c970;p=u-boot.git test/py: spi: Add tests for SPI flash device Add test cases for sf commands to verify various SPI flash operations such as erase, write and read. It also adds qspi lock unlock cases. This test relies on boardenv_* configurations to run it for different SPI flash family such as single SPI, QSPI, and OSPI. Signed-off-by: Love Kumar --- diff --git a/test/py/tests/test_spi.py b/test/py/tests/test_spi.py new file mode 100644 index 0000000000..c81eca5fba --- /dev/null +++ b/test/py/tests/test_spi.py @@ -0,0 +1,696 @@ +# SPDX-License-Identifier: GPL-2.0 +# (C) Copyright 2024, Advanced Micro Devices, Inc. + +""" +Note: This test relies on boardenv_* containing configuration values to define +spi minimum and maximum frequencies at which the flash part can operate on and +these tests run at different spi frequency randomised values in the range +multiple times based on the user defined iteration value. +It also defines the SPI bus number containing the SPI-flash chip, SPI +chip-select, SPI mode, SPI flash part name and timeout parameters. If minimum +and maximum frequency is not defined, it will run on freq 0 by default. + +Without the boardenv_* configuration, this test will be automatically skipped. + +It also relies on configuration values for supported flashes for lock and +unlock cases for SPI family flash. It will run lock-unlock cases only for the +supported flash parts. + +For Example: + +# Details of SPI device test parameters required for SPI device testing: + +# bus - SPI bus number to init the flash device +# chip_select - SPI chip select number to init the flash device +# min_freq - Minimum frequency in hz at which the flash part can operate, set 0 +# or None for default frequency +# max_freq - Maximum frequency in hz at which the flash part can operate, set 0 +# or None for default frequency +# mode - SPI mode to init the flash device +# part_name - SPI flash part name to be detected +# timeout - Default timeout to run the sf commands +# iteration - No of iteration to run SPI flash test + +env__spi_device_test = { + 'bus': 0, + 'chip_select': 0, + 'min_freq': 10000000, + 'max_freq': 100000000, + 'mode': 0, + 'part_name': 'n25q00a', + 'timeout': 100000, + 'iteration': 5, +} + +# supported_flash - Flash parts name which support lock-unlock functionality +env__spi_lock_unlock = { + 'supported_flash': 'mt25qu512a, n25q00a, n25q512ax3', +} +""" + +import random +import re +import pytest +import u_boot_utils + +SPI_DATA = {} +EXPECTED_ERASE = 'Erased: OK' +EXPECTED_WRITE = 'Written: OK' +EXPECTED_READ = 'Read: OK' +EXPECTED_ERASE_ERRORS = [ + 'Erase operation failed', + 'Attempted to modify a protected sector', + 'Erased: ERROR', + 'is protected and cannot be erased', + 'ERROR: flash area is locked', +] +EXPECTED_WRITE_ERRORS = [ + 'ERROR: flash area is locked', + 'Program operation failed', + 'Attempted to modify a protected sector', + 'Written: ERROR', +] + +def get_params_spi(u_boot_console): + ''' Get SPI device test parameters from boardenv file ''' + f = u_boot_console.config.env.get('env__spi_device_test', None) + if not f: + pytest.skip('No env file to read for SPI family device test') + + bus = f.get('bus', 0) + cs = f.get('chip_select', 0) + mode = f.get('mode', 0) + part_name = f.get('part_name', None) + timeout = f.get('timeout', None) + + if not part_name: + pytest.skip('No env file to read SPI family flash part name') + + return bus, cs, mode, part_name, timeout + +def spi_find_freq_range(u_boot_console): + '''Find out minimum and maximum frequnecies that SPI device can operate''' + f = u_boot_console.config.env.get('env__spi_device_test', None) + if not f: + pytest.skip('No env file to read for SPI family device test') + + min_f = f.get('min_freq', None) + max_f = f.get('max_freq', None) + iterations = f.get('iteration', 1) + + if not min_f: + min_f = 0 + if not max_f: + max_f = 0 + + max_f = max(max_f, min_f) + + return min_f, max_f, iterations + +def spi_pre_commands(u_boot_console, freq): + ''' Find out SPI family flash memory parameters ''' + bus, cs, mode, part_name, timeout = get_params_spi(u_boot_console) + + output = u_boot_console.run_command(f'sf probe {bus}:{cs} {freq} {mode}') + if not 'SF: Detected' in output: + pytest.fail('No SPI device available') + + if not part_name in output: + pytest.fail('SPI flash part name not recognized') + + m = re.search('page size (.+?) Bytes', output) + if m: + try: + page_size = int(m.group(1)) + except ValueError: + pytest.fail('SPI page size not recognized') + + m = re.search('erase size (.+?) KiB', output) + if m: + try: + erase_size = int(m.group(1)) + except ValueError: + pytest.fail('SPI erase size not recognized') + + erase_size *= 1024 + + m = re.search('total (.+?) MiB', output) + if m: + try: + total_size = int(m.group(1)) + except ValueError: + pytest.fail('SPI total size not recognized') + + total_size *= 1024 * 1024 + + m = re.search('Detected (.+?) with', output) + if m: + try: + flash_part = m.group(1) + assert flash_part == part_name + except ValueError: + pytest.fail('SPI flash part not recognized') + + global SPI_DATA + SPI_DATA = { + 'page_size': page_size, + 'erase_size': erase_size, + 'total_size': total_size, + 'flash_part': flash_part, + 'timeout': timeout, + } + +def get_page_size(): + ''' Get the SPI page size from spi data ''' + return SPI_DATA['page_size'] + +def get_erase_size(): + ''' Get the SPI erase size from spi data ''' + return SPI_DATA['erase_size'] + +def get_total_size(): + ''' Get the SPI total size from spi data ''' + return SPI_DATA['total_size'] + +def get_flash_part(): + ''' Get the SPI flash part name from spi data ''' + return SPI_DATA['flash_part'] + +def get_timeout(): + ''' Get the SPI timeout from spi data ''' + return SPI_DATA['timeout'] + +def spi_erase_block(u_boot_console, erase_size, total_size): + ''' Erase SPI flash memory block wise ''' + for start in range(0, total_size, erase_size): + output = u_boot_console.run_command(f'sf erase {hex(start)} {hex(erase_size)}') + assert EXPECTED_ERASE in output + +@pytest.mark.buildconfigspec('cmd_sf') +def test_spi_erase_block(u_boot_console): + ''' Test case to check SPI erase functionality by erasing memory regions + block-wise ''' + + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + spi_erase_block(u_boot_console, get_erase_size(), get_total_size()) + i = i + 1 + +def spi_write_twice(u_boot_console, page_size, erase_size, total_size, timeout): + ''' Random write till page size, random till size and full size ''' + addr = u_boot_utils.find_ram_base(u_boot_console) + + old_size = 0 + for size in ( + random.randint(4, page_size), + random.randint(page_size, total_size), + total_size, + ): + offset = random.randint(4, page_size) + offset = offset & ~3 + size = size & ~3 + size = size - old_size + output = u_boot_console.run_command(f'crc32 {hex(addr + total_size)} {hex(size)}') + m = re.search('==> (.+?)$', output) + if not m: + pytest.fail('CRC32 failed') + + expected_crc32 = m.group(1) + if old_size % page_size: + old_size = int(old_size / page_size) + old_size *= page_size + + if size % erase_size: + erasesize = int(size / erase_size + 1) + erasesize *= erase_size + + eraseoffset = int(old_size / erase_size) + eraseoffset *= erase_size + + timeout = 100000000 + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf erase {hex(eraseoffset)} {hex(erasesize)}' + ) + assert EXPECTED_ERASE in output + + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf write {hex(addr + total_size)} {hex(old_size)} {hex(size)}' + ) + assert EXPECTED_WRITE in output + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf read {hex(addr + total_size + offset)} {hex(old_size)} {hex(size)}' + ) + assert EXPECTED_READ in output + output = u_boot_console.run_command( + f'crc32 {hex(addr + total_size + offset)} {hex(size)}' + ) + assert expected_crc32 in output + old_size = size + +@pytest.mark.buildconfigspec('cmd_bdi') +@pytest.mark.buildconfigspec('cmd_sf') +@pytest.mark.buildconfigspec('cmd_memory') +def test_spi_write_twice(u_boot_console): + ''' Test to write data with random size twice for SPI ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + spi_write_twice( + u_boot_console, + get_page_size(), + get_erase_size(), + get_total_size(), + get_timeout() + ) + i = i + 1 + +def spi_write_continues(u_boot_console, page_size, erase_size, total_size, timeout): + ''' Write with random size of data to continue SPI write case ''' + spi_erase_block(u_boot_console, erase_size, total_size) + addr = u_boot_utils.find_ram_base(u_boot_console) + + output = u_boot_console.run_command(f'crc32 {hex(addr + 0x10000)} {hex(total_size)}') + m = re.search('==> (.+?)$', output) + if not m: + pytest.fail('CRC32 failed') + expected_crc32 = m.group(1) + + old_size = 0 + for size in ( + random.randint(4, page_size), + random.randint(page_size, total_size), + total_size, + ): + size = size & ~3 + size = size - old_size + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf write {hex(addr + 0x10000 + old_size)} {hex(old_size)} {hex(size)}' + ) + assert EXPECTED_WRITE in output + old_size += size + + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf read {hex(addr + 0x10000 + total_size)} 0 {hex(total_size)}' + ) + assert EXPECTED_READ in output + + output = u_boot_console.run_command( + f'crc32 {hex(addr + 0x10000 + total_size)} {hex(total_size)}' + ) + assert expected_crc32 in output + +@pytest.mark.buildconfigspec('cmd_bdi') +@pytest.mark.buildconfigspec('cmd_sf') +@pytest.mark.buildconfigspec('cmd_memory') +def test_spi_write_continues(u_boot_console): + ''' Test to write more random size data for SPI ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + spi_write_twice( + u_boot_console, + get_page_size(), + get_erase_size(), + get_total_size(), + get_timeout(), + ) + i = i + 1 + +def spi_read_twice(u_boot_console, page_size, total_size, timeout): + ''' Read the whole SPI flash twice, random_size till full flash size, + random till page size ''' + for size in random.randint(4, page_size), random.randint(4, total_size), total_size: + addr = u_boot_utils.find_ram_base(u_boot_console) + size = size & ~3 + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf read {hex(addr + total_size)} 0 {hex(size)}' + ) + assert EXPECTED_READ in output + output = u_boot_console.run_command(f'crc32 {hex(addr + total_size)} {hex(size)}') + m = re.search('==> (.+?)$', output) + if not m: + pytest.fail('CRC32 failed') + expected_crc32 = m.group(1) + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf read {hex(addr + total_size + 10)} 0 {hex(size)}' + ) + assert EXPECTED_READ in output + output = u_boot_console.run_command( + f'crc32 {hex(addr + total_size + 10)} {hex(size)}' + ) + assert expected_crc32 in output + +@pytest.mark.buildconfigspec('cmd_sf') +@pytest.mark.buildconfigspec('cmd_bdi') +@pytest.mark.buildconfigspec('cmd_memory') +def test_spi_read_twice(u_boot_console): + ''' Test to read random data twice from SPI ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + spi_read_twice(u_boot_console, get_page_size(), get_total_size(), get_timeout()) + i = i + 1 + +def spi_erase_all(u_boot_console, total_size, timeout): + ''' Erase the full chip SPI ''' + start = 0 + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command(f'sf erase {start} {hex(total_size)}') + assert EXPECTED_ERASE in output + +@pytest.mark.buildconfigspec('cmd_sf') +def test_spi_erase_all(u_boot_console): + ''' Test to check full chip erase for SPI ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + spi_erase_all(u_boot_console, get_total_size(), get_timeout()) + i = i + 1 + +def flash_ops( + u_boot_console, ops, start, size, offset=0, exp_ret=0, exp_str='', not_exp_str='' +): + ''' Flash operations: erase, write and read ''' + + f = u_boot_console.config.env.get('env__spi_device_test', None) + if not f: + timeout = 1000000 + + timeout = f.get('timeout', 1000000) + + if ops == 'erase': + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command(f'sf erase {hex(start)} {hex(size)}') + else: + with u_boot_console.temporary_timeout(timeout): + output = u_boot_console.run_command( + f'sf {ops} {hex(offset)} {hex(start)} {hex(size)}' + ) + + if exp_str: + assert exp_str in output + if not_exp_str: + assert not_exp_str not in output + + ret_code = u_boot_console.run_command('echo $?') + if exp_ret >= 0: + assert ret_code.endswith(str(exp_ret)) + + return output, ret_code + +def spi_unlock_exit(u_boot_console, addr, size): + ''' Unlock the flash before making it fail ''' + u_boot_console.run_command(f'sf protect unlock {hex(addr)} {hex(size)}') + assert False, 'FAIL: Flash lock is unable to protect the data!' + +def find_prot_region(lock_addr, lock_size): + ''' Get the protected and un-protected region of flash ''' + total_size = get_total_size() + erase_size = get_erase_size() + + if lock_addr < (total_size // 2): + sect_num = (lock_addr + lock_size) // erase_size + x = 1 + while x < sect_num: + x *= 2 + prot_start = 0 + prot_size = x * erase_size + unprot_start = prot_start + prot_size + unprot_size = total_size - unprot_start + else: + sect_num = (total_size - lock_addr) // erase_size + x = 1 + while x < sect_num: + x *= 2 + prot_start = total_size - (x * erase_size) + prot_size = total_size - prot_start + unprot_start = 0 + unprot_size = prot_start + + return prot_start, prot_size, unprot_start, unprot_size + +def protect_ops(u_boot_console, lock_addr, lock_size, ops="unlock"): + ''' Run the command to lock or Unlock the flash ''' + u_boot_console.run_command(f'sf protect {ops} {hex(lock_addr)} {hex(lock_size)}') + output = u_boot_console.run_command('echo $?') + if ops == "lock" and not output.endswith('0'): + u_boot_console.run_command(f'sf protect unlock {hex(lock_addr)} {hex(lock_size)}') + assert False, "sf protect lock command exits with non-zero return code" + assert output.endswith('0') + +def erase_write_ops(u_boot_console, start, size): + ''' Basic erase and write operation for flash ''' + addr = u_boot_utils.find_ram_base(u_boot_console) + flash_ops(u_boot_console, 'erase', start, size, 0, 0, EXPECTED_ERASE) + flash_ops(u_boot_console, 'write', start, size, addr, 0, EXPECTED_WRITE) + +def spi_lock_unlock(u_boot_console, lock_addr, lock_size): + ''' Lock unlock operations for SPI family flash ''' + addr = u_boot_utils.find_ram_base(u_boot_console) + erase_size = get_erase_size() + + # Find the protected/un-protected region + prot_start, prot_size, unprot_start, unprot_size = find_prot_region(lock_addr, lock_size) + + # Check erase/write operation before locking + erase_write_ops(u_boot_console, prot_start, prot_size) + + # Locking the flash + protect_ops(u_boot_console, lock_addr, lock_size, 'lock') + + # Check erase/write operation after locking + output, ret_code = flash_ops(u_boot_console, 'erase', prot_start, prot_size, 0, -1) + if not any(error in output for error in EXPECTED_ERASE_ERRORS) or ret_code.endswith( + '0' + ): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + output, ret_code = flash_ops( + u_boot_console, 'write', prot_start, prot_size, addr, -1 + ) + if not any(error in output for error in EXPECTED_WRITE_ERRORS) or ret_code.endswith( + '0' + ): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + # Check locked sectors + sect_lock_start = random.randrange(prot_start, (prot_start + prot_size), erase_size) + if prot_size > erase_size: + sect_lock_size = random.randrange( + erase_size, (prot_start + prot_size - sect_lock_start), erase_size + ) + else: + sect_lock_size = erase_size + sect_write_size = random.randint(1, sect_lock_size) + + output, ret_code = flash_ops( + u_boot_console, 'erase', sect_lock_start, sect_lock_size, 0, -1 + ) + if not any(error in output for error in EXPECTED_ERASE_ERRORS) or ret_code.endswith( + '0' + ): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + output, ret_code = flash_ops( + u_boot_console, 'write', sect_lock_start, sect_write_size, addr, -1 + ) + if not any(error in output for error in EXPECTED_WRITE_ERRORS) or ret_code.endswith( + '0' + ): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + # Check unlocked sectors + if unprot_size != 0: + sect_unlock_start = random.randrange( + unprot_start, (unprot_start + unprot_size), erase_size + ) + if unprot_size > erase_size: + sect_unlock_size = random.randrange( + erase_size, (unprot_start + unprot_size - sect_unlock_start), erase_size + ) + else: + sect_unlock_size = erase_size + sect_write_size = random.randint(1, sect_unlock_size) + + output, ret_code = flash_ops( + u_boot_console, 'erase', sect_unlock_start, sect_unlock_size, 0, -1 + ) + if EXPECTED_ERASE not in output or ret_code.endswith('1'): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + output, ret_code = flash_ops( + u_boot_console, 'write', sect_unlock_start, sect_write_size, addr, -1 + ) + if EXPECTED_WRITE not in output or ret_code.endswith('1'): + spi_unlock_exit(u_boot_console, lock_addr, lock_size) + + # Unlocking the flash + protect_ops(u_boot_console, lock_addr, lock_size, 'unlock') + + # Check erase/write operation after un-locking + erase_write_ops(u_boot_console, prot_start, prot_size) + + # Check previous locked sectors + sect_lock_start = random.randrange(prot_start, (prot_start + prot_size), erase_size) + if prot_size > erase_size: + sect_lock_size = random.randrange( + erase_size, (prot_start + prot_size - sect_lock_start), erase_size + ) + else: + sect_lock_size = erase_size + sect_write_size = random.randint(1, sect_lock_size) + + flash_ops( + u_boot_console, 'erase', sect_lock_start, sect_lock_size, 0, 0, EXPECTED_ERASE + ) + flash_ops( + u_boot_console, + 'write', + sect_lock_start, + sect_write_size, + addr, + 0, + EXPECTED_WRITE, + ) + +@pytest.mark.buildconfigspec('cmd_bdi') +@pytest.mark.buildconfigspec('cmd_sf') +@pytest.mark.buildconfigspec('cmd_memory') +def test_spi_lock_unlock(u_boot_console): + ''' Test to check the lock-unlock functionality for SPI family flash ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + flashes = u_boot_console.config.env.get('env__spi_lock_unlock', False) + if not flashes: + pytest.skip('No supported flash list for lock/unlock provided') + + i = 0 + while i < loop: + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + total_size = get_total_size() + flash_part = get_flash_part() + + flashes_list = flashes.get('supported_flash', None).split(',') + flashes_list = [x.strip() for x in flashes_list] + if flash_part not in flashes_list: + pytest.skip('Detected flash does not support lock/unlock') + + # For lower half of memory + lock_addr = random.randint(0, (total_size // 2) - 1) + lock_size = random.randint(1, ((total_size // 2) - lock_addr)) + spi_lock_unlock(u_boot_console, lock_addr, lock_size) + + # For upper half of memory + lock_addr = random.randint((total_size // 2), total_size - 1) + lock_size = random.randint(1, (total_size - lock_addr)) + spi_lock_unlock(u_boot_console, lock_addr, lock_size) + + # For entire flash + lock_addr = random.randint(0, total_size - 1) + lock_size = random.randint(1, (total_size - lock_addr)) + spi_lock_unlock(u_boot_console, lock_addr, lock_size) + + i = i + 1 + +@pytest.mark.buildconfigspec('cmd_bdi') +@pytest.mark.buildconfigspec('cmd_sf') +@pytest.mark.buildconfigspec('cmd_memory') +def test_spi_negative(u_boot_console): + ''' Negative tests for SPI ''' + min_f, max_f, loop = spi_find_freq_range(u_boot_console) + spi_pre_commands(u_boot_console, random.randint(min_f, max_f)) + total_size = get_total_size() + erase_size = get_erase_size() + page_size = get_page_size() + addr = u_boot_utils.find_ram_base(u_boot_console) + i = 0 + while i < loop: + # Erase negative test + start = random.randint(0, total_size) + esize = erase_size + + # If erasesize is not multiple of flash's erase size + while esize % erase_size == 0: + esize = random.randint(0, total_size - start) + + error_msg = 'Erased: ERROR' + flash_ops( + u_boot_console, 'erase', start, esize, 0, 1, error_msg, EXPECTED_ERASE + ) + + # If eraseoffset exceeds beyond flash size + eoffset = random.randint(total_size, (total_size + int(0x1000000))) + error_msg = 'Offset exceeds device limit' + flash_ops( + u_boot_console, 'erase', eoffset, esize, 0, 1, error_msg, EXPECTED_ERASE + ) + + # If erasesize exceeds beyond flash size + esize = random.randint((total_size - start), (total_size + int(0x1000000))) + error_msg = 'ERROR: attempting erase past flash size' + flash_ops( + u_boot_console, 'erase', start, esize, 0, 1, error_msg, EXPECTED_ERASE + ) + + # If erase size is 0 + esize = 0 + error_msg = 'ERROR: Invalid size 0' + flash_ops( + u_boot_console, 'erase', start, esize, 0, 1, error_msg, EXPECTED_ERASE + ) + + # If erasesize is less than flash's page size + esize = random.randint(0, page_size) + start = random.randint(0, (total_size - page_size)) + error_msg = 'Erased: ERROR' + flash_ops( + u_boot_console, 'erase', start, esize, 0, 1, error_msg, EXPECTED_ERASE + ) + + # Write/Read negative test + # if Write/Read size exceeds beyond flash size + offset = random.randint(0, total_size) + size = random.randint((total_size - offset), (total_size + int(0x1000000))) + error_msg = 'Size exceeds partition or device limit' + flash_ops( + u_boot_console, 'write', offset, size, addr, 1, error_msg, EXPECTED_WRITE + ) + flash_ops( + u_boot_console, 'read', offset, size, addr, 1, error_msg, EXPECTED_READ + ) + + # if Write/Read offset exceeds beyond flash size + offset = random.randint(total_size, (total_size + int(0x1000000))) + size = random.randint(0, total_size) + error_msg = 'Offset exceeds device limit' + flash_ops( + u_boot_console, 'write', offset, size, addr, 1, error_msg, EXPECTED_WRITE + ) + flash_ops( + u_boot_console, 'read', offset, size, addr, 1, error_msg, EXPECTED_READ + ) + + # if Write/Read size is 0 + offset = random.randint(0, 2) + size = 0 + error_msg = 'ERROR: Invalid size 0' + flash_ops( + u_boot_console, 'write', offset, size, addr, 1, error_msg, EXPECTED_WRITE + ) + flash_ops( + u_boot_console, 'read', offset, size, addr, 1, error_msg, EXPECTED_READ + ) + + i = i + 1