flash: flash_shell: Add flash read/write/erase speed test commands

Adds commands which can be used for timing flash device tasks and
outputting a rough speed.

Signed-off-by: Jamie McCrae <jamie.mccrae@nordicsemi.no>
This commit is contained in:
Jamie McCrae 2023-01-25 13:13:49 +00:00 committed by Fabio Baltieri
commit 3c9d529899
2 changed files with 285 additions and 5 deletions

View file

@ -51,6 +51,26 @@ config FLASH_SHELL
Enable the flash shell with flash related commands such as test, Enable the flash shell with flash related commands such as test,
write, read and erase. write, read and erase.
if FLASH_SHELL
config FLASH_SHELL_TEST_COMMANDS
bool "Flash read/write/erase test commands"
select CBPRINTF_FP_SUPPORT
help
Enable additional flash shell commands for performing
read/write/erase tests with speed output.
config FLASH_SHELL_BUFFER_SIZE
hex "Flash shell buffer size"
default 0x4000 if FLASH_SHELL_TEST_COMMANDS
default 0x1000
range 0x400 0x1000000
help
Size of the buffer used for flash commands, will determine the
maximum size that can be used with a read/write test.
endif # FLASH_SHELL
config FLASH_PAGE_LAYOUT config FLASH_PAGE_LAYOUT
bool "API for retrieving the layout of pages" bool "API for retrieving the layout of pages"
depends on FLASH_HAS_PAGE_LAYOUT depends on FLASH_HAS_PAGE_LAYOUT

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017 Nordic Semiconductor ASA * Copyright (c) 2017-2023 Nordic Semiconductor ASA
* Copyright (c) 2018 Intel Corporation * Copyright (c) 2018 Intel Corporation
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
@ -17,7 +17,6 @@
/* Buffer is only needed for bytes that follow command and offset */ /* Buffer is only needed for bytes that follow command and offset */
#define BUF_ARRAY_CNT (CONFIG_SHELL_ARGC_MAX - 2) #define BUF_ARRAY_CNT (CONFIG_SHELL_ARGC_MAX - 2)
#define TEST_ARR_SIZE 0x1000
/* This only issues compilation error when it would not be possible /* This only issues compilation error when it would not be possible
* to extract at least one byte from command line arguments, yet * to extract at least one byte from command line arguments, yet
@ -29,7 +28,7 @@ BUILD_ASSERT(BUF_ARRAY_CNT >= 1);
static const struct device *const zephyr_flash_controller = static const struct device *const zephyr_flash_controller =
DEVICE_DT_GET_OR_NULL(DT_CHOSEN(zephyr_flash_controller)); DEVICE_DT_GET_OR_NULL(DT_CHOSEN(zephyr_flash_controller));
static uint8_t __aligned(4) test_arr[TEST_ARR_SIZE]; static uint8_t __aligned(4) test_arr[CONFIG_FLASH_SHELL_BUFFER_SIZE];
static int parse_helper(const struct shell *shell, size_t *argc, static int parse_helper(const struct shell *shell, size_t *argc,
char **argv[], const struct device * *flash_dev, char **argv[], const struct device * *flash_dev,
@ -216,9 +215,9 @@ static int cmd_test(const struct shell *shell, size_t argc, char *argv[])
size = strtoul(argv[2], NULL, 16); size = strtoul(argv[2], NULL, 16);
repeat = strtoul(argv[3], NULL, 16); repeat = strtoul(argv[3], NULL, 16);
if (size > TEST_ARR_SIZE) { if (size > CONFIG_FLASH_SHELL_BUFFER_SIZE) {
shell_error(shell, "<size> must be at most 0x%x.", shell_error(shell, "<size> must be at most 0x%x.",
TEST_ARR_SIZE); CONFIG_FLASH_SHELL_BUFFER_SIZE);
return -EINVAL; return -EINVAL;
} }
@ -255,6 +254,251 @@ static int cmd_test(const struct shell *shell, size_t argc, char *argv[])
return result; return result;
} }
#ifdef CONFIG_FLASH_SHELL_TEST_COMMANDS
const static uint8_t speed_types[][4] = { "B", "KiB", "MiB", "GiB" };
const static uint32_t speed_divisor = 1024;
static int read_write_erase_validate(const struct shell *sh, size_t argc, char *argv[],
uint32_t *size, uint32_t *repeat)
{
if (argc < 4) {
shell_error(sh, "Missing parameters: <device> <offset> <size> <repeat>");
return -EINVAL;
}
*size = strtoul(argv[2], NULL, 0);
*repeat = strtoul(argv[3], NULL, 0);
if (*size == 0 || *size > CONFIG_FLASH_SHELL_BUFFER_SIZE) {
shell_error(sh, "<size> must be between 0x1 and 0x%x.",
CONFIG_FLASH_SHELL_BUFFER_SIZE);
return -EINVAL;
}
if (*repeat == 0 || *repeat > 10) {
shell_error(sh, "<repeat> must be between 1 and 10.");
return -EINVAL;
}
return 0;
}
static void speed_output(const struct shell *sh, uint64_t total_time, double loops, double size)
{
double time_per_loop = (double)total_time / loops;
double throughput = size;
uint8_t speed_index = 0;
if (time_per_loop > 0) {
throughput /= (time_per_loop / 1000.0);
}
while (throughput >= (double)speed_divisor && speed_index < ARRAY_SIZE(speed_types)) {
throughput /= (double)speed_divisor;
++speed_index;
}
shell_print(sh, "Total: %llums, Per loop: ~%.0fms, Speed: ~%.1f%sps",
total_time, time_per_loop, throughput, speed_types[speed_index]);
}
static int cmd_read_test(const struct shell *sh, size_t argc, char *argv[])
{
const struct device *flash_dev;
uint32_t repeat;
int result;
uint32_t addr;
uint32_t size;
uint64_t start_time;
uint64_t loop_time;
uint64_t total_time = 0;
uint32_t loops = 0;
result = parse_helper(sh, &argc, &argv, &flash_dev, &addr);
if (result) {
return result;
}
result = read_write_erase_validate(sh, argc, argv, &size, &repeat);
if (result) {
return result;
}
while (repeat--) {
start_time = k_uptime_get();
result = flash_read(flash_dev, addr, test_arr, size);
loop_time = k_uptime_delta(&start_time);
if (result) {
shell_error(sh, "Read failed: %d", result);
break;
}
++loops;
total_time += loop_time;
shell_print(sh, "Loop #%u done in %llums.", loops, loop_time);
}
if (result == 0) {
speed_output(sh, total_time, (double)loops, (double)size);
}
return result;
}
static int cmd_write_test(const struct shell *sh, size_t argc, char *argv[])
{
const struct device *flash_dev;
uint32_t repeat;
int result;
uint32_t addr;
uint32_t size;
uint64_t start_time;
uint64_t loop_time;
uint64_t total_time = 0;
uint32_t loops = 0;
result = parse_helper(sh, &argc, &argv, &flash_dev, &addr);
if (result) {
return result;
}
result = read_write_erase_validate(sh, argc, argv, &size, &repeat);
if (result) {
return result;
}
for (uint32_t i = 0; i < size; i++) {
test_arr[i] = (uint8_t)i;
}
while (repeat--) {
start_time = k_uptime_get();
result = flash_write(flash_dev, addr, test_arr, size);
loop_time = k_uptime_delta(&start_time);
if (result) {
shell_error(sh, "Write failed: %d", result);
break;
}
++loops;
total_time += loop_time;
shell_print(sh, "Loop #%u done in %llu ticks.", loops, loop_time);
}
if (result == 0) {
speed_output(sh, total_time, (double)loops, (double)size);
}
return result;
}
static int cmd_erase_test(const struct shell *sh, size_t argc, char *argv[])
{
const struct device *flash_dev;
uint32_t repeat;
int result;
uint32_t addr;
uint32_t size;
uint64_t start_time;
uint64_t loop_time;
uint64_t total_time = 0;
uint32_t loops = 0;
result = parse_helper(sh, &argc, &argv, &flash_dev, &addr);
if (result) {
return result;
}
result = read_write_erase_validate(sh, argc, argv, &size, &repeat);
if (result) {
return result;
}
for (uint32_t i = 0; i < size; i++) {
test_arr[i] = (uint8_t)i;
}
while (repeat--) {
start_time = k_uptime_get();
result = flash_erase(flash_dev, addr, size);
loop_time = k_uptime_delta(&start_time);
if (result) {
shell_error(sh, "Erase failed: %d", result);
break;
}
++loops;
total_time += loop_time;
shell_print(sh, "Loop #%u done in %llums.", loops, loop_time);
}
if (result == 0) {
speed_output(sh, total_time, (double)loops, (double)size);
}
return result;
}
static int cmd_erase_write_test(const struct shell *sh, size_t argc, char *argv[])
{
const struct device *flash_dev;
uint32_t repeat;
int result_erase = 0;
int result_write = 0;
uint32_t addr;
uint32_t size;
uint64_t start_time;
uint64_t loop_time;
uint64_t total_time = 0;
uint32_t loops = 0;
result_erase = parse_helper(sh, &argc, &argv, &flash_dev, &addr);
if (result_erase) {
return result_erase;
}
result_erase = read_write_erase_validate(sh, argc, argv, &size, &repeat);
if (result_erase) {
return result_erase;
}
for (uint32_t i = 0; i < size; i++) {
test_arr[i] = (uint8_t)i;
}
while (repeat--) {
start_time = k_uptime_get();
result_erase = flash_erase(flash_dev, addr, size);
result_write = flash_write(flash_dev, addr, test_arr, size);
loop_time = k_uptime_delta(&start_time);
if (result_erase) {
shell_error(sh, "Erase failed: %d", result_erase);
break;
}
if (result_write) {
shell_error(sh, "Write failed: %d", result_write);
break;
}
++loops;
total_time += loop_time;
shell_print(sh, "Loop #%u done in %llums.", loops, loop_time);
}
if (result_erase == 0 && result_write == 0) {
speed_output(sh, total_time, (double)loops, (double)size);
}
return (result_erase != 0 ? result_erase : result_write);
}
#endif
static int set_bypass(const struct shell *sh, shell_bypass_cb_t bypass) static int set_bypass(const struct shell *sh, shell_bypass_cb_t bypass)
{ {
static bool in_use; static bool in_use;
@ -462,6 +706,22 @@ SHELL_STATIC_SUBCMD_SET_CREATE(flash_cmds,
SHELL_CMD_ARG(page_info, &dsub_device_name, SHELL_CMD_ARG(page_info, &dsub_device_name,
"[<device>] <address>", "[<device>] <address>",
cmd_page_info, 2, 1), cmd_page_info, 2, 1),
#ifdef CONFIG_FLASH_SHELL_TEST_COMMANDS
SHELL_CMD_ARG(read_test, &dsub_device_name,
"[<device>] <address> <size> <repeat count>",
cmd_read_test, 4, 1),
SHELL_CMD_ARG(write_test, &dsub_device_name,
"[<device>] <address> <size> <repeat count>",
cmd_write_test, 4, 1),
SHELL_CMD_ARG(erase_test, &dsub_device_name,
"[<device>] <address> <size> <repeat count>",
cmd_erase_test, 4, 1),
SHELL_CMD_ARG(erase_write_test, &dsub_device_name,
"[<device>] <address> <size> <repeat count>",
cmd_erase_write_test, 4, 1),
#endif
SHELL_SUBCMD_SET_END SHELL_SUBCMD_SET_END
); );