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