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