60326fed12a6c0cf20249a1678ff3ce372dcc394
[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_priv {
31         int     fd;
32         int     idx;
33         char    *chr;
34 };
35
36 static struct driver mtd_driver;
37
38 static int mtd_open(const char *mtd, int block)
39 {
40         FILE *fp;
41         char dev[PATH_MAX];
42         int i, ret, flags = O_RDWR | O_SYNC;
43
44         if ((fp = fopen("/proc/mtd", "r"))) {
45                 while (fgets(dev, sizeof(dev), fp)) {
46                         if (sscanf(dev, "mtd%d:", &i) && strstr(dev, mtd)) {
47                                 snprintf(dev, sizeof(dev), "/dev/mtd%s/%d", (block ? "block" : ""), i);
48                                 ret = open(dev, flags);
49                                 if (ret < 0) {
50                                         snprintf(dev, sizeof(dev), "/dev/mtd%s%d", (block ? "block" : ""), i);
51                                         ret = open(dev, flags);
52                                 }
53                                 fclose(fp);
54                                 return ret;
55                         }
56                 }
57                 fclose(fp);
58         }
59
60         return open(mtd, flags);
61 }
62
63 static void mtd_volume_close(struct volume *v)
64 {
65         struct mtd_priv *p = (struct mtd_priv*) v->priv;
66
67         if (!p->fd)
68                 return;
69
70         close(p->fd);
71         p->fd = 0;
72 }
73
74 static int mtd_volume_load(struct volume *v)
75 {
76         struct mtd_priv *p = (struct mtd_priv*) v->priv;
77         struct mtd_info_user mtdInfo;
78         struct erase_info_user mtdLockInfo;
79
80         if (p->fd)
81                 return 0;
82
83         if (!p->chr)
84                 return -1;
85
86         p->fd = mtd_open(p->chr, 0);
87         if (p->fd < 0) {
88                 p->fd = 0;
89                 fprintf(stderr, "Could not open mtd device: %s\n", p->chr);
90                 return -1;
91         }
92
93         if (ioctl(p->fd, MEMGETINFO, &mtdInfo)) {
94                 mtd_volume_close(v);
95                 fprintf(stderr, "Could not get MTD device info from %s\n", p->chr);
96                 return -1;
97         }
98
99         v->size = mtdInfo.size;
100         v->block_size = mtdInfo.erasesize;
101         switch (mtdInfo.type) {
102         case MTD_NORFLASH:
103                 v->type = NORFLASH;
104                 break;
105         case MTD_NANDFLASH:
106                 v->type = NANDFLASH;
107                 break;
108         case MTD_UBIVOLUME:
109                 v->type = UBIVOLUME;
110                 break;
111         default:
112                 v->type = UNKNOWN_TYPE;
113                 break;
114         }
115
116         mtdLockInfo.start = 0;
117         mtdLockInfo.length = v->size;
118         ioctl(p->fd, MEMUNLOCK, &mtdLockInfo);
119
120         return 0;
121 }
122
123 static char* mtd_find_index(char *name)
124 {
125         FILE *fp = fopen("/proc/mtd", "r");
126         static char line[256];
127         char *index = NULL;
128
129         if(!fp)
130                 return index;
131
132         while (!index && fgets(line, sizeof(line), fp)) {
133                 if (strstr(line, name)) {
134                         char *eol = strstr(line, ":");
135
136                         if (!eol)
137                                 continue;
138
139                         *eol = '\0';
140                         index = &line[3];
141                 }
142         }
143
144         fclose(fp);
145
146         return index;
147 }
148
149 static int mtd_volume_find(struct volume *v, char *name)
150 {
151         char *idx = mtd_find_index(name);
152         struct mtd_priv *p;
153         char buffer[32];
154
155         if (!idx)
156                 return -1;
157
158         p = calloc(1, sizeof(struct mtd_priv));
159         if (!p)
160                 return -1;
161
162         v->priv = p;
163         v->name = strdup(name);
164         v->drv = &mtd_driver;
165         p->idx = atoi(idx);
166
167         snprintf(buffer, sizeof(buffer), "/dev/mtdblock%s", idx);
168         v->blk = strdup(buffer);
169
170         snprintf(buffer, sizeof(buffer), "/dev/mtd%s", idx);
171         p->chr = strdup(buffer);
172
173         return 0;
174 }
175
176 static int mtd_volume_identify(struct volume *v)
177 {
178         struct mtd_priv *p = (struct mtd_priv*) v->priv;
179         __u32 deadc0de;
180         __u16 jffs2;
181         size_t sz;
182
183         if (mtd_volume_load(v)) {
184                 fprintf(stderr, "reading %s failed\n", v->name);
185                 return -1;
186         }
187
188         sz = read(p->fd, &deadc0de, sizeof(deadc0de));
189
190         if (sz != sizeof(deadc0de)) {
191                 fprintf(stderr, "reading %s failed: %s\n", v->name, strerror(errno));
192                 return -1;
193         }
194
195         if (deadc0de == 0x4f575254)
196                 return FS_SNAPSHOT;
197
198         deadc0de = __be32_to_cpu(deadc0de);
199         if (deadc0de == 0xdeadc0de) {
200                 fprintf(stderr, "jffs2 is not ready - marker found\n");
201                 return FS_DEADCODE;
202         }
203
204         jffs2 = __be16_to_cpu(deadc0de >> 16);
205         if (jffs2 == 0x1985) {
206                 fprintf(stderr, "jffs2 is ready\n");
207                 return FS_JFFS2;
208         }
209
210         if (v->type == UBIVOLUME && deadc0de == 0xffffffff) {
211                 fprintf(stderr, "jffs2 is ready\n");
212                 return FS_JFFS2;
213         }
214
215         fprintf(stderr, "No jffs2 marker was found\n");
216
217         return FS_NONE;
218 }
219
220 static int mtd_volume_erase(struct volume *v, int offset, int len)
221 {
222         struct mtd_priv *p = (struct mtd_priv*) v->priv;
223         struct erase_info_user eiu;
224         int first_block, num_blocks;
225
226         if (mtd_volume_load(v))
227                 return -1;
228
229         if (offset % v->block_size || len % v->block_size) {
230                 fprintf(stderr, "mtd erase needs to be block aligned\n");
231                 return -1;
232         }
233
234         first_block = offset / v->block_size;
235         num_blocks = len / v->block_size;
236         eiu.length = v->block_size;
237
238         for (eiu.start = first_block * v->block_size;
239                         eiu.start < v->size && eiu.start < (first_block + num_blocks) * v->block_size;
240                         eiu.start += v->block_size) {
241                 fprintf(stderr, "erasing %x %x\n", eiu.start, v->block_size);
242                 ioctl(p->fd, MEMUNLOCK, &eiu);
243                 if (ioctl(p->fd, MEMERASE, &eiu))
244                         fprintf(stderr, "Failed to erase block at 0x%x\n", eiu.start);
245         }
246
247         mtd_volume_close(v);
248
249         return 0;
250 }
251
252 static int mtd_volume_erase_all(struct volume *v)
253 {
254         mtd_volume_erase(v, 0, v->size);
255         mtd_volume_close(v);
256
257         return 0;
258 }
259
260 static int mtd_volume_init(struct volume *v)
261 {
262         struct mtd_priv *p = (struct mtd_priv*) v->priv;
263         struct mtd_info_user mtdinfo;
264         int ret;
265
266         if (mtd_volume_load(v))
267                 return -1;
268
269         ret = ioctl(p->fd, MEMGETINFO, &mtdinfo);
270         if (ret) {
271                 fprintf(stderr, "ioctl(%d, MEMGETINFO) failed: %s\n", p->fd, strerror(errno));
272         } else {
273                 struct erase_info_user mtdlock;
274
275                 mtdlock.start = 0;
276                 mtdlock.length = mtdinfo.size;
277                 ioctl(p->fd, MEMUNLOCK, &mtdlock);
278         }
279
280         return ret;
281 }
282
283 static int mtd_volume_read(struct volume *v, void *buf, int offset, int length)
284 {
285         struct mtd_priv *p = (struct mtd_priv*) v->priv;
286
287         if (mtd_volume_load(v))
288                 return -1;
289
290         if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) {
291                 fprintf(stderr, "lseek/read failed\n");
292                 return -1;
293         }
294
295         if (read(p->fd, buf, length) == -1) {
296                 fprintf(stderr, "read failed\n");
297                 return -1;
298         }
299
300         return 0;
301 }
302
303 static int mtd_volume_write(struct volume *v, void *buf, int offset, int length)
304 {
305         struct mtd_priv *p = (struct mtd_priv*) v->priv;
306
307         if (mtd_volume_load(v))
308                 return -1;
309
310         if (lseek(p->fd, offset, SEEK_SET) == (off_t) -1) {
311                 fprintf(stderr, "lseek/write failed at offset %d\n", offset);
312                 perror("lseek");
313                 return -1;
314         }
315
316         if (write(p->fd, buf, length) == -1) {
317                 fprintf(stderr, "write failed\n");
318                 return -1;
319         }
320
321         return 0;
322 }
323
324 static struct driver mtd_driver = {
325         .name = "mtd",
326         .find = mtd_volume_find,
327         .init = mtd_volume_init,
328         .erase = mtd_volume_erase,
329         .erase_all = mtd_volume_erase_all,
330         .read = mtd_volume_read,
331         .write = mtd_volume_write,
332         .identify = mtd_volume_identify,
333 };
334 DRIVER(mtd_driver);