[U-Boot] [PATCH] test/py: Add basic QSPI testing

Stephen Warren swarren at wwwdotorg.org
Tue May 17 19:30:45 CEST 2016


On 05/17/2016 08:05 AM, Michal Simek wrote:
> This is the first attempt how to test qspi.
> Detect SPI size. Read it all, random size, erase every block,
> write random data with random size and read it back,
> erase the whole qspi and at the end load kernel image via tftp
> and save it to memory.

I don't see anything that downloads a kernel image via tftp. That's good 
because I'd rather not tie SPI testing to working networking; we should 
keep tests independent as much as possible.

I think this (test filename, all symbols/strings in the patch, etc.) 
should be named "sf" not "qspi" since (a) it tests serial flash 
specifically, not SPI in general (other device types besides flash might 
exist on SPI), and (b) the test looks like it will work for any SPI 
flash, not just QSPI; IIUC the U-Boot commands work identically for 
single-, dual- and quad-wide buses.

I would rather this test were explicitly parametrized via the boardenv:

* The boardenv should specify whether sf testing is expected to occur. 
The current test attempts to detect sf, and if U-Boot can't, it just 
skips the test. I'd rather the boardenv say whether sf detection should 
work, and if it fails when boardenv says it should work, the test should 
fail rather than be skipped.

* Some devices might store useful data in their SPI flash (e.g. some 
Tegra devices boot from SPI) and we should not destroy that data in this 
test. As such, the boardenv should indicate whether the test should 
erase anything at all, and specify what part(s) of the flash can be 
erased if so (e.g. offset/size of a writable region, or a flash to 
indicate whether the entire flash erase can be tested).

* A board might have multiple sf chips attached. The boardenv could list 
which to test, whereas this test currently only tests the default 
device, I think.

> diff --git a/test/py/tests/test_qspi.py b/test/py/tests/test_qspi.py

> +# Find out qspi memory parameters
> +def qspi_pre_commands(u_boot_console):
> +    output = u_boot_console.run_command('sf probe')
> +    if not "SF: Detected" in output:
> +        pytest.skip('No QSPI device available')

I think that should be an error.

> +    m = re.search('page size (.+?) Bytes', output)
> +    if m:
> +        try:
> +            global page_size
> +            page_size = int(m.group(1))
> +        except ValueError:
> +            pytest.fail("QSPI page size not recognized")
> +
> +        print 'Page size is: ' + str(page_size) + " B"

page_size appears to be used unconditionally elsewhere. I think that 
should be written as:

m = re.search(...)
if not m:
     raise Exception(...)
try:
    global page_size
...

I wonder if it makes sense to apply further validation to these values. 
i.e. at least check the sizes aren't zero or at least some minimal size 
and alignment, so that later test code is guaranteed not to do nothing 
or fail in unexpected ways. Perhaps the boardenv should list the values 
that the HW is expected to show, so the test can validate that the probe 
command generates the expected output? I'm not totally sure on that last 
point, but maybe.

Rather than using print, perhaps use u_boot_console.log.info() so that 
the text shows up separately from any stream content in the log file?

> +    global qspi_detected
> +    qspi_detected = True

I think you can drop that. Rather, qspi_pre_commands should raise an 
exception (either error or skip) in any case where the test should not 
continue ...

> +# Read the whole QSPI flash twice, random_size till full flash size, random till page size
> + at pytest.mark.buildconfigspec('cmd_sf')
> + at pytest.mark.buildconfigspec('cmd_memory')
> +def test_qspi_read_twice(u_boot_console):
> +    qspi_pre_commands(u_boot_console)
> +
> +    if not qspi_detected:
> +        pytest.skip('QSPI not detected')

... which would allow that if condition to be removed from all test 
functions.

> +    expected_read = "Read: OK"

I'm not totally sure it's worth putting that into a string; while in 
this case it slightly reduces duplication (although in other cases in 
this file where the same style is used, there's no duplication being 
avoided), it makes it harder to see what the asserts are actually 
checking later in the code.

Simon Glass has requested that all strings use ' not ", except perhaps 
where there's an embedded ' in the string. This comment applies 
elsewhere in the file too.

> +    # TODO maybe add alignment and different start for pages
> +    for size in random.randint(4, page_size), random.randint(4, total_size), total_size:

I'm not sure it's a good idea for tests to use random numbers and hence 
operate differently each time. This can lead to intermittent failures 
that are hard to track down. Rather, if there are known corner-cases, 
the test should always test them all. Take a look at the DFU test (and 
its example boardenv content) for how that's handled there.

> +        # FIXME using 0 is failing for me
> +        output = u_boot_console.run_command('sf read %x 0 %x' % (addr + total_size, size))

"using 0" for what fails? That's confusing since the second parameter is 
"0" so I wonder if this test fails or passes for you? Perhaps that 
comment applies to the address parameter? If so, explicitly mentioning 
the parameter name would be useful to remove the ambiguity. Are you 
investigating the problem?

> +        assert expected_read in output
> +        output = u_boot_console.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 = u_boot_console.run_command('sf read %x 0 %x' % (addr + total_size + 10, size))
> +        assert expected_read in output
> +        output = u_boot_console.run_command('crc32 %x %x' % (addr + total_size + 10, size))
> +        assert expected_crc32 in output

That just proves that the SPI read wrote the same data into memory both 
times, or failed to write anything both times thus leaving the same 
pre-existing content in memory each time. It would be better to at least 
clear the memory before each sf read (via mw I imagine) to ensure that 
sf read actually wrote something to memory, and even better to compare 
against some known crc32 rather than just assuming that anything read 
from SPI is correct. The latter could be achieved by either:

a) In boardenv, specify the offset/length/crc32 of a region to be 
verified by read. This would make sense if at least part of the flash 
content is invariant across test runs.

b) Do a write-then-read test of known data. Something like:

1) erase the region
2) read the region, validate crc mismatches (i.e. the erase worked)
3) write the region
4) read the region, validate crc matches written data

Make sure that in steps 2 and 4, the memory buffer used is cleared 
before the sf read to guarantee that sf read actually read the data, 
rather than simply leaving existing RAM content in place.

> + at pytest.mark.buildconfigspec('cmd_sf')
> +def test_qspi_erase_all(u_boot_console):
> +    qspi_pre_commands(u_boot_console)
> +
> +    if not qspi_detected:
> +        pytest.skip('QSPI not detected')
> +
> +    expected_erase = "Erased: OK"
> +    start = 0
> +    output = u_boot_console.run_command('sf erase 0 ' + str(hex(total_size)))
> +    assert expected_erase in output

That should probably read the region and validate that the erase 
actually happened?


More information about the U-Boot mailing list