diff options
Diffstat (limited to 'drivers/mtd/ubi/gluebi.c')
-rw-r--r-- | drivers/mtd/ubi/gluebi.c | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/drivers/mtd/ubi/gluebi.c b/drivers/mtd/ubi/gluebi.c new file mode 100644 index 000000000000..fc9478d605ff --- /dev/null +++ b/drivers/mtd/ubi/gluebi.c | |||
@@ -0,0 +1,323 @@ | |||
1 | /* | ||
2 | * Copyright (c) International Business Machines Corp., 2006 | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See | ||
12 | * the GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the Free Software | ||
16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
17 | * | ||
18 | * Author: Artem Bityutskiy (Битюцкий Артём), Joern Engel | ||
19 | */ | ||
20 | |||
21 | /* | ||
22 | * This file includes implementation of fake MTD devices for each UBI volume. | ||
23 | * This sounds strange, but it is in fact quite useful to make MTD-oriented | ||
24 | * software (including all the legacy software) to work on top of UBI. | ||
25 | * | ||
26 | * Gluebi emulates MTD devices of "MTD_UBIVOLUME" type. Their minimal I/O unit | ||
27 | * size (mtd->writesize) is equivalent to the UBI minimal I/O unit. The | ||
28 | * eraseblock size is equivalent to the logical eraseblock size of the volume. | ||
29 | */ | ||
30 | |||
31 | #include <asm/div64.h> | ||
32 | #include "ubi.h" | ||
33 | |||
34 | /** | ||
35 | * gluebi_get_device - get MTD device reference. | ||
36 | * @mtd: the MTD device description object | ||
37 | * | ||
38 | * This function is called every time the MTD device is being opened and | ||
39 | * implements the MTD get_device() operation. Returns zero in case of success | ||
40 | * and a negative error code in case of failure. | ||
41 | */ | ||
42 | static int gluebi_get_device(struct mtd_info *mtd) | ||
43 | { | ||
44 | struct ubi_volume *vol; | ||
45 | |||
46 | vol = container_of(mtd, struct ubi_volume, gluebi_mtd); | ||
47 | |||
48 | /* | ||
49 | * We do not introduce locks for gluebi reference count because the | ||
50 | * get_device()/put_device() calls are already serialized at MTD. | ||
51 | */ | ||
52 | if (vol->gluebi_refcount > 0) { | ||
53 | /* | ||
54 | * The MTD device is already referenced and this is just one | ||
55 | * more reference. MTD allows many users to open the same | ||
56 | * volume simultaneously and do not distinguish between | ||
57 | * readers/writers/exclusive openers as UBI does. So we do not | ||
58 | * open the UBI volume again - just increase the reference | ||
59 | * counter and return. | ||
60 | */ | ||
61 | vol->gluebi_refcount += 1; | ||
62 | return 0; | ||
63 | } | ||
64 | |||
65 | /* | ||
66 | * This is the first reference to this UBI volume via the MTD device | ||
67 | * interface. Open the corresponding volume in read-write mode. | ||
68 | */ | ||
69 | vol->gluebi_desc = ubi_open_volume(vol->ubi->ubi_num, vol->vol_id, | ||
70 | UBI_READWRITE); | ||
71 | if (IS_ERR(vol->gluebi_desc)) | ||
72 | return PTR_ERR(vol->gluebi_desc); | ||
73 | vol->gluebi_refcount += 1; | ||
74 | return 0; | ||
75 | } | ||
76 | |||
77 | /** | ||
78 | * gluebi_put_device - put MTD device reference. | ||
79 | * @mtd: the MTD device description object | ||
80 | * | ||
81 | * This function is called every time the MTD device is being put. Returns | ||
82 | * zero in case of success and a negative error code in case of failure. | ||
83 | */ | ||
84 | static void gluebi_put_device(struct mtd_info *mtd) | ||
85 | { | ||
86 | struct ubi_volume *vol; | ||
87 | |||
88 | vol = container_of(mtd, struct ubi_volume, gluebi_mtd); | ||
89 | vol->gluebi_refcount -= 1; | ||
90 | ubi_assert(vol->gluebi_refcount >= 0); | ||
91 | if (vol->gluebi_refcount == 0) | ||
92 | ubi_close_volume(vol->gluebi_desc); | ||
93 | } | ||
94 | |||
95 | /** | ||
96 | * gluebi_read - read operation of emulated MTD devices. | ||
97 | * @mtd: MTD device description object | ||
98 | * @from: absolute offset from where to read | ||
99 | * @len: how many bytes to read | ||
100 | * @retlen: count of read bytes is returned here | ||
101 | * @buf: buffer to store the read data | ||
102 | * | ||
103 | * This function returns zero in case of success and a negative error code in | ||
104 | * case of failure. | ||
105 | */ | ||
106 | static int gluebi_read(struct mtd_info *mtd, loff_t from, size_t len, | ||
107 | size_t *retlen, unsigned char *buf) | ||
108 | { | ||
109 | int err = 0, lnum, offs, total_read; | ||
110 | struct ubi_volume *vol; | ||
111 | struct ubi_device *ubi; | ||
112 | uint64_t tmp = from; | ||
113 | |||
114 | dbg_msg("read %zd bytes from offset %lld", len, from); | ||
115 | |||
116 | if (len < 0 || from < 0 || from + len > mtd->size) | ||
117 | return -EINVAL; | ||
118 | |||
119 | vol = container_of(mtd, struct ubi_volume, gluebi_mtd); | ||
120 | ubi = vol->ubi; | ||
121 | |||
122 | offs = do_div(tmp, mtd->erasesize); | ||
123 | lnum = tmp; | ||
124 | |||
125 | total_read = len; | ||
126 | while (total_read) { | ||
127 | size_t to_read = mtd->erasesize - offs; | ||
128 | |||
129 | if (to_read > total_read) | ||
130 | to_read = total_read; | ||
131 | |||
132 | err = ubi_eba_read_leb(ubi, vol->vol_id, lnum, buf, offs, | ||
133 | to_read, 0); | ||
134 | if (err) | ||
135 | break; | ||
136 | |||
137 | lnum += 1; | ||
138 | offs = 0; | ||
139 | total_read -= to_read; | ||
140 | buf += to_read; | ||
141 | } | ||
142 | |||
143 | *retlen = len - total_read; | ||
144 | return err; | ||
145 | } | ||
146 | |||
147 | /** | ||
148 | * gluebi_write - write operation of emulated MTD devices. | ||
149 | * @mtd: MTD device description object | ||
150 | * @to: absolute offset where to write | ||
151 | * @len: how many bytes to write | ||
152 | * @retlen: count of written bytes is returned here | ||
153 | * @buf: buffer with data to write | ||
154 | * | ||
155 | * This function returns zero in case of success and a negative error code in | ||
156 | * case of failure. | ||
157 | */ | ||
158 | static int gluebi_write(struct mtd_info *mtd, loff_t to, size_t len, | ||
159 | size_t *retlen, const u_char *buf) | ||
160 | { | ||
161 | int err = 0, lnum, offs, total_written; | ||
162 | struct ubi_volume *vol; | ||
163 | struct ubi_device *ubi; | ||
164 | uint64_t tmp = to; | ||
165 | |||
166 | dbg_msg("write %zd bytes to offset %lld", len, to); | ||
167 | |||
168 | if (len < 0 || to < 0 || len + to > mtd->size) | ||
169 | return -EINVAL; | ||
170 | |||
171 | vol = container_of(mtd, struct ubi_volume, gluebi_mtd); | ||
172 | ubi = vol->ubi; | ||
173 | |||
174 | if (ubi->ro_mode) | ||
175 | return -EROFS; | ||
176 | |||
177 | offs = do_div(tmp, mtd->erasesize); | ||
178 | lnum = tmp; | ||
179 | |||
180 | if (len % mtd->writesize || offs % mtd->writesize) | ||
181 | return -EINVAL; | ||
182 | |||
183 | total_written = len; | ||
184 | while (total_written) { | ||
185 | size_t to_write = mtd->erasesize - offs; | ||
186 | |||
187 | if (to_write > total_written) | ||
188 | to_write = total_written; | ||
189 | |||
190 | err = ubi_eba_write_leb(ubi, vol->vol_id, lnum, buf, offs, | ||
191 | to_write, UBI_UNKNOWN); | ||
192 | if (err) | ||
193 | break; | ||
194 | |||
195 | lnum += 1; | ||
196 | offs = 0; | ||
197 | total_written -= to_write; | ||
198 | buf += to_write; | ||
199 | } | ||
200 | |||
201 | *retlen = len - total_written; | ||
202 | return err; | ||
203 | } | ||
204 | |||
205 | /** | ||
206 | * gluebi_erase - erase operation of emulated MTD devices. | ||
207 | * @mtd: the MTD device description object | ||
208 | * @instr: the erase operation description | ||
209 | * | ||
210 | * This function calls the erase callback when finishes. Returns zero in case | ||
211 | * of success and a negative error code in case of failure. | ||
212 | */ | ||
213 | static int gluebi_erase(struct mtd_info *mtd, struct erase_info *instr) | ||
214 | { | ||
215 | int err, i, lnum, count; | ||
216 | struct ubi_volume *vol; | ||
217 | struct ubi_device *ubi; | ||
218 | |||
219 | dbg_msg("erase %u bytes at offset %u", instr->len, instr->addr); | ||
220 | |||
221 | if (instr->addr < 0 || instr->addr > mtd->size - mtd->erasesize) | ||
222 | return -EINVAL; | ||
223 | |||
224 | if (instr->len < 0 || instr->addr + instr->len > mtd->size) | ||
225 | return -EINVAL; | ||
226 | |||
227 | if (instr->addr % mtd->writesize || instr->len % mtd->writesize) | ||
228 | return -EINVAL; | ||
229 | |||
230 | lnum = instr->addr / mtd->erasesize; | ||
231 | count = instr->len / mtd->erasesize; | ||
232 | |||
233 | vol = container_of(mtd, struct ubi_volume, gluebi_mtd); | ||
234 | ubi = vol->ubi; | ||
235 | |||
236 | if (ubi->ro_mode) | ||
237 | return -EROFS; | ||
238 | |||
239 | for (i = 0; i < count; i++) { | ||
240 | err = ubi_eba_unmap_leb(ubi, vol->vol_id, lnum + i); | ||
241 | if (err) | ||
242 | goto out_err; | ||
243 | } | ||
244 | |||
245 | /* | ||
246 | * MTD erase operations are synchronous, so we have to make sure the | ||
247 | * physical eraseblock is wiped out. | ||
248 | */ | ||
249 | err = ubi_wl_flush(ubi); | ||
250 | if (err) | ||
251 | goto out_err; | ||
252 | |||
253 | instr->state = MTD_ERASE_DONE; | ||
254 | mtd_erase_callback(instr); | ||
255 | return 0; | ||
256 | |||
257 | out_err: | ||
258 | instr->state = MTD_ERASE_FAILED; | ||
259 | instr->fail_addr = lnum * mtd->erasesize; | ||
260 | return err; | ||
261 | } | ||
262 | |||
263 | /** | ||
264 | * ubi_create_gluebi - initialize gluebi for an UBI volume. | ||
265 | * @ubi: UBI device description object | ||
266 | * @vol: volume description object | ||
267 | * | ||
268 | * This function is called when an UBI volume is created in order to create | ||
269 | * corresponding fake MTD device. Returns zero in case of success and a | ||
270 | * negative error code in case of failure. | ||
271 | */ | ||
272 | int ubi_create_gluebi(struct ubi_device *ubi, struct ubi_volume *vol) | ||
273 | { | ||
274 | struct mtd_info *mtd = &vol->gluebi_mtd; | ||
275 | |||
276 | mtd->name = kmemdup(vol->name, vol->name_len + 1, GFP_KERNEL); | ||
277 | if (!mtd->name) | ||
278 | return -ENOMEM; | ||
279 | |||
280 | mtd->type = MTD_UBIVOLUME; | ||
281 | if (!ubi->ro_mode) | ||
282 | mtd->flags = MTD_WRITEABLE; | ||
283 | mtd->writesize = ubi->min_io_size; | ||
284 | mtd->owner = THIS_MODULE; | ||
285 | mtd->size = vol->usable_leb_size * vol->reserved_pebs; | ||
286 | mtd->erasesize = vol->usable_leb_size; | ||
287 | mtd->read = gluebi_read; | ||
288 | mtd->write = gluebi_write; | ||
289 | mtd->erase = gluebi_erase; | ||
290 | mtd->get_device = gluebi_get_device; | ||
291 | mtd->put_device = gluebi_put_device; | ||
292 | |||
293 | if (add_mtd_device(mtd)) { | ||
294 | ubi_err("cannot not add MTD device\n"); | ||
295 | kfree(mtd->name); | ||
296 | return -ENFILE; | ||
297 | } | ||
298 | |||
299 | dbg_msg("added mtd%d (\"%s\"), size %u, EB size %u", | ||
300 | mtd->index, mtd->name, mtd->size, mtd->erasesize); | ||
301 | return 0; | ||
302 | } | ||
303 | |||
304 | /** | ||
305 | * ubi_destroy_gluebi - close gluebi for an UBI volume. | ||
306 | * @vol: volume description object | ||
307 | * | ||
308 | * This function is called when an UBI volume is removed in order to remove | ||
309 | * corresponding fake MTD device. Returns zero in case of success and a | ||
310 | * negative error code in case of failure. | ||
311 | */ | ||
312 | int ubi_destroy_gluebi(struct ubi_volume *vol) | ||
313 | { | ||
314 | int err; | ||
315 | struct mtd_info *mtd = &vol->gluebi_mtd; | ||
316 | |||
317 | dbg_msg("remove mtd%d", mtd->index); | ||
318 | err = del_mtd_device(mtd); | ||
319 | if (err) | ||
320 | return err; | ||
321 | kfree(mtd->name); | ||
322 | return 0; | ||
323 | } | ||