Commit | Line | Data |
---|---|---|
65fa28ca DE |
1 | /* |
2 | * MTD engine | |
3 | * | |
4 | * IO engine that reads/writes from MTD character devices. | |
5 | * | |
6 | */ | |
65fa28ca DE |
7 | #include <stdio.h> |
8 | #include <stdlib.h> | |
65fa28ca DE |
9 | #include <errno.h> |
10 | #include <sys/ioctl.h> | |
11 | #include <mtd/mtd-user.h> | |
12 | ||
13 | #include "../fio.h" | |
e59b9e11 | 14 | #include "../optgroup.h" |
984f30c9 | 15 | #include "../oslib/libmtd.h" |
65fa28ca | 16 | |
a89ba4b1 | 17 | static libmtd_t desc; |
65fa28ca DE |
18 | |
19 | struct fio_mtd_data { | |
20 | struct mtd_dev_info info; | |
21 | }; | |
22 | ||
e59b9e11 TK |
23 | struct fio_mtd_options { |
24 | void *pad; /* avoid off1 == 0 */ | |
25 | unsigned int skip_bad; | |
26 | }; | |
27 | ||
28 | static struct fio_option options[] = { | |
29 | { | |
30 | .name = "skip_bad", | |
31 | .lname = "Skip operations against bad blocks", | |
32 | .type = FIO_OPT_BOOL, | |
33 | .off1 = offsetof(struct fio_mtd_options, skip_bad), | |
34 | .help = "Skip operations against known bad blocks.", | |
35 | .hide = 1, | |
36 | .def = "0", | |
37 | .category = FIO_OPT_C_ENGINE, | |
38 | .group = FIO_OPT_G_MTD, | |
39 | }, | |
40 | { | |
41 | .name = NULL, | |
42 | }, | |
43 | }; | |
44 | ||
65fa28ca DE |
45 | static int fio_mtd_maybe_mark_bad(struct thread_data *td, |
46 | struct fio_mtd_data *fmd, | |
47 | struct io_u *io_u, int eb) | |
48 | { | |
49 | int ret; | |
50 | if (errno == EIO) { | |
51 | ret = mtd_mark_bad(&fmd->info, io_u->file->fd, eb); | |
52 | if (ret != 0) { | |
53 | io_u->error = errno; | |
54 | td_verror(td, errno, "mtd_mark_bad"); | |
55 | return -1; | |
56 | } | |
57 | } | |
58 | return 0; | |
59 | } | |
60 | ||
61 | static int fio_mtd_is_bad(struct thread_data *td, | |
62 | struct fio_mtd_data *fmd, | |
63 | struct io_u *io_u, int eb) | |
64 | { | |
65 | int ret = mtd_is_bad(&fmd->info, io_u->file->fd, eb); | |
66 | if (ret == -1) { | |
67 | io_u->error = errno; | |
68 | td_verror(td, errno, "mtd_is_bad"); | |
69 | } else if (ret == 1) | |
70 | io_u->error = EIO; /* Silent failure--don't flood stderr */ | |
71 | return ret; | |
72 | } | |
73 | ||
74 | static int fio_mtd_queue(struct thread_data *td, struct io_u *io_u) | |
75 | { | |
76 | struct fio_file *f = io_u->file; | |
77 | struct fio_mtd_data *fmd = FILE_ENG_DATA(f); | |
e59b9e11 | 78 | struct fio_mtd_options *o = td->eo; |
65fa28ca DE |
79 | int local_offs = 0; |
80 | int ret; | |
81 | ||
82 | fio_ro_check(td, io_u); | |
83 | ||
84 | /* | |
85 | * Errors tend to pertain to particular erase blocks, so divide up | |
86 | * I/O to erase block size. | |
87 | * If an error is encountered, log it and keep going onto the next | |
88 | * block because the error probably just pertains to that block. | |
89 | * TODO(dehrenberg): Divide up reads and writes into page-sized | |
90 | * operations to get more fine-grained information about errors. | |
91 | */ | |
92 | while (local_offs < io_u->buflen) { | |
93 | int eb = (io_u->offset + local_offs) / fmd->info.eb_size; | |
94 | int eb_offs = (io_u->offset + local_offs) % fmd->info.eb_size; | |
95 | /* The length is the smaller of the length remaining in the | |
96 | * buffer and the distance to the end of the erase block */ | |
97 | int len = min((int)io_u->buflen - local_offs, | |
98 | (int)fmd->info.eb_size - eb_offs); | |
99 | char *buf = ((char *)io_u->buf) + local_offs; | |
100 | ||
e59b9e11 | 101 | if (o->skip_bad) { |
65fa28ca DE |
102 | ret = fio_mtd_is_bad(td, fmd, io_u, eb); |
103 | if (ret == -1) | |
104 | break; | |
105 | else if (ret == 1) | |
106 | goto next; | |
107 | } | |
108 | if (io_u->ddir == DDIR_READ) { | |
109 | ret = mtd_read(&fmd->info, f->fd, eb, eb_offs, buf, len); | |
110 | if (ret != 0) { | |
111 | io_u->error = errno; | |
112 | td_verror(td, errno, "mtd_read"); | |
113 | if (fio_mtd_maybe_mark_bad(td, fmd, io_u, eb)) | |
114 | break; | |
115 | } | |
116 | } else if (io_u->ddir == DDIR_WRITE) { | |
117 | ret = mtd_write(desc, &fmd->info, f->fd, eb, | |
118 | eb_offs, buf, len, NULL, 0, 0); | |
119 | if (ret != 0) { | |
120 | io_u->error = errno; | |
121 | td_verror(td, errno, "mtd_write"); | |
122 | if (fio_mtd_maybe_mark_bad(td, fmd, io_u, eb)) | |
123 | break; | |
124 | } | |
125 | } else if (io_u->ddir == DDIR_TRIM) { | |
126 | if (eb_offs != 0 || len != fmd->info.eb_size) { | |
127 | io_u->error = EINVAL; | |
128 | td_verror(td, EINVAL, | |
129 | "trim on MTD must be erase block-aligned"); | |
130 | } | |
131 | ret = mtd_erase(desc, &fmd->info, f->fd, eb); | |
132 | if (ret != 0) { | |
133 | io_u->error = errno; | |
134 | td_verror(td, errno, "mtd_erase"); | |
135 | if (fio_mtd_maybe_mark_bad(td, fmd, io_u, eb)) | |
136 | break; | |
137 | } | |
138 | } else { | |
139 | io_u->error = ENOTSUP; | |
140 | td_verror(td, io_u->error, "operation not supported on mtd"); | |
141 | } | |
142 | ||
143 | next: | |
144 | local_offs += len; | |
145 | } | |
146 | ||
147 | return FIO_Q_COMPLETED; | |
148 | } | |
149 | ||
150 | static int fio_mtd_open_file(struct thread_data *td, struct fio_file *f) | |
151 | { | |
152 | struct fio_mtd_data *fmd; | |
153 | int ret; | |
154 | ||
155 | ret = generic_open_file(td, f); | |
156 | if (ret) | |
157 | return ret; | |
158 | ||
159 | fmd = calloc(1, sizeof(*fmd)); | |
160 | if (!fmd) | |
161 | goto err_close; | |
162 | ||
163 | ret = mtd_get_dev_info(desc, f->file_name, &fmd->info); | |
164 | if (ret != 0) { | |
165 | td_verror(td, errno, "mtd_get_dev_info"); | |
166 | goto err_free; | |
167 | } | |
168 | ||
169 | FILE_SET_ENG_DATA(f, fmd); | |
170 | return 0; | |
171 | ||
172 | err_free: | |
173 | free(fmd); | |
174 | err_close: | |
175 | { | |
8a68c41c JA |
176 | int fio_unused __ret; |
177 | __ret = generic_close_file(td, f); | |
65fa28ca DE |
178 | return 1; |
179 | } | |
180 | } | |
181 | ||
182 | static int fio_mtd_close_file(struct thread_data *td, struct fio_file *f) | |
183 | { | |
184 | struct fio_mtd_data *fmd = FILE_ENG_DATA(f); | |
185 | ||
186 | FILE_SET_ENG_DATA(f, NULL); | |
187 | free(fmd); | |
188 | ||
189 | return generic_close_file(td, f); | |
190 | } | |
191 | ||
a89ba4b1 | 192 | static int fio_mtd_get_file_size(struct thread_data *td, struct fio_file *f) |
65fa28ca DE |
193 | { |
194 | struct mtd_dev_info info; | |
195 | ||
196 | int ret = mtd_get_dev_info(desc, f->file_name, &info); | |
197 | if (ret != 0) { | |
198 | td_verror(td, errno, "mtd_get_dev_info"); | |
199 | return errno; | |
200 | } | |
201 | f->real_file_size = info.size; | |
202 | ||
203 | return 0; | |
204 | } | |
205 | ||
206 | static struct ioengine_ops ioengine = { | |
207 | .name = "mtd", | |
208 | .version = FIO_IOOPS_VERSION, | |
209 | .queue = fio_mtd_queue, | |
210 | .open_file = fio_mtd_open_file, | |
211 | .close_file = fio_mtd_close_file, | |
212 | .get_file_size = fio_mtd_get_file_size, | |
213 | .flags = FIO_SYNCIO | FIO_NOEXTEND, | |
e59b9e11 TK |
214 | .options = options, |
215 | .option_struct_size = sizeof(struct fio_mtd_options), | |
65fa28ca DE |
216 | }; |
217 | ||
218 | static void fio_init fio_mtd_register(void) | |
219 | { | |
220 | desc = libmtd_open(); | |
221 | register_ioengine(&ioengine); | |
222 | } | |
223 | ||
224 | static void fio_exit fio_mtd_unregister(void) | |
225 | { | |
226 | unregister_ioengine(&ioengine); | |
227 | libmtd_close(desc); | |
228 | desc = NULL; | |
229 | } | |
230 | ||
231 | ||
232 |