blob: 3160d58540f288a327552636db6dd70d15001ad9 [file] [log] [blame]
# 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