diff options
author | Seth Forshee <seth.forshee@garmin.com> | 2010-04-14 14:04:41 -0500 |
---|---|---|
committer | Garmin Android technology group <android@garmin.com> | 2010-04-15 10:02:43 -0500 |
commit | c610219fe9441f673e3aee12cf9479ae57feae25 (patch) | |
tree | 57dec7865465d5fbd035972f851e0c9bfdd8dac5 | |
parent | 48b40fefa1cd8b5371cb468dfcd79369a137566f (diff) | |
download | extras-c610219fe9441f673e3aee12cf9479ae57feae25.tar.gz |
tests: Add simple direct i/o test
Adds a simple write/readback test of direct i/o on a block
device node.
Change-Id: I06c2bb50fbc014157f5e0eaf2bbb910b89f5fc25
Signed-off-by: Garmin Android technology group <android@garmin.com>
-rw-r--r-- | tests/directiotest/Android.mk | 8 | ||||
-rw-r--r-- | tests/directiotest/directiotest.c | 272 |
2 files changed, 280 insertions, 0 deletions
diff --git a/tests/directiotest/Android.mk b/tests/directiotest/Android.mk new file mode 100644 index 00000000..fb5f12a2 --- /dev/null +++ b/tests/directiotest/Android.mk @@ -0,0 +1,8 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := directiotest +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := eng +LOCAL_SRC_FILES := directiotest.c +include $(BUILD_EXECUTABLE) diff --git a/tests/directiotest/directiotest.c b/tests/directiotest/directiotest.c new file mode 100644 index 00000000..989f9e09 --- /dev/null +++ b/tests/directiotest/directiotest.c @@ -0,0 +1,272 @@ +/* + * Copyright 2010 by Garmin Ltd. or its subsidiaries + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Performs a simple write/readback test to verify correct functionality + * of direct i/o on a block device node. + */ + +/* For large-file support */ +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE_SOURCE +#define _LARGEFILE64_SOURCE + +/* For O_DIRECT */ +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/mman.h> + +#include <linux/fs.h> + +#define NUM_TEST_BLKS 128 + +/* + * Allocate page-aligned memory. Could use posix_memalign(3), but some + * systems don't support it. Also pre-faults memory since we'll be using + * it all right away anyway. + */ +static void *pagealign_alloc(size_t size) +{ + void *ret = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE | MAP_LOCKED, + -1, 0); + if (ret == MAP_FAILED) { + perror("mmap"); + ret = NULL; + } + return ret; +} + +static void pagealign_free(void *addr, size_t size) +{ + int ret = munmap(addr, size); + if (ret == -1) + perror("munmap"); +} + +static ssize_t do_read(int fd, void *buf, off64_t start, size_t count) +{ + ssize_t ret; + size_t bytes_read = 0; + + lseek64(fd, start, SEEK_SET); + + do { + ret = read(fd, (char *)buf + bytes_read, count - bytes_read); + if (ret == -1) { + perror("read"); + return -1; + } else if (ret == 0) { + fprintf(stderr, "Unexpected end-of-file\n"); + return -1; + } + bytes_read += ret; + } while (bytes_read < count); + + return bytes_read; +} + +static ssize_t do_write(int fd, const void *buf, off64_t start, size_t count) +{ + ssize_t ret; + size_t bytes_out = 0; + + lseek64(fd, start, SEEK_SET); + + do { + ret = write(fd, (char *)buf + bytes_out, count - bytes_out); + if (ret == -1) { + perror("write"); + return -1; + } else if (ret == 0) { + fprintf(stderr, "write returned 0\n"); + return -1; + } + bytes_out += ret; + } while (bytes_out < count); + + return bytes_out; +} + +/* + * Initializes test buffer with locally-unique test pattern. High 16-bits of + * each 32-bit word contain first disk block number of the test area, low + * 16-bits contain word offset into test area. The goal is that a given test + * area should never contain the same data as a nearby test area, and that the + * data for a given test area be easily reproducable given the start block and + * test area size. + */ +static void init_test_buf(void *buf, uint64_t start_blk, size_t len) +{ + uint32_t *data = buf; + size_t i; + + len /= sizeof(uint32_t); + for (i = 0; i < len; i++) + data[i] = (start_blk & 0xFFFF) << 16 | (i & 0xFFFF); +} + +static void dump_hex(const void *buf, int len) +{ + const uint8_t *data = buf; + int i; + char ascii_buf[17]; + + ascii_buf[16] = '\0'; + + for (i = 0; i < len; i++) { + int val = data[i]; + int off = i % 16; + + if (off == 0) + printf("%08x ", i); + printf("%02x ", val); + ascii_buf[off] = isprint(val) ? val : '.'; + if (off == 15) + printf(" %-16s\n", ascii_buf); + } + + i %= 16; + if (i) { + ascii_buf[i] = '\0'; + while (i++ < 16) + printf(" "); + printf(" %-16s\n", ascii_buf); + } +} + +static void update_progress(int current, int total) +{ + double pct_done = (double)current * 100 / total; + printf("Testing area %d/%d (%6.2f%% complete)\r", current, total, + pct_done); + fflush(stdout); +} + +int main(int argc, const char *argv[]) +{ + int ret = 1; + const char *path; + int fd; + struct stat stat; + void *read_buf = NULL, *write_buf = NULL; + int blk_size; + uint64_t num_blks; + size_t test_size; + int test_areas, i; + + if (argc != 2) { + printf("Usage: directiotest blkdev_path\n"); + exit(1); + } + + path = argv[1]; + fd = open(path, O_RDWR | O_DIRECT | O_LARGEFILE); + if (fd == -1) { + perror("open"); + exit(1); + } + if (fstat(fd, &stat) == -1) { + perror("stat"); + goto cleanup; + } else if (!S_ISBLK(stat.st_mode)) { + fprintf(stderr, "%s is not a block device\n", path); + goto cleanup; + } + + if (ioctl(fd, BLKSSZGET, &blk_size) == -1) { + perror("ioctl"); + goto cleanup; + } + if (ioctl(fd, BLKGETSIZE64, &num_blks) == -1) { + perror("ioctl"); + goto cleanup; + } + num_blks /= blk_size; + + test_size = (size_t)blk_size * NUM_TEST_BLKS; + read_buf = pagealign_alloc(test_size); + write_buf = pagealign_alloc(test_size); + if (!read_buf || !write_buf) { + fprintf(stderr, "Error allocating test buffers\n"); + goto cleanup; + } + + /* + * Start the actual test. Go through the entire device, writing + * locally-unique patern to each test block and then reading it + * back. + */ + if (num_blks / NUM_TEST_BLKS > INT_MAX) { + printf("Warning: Device too large for test variables\n"); + printf("Entire device will not be tested\n"); + test_areas = INT_MAX; + } else { + test_areas = num_blks / NUM_TEST_BLKS; + } + + printf("Starting test\n"); + + for (i = 0; i < test_areas; i++) { + uint64_t cur_blk = (uint64_t)i * NUM_TEST_BLKS; + + update_progress(i + 1, test_areas); + + init_test_buf(write_buf, cur_blk, test_size); + + if (do_write(fd, write_buf, cur_blk * blk_size, test_size) != + (ssize_t)test_size) { + fprintf(stderr, "write failed, aborting test\n"); + goto cleanup; + } + if (do_read(fd, read_buf, cur_blk * blk_size, test_size) != + (ssize_t)test_size) { + fprintf(stderr, "read failed, aborting test\n"); + goto cleanup; + } + + if (memcmp(write_buf, read_buf, test_size)) { + printf("Readback verification failed at block %llu\n\n", + cur_blk); + printf("Written data:\n"); + dump_hex(write_buf, test_size); + printf("\nRead data:\n"); + dump_hex(read_buf, test_size); + goto cleanup; + } + } + + printf("\nTest complete\n"); + ret = 0; + +cleanup: + if (read_buf) + pagealign_free(read_buf, test_size); + if (write_buf) + pagealign_free(write_buf, test_size); + close(fd); + return ret; +} |