From 0d447524425ed29cf80987fb058e3bc5e941ba87 Mon Sep 17 00:00:00 2001 From: Patrick Delaunay Date: Wed, 10 Apr 2019 14:09:28 +0200 Subject: [PATCH] stm32mp1: ram: add tests in DDR interactive mode Add command tests for DDR interactive mode, used during board bring-up or with CubeMX DDR tools to verify the DDR configuration. Signed-off-by: Patrick Delaunay --- drivers/ram/stm32mp1/Kconfig | 8 + drivers/ram/stm32mp1/Makefile | 1 + drivers/ram/stm32mp1/stm32mp1_interactive.c | 75 + drivers/ram/stm32mp1/stm32mp1_tests.c | 1426 +++++++++++++++++++ drivers/ram/stm32mp1/stm32mp1_tests.h | 31 + 5 files changed, 1541 insertions(+) create mode 100644 drivers/ram/stm32mp1/stm32mp1_tests.c create mode 100644 drivers/ram/stm32mp1/stm32mp1_tests.h diff --git a/drivers/ram/stm32mp1/Kconfig b/drivers/ram/stm32mp1/Kconfig index 511eaf2c4c..4e682b4edf 100644 --- a/drivers/ram/stm32mp1/Kconfig +++ b/drivers/ram/stm32mp1/Kconfig @@ -29,3 +29,11 @@ config STM32MP1_DDR_INTERACTIVE_FORCE skip the polling of character 'd' in console useful when SPL is loaded in sysram directly by programmer + +config STM32MP1_DDR_TESTS + bool "STM32MP1 DDR driver : tests support" + depends on STM32MP1_DDR_INTERACTIVE + default y + help + activate test support for interactive support in + STM32MP1 DDR controller driver: command test diff --git a/drivers/ram/stm32mp1/Makefile b/drivers/ram/stm32mp1/Makefile index 7b1f095f90..71ded6bed4 100644 --- a/drivers/ram/stm32mp1/Makefile +++ b/drivers/ram/stm32mp1/Makefile @@ -7,6 +7,7 @@ obj-y += stm32mp1_ram.o obj-y += stm32mp1_ddr.o obj-$(CONFIG_STM32MP1_DDR_INTERACTIVE) += stm32mp1_interactive.o +obj-$(CONFIG_STM32MP1_DDR_TESTS) += stm32mp1_tests.o ifneq ($(DDR_INTERACTIVE),) CFLAGS_stm32mp1_interactive.o += -DCONFIG_STM32MP1_DDR_INTERACTIVE_FORCE=y diff --git a/drivers/ram/stm32mp1/stm32mp1_interactive.c b/drivers/ram/stm32mp1/stm32mp1_interactive.c index 1a31948cb1..62d61ac869 100644 --- a/drivers/ram/stm32mp1/stm32mp1_interactive.c +++ b/drivers/ram/stm32mp1/stm32mp1_interactive.c @@ -11,6 +11,7 @@ #include #include #include "stm32mp1_ddr.h" +#include "stm32mp1_tests.h" DECLARE_GLOBAL_DATA_PTR; @@ -51,6 +52,9 @@ enum ddr_command stm32mp1_get_command(char *cmd, int argc) [DDR_CMD_STEP] = "step", [DDR_CMD_NEXT] = "next", [DDR_CMD_GO] = "go", +#ifdef CONFIG_STM32MP1_DDR_TESTS + [DDR_CMD_TEST] = "test", +#endif }; /* min and max number of argument */ const char cmd_arg[DDR_CMD_UNKNOWN][2] = { @@ -64,6 +68,9 @@ enum ddr_command stm32mp1_get_command(char *cmd, int argc) [DDR_CMD_STEP] = { 0, 1 }, [DDR_CMD_NEXT] = { 0, 0 }, [DDR_CMD_GO] = { 0, 0 }, +#ifdef CONFIG_STM32MP1_DDR_TESTS + [DDR_CMD_TEST] = { 0, 255 }, +#endif }; int i; @@ -105,6 +112,9 @@ static void stm32mp1_do_usage(void) "next goes to the next step\n" "go continues the U-Boot SPL execution\n" "reset reboots machine\n" +#ifdef CONFIG_STM32MP1_DDR_TESTS + "test [help] | [...] lists (with help) or executes test \n" +#endif "\nwith for [type|reg]:\n" " all registers if absent\n" " = ctl, phy\n" @@ -287,6 +297,63 @@ end: return step; } +#if defined(CONFIG_STM32MP1_DDR_TESTS) +static const char * const s_result[] = { + [TEST_PASSED] = "Pass", + [TEST_FAILED] = "Failed", + [TEST_ERROR] = "Error" +}; + +static void stm32mp1_ddr_subcmd(struct ddr_info *priv, + int argc, char *argv[], + const struct test_desc array[], + const int array_nb) +{ + int i; + unsigned long value; + int result; + char string[50] = ""; + + if (argc == 1) { + printf("%s:%d\n", argv[0], array_nb); + for (i = 0; i < array_nb; i++) + printf("%d:%s:%s\n", + i, array[i].name, array[i].usage); + return; + } + if (argc > 1 && !strcmp(argv[1], "help")) { + printf("%s:%d\n", argv[0], array_nb); + for (i = 0; i < array_nb; i++) + printf("%d:%s:%s:%s\n", i, + array[i].name, array[i].usage, array[i].help); + return; + } + + if ((strict_strtoul(argv[1], 0, &value) < 0) || + value >= array_nb) { + sprintf(string, "invalid argument %s", + argv[1]); + result = TEST_FAILED; + goto end; + } + + if (argc > (array[value].max_args + 2)) { + sprintf(string, "invalid nb of args %d, max %d", + argc - 2, array[value].max_args); + result = TEST_FAILED; + goto end; + } + + printf("execute %d:%s\n", (int)value, array[value].name); + clear_ctrlc(); + result = array[value].fct(priv->ctl, priv->phy, + string, argc - 2, &argv[2]); + +end: + printf("Result: %s [%s]\n", s_result[result], string); +} +#endif + bool stm32mp1_ddr_interactive(void *priv, enum stm32mp1_ddr_interact_step step, const struct stm32mp1_ddr_config *config) @@ -382,6 +449,14 @@ bool stm32mp1_ddr_interactive(void *priv, next_step = stm32mp1_do_step(step, argc, argv); break; +#ifdef CONFIG_STM32MP1_DDR_TESTS + case DDR_CMD_TEST: + if (!stm32mp1_check_step(step, STEP_DDR_READY)) + continue; + stm32mp1_ddr_subcmd(priv, argc, argv, test, test_nb); + break; +#endif + default: break; } diff --git a/drivers/ram/stm32mp1/stm32mp1_tests.c b/drivers/ram/stm32mp1/stm32mp1_tests.c new file mode 100644 index 0000000000..b6fb2a9c58 --- /dev/null +++ b/drivers/ram/stm32mp1/stm32mp1_tests.c @@ -0,0 +1,1426 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + */ +#include +#include +#include +#include +#include "stm32mp1_tests.h" + +#define ADDR_INVALID 0xFFFFFFFF + +DECLARE_GLOBAL_DATA_PTR; + +static int get_bufsize(char *string, int argc, char *argv[], int arg_nb, + size_t *bufsize, size_t default_size) +{ + unsigned long value; + + if (argc > arg_nb) { + if (strict_strtoul(argv[arg_nb], 0, &value) < 0) { + sprintf(string, "invalid %d parameter %s", + arg_nb, argv[arg_nb]); + return -1; + } + if (value > STM32_DDR_SIZE || value == 0) { + sprintf(string, "invalid size %s", argv[arg_nb]); + return -1; + } + if (value & 0x3) { + sprintf(string, "unaligned size %s", + argv[arg_nb]); + return -1; + } + *bufsize = value; + } else { + if (default_size != STM32_DDR_SIZE) + *bufsize = default_size; + else + *bufsize = get_ram_size((long *)STM32_DDR_BASE, + STM32_DDR_SIZE); + } + return 0; +} + +static int get_nb_loop(char *string, int argc, char *argv[], int arg_nb, + u32 *nb_loop, u32 default_nb_loop) +{ + unsigned long value; + + if (argc > arg_nb) { + if (strict_strtoul(argv[arg_nb], 0, &value) < 0) { + sprintf(string, "invalid %d parameter %s", + arg_nb, argv[arg_nb]); + return -1; + } + if (value == 0) + printf("WARNING: infinite loop requested\n"); + *nb_loop = value; + } else { + *nb_loop = default_nb_loop; + } + + return 0; +} + +static int get_addr(char *string, int argc, char *argv[], int arg_nb, + u32 *addr) +{ + unsigned long value; + + if (argc > arg_nb) { + if (strict_strtoul(argv[arg_nb], 16, &value) < 0) { + sprintf(string, "invalid %d parameter %s", + arg_nb, argv[arg_nb]); + return -1; + } + if (value < STM32_DDR_BASE) { + sprintf(string, "too low address %s", argv[arg_nb]); + return -1; + } + if (value & 0x3 && value != ADDR_INVALID) { + sprintf(string, "unaligned address %s", + argv[arg_nb]); + return -1; + } + *addr = value; + } else { + *addr = STM32_DDR_BASE; + } + + return 0; +} + +static int get_pattern(char *string, int argc, char *argv[], int arg_nb, + u32 *pattern, u32 default_pattern) +{ + unsigned long value; + + if (argc > arg_nb) { + if (strict_strtoul(argv[arg_nb], 16, &value) < 0) { + sprintf(string, "invalid %d parameter %s", + arg_nb, argv[arg_nb]); + return -1; + } + *pattern = value; + } else { + *pattern = default_pattern; + } + + return 0; +} + +static u32 check_addr(u32 addr, u32 value) +{ + u32 data = readl(addr); + + if (value != data) { + printf("0x%08x: 0x%08x <=> 0x%08x", addr, data, value); + data = readl(addr); + printf("(2nd read: 0x%08x)", data); + if (value == data) + printf("- read error"); + else + printf("- write error"); + printf("\n"); + return -1; + } + return 0; +} + +static int progress(u32 offset) +{ + if (!(offset & 0xFFFFFF)) { + putc('.'); + if (ctrlc()) { + printf("\ntest interrupted!\n"); + return 1; + } + } + return 0; +} + +static int test_loop_end(u32 *loop, u32 nb_loop, u32 progress) +{ + (*loop)++; + if (nb_loop && *loop >= nb_loop) + return 1; + if ((*loop) % progress) + return 0; + /* allow to interrupt the test only for progress step */ + if (ctrlc()) { + printf("test interrupted!\n"); + return 1; + } + printf("loop #%d\n", *loop); + return 0; +} + +/********************************************************************** + * + * Function: memTestDataBus() + * + * Description: Test the data bus wiring in a memory region by + * performing a walking 1's test at a fixed address + * within that region. The address is selected + * by the caller. + * + * Notes: + * + * Returns: 0 if the test succeeds. + * A non-zero result is the first pattern that failed. + * + **********************************************************************/ +static u32 databus(u32 *address) +{ + u32 pattern; + u32 read_value; + + /* Perform a walking 1's test at the given address. */ + for (pattern = 1; pattern != 0; pattern <<= 1) { + /* Write the test pattern. */ + writel(pattern, address); + + /* Read it back (immediately is okay for this test). */ + read_value = readl(address); + debug("%x: %x <=> %x\n", + (u32)address, read_value, pattern); + + if (read_value != pattern) + return pattern; + } + + return 0; +} + +/********************************************************************** + * + * Function: memTestAddressBus() + * + * Description: Test the address bus wiring in a memory region by + * performing a walking 1's test on the relevant bits + * of the address and checking for aliasing. This test + * will find single-bit address failures such as stuck + * -high, stuck-low, and shorted pins. The base address + * and size of the region are selected by the caller. + * + * Notes: For best results, the selected base address should + * have enough LSB 0's to guarantee single address bit + * changes. For example, to test a 64-Kbyte region, + * select a base address on a 64-Kbyte boundary. Also, + * select the region size as a power-of-two--if at all + * possible. + * + * Returns: NULL if the test succeeds. + * A non-zero result is the first address at which an + * aliasing problem was uncovered. By examining the + * contents of memory, it may be possible to gather + * additional information about the problem. + * + **********************************************************************/ +static u32 *addressbus(u32 *address, u32 nb_bytes) +{ + u32 mask = (nb_bytes / sizeof(u32) - 1); + u32 offset; + u32 test_offset; + u32 read_value; + + u32 pattern = 0xAAAAAAAA; + u32 antipattern = 0x55555555; + + /* Write the default pattern at each of the power-of-two offsets. */ + for (offset = 1; (offset & mask) != 0; offset <<= 1) + writel(pattern, &address[offset]); + + /* Check for address bits stuck high. */ + test_offset = 0; + writel(antipattern, &address[test_offset]); + + for (offset = 1; (offset & mask) != 0; offset <<= 1) { + read_value = readl(&address[offset]); + debug("%x: %x <=> %x\n", + (u32)&address[offset], read_value, pattern); + if (read_value != pattern) + return &address[offset]; + } + + writel(pattern, &address[test_offset]); + + /* Check for address bits stuck low or shorted. */ + for (test_offset = 1; (test_offset & mask) != 0; test_offset <<= 1) { + writel(antipattern, &address[test_offset]); + if (readl(&address[0]) != pattern) + return &address[test_offset]; + + for (offset = 1; (offset & mask) != 0; offset <<= 1) { + if (readl(&address[offset]) != pattern && + offset != test_offset) + return &address[test_offset]; + } + writel(pattern, &address[test_offset]); + } + + return NULL; +} + +/********************************************************************** + * + * Function: memTestDevice() + * + * Description: Test the integrity of a physical memory device by + * performing an increment/decrement test over the + * entire region. In the process every storage bit + * in the device is tested as a zero and a one. The + * base address and the size of the region are + * selected by the caller. + * + * Notes: + * + * Returns: NULL if the test succeeds. + * + * A non-zero result is the first address at which an + * incorrect value was read back. By examining the + * contents of memory, it may be possible to gather + * additional information about the problem. + * + **********************************************************************/ +static u32 *memdevice(u32 *address, u32 nb_bytes) +{ + u32 offset; + u32 nb_words = nb_bytes / sizeof(u32); + + u32 pattern; + u32 antipattern; + + puts("Fill with pattern"); + /* Fill memory with a known pattern. */ + for (pattern = 1, offset = 0; offset < nb_words; pattern++, offset++) { + writel(pattern, &address[offset]); + if (progress(offset)) + return NULL; + } + + puts("\nCheck and invert pattern"); + /* Check each location and invert it for the second pass. */ + for (pattern = 1, offset = 0; offset < nb_words; pattern++, offset++) { + if (readl(&address[offset]) != pattern) + return &address[offset]; + + antipattern = ~pattern; + writel(antipattern, &address[offset]); + if (progress(offset)) + return NULL; + } + + puts("\nCheck inverted pattern"); + /* Check each location for the inverted pattern and zero it. */ + for (pattern = 1, offset = 0; offset < nb_words; pattern++, offset++) { + antipattern = ~pattern; + if (readl(&address[offset]) != antipattern) + return &address[offset]; + if (progress(offset)) + return NULL; + } + printf("\n"); + + return NULL; +} + +static enum test_result databuswalk0(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + int i; + u32 loop = 0, nb_loop; + u32 addr; + u32 error = 0; + u32 data; + + if (get_nb_loop(string, argc, argv, 0, &nb_loop, 100)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%x\n", nb_loop, addr); + while (!error) { + for (i = 0; i < 32; i++) + writel(~(1 << i), addr + 4 * i); + for (i = 0; i < 32; i++) { + data = readl(addr + 4 * i); + if (~(1 << i) != data) { + error |= 1 << i; + debug("%x: error %x expected %x => error:%x\n", + addr + 4 * i, data, ~(1 << i), error); + } + } + if (test_loop_end(&loop, nb_loop, 1000)) + break; + for (i = 0; i < 32; i++) + writel(0, addr + 4 * i); + } + if (error) { + sprintf(string, "loop %d: error for bits 0x%x", + loop, error); + return TEST_FAILED; + } + sprintf(string, "no error for %d loops", loop); + return TEST_PASSED; +} + +static enum test_result databuswalk1(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + int i; + u32 loop = 0, nb_loop; + u32 addr; + u32 error = 0; + u32 data; + + if (get_nb_loop(string, argc, argv, 0, &nb_loop, 100)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + printf("running %d loops at 0x%x\n", nb_loop, addr); + while (!error) { + for (i = 0; i < 32; i++) + writel(1 << i, addr + 4 * i); + for (i = 0; i < 32; i++) { + data = readl(addr + 4 * i); + if ((1 << i) != data) { + error |= 1 << i; + debug("%x: error %x expected %x => error:%x\n", + addr + 4 * i, data, (1 << i), error); + } + } + if (test_loop_end(&loop, nb_loop, 1000)) + break; + for (i = 0; i < 32; i++) + writel(0, addr + 4 * i); + } + if (error) { + sprintf(string, "loop %d: error for bits 0x%x", + loop, error); + return TEST_FAILED; + } + sprintf(string, "no error for %d loops", loop); + return TEST_PASSED; +} + +static enum test_result test_databus(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr; + u32 error; + + if (get_addr(string, argc, argv, 0, &addr)) + return TEST_ERROR; + error = databus((u32 *)addr); + if (error) { + sprintf(string, "0x%x: error for bits 0x%x", + addr, error); + return TEST_FAILED; + } + sprintf(string, "address 0x%x", addr); + return TEST_PASSED; +} + +static enum test_result test_addressbus(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr; + u32 bufsize; + u32 error; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (!is_power_of_2(bufsize)) { + sprintf(string, "size 0x%x is not a power of 2", + (u32)bufsize); + return TEST_ERROR; + } + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + + error = (u32)addressbus((u32 *)addr, bufsize); + if (error) { + sprintf(string, "0x%x: error for address 0x%x", + addr, error); + return TEST_FAILED; + } + sprintf(string, "address 0x%x, size 0x%x", + addr, bufsize); + return TEST_PASSED; +} + +static enum test_result test_memdevice(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr; + size_t bufsize; + u32 error; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + error = (u32)memdevice((u32 *)addr, (unsigned long)bufsize); + if (error) { + sprintf(string, "0x%x: error for address 0x%x", + addr, error); + return TEST_FAILED; + } + sprintf(string, "address 0x%x, size 0x%x", + addr, bufsize); + return TEST_PASSED; +} + +/********************************************************************** + * + * Function: sso + * + * Description: Test the Simultaneous Switching Output. + * Verifies succes sive reads and writes to the same memory word, + * holding one bit constant while toggling all other data bits + * simultaneously + * => stress the data bus over an address range + * + * The CPU writes to each address in the given range. + * For each bit, first the CPU holds the bit at 1 while + * toggling the other bits, and then the CPU holds the bit at 0 + * while toggling the other bits. + * After each write, the CPU reads the address that was written + * to verify that it contains the correct data + * + **********************************************************************/ +static enum test_result test_sso(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + int i, j; + u32 addr, bufsize, remaining, offset; + u32 error = 0; + u32 data; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + + printf("running sso at 0x%x length 0x%x", addr, bufsize); + offset = addr; + remaining = bufsize; + while (remaining) { + for (i = 0; i < 32; i++) { + /* write pattern. */ + for (j = 0; j < 6; j++) { + switch (j) { + case 0: + case 2: + data = 1 << i; + break; + case 3: + case 5: + data = ~(1 << i); + break; + case 1: + data = ~0x0; + break; + case 4: + data = 0x0; + break; + } + + writel(data, offset); + error = check_addr(offset, data); + if (error) + goto end; + } + } + offset += 4; + remaining -= 4; + if (progress(offset << 7)) + goto end; + } + puts("\n"); + +end: + if (error) { + sprintf(string, "error for pattern 0x%x @0x%x", + data, offset); + return TEST_FAILED; + } + sprintf(string, "no error for sso at 0x%x length 0x%x", addr, bufsize); + return TEST_PASSED; +} + +/********************************************************************** + * + * Function: Random + * + * Description: Verifies r/w with pseudo-ramdom value on one region + * + write the region (individual access) + * + memcopy to the 2nd region (try to use burst) + * + verify the 2 regions + * + **********************************************************************/ +static enum test_result test_random(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr, offset, value = 0; + size_t bufsize; + u32 loop = 0, nb_loop; + u32 error = 0; + unsigned int seed; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%x\n", nb_loop, addr); + while (!error) { + seed = rand(); + for (offset = addr; offset < addr + bufsize; offset += 4) + writel(rand(), offset); + + memcpy((void *)addr + bufsize, (void *)addr, bufsize); + + srand(seed); + for (offset = addr; offset < addr + 2 * bufsize; offset += 4) { + if (offset == (addr + bufsize)) + srand(seed); + value = rand(); + error = check_addr(offset, value); + if (error) + break; + if (progress(offset)) + return TEST_FAILED; + } + if (test_loop_end(&loop, nb_loop, 100)) + break; + } + + if (error) { + sprintf(string, + "loop %d: error for address 0x%x: 0x%x expected 0x%x", + loop, offset, readl(offset), value); + return TEST_FAILED; + } + sprintf(string, "no error for %d loops, size 0x%x", + loop, bufsize); + return TEST_PASSED; +} + +/********************************************************************** + * + * Function: noise + * + * Description: Verifies r/w while forcing switching of all data bus lines. + * optimised 4 iteration write/read/write/read cycles... + * for pattern and inversed pattern + * + **********************************************************************/ +void do_noise(u32 addr, u32 pattern, u32 *result) +{ + __asm__("push {R0-R11}"); + __asm__("mov r0, %0" : : "r" (addr)); + __asm__("mov r1, %0" : : "r" (pattern)); + __asm__("mov r11, %0" : : "r" (result)); + + __asm__("mvn r2, r1"); + + __asm__("str r1, [r0]"); + __asm__("ldr r3, [r0]"); + __asm__("str r2, [r0]"); + __asm__("ldr r4, [r0]"); + + __asm__("str r1, [r0]"); + __asm__("ldr r5, [r0]"); + __asm__("str r2, [r0]"); + __asm__("ldr r6, [r0]"); + + __asm__("str r1, [r0]"); + __asm__("ldr r7, [r0]"); + __asm__("str r2, [r0]"); + __asm__("ldr r8, [r0]"); + + __asm__("str r1, [r0]"); + __asm__("ldr r9, [r0]"); + __asm__("str r2, [r0]"); + __asm__("ldr r10, [r0]"); + + __asm__("stmia R11!, {R3-R10}"); + + __asm__("pop {R0-R11}"); +} + +static enum test_result test_noise(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr, pattern; + u32 result[8]; + int i; + enum test_result res = TEST_PASSED; + + if (get_pattern(string, argc, argv, 0, &pattern, 0xFFFFFFFF)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 1, &addr)) + return TEST_ERROR; + + printf("running noise for 0x%x at 0x%x\n", pattern, addr); + + do_noise(addr, pattern, result); + + for (i = 0; i < 0x8;) { + if (check_addr((u32)&result[i++], pattern)) + res = TEST_FAILED; + if (check_addr((u32)&result[i++], ~pattern)) + res = TEST_FAILED; + } + + return res; +} + +/********************************************************************** + * + * Function: noise_burst + * + * Description: Verifies r/w while forcing switching of all data bus lines. + * optimised write loop witrh store multiple to use burst + * for pattern and inversed pattern + * + **********************************************************************/ +void do_noise_burst(u32 addr, u32 pattern, size_t bufsize) +{ + __asm__("push {R0-R9}"); + __asm__("mov r0, %0" : : "r" (addr)); + __asm__("mov r1, %0" : : "r" (pattern)); + __asm__("mov r9, %0" : : "r" (bufsize)); + + __asm__("mvn r2, r1"); + __asm__("mov r3, r1"); + __asm__("mov r4, r2"); + __asm__("mov r5, r1"); + __asm__("mov r6, r2"); + __asm__("mov r7, r1"); + __asm__("mov r8, r2"); + + __asm__("loop1:"); + __asm__("stmia R0!, {R1-R8}"); + __asm__("stmia R0!, {R1-R8}"); + __asm__("stmia R0!, {R1-R8}"); + __asm__("stmia R0!, {R1-R8}"); + __asm__("subs r9, r9, #128"); + __asm__("bge loop1"); + __asm__("pop {R0-R9}"); +} + +/* chunk size enough to allow interruption with Ctrl-C*/ +#define CHUNK_SIZE 0x8000000 +static enum test_result test_noise_burst(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 addr, offset, pattern; + size_t bufsize, remaining, size; + int i; + enum test_result res = TEST_PASSED; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_pattern(string, argc, argv, 1, &pattern, 0xFFFFFFFF)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running noise burst for 0x%x at 0x%x + 0x%x", + pattern, addr, bufsize); + + offset = addr; + remaining = bufsize; + size = CHUNK_SIZE; + while (remaining) { + if (remaining < size) + size = remaining; + do_noise_burst(offset, pattern, size); + remaining -= size; + offset += size; + if (progress(offset)) { + res = TEST_FAILED; + goto end; + } + } + puts("\ncheck buffer"); + for (i = 0; i < bufsize;) { + if (check_addr(addr + i, pattern)) + res = TEST_FAILED; + i += 4; + if (check_addr(addr + i, ~pattern)) + res = TEST_FAILED; + i += 4; + if (progress(i)) { + res = TEST_FAILED; + goto end; + } + } +end: + puts("\n"); + return res; +} + +/********************************************************************** + * + * Function: pattern test + * + * Description: optimized loop for read/write pattern (array of 8 u32) + * + **********************************************************************/ +#define PATTERN_SIZE 8 +static enum test_result test_loop(const u32 *pattern, u32 *address, + const u32 bufsize) +{ + int i; + int j; + enum test_result res = TEST_PASSED; + u32 *offset, testsize, remaining; + + offset = address; + remaining = bufsize; + while (remaining) { + testsize = bufsize > 0x1000000 ? 0x1000000 : bufsize; + + __asm__("push {R0-R10}"); + __asm__("mov r0, %0" : : "r" (pattern)); + __asm__("mov r1, %0" : : "r" (offset)); + __asm__("mov r2, %0" : : "r" (testsize)); + __asm__("ldmia r0!, {R3-R10}"); + + __asm__("loop2:"); + __asm__("stmia r1!, {R3-R10}"); + __asm__("stmia r1!, {R3-R10}"); + __asm__("stmia r1!, {R3-R10}"); + __asm__("stmia r1!, {R3-R10}"); + __asm__("subs r2, r2, #8"); + __asm__("bge loop2"); + __asm__("pop {R0-R10}"); + + offset += testsize; + remaining -= testsize; + if (progress((u32)offset)) { + res = TEST_FAILED; + goto end; + } + } + + puts("\ncheck buffer"); + for (i = 0; i < bufsize; i += PATTERN_SIZE * 4) { + for (j = 0; j < PATTERN_SIZE; j++, address++) + if (check_addr((u32)address, pattern[j])) { + res = TEST_FAILED; + goto end; + } + if (progress(i)) { + res = TEST_FAILED; + goto end; + } + } + +end: + puts("\n"); + return res; +} + +const u32 pattern_div1_x16[PATTERN_SIZE] = { + 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, + 0x0000FFFF, 0x0000FFFF, 0x0000FFFF, 0x0000FFFF +}; + +const u32 pattern_div2_x16[PATTERN_SIZE] = { + 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000, + 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000 +}; + +const u32 pattern_div4_x16[PATTERN_SIZE] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000 +}; + +const u32 pattern_div4_x32[PATTERN_SIZE] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + +const u32 pattern_mostly_zero_x16[PATTERN_SIZE] = { + 0x00000000, 0x00000000, 0x00000000, 0x0000FFFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + +const u32 pattern_mostly_zero_x32[PATTERN_SIZE] = { + 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 +}; + +const u32 pattern_mostly_one_x16[PATTERN_SIZE] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +const u32 pattern_mostly_one_x32[PATTERN_SIZE] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +#define NB_PATTERN 5 +static enum test_result test_freq_pattern(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + const u32 * const patterns_x16[NB_PATTERN] = { + pattern_div1_x16, + pattern_div2_x16, + pattern_div4_x16, + pattern_mostly_zero_x16, + pattern_mostly_one_x16, + }; + const u32 * const patterns_x32[NB_PATTERN] = { + pattern_div2_x16, + pattern_div4_x16, + pattern_div4_x32, + pattern_mostly_zero_x32, + pattern_mostly_one_x32 + }; + const char *patterns_comments[NB_PATTERN] = { + "switching at frequency F/1", + "switching at frequency F/2", + "switching at frequency F/4", + "mostly zero", + "mostly one" + }; + + enum test_result res = TEST_PASSED, pattern_res; + int i, bus_width; + const u32 **patterns; + u32 bufsize; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + + switch (readl(&ctl->mstr) & DDRCTRL_MSTR_DATA_BUS_WIDTH_MASK) { + case DDRCTRL_MSTR_DATA_BUS_WIDTH_HALF: + case DDRCTRL_MSTR_DATA_BUS_WIDTH_QUARTER: + bus_width = 16; + break; + default: + bus_width = 32; + break; + } + + printf("running test pattern at 0x%08x length 0x%x width = %d\n", + STM32_DDR_BASE, bufsize, bus_width); + + patterns = + (const u32 **)(bus_width == 16 ? patterns_x16 : patterns_x32); + + for (i = 0; i < NB_PATTERN; i++) { + printf("test data pattern %s:", patterns_comments[i]); + pattern_res = test_loop(patterns[i], (u32 *)STM32_DDR_BASE, + bufsize); + if (pattern_res != TEST_PASSED) { + printf("Failed\n"); + return pattern_res; + } + printf("Passed\n"); + } + + return res; +} + +/********************************************************************** + * + * Function: pattern test with size + * + * Description: loop for write pattern + * + **********************************************************************/ + +static enum test_result test_loop_size(const u32 *pattern, u32 size, + u32 *address, + const u32 bufsize) +{ + int i, j; + enum test_result res = TEST_PASSED; + u32 *p = address; + + for (i = 0; i < bufsize; i += size * 4) { + for (j = 0; j < size ; j++, p++) + *p = pattern[j]; + if (progress(i)) { + res = TEST_FAILED; + goto end; + } + } + + puts("\ncheck buffer"); + p = address; + for (i = 0; i < bufsize; i += size * 4) { + for (j = 0; j < size; j++, p++) + if (check_addr((u32)p, pattern[j])) { + res = TEST_FAILED; + goto end; + } + if (progress(i)) { + res = TEST_FAILED; + goto end; + } + } + +end: + puts("\n"); + return res; +} + +static enum test_result test_checkboard(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr; + int i; + + u32 checkboard[2] = {0x55555555, 0xAAAAAAAA}; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 0; i < 2; i++) { + res = test_loop_size(checkboard, 2, (u32 *)addr, + bufsize); + if (res) + return res; + checkboard[0] = ~checkboard[0]; + checkboard[1] = ~checkboard[1]; + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +static enum test_result test_blockseq(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr, value; + int i; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 0; i < 256; i++) { + value = i | i << 8 | i << 16 | i << 24; + printf("pattern = %08x", value); + res = test_loop_size(&value, 1, (u32 *)addr, bufsize); + if (res != TEST_PASSED) + return res; + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +static enum test_result test_walkbit0(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr, value; + int i; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 0; i < 64; i++) { + if (i < 32) + value = 1 << i; + else + value = 1 << (63 - i); + + printf("pattern = %08x", value); + res = test_loop_size(&value, 1, (u32 *)addr, bufsize); + if (res != TEST_PASSED) + return res; + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +static enum test_result test_walkbit1(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr, value; + int i; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 0; i < 64; i++) { + if (i < 32) + value = ~(1 << i); + else + value = ~(1 << (63 - i)); + + printf("pattern = %08x", value); + res = test_loop_size(&value, 1, (u32 *)addr, bufsize); + if (res != TEST_PASSED) + return res; + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +/* + * try to catch bad bits which are dependent on the current values of + * surrounding bits in either the same word32 + */ +static enum test_result test_bitspread(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr, bitspread[4]; + int i, j; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 1; i < 32; i++) { + for (j = 0; j < i; j++) { + if (i < 32) + bitspread[0] = (1 << i) | (1 << j); + else + bitspread[0] = (1 << (63 - i)) | + (1 << (63 - j)); + bitspread[1] = bitspread[0]; + bitspread[2] = ~bitspread[0]; + bitspread[3] = ~bitspread[0]; + printf("pattern = %08x", bitspread[0]); + + res = test_loop_size(bitspread, 4, (u32 *)addr, + bufsize); + if (res != TEST_PASSED) + return res; + } + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +static enum test_result test_bitflip(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED; + u32 bufsize, nb_loop, loop = 0, addr; + int i; + + u32 bitflip[4]; + + if (get_bufsize(string, argc, argv, 0, &bufsize, 4 * 1024)) + return TEST_ERROR; + if (get_nb_loop(string, argc, argv, 1, &nb_loop, 1)) + return TEST_ERROR; + if (get_addr(string, argc, argv, 2, &addr)) + return TEST_ERROR; + + printf("running %d loops at 0x%08x length 0x%x\n", + nb_loop, addr, bufsize); + while (1) { + for (i = 0; i < 32; i++) { + bitflip[0] = 1 << i; + bitflip[1] = bitflip[0]; + bitflip[2] = ~bitflip[0]; + bitflip[3] = bitflip[2]; + printf("pattern = %08x", bitflip[0]); + + res = test_loop_size(bitflip, 4, (u32 *)addr, bufsize); + if (res != TEST_PASSED) + return res; + } + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + sprintf(string, "no error for %d loops at 0x%08x length 0x%x", + loop, addr, bufsize); + + return res; +} + +/********************************************************************** + * + * Function: infinite read access to DDR + * + * Description: continuous read the same pattern at the same address + * + **********************************************************************/ +static enum test_result test_read(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 *addr; + u32 data; + u32 loop = 0; + bool random = false; + + if (get_addr(string, argc, argv, 0, (u32 *)&addr)) + return TEST_ERROR; + + if ((u32)addr == ADDR_INVALID) { + printf("random "); + random = true; + } + + printf("running at 0x%08x\n", (u32)addr); + + while (1) { + if (random) + addr = (u32 *)(STM32_DDR_BASE + + (rand() & (STM32_DDR_SIZE - 1) & ~0x3)); + data = readl(addr); + if (test_loop_end(&loop, 0, 1000)) + break; + } + sprintf(string, "0x%x: %x", (u32)addr, data); + + return TEST_PASSED; +} + +/********************************************************************** + * + * Function: infinite write access to DDR + * + * Description: continuous write the same pattern at the same address + * + **********************************************************************/ +static enum test_result test_write(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + u32 *addr; + u32 data = 0xA5A5AA55; + u32 loop = 0; + bool random = false; + + if (get_addr(string, argc, argv, 0, (u32 *)&addr)) + return TEST_ERROR; + + if ((u32)addr == ADDR_INVALID) { + printf("random "); + random = true; + } + + printf("running at 0x%08x\n", (u32)addr); + + while (1) { + if (random) { + addr = (u32 *)(STM32_DDR_BASE + + (rand() & (STM32_DDR_SIZE - 1) & ~0x3)); + data = rand(); + } + writel(data, addr); + if (test_loop_end(&loop, 0, 1000)) + break; + } + sprintf(string, "0x%x: %x", (u32)addr, data); + + return TEST_PASSED; +} + +#define NB_TEST_INFINITE 2 +static enum test_result test_all(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, int argc, char *argv[]) +{ + enum test_result res = TEST_PASSED, result; + int i, nb_error = 0; + u32 loop = 0, nb_loop; + + if (get_nb_loop(string, argc, argv, 0, &nb_loop, 1)) + return TEST_ERROR; + + while (!nb_error) { + /* execute all the test except the lasts which are infinite */ + for (i = 1; i < test_nb - NB_TEST_INFINITE; i++) { + printf("execute %d:%s\n", (int)i, test[i].name); + result = test[i].fct(ctl, phy, string, 0, NULL); + printf("result %d:%s = ", (int)i, test[i].name); + if (result != TEST_PASSED) { + nb_error++; + res = TEST_FAILED; + puts("Failed"); + } else { + puts("Passed"); + } + puts("\n\n"); + } + printf("loop %d: %d/%d test failed\n\n\n", + loop + 1, nb_error, test_nb - NB_TEST_INFINITE); + if (test_loop_end(&loop, nb_loop, 1)) + break; + } + if (res != TEST_PASSED) { + sprintf(string, "loop %d: %d/%d test failed", loop, nb_error, + test_nb - NB_TEST_INFINITE); + } else { + sprintf(string, "loop %d: %d tests passed", loop, + test_nb - NB_TEST_INFINITE); + } + return res; +} + +/**************************************************************** + * TEST Description + ****************************************************************/ + +const struct test_desc test[] = { + {test_all, "All", "[loop]", "Execute all tests", 1 }, + {test_databus, "Simple DataBus", "[addr]", + "Verifies each data line by walking 1 on fixed address", + 1 + }, + {databuswalk0, "DataBusWalking0", "[loop] [addr]", + "Verifies each data bus signal can be driven low (32 word burst)", + 2 + }, + {databuswalk1, "DataBusWalking1", "[loop] [addr]", + "Verifies each data bus signal can be driven high (32 word burst)", + 2 + }, + {test_addressbus, "AddressBus", "[size] [addr]", + "Verifies each relevant bits of the address and checking for aliasing", + 2 + }, + {test_memdevice, "MemDevice", "[size] [addr]", + "Test the integrity of a physical memory (test every storage bit in the region)", + 2 + }, + {test_sso, "SimultaneousSwitchingOutput", "[size] [addr] ", + "Stress the data bus over an address range", + 2 + }, + {test_noise, "Noise", "[pattern] [addr]", + "Verifies r/w while forcing switching of all data bus lines.", + 3 + }, + {test_noise_burst, "NoiseBurst", "[size] [pattern] [addr]", + "burst transfers while forcing switching of the data bus lines", + 3 + }, + {test_random, "Random", "[size] [loop] [addr]", + "Verifies r/w and memcopy(burst for pseudo random value.", + 3 + }, + {test_freq_pattern, "FrequencySelectivePattern ", "[size]", + "write & test patterns: Mostly Zero, Mostly One and F/n", + 1 + }, + {test_blockseq, "BlockSequential", "[size] [loop] [addr]", + "test incremental pattern", + 3 + }, + {test_checkboard, "Checkerboard", "[size] [loop] [addr]", + "test checker pattern", + 3 + }, + {test_bitspread, "BitSpread", "[size] [loop] [addr]", + "test Bit Spread pattern", + 3 + }, + {test_bitflip, "BitFlip", "[size] [loop] [addr]", + "test Bit Flip pattern", + 3 + }, + {test_walkbit0, "WalkingOnes", "[size] [loop] [addr]", + "test Walking Ones pattern", + 3 + }, + {test_walkbit1, "WalkingZeroes", "[size] [loop] [addr]", + "test Walking Zeroes pattern", + 3 + }, + /* need to the the 2 last one (infinite) : skipped for test all */ + {test_read, "infinite read", "[addr]", + "basic test : infinite read access", 1}, + {test_write, "infinite write", "[addr]", + "basic test : infinite write access", 1}, +}; + +const int test_nb = ARRAY_SIZE(test); diff --git a/drivers/ram/stm32mp1/stm32mp1_tests.h b/drivers/ram/stm32mp1/stm32mp1_tests.h new file mode 100644 index 0000000000..8436780790 --- /dev/null +++ b/drivers/ram/stm32mp1/stm32mp1_tests.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ +/* + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + */ + +#ifndef _RAM_STM32MP1_TESTS_H_ +#define _RAM_STM32MP1_TESTS_H_ + +#include "stm32mp1_ddr_regs.h" + +enum test_result { + TEST_PASSED, + TEST_FAILED, + TEST_ERROR +}; + +struct test_desc { + enum test_result (*fct)(struct stm32mp1_ddrctl *ctl, + struct stm32mp1_ddrphy *phy, + char *string, + int argc, char *argv[]); + const char *name; + const char *usage; + const char *help; + u8 max_args; +}; + +extern const struct test_desc test[]; +extern const int test_nb; + +#endif -- 2.25.1