[PATCH] shell: Add blktest command.
Christian Mauderer
christian.mauderer at embedded-brains.de
Fri Jul 28 06:31:56 UTC 2017
From: Christian Mauderer <Christian.Mauderer at embedded-brains.de>
This command writes a test pattern (address + start value) to a raw
block device. After that it reads the pattern back and verifies it's
content. That is useful when developing block device drivers to verify
the low level read and write operations.
---
cpukit/libmisc/Makefile.am | 2 +-
cpukit/libmisc/shell/main_blktest.c | 394 ++++++++++++++++++++++++++++++++++++
cpukit/libmisc/shell/shellconfig.h | 1 +
3 files changed, 396 insertions(+), 1 deletion(-)
create mode 100644 cpukit/libmisc/shell/main_blktest.c
diff --git a/cpukit/libmisc/Makefile.am b/cpukit/libmisc/Makefile.am
index 6772dd1673..668ee95e04 100644
--- a/cpukit/libmisc/Makefile.am
+++ b/cpukit/libmisc/Makefile.am
@@ -110,7 +110,7 @@ libshell_a_SOURCES = shell/cat_file.c shell/cmds.c shell/internal.h \
shell/main_setenv.c shell/main_getenv.c shell/main_unsetenv.c \
shell/main_mkrfs.c shell/main_debugrfs.c shell/main_df.c \
shell/main_lsof.c shell/main_edit.c \
- shell/main_blkstats.c shell/main_rtrace.c \
+ shell/main_blkstats.c shell/main_blktest.c shell/main_rtrace.c \
shell/shell-wait-for-input.c
libshell_a_SOURCES += shell/main_cmdls.c
libshell_a_SOURCES += shell/main_cmdchown.c
diff --git a/cpukit/libmisc/shell/main_blktest.c b/cpukit/libmisc/shell/main_blktest.c
new file mode 100644
index 0000000000..c48c3821f3
--- /dev/null
+++ b/cpukit/libmisc/shell/main_blktest.c
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) 2017 embedded brains GmbH. All rights reserved.
+ *
+ * embedded brains GmbH
+ * Dornierstr. 4
+ * 82178 Puchheim
+ * Germany
+ * <rtems at embedded-brains.de>
+ *
+ * The license and distribution terms for this file may be
+ * found in the file LICENSE in this distribution or at
+ * http://www.rtems.org/license/LICENSE.
+ */
+
+#if HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include <rtems/blkdev.h>
+#include <rtems/shell.h>
+#include <rtems/shellconfig.h>
+
+#define TOSTRING_HELPER(a) #a
+#define TOSTRING(a) TOSTRING_HELPER(a)
+
+#define DEFAULT_BLK_SIZE 0
+#define DEFAULT_TEST_AREA 0x100000
+#define DEFAULT_RW_CHUNK_SIZE 4096
+
+static const char rtems_shell_blktest_usage [] =
+ "block device test utility\n"
+ "\n"
+ "blktest [-b BLK_SIZE] [-t TEST_AREA] \\\n"
+ " [-w WR_SIZE] [-r RD_SIZE] \\\n"
+ " [-p PATTERN_START] [-v] BLK_DEV\n"
+ "\n"
+ "**** ATTENTION ****\n"
+ "THIS UTILITY IS DESTRUCTIVE.\n"
+ "All data on the device under test will be lost.\n"
+ "\n"
+ "option values (numeric values support the suffixes K, M, G):\n"
+ " BLK_DEV: path to the block device to test like '/dev/mmcsd-0-0'\n"
+ " BLK_SIZE: Block size to set the device to in Byte.\n"
+ " Default: Unchanged.\n"
+ " WR_SIZE: Size of the chunks that will be written in Byte\n"
+ " (32 Bit aligned). Default: " TOSTRING(DEFAULT_RW_CHUNK_SIZE) "\n"
+ " RD_SIZE: Size of the chunks that will be read in Byte\n"
+ " (32 Bit aligned). Default: " TOSTRING(DEFAULT_RW_CHUNK_SIZE) "\n"
+ " TEST_AREA: Test area size in Byte. Default: "
+ TOSTRING(DEFAULT_TEST_AREA) "\n"
+ " PATTERN_START: Some random number that will be used for\n"
+ " generating the pattern in the block device.\n"
+ " Default: Based on system uptime.\n"
+ ;
+
+#define PATTERN_SIZE (sizeof(uint32_t))
+
+static uint32_t get_pattern_from_offset(off_t offset, uint32_t random)
+{
+ return ((uint32_t) offset) + random;
+}
+
+/*
+ * Read @a rd_chunk_size bytes from the file @a fd at position @a pos. The @a
+ * buffer will be used to read the pattern from the file and check the result.
+ */
+static int check_block(
+ int fd,
+ off_t pos,
+ size_t rd_chunk_size,
+ void *buffer,
+ uint32_t random,
+ bool verbose
+)
+{
+ ssize_t ret;
+ int err = 0;
+ size_t i;
+
+ ret = pread(fd, buffer, rd_chunk_size, pos);
+ if (ret == -1) {
+ warn("error: read failed");
+ return 1;
+ }
+ if ((size_t)ret != rd_chunk_size) {
+ warn("error: Not enough data read");
+ return 1;
+ }
+
+ for (i = 0; i < rd_chunk_size; i += PATTERN_SIZE) {
+ uint32_t pattern = get_pattern_from_offset(pos + i, random);
+ uint32_t actual = *(uint32_t *)(buffer + i);
+
+ if (actual != pattern) {
+ if (verbose) {
+ printf("error: mismatch at position 0x%08llx: is 0x%08lx but should be 0x%08lx\n",
+ pos + i, actual, pattern);
+ }
+ err += 1;
+ };
+ }
+
+ return err;
+}
+
+/*
+ * Write @a wr_chunk_size bytes to the file @a fd at position @a pos. The @a
+ * buffer will be used to prepare a pattern and write it to the file.
+ */
+static int write_block(
+ int fd,
+ off_t pos,
+ size_t wr_chunk_size,
+ void *buffer,
+ uint32_t random
+)
+{
+ ssize_t ret;
+ size_t i;
+
+ for (i = 0; i < wr_chunk_size; i += PATTERN_SIZE) {
+ *(uint32_t *)(buffer + i) = get_pattern_from_offset(pos + i, random);
+ }
+
+ ret = pwrite(fd, buffer, wr_chunk_size, pos);
+ if (ret == -1) {
+ warn("error: write failed");
+ return 1;
+ }
+ if ((size_t)ret != wr_chunk_size) {
+ warnx("error: Not all data written.");
+ return 1;
+ }
+
+ return 0;
+}
+
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+static int do_blktest(
+ const char *device,
+ size_t block_size,
+ off_t test_area,
+ size_t wr_chunk_size,
+ size_t rd_chunk_size,
+ uint32_t random,
+ bool verbose
+)
+{
+ int fd;
+ int rv;
+ size_t pos;
+ int err = 0;
+ unsigned int nr_wr_chunks = (unsigned int)(test_area / wr_chunk_size);
+ unsigned int nr_rd_chunks = (unsigned int)(test_area / rd_chunk_size);
+ size_t remaining_wr = (size_t)(test_area - wr_chunk_size * nr_wr_chunks);
+ size_t remaining_rd = (size_t)(test_area - rd_chunk_size * nr_rd_chunks);
+
+ uint8_t *buffer = malloc(max(wr_chunk_size, rd_chunk_size));
+
+ assert((wr_chunk_size & 0x3) == 0);
+ assert((rd_chunk_size & 0x3) == 0);
+ assert((test_area & 0x3) == 0);
+
+ fd = open(device, O_RDWR);
+ if (fd == -1) {
+ warn("error: Could not open file");
+ return 1;
+ }
+
+ if (block_size != 0) {
+ rv = rtems_disk_fd_set_block_size(fd, block_size);
+ if (rv == -1) {
+ warn("error: Could not set block size");
+ return 1;
+ }
+ }
+
+ /* Write pattern. */
+ if (err == 0) {
+ printf("Write even chunks ");
+ }
+ for (pos = 0; pos < nr_wr_chunks && err == 0; pos += 2) {
+ err = write_block(fd, pos*wr_chunk_size, wr_chunk_size, buffer, random);
+ putc('.', stdout);
+ }
+ putc('\n', stdout);
+ if (err == 0) {
+ printf("Write odd chunks ");
+ }
+ for (pos = 1; pos < nr_wr_chunks && err == 0; pos += 2) {
+ err = write_block(fd, pos*wr_chunk_size, wr_chunk_size, buffer, random);
+ putc('.', stdout);
+ }
+ putc('\n', stdout);
+ if (remaining_wr > 0 && err == 0) {
+ printf("Write remaining part\n");
+ err = write_block(fd, nr_wr_chunks * wr_chunk_size, remaining_wr, buffer,
+ random);
+ }
+
+ /* Issue sync */
+ sync();
+
+ /* Check pattern. */
+ if (err == 0) {
+ printf("Check pattern ");
+ for (pos = 0; pos < nr_rd_chunks; ++pos) {
+ int newerrs = check_block(fd, pos*rd_chunk_size, rd_chunk_size, buffer,
+ random, verbose);
+ err += newerrs;
+ if (newerrs) {
+ putc('E', stdout);
+ } else {
+ putc('.', stdout);
+ }
+ }
+ if (remaining_rd > 0) {
+ int newerrs = check_block(fd, nr_rd_chunks * rd_chunk_size, remaining_rd,
+ buffer, random, verbose);
+ err += newerrs;
+ if (newerrs) {
+ putc('E', stdout);
+ } else {
+ putc('.', stdout);
+ }
+ }
+ putc('\n', stdout);
+ if (err) {
+ printf("Errors during check: %d\n", err);
+ }
+ }
+
+ if (err == 0) {
+ puts("DONE. Everything worked well.");
+ } else {
+ puts("ERROR. Something went wrong.");
+ }
+
+ return err;
+}
+
+static bool get_si_value_from_string(uint64_t *value, char *str, char *desc)
+{
+ char *remaining;
+ bool rv = true;
+
+ errno = 0;
+ *value = strtoul(str, &remaining, 0);
+ if (*value == 0 && errno != 0)
+ {
+ warn("error: invalid %s", desc);
+ rv = false;
+ }
+
+ if (rv && (*remaining != '\0')) {
+ switch (*remaining) {
+ case 'k':
+ case 'K':
+ *value *= 1024;
+ break;
+
+ case 'M':
+ *value *= 1024 * 1024;
+ break;
+
+ case 'G':
+ *value *= 1024 * 1024 * 1024;
+ break;
+
+ default:
+ warnx("error: Unknown suffix for %s: %c", desc, *remaining);
+ rv = false;
+ break;
+ }
+ }
+
+ return rv;
+}
+
+static int rtems_shell_main_blktest(int argc, char **argv)
+{
+ size_t block_size = DEFAULT_BLK_SIZE;
+ off_t test_area = DEFAULT_TEST_AREA;
+ size_t rd_chunk_size = DEFAULT_RW_CHUNK_SIZE;
+ size_t wr_chunk_size = DEFAULT_RW_CHUNK_SIZE;
+ uint32_t random;
+ uint64_t temp;
+ const char *device = NULL;
+ int arg;
+ bool verbose = false;
+ int answer;
+
+ if (argc == 1) {
+ puts(rtems_shell_blktest_usage);
+ return 0;
+ }
+
+ random = (uint32_t)((rtems_clock_get_uptime_nanoseconds())/1000);
+
+ for (arg = 1; arg < argc; ++arg) {
+ if (argv[arg][0] != '-') {
+ if (device != NULL) {
+ warnx("error: BLK_DEV given multiple times: '%s' and '%s'",
+ device, argv[arg]);
+ return 1;
+ }
+ device = argv[arg];
+ } else {
+ switch (argv[arg][1]) {
+ case 'h':
+ puts(rtems_shell_blktest_usage);
+ return 0;
+ break;
+
+ case 'b':
+ ++arg;
+ if (!get_si_value_from_string(&temp, argv[arg], "BLK_SIZE")) {
+ return 1;
+ }
+ block_size = (size_t) temp;
+ break;
+
+ case 't':
+ ++arg;
+ if (!get_si_value_from_string(&temp, argv[arg], "TEST_AREA")) {
+ return 1;
+ }
+ test_area = (off_t) temp;
+ break;
+
+ case 'p':
+ ++arg;
+ if (!get_si_value_from_string(&temp, argv[arg], "RANDOM")) {
+ return 1;
+ }
+ random = (uint32_t) temp;
+ break;
+
+ case 'r':
+ ++arg;
+ if (!get_si_value_from_string(&temp, argv[arg], "RD_SIZE")) {
+ return 1;
+ }
+ rd_chunk_size = (size_t) temp;
+ break;
+
+ case 'w':
+ ++arg;
+ if (!get_si_value_from_string(&temp, argv[arg], "WR_SIZE")) {
+ return 1;
+ }
+ wr_chunk_size = (size_t) temp;
+ break;
+
+ case 'v':
+ verbose = true;
+ break;
+
+ default:
+ warnx("error: invalid option: '%s'\n", argv[arg]);
+ return 1;
+ }
+ }
+ }
+
+ printf("ATTENTION: This is destructive for all data on the device. Really start? [y|n]\n");
+ answer = getchar();
+
+ if (answer == 'y') {
+ printf("Use the following start value for the pattern: 0x%08lx\n", random);
+
+ return do_blktest(device, block_size, test_area,
+ wr_chunk_size, rd_chunk_size, random, verbose);
+ }
+
+ printf("Abort.");
+ return 1;
+}
+
+rtems_shell_cmd_t rtems_shell_BLKTEST_Command = {
+ .name = "blktest",
+ .usage = rtems_shell_blktest_usage,
+ .topic = "files",
+ .command = rtems_shell_main_blktest
+};
diff --git a/cpukit/libmisc/shell/shellconfig.h b/cpukit/libmisc/shell/shellconfig.h
index 9f68b313fb..4eb651caad 100644
--- a/cpukit/libmisc/shell/shellconfig.h
+++ b/cpukit/libmisc/shell/shellconfig.h
@@ -70,6 +70,7 @@ extern rtems_shell_cmd_t rtems_shell_MOUNT_Command;
extern rtems_shell_cmd_t rtems_shell_UNMOUNT_Command;
extern rtems_shell_cmd_t rtems_shell_BLKSYNC_Command;
extern rtems_shell_cmd_t rtems_shell_BLKSTATS_Command;
+extern rtems_shell_cmd_t rtems_shell_BLKTEST_Command;
extern rtems_shell_cmd_t rtems_shell_FDISK_Command;
extern rtems_shell_cmd_t rtems_shell_DD_Command;
extern rtems_shell_cmd_t rtems_shell_HEXDUMP_Command;
--
2.12.3
More information about the devel
mailing list