1 /* 2 * Copyright 2010 by Garmin Ltd. or its subsidiaries 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 * Performs a simple write/readback test to verify correct functionality 17 * of direct i/o on a block device node. 18 */ 19 20 /* For large-file support */ 21 #define _FILE_OFFSET_BITS 64 22 #define _LARGEFILE_SOURCE 23 #define _LARGEFILE64_SOURCE 24 25 /* For O_DIRECT */ 26 #define _GNU_SOURCE 27 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <stdint.h> 31 #include <string.h> 32 #include <ctype.h> 33 #include <errno.h> 34 #include <limits.h> 35 #include <fcntl.h> 36 #include <unistd.h> 37 #include <sys/stat.h> 38 #include <sys/types.h> 39 #include <sys/ioctl.h> 40 #include <sys/mman.h> 41 42 #include <linux/fs.h> 43 44 #define NUM_TEST_BLKS 128 45 46 /* 47 * Allocate page-aligned memory. Could use posix_memalign(3), but some 48 * systems don't support it. Also pre-faults memory since we'll be using 49 * it all right away anyway. 50 */ 51 static void *pagealign_alloc(size_t size) 52 { 53 void *ret = mmap(NULL, size, PROT_READ | PROT_WRITE, 54 MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE | MAP_LOCKED, 55 -1, 0); 56 if (ret == MAP_FAILED) { 57 perror("mmap"); 58 ret = NULL; 59 } 60 return ret; 61 } 62 63 static void pagealign_free(void *addr, size_t size) 64 { 65 int ret = munmap(addr, size); 66 if (ret == -1) 67 perror("munmap"); 68 } 69 70 static ssize_t do_read(int fd, void *buf, off64_t start, size_t count) 71 { 72 ssize_t ret; 73 size_t bytes_read = 0; 74 75 lseek64(fd, start, SEEK_SET); 76 77 do { 78 ret = read(fd, (char *)buf + bytes_read, count - bytes_read); 79 if (ret == -1) { 80 perror("read"); 81 return -1; 82 } else if (ret == 0) { 83 fprintf(stderr, "Unexpected end-of-file\n"); 84 return -1; 85 } 86 bytes_read += ret; 87 } while (bytes_read < count); 88 89 return bytes_read; 90 } 91 92 static ssize_t do_write(int fd, const void *buf, off64_t start, size_t count) 93 { 94 ssize_t ret; 95 size_t bytes_out = 0; 96 97 lseek64(fd, start, SEEK_SET); 98 99 do { 100 ret = write(fd, (char *)buf + bytes_out, count - bytes_out); 101 if (ret == -1) { 102 perror("write"); 103 return -1; 104 } else if (ret == 0) { 105 fprintf(stderr, "write returned 0\n"); 106 return -1; 107 } 108 bytes_out += ret; 109 } while (bytes_out < count); 110 111 return bytes_out; 112 } 113 114 /* 115 * Initializes test buffer with locally-unique test pattern. High 16-bits of 116 * each 32-bit word contain first disk block number of the test area, low 117 * 16-bits contain word offset into test area. The goal is that a given test 118 * area should never contain the same data as a nearby test area, and that the 119 * data for a given test area be easily reproducable given the start block and 120 * test area size. 121 */ 122 static void init_test_buf(void *buf, uint64_t start_blk, size_t len) 123 { 124 uint32_t *data = buf; 125 size_t i; 126 127 len /= sizeof(uint32_t); 128 for (i = 0; i < len; i++) 129 data[i] = (start_blk & 0xFFFF) << 16 | (i & 0xFFFF); 130 } 131 132 static void dump_hex(const void *buf, int len) 133 { 134 const uint8_t *data = buf; 135 int i; 136 char ascii_buf[17]; 137 138 ascii_buf[16] = '\0'; 139 140 for (i = 0; i < len; i++) { 141 int val = data[i]; 142 int off = i % 16; 143 144 if (off == 0) 145 printf("%08x ", i); 146 printf("%02x ", val); 147 ascii_buf[off] = isprint(val) ? val : '.'; 148 if (off == 15) 149 printf(" %-16s\n", ascii_buf); 150 } 151 152 i %= 16; 153 if (i) { 154 ascii_buf[i] = '\0'; 155 while (i++ < 16) 156 printf(" "); 157 printf(" %-16s\n", ascii_buf); 158 } 159 } 160 161 static void update_progress(int current, int total) 162 { 163 double pct_done = (double)current * 100 / total; 164 printf("Testing area %d/%d (%6.2f%% complete)\r", current, total, 165 pct_done); 166 fflush(stdout); 167 } 168 169 int main(int argc, const char *argv[]) 170 { 171 int ret = 1; 172 const char *path; 173 int fd; 174 struct stat stat; 175 void *read_buf = NULL, *write_buf = NULL; 176 int blk_size; 177 uint64_t num_blks; 178 size_t test_size; 179 int test_areas, i; 180 181 if (argc != 2) { 182 printf("Usage: directiotest blkdev_path\n"); 183 exit(1); 184 } 185 186 path = argv[1]; 187 fd = open(path, O_RDWR | O_DIRECT | O_LARGEFILE); 188 if (fd == -1) { 189 perror("open"); 190 exit(1); 191 } 192 if (fstat(fd, &stat) == -1) { 193 perror("stat"); 194 goto cleanup; 195 } else if (!S_ISBLK(stat.st_mode)) { 196 fprintf(stderr, "%s is not a block device\n", path); 197 goto cleanup; 198 } 199 200 if (ioctl(fd, BLKSSZGET, &blk_size) == -1) { 201 perror("ioctl"); 202 goto cleanup; 203 } 204 if (ioctl(fd, BLKGETSIZE64, &num_blks) == -1) { 205 perror("ioctl"); 206 goto cleanup; 207 } 208 num_blks /= blk_size; 209 210 test_size = (size_t)blk_size * NUM_TEST_BLKS; 211 read_buf = pagealign_alloc(test_size); 212 write_buf = pagealign_alloc(test_size); 213 if (!read_buf || !write_buf) { 214 fprintf(stderr, "Error allocating test buffers\n"); 215 goto cleanup; 216 } 217 218 /* 219 * Start the actual test. Go through the entire device, writing 220 * locally-unique patern to each test block and then reading it 221 * back. 222 */ 223 if (num_blks / NUM_TEST_BLKS > INT_MAX) { 224 printf("Warning: Device too large for test variables\n"); 225 printf("Entire device will not be tested\n"); 226 test_areas = INT_MAX; 227 } else { 228 test_areas = num_blks / NUM_TEST_BLKS; 229 } 230 231 printf("Starting test\n"); 232 233 for (i = 0; i < test_areas; i++) { 234 uint64_t cur_blk = (uint64_t)i * NUM_TEST_BLKS; 235 236 update_progress(i + 1, test_areas); 237 238 init_test_buf(write_buf, cur_blk, test_size); 239 240 if (do_write(fd, write_buf, cur_blk * blk_size, test_size) != 241 (ssize_t)test_size) { 242 fprintf(stderr, "write failed, aborting test\n"); 243 goto cleanup; 244 } 245 if (do_read(fd, read_buf, cur_blk * blk_size, test_size) != 246 (ssize_t)test_size) { 247 fprintf(stderr, "read failed, aborting test\n"); 248 goto cleanup; 249 } 250 251 if (memcmp(write_buf, read_buf, test_size)) { 252 printf("Readback verification failed at block %llu\n\n", 253 cur_blk); 254 printf("Written data:\n"); 255 dump_hex(write_buf, test_size); 256 printf("\nRead data:\n"); 257 dump_hex(read_buf, test_size); 258 goto cleanup; 259 } 260 } 261 262 printf("\nTest complete\n"); 263 ret = 0; 264 265 cleanup: 266 if (read_buf) 267 pagealign_free(read_buf, test_size); 268 if (write_buf) 269 pagealign_free(write_buf, test_size); 270 close(fd); 271 return ret; 272 } 273