libfstools/mtd: attempt to read from OOB data if empty space is found
[project/fstools.git] / libfstools / mtd.c
1 /*
2 * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License version 2.1
6 * as published by the Free Software Foundation
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14 #include <sys/mount.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
18 #include <asm/byteorder.h>
19 #include <unistd.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <mtd/mtd-user.h>
23
24 #include "libfstools.h"
25
26 #include "volume.h"
27
28 #define PATH_MAX 256
29
30 struct mtd_volume {
31 struct volume v;
32 int fd;
33 int idx;
34 char *chr;
35 };
36
37 static struct driver mtd_driver;
38
39 static int mtd_open(const char *mtd, int block)
40 {
41 FILE *fp;
42 char dev[PATH_MAX];
43 int i, ret, flags = O_RDWR | O_SYNC;
44
45 if ((fp = fopen("/proc/mtd", "r"))) {
46 while (fgets(dev, sizeof(dev), fp)) {
47 if (sscanf(dev, "mtd%d:", &i) && strstr(dev, mtd)) {
48 snprintf(dev, sizeof(dev), "/dev/mtd%s/%d", (block ? "block" : ""), i);
49 ret = open(dev, flags);
50 if (ret < 0) {
51 snprintf(dev, sizeof(dev), "/dev/mtd%s%d", (block ? "block" : ""), i);
52 ret = open(dev, flags);
53 }
54 fclose(fp);
55 return ret;
56 }
57 }
58 fclose(fp);
59 }
60
61 return open(mtd, flags);
62 }
63
64 static void mtd_volume_close(struct mtd_volume *p)
65 {
66 if (!p->fd)
67 return;
68
69 close(p->fd);
70 p->fd = 0;
71 }
72
73 static int mtd_volume_load(struct mtd_volume *p)
74 {
75 struct volume *v = &p->v;
76 struct mtd_info_user mtdInfo;
77 struct erase_info_user mtdLockInfo;
78
79 if (p->fd) {
80 lseek(p->fd, 0, SEEK_SET);
81 return 0;
82 }
83
84 if (!p->chr)
85 return -1;
86
87 p->fd = mtd_open(p->chr, 0);
88 if (p->fd < 0) {
89 p->fd = 0;
90 ULOG_ERR("Could not open mtd device: %s\n", p->chr);
91 return -1;
92 }
93
94 if (ioctl(p->fd, MEMGETINFO, &mtdInfo)) {
95 mtd_volume_close(p);
96 ULOG_ERR("Could not get MTD device info from %s\n", p->chr);
97 return -1;
98 }
99
100 v->size = mtdInfo.size;
101 v->block_size = mtdInfo.erasesize;
102 switch (mtdInfo.type) {
103 case MTD_NORFLASH:
104 v->type = NORFLASH;
105 break;
106 case MTD_NANDFLASH:
107 v->type = NANDFLASH;
108 break;
109 case MTD_UBIVOLUME:
110 v->type = UBIVOLUME;
111 break;
112 default:
113 v->type = UNKNOWN_TYPE;
114 break;
115 }
116
117 mtdLockInfo.start = 0;
118 mtdLockInfo.length = v->size;
119 ioctl(p->fd, MEMUNLOCK, &mtdLockInfo);
120
121 return 0;
122 }
123
124 static char* mtd_find_index(char *name)
125 {
126 FILE *fp = fopen("/proc/mtd", "r");
127 static char line[256];
128 char *index = NULL;
129
130 if(!fp)
131 return index;
132
133 while (!index && fgets(line, sizeof(line), fp)) {
134 char *ret;
135
136 if ((ret = strstr(line, name)) && (ret[strlen(name)] == '"')) {
137 char *eol = strstr(line, ":");
138
139 if (!eol)
140 continue;
141
142 *eol = '\0';
143 index = &line[3];
144 }
145 }
146
147 fclose(fp);
148
149 return index;
150 }
151
152 static struct volume *mtd_volume_find(char *name)
153 {
154 char *idx = mtd_find_index(name);
155 struct mtd_volume *p;
156 struct volume *v;
157 char buffer[32];
158
159 if (!idx)
160 return NULL;
161
162 p = calloc(1, sizeof(struct mtd_volume));
163 if (!p)
164 return NULL;
165
166 v = &p->v;
167 v->name = strdup(name);
168 v->drv = &mtd_driver;
169 p->idx = atoi(idx);
170
171 snprintf(buffer, sizeof(buffer), "/dev/mtdblock%s", idx);
172 v->blk = strdup(buffer);
173
174 snprintf(buffer, sizeof(buffer), "/dev/mtd%s", idx);
175 p->chr = strdup(buffer);
176
177 if (mtd_volume_load(p)) {
178 ULOG_ERR("reading %s failed\n", v->name);
179 free(p);
180 return NULL;
181 }
182
183 return v;
184 }
185
186 static int mtd_volume_identify(struct volume *v)
187 {
188 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
189 __u32 deadc0de;
190 size_t sz;
191
192 if (mtd_volume_load(p)) {
193 ULOG_ERR("reading %s failed\n", v->name);
194 return -1;
195 }
196
197 sz = read(p->fd, &deadc0de, sizeof(deadc0de));
198
199 if (sz != sizeof(deadc0de)) {
200 ULOG_ERR("reading %s failed: %m\n", v->name);
201 return -1;
202 }
203
204 if (deadc0de == ~0) {
205 struct mtd_oob_buf oob = {
206 .start = 0,
207 .length = sizeof(deadc0de),
208 .ptr = (void *)&deadc0de,
209 };
210
211 ioctl(p->fd, MEMREADOOB, &oob);
212 }
213
214 if (deadc0de == __be32_to_cpu(0x4f575254))
215 return FS_SNAPSHOT;
216
217 deadc0de = __be32_to_cpu(deadc0de);
218 if (deadc0de == 0xdeadc0de) {
219 return FS_DEADCODE;
220 }
221
222 if (__be16_to_cpu(deadc0de) == 0x1985 ||
223 __be16_to_cpu(deadc0de >> 16) == 0x1985)
224 return FS_JFFS2;
225
226 if (v->type == UBIVOLUME && deadc0de == 0xffffffff) {
227 return FS_JFFS2;
228 }
229
230 return FS_NONE;
231 }
232
233 static int mtd_volume_erase(struct volume *v, int offset, int len)
234 {
235 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
236 struct erase_info_user eiu;
237 int first_block, num_blocks;
238
239 if (mtd_volume_load(p))
240 return -1;
241
242 if (offset % v->block_size || len % v->block_size) {
243 ULOG_ERR("mtd erase needs to be block aligned\n");
244 return -1;
245 }
246
247 first_block = offset / v->block_size;
248 num_blocks = len / v->block_size;
249 eiu.length = v->block_size;
250
251 for (eiu.start = first_block * v->block_size;
252 eiu.start < v->size && eiu.start < (first_block + num_blocks) * v->block_size;
253 eiu.start += v->block_size) {
254 ULOG_INFO("erasing %x %x\n", eiu.start, v->block_size);
255 ioctl(p->fd, MEMUNLOCK, &eiu);
256 if (ioctl(p->fd, MEMERASE, &eiu))
257 ULOG_ERR("Failed to erase block at 0x%x\n", eiu.start);
258 }
259
260 mtd_volume_close(p);
261
262 return 0;
263 }
264
265 static int mtd_volume_erase_all(struct volume *v)
266 {
267 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
268
269 mtd_volume_erase(v, 0, v->size);
270 mtd_volume_close(p);
271
272 return 0;
273 }
274
275 static int mtd_volume_init(struct volume *v)
276 {
277 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
278 struct mtd_info_user mtdinfo;
279 int ret;
280
281 if (mtd_volume_load(p))
282 return -1;
283
284 ret = ioctl(p->fd, MEMGETINFO, &mtdinfo);
285 if (ret) {
286 ULOG_ERR("ioctl(%d, MEMGETINFO) failed: %m\n", p->fd);
287 } else {
288 struct erase_info_user mtdlock;
289
290 mtdlock.start = 0;
291 mtdlock.length = mtdinfo.size;
292 ioctl(p->fd, MEMUNLOCK, &mtdlock);
293 }
294
295 return ret;
296 }
297
298 static int mtd_volume_read(struct volume *v, void *buf, int offset, int length)
299 {
300 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
301
302 if (mtd_volume_load(p))
303 return -1;
304
305 if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) {
306 ULOG_ERR("lseek/read failed\n");
307 return -1;
308 }
309
310 if (read(p->fd, buf, length) == -1) {
311 ULOG_ERR("read failed\n");
312 return -1;
313 }
314
315 return 0;
316 }
317
318 static int mtd_volume_write(struct volume *v, void *buf, int offset, int length)
319 {
320 struct mtd_volume *p = container_of(v, struct mtd_volume, v);;
321
322 if (mtd_volume_load(p))
323 return -1;
324
325 if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) {
326 ULOG_ERR("lseek/write failed at offset %d\n", offset);
327 perror("lseek");
328 return -1;
329 }
330
331 if (write(p->fd, buf, length) == -1) {
332 ULOG_ERR("write failed\n");
333 return -1;
334 }
335
336 return 0;
337 }
338
339 static struct driver mtd_driver = {
340 .name = "mtd",
341 .find = mtd_volume_find,
342 .init = mtd_volume_init,
343 .erase = mtd_volume_erase,
344 .erase_all = mtd_volume_erase_all,
345 .read = mtd_volume_read,
346 .write = mtd_volume_write,
347 .identify = mtd_volume_identify,
348 };
349 DRIVER(mtd_driver);