1/*
2 * YAFFS: Yet Another Flash File System. A NAND-flash specific file system.
3 *
4 * Copyright (C) 2002-2011 Aleph One Ltd.
5 *   for Toby Churchill Ltd and Brightstar Engineering
6 *
7 * Created by Charles Manning <charles@aleph1.co.uk>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
12 */
13
14#include "yportenv.h"
15
16#include "yaffs_mtdif.h"
17
18#include "linux/mtd/mtd.h"
19#include "linux/types.h"
20#include "linux/time.h"
21#include "linux/mtd/nand.h"
22#include "linux/kernel.h"
23#include "linux/version.h"
24#include "linux/types.h"
25#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0))
26#include "uapi/linux/major.h"
27#endif
28
29#include "yaffs_trace.h"
30#include "yaffs_guts.h"
31#include "yaffs_linux.h"
32
33#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 2, 0))
34#define MTD_OPS_AUTO_OOB MTD_OOB_AUTO
35#endif
36
37
38#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0))
39#define mtd_erase(m, ei) (m)->erase(m, ei)
40#define mtd_write_oob(m, addr, pops) (m)->write_oob(m, addr, pops)
41#define mtd_read_oob(m, addr, pops) (m)->read_oob(m, addr, pops)
42#define mtd_block_isbad(m, offs) (m)->block_isbad(m, offs)
43#define mtd_block_markbad(m, offs) (m)->block_markbad(m, offs)
44#endif
45
46
47
48int nandmtd_erase_block(struct yaffs_dev *dev, int block_no)
49{
50	struct mtd_info *mtd = yaffs_dev_to_mtd(dev);
51	u32 addr =
52	    ((loff_t) block_no) * dev->param.total_bytes_per_chunk *
53	    dev->param.chunks_per_block;
54	struct erase_info ei;
55	int retval = 0;
56
57	ei.mtd = mtd;
58	ei.addr = addr;
59	ei.len = dev->param.total_bytes_per_chunk * dev->param.chunks_per_block;
60	ei.time = 1000;
61	ei.retries = 2;
62	ei.callback = NULL;
63	ei.priv = (u_long) dev;
64
65	retval = mtd_erase(mtd, &ei);
66
67	if (retval == 0)
68		return YAFFS_OK;
69
70	return YAFFS_FAIL;
71}
72
73
74static 	int yaffs_mtd_write(struct yaffs_dev *dev, int nand_chunk,
75				   const u8 *data, int data_len,
76				   const u8 *oob, int oob_len)
77{
78	struct mtd_info *mtd = yaffs_dev_to_mtd(dev);
79	loff_t addr;
80	struct mtd_oob_ops ops;
81	int retval;
82
83	yaffs_trace(YAFFS_TRACE_MTD,
84			"yaffs_mtd_write(%p, %d, %p, %d, %p, %d)\n",
85			dev, nand_chunk, data, data_len, oob, oob_len);
86
87	if (!data || !data_len) {
88		data = NULL;
89		data_len = 0;
90	}
91
92	if (!oob || !oob_len) {
93		oob = NULL;
94		oob_len = 0;
95	}
96
97	addr = ((loff_t) nand_chunk) * dev->param.total_bytes_per_chunk;
98	memset(&ops, 0, sizeof(ops));
99	ops.mode = MTD_OPS_AUTO_OOB;
100	ops.len = (data) ? data_len : 0;
101	ops.ooblen = oob_len;
102	ops.datbuf = (u8 *)data;
103	ops.oobbuf = (u8 *)oob;
104
105	retval = mtd_write_oob(mtd, addr, &ops);
106	if (retval) {
107		yaffs_trace(YAFFS_TRACE_MTD,
108			"write_oob failed, chunk %d, mtd error %d",
109			nand_chunk, retval);
110	}
111	return retval ? YAFFS_FAIL : YAFFS_OK;
112}
113
114static int yaffs_mtd_read(struct yaffs_dev *dev, int nand_chunk,
115				   u8 *data, int data_len,
116				   u8 *oob, int oob_len,
117				   enum yaffs_ecc_result *ecc_result)
118{
119	struct mtd_info *mtd = yaffs_dev_to_mtd(dev);
120	loff_t addr;
121	struct mtd_oob_ops ops;
122	int retval;
123
124	addr = ((loff_t) nand_chunk) * dev->param.total_bytes_per_chunk;
125	memset(&ops, 0, sizeof(ops));
126	ops.mode = MTD_OPS_AUTO_OOB;
127	ops.len = (data) ? data_len : 0;
128	ops.ooblen = oob_len;
129	ops.datbuf = data;
130	ops.oobbuf = oob;
131
132#if (MTD_VERSION_CODE < MTD_VERSION(2, 6, 20))
133	/* In MTD 2.6.18 to 2.6.19 nand_base.c:nand_do_read_oob() has a bug;
134	 * help it out with ops.len = ops.ooblen when ops.datbuf == NULL.
135	 */
136	ops.len = (ops.datbuf) ? ops.len : ops.ooblen;
137#endif
138	/* Read page and oob using MTD.
139	 * Check status and determine ECC result.
140	 */
141	retval = mtd_read_oob(mtd, addr, &ops);
142	if (retval)
143		yaffs_trace(YAFFS_TRACE_MTD,
144			"read_oob failed, chunk %d, mtd error %d",
145			nand_chunk, retval);
146
147	switch (retval) {
148	case 0:
149		/* no error */
150		if(ecc_result)
151			*ecc_result = YAFFS_ECC_RESULT_NO_ERROR;
152		break;
153
154	case -EUCLEAN:
155		/* MTD's ECC fixed the data */
156		if(ecc_result)
157			*ecc_result = YAFFS_ECC_RESULT_FIXED;
158		dev->n_ecc_fixed++;
159		break;
160
161	case -EBADMSG:
162	default:
163		/* MTD's ECC could not fix the data */
164		dev->n_ecc_unfixed++;
165		if(ecc_result)
166			*ecc_result = YAFFS_ECC_RESULT_UNFIXED;
167		return YAFFS_FAIL;
168	}
169
170	return YAFFS_OK;
171}
172
173static 	int yaffs_mtd_erase(struct yaffs_dev *dev, int block_no)
174{
175	struct mtd_info *mtd = yaffs_dev_to_mtd(dev);
176
177	loff_t addr;
178	struct erase_info ei;
179	int retval = 0;
180	u32 block_size;
181
182	block_size = dev->param.total_bytes_per_chunk *
183		     dev->param.chunks_per_block;
184	addr = ((loff_t) block_no) * block_size;
185
186	ei.mtd = mtd;
187	ei.addr = addr;
188	ei.len = block_size;
189	ei.time = 1000;
190	ei.retries = 2;
191	ei.callback = NULL;
192	ei.priv = (u_long) dev;
193
194	retval = mtd_erase(mtd, &ei);
195
196	if (retval == 0)
197		return YAFFS_OK;
198
199	return YAFFS_FAIL;
200}
201
202static int yaffs_mtd_mark_bad(struct yaffs_dev *dev, int block_no)
203{
204	struct mtd_info *mtd = yaffs_dev_to_mtd(dev);
205	int blocksize = dev->param.chunks_per_block * dev->param.total_bytes_per_chunk;
206	int retval;
207
208	yaffs_trace(YAFFS_TRACE_BAD_BLOCKS, "marking block %d bad", block_no);
209
210	retval = mtd_block_markbad(mtd, (loff_t) blocksize * block_no);
211	return (retval) ? YAFFS_FAIL : YAFFS_OK;
212}
213
214static int yaffs_mtd_check_bad(struct yaffs_dev *dev, int block_no)
215{
216	struct mtd_info *mtd = yaffs_dev_to_mtd(dev);
217	int blocksize = dev->param.chunks_per_block * dev->param.total_bytes_per_chunk;
218	int retval;
219
220	yaffs_trace(YAFFS_TRACE_MTD, "checking block %d bad", block_no);
221
222	retval = mtd_block_isbad(mtd, (loff_t) blocksize * block_no);
223	return (retval) ? YAFFS_FAIL : YAFFS_OK;
224}
225
226static int yaffs_mtd_initialise(struct yaffs_dev *dev)
227{
228	return YAFFS_OK;
229}
230
231static int yaffs_mtd_deinitialise(struct yaffs_dev *dev)
232{
233	return YAFFS_OK;
234}
235
236
237void yaffs_mtd_drv_install(struct yaffs_dev *dev)
238{
239	struct yaffs_driver *drv = &dev->drv;
240
241	drv->drv_write_chunk_fn = yaffs_mtd_write;
242	drv->drv_read_chunk_fn = yaffs_mtd_read;
243	drv->drv_erase_fn = yaffs_mtd_erase;
244	drv->drv_mark_bad_fn = yaffs_mtd_mark_bad;
245	drv->drv_check_bad_fn = yaffs_mtd_check_bad;
246	drv->drv_initialise_fn = yaffs_mtd_initialise;
247	drv->drv_deinitialise_fn = yaffs_mtd_deinitialise;
248}
249
250
251struct mtd_info * yaffs_get_mtd_device(dev_t sdev)
252{
253	struct mtd_info *mtd;
254
255	mtd = yaffs_get_mtd_device(sdev);
256
257	/* Check it's an mtd device..... */
258	if (MAJOR(sdev) != MTD_BLOCK_MAJOR)
259		return NULL;	/* This isn't an mtd device */
260
261	/* Check it's NAND */
262	if (mtd->type != MTD_NANDFLASH) {
263		yaffs_trace(YAFFS_TRACE_ALWAYS,
264			"yaffs: MTD device is not NAND it's type %d",
265			mtd->type);
266		return NULL;
267	}
268
269	yaffs_trace(YAFFS_TRACE_OS, " %s %d", WRITE_SIZE_STR, WRITE_SIZE(mtd));
270	yaffs_trace(YAFFS_TRACE_OS, " oobsize %d", mtd->oobsize);
271	yaffs_trace(YAFFS_TRACE_OS, " erasesize %d", mtd->erasesize);
272#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
273	yaffs_trace(YAFFS_TRACE_OS, " size %u", mtd->size);
274#else
275	yaffs_trace(YAFFS_TRACE_OS, " size %lld", mtd->size);
276#endif
277
278	return mtd;
279}
280
281int yaffs_verify_mtd(struct mtd_info *mtd, int yaffs_version, int inband_tags)
282{
283	if (yaffs_version == 2) {
284		if ((WRITE_SIZE(mtd) < YAFFS_MIN_YAFFS2_CHUNK_SIZE ||
285		     mtd->oobsize < YAFFS_MIN_YAFFS2_SPARE_SIZE) &&
286		    !inband_tags) {
287			yaffs_trace(YAFFS_TRACE_ALWAYS,
288				"MTD device does not have the right page sizes"
289			);
290			return -1;
291		}
292	} else {
293		if (WRITE_SIZE(mtd) < YAFFS_BYTES_PER_CHUNK ||
294		    mtd->oobsize != YAFFS_BYTES_PER_SPARE) {
295			yaffs_trace(YAFFS_TRACE_ALWAYS,
296				"MTD device does not support have the right page sizes"
297			);
298			return -1;
299		}
300	}
301
302	return 0;
303}
304
305
306void yaffs_put_mtd_device(struct mtd_info *mtd)
307{
308	if(mtd)
309		put_mtd_device(mtd);
310}
311