diff options
Diffstat (limited to 'fs/yaffs2/yaffs_tagscompat.c')
-rw-r--r-- | fs/yaffs2/yaffs_tagscompat.c | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/fs/yaffs2/yaffs_tagscompat.c b/fs/yaffs2/yaffs_tagscompat.c new file mode 100644 index 00000000000..7578075d9ac --- /dev/null +++ b/fs/yaffs2/yaffs_tagscompat.c | |||
@@ -0,0 +1,422 @@ | |||
1 | /* | ||
2 | * YAFFS: Yet Another Flash File System. A NAND-flash specific file system. | ||
3 | * | ||
4 | * Copyright (C) 2002-2010 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 "yaffs_guts.h" | ||
15 | #include "yaffs_tagscompat.h" | ||
16 | #include "yaffs_ecc.h" | ||
17 | #include "yaffs_getblockinfo.h" | ||
18 | #include "yaffs_trace.h" | ||
19 | |||
20 | static void yaffs_handle_rd_data_error(struct yaffs_dev *dev, int nand_chunk); | ||
21 | |||
22 | |||
23 | /********** Tags ECC calculations *********/ | ||
24 | |||
25 | void yaffs_calc_ecc(const u8 * data, struct yaffs_spare *spare) | ||
26 | { | ||
27 | yaffs_ecc_cacl(data, spare->ecc1); | ||
28 | yaffs_ecc_cacl(&data[256], spare->ecc2); | ||
29 | } | ||
30 | |||
31 | void yaffs_calc_tags_ecc(struct yaffs_tags *tags) | ||
32 | { | ||
33 | /* Calculate an ecc */ | ||
34 | |||
35 | unsigned char *b = ((union yaffs_tags_union *)tags)->as_bytes; | ||
36 | unsigned i, j; | ||
37 | unsigned ecc = 0; | ||
38 | unsigned bit = 0; | ||
39 | |||
40 | tags->ecc = 0; | ||
41 | |||
42 | for (i = 0; i < 8; i++) { | ||
43 | for (j = 1; j & 0xff; j <<= 1) { | ||
44 | bit++; | ||
45 | if (b[i] & j) | ||
46 | ecc ^= bit; | ||
47 | } | ||
48 | } | ||
49 | |||
50 | tags->ecc = ecc; | ||
51 | |||
52 | } | ||
53 | |||
54 | int yaffs_check_tags_ecc(struct yaffs_tags *tags) | ||
55 | { | ||
56 | unsigned ecc = tags->ecc; | ||
57 | |||
58 | yaffs_calc_tags_ecc(tags); | ||
59 | |||
60 | ecc ^= tags->ecc; | ||
61 | |||
62 | if (ecc && ecc <= 64) { | ||
63 | /* TODO: Handle the failure better. Retire? */ | ||
64 | unsigned char *b = ((union yaffs_tags_union *)tags)->as_bytes; | ||
65 | |||
66 | ecc--; | ||
67 | |||
68 | b[ecc / 8] ^= (1 << (ecc & 7)); | ||
69 | |||
70 | /* Now recvalc the ecc */ | ||
71 | yaffs_calc_tags_ecc(tags); | ||
72 | |||
73 | return 1; /* recovered error */ | ||
74 | } else if (ecc) { | ||
75 | /* Wierd ecc failure value */ | ||
76 | /* TODO Need to do somethiong here */ | ||
77 | return -1; /* unrecovered error */ | ||
78 | } | ||
79 | |||
80 | return 0; | ||
81 | } | ||
82 | |||
83 | /********** Tags **********/ | ||
84 | |||
85 | static void yaffs_load_tags_to_spare(struct yaffs_spare *spare_ptr, | ||
86 | struct yaffs_tags *tags_ptr) | ||
87 | { | ||
88 | union yaffs_tags_union *tu = (union yaffs_tags_union *)tags_ptr; | ||
89 | |||
90 | yaffs_calc_tags_ecc(tags_ptr); | ||
91 | |||
92 | spare_ptr->tb0 = tu->as_bytes[0]; | ||
93 | spare_ptr->tb1 = tu->as_bytes[1]; | ||
94 | spare_ptr->tb2 = tu->as_bytes[2]; | ||
95 | spare_ptr->tb3 = tu->as_bytes[3]; | ||
96 | spare_ptr->tb4 = tu->as_bytes[4]; | ||
97 | spare_ptr->tb5 = tu->as_bytes[5]; | ||
98 | spare_ptr->tb6 = tu->as_bytes[6]; | ||
99 | spare_ptr->tb7 = tu->as_bytes[7]; | ||
100 | } | ||
101 | |||
102 | static void yaffs_get_tags_from_spare(struct yaffs_dev *dev, | ||
103 | struct yaffs_spare *spare_ptr, | ||
104 | struct yaffs_tags *tags_ptr) | ||
105 | { | ||
106 | union yaffs_tags_union *tu = (union yaffs_tags_union *)tags_ptr; | ||
107 | int result; | ||
108 | |||
109 | tu->as_bytes[0] = spare_ptr->tb0; | ||
110 | tu->as_bytes[1] = spare_ptr->tb1; | ||
111 | tu->as_bytes[2] = spare_ptr->tb2; | ||
112 | tu->as_bytes[3] = spare_ptr->tb3; | ||
113 | tu->as_bytes[4] = spare_ptr->tb4; | ||
114 | tu->as_bytes[5] = spare_ptr->tb5; | ||
115 | tu->as_bytes[6] = spare_ptr->tb6; | ||
116 | tu->as_bytes[7] = spare_ptr->tb7; | ||
117 | |||
118 | result = yaffs_check_tags_ecc(tags_ptr); | ||
119 | if (result > 0) | ||
120 | dev->n_tags_ecc_fixed++; | ||
121 | else if (result < 0) | ||
122 | dev->n_tags_ecc_unfixed++; | ||
123 | } | ||
124 | |||
125 | static void yaffs_spare_init(struct yaffs_spare *spare) | ||
126 | { | ||
127 | memset(spare, 0xFF, sizeof(struct yaffs_spare)); | ||
128 | } | ||
129 | |||
130 | static int yaffs_wr_nand(struct yaffs_dev *dev, | ||
131 | int nand_chunk, const u8 * data, | ||
132 | struct yaffs_spare *spare) | ||
133 | { | ||
134 | if (nand_chunk < dev->param.start_block * dev->param.chunks_per_block) { | ||
135 | yaffs_trace(YAFFS_TRACE_ERROR, | ||
136 | "**>> yaffs chunk %d is not valid", | ||
137 | nand_chunk); | ||
138 | return YAFFS_FAIL; | ||
139 | } | ||
140 | |||
141 | return dev->param.write_chunk_fn(dev, nand_chunk, data, spare); | ||
142 | } | ||
143 | |||
144 | static int yaffs_rd_chunk_nand(struct yaffs_dev *dev, | ||
145 | int nand_chunk, | ||
146 | u8 * data, | ||
147 | struct yaffs_spare *spare, | ||
148 | enum yaffs_ecc_result *ecc_result, | ||
149 | int correct_errors) | ||
150 | { | ||
151 | int ret_val; | ||
152 | struct yaffs_spare local_spare; | ||
153 | |||
154 | if (!spare && data) { | ||
155 | /* If we don't have a real spare, then we use a local one. */ | ||
156 | /* Need this for the calculation of the ecc */ | ||
157 | spare = &local_spare; | ||
158 | } | ||
159 | |||
160 | if (!dev->param.use_nand_ecc) { | ||
161 | ret_val = | ||
162 | dev->param.read_chunk_fn(dev, nand_chunk, data, spare); | ||
163 | if (data && correct_errors) { | ||
164 | /* Do ECC correction */ | ||
165 | /* Todo handle any errors */ | ||
166 | int ecc_result1, ecc_result2; | ||
167 | u8 calc_ecc[3]; | ||
168 | |||
169 | yaffs_ecc_cacl(data, calc_ecc); | ||
170 | ecc_result1 = | ||
171 | yaffs_ecc_correct(data, spare->ecc1, calc_ecc); | ||
172 | yaffs_ecc_cacl(&data[256], calc_ecc); | ||
173 | ecc_result2 = | ||
174 | yaffs_ecc_correct(&data[256], spare->ecc2, | ||
175 | calc_ecc); | ||
176 | |||
177 | if (ecc_result1 > 0) { | ||
178 | yaffs_trace(YAFFS_TRACE_ERROR, | ||
179 | "**>>yaffs ecc error fix performed on chunk %d:0", | ||
180 | nand_chunk); | ||
181 | dev->n_ecc_fixed++; | ||
182 | } else if (ecc_result1 < 0) { | ||
183 | yaffs_trace(YAFFS_TRACE_ERROR, | ||
184 | "**>>yaffs ecc error unfixed on chunk %d:0", | ||
185 | nand_chunk); | ||
186 | dev->n_ecc_unfixed++; | ||
187 | } | ||
188 | |||
189 | if (ecc_result2 > 0) { | ||
190 | yaffs_trace(YAFFS_TRACE_ERROR, | ||
191 | "**>>yaffs ecc error fix performed on chunk %d:1", | ||
192 | nand_chunk); | ||
193 | dev->n_ecc_fixed++; | ||
194 | } else if (ecc_result2 < 0) { | ||
195 | yaffs_trace(YAFFS_TRACE_ERROR, | ||
196 | "**>>yaffs ecc error unfixed on chunk %d:1", | ||
197 | nand_chunk); | ||
198 | dev->n_ecc_unfixed++; | ||
199 | } | ||
200 | |||
201 | if (ecc_result1 || ecc_result2) { | ||
202 | /* We had a data problem on this page */ | ||
203 | yaffs_handle_rd_data_error(dev, nand_chunk); | ||
204 | } | ||
205 | |||
206 | if (ecc_result1 < 0 || ecc_result2 < 0) | ||
207 | *ecc_result = YAFFS_ECC_RESULT_UNFIXED; | ||
208 | else if (ecc_result1 > 0 || ecc_result2 > 0) | ||
209 | *ecc_result = YAFFS_ECC_RESULT_FIXED; | ||
210 | else | ||
211 | *ecc_result = YAFFS_ECC_RESULT_NO_ERROR; | ||
212 | } | ||
213 | } else { | ||
214 | /* Must allocate enough memory for spare+2*sizeof(int) */ | ||
215 | /* for ecc results from device. */ | ||
216 | struct yaffs_nand_spare nspare; | ||
217 | |||
218 | memset(&nspare, 0, sizeof(nspare)); | ||
219 | |||
220 | ret_val = dev->param.read_chunk_fn(dev, nand_chunk, data, | ||
221 | (struct yaffs_spare *) | ||
222 | &nspare); | ||
223 | memcpy(spare, &nspare, sizeof(struct yaffs_spare)); | ||
224 | if (data && correct_errors) { | ||
225 | if (nspare.eccres1 > 0) { | ||
226 | yaffs_trace(YAFFS_TRACE_ERROR, | ||
227 | "**>>mtd ecc error fix performed on chunk %d:0", | ||
228 | nand_chunk); | ||
229 | } else if (nspare.eccres1 < 0) { | ||
230 | yaffs_trace(YAFFS_TRACE_ERROR, | ||
231 | "**>>mtd ecc error unfixed on chunk %d:0", | ||
232 | nand_chunk); | ||
233 | } | ||
234 | |||
235 | if (nspare.eccres2 > 0) { | ||
236 | yaffs_trace(YAFFS_TRACE_ERROR, | ||
237 | "**>>mtd ecc error fix performed on chunk %d:1", | ||
238 | nand_chunk); | ||
239 | } else if (nspare.eccres2 < 0) { | ||
240 | yaffs_trace(YAFFS_TRACE_ERROR, | ||
241 | "**>>mtd ecc error unfixed on chunk %d:1", | ||
242 | nand_chunk); | ||
243 | } | ||
244 | |||
245 | if (nspare.eccres1 || nspare.eccres2) { | ||
246 | /* We had a data problem on this page */ | ||
247 | yaffs_handle_rd_data_error(dev, nand_chunk); | ||
248 | } | ||
249 | |||
250 | if (nspare.eccres1 < 0 || nspare.eccres2 < 0) | ||
251 | *ecc_result = YAFFS_ECC_RESULT_UNFIXED; | ||
252 | else if (nspare.eccres1 > 0 || nspare.eccres2 > 0) | ||
253 | *ecc_result = YAFFS_ECC_RESULT_FIXED; | ||
254 | else | ||
255 | *ecc_result = YAFFS_ECC_RESULT_NO_ERROR; | ||
256 | |||
257 | } | ||
258 | } | ||
259 | return ret_val; | ||
260 | } | ||
261 | |||
262 | /* | ||
263 | * Functions for robustisizing | ||
264 | */ | ||
265 | |||
266 | static void yaffs_handle_rd_data_error(struct yaffs_dev *dev, int nand_chunk) | ||
267 | { | ||
268 | int flash_block = nand_chunk / dev->param.chunks_per_block; | ||
269 | |||
270 | /* Mark the block for retirement */ | ||
271 | yaffs_get_block_info(dev, | ||
272 | flash_block + dev->block_offset)->needs_retiring = | ||
273 | 1; | ||
274 | yaffs_trace(YAFFS_TRACE_ERROR | YAFFS_TRACE_BAD_BLOCKS, | ||
275 | "**>>Block %d marked for retirement", | ||
276 | flash_block); | ||
277 | |||
278 | /* TODO: | ||
279 | * Just do a garbage collection on the affected block | ||
280 | * then retire the block | ||
281 | * NB recursion | ||
282 | */ | ||
283 | } | ||
284 | |||
285 | int yaffs_tags_compat_wr(struct yaffs_dev *dev, | ||
286 | int nand_chunk, | ||
287 | const u8 * data, const struct yaffs_ext_tags *ext_tags) | ||
288 | { | ||
289 | struct yaffs_spare spare; | ||
290 | struct yaffs_tags tags; | ||
291 | |||
292 | yaffs_spare_init(&spare); | ||
293 | |||
294 | if (ext_tags->is_deleted) | ||
295 | spare.page_status = 0; | ||
296 | else { | ||
297 | tags.obj_id = ext_tags->obj_id; | ||
298 | tags.chunk_id = ext_tags->chunk_id; | ||
299 | |||
300 | tags.n_bytes_lsb = ext_tags->n_bytes & 0x3ff; | ||
301 | |||
302 | if (dev->data_bytes_per_chunk >= 1024) | ||
303 | tags.n_bytes_msb = (ext_tags->n_bytes >> 10) & 3; | ||
304 | else | ||
305 | tags.n_bytes_msb = 3; | ||
306 | |||
307 | tags.serial_number = ext_tags->serial_number; | ||
308 | |||
309 | if (!dev->param.use_nand_ecc && data) | ||
310 | yaffs_calc_ecc(data, &spare); | ||
311 | |||
312 | yaffs_load_tags_to_spare(&spare, &tags); | ||
313 | |||
314 | } | ||
315 | |||
316 | return yaffs_wr_nand(dev, nand_chunk, data, &spare); | ||
317 | } | ||
318 | |||
319 | int yaffs_tags_compat_rd(struct yaffs_dev *dev, | ||
320 | int nand_chunk, | ||
321 | u8 * data, struct yaffs_ext_tags *ext_tags) | ||
322 | { | ||
323 | |||
324 | struct yaffs_spare spare; | ||
325 | struct yaffs_tags tags; | ||
326 | enum yaffs_ecc_result ecc_result = YAFFS_ECC_RESULT_UNKNOWN; | ||
327 | |||
328 | static struct yaffs_spare spare_ff; | ||
329 | static int init; | ||
330 | |||
331 | if (!init) { | ||
332 | memset(&spare_ff, 0xFF, sizeof(spare_ff)); | ||
333 | init = 1; | ||
334 | } | ||
335 | |||
336 | if (yaffs_rd_chunk_nand(dev, nand_chunk, data, &spare, &ecc_result, 1)) { | ||
337 | /* ext_tags may be NULL */ | ||
338 | if (ext_tags) { | ||
339 | |||
340 | int deleted = | ||
341 | (hweight8(spare.page_status) < 7) ? 1 : 0; | ||
342 | |||
343 | ext_tags->is_deleted = deleted; | ||
344 | ext_tags->ecc_result = ecc_result; | ||
345 | ext_tags->block_bad = 0; /* We're reading it */ | ||
346 | /* therefore it is not a bad block */ | ||
347 | ext_tags->chunk_used = | ||
348 | (memcmp(&spare_ff, &spare, sizeof(spare_ff)) != | ||
349 | 0) ? 1 : 0; | ||
350 | |||
351 | if (ext_tags->chunk_used) { | ||
352 | yaffs_get_tags_from_spare(dev, &spare, &tags); | ||
353 | |||
354 | ext_tags->obj_id = tags.obj_id; | ||
355 | ext_tags->chunk_id = tags.chunk_id; | ||
356 | ext_tags->n_bytes = tags.n_bytes_lsb; | ||
357 | |||
358 | if (dev->data_bytes_per_chunk >= 1024) | ||
359 | ext_tags->n_bytes |= | ||
360 | (((unsigned)tags. | ||
361 | n_bytes_msb) << 10); | ||
362 | |||
363 | ext_tags->serial_number = tags.serial_number; | ||
364 | } | ||
365 | } | ||
366 | |||
367 | return YAFFS_OK; | ||
368 | } else { | ||
369 | return YAFFS_FAIL; | ||
370 | } | ||
371 | } | ||
372 | |||
373 | int yaffs_tags_compat_mark_bad(struct yaffs_dev *dev, int flash_block) | ||
374 | { | ||
375 | |||
376 | struct yaffs_spare spare; | ||
377 | |||
378 | memset(&spare, 0xff, sizeof(struct yaffs_spare)); | ||
379 | |||
380 | spare.block_status = 'Y'; | ||
381 | |||
382 | yaffs_wr_nand(dev, flash_block * dev->param.chunks_per_block, NULL, | ||
383 | &spare); | ||
384 | yaffs_wr_nand(dev, flash_block * dev->param.chunks_per_block + 1, | ||
385 | NULL, &spare); | ||
386 | |||
387 | return YAFFS_OK; | ||
388 | |||
389 | } | ||
390 | |||
391 | int yaffs_tags_compat_query_block(struct yaffs_dev *dev, | ||
392 | int block_no, | ||
393 | enum yaffs_block_state *state, | ||
394 | u32 * seq_number) | ||
395 | { | ||
396 | |||
397 | struct yaffs_spare spare0, spare1; | ||
398 | static struct yaffs_spare spare_ff; | ||
399 | static int init; | ||
400 | enum yaffs_ecc_result dummy; | ||
401 | |||
402 | if (!init) { | ||
403 | memset(&spare_ff, 0xFF, sizeof(spare_ff)); | ||
404 | init = 1; | ||
405 | } | ||
406 | |||
407 | *seq_number = 0; | ||
408 | |||
409 | yaffs_rd_chunk_nand(dev, block_no * dev->param.chunks_per_block, NULL, | ||
410 | &spare0, &dummy, 1); | ||
411 | yaffs_rd_chunk_nand(dev, block_no * dev->param.chunks_per_block + 1, | ||
412 | NULL, &spare1, &dummy, 1); | ||
413 | |||
414 | if (hweight8(spare0.block_status & spare1.block_status) < 7) | ||
415 | *state = YAFFS_BLOCK_STATE_DEAD; | ||
416 | else if (memcmp(&spare_ff, &spare0, sizeof(spare_ff)) == 0) | ||
417 | *state = YAFFS_BLOCK_STATE_EMPTY; | ||
418 | else | ||
419 | *state = YAFFS_BLOCK_STATE_NEEDS_SCANNING; | ||
420 | |||
421 | return YAFFS_OK; | ||
422 | } | ||