• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
pagealign_alloc(size_t size)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 
pagealign_free(void * addr,size_t size)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 
do_read(int fd,void * buf,off64_t start,size_t count)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 
do_write(int fd,const void * buf,off64_t start,size_t count)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  */
init_test_buf(void * buf,uint64_t start_blk,size_t len)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 
dump_hex(const void * buf,int len)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 
update_progress(int current,int total)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 
main(int argc,const char * argv[])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